Breakout Powershell

PowerShell eignet sich nur für Batch- und und Automatisierungsscripts, sondern auch zur Retro-Spieleentwicklung. In diesem Artikel zeige ich, wie ich einen “vollständigen” Breakout-Klon erstellt habe, der direkt in der PowerShell-Konsole läuft.

Die Herausforderung

Das Ziel war es, das klassische Arcade-Spiel Breakout mit nichts anderem als PowerShell-Skripten nachzubauen. Breakout, ursprünglich 1976 von Atari veröffentlicht, ist ein zeitloser Klassiker: Ein Ball prallt zwischen Wänden hin und her, der Spieler steuert ein Paddle und muss damit Blöcke zerstören.

Technische Umsetzung

ASCII-Grafik im Terminal

Da PowerShell keine nativen Grafikfunktionen besitzt, musste ich kreativ werden. Die Lösung: ASCII-Zeichen als Grafik-Engine verwenden. Der Ball wird als O dargestellt, das Paddle als === und die zerstörbaren Blöcke als ###.

Gameplay-Features

Das fertige Spiel bietet:

  • ✅ “Physik” mit Wandkollisionen
  • ✅ Steuerbare Paddle-Bewegung (A/D-Tasten)
  • ✅ 32 zerstörbare Blöcke in 4 Reihen
  • ✅ Punktesystem und Leben-System
  • ✅ Sieg- und Verlust-Bedingungen

Code-Highlights

Das Herzstück ist die Game-Loop, die in einem while-Loop läuft:

while ($gameRunning) {
    Draw-Game      # Bildschirm zeichnen
    Get-Input      # Tastatureingaben verarbeiten
    Update-Ball    # Ball-Position und Kollisionen aktualisieren
    Start-Sleep -Milliseconds 100  # 10 FPS
}

Die Bildschirm-Ausgabe erfolgt über ASCII-Rahmen mit Unicode-Zeichen:

╔════════════════════════════════════════╗
║                                        ║
║  ### ### ### ### ### ### ### ###       ║
║  ### ### ### ### ### ### ### ###       ║
║                                        ║
║                    O                   ║
║                                        ║
║               ======                   ║
╚════════════════════════════════════════╝

Fazit

Dieses Projekt zeigt eindrucksvoll, dass PowerShell weit mehr kann als nur Scripts für IT-Administration. Mit etwas Kreativität lassen sich sogar vollwertige Spiele entwickeln!

Der vollständige Quellcode umfasst knapp 200 Zeilen und demonstriert verschiedene PowerShell-Konzepte: Arrays, Funktionen, Schleifen, Benutzereingaben und String-Manipulation.

Wer Lust bekommen hat, kann das Spiel direkt ausprobieren - einfach den Code in eine .ps1-Datei speichern und in PowerShell ausführen. Viel Spaß beim Zerstören der Blöcke!

Code

# PowerShell Breakout Clone
# Run this script in PowerShell to play!

# Game configuration
$gameWidth = 40
$gameHeight = 20
$paddleWidth = 6
$brickRows = 4
$bricksPerRow = 8

# Game state
$score = 0
$lives = 3
$gameRunning = $true

# Ball properties
$ballX = [math]::Floor($gameWidth / 2)
$ballY = $gameHeight - 3
$ballDX = 1
$ballDY = -1

# Paddle properties
$paddleX = [math]::Floor(($gameWidth - $paddleWidth) / 2)
$paddleY = $gameHeight - 1

# Initialize bricks (1 = brick exists, 0 = destroyed)
$bricks = @()
for ($row = 0; $row -lt $brickRows; $row++) {
    $brickRow = @()
    for ($col = 0; $col -lt $bricksPerRow; $col++) {
        $brickRow += 1
    }
    $bricks += ,$brickRow
}

function Clear-GameScreen {
    Clear-Host
    [Console]::CursorVisible = $false
}

function Draw-Game {
    # Initialize screen as array of strings
    $screen = @()
    for ($y = 0; $y -lt $gameHeight; $y++) {
        $screen += " " * $gameWidth
    }
    
    # Draw bricks
    for ($row = 0; $row -lt $brickRows; $row++) {
        for ($col = 0; $col -lt $bricksPerRow; $col++) {
            if ($bricks[$row][$col] -eq 1) {
                $brickX = $col * 5 + 1
                $brickY = $row + 2
                if ($brickX -lt $gameWidth -and $brickY -lt $gameHeight -and $brickX -ge 0) {
                    $chars = $screen[$brickY].ToCharArray()
                    $chars[$brickX] = '#'
                    if ($brickX + 1 -lt $gameWidth) { $chars[$brickX + 1] = '#' }
                    if ($brickX + 2 -lt $gameWidth) { $chars[$brickX + 2] = '#' }
                    $screen[$brickY] = -join $chars
                }
            }
        }
    }
    
    # Draw paddle
    if ($paddleY -ge 0 -and $paddleY -lt $gameHeight) {
        $chars = $screen[$paddleY].ToCharArray()
        for ($i = 0; $i -lt $paddleWidth; $i++) {
            if ($paddleX + $i -ge 0 -and $paddleX + $i -lt $gameWidth) {
                $chars[$paddleX + $i] = '='
            }
        }
        $screen[$paddleY] = -join $chars
    }
    
    # Draw ball
    if ($ballX -ge 0 -and $ballX -lt $gameWidth -and $ballY -ge 0 -and $ballY -lt $gameHeight) {
        $chars = $screen[$ballY].ToCharArray()
        $chars[$ballX] = 'O'
        $screen[$ballY] = -join $chars
    }
    
    # Output screen
    Clear-GameScreen
    Write-Host "╔$('═' * $gameWidth)╗"
    for ($y = 0; $y -lt $gameHeight; $y++) {
        Write-Host "║$($screen[$y])║"
    }
    Write-Host "╚$('═' * $gameWidth)╝"
    Write-Host "Score: $score | Lives: $lives | Controls: A/D to move, Q to quit"
}

function Update-Ball {
    # Move ball
    $script:ballX += $script:ballDX
    $script:ballY += $script:ballDY
    
    # Wall collisions
    if ($script:ballX -le 0 -or $script:ballX -ge $gameWidth - 1) {
        $script:ballDX = -$script:ballDX
    }
    if ($script:ballY -le 0) {
        $script:ballDY = -$script:ballDY
    }
    
    # Ball goes below paddle (lose life)
    if ($script:ballY -ge $gameHeight) {
        $script:lives--
        if ($script:lives -le 0) {
            $script:gameRunning = $false
            return
        }
        # Reset ball position
        $script:ballX = [math]::Floor($gameWidth / 2)
        $script:ballY = $gameHeight - 3
        $script:ballDX = 1
        $script:ballDY = -1
        return
    }
    
    # Paddle collision - check if ball is hitting paddle from above
    if ($script:ballY -eq $paddleY -and $script:ballX -ge $paddleX -and $script:ballX -lt $paddleX + $paddleWidth -and $script:ballDY -gt 0) {
        $script:ballDY = -$script:ballDY
        # Add some angle based on where ball hits paddle
        $hitPos = $script:ballX - $paddleX
        if ($hitPos -lt $paddleWidth / 3) {
            $script:ballDX = -1
        } elseif ($hitPos -gt ($paddleWidth * 2 / 3)) {
            $script:ballDX = 1
        }
    }
    
    # Brick collision
    $brickRow = $script:ballY - 2
    $brickCol = [math]::Floor(($script:ballX - 1) / 5)
    
    if ($brickRow -ge 0 -and $brickRow -lt $brickRows -and $brickCol -ge 0 -and $brickCol -lt $bricksPerRow) {
        if ($bricks[$brickRow][$brickCol] -eq 1) {
            $bricks[$brickRow][$brickCol] = 0
            $script:ballDY = -$script:ballDY
            $script:score += 10
            
            # Check if all bricks destroyed
            $bricksLeft = 0
            for ($row = 0; $row -lt $brickRows; $row++) {
                for ($col = 0; $col -lt $bricksPerRow; $col++) {
                    $bricksLeft += $bricks[$row][$col]
                }
            }
            if ($bricksLeft -eq 0) {
                $script:gameRunning = $false
                Write-Host "`nCongratulations! You won! Final Score: $score"
                return
            }
        }
    }
}

function Get-Input {
    if ([Console]::KeyAvailable) {
        $key = [Console]::ReadKey($true)
        switch ($key.Key) {
            'A' { 
                $script:paddleX = [math]::Max(0, $paddleX - 2)
            }
            'D' { 
                $script:paddleX = [math]::Min($gameWidth - $paddleWidth, $paddleX + 2)
            }
            'Q' { 
                $script:gameRunning = $false
            }
        }
    }
}

# Game loop
Write-Host "PowerShell Breakout!"
Write-Host "Use A and D keys to move the paddle"
Write-Host "Press any key to start..."
[Console]::ReadKey() | Out-Null

while ($gameRunning) {
    Draw-Game
    Get-Input
    Update-Ball
    Start-Sleep -Milliseconds 100
}

# Game over
Clear-GameScreen
if ($lives -le 0) {
    Write-Host "Game Over! Final Score: $score"
} else {
    Write-Host "Thanks for playing!"
}
Write-Host "Press any key to exit..."
[Console]::ReadKey() | Out-Null