2023-06-12 20:32:31 +00:00
|
|
|
package chess
|
|
|
|
|
2023-06-20 21:53:54 +00:00
|
|
|
import (
|
2023-06-27 20:32:24 +00:00
|
|
|
"errors"
|
2023-06-25 14:11:29 +00:00
|
|
|
"mchess_server/types"
|
2023-06-12 20:32:31 +00:00
|
|
|
|
2023-06-20 21:53:54 +00:00
|
|
|
"github.com/samber/lo"
|
|
|
|
)
|
|
|
|
|
2023-06-25 22:51:20 +00:00
|
|
|
type Position map[types.Coordinate]Piece
|
2023-06-12 20:32:31 +00:00
|
|
|
|
2023-06-25 22:51:20 +00:00
|
|
|
type Board struct {
|
2023-06-27 21:47:24 +00:00
|
|
|
position Position
|
|
|
|
history []types.Move
|
|
|
|
colorToMove types.ChessColor
|
2023-07-05 19:15:01 +00:00
|
|
|
state types.AdditionalState
|
2023-06-25 22:51:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newBoard() Board {
|
|
|
|
return Board{
|
2023-06-27 21:47:24 +00:00
|
|
|
position: make(Position),
|
|
|
|
history: make([]types.Move, 0),
|
|
|
|
colorToMove: types.White,
|
2023-07-05 19:15:01 +00:00
|
|
|
state: types.AdditionalState{},
|
2023-06-25 22:51:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Board) Init() {
|
2023-06-12 20:32:31 +00:00
|
|
|
var coord types.Coordinate
|
|
|
|
|
|
|
|
for i := 1; i <= 8; i++ {
|
|
|
|
coord.Row = 2
|
|
|
|
coord.Col = i
|
2023-06-25 22:51:20 +00:00
|
|
|
b.position[coord] = Pawn{Color: types.White}
|
2023-06-12 20:32:31 +00:00
|
|
|
|
|
|
|
coord.Row = 7
|
|
|
|
coord.Col = i
|
2023-06-25 22:51:20 +00:00
|
|
|
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}
|
2023-06-12 20:32:31 +00:00
|
|
|
}
|
2023-06-14 17:46:46 +00:00
|
|
|
|
2024-01-17 16:38:06 +00:00
|
|
|
func (b *Board) CheckAndPlay(move *types.Move) (bool, Violation) {
|
2023-06-25 14:11:29 +00:00
|
|
|
tempBoard := b.getCopyOfBoard()
|
|
|
|
|
|
|
|
//Check start square of move
|
2023-06-25 22:51:20 +00:00
|
|
|
pieceAtStartSquare := tempBoard.getPieceAt(move.StartSquare)
|
2023-06-20 21:53:54 +00:00
|
|
|
if pieceAtStartSquare == nil {
|
2023-07-11 20:28:07 +00:00
|
|
|
return false, NoPieceAtStartSquare
|
2023-06-12 20:32:31 +00:00
|
|
|
}
|
2023-06-27 21:47:24 +00:00
|
|
|
|
2023-06-25 22:51:20 +00:00
|
|
|
move.ColorMoved = pieceAtStartSquare.GetColor()
|
2023-06-27 21:47:24 +00:00
|
|
|
if move.ColorMoved != tempBoard.colorToMove {
|
2023-07-11 20:28:07 +00:00
|
|
|
return false, WrongColorMoved
|
2023-06-27 21:47:24 +00:00
|
|
|
}
|
2023-06-25 22:51:20 +00:00
|
|
|
move.PieceMoved = GetShortNameForPiece(pieceAtStartSquare)
|
2023-06-12 20:32:31 +00:00
|
|
|
|
2023-06-25 14:11:29 +00:00
|
|
|
//Check end square of move
|
2023-06-25 22:51:20 +00:00
|
|
|
pieceAtEndSquare := tempBoard.getPieceAt(move.EndSquare)
|
2023-06-20 21:53:54 +00:00
|
|
|
if pieceAtEndSquare != nil {
|
|
|
|
if pieceAtEndSquare.GetColor() == pieceAtStartSquare.GetColor() {
|
2023-07-11 20:28:07 +00:00
|
|
|
return false, TargetSquareIsOccupied
|
2023-06-14 17:46:46 +00:00
|
|
|
}
|
|
|
|
}
|
2023-06-25 14:11:29 +00:00
|
|
|
|
2024-01-17 16:38:06 +00:00
|
|
|
wasSpecialMove, err := tempBoard.handleSpecialMove(*move)
|
2023-08-12 09:24:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, InvalidMove
|
|
|
|
}
|
|
|
|
|
|
|
|
if !wasSpecialMove {
|
2024-01-17 16:38:06 +00:00
|
|
|
allMovesExceptBlocked := pieceAtStartSquare.GetAllNonBlockedSquares(tempBoard, move.StartSquare)
|
2023-06-25 14:11:29 +00:00
|
|
|
legal := lo.Contains(allMovesExceptBlocked, move.EndSquare)
|
|
|
|
if !legal {
|
2023-07-11 20:28:07 +00:00
|
|
|
return false, InvalidMove
|
2023-06-25 14:11:29 +00:00
|
|
|
}
|
2023-06-14 17:46:46 +00:00
|
|
|
|
2023-06-25 14:11:29 +00:00
|
|
|
//We play the move on the temporary board
|
2023-06-25 22:51:20 +00:00
|
|
|
delete(tempBoard.position, move.StartSquare)
|
|
|
|
tempBoard.position[move.EndSquare] = pieceAtStartSquare
|
2023-06-20 21:53:54 +00:00
|
|
|
}
|
|
|
|
|
2023-06-27 20:32:24 +00:00
|
|
|
kingAttacked, err := tempBoard.isKingOfMovingColorInCheck(move.ColorMoved)
|
|
|
|
if err != nil {
|
2023-07-11 20:28:07 +00:00
|
|
|
return false, SomethingWentWrong
|
2023-06-20 21:53:54 +00:00
|
|
|
}
|
2023-06-27 20:32:24 +00:00
|
|
|
if kingAttacked {
|
2023-07-11 20:28:07 +00:00
|
|
|
return false, KingInCheck
|
2023-06-20 21:53:54 +00:00
|
|
|
}
|
2023-06-13 22:59:16 +00:00
|
|
|
|
2023-06-25 14:11:29 +00:00
|
|
|
//We play the move on the real board
|
2023-06-25 22:51:20 +00:00
|
|
|
b.position = tempBoard.position
|
|
|
|
b.history = tempBoard.history
|
2023-06-27 21:47:24 +00:00
|
|
|
b.colorToMove = b.colorToMove.Opposite()
|
2024-01-17 16:38:06 +00:00
|
|
|
b.appendMoveToHistory(*move)
|
2023-07-05 19:15:01 +00:00
|
|
|
|
|
|
|
pieceAtStartSquare.AfterMoveAction(b, move.StartSquare)
|
|
|
|
|
2023-06-12 20:32:31 +00:00
|
|
|
return true, ""
|
|
|
|
}
|
2023-06-14 17:46:46 +00:00
|
|
|
|
2023-06-27 20:32:24 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-06-20 21:53:54 +00:00
|
|
|
func (b Board) getSquareOfPiece(piece Piece) *types.Coordinate {
|
2023-06-25 22:51:20 +00:00
|
|
|
for k, v := range b.position {
|
2023-06-14 17:46:46 +00:00
|
|
|
if v == piece {
|
|
|
|
return &k
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b Board) isSquareAttacked(square types.Coordinate, byColor types.ChessColor) bool {
|
2023-06-20 21:53:54 +00:00
|
|
|
var attackedSquares []types.Coordinate
|
|
|
|
|
2023-06-25 22:51:20 +00:00
|
|
|
for square, piece := range b.position {
|
2023-06-27 20:32:24 +00:00
|
|
|
if piece.GetColor() == byColor {
|
|
|
|
attackedSquares = append(attackedSquares, piece.GetAllAttackedSquares(b, square)...)
|
|
|
|
}
|
2023-06-20 21:53:54 +00:00
|
|
|
}
|
|
|
|
return lo.Contains(attackedSquares, square)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b Board) getPieceAt(coord types.Coordinate) Piece {
|
2023-06-25 22:51:20 +00:00
|
|
|
piece, found := b.position[coord]
|
2023-06-20 21:53:54 +00:00
|
|
|
if !found {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return piece
|
2023-06-14 17:46:46 +00:00
|
|
|
}
|
2023-06-25 14:11:29 +00:00
|
|
|
|
2023-06-25 22:51:20 +00:00
|
|
|
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{
|
2023-06-28 09:39:44 +00:00
|
|
|
position: b.position.getCopyOfPosition(),
|
|
|
|
history: b.history,
|
|
|
|
colorToMove: b.colorToMove,
|
2023-07-05 19:15:01 +00:00
|
|
|
state: b.state,
|
2023-06-25 22:51:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p Position) getCopyOfPosition() Position {
|
|
|
|
position := make(Position)
|
|
|
|
|
|
|
|
for coord, piece := range p {
|
|
|
|
position[coord] = piece
|
|
|
|
}
|
|
|
|
return position
|
|
|
|
}
|
|
|
|
|
2023-07-11 20:28:07 +00:00
|
|
|
func (b *Board) handleSpecialMove(move types.Move) (bool, error) {
|
2023-06-25 22:51:20 +00:00
|
|
|
var was bool
|
2023-08-12 09:24:40 +00:00
|
|
|
var err error
|
2023-06-25 22:51:20 +00:00
|
|
|
var pieceAtStartSquare = b.getPieceAt(move.StartSquare)
|
|
|
|
|
2023-06-28 09:39:44 +00:00
|
|
|
switch piece := pieceAtStartSquare.(type) {
|
2023-06-25 22:51:20 +00:00
|
|
|
case Pawn:
|
2023-07-11 20:28:07 +00:00
|
|
|
was, err = piece.HandlePossiblePromotion(b, move)
|
2023-06-25 22:51:20 +00:00
|
|
|
if !was {
|
2023-07-11 20:28:07 +00:00
|
|
|
was, err = piece.HandleEnPassant(b, move, b.getLastMove())
|
2023-06-25 22:51:20 +00:00
|
|
|
}
|
2023-07-05 19:15:01 +00:00
|
|
|
case King:
|
2023-07-11 20:28:07 +00:00
|
|
|
was, err = piece.HandleCastling(b, move)
|
2023-06-25 22:51:20 +00:00
|
|
|
}
|
2023-08-12 09:24:40 +00:00
|
|
|
return was, err
|
2023-06-25 22:51:20 +00:00
|
|
|
}
|
2024-01-17 16:38:06 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|