PowerShell Snake: A Small Game, A Weird Idea, and a Good Time

It’s still summer and I still don’t have classes.

This is how trouble starts.

Some people relax. Some people travel. Some people touch grass.

I opened PowerShell and thought, “Can I build Snake in this?”

That was not a normal question. But it was a productive one, which is how questionable decisions survive review.

The answer was yes. PowerShell can run Snake.

Should PowerShell run Snake?

That is between me, the terminal, and whatever part of my brain thought this was a good use of free time.

The splash screen. Because apparently this tiny console game needed branding.

The Setup

PowerShell usually does respectable work.

It moves files.
It checks services.
It runs reports.
It automates repetitive tasks.
It quietly saves people from clicking the same thing 400 times.

So naturally, I used it to make a snake crawl across the screen.

This started as a simple console game. Then, like every “quick little script,” it grew legs. Or in this case, no legs, because snake.

The final version includes a bigger play area, centered layout, animated splash screen, score tracking, food generation, wall collision, self-collision, a game-over screen, and a play-again option.

Basically, it became a small application with a release history.

Which is ridiculous.

And also kind of great.

Game Settings

The board size and speed are controlled near the top of the script.

$Width = 70
$Height = 25

# Lower number = faster snake
$DelayMs = 75

The wider board gives the snake room to move.

Not wisdom. Just room.

The delay controls the speed. Lower delay means faster snake. Faster snake means faster bad decisions. This is also how some projects are managed, but that is another blog post.

Centering the Screen

I did not want the game awkwardly sitting in the upper-left corner like a forgotten log file.

So I added functions to center content in the console.

function Get-CenteredX {
    param(
        [int]$ContentWidth
    )

    return [Math]::Max(0, [int][Math]::Floor(([Console]::WindowWidth - $ContentWidth) / 2))
}

Then I used that to write centered text.

function Write-CenteredText {
    param(
        [string]$Text,
        [int]$Y
    )

    $x = Get-CenteredX -ContentWidth $Text.Length

    [Console]::SetCursorPosition($x, $Y)
    Write-Host $Text -NoNewline
}

This made the splash screen, board, score line, and game-over screen look intentional.

That matters.

In IT, “intentional” is often the difference between “nice work” and “why is this in production?”

Animated Splash Screen

The splash screen prints text one character at a time.

Was this necessary?

Absolutely not.

Did I add it anyway?

Obviously.

function Write-CenteredAnimatedText {
    param(
        [string]$Text,
        [int]$Y,
        [int]$DelayMs = 25
    )

    $x = Get-CenteredX -ContentWidth $Text.Length
    [Console]::SetCursorPosition($x, $Y)

    foreach ($char in $Text.ToCharArray()) {
        Write-Host $char -NoNewline
        Start-Sleep -Milliseconds $DelayMs
    }
}

This gives the intro some personality.

Not much. It is still PowerShell.

But it is enough to make the terminal look like it briefly developed ambition.

The splash screen text is stored as an array:

$splashLines = @(
    "############################################"
    New-BoxLine -Text ""
    New-BoxLine -Text "POWERSHELL SNAKE"
    New-BoxLine -Text ""
    New-BoxLine -Text "Teo Espero"
    New-BoxLine -Text ""
    "############################################"
    ""
    "Eat the food (*) and avoid the walls."
    "Use Arrow Keys or WASD to move."
    "Press Q anytime to quit."
)

I also learned, or was reminded by force, that PowerShell arrays can be picky when mixing strings and function calls.

One misplaced comma and PowerShell acted like I had insulted its family.

Fair enough.

Making the Snake

The snake is made of points.

Each point has an X and Y coordinate.

function New-Point {
    param(
        [int]$X,
        [int]$Y
    )

    [PSCustomObject]@{
        X = $X
        Y = $Y
    }
}

The snake starts near the center of the board.

$startX = [int][Math]::Floor($Width / 2)
$startY = [int][Math]::Floor($Height / 2)

$snake = @(
    New-Point -X $startX -Y $startY
    New-Point -X ($startX - 1) -Y $startY
    New-Point -X ($startX - 2) -Y $startY
)

The first item is the head.

Everything else is the body.

That is the whole anatomy lesson. No lab fee required.

The main game board. Simple, centered, and somehow more entertaining than it should be.

Drawing the Board

The board is drawn with # characters.

Very advanced graphics.

Please hold your applause.

function Draw-Border {
    param(
        [int]$Width,
        [int]$Height
    )

    [Console]::SetCursorPosition($script:GameOffsetX, $script:GameOffsetY)
    Write-Host ("#" * ($Width + 2)) -NoNewline

    for ($y = 1; $y -le $Height; $y++) {
        [Console]::SetCursorPosition($script:GameOffsetX, $script:GameOffsetY + $y)
        Write-Host "#" -NoNewline

        [Console]::SetCursorPosition($script:GameOffsetX + $Width + 1, $script:GameOffsetY + $y)
        Write-Host "#" -NoNewline
    }

    [Console]::SetCursorPosition($script:GameOffsetX, $script:GameOffsetY + $Height + 1)
    Write-Host ("#" * ($Width + 2)) -NoNewline
}

No sprites.
No engine.
No fancy rendering.
Just a console window doing its best.

Honestly, same.

Reading Keyboard Input

The game reads input while the snake keeps moving.

while ([Console]::KeyAvailable) {
    $key = [Console]::ReadKey($true).Key

    switch ($key) {
        "UpArrow" {
            if ($nextDirection -ne "Down") {
                $nextDirection = "Up"
            }
        }

        "W" {
            if ($nextDirection -ne "Down") {
                $nextDirection = "Up"
            }
        }

        "Q" {
            $gameOver = $true
        }
    }
}

Arrow keys work.

WASD works.

Q quits, because sometimes the most mature thing you can do is leave.

The script also prevents the snake from instantly reversing into itself. That is generous. The snake will find other ways to fail.

Moving the Snake

Movement starts with the current head position.

$head = $snake[0]

$newX = $head.X
$newY = $head.Y

switch ($direction) {
    "Up" {
        $newY--
    }

    "Down" {
        $newY++
    }

    "Left" {
        $newX--
    }

    "Right" {
        $newX++
    }
}

Then a new head gets added to the front of the snake.

$newHead = New-Point -X $newX -Y $newY
$oldTail = $snake[$snake.Count - 1]

$snake = @($newHead) + $snake

If the snake eats food, it grows.

If not, the tail is removed.

if ($willEat) {
    $score += 10

    $food = New-Food -Snake $snake -Width $Width -Height $Height

    Draw-Point -X $food.X -Y $food.Y -Char "*"
    Draw-Score -Score $score
}
else {
    $snake = $snake[0..($snake.Count - 2)]
    Draw-Point -X $oldTail.X -Y $oldTail.Y -Char " "
}

That is the whole trick.

Add a head.
Maybe remove a tail.
Repeat until the user makes a bad turn and blames the keyboard.

Collision Checks

The wall collision check is direct.

if ($newX -lt 1 -or $newX -gt $Width -or $newY -lt 1 -or $newY -gt $Height) {
    $gameOver = $true
    break
}

If the snake leaves the board, the game ends.

No pop-up.

No warning.

No “Are you sure?”

Just consequences.

The self-collision check is also simple.

if (Test-SnakeContains -Snake $bodyToCheck -X $newX -Y $newY) {
    $gameOver = $true
    break
}

The snake hits itself and loses.

A painfully accurate metaphor for many technical problems.

Game Over

When the game ends, the player gets a centered game-over screen.

$gameOverLines = @(
    "############################################"
    New-BoxLine -Text ""
    New-BoxLine -Text "GAME OVER"
    New-BoxLine -Text ""
    New-BoxLine -Text "Final Score: $Score"
    New-BoxLine -Text ""
    "############################################"
    ""
    "Press P to play again."
    "Press E or Q to exit."
)

Then the script waits for the player to choose.

switch ($key) {
    "P" {
        return "PlayAgain"
    }

    "E" {
        return "Exit"
    }

    "Q" {
        return "Exit"
    }
}

Play again or exit.

Usually play again, because leaving after a score of zero feels legally questionable.

The game-over screen, where confidence goes to fill out an incident report.

What This Covered

For a silly little console game, this touched a decent amount of practical scripting logic:

  • Functions
  • Arrays
  • Loops
  • Keyboard input
  • Console positioning
  • Random food placement
  • Score tracking
  • Collision checks
  • Game state
  • Simple animation

That is a lot of mileage from a snake made out of letters.

Closing

This project started because I had time, PowerShell, and apparently not enough adult supervision.

But it was useful.

It made me think through movement, state, layout, input, timing, and error handling in a way that was easy to see.

That is the nice thing about small projects. They do not need a committee, a kickoff meeting, a steering group, or a 47-page implementation plan.

They just need a weird idea and enough stubbornness to make it work.

And in this case, the weird idea had a snake.

Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *