Boring Powershell

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

Powershell is typically described as the automation tool for system administrators. And of course, that it has shown little interest. So I want to tell you that it can be used not only for boring work tasks.

image

For the sake of experiment and as the diversity I had the idea to write a small game with the mechanics of scroller. First, I wanted to limit it to one console, but then reason prevailed. So for a graphics engine, it was decided to use elements Windows.Forms:

the
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 "<-- A  D  --> '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 the script is essentially blocked, because after the launch of the dialog — he expects this of the reply window and then fails. Of course, it would be possible to make a multithreaded script, but it was found more simple solution: add a timer.

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

Every 100 milliseconds the timer calls the function Check regardless of what is done in the script itself. The time interval chosen on the eyes. My sense is that the game is updated quite smoothly, but you can make the upgrade more often.

As it turned out later, all the variables listed in the "tick" of the timer to retain the value at the time of activation of the timer and Check each time invoked with the same set of data. So the function has access to current data, all the necessary information has been Packed into the object:

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

To give Start-Scroll finished look, add hotkeys and control controller sound:

the
 $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-265 lt) {$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.The SoundPlayer;
$sound.SoundLocation = "$env:WINDIR\Media\Windows Information Bar.wav"

Total in the game there is a checkbox $Data.run, which means if you start the game, there is a checkbox $Data.hide, which acts as a pause, there is a set of variables, where you store the player's coordinates (pos), the number of points (score), the timer before the shot (shot) and the timer before adding the enemy (spawn), and two arrays of fires and enemies in which are stored respectively in the data on the shells and opponents.

Management is quite simple: A and D to move your character, Esc is to exit, and the space bar replaces the Start button when starting the game or putting it on pause. To pause all the game elements were hidden, use the function Set-Hide:

the
function Set-Hide (){
if ($Data.hide) {
$start.Text = ""
$start.Location=New-Object System.Drawing.Point(90, 200)

$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 described in the function Check:

function Check ()
function Check () {
# If the game is not running - do nothing
if (!$Data.run) {return}
# If pause - derive third-party text
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
}
# Update the player's position
$ship.Location=New-Object System.Drawing.Point($Data.pos, 300)
# Create a projectile if it's time
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 }
# Create an enemy if it's time
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 }
# Check the shells
foreach ($fire in $Data.fires){
# Update the position
$fire.obj.Location = New-Object System.Drawing.Point($fire.x $fire.y)
$fire.y -= 5
# Check for each projectile/enemy - do the collision
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 the first in the list of projectile is out of screen - remove it
if ($Data.fires[0].y-lt -10) {
$form.Controls.Remove($Data.fires[0].obj)
$Data.fires = $Data.fires[1..($Data.fires.Length - 1)]
}
# Check the opponents
foreach ($enemy in $Data.enemies){
# If killed - restart
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
}
# Update the position
$enemy.obj.Location = New-Object System.Drawing.Point($enemy.x, $enemy.y)
# If landed - stop the game
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 not only be used to configure access rights and monitor the work of the local network.

And, as a bonus, pause displays a list of connected USB devices )

PS And those who are too lazy to assemble the code in the article can download the file with the script and the bat-nick to run.
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

ODBC Firebird, Postgresql, executing queries in Powershell

Installation LivestreetCMS on MODX Revolution package 10 clicks

The Ministry of communications wants to ban phones without GLONASS