mchess-server/chess/board.go

183 lines
5.2 KiB
Go
Raw Normal View History

2023-06-12 20:32:31 +00:00
package chess
import (
2023-06-25 14:11:29 +00:00
"mchess_server/types"
2023-06-12 20:32:31 +00:00
"github.com/samber/lo"
)
type Board map[types.Coordinate]Piece
2023-06-12 20:32:31 +00:00
func (b Board) Init() {
var coord types.Coordinate
for i := 1; i <= 8; i++ {
coord.Row = 2
coord.Col = i
2023-06-25 14:11:29 +00:00
b[coord] = Pawn{Color: types.White}
2023-06-12 20:32:31 +00:00
coord.Row = 7
coord.Col = i
2023-06-25 14:11:29 +00:00
b[coord] = Pawn{Color: types.Black}
2023-06-12 20:32:31 +00:00
}
b[types.Coordinate{Row: 1, Col: 1}] = Rook{Color: types.White}
b[types.Coordinate{Row: 1, Col: 2}] = Knight{Color: types.White}
b[types.Coordinate{Row: 1, Col: 3}] = Bishop{Color: types.White}
b[types.Coordinate{Row: 1, Col: 4}] = Queen{Color: types.White}
b[types.Coordinate{Row: 1, Col: 5}] = King{Color: types.White}
b[types.Coordinate{Row: 1, Col: 6}] = Bishop{Color: types.White}
b[types.Coordinate{Row: 1, Col: 7}] = Knight{Color: types.White}
b[types.Coordinate{Row: 1, Col: 8}] = Rook{Color: types.White}
b[types.Coordinate{Row: 8, Col: 1}] = Rook{Color: types.Black}
b[types.Coordinate{Row: 8, Col: 2}] = Knight{Color: types.Black}
b[types.Coordinate{Row: 8, Col: 3}] = Bishop{Color: types.Black}
b[types.Coordinate{Row: 8, Col: 4}] = Queen{Color: types.Black}
b[types.Coordinate{Row: 8, Col: 5}] = King{Color: types.Black}
b[types.Coordinate{Row: 8, Col: 6}] = Bishop{Color: types.Black}
b[types.Coordinate{Row: 8, Col: 7}] = Knight{Color: types.Black}
b[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
func (b Board) CheckMove(move types.Move) (bool, string) {
2023-06-25 14:11:29 +00:00
// We make a copy of the original board to play moves on it,
// We can play the move on it and then check if it is invalid
tempBoard := b.getCopyOfBoard()
//Check start square of move
pieceAtStartSquare := b.getPieceAt(move.StartSquare)
if pieceAtStartSquare == nil {
2023-06-12 20:32:31 +00:00
return false, "no piece at start square"
}
movingColor := pieceAtStartSquare.GetColor()
2023-06-12 20:32:31 +00:00
2023-06-25 14:11:29 +00:00
//Check end square of move
pieceAtEndSquare := b.getPieceAt(move.EndSquare)
if pieceAtEndSquare != nil {
if pieceAtEndSquare.GetColor() == pieceAtStartSquare.GetColor() {
2023-06-14 17:46:46 +00:00
return false, "same-coloured piece at end square"
}
}
2023-06-25 14:11:29 +00:00
var wasPromotionMove bool
// var piece types.PieceShortName
switch pieceAtStartSquare.(type) {
case Pawn:
wasPromotionMove, _ = tempBoard.handlePossiblePromotion(move, movingColor)
}
if !wasPromotionMove {
// At the moment, we do not need to check if the correct color is moving,
//since we are only reading moves from the player whose turn it is.
allMovesExceptBlocked := pieceAtStartSquare.GetAllMovesButBlocked(tempBoard, move.StartSquare)
legal := lo.Contains(allMovesExceptBlocked, move.EndSquare)
if !legal {
return false, "not a legal square"
}
2023-06-14 17:46:46 +00:00
2023-06-25 14:11:29 +00:00
//We play the move on the temporary board
delete(tempBoard, move.StartSquare)
tempBoard[move.EndSquare] = pieceAtStartSquare
}
2023-06-14 17:46:46 +00:00
//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.
2023-06-25 14:11:29 +00:00
ownKingCoordinate := tempBoard.getSquareOfPiece(King{Color: movingColor})
if ownKingCoordinate == nil {
return false, string(movingColor) + " king not found"
}
2023-06-25 14:11:29 +00:00
kingIsAttacked := tempBoard.isSquareAttacked(*ownKingCoordinate, movingColor.Opposite())
if kingIsAttacked {
return false, "king is attacked after move"
}
2023-06-25 14:11:29 +00:00
//Check for checkmat&e
2023-06-14 17:46:46 +00:00
//Is every square that the king can move to attacked? And can no other
//piece block? -> checkmate
//only check if paths of attacking pieces can be blocked
2023-06-14 17:46:46 +00:00
//Maybe for checking checkmate, we have to check the 'path' in which the
//checkmate is given
2023-06-14 17:46:46 +00:00
// |K| | | | |Q|
// in this scenaria the path are all the squares between queen and king.
// If a piece can be moved into the path, it is no checkmate
2023-06-25 14:11:29 +00:00
//We play the move on the real board
b = tempBoard
2023-06-12 20:32:31 +00:00
return true, ""
}
2023-06-14 17:46:46 +00:00
func (b Board) getSquareOfPiece(piece Piece) *types.Coordinate {
2023-06-14 17:46:46 +00:00
for k, v := range b {
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 {
2023-06-25 14:11:29 +00:00
attackedSquares = append(attackedSquares, piece.GetAllMovesButBlocked(b, square)...)
}
return lo.Contains(attackedSquares, square)
}
func (b Board) getPieceAt(coord types.Coordinate) Piece {
piece, found := b[coord]
if !found {
return nil
}
return piece
2023-06-14 17:46:46 +00:00
}
2023-06-25 14:11:29 +00:00
func (b Board) handlePossiblePromotion(move types.Move, color types.ChessColor) (bool, types.PieceShortName) {
var isPromotionMove bool
var promotionToPiece types.PieceShortName
messageContainsPromotion := move.IsPromotionMove()
if messageContainsPromotion {
promotionToPiece = *move.PromotionToPiece
}
switch color {
case types.White:
if move.StartSquare.Row == types.RangeLastValid-1 &&
move.EndSquare.Row == types.RangeLastValid {
isPromotionMove = true
}
case types.Black:
if move.StartSquare.Row == types.RangeFirstValid+1 &&
move.EndSquare.Row == types.RangeFirstValid {
isPromotionMove = true
}
}
if isPromotionMove {
delete(b, move.StartSquare)
b[move.EndSquare] = GetPieceForShortName(promotionToPiece)
}
return isPromotionMove, promotionToPiece
}
func (b Board) getCopyOfBoard() Board {
board := make(map[types.Coordinate]Piece)
for coord, piece := range b {
board[coord] = piece
}
return board
}