
Introduction
Does the world really need yet another Falling Blocks game? Probably not. Did Windows need a powerful shell when CMD.exe was the only kid on the block? It most certainly did.
The goal of this article is to show just how powerful the PowerShell scripting language has become. It will lay out the basics of how an application could be written in pure PowerShell script and furthermore the RawUI
functionality of PowerShell is treated in some detail.
This article has no intention of going into details about how to implement the original falling blocks game itself, there are plenty of resources out there doing just that. However, feel free to browse the code to see how I have chosen to implement the old classic.
Background
This project started - like most projects do - by someone saying something they should not have said. This time it was me!
I was trying to explain to a non technical friend of mine that the guys in Redmond had now finally included a powerful shell in Windows. Why I was doing this I cannot recall - but this was clearly mistake number one.
My friend looked unimpressed at the white blinking cursor on the blue background. "Can you play games in it?", he finally said. Mistake number two was not ending the conversation right there. Instead I answered something like "Yes, I should think so", to which he of course immediately responded: "What games?".
Sadly, I discovered that there were no convincing games I could show him. Now why would there be?
Mistake number three was not admitting that Power Shell was never meant for gaming. Instead I set out on a meaningless crusade, and thus Falling Blocks for Power Shell was born.
Running the Game
Fire up PowerShell. Change to the directory containing the downloaded source and run the fallingblocks.ps1 script
>.\fallingblocks.ps1
In the odd case that you are not really the graphics kind of guy, you can play around with the game board in pure integer mode. Just uncomment the last line in the board.ps1 script and run that script from the command line:
>.\board.ps1
This should produce something like the output shown in the figure below.

Saving the State of Affairs
Unlike traditional application development we do not own the host window in PowerShell scripting. The user of our application will expect us to restore all sizes, coloring and buffer contents when he is done playing.
Luckily for us the PSHostRawUserInterface
class provides all the means needed to record the state of the UI before we start messing with it:
function Record-Host-State()
{
$global:hostWindowSize = $Host.UI.RawUI.WindowSize
$global:hostWindowPosition = $Host.UI.RawUI.WindowPosition
$global:hostBufferSize = $Host.UI.RawUI.BufferSize
$global:hostTitle = $Host.UI.RawUI.WindowTitle
$global:hostBackground = $Host.UI.RawUI.BackgroundColor
$global:hostForeground = $Host.UI.RawUI.ForegroundColor
$global:hostCursorSize = $Host.UI.RawUI.CursorSize
$global:hostCursorPosition = $Host.UI.RawUI.CursorPosition
#Store the full buffer
$rectClass = "System.Management.Automation.Host.Rectangle"
$bufferRect = new-object $rectClass 0, 0, $global:hostBufferSize.width,
$global:hostBufferSize.height
$global:hostBuffer = $Host.UI.RawUI.GetBufferContents($bufferRect)
}
Given the recorded information we can then restore the previous state later as shown in the next function:
function Recover-Host-State()
{
$Host.UI.RawUI.CursorSize = $global:hostCursorSize
$Host.UI.RawUI.BufferSize = $global:hostBufferSize
$Host.UI.RawUI.WindowSize = $global:hostWindowSize
$Host.UI.RawUI.WindowTitle = $global:hostTitle
$Host.UI.RawUI.BackgroundColor = $global:hostBackground
$Host.UI.RawUI.ForegroundColor = $global:hostForeground
$pos = $Host.UI.RawUI.WindowPosition
$pos.x = 0
$pos.y = 0
#First restore the contents of the buffer and then reposition the cursor
$Host.UI.RawUI.SetBufferContents($pos, $global:hostBuffer)
$Host.UI.RawUI.CursorPosition = $global:hostCursorPosition
}
The Main function in the application script can then take the form:
function Main {
#Store the current state of the PS Window
Record-Host-State
try {
...
} finally {
#When done we make sure to return the shell
#to the same state we started in
Restore-Host-State
}
}
#Run the main function
. Main
The host state is recorded as the first thing and the remaining execution is placed within a try
block which is associated with a finally
block restoring the state of the host.
A Simple Event Loop
When dealing with user input in PowerShell ReadKey
will suffice in most cases. However, in a gaming context the blocking nature of ReadKey
will pose a problem as it will cause the application to pause and wait for input. Fortunately there is a property called KeyAvailable
which can be checked prior to calling ReadKey
. We introduce a function for reading characters of the keyboard which will return 0 if no character is available or if it is a meta character being pressed.
function Read-Character()
{
if ($host.ui.RawUI.KeyAvailable) {
return $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp").Character
}
return 0
}
With this new Read-Character
function in our toolbox it is easy to sketch up a simple event loop which can handle the state of things. The loop below will continue until the user hits the q character on the keyboard.
[boolean]$quit = $FALSE
#
# Go into the control loop
#
do {
if ($repaint) {
...
}
if (Is-Time-To-Move) {
...
}
$character = Read-Character
# make sure we have a character
# so it is not just a meta key
if ($character -ne 0) {
switch -regex ($character) {
# Quit
"q" {
$quit = $TRUE
}
}
}
} while (-not $quit)
To Paint or not to Paint
Eventhough we do have a repaint flag in the control loop shown previously it is not like we are drawing any pixels, this is taken care of by the PowerShell host window. The UI model of PowerShell is limited to a two-dimensional character cell grid which we can alter as we see fit. Any changes made to the grid is reflected by the host window.
All UI in the implemented game is "drawn" as strings using the function shown below.
function Draw-String([int] $x, [int] $y, [string] $fgColor, [string] $bgColor,
[string] $str)
{
$pos = $Host.UI.RawUI.WindowPosition
$pos.x = $x
$pos.y = $y
$row = $Host.UI.RawUI.NewBufferCellArray($str, $fgColor, $bgColor)
$Host.UI.RawUI.SetBufferContents($pos,$row)
}
Exactly the same thing can be achieved using Write-Host
if that is your preferred output method:
function Draw-String([int] $x, [int] $y, [string] $fgColor, [string] $bgColor,
[string] $str)
{
$cursor = $Host.UI.RawUI.CursorPosition
$cursor.x = $x
$cursor.y = $y
$Host.UI.RawUI.CursorPosition = $cursor #reposition cursor
Write-Host -NoNewline -BackgroundColor $bgColor -ForegroundColor $fgColor $str
}
To make updates to the screen as effective as possible we only update cells where changes have occurred. When for example a piece is locked on the game board we compare the new state of the board with the previous state and update the screen buffer accordingly:
function Update-Board([array] $oldBoard, [array] $newBoard)
{
#Check for differences - line by line
for ($y = 0; $y -lt $global:BOARD_HEIGHT_IN_SQUARES; $y++) {
for ($x = 0; $x -lt $global:BOARD_WIDTH_IN_SQUARES; $x++) {
#If the values are different we need to take action
if ($oldBoard[$x][$y] -ne $newBoard[$x][$y]) {
if ($newBoard[$x][$y] -eq 0) {
Erase-Square $x $y
} else {
Draw-Piece-Square $x $y $newBoard[$x][$y]
}
}
}
}
}
Drawing the squares as strings requires some of that old fashioned ascii magic. We settled with an approximation made up of four characters on three lines:
function Draw-Square-With-Offset([int]$offsetX, [int]$offsetY, [int]$hIndex,
[int]$vIndex, [string]$fgColor, [string]$bgColor)
{
$x = $offsetX + $hIndex*$global:SQUARE_WIDTH
$y = $offsetY + $vIndex*$global:SQUARE_HEIGHT
#Draw the three lines
Draw-String $x $y $fgColor $bgColor "┌──┐"
Draw-String $x ($y+1) $fgColor $bgColor "│ │"
Draw-String $x ($y+2) $fgColor $bgColor "└──┘"
}
Initializing a Jagged Array
When implementing the board functionality it was not easy finding any good resources on how to create and initialize a jagged array in PowerShell. After playing around a bit I came up with the construct shown below. The ForEach
operator is used to create a column of zeros, this column is then placed in yet another array by value using an additional $.
function Initialize-Board([int] $width=10, [int] $height=18) {
$global:boardWidth = $width
$global:boardHeight = $height
#Note % is an alias for ForEach-Object
$column = 1..$global:boardHeight | % { 0 }
#The extra $ is there to make sure it is not a
#bunch of references to the same column
$global:board = 1..$global:boardWidth | % { ,$($column) }
$global:lineCount = 0
}
Pacman Anyone?
That was all for me. Download the source, give the game a try and let me know what you think. And now that we are at it why not try out gaming for PowerShell yourself? I will be back next week to read your article about that crazy cheese Mr. Pacman now living in the land of PowerShell - and then there will be even more I can show my gaming friend.
Troubleshooting
If you are having trouble executing the script make sure you have not specified restricted
execution policy:
>Set-ExecutionPolicy RemoteSigned
History
August 2011: initial version