📜 ⬆️ ⬇️

Bored Powershell

At work, I occasionally have to edit and append scripts for auto tests. And so historically, they were written in Powershell. But the article is not about that.

Powershell is usually described as an automation tool for system administrators. And naturally, there is 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 scrolling shooter. At first I wanted to confine myself to one console, but then my mind prevailed. So for the graphics engine, it was decided to use the elements of Windows.Forms:
')
Add-Type -Assemblyname System.Windows.Forms function Create-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 } function Create-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 } function Create-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 } function Start-Scroll (){ $form = Create-Form "Let's GO!" 200 150 300 400 $start = Create-Label "Press SPACE to run" 90 200 $info = Create-Label "<-- AD --> 'Esc' for exit" 80 340 $ship = Create-Label "/|\" 135 400 $form.Controls.Add($start) $form.Controls.Add($info) $form.Controls.Add($ship) $form.ShowDialog() } 

The result is a “start screen”. But at the same time, the execution of the script was essentially blocked, since after launching the dialog box - it expects a response from this window and does not continue. Of course, it would be possible to make a multi-threaded script, 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 update of the game takes place quite smoothly, but if you wish, you can make the update more often.

As it turned out later, all variables specified in the “tick” of the timer retain the value at the time of activation of the timer 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 the object:

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

In order to give the Start-Scroll function a finished look, it remains to add control hot keys and a sound controller:

  $form.KeyPreview = $True $form.Add_KeyDown({ if ($_.KeyCode -eq "A") {if ($Data.run -and -not $Data.hide -and $Data.pos -gt 0) {$Data.pos -= 5}} }) $form.Add_KeyDown({ if ($_.KeyCode -eq "D") {if ($Data.run -and -not $Data.hide -and $Data.pos -lt 265) {$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" 

In total, there is a $ Data.run flag in the game, which means whether the game is running, there is a $ Data.hide flag that pauses, there is a set of variables where the player’s coordinates (pos) are stored, the points to the shot (shot) and the timer before adding the enemy (spawn), as well as two arrays of fires and enemies, which store data on shells and opponents respectively.

The control turned out to be quite simple: A and D to move your character, Esc - to exit, and the space replaces the “Start” button by starting the game or putting it on pause. To hide all game elements for a while, use the Set-Hide function:

 function Set-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 -22 30 20; 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 -gt 0){ $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 -gt 300) { $Data.run = $false $start.Text = "Total score: " + $Data.score } } } 

Of course, this 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 a 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 the script and the bat-nickname to run.

Source: https://habr.com/ru/post/319872/


All Articles