280 lines
7.6 KiB
Go
280 lines
7.6 KiB
Go
package chess
|
|
|
|
import (
|
|
"mchess_server/types"
|
|
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
type Position map[types.Coordinate]Piece
|
|
|
|
type Board struct {
|
|
position Position
|
|
history []types.Move
|
|
}
|
|
|
|
func newBoard() Board {
|
|
return Board{
|
|
position: make(Position),
|
|
history: make([]types.Move, 0),
|
|
}
|
|
}
|
|
|
|
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, string) {
|
|
// 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 := tempBoard.getPieceAt(move.StartSquare)
|
|
if pieceAtStartSquare == nil {
|
|
return false, "no piece at start square"
|
|
}
|
|
move.ColorMoved = pieceAtStartSquare.GetColor()
|
|
move.PieceMoved = GetShortNameForPiece(pieceAtStartSquare)
|
|
|
|
//Check end square of move
|
|
pieceAtEndSquare := tempBoard.getPieceAt(move.EndSquare)
|
|
if pieceAtEndSquare != nil {
|
|
if pieceAtEndSquare.GetColor() == pieceAtStartSquare.GetColor() {
|
|
return false, "same-coloured piece at end square"
|
|
}
|
|
}
|
|
|
|
wasSpecialMove := tempBoard.handleSpecialMove(move)
|
|
|
|
if !wasSpecialMove {
|
|
// 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"
|
|
}
|
|
|
|
//We play the move on the temporary board
|
|
delete(tempBoard.position, move.StartSquare)
|
|
tempBoard.position[move.EndSquare] = pieceAtStartSquare
|
|
}
|
|
|
|
//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 := tempBoard.getSquareOfPiece(King{Color: move.ColorMoved})
|
|
if ownKingCoordinate == nil {
|
|
return false, string(move.ColorMoved) + " king not found"
|
|
}
|
|
|
|
kingIsAttacked := tempBoard.isSquareAttacked(*ownKingCoordinate, move.ColorMoved.Opposite())
|
|
if kingIsAttacked {
|
|
return false, "king is attacked after move"
|
|
}
|
|
|
|
//Check for checkmat&e
|
|
//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
|
|
|
|
//Maybe for checking checkmate, we have to check the 'path' in which the
|
|
//checkmate is given
|
|
|
|
// |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
|
|
|
|
//We play the move on the real board
|
|
b.position = tempBoard.position
|
|
b.history = tempBoard.history
|
|
b.appendMoveToHistory(move)
|
|
|
|
return true, ""
|
|
}
|
|
|
|
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 {
|
|
attackedSquares = append(attackedSquares, piece.GetAllMovesButBlocked(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,
|
|
}
|
|
}
|
|
|
|
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 {
|
|
var was bool
|
|
var pieceAtStartSquare = b.getPieceAt(move.StartSquare)
|
|
|
|
switch pieceAtStartSquare.(type) {
|
|
case Pawn:
|
|
was = b.handlePossiblePromotion(move)
|
|
if !was {
|
|
was = b.handleEnPassant(move, b.getLastMove())
|
|
}
|
|
}
|
|
return was
|
|
}
|
|
|
|
func (b *Board) handlePossiblePromotion(move types.Move) bool {
|
|
var isPromotionMove bool
|
|
var promotionToPiece types.PieceShortName
|
|
|
|
//TODO(m): What if message does not contain a promotion, but should be because a pawn is moved to the end square
|
|
messageContainsPromotion := move.IsPromotionMove()
|
|
|
|
if messageContainsPromotion {
|
|
promotionToPiece = *move.PromotionToPiece
|
|
}
|
|
|
|
switch move.ColorMoved {
|
|
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.position, move.StartSquare)
|
|
b.position[move.EndSquare] = GetPieceForShortName(promotionToPiece, move.ColorMoved)
|
|
}
|
|
|
|
return isPromotionMove
|
|
}
|
|
|
|
func (b *Board) handleEnPassant(move, lastMove types.Move) bool {
|
|
var wasEnPassant bool
|
|
|
|
if lastMove.PieceMoved != types.PawnShortName {
|
|
return false
|
|
}
|
|
|
|
switch move.ColorMoved {
|
|
case types.White:
|
|
if lastMove.StartSquare.Row != 7 || lastMove.EndSquare.Row != 5 {
|
|
wasEnPassant = false
|
|
}
|
|
if move.StartSquare.Row != 5 {
|
|
wasEnPassant = false
|
|
}
|
|
if move.EndSquare.Row != 6 {
|
|
wasEnPassant = false
|
|
}
|
|
if move.StartSquare.Col == lastMove.EndSquare.Col+1 &&
|
|
move.EndSquare.Col == lastMove.EndSquare.Col {
|
|
wasEnPassant = true
|
|
}
|
|
if move.StartSquare.Col == lastMove.EndSquare.Col-1 &&
|
|
move.EndSquare.Col == lastMove.EndSquare.Col {
|
|
wasEnPassant = true
|
|
}
|
|
case types.Black:
|
|
if lastMove.StartSquare.Row != 2 || lastMove.EndSquare.Row != 4 {
|
|
wasEnPassant = false
|
|
}
|
|
if move.StartSquare.Row != 4 {
|
|
wasEnPassant = false
|
|
}
|
|
if move.EndSquare.Row != 3 {
|
|
wasEnPassant = false
|
|
}
|
|
if move.StartSquare.Col == lastMove.EndSquare.Col+1 &&
|
|
move.EndSquare.Col == lastMove.EndSquare.Col {
|
|
wasEnPassant = true
|
|
}
|
|
if move.StartSquare.Col == lastMove.EndSquare.Col-1 &&
|
|
move.EndSquare.Col == lastMove.EndSquare.Col {
|
|
wasEnPassant = true
|
|
}
|
|
}
|
|
|
|
if wasEnPassant { //play the move
|
|
delete(b.position, lastMove.EndSquare)
|
|
b.position[move.EndSquare] = GetPieceForShortName(move.PieceMoved, move.ColorMoved)
|
|
}
|
|
|
|
return wasEnPassant
|
|
}
|