mchess-server/chess/board.go
Marco af993876fd Introduce check if the game has ended (checkmate/stalemate)
Introduce checkmate check and send out 'gameEnded' message
2024-01-17 23:06:48 +01:00

270 lines
6.9 KiB
Go

package chess
import (
"errors"
"mchess_server/types"
"github.com/samber/lo"
)
type Position map[types.Coordinate]Piece
type Board struct {
position Position
history []types.Move
colorToMove types.ChessColor
state types.AdditionalState
}
func newBoard() Board {
return Board{
position: make(Position),
history: make([]types.Move, 0),
colorToMove: types.White,
state: types.AdditionalState{},
}
}
func (b *Board) Init() {
var coord types.Coordinate
for i := 1; i <= 8; i++ {
coord.Row = 2
coord.Col = i
b.position[coord] = Pawn{Color: types.White}
coord.Row = 7
coord.Col = i
b.position[coord] = Pawn{Color: types.Black}
}
b.position[types.Coordinate{Row: 1, Col: 1}] = Rook{Color: types.White}
b.position[types.Coordinate{Row: 1, Col: 2}] = Knight{Color: types.White}
b.position[types.Coordinate{Row: 1, Col: 3}] = Bishop{Color: types.White}
b.position[types.Coordinate{Row: 1, Col: 4}] = Queen{Color: types.White}
b.position[types.Coordinate{Row: 1, Col: 5}] = King{Color: types.White}
b.position[types.Coordinate{Row: 1, Col: 6}] = Bishop{Color: types.White}
b.position[types.Coordinate{Row: 1, Col: 7}] = Knight{Color: types.White}
b.position[types.Coordinate{Row: 1, Col: 8}] = Rook{Color: types.White}
b.position[types.Coordinate{Row: 8, Col: 1}] = Rook{Color: types.Black}
b.position[types.Coordinate{Row: 8, Col: 2}] = Knight{Color: types.Black}
b.position[types.Coordinate{Row: 8, Col: 3}] = Bishop{Color: types.Black}
b.position[types.Coordinate{Row: 8, Col: 4}] = Queen{Color: types.Black}
b.position[types.Coordinate{Row: 8, Col: 5}] = King{Color: types.Black}
b.position[types.Coordinate{Row: 8, Col: 6}] = Bishop{Color: types.Black}
b.position[types.Coordinate{Row: 8, Col: 7}] = Knight{Color: types.Black}
b.position[types.Coordinate{Row: 8, Col: 8}] = Rook{Color: types.Black}
}
func (b *Board) CheckAndPlay(move *types.Move) (bool, Violation) {
tempBoard := b.getCopyOfBoard()
//Check start square of move
pieceAtStartSquare := tempBoard.getPieceAt(move.StartSquare)
if pieceAtStartSquare == nil {
return false, NoPieceAtStartSquare
}
move.ColorMoved = pieceAtStartSquare.GetColor()
if move.ColorMoved != tempBoard.colorToMove {
return false, WrongColorMoved
}
move.PieceMoved = GetShortNameForPiece(pieceAtStartSquare)
//Check end square of move
pieceAtEndSquare := tempBoard.getPieceAt(move.EndSquare)
if pieceAtEndSquare != nil {
if pieceAtEndSquare.GetColor() == pieceAtStartSquare.GetColor() {
return false, TargetSquareIsOccupied
}
}
wasSpecialMove, err := tempBoard.handleSpecialMove(*move)
if err != nil {
return false, InvalidMove
}
if !wasSpecialMove {
allMovesExceptBlocked := pieceAtStartSquare.GetAllNonBlockedSquares(tempBoard, move.StartSquare)
legal := lo.Contains(allMovesExceptBlocked, move.EndSquare)
if !legal {
return false, InvalidMove
}
//We play the move on the temporary board
delete(tempBoard.position, move.StartSquare)
tempBoard.position[move.EndSquare] = pieceAtStartSquare
}
kingAttacked, err := tempBoard.isKingOfMovingColorInCheck(move.ColorMoved)
if err != nil {
return false, SomethingWentWrong
}
if kingAttacked {
return false, KingInCheck
}
//We play the move on the real board
b.position = tempBoard.position
b.history = tempBoard.history
b.colorToMove = b.colorToMove.Opposite()
b.appendMoveToHistory(*move)
pieceAtStartSquare.AfterMoveAction(b, move.StartSquare)
return true, ""
}
func (b Board) isKingOfMovingColorInCheck(color types.ChessColor) (bool, error) {
//Check if king of moving color is in check -> move not allowed
//Do that by checking if the king is in a square attacked by the other color.
ownKingCoordinate := b.getSquareOfPiece(King{Color: color})
if ownKingCoordinate == nil {
return false, errors.New("no king found")
}
kingIsAttacked := b.isSquareAttacked(*ownKingCoordinate, color.Opposite())
if kingIsAttacked {
return true, nil
}
return false, nil
}
func (b Board) getSquareOfPiece(piece Piece) *types.Coordinate {
for k, v := range b.position {
if v == piece {
return &k
}
}
return nil
}
func (b Board) isSquareAttacked(square types.Coordinate, byColor types.ChessColor) bool {
var attackedSquares []types.Coordinate
for square, piece := range b.position {
if piece.GetColor() == byColor {
attackedSquares = append(attackedSquares, piece.GetAllAttackedSquares(b, square)...)
}
}
return lo.Contains(attackedSquares, square)
}
func (b Board) getPieceAt(coord types.Coordinate) Piece {
piece, found := b.position[coord]
if !found {
return nil
}
return piece
}
func (b *Board) appendMoveToHistory(move types.Move) {
b.history = append(b.history, move)
}
func (b Board) getLastMove() types.Move {
if len(b.history) < 1 {
return types.Move{}
}
return b.history[len(b.history)-1]
}
func (b Board) getCopyOfBoard() Board {
return Board{
position: b.position.getCopyOfPosition(),
history: b.history,
colorToMove: b.colorToMove,
state: b.state,
}
}
func (p Position) getCopyOfPosition() Position {
position := make(Position)
for coord, piece := range p {
position[coord] = piece
}
return position
}
func (b *Board) handleSpecialMove(move types.Move) (bool, error) {
var was bool
var err error
var pieceAtStartSquare = b.getPieceAt(move.StartSquare)
switch piece := pieceAtStartSquare.(type) {
case Pawn:
was, err = piece.HandlePossiblePromotion(b, move)
if !was {
was, err = piece.HandleEnPassant(b, move, b.getLastMove())
}
case King:
was, err = piece.HandleCastling(b, move)
}
return was, err
}
type GameEndedReason string
const (
NoReason GameEndedReason = "noReason"
WhiteIsCheckmated GameEndedReason = "whiteIsCheckmated"
BlackIsCheckmated GameEndedReason = "blackIsCheckmated"
StalemateReason GameEndedReason = "stalemate"
)
func (r GameEndedReason) String() string {
return string(r)
}
func (b *Board) HasGameEnded(lastMove types.Move) (bool, GameEndedReason) {
checkForColor := lastMove.ColorMoved.Opposite()
if checkmate := b.isColorCheckmated(checkForColor); checkmate {
switch checkForColor {
case types.White:
return true, WhiteIsCheckmated
case types.Black:
return true, BlackIsCheckmated
}
}
if b.isStalemate() {
return true, StalemateReason
}
return false, NoReason
}
func (b *Board) isColorCheckmated(color types.ChessColor) bool {
inCheck, _ := b.isKingOfMovingColorInCheck(color)
if !inCheck {
return false
}
copyOfBoard := b.getCopyOfBoard()
var movesToCheck []types.Move
for startSquare, piece := range b.position {
if piece.GetColor() == color {
for _, endSquare := range piece.GetAllNonBlockedSquares(*b, startSquare) {
move := types.Move{StartSquare: startSquare, EndSquare: endSquare}
movesToCheck = append(movesToCheck, move)
}
}
}
for _, move := range movesToCheck {
valid, _ := copyOfBoard.CheckAndPlay(&move)
if valid {
return false
}
}
return true
}
func (b *Board) isStalemate() bool {
return false
}