As a network administrator, PowerShell is just one of those tools that, once you use it, you can never imagine doing a lot of admin tasks without it.
Interested in Powershell games? Check out my new Powershell Match Three game
Given it’s similar structure to other languages, I picked up on PowerShell pretty quickly, and while doodling around with it decided to try using it for something for which it was really never intended. After all PowerShell is awesome for automating data loading, updating Active Directory attributes, generating reports or extracting data to build a dynamic HTML snippet to be served up by a web page, but there are GAMES to be considered!
So here is my PowerShell game. The classic “Snake” computer game, allowing you to use the arrow keys on the keyboard to hunt down and eat apples (stunningly represented by a red @ sign – I know, the graphics are impressive!)
Below is the (fairly heavily commented) result (along with a .ZIP file to download the script at the bottom of the page in case copying and pasting from a website isn’t for you – and does that ever work right?)
The script utilizes PowerShell’s RawUI functionality to position the cursor to write individual characters. Each segment of the snake’s body is stored in an array, but when the snake moves forward, the only “drawing” that takes place is to remove the last block of the tail and draw the new head block.
In most games, you would clear and redraw the entire screen after every frame, but given the interpreted nature of PowerShell, it just isn’t fast enough to do that. Even this method is pushing it once the snake grows a bit. Things get slower over time as more blocks need to be checked to determine if the snake has run into itself. The code here could be improved upon – for example, it should be possible to determine a general slowdown rate as the snake grows and adjust the sleep near the end of the script (that delays between each frame) to eliminate at least some of the lag. Since this was a project for an hour of free time, I didn’t go that far with it.
Update: See the comments for Dave Wyatt’s update that changes the way the tail is checked for his and removes the slowdown completely 🙂
I do have a couple more ideas for games that could be written in PowerShell when considering the speed restrictions, so we’ll just have to see if they every make it into .PS1 files. In the mean time, enjoy chasing the apple…
#requires -version 2 # # Powershell Snake Game # Author : Kurt Jaegers # # # Draws the snake to the screen, including cleaning up the last segment of the tail # function DrawTheSnake { # Erase the tail segment that is disappearing $rui.cursorposition = $tail[0] write-host -foregroundcolor white -backgroundcolor black -NoNewline " " # Shift all of the tail segments down one for ($i=0; $i -lt ($tailLength - 1); $i++) { $tail[$i].x = $tail[$i+1].x $tail[$i].y = $tail[$i+1].y } # Set the last segment of the tail to the current position $tail[-1].x = $coord.x $tail[-1].y = $coord.y # Draw all segments of the snake for ($i=0; $i -lt $tailLength; $i++) { $rui.cursorposition = $tail[$i] write-host -foregroundcolor white -backgroundcolor white -NoNewline " " } } # # Generate a random location for the apple, making sure it isnt inside the snake # function MoveTheApple { $ok = $true; do { $script:apple.x = get-random -min 2 -max ($rui.WindowSize.width - 2) $script:apple.y = get-random -min 2 -max ($rui.WindowSize.height - 2) $ok=$true for ($i=0; $i -lt $tailLength; $i++) { if (($tail[$i].x -eq $apple.x) -and ($tail[$i].y -eq $apple.y)) { $ok=$false; } } } While (!$ok) } # # Draw the apple to the screen # function DrawTheApple { $rui.CursorPosition = $apple write-host -foregroundcolor red -backgroundcolor black "@" } # # Check to see if the snake hits the apple # function CheckAppleHit { # if the x/y of the head matches the x/y of the apple, we hit the apple if (($tail[-1].x -eq $apple.x) -and ($tail[-1].y -eq $apple.y)) { # relocate the apple MoveTheApple $score += 500 # Add to the snake's length $script:tailLength++ $script:tail += new-object System.Management.Automation.Host.Coordinates $script:tail[-1].x = $coord.x $script:tail[-1].y = $coord.y } } # # Check to see if the snake's head hits the walls of the screen # function CheckWallHits { if (($coord.x -eq 0) -or ($coord.y -eq 0) -or ($coord.x -eq $host.ui.rawui.windowsize.width-1) -or ($coord.y -eq $host.ui.rawui.windowsize.height-1)) { cls write-host -foregroundcolor red "You lost! Score was $score" exit } } # # Draw a fence around the edges of the screen # function DrawScreenBorders { $cur = new-object System.Management.Automation.Host.Coordinates $cur.x=0 $cur.y=0 for ($x=0; $x -lt $host.ui.rawui.windowsize.width; $x++) { $cur.x=$x $cur.y=0 $host.ui.rawui.cursorposition = $cur write-host -foregroundcolor black -backgroundcolor white -nonewline "#" $cur.y=$host.ui.rawui.windowsize.height-1 $host.ui.rawui.cursorposition = $cur write-host -foregroundcolor black -backgroundcolor white -nonewline "#" } for ($y=0; $y -lt $host.ui.rawui.windowsize.height-1; $y++) { $cur.y=$y $cur.x=0 $host.ui.rawui.cursorposition = $cur write-host -foregroundcolor black -backgroundcolor white -nonewline "#" $cur.x=$host.ui.rawui.windowsize.width-1 $host.ui.rawui.cursorposition = $cur write-host -foregroundcolor black -backgroundcolor white -nonewline "#" } } function CheckSnakeBodyHits { for ($i=0; $i -lt $tailLength -1; $i++) { if (($tail[$i].x -eq $coord.x) -and ($tail[$i].y -eq $coord.y)) { cls write-host -foregroundcolor red "You lost! Score was $score" exit } } } # --------------------------------- # --------------------------------- # Main script block starts here # --------------------------------- # --------------------------------- # Grab UI objects and set some colors $ui=(get-host).ui $rui=$ui.rawui $rui.BackgroundColor="Black" $rui.ForegroundColor="Red" cls # write out lines to make sure the buffer is big enough to cover the screen for ($i=0; $i -lt $rui.screensize.height; $i++) { write-host "" } $coord = $rui.CursorPosition $save = $coord $cs = $rui.cursorsize $rui.cursorsize=0 $score = 0 $done = $false $before = 0 $after = 15 $dir = 0 $coord.X = $rui.screensize.width/2 $coord.y = $rui.screensize.height/2 $coord.x = 80 $coord.Y = 15 $apple = new-object System.Management.Automation.Host.Coordinates DrawScreenBorders; MoveTheApple; $tail = @() $tailLength = 5 for ($i=0; $i -lt $tailLength; $i++) { $tail += new-object System.Management.Automation.Host.Coordinates $tail[$i].x = $coord.x $tail[$i].y = $coord.y } while (!$done) { if ($rui.KeyAvailable) { $key = $rui.ReadKey() if ($key.virtualkeycode -eq -27) { $done=$true } if ($key.keydown) { # Left if ($key.virtualkeycode -eq 37) { $dir=0 } # Up if ($key.virtualkeycode -eq 38) { $dir=1 } # Right if ($key.virtualkeycode -eq 39) { $dir=2 } # Down if ($key.virtualkeycode -eq 40) { $dir=3 } } } if ($dir -eq 0) { $coord.x--; } if ($dir -eq 1) { $coord.y--; } if ($dir -eq 2) { $coord.x++; } if ($dir -eq 3) { $coord.y++; } DrawTheApple; DrawTheSnake; CheckWallHits; CheckSnakeBodyHits; CheckAppleHit; start-sleep -mil 100 $score += $tailLength; } $rui.cursorsize=$cs
Download Snake.zip – PowerShell Snake Game
this script doesn’t work in the Powershell ISE, and also permanently changes foreground and background colors of the console pane in the ISE, which is an unpleasant surprise. might want to check $host.name first, and/or capture the original values and reset them on exit.
I updated the script to only run in the Console window and give you a message about not running it in the ISE 🙂
Neat. 🙂 The reason for the performance drag were the many loops over your $tail array inside the main event loop. As the size of the tail goes up, these operations take longer and longer. I decided to fiddle with this to improve the performance; the new version can be seen at https://gist.github.com/dlwyatt/be5a20ee80880c9f597a .
In this version, the script maintains a 2-dimensional array to track which positions are walls, snake body, apple, or empty. Every one of the “check the body” loops has been replaced with a simple lookup into that matrix, which makes each time through the main event loop take pretty much constant time, regardless of the length of the snake.
Hmmm… that’s a much better way to check for tail hits 🙂
Nice! 🙂
I’ve wrapped-up a snake game in PS few weeks ago as well, check my implementation if you’re interested: http://traal.eu/wp/snake-in-powershell-console-buffer/
BTW, I’ve noticed that your script isn’t 100% fool-proof, it behaves weird if the console window initial size is something non-standard: http://prntscr.com/9wzkto
It is probably best to set window size at the start of the script to w known dimension.
Cheers!
Am I too late to this party? My rendition of Snake is here: https://github.com/CAKEbuilder/Snake.
I’m excited to dive into the other examples from the comments. I had a great time with this project. This started out as a monstrosity that slowly got refined further and further.
add:
$keyopt = [System.Management.Automation.Host.ReadKeyOptions]”NoEcho,IncludeKeyDown,IncludeKeyUp”
then modify:
$key = $rui.ReadKey($keyopt)
and animation is better 😉
Doesnt work unfortunately in ISE. I created a simple russian roulette game if anyones interested in checking it out. Heres the source code
Running in the ISE is disable intentionally because if the issues it can cause with your session.