PowerShell/docs/play-chess.md
2024-11-20 11:52:20 +01:00

48 KiB

The play-chess.ps1 Script

Started off of code from https://github.com/bhassen99/POSH-Chess, which was very much incomplete. I kept the board shape, but have changed everything else. The unicode chess pieces unfortunately do not render in the base PowerShell console, they only appear when run in PowerShell ISE.

Parameters

/home/markus/Repos/PowerShell/scripts/play-chess.ps1 [<CommonParameters>]

[<CommonParameters>]
    This script supports the common parameters: Verbose, Debug, ErrorAction, ErrorVariable, WarningAction, 
    WarningVariable, OutBuffer, PipelineVariable, and OutVariable.

Notes

Name: Chess.ps1 Version: 0.3.1 Author: Michael Shen Date: 10-19-2017

Script Content

<#
.SYNOPSIS
    Local multiplayer chess game in PowerShell.

.DESCRIPTION
    Started off of code from https://github.com/bhassen99/POSH-Chess, which was very
much incomplete. I kept the board shape, but have changed everything else.
The unicode chess pieces unfortunately do not render in the base PowerShell console, 
they only appear when run in PowerShell ISE.

.NOTES
    Name: Chess.ps1
    Version: 0.3.1
    Author: Michael Shen
    Date: 10-19-2017
#>

#Update-Board must be run before Publish-Board
#Draws the board to the screen and displays all the icons
Function Publish-Board {
    Clear
    Write-Host "`n`n"
    Write-Host '     A   B   C   D   E   F   G   H'
    Write-Host '   --------------------------------- '
    Write-Host ' 8 |'$board[0,7].Icon'|'$board[1,7].Icon'|'$board[2,7].Icon'|'$board[3,7].Icon'|'$board[4,7].Icon'|'$board[5,7].Icon'|'$board[6,7].Icon'|'$board[7,7].Icon'| 8'
    Write-Host '   --------------------------------- '
    Write-Host ' 7 |'$board[0,6].Icon'|'$board[1,6].Icon'|'$board[2,6].Icon'|'$board[3,6].Icon'|'$board[4,6].Icon'|'$board[5,6].Icon'|'$board[6,6].Icon'|'$board[7,6].Icon'| 7'
    Write-Host '   --------------------------------- '
    Write-Host ' 6 |'$board[0,5].Icon'|'$board[1,5].Icon'|'$board[2,5].Icon'|'$board[3,5].Icon'|'$board[4,5].Icon'|'$board[5,5].Icon'|'$board[6,5].Icon'|'$board[7,5].Icon'| 6'
    Write-Host '   --------------------------------- '
    Write-Host ' 5 |'$board[0,4].Icon'|'$board[1,4].Icon'|'$board[2,4].Icon'|'$board[3,4].Icon'|'$board[4,4].Icon'|'$board[5,4].Icon'|'$board[6,4].Icon'|'$board[7,4].Icon'| 5'
    Write-Host '   --------------------------------- '
    Write-Host ' 4 |'$board[0,3].Icon'|'$board[1,3].Icon'|'$board[2,3].Icon'|'$board[3,3].Icon'|'$board[4,3].Icon'|'$board[5,3].Icon'|'$board[6,3].Icon'|'$board[7,3].Icon'| 4'
    Write-Host '   --------------------------------- '
    Write-Host ' 3 |'$board[0,2].Icon'|'$board[1,2].Icon'|'$board[2,2].Icon'|'$board[3,2].Icon'|'$board[4,2].Icon'|'$board[5,2].Icon'|'$board[6,2].Icon'|'$board[7,2].Icon'| 3'
    Write-Host '   --------------------------------- '
    Write-Host ' 2 |'$board[0,1].Icon'|'$board[1,1].Icon'|'$board[2,1].Icon'|'$board[3,1].Icon'|'$board[4,1].Icon'|'$board[5,1].Icon'|'$board[6,1].Icon'|'$board[7,1].Icon'| 2'
    Write-Host '   --------------------------------- '
    Write-Host ' 1 |'$board[0,0].Icon'|'$board[1,0].Icon'|'$board[2,0].Icon'|'$board[3,0].Icon'|'$board[4,0].Icon'|'$board[5,0].Icon'|'$board[6,0].Icon'|'$board[7,0].Icon'| 1'
    Write-Host '   --------------------------------- '
    Write-Host '     A   B   C   D   E   F   G   H'
}

#Read and clean text input before calling New-Move
Function Read-Input {
    Write-Host ""
    if($Script:whiteTurn) {
        try {
            [ValidateScript({$_.Length -eq 2 -or $_ -like '*resign*'})]$src = Read-Host 'White piece source'
            if ($src -eq 'resign') {
                $Script:gameStatus = [gamestatus]::blackWin
                Update-Log -resign $true
            } else {
                [Int]$cc = Get-Column $src[0]
                [Int]$cr = Get-Row $src[1]
                [ValidateScript({$_.Color -eq 'White'})]$pc = $board[$cc, $cr]
                [ValidateScript({$_.Length -eq 2})]$dst = Read-Host 'White piece destination'
                New-Move $src $dst
            }
        } catch {
            Write-Error "Illegal input: Not a white piece or valid location"
            Write-Error $src
            Read-Input
        }
    } else {
        try {
            [ValidateScript({$_.Length -eq 2 -or $_.Value -eq 'resign'})]$src = Read-Host 'Black piece source'
            if ($src -like '*resign*') {
                $Script:gameStatus = [gamestatus]::whiteWin
                Update-Log -resign $true
            }
            [Int]$cc = Get-Column $src[0]
            [Int]$cr = Get-Row $src[1]
            [ValidateScript({$_.Color -eq 'Black'})]$pc = $board[$cc, $cr]
            [ValidateScript({$_.Length -eq 2})]$dst = Read-Host 'Black piece destination'
            New-Move $src $dst
        } catch {
            Write-Error "Illegal input: Not a black piece or valid location"
            Read-Input
        }
    }
}

#Update the status of all the pieces and place them
Function Update-Board {
    #Get arrays of all piece that are still alive
    [Array]$CurrentWhite = $Script:WhitePieces | Where-Object {$_.Alive -eq $true}
    [Array]$CurrentBlack = $Script:BlackPieces | Where-Object {$_.Alive -eq $true}

    #Place all the white pieces
    foreach ($pc in $CurrentWhite) {
        $board[($pc.CurrentColumn),($pc.CurrentRow)] = $pc
    }
    #Place all the black pieces
    foreach ($pc in $CurrentBlack) {
        $board[($pc.CurrentColumn),($pc.CurrentRow)] = $pc
    }

    #Check for spaces without a piece in them, then fill it with the empty placeholder.
    for ($i = 0; $i -le 7; $i++) {
        for ($j = 0; $j -le 7; $j++) {
            if ($board[$i, $j] -eq $null) {
                $board[$i, $j] = $Empty
            }
        }
    }
}

#Used to move pieces on the board
Function New-Move {
    param ([string]$src, [string]$dst)

    enum castleOptions {
        none = 0
        kingside = 1
        queenside = 2
    }

    [bool]$attack = $false
    [bool]$moveSuccess = $false
    [int]$castle = [castleOptions]::none
    [bool]$promote = $false
    [bool]$ep = $false
    [bool]$check = $false

    try {
        [Int]$CurrentColumn = Get-Column $src[0]
        [Int]$CurrentRow = Get-Row $src[1]
        [Int]$DesiredColumn = Get-Column $dst[0]
        [Int]$DesiredRow = Get-Row $dst[1]

        $pc = $board[$CurrentColumn, $CurrentRow]
    } catch {
        Write-Error "Out of bounds"
        Read-Input
    }

    #Moving nothing, nowhere, or trying to capture your own piece
    if ($board[$CurrentColumn, $CurrentRow] -eq $Empty) {
        Write-Error "There is nothing there."
        Read-Input
    } elseif (($CurrentRow -eq $DesiredRow) -and ($CurrentColumn -eq $DesiredColumn)) {
        Write-Error "That wouldn't move anywhere."
        Read-Input
    } elseif ($board[$DesiredColumn, $DesiredRow] -ne $Empty -and `
              $pc.Color -eq $board[$DesiredColumn, $DesiredRow].Color) {
        Write-Error "Collision with own team"
        Read-Input
    } else {
        [int]$MoveX = $DesiredColumn - $CurrentColumn
        [int]$MoveY = $DesiredRow - $CurrentRow

        #Move verification logic for each piece
        switch ($pc.GetType().Name) {
            'Pawn' {
                $MoveX = [math]::abs($MoveX)

                #Pawns can max move one to the side when capturing, two forward when moving
                if (($MoveX -gt 1) -or ([math]::abs($MoveY) -gt 2)) {
                    Write-Error "Illegal Pawn Move: Too many spaces"
                } else {
                    #Force pawns to only move "forward"
                    if ($pc.Color -eq 'Black') {
                        $MoveY *= -1
                    }

                    if ($MoveX -eq 0) {
                        if ($board[$DesiredColumn, $DesiredRow] -eq $Empty) {
                            if ($MoveY -eq 1) {
                                $moveSuccess = $true
                                $pc.firstmove = $false
                            } elseif ($MoveY -eq 2 -and $pc.firstmove -eq $true) {
                                if ($board[$DesiredColumn, ($DesiredRow + 1)] -eq $Empty) {
                                    $moveSuccess = $true
                                    $pc.firstmove = $false
                                    $pc.inpassing = $Script:turnCounter
                                } else {
                                    Write-Error "Illegal Pawn Move: Blocked Path"
                                }
                            } else {
                                Write-Error "Illegal Pawn Move: Cannot Move 2 Spaces"
                            }
                        } else {
                            Write-Error "Illegal Pawn Move: Blocked Path"
                        }
                    } elseif (($MoveX -eq 1) -and ($MoveY -eq 1)) {
                        if ($board[$DesiredColumn, $DesiredRow] -eq $Empty) {
                            $enpassant = $board[$DesiredColumn, $CurrentRow]
                            if (($enpassant.GetType().Name -eq 'Pawn') -and `
                                ($pc.Color -ne $enpassant.Color) -and `
                                ($enpassant.inpassing -eq ($Script:turnCounter - 1))) {
                                
                                $moveSuccess = $true
                                $attack = $true
                                $ep = $true
                                
                                $enpassant.Alive = $false
                                $enpassant.CurrentPosition = $null
                                $enpassant.CurrentRow = $null
                                $enpassant.CurrentColumn = $null
                                $board[$DesiredColumn, $CurrentRow] = $Empty
                            } else {
                                Write-Error 'Illegal Pawn Move: Cannot Capture en passant'
                            }
                        } else {
                            $attack = $true
                            $moveSuccess = $true
                            $pc.firstmove = $false
                        }
                    } else {
                        #Catch-all, should never get here
                        Write-Error "Illegal Pawn Move"
                    }
                }
            }

            'Knight' {
                $MoveX = [math]::abs($MoveX)
                $MoveY = [math]::abs($MoveY)

                if ((($MoveX -eq 1) -and ($MoveY -eq 2)) -or (($MoveX -eq 2) -and ($MoveY -eq 1))) {
                    $moveSuccess = $true
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $attack = $true
                    }
                } else {
                    Write-Error "Illegal Knight Move"
                }
            }

            'Bishop' {
                if ([math]::abs($MoveX) -ne [math]::abs($MoveY)) {
                    Write-Error "Illegal Bishop Move: Not a Diagonal"
                } else {
                    if ($MoveX -gt 0) {
                        if ($MoveY -gt 0) {
                            for ($i = 1; $i -lt $MoveX; $i++) {
                                if ($board[($CurrentColumn + $i) , ($CurrentRow + $i)] -ne $Empty) {
                                    Write-Error "Illegal Bishop Move: Blocked Path"
                                    Read-Input
                                    break
                                }
                            }
                        } else {
                            for ($i = 1; $i -lt $MoveX; $i++) {
                                if ($board[($CurrentColumn + $i) , ($CurrentRow - $i)] -ne $Empty) {
                                    Write-Error "Illegal Bishop Move: Blocked Path"
                                    Read-Input
                                    break
                                }
                            }
                        }
                    } else {
                        if ($MoveY -gt 0) {
                            for ($i = 1; $i -lt $MoveY; $i++) {
                                if ($board[($CurrentColumn - $i) , ($CurrentRow + $i)] -ne $Empty) {
                                    Write-Error "Illegal Bishop Move: Blocked Path"
                                    Publish-Board
                                    break
                                }
                            }
                        } else {
                            for ($i = 1; $i -lt [math]::abs($MoveX); $i++) {
                                if ($board[($CurrentColumn - $i) , ($CurrentRow - $i)] -ne $Empty) {
                                    Write-Error "Illegal Bishop Move: Blocked Path"
                                    Publish-Board
                                    break
                                }
                            }
                        }
                    }
                    $moveSuccess = $true
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $attack = $true
                    }
                }
            }

            'Rook' {
                if (([math]::abs($MoveX) -gt 0) -and ([math]::abs($MoveY) -gt 0)) {
                    Write-Error "Illegal Rook Move"
                } else {
                    if ($MoveX -gt 0) {
                        for ($i = 1; $i -lt $MoveX; $i++) {
                            if ($board[($CurrentColumn + $i), $CurrentRow] -ne $Empty) {
                                    Write-Error "Illegal Rook Move: Blocked Path"
                                    Publish-Board
                                    break
                            }
                        }
                    } elseif ($MoveX -lt 0) {
                        for ($i = 1; $i -lt [math]::abs($MoveX); $i++) {
                            if ($board[($CurrentColumn - $i), $CurrentRow] -ne $Empty) {
                                    Write-Error "Illegal Rook Move: Blocked Path"
                                    Publish-Board
                                    break
                            }
                        }
                    } elseif ($MoveY -gt 0) {
                        for ($i = 1; $i -lt $MoveY; $i++) {
                            if ($board[$CurrentColumn, ($CurrentRow + $i)] -ne $Empty) {
                                    Write-Error "Illegal Rook Move: Blocked Path"
                                    Publish-Board
                                    break
                            }
                        }
                    } else {
                        for ($i = 1; $i -lt [math]::abs($MoveY); $i++) {
                            if ($board[$CurrentColumn, ($CurrentRow - $i)] -ne $Empty) {
                                    Write-Error "Illegal Rook Move: Blocked Path"
                                    Publish-Board
                                    break
                            }
                        }
                    }
                    $moveSuccess = $true
                    $pc.firstmove = $false
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $attack = $true
                    }
                }
            }

            'King' {
                $MoveX = [math]::abs($MoveX)
                $MoveY = [math]::abs($MoveY)

                if (($MoveX -eq 1) -or ($MoveY -eq 1)) {
                    $moveSuccess = $true
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $attack = $true
                    }
                } elseif (($pc.firstmove -eq $true) -and `
                        ($pc.color -eq 'White')) {
                    if (($dst -eq 'G1') -and `
                        ($wHR.firstmove -eq $true)) {
                        
                        $Crk = $board[7, 0]
                        $board[7, 0] = $Empty
                        $Crk.CurrentPosition = 'F1'
                        $Crk.CurrentRow = 0
                        $Crk.CurrentColumn = 5
                        $Crk.firstmove = $false

                        $moveSuccess = $true
                        $castle = [castleOptions]::kingside
                        $pc.firstmove = $false
                    } elseif (($dst -eq 'C1') -and `
                            ($wAR.firstmove -eq $true)) {
                        
                        $Crk = $board[0, 0]
                        $board[0, 0] = $Empty
                        $Crk.CurrentPosition = 'D1'
                        $Crk.CurrentRow = 0
                        $Crk.CurrentColumn = 3
                        $Crk.firstmove = $false

                        $moveSuccess = $true
                        $castle = [castleOptions]::queenside
                        $pc.firstmove = $false
                    }
                } elseif (($pc.firstmove -eq $true) -and `
                        ($pc.color -eq 'Black')) {
                    if (($dst -eq 'G8') -and `
                        ($bHR.firstmove -eq $true)) {
                        
                        $Crk = $board[7, 7]
                        $board[7, 7] = $Empty
                        $Crk.CurrentPosition = 'F8'
                        $Crk.CurrentRow = 7
                        $Crk.CurrentColumn = 5
                        $Crk.firstmove = $false

                        $moveSuccess = $true
                        $castle = [castleOptions]::kingside
                        $pc.firstmove = $false
                    } elseif (($dst -eq 'C8') -and `
                            ($bAR.firstmove -eq $true)) {
                        
                        $Crk = $board[0, 7]
                        $board[0, 7] = $Empty
                        $Crk.CurrentPosition = 'D8'
                        $Crk.CurrentRow = 7
                        $Crk.CurrentColumn = 3
                        $Crk.firstmove = $false

                        $moveSuccess = $true
                        $castle = [castleOptions]::queenside
                        $pc.firstmove = $false
                    }
                } else {
                    Write-Error "Illegal King Move"
                }
            }

            'Queen' {
                if ([math]::abs($MoveX) -eq [math]::abs($MoveY)) {
                    if ($MoveX -gt 0) {
                        if ($MoveY -gt 0) {
                            for ($i = 1; $i -lt $MoveX; $i++) {
                                if ($board[($CurrentColumn + $i) , ($CurrentRow + $i)] -ne $Empty) {
                                    Write-Error "Illegal Queen Move"
                                    Publish-Board
                                    break
                                }
                            }
                        } else {
                            for ($i = 1; $i -lt $MoveX; $i++) {
                                if ($board[($CurrentColumn + $i) , ($CurrentRow - $i)] -ne $Empty) {
                                    Write-Error "Illegal Queen Move"
                                    Publish-Board
                                    break
                                }
                            }
                        }
                    } else {
                        if ($MoveY -gt 0) {
                            for ($i = 1; $i -lt $MoveY; $i++) {
                                if ($board[($CurrentColumn - $i), ($CurrentRow + $i)] -ne $Empty) {
                                    Write-Error "Illegal Queen Move"
                                    Publish-Board
                                    break
                                }
                            }
                        } else {
                            for ($i = 1; $i -lt [math]::abs($MoveX); $i++) {
                                if ($board[($CurrentColumn - $i) , ($CurrentRow - $i)] -ne $Empty) {
                                    Write-Error "Illegal Queen Move"
                                    Publish-Board
                                    break
                                }
                            }
                        }
                    }
                    $moveSuccess = $true
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $attack = $true
                    }
                } elseif (($MoveX -ne 0 -and $MoveY -eq 0) -or `
                        ($MoveX -eq 0 -and $MoveY -ne 0)) {
                    if ($MoveX -gt 0) {
                        for ($i = 1; $i -lt $MoveX; $i++) {
                            if ($board[($CurrentColumn + $i), $CurrentRow] -ne $Empty) {
                                    Write-Error "Illegal Queen Move"
                                    Publish-Board
                                    break
                            }
                        }
                    } elseif ($MoveX -lt 0) {
                        for ($i = 1; $i -lt [math]::abs($MoveX); $i++) {
                            if ($board[($CurrentColumn - $i), $CurrentRow] -ne $Empty) {
                                    Write-Error "Illegal Queen Move"
                                    Publish-Board
                                    break
                            }
                        }
                    } elseif ($MoveY -gt 0) {
                        for ($i = 1; $i -lt $MoveY; $i++) {
                            if ($board[$CurrentColumn, ($CurrentRow + $i)] -ne $Empty) {
                                    Write-Error "Illegal Queen Move"
                                    Publish-Board
                                    break
                            }
                        }
                    } else {
                        for ($i = 1; $i -lt [math]::abs($MoveY); $i++) {
                            if ($board[$CurrentColumn, ($CurrentRow - $i)] -ne $Empty) {
                                    Write-Error "Illegal Queen Move"
                                    Publish-Board
                                    break
                            }
                        }
                    }
                    $moveSuccess = $true
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $attack = $true
                    }
                } else {
                    Write-Error "Illegal Queen Move"
                }
            }
        }

        if ($moveSuccess -eq $true) {
            if ($attack -eq $true -and $ep -eq $false) {
                $board[$DesiredColumn, $DesiredRow].Alive = $false
                $board[$DesiredColumn, $DesiredRow].CurrentPosition = $null
                $board[$DesiredColumn, $DesiredRow].CurrentRow = $null
                $board[$DesiredColumn, $DesiredRow].CurrentColumn = $null
            }

            #Pawn Promotion logic
            if (($pc.GetType().Name -eq 'Pawn') -and ($DesiredRow -eq 0)) {
                [ValidateSet('Knight', 'Bishop', 'Rook', 'Queen')]$ptype = Read-Host 'Promote black pawn to'
                
                $promote = $true
                $pc.Type = $ptype
                
                switch ($ptype) {
                    'Knight' {
                        $pc.Icon = '♞'
                        $pc.Symbol = 'N'
                    }
                    'Bishop' {
                        $pc.Icon = '♝'
                        $pc.Symbol = 'B'
                    }
                    'Rook' {
                        $pc.Icon = '♜'
                        $pc.Symbol = 'R'
                    }
                    'Queen' {
                        $pc.Icon = '♛'
                        $pc.Symbol = 'Q'
                    }
                }
            } elseif (($pc.GetType().Name -eq 'Pawn') -and ($DesiredRow -eq 7)) {
                [ValidateSet('Knight', 'Bishop', 'Rook', 'Queen')]$ptype = Read-Host 'Promote white pawn to'
                
                $promote = $true
                $pc.Type = $ptype

                switch ($ptype) {
                    'Knight' {
                        $pc.Icon = '♘'
                        $pc.Symbol = 'N'
                    }
                    'Bishop' {
                        $pc.Icon = '♗'
                        $pc.Symbol = 'B'
                    }
                    'Rook' {
                        $pc.Icon = '♖'
                        $pc.Symbol = 'R'
                    }
                    'Queen' {
                        $pc.Icon = '♕'
                        $pc.Symbol = 'Q'
                    }
                }
            }
            
            $board[$CurrentColumn, $CurrentRow] = $Empty
            $pc.CurrentPosition = $dst.ToUpper()
            $pc.CurrentRow = $DesiredRow
            $pc.CurrentColumn = $DesiredColumn

            Update-Board

            #Check logic
            #TODO: Shouldn't check when king is captured Issue 25
            Test-Gamestatus
            if ($Script:gameStatus -eq [gamestatus]::ongoing) {
                [Array]$curWhite = $Script:WhitePieces | Where-Object {$_.Alive -eq $true}
                [Array]$curBlack = $Script:BlackPieces | Where-Object {$_.Alive -eq $true}
    
                if ($Script:whiteTurn -eq $true) {
                    foreach ($whitePiece in $curWhite) {
                        if ($(Test-Move $whitePiece.CurrentPosition $Script:bK.CurrentPosition)[0] -eq $true) {
                            $check = $true
                        }
                    }
                } else {
                    foreach ($blackPiece in $curBlack) {
                        if ($(Test-Move $blackPiece.CurrentPosition $Script:wK.CurrentPosition)[0] -eq $true) {
                            $check = $true
                        }
                    }
                }
            }
            
            #Update the log, advance turn
            Update-Log $src $dst $pc.Symbol $attack $castle $promote $ep $check
            $Script:turnCounter += 1
            $Script:whiteTurn = !($Script:whiteTurn)
        } else {
            Read-Input
        }
    }
}

#Log logic will go here
Function Update-Log {
    param([string]$src, [string]$dst, [string]$piece, [bool]$attack, 
          [int]$castle, [bool]$promote, [bool]$ep, [bool]$check, [bool]$resign)

    [string]$logentry = ''

    enum castleOptions {
        none = 0
        kingside = 1
        queenside = 2
    }

    if ($castle -eq [castleOptions]::kingside) {
        $logentry = '0-0'
    } elseif ($castle -eq [castleOptions]::queenside) {
        $logentry = '0-0-0'
    } elseif ($promote -eq $true) {
        if ($attack) {
            $logentry += 'x'
        }
        $logentry += $dst + '=' + $piece
    } else {
        $logentry = $piece
        if ($attack) {
            $logentry += 'x'
        }
        $logentry += $dst
    }

    if ($ep -eq $true) {
        $logentry += ' ep'
    }

    if ($check -eq $true) {
        $logentry += '+'
    }

    if ($Script:gameStatus -ne 0) {
        $logentry += '#'
    }

    if ($resign -eq $true) {
        $logentry = 'resigned'
    }

    $Script:log += $logentry
   
    #Equivalent of touch command to ensure a log exists
    Write-Output $null >> $Script:logpath

    #Clear and rewrite the log each time
    Clear-Content $Script:logpath
    $line = "White`t`tBlack`r`n--------------------"

    #Header
    Add-Content -Encoding Unicode $Script:logpath $line

    if ($log.Length -eq 1) {
        Add-Content -Encoding Unicode $Script:logpath $log[0]
    } else {
        for ($i = 0; $i -lt $log.Length - 1; $i += 2) {
            $line = $Script:log[$i]
            $line += " `t`t"
            $line += $Script:log[$i + 1]
            Add-Content -Encoding Unicode $Script:logpath $line
        }
        
        #If game ended in white move, print out the "half-line"
        if ($log.Length % 2 -eq 1) {
            Add-Content -Encoding Unicode $Script:logpath $Script:log[$Script:log.Length - 1]
        }
    } 
}

#Try a move, used for check and castling logic
Function Test-Move {
    param ([string]$src, [string]$dst)

    [bool]$attack = $false
    [bool]$moveSuccess = $false
    [bool[]]$status = @($moveSuccess, $attack)

    try {
        [Int]$CurrentColumn = Get-Column $src[0]
        [Int]$CurrentRow = Get-Row $src[1]
        [Int]$DesiredColumn = Get-Column $dst[0]
        [Int]$DesiredRow = Get-Row $dst[1]

        $pc = $board[$CurrentColumn, $CurrentRow]
    } catch {
        Write-Error "Out of bounds"
        return $status
    }

    #Moving nothing, nowhere, or trying to capture your own piece
    if ($board[$CurrentColumn, $CurrentRow] -eq $Empty) {
        Write-Error "There is nothing there."
        return $status
    } elseif (($CurrentRow -eq $DesiredRow) -and ($CurrentColumn -eq $DesiredColumn)) {
        Write-Error "That wouldn't move anywhere."
        return $status
    } elseif ($board[$DesiredColumn, $DesiredRow] -ne $Empty -and `
              $pc.Color -eq $board[$DesiredColumn, $DesiredRow].Color) {
        Write-Error "Collision with own team"
        return $status
    } else {
        [int]$MoveX = $DesiredColumn - $CurrentColumn
        [int]$MoveY = $DesiredRow - $CurrentRow
        
        #Pieces playable
        switch ($pc.Type) {
            'Pawn' {
                $MoveX = [math]::abs($MoveX)
                if (($MoveX -gt 1) -or ([math]::abs($MoveY) -gt 2)) {
                    return $status
                } else {
                    #Force pawns to only move "forward"
                    if ($pc.Color -eq 'Black') {
                        $MoveY *= -1
                    }
                    if (($MoveX -eq 0) -and ($MoveY -eq 1)) {
                        if ($board[$DesiredColumn,$DesiredRow] -ne $Empty) {
                            return $status
                        } else {
                            $status[0] = $true
                            $pc.firstmove = $false
                        }
                    } elseif (($MoveX -eq 0) -and ($MoveY -eq 2)) {
                        if (($pc.firstmove = $true) -and `
                            (($board[$DesiredColumn, $DesiredRow] -eq $Empty) -and `
                            ($board[($DesiredColumn + 1), $DesiredRow] -eq $Empty))) {

                            $status[0] = $true
                            $pc.firstmove = $false
                            $pc.inpassing = $Script:turnCounter
                        } else {
                            return $status
                        }
                    } elseif (($MoveX -eq 1) -and ($MoveY -eq 1)) {
                        if ($board[$DesiredColumn,$DesiredRow] -eq $Empty) {
                            $enpassant = $board[$DesiredColumn, $CurrentRow]
                            if (($enpassant.GetType().Name -eq 'Pawn') -and `
                                ($pc.Color -ne $enpassant.Color) -and `
                                ($enpassant.inpassing -eq ($Script:turnCounter - 1))) {
                                
                                $status[0] = $true
                                
                                $enpassant.Alive = $false
                                $enpassant.CurrentPosition = $null
                                $enpassant.CurrentRow = $null
                                $enpassant.CurrentColumn = $null
                                $board[$DesiredColumn, $CurrentRow] = $Empty
                            } else {
                                return $status
                            }
                        } else {
                            $status[1] = $true
                            $status[0] = $true
                            $pc.firstmove = $false
                        }
                    } else {
                        return $status
                    }
                }
            }

            'Knight' {
                $MoveX = [math]::abs($MoveX)
                $MoveY = [math]::abs($MoveY)

                if ((($MoveX -eq 1) -and ($MoveY -eq 2)) -or (($MoveX -eq 2) -and ($MoveY -eq 1))) {
                    $status[0] = $true
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $status[1] = $true
                    }
                } else {
                    return $status
                }
            }

            'Bishop' {
                if ([math]::abs($MoveX) -ne [math]::abs($MoveY)) {
                    return $status
                } else {
                    if ($MoveX -gt 0) {
                        if ($MoveY -gt 0) {
                            for ($i = 1; $i -lt $MoveX; $i++) {
                                if ($board[($CurrentColumn + $i) , ($CurrentRow + $i)] -ne $Empty) {
                                    return $status
                                }
                            }
                        } else {
                            for ($i = 1; $i -lt $MoveX; $i++) {
                                if ($board[($CurrentColumn + $i) , ($CurrentRow - $i)] -ne $Empty) {
                                    return $status
                                }
                            }
                        }
                    } else {
                        if ($MoveY -gt 0) {
                            for ($i = 1; $i -lt $MoveY; $i++) {
                                if ($board[($CurrentColumn - $i) , ($CurrentRow + $i)] -ne $Empty) {
                                    return $status
                                }
                            }
                        } else {
                            for ($i = 1; $i -lt [math]::abs($MoveX); $i++) {
                                if ($board[($CurrentColumn - $i) , ($CurrentRow - $i)] -ne $Empty) {
                                    return $status
                                }
                            }
                        }
                    }
                    $status[0] = $true
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $status[1] = $true
                    }
                }
            }

            'Rook' {
                if (([math]::abs($MoveX) -gt 0) -and ([math]::abs($MoveY) -gt 0)) {
                    return $status
                } else {
                    if ($MoveX -gt 0) {
                        for ($i = 1; $i -lt $MoveX; $i++) {
                            if ($board[($CurrentColumn + $i), $CurrentRow] -ne $Empty) {
                                return $status
                            }
                        }
                    } elseif ($MoveX -lt 0) {
                        for ($i = 1; $i -lt [math]::abs($MoveX); $i++) {
                            if ($board[($CurrentColumn - $i), $CurrentRow] -ne $Empty) {
                                return $status
                            }
                        }
                    } elseif ($MoveY -gt 0) {
                        for ($i = 1; $i -lt $MoveY; $i++) {
                            if ($board[$CurrentColumn, ($CurrentRow + $i)] -ne $Empty) {
                                return $status
                            }
                        }
                    } else {
                        for ($i = 1; $i -lt [math]::abs($MoveY); $i++) {
                            if ($board[$CurrentColumn, ($CurrentRow - $i)] -ne $Empty) {
                                return $status
                            }
                        }
                    }
                    $status[0] = $true
                    $pc.firstmove = $false
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $status[1] = $true
                    }
                }
            }

            'Queen' {
                if ([math]::abs($MoveX) -eq [math]::abs($MoveY)) {
                    if ($MoveX -gt 0) {
                        if ($MoveY -gt 0) {
                            for ($i = 1; $i -lt $MoveX; $i++) {
                                if ($board[($CurrentColumn + $i) , ($CurrentRow + $i)] -ne $Empty) {
                                    return $status
                                }
                            }
                        } else {
                            for ($i = 1; $i -lt $MoveX; $i++) {
                                if ($board[($CurrentColumn + $i) , ($CurrentRow - $i)] -ne $Empty) {
                                    return $status
                                }
                            }
                        }
                    } else {
                        if ($MoveY -gt 0) {
                            for ($i = 1; $i -lt $MoveY; $i++) {
                                if ($board[($CurrentColumn - $i), ($CurrentRow + $i)] -ne $Empty) {
                                    return $status
                                }
                            }
                        } else {
                            for ($i = 1; $i -lt [math]::abs($MoveX); $i++) {
                                if ($board[($CurrentColumn - $i) , ($CurrentRow - $i)] -ne $Empty) {
                                    return $status
                                }
                            }
                        }
                    }
                    $status[0] = $true
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $status[1] = $true
                    }
                } elseif (($MoveX -ne 0 -and $MoveY -eq 0) -or `
                        ($MoveX -eq 0 -and $MoveY -ne 0)) {
                    if ($MoveX -gt 0) {
                        for ($i = 1; $i -lt $MoveX; $i++) {
                            if ($board[($CurrentColumn + $i), $CurrentRow] -ne $Empty) {
                                    return $status
                            }
                        }
                    } elseif ($MoveX -lt 0) {
                        for ($i = 1; $i -lt [math]::abs($MoveX); $i++) {
                            if ($board[($CurrentColumn - $i), $CurrentRow] -ne $Empty) {
                                    return $status
                            }
                        }
                    } elseif ($MoveY -gt 0) {
                        for ($i = 1; $i -lt $MoveY; $i++) {
                            if ($board[$CurrentColumn, ($CurrentRow + $i)] -ne $Empty) {
                                    return $status
                            }
                        }
                    } else {
                        for ($i = 1; $i -lt [math]::abs($MoveY); $i++) {
                            if ($board[$CurrentColumn, ($CurrentRow - $i)] -ne $Empty) {
                                    return $status
                            }
                        }
                    }
                    $status[0] = $true
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $status[1] = $true
                    }
                } else {
                    return $status
                }
            }

            'King' {
                $MoveX = [math]::abs($MoveX)
                $MoveY = [math]::abs($MoveY)

                if (($MoveX -le 1) -and ($MoveY -le 1)) {
                    $status[0] = $true
                    if ($board[$DesiredColumn, $DesiredRow] -ne $Empty) {
                        $status[1] = $true
                    }
                } elseif (($pc.firstmove -eq $true) -and `
                        ($pc.color -eq 'White')) {
                    if (($dst -eq 'G1') -and `
                        ($wHR.firstmove -eq $true)) {
                        
                        $Crk = $board[7, 0]
                        $board[7, 0] = $Empty
                        $Crk.CurrentPosition = 'F1'
                        $Crk.CurrentRow = 0
                        $Crk.CurrentColumn = 5
                        $Crk.firstmove = $false

                        $status[0] = $true
                        $pc.firstmove = $false
                    } elseif (($dst -eq 'C1') -and `
                            ($wAR.firstmove -eq $true)) {
                        
                        $Crk = $board[0, 0]
                        $board[0, 0] = $Empty
                        $Crk.CurrentPosition = 'D1'
                        $Crk.CurrentRow = 0
                        $Crk.CurrentColumn = 3
                        $Crk.firstmove = $false

                        $status[0] = $true
                        $pc.firstmove = $false
                    }
                } elseif (($pc.firstmove -eq $true) -and `
                        ($pc.color -eq 'Black')) {
                    if (($dst -eq 'G8') -and `
                        ($bHR.firstmove -eq $true)) {
                        
                        $Crk = $board[7, 7]
                        $board[7, 7] = $Empty
                        $Crk.CurrentPosition = 'F8'
                        $Crk.CurrentRow = 7
                        $Crk.CurrentColumn = 5
                        $Crk.firstmove = $false

                        $status[0] = $true
                        $pc.firstmove = $false
                    } elseif (($dst -eq 'C8') -and `
                            ($bAR.firstmove -eq $true)) {
                        
                        $Crk = $board[0, 7]
                        $board[0, 7] = $Empty
                        $Crk.CurrentPosition = 'D8'
                        $Crk.CurrentRow = 7
                        $Crk.CurrentColumn = 3
                        $Crk.firstmove = $false

                        $status[0] = $true
                        $pc.firstmove = $false
                    }
                } else {
                    return $status
                }
            }
        }

        return $status
    }
}

#Figure out if the game is over or still ongoing
Function Test-Gamestatus {
    if ($wK.Alive -eq $false) {
        $Script:gameStatus = [gamestatus]::blackWin
    } elseif ($bK.Alive -eq $false) {
        $Script:gameStatus = [gamestatus]::whiteWin
    }
}

Function Get-Column {
    param ([ValidatePattern('[A-H]')][string]$Col)
    switch ($Col) {
        "A" {Return "0"}
        "B" {Return "1"}
        "C" {Return "2"}
        "D" {Return "3"}
        "E" {Return "4"}
        "F" {Return "5"}
        "G" {Return "6"}
        "H" {Return "7"}
    }
}

Function Get-Row {
    param ([ValidateRange(1,8)][string]$row)

    return ($row - 1)
}

###########################
#endregion: Functions
####################################################


####################################################
#region: Classes
###########################

#Gives all classes that inherit(:) this class the base properties
Class ChessPiece {
    [bool]$Alive=$true
    [string]$Type
    [string]$Icon
    [string]$Symbol
    [ValidateSet('White', 'Black')][String]$Color
    [String]$CurrentPosition
    [ValidateRange(0,7)][Int]$CurrentRow
    [ValidateRange(0,7)][Int]$CurrentColumn
}

Class Pawn : ChessPiece {
    [bool]$firstmove = $true
    [int]$inpassing = 0
    [string]$Type = $this.GetType().Name
    [string]$Symbol = " "
    Pawn([string]$Position, [string]$color) {
        $this.Color = $color
        $this.CurrentPosition = $Position
        $this.CurrentRow = Get-Row $Position[1] 
        $this.CurrentColumn = Get-Column $Position[0]

        if ($color -eq 'White') {
            $this.Icon = '♙'
        } elseif ($color -eq 'Black') {
            $this.Icon = '♟'
        }
    }
}

Class Rook : ChessPiece {
    [bool]$firstmove = $true
    [string]$Type = $this.GetType().Name
    [string]$Symbol = "R"
    Rook([string]$Position, [string]$color) {
        $this.Color = $color
        $this.CurrentPosition = $Position
        $this.CurrentRow = Get-Row $Position[1] 
        $this.CurrentColumn = Get-Column $Position[0]

        if ($color -eq 'White') {
            $this.Icon = '♖'
        } elseif ($color -eq 'Black') {
            $this.Icon = '♜'
        }
    }
}

Class Knight : ChessPiece {
    [string]$Type = $this.GetType().Name
    [string]$Symbol = "N"
    Knight([string]$Position, [string]$color) {
        $this.Color = $color
        $this.CurrentPosition = $Position
        $this.CurrentRow = Get-Row $Position[1] 
        $this.CurrentColumn = Get-Column $Position[0]

        if ($color -eq 'White') {
            $this.Icon = '♘'
        } elseif ($color -eq 'Black') {
            $this.Icon = '♞'
        }
    }
}

Class Bishop : ChessPiece {
    [string]$Type = $this.GetType().Name
    [string]$Symbol = "B"
    Bishop([String]$Position, [string]$color) {
        $this.Color = $color
        $this.CurrentPosition = $Position
        $this.CurrentRow = Get-Row $Position[1] 
        $this.CurrentColumn = Get-Column $Position[0]

        if ($color -eq 'White') {
            $this.Icon = '♗'
        } elseif ($color -eq 'Black') {
            $this.Icon = '♝'
        }
    }
}

Class Queen : ChessPiece {
    [string]$Type = $this.GetType().Name
    [string]$Symbol = "Q"
    Queen([String]$Position, [string]$color) {
        $this.Color = $color
        $this.CurrentPosition = $Position
        $this.CurrentRow = Get-Row $Position[1] 
        $this.CurrentColumn = Get-Column $Position[0]

        if ($color -eq 'White') {
            $this.Icon = '♕'
        } elseif ($color -eq 'Black') {
            $this.Icon = '♛'
        }
    }
}

Class King : ChessPiece {
    [bool]$firstmove = $true
    [string]$Type = $this.GetType().Name
    [string]$Symbol = "K"
    King([String]$Position, [string]$color) {
        $this.Color = $color
        $this.CurrentPosition = $Position
        $this.CurrentRow = Get-Row $Position[1] 
        $this.CurrentColumn = Get-Column $Position[0]

        if ($color -eq 'White') {
            $this.Icon = '♔'
        } elseif ($color -eq 'Black') {
            $this.Icon = '♚'
        }
    }
}

Class Blank {
    [String]$Icon=' '
}

###########################
#endregion: Classes
####################################################

#Creates the game board
[Object]$Script:board = New-Object 'object[,]' 8,8

#Creates a turn status
[bool]$Script:whiteTurn = $true

#SAN log.txt path, currently on desktop
$DesktopPath = [Environment]::GetFolderPath("Desktop")
[string]$Script:logpath = $DesktopPath + '\log.txt'
[string[]]$Script:log = @()

$Script:wAP = [Pawn]::New('A2', 'White')
$Script:wBP = [Pawn]::New('B2', 'White')
$Script:wCP = [Pawn]::New('C2', 'White')
$Script:wDP = [Pawn]::New('D2', 'White')
$Script:wEP = [Pawn]::New('E2', 'White')
$Script:wFP = [Pawn]::New('F2', 'White')
$Script:wGP = [Pawn]::New('G2', 'White')
$Script:wHP = [Pawn]::New('H2', 'White')
$Script:wAR = [Rook]::New('A1', 'White')
$Script:wBN = [Knight]::New('B1', 'White')
$Script:wCB = [Bishop]::New('C1', 'White')
$Script:wQ  = [Queen]::New('D1', 'White')
$Script:wK  = [King]::New('E1', 'White')
$Script:wFB = [Bishop]::New('F1', 'White')
$Script:wGN = [Knight]::New('G1', 'White')
$Script:wHR = [Rook]::New('H1', 'White')

$Script:bAP = [Pawn]::New('A7', 'Black')
$Script:bBP = [Pawn]::New('B7', 'Black')
$Script:bCP = [Pawn]::New('C7', 'Black')
$Script:bDP = [Pawn]::New('D7', 'Black')
$Script:bEP = [Pawn]::New('E7', 'Black')
$Script:bFP = [Pawn]::New('F7', 'Black')
$Script:bGP = [Pawn]::New('G7', 'Black')
$Script:bHP = [Pawn]::New('H7', 'Black')
$Script:bAR = [Rook]::New('A8', 'Black')
$Script:bBN = [Knight]::New('B8', 'Black')
$Script:bCB = [Bishop]::New('C8', 'Black')
$Script:bQ  = [Queen]::New('D8', 'Black')
$Script:bK  = [King]::New('E8', 'Black')
$Script:bFB = [Bishop]::New('F8', 'Black')
$Script:bGN = [Knight]::New('G8', 'Black')
$Script:bHR = [Rook]::New('H8', 'Black')

$Script:Empty = [Blank]::New()

[Array] $Script:WhitePieces = @(
    $Script:wAP,$Script:wBP,$Script:wCP,$Script:wDP,
    $Script:wEP,$Script:wFP,$Script:wGP,$Script:wHP,
    $Script:wAR,$Script:wHR,$Script:wBN,$Script:wGN,
    $Script:wCB,$Script:wFB,$Script:wQ,$Script:wK
)

[Array] $Script:BlackPieces = @(
    $Script:bAP,$Script:bBP,$Script:bCP,$Script:bDP,
    $Script:bEP,$Script:bFP,$Script:bGP,$Script:bHP,
    $Script:bAR,$Script:bHR,$Script:bBN,$Script:bGN,
    $Script:bCB,$Script:bFB,$Script:bQ,$Script:bK
)

enum gamestatus {
    ongoing = 0
    whiteWin = 1
    blackWin = 2
    quit = 3
}

#Set global variables to keep track of turn and game status
[int]$Script:turnCounter = 0
$Script:gameStatus = [gamestatus]::ongoing

while ($Script:gameStatus -eq [gamestatus]::ongoing) {
    Update-Board
    Publish-Board
    Read-Input

    if ($Script:gameStatus -eq [gamestatus]::blackWin) {
        Write-Output "Black Wins!"
    } elseif ($Script:gameStatus -eq [gamestatus]::whiteWin) {
        Write-Output "White Wins!"
    } elseif ($Script:gameStatus -eq [gamestatus]::quit) {
        Write-Output "Game ended by request."
    }
}

(generated by convert-ps2md.ps1 as of 11/20/2024 11:51:59)