functions/MiniGameSnake/Start-MiniGameSnake.ps1

function Start-MiniGameSnake {
    <#
    .SYNOPSIS
    Draws a snake game. Use UP,DOWN,LEFT,RIGHT Arrows or WASD for movement.
 
    .DESCRIPTION
    Draws a snake game. Use UP,DOWN,LEFT,RIGHT Arrows or WASD for movement.
 
    .OUTPUTS
    Moderate
    Snacks eaten: 5 Snake: 8
    +------------------------------+
    | |
    | O |
    | O |
    | O |
    | OOOOO@ * |
    | |
    | |
    | |
    | |
    | |
    +------------------------------+
 
 
    NOTE: Requires Powershell 7 or higher
 
 
 
     
    .EXAMPLE
 
     
 
 
    .LINK
     
    #>


    

    [CmdletBinding(
        DefaultParameterSetName = "difficultyLevels"
    )]
    param (
        [Parameter(
            Position = 0,
            Mandatory = $false,
            ParameterSetName = "difficultyLevels"
        )]
        [System.String]
        [ArgumentCompleter(
            {
                return @('Beginner', 'Moderate', 'Difficult', 'Challenge', 'Hardcore', 'Unbeatable')
            }
        )]
        [ValidateScript(
            {
                $_ -in @('Beginner', 'Moderate', 'Difficult', 'Challenge', 'Hardcore', 'Unbeatable')
            },
            ErrorMessage = "Valid diffculites: 'Beginner', 'Moderate', 'Difficult', 'Challenge', 'Hardcore', 'Unbeatable"
        )]
        $Difficulty = "Moderate",

        [Parameter(
            Position = 1,
            Mandatory = $false
        )]
        [switch]
        $Mute,



        [Parameter(
            Position = 2,
            Mandatory = $false
        )]
        [ValidateScript(
            {
                $_ -match '\d+x\d+'
            },
            ErrorMessage = "Must match format '<Widht>x<Height>' e.g. 30x10."
        )]
        [System.String]
        $Dimension = "30x10",

        [Parameter(
            Position = 3,
            Mandatory = $false
        )]
        [switch]
        $MaxDimension,



        # The initial snake length.
        [Parameter(
            Position = 4,
            Mandatory = $false
        )]
        [System.Int32]
        $SnakeLength = 3,



        [Parameter(
            Position = 5,
            ParameterSetName = 'customTicks'
        )]
        [System.Int32]
        [ValidateRange(1, 1000)]
        $CustomTicks
    )

    $GameHeight = [System.Int32]::Parse($Dimension.split('x')[1])
    $GameWidth = [System.Int32]::Parse($Dimension.split('x')[0])
    $WindowHeight = $Host.UI.RawUI.BufferSize.Height
    $WindowWidth = $Host.UI.RawUI.BufferSize.Width

    if ($GameWidth -LT 2) {
        throw [System.Exception]::new('The GameWidth must be at least 2')
    }

    if ($GameHeight -LT 2) {
        throw [System.Exception]::new('The GameHeight must be at least 2')
    }

    # Game Height
    # + 1 empty line above
    # + 2 lines of text above
    # + 2 lines (Upper/Lower Wall)
    # + 2 lines of text below
    $RequiredHeight = $GameHeight + 1 + 2 + 2 + 2

    # Game Width
    # + 2 Walls
    $RequiredWidth = $GameWidth + 2


    if ($MaxDimension.IsPresent) {
        $RequiredHeight -= $GameHeight
        $RequiredWidth -= $GameWidth

        $GameHeight = $WindowHeight - $RequiredHeight - 2
        $GameWidth = $WindowWidth - $RequiredWidth - 2

        $RequiredHeight += $GameHeight
        $RequiredWidth += $GameWidth
    }
    
    if ($WindowWidth -LT $RequiredWidth) {
        throw [System.Exception]::new("Terminal Width must be at least: $RequiredWidth (Currently: $WindowWidth)")
    }
    if ($WindowHeight -LT $RequiredHeight) {
        throw [System.Exception]::new("Terminal Height must be at least: $RequiredHeight (Currently: $WindowHeight)")
    }

    ########################################################
    ###### Setting up the game loop

    $GameOffsetLength = [System.Math]::floor($WindowWidth / 2 - $GameWidth / 2)

    $gameSettings = @{
        MinimumX = $GameOffsetLength
        MinimumY = 0
        MaximumX = $RequiredWidth + $GameOffsetLength
        MaximumY = $RequiredHeight
        Mute     = $Mute.IsPresent
        Load     = $PSScriptRoot
        Vars     = @{
            isWindows          = $IsWindows
            gameHeight         = $gameHeight
            gameWidth          = $gameWidth

            # Initial snake position is the center of the screen.
            snake              = $null
            snakeLength        = [System.Int32]$SnakeLength
            snakeSnack         = $null
            snackedSnakeSnacks = 0

            # Velocity is overwritte to next velocity on every loop.
            # This is to avoid invalid velocities when the user presses the arrows to fast,
            # before the call of the loop function.
            velocity           = [System.Numerics.Vector2]::new(0, 1)
            nextVelocity       = [System.Numerics.Vector2]::new(0, 1)
            collisionMap       = [System.Collections.Hashtable]::new()
        }
    }

    $game = New-MiniGame @gameSettings
    
    $game.Set('difficulty', $game.Get('difficultyMapping')[$Difficulty])
    $game.ticks($game.Get('difficulty').ticks ?? $CustomTicks)

    $game.alias([System.ConsoleKey]::A, [System.ConsoleKey]::LeftArrow)
    $game.alias([System.ConsoleKey]::D, [System.ConsoleKey]::RightArrow)
    $game.alias([System.ConsoleKey]::W, [System.ConsoleKey]::UpArrow)
    $game.alias([System.ConsoleKey]::S, [System.ConsoleKey]::DownArrow)
    $game.OnKey([System.ConsoleKey]::A, { 
            param($key, $var, $func, $canvas, $sound, $object, $stop)
            if ($var.velocity.X -EQ 0) {
                # Only accept if the snake is not moving to the right
                $var.nextVelocity = [System.Numerics.Vector2]::new(-1, 0)
            }
        })
    $game.OnKey([System.ConsoleKey]::D, { 
            param($key, $var, $func, $canvas, $sound, $object, $stop)
            if ($var.velocity.X -EQ 0) {
                # Only accept if the snake is not moving to the left
                $var.nextVelocity = [System.Numerics.Vector2]::new(1, 0)
            }
        })
    $game.OnKey([System.ConsoleKey]::W, { 
            param($key, $var, $func, $canvas, $sound, $object, $stop)
            if ($var.velocity.Y -EQ 0) {
                # Only accept if the snake is not moving down
                $var.nextVelocity = [System.Numerics.Vector2]::new(0, -1)
            }
        })
    $game.OnKey([System.ConsoleKey]::S, { 
            param($key, $var, $func, $canvas, $sound, $object, $stop)
            if ($var.velocity.Y -EQ 0) {
                # Only accept if the snake is not moving up
                $var.nextVelocity = [System.Numerics.Vector2]::new(0, 1)
            }
        })

    $game.Start()
}