Marco
ff2ec599fe
In order to simplify special moves like en passant or castling for the client, we want to deliver the board state after every move (and not only start square and end square). With PGN we can encode a chess position into a string. This commit implies changes to logic of the pieces' shortnames. This will break the client/server connection (at least for promotions).
207 lines
5.5 KiB
Go
207 lines
5.5 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.GetAllNonBlockedMoves(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
|
|
}
|