Boring Powershell

At work, I periodically have to edit and add scripts for auto-tests. And so historically, they were written in Powershell. But the article will not be about that.

Typically, Powershell is described as an automation tool for system administrators. And naturally, they show little interest in him. Therefore, I want to tell you that it can be used not only for boring work tasks.

image

For the sake of experiment and as a variety, I had the idea to write a small game with the mechanics of a scroll shooter. At first, I wanted to limit myself to one console, but then the mind prevailed. So for the graphics engine, it was decided to use Windows.Forms elements:

Add-Type -Assemblyname System.Windows.Forms
functionCreate-Form([string]$name, $x, $y, $w, $h){
    $win                     = New-Object System.Windows.Forms.Form
    $win.StartPosition  = "Manual"
    $win.Location         = New-Object System.Drawing.Size($x, $y)
    $win.Width            = $w
    $win.Height           = $h
    $win.Text              = $name
    $win.Topmost        = $True
    $win
}
functionCreate-Label([string]$name, $x, $y){
    $label                  = New-Object System.Windows.Forms.Label
    $label.Location     = New-Object System.Drawing.Point($x, $y)
    $label.Text           = $name
    $label.AutoSize     = $true
    $label
}
functionCreate-Button([string]$name, $x, $y, $w, $h){
    $button                = New-Object System.Windows.Forms.Button
    $button.Location   = New-Object System.Drawing.Point($x, $y)
    $button.Size         = New-Object System.Drawing.Size($w, $h)
    $button.Text         = $name
    $button.Enabled    = $false
    $button
}
functionStart-Scroll(){
    $form  = Create-Form "Let's GO!"200150300400
    $start  = Create-Label "Press SPACE to run"90200
    $info    = Create-Label "<-- A   D -->    'Esc' for exit"80340
    $ship   = Create-Label "/|\" 135 400
    $form.Controls.Add($start)
    $form.Controls.Add($info)
    $form.Controls.Add($ship)
    $form.ShowDialog()
}

As a result, a “start screen” appeared. But at the same time, the execution of the script was essentially blocked, because after launching the dialog box - it expects a response from this window and is not executed further. Of course, a multi-threaded script could be made, but a simpler solution to the problem was found: adding a timer.

    $timer = New-Object system.windows.forms.timer
    $timer.Interval = 100
    $timer.add_tick({Check})
    $timer.start()

Every 100 milliseconds, the timer calls the Check function, regardless of what is executed in the script itself. The time interval is selected by eye. According to my feelings, the game is updated quite smoothly, but if you wish, you can do the update more often.

As it turned out later, all the variables indicated in the “tick” of the timer retain the value at the time the timer was activated and Check is called each time with the same data set. Therefore, in order for the function to have access to relevant data, all the necessary information was packed into an object:

    $Data = @{run = $false; hide = $false; pos = 135; shot = 0; spawn = 0; usb = 0; score = 0; fires = @(); enemies = @()}

To give the Start-Scroll function a finished look, it remains to add hotkey controls and a sound controller:

    $form.KeyPreview = $True
    $form.Add_KeyDown({
        if ($_.KeyCode -eq "A") {if ($Data.run -and -not $Data.hide -and $Data.pos -gt0) {$Data.pos -= 5}}
    })
    $form.Add_KeyDown({
        if ($_.KeyCode -eq "D") {if ($Data.run -and -not $Data.hide -and $Data.pos -lt265) {$Data.pos += 5}}
    })    
    $form.Add_KeyDown({
        if ($_.KeyCode -eq "Escape") {$timer.stop(); $form.Close()}
    })    
    $form.Add_KeyDown({
        if ($_.KeyCode -eq "Space") {
            if ($Data.run) { Set-Hide }
            else { $start.Text = ""; $Data.run = $true }
        }
    })
    $sound = new-Object System.Media.SoundPlayer;
    $sound.SoundLocation = "$env:WINDIR\Media\Windows Information Bar.wav"

Total in the game there is a flag $ Data.run, which indicates whether the game is running, there is a flag $ Data.hide, which acts as a pause, there is a set of variables where the player’s coordinates (pos), the number of points (score), the timer before the shot is stored (shot) and a timer before adding an enemy (spawn), as well as two arrays of fires and enemies, which store data on projectiles and opponents, respectively.

The controls turned out to be quite simple: A and D to move your character, Esc to exit, and the space bar replaces the “Start” button, launching the game or pausing it. In order to hide all game elements during the pause, the Set-Hide function is used:

functionSet-Hide(){
    if ($Data.hide) {
        $start.Text = ""
        $start.Location=New-Object System.Drawing.Point(90, 200)
        $Data.enemies | foreach {$_.obj.Visible = $true}
        $Data.fires | foreach {$_.obj.Visible = $true}
        $info.Visible = $true
        $ship.Visible = $true
    } else {
        $start.Location=New-Object System.Drawing.Point(10, 10)
        $Data.enemies | foreach {$_.obj.Visible = $false}
        $Data.fires | foreach {$_.obj.Visible = $false}
        $info.Visible = $false
        $ship.Visible = $false
    }
    $Data.hide = -not $Data.hide
}

The basic logic of the game is described in the Check function:

function Check ()
function Check () {
    # Если игра не запущена - ничего не делаемif (!$Data.run) {return}# Если пауза - выводим сторонний текстif ($Data.hide) {
        if ($Data.usb -eq 0){
            $start.Text = ""
            gwmi Win32_USBControllerDevice | %{[wmi]($_.Dependent)} | where {$_.DeviceID -notlike '*ROOT_HUB*'} | Sort Description | foreach { $start.Text += $_.Description +"`n" }
            $Data.usb = 500
        } else { $Data.usb -= 1 }
        return
    }
    # Обновляем положение игрока
    $ship.Location=New-Object System.Drawing.Point($Data.pos, 300)
    # Создаем снаряд, если пришло времяif ($Data.shot -eq 0) {
        $Data.fires += @{ obj = Create-Label "*" ($Data.pos + 5) 290; x = $Data.pos + 5; y = 290 }
        $form.Controls.Add($Data.fires[$Data.fires.Length - 1].obj)
        $Data.shot = 4
    } else { $Data.shot -= 1 }
    # Создаем противника, если пришло времяif ($Data.spawn -eq 0) {
        $hp  = Get-Random -minimum 4 -maximum 6
        $pos = Get-Random -minimum 0 -maximum 200
        $Data.enemies += @{ obj = Create-Button "$hp" $pos -223020; x = $pos; y = -22; health = $hp }
        $form.Controls.Add($Data.enemies[$Data.enemies.Length - 1].obj)
        $Data.spawn = 150 * $Data.enemies.Length
    } else { $Data.spawn -= 1 }
    # Проверяем снарядыforeach ($fire in $Data.fires){
        # Обновляем положение
        $fire.obj.Location = New-Object System.Drawing.Point($fire.x, $fire.y)
        $fire.y -= 5# Проверяем для каждого снаряда/противника - нет ли столкновенияforeach ($enemy in $Data.enemies){
            if ($fire.x + 5 -gt $enemy.x -and $fire.x -lt $enemy.x + 25 -and $fire.y -gt $enemy.y -and $fire.y -lt $enemy.y + 20){
                $enemy.health -= 1
                $enemy.obj.Text = $enemy.health
                $fire.y = -20
                $sound.Play()
            }
        }
    }
    # Если первый в списке снаряд вышел за экран - убираем егоif ($Data.fires[0].y -lt -10) {
        $form.Controls.Remove($Data.fires[0].obj)
        $Data.fires = $Data.fires[1..($Data.fires.Length - 1)]
    }
    # Проверяем противниковforeach ($enemy in $Data.enemies){
        # Если убит - перезапускаемif ($enemy.health -gt0){ $enemy.y += 1    } else {
            $Data.score += 1
            $enemy.health = Get-Random -minimum 4 -maximum 6
            $enemy.x = Get-Random -minimum 1 -maximum 200
            $enemy.y = -22
            $enemy.obj.Text = $enemy.health
        }
        # Обновляем положение
        $enemy.obj.Location = New-Object System.Drawing.Point($enemy.x, $enemy.y)
        # Если приземлился - останавливаем игруif ($enemy.y -gt300) {
            $Data.run = $false
            $start.Text = "Total score: " + $Data.score
        }
    }
}

Of course, such a game does not claim to be “The Best Game of the Year”. But it can show that Powershell can be used not only to configure access rights and control the operation of the local network.

And also, as a bonus, in the pause mode, a list of connected USB devices is displayed)

PS And those who are too lazy to collect the code for the article can download the archive with a script and a bat-nickname to run.

Also popular now: