Merge pull request 'checkmate-and-stalemate-check' (#2) from checkmate-and-stalemate-check into master

Reviewed-on: #2
This commit is contained in:
marco 2024-01-17 23:07:14 +01:00
commit 0190ff99d4
15 changed files with 243 additions and 57 deletions

View File

@ -21,6 +21,7 @@ const (
MoveMessage MessageType = "move" MoveMessage MessageType = "move"
InvalidMoveMessage MessageType = "invalidMove" InvalidMoveMessage MessageType = "invalidMove"
ColorDetermined MessageType = "colorDetermined" ColorDetermined MessageType = "colorDetermined"
GameEnded MessageType = "gameEnded"
) )
func (m WebsocketMessage) IsValid() bool { func (m WebsocketMessage) IsValid() bool {

View File

@ -9,14 +9,14 @@ type Bishop struct {
} }
func (b Bishop) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate { func (b Bishop) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return b.GetAllNonBlockedMoves(board, fromSquare) return b.GetAllNonBlockedSquares(board, fromSquare)
} }
func (b Bishop) GetColor() types.ChessColor { func (b Bishop) GetColor() types.ChessColor {
return b.Color return b.Color
} }
func (b Bishop) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate { func (b Bishop) GetAllNonBlockedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return board.GetNonBlockedDiagonals(fromSquare) return board.GetNonBlockedDiagonals(fromSquare)
} }

View File

@ -57,7 +57,7 @@ func (b *Board) Init() {
b.position[types.Coordinate{Row: 8, Col: 8}] = Rook{Color: types.Black} b.position[types.Coordinate{Row: 8, Col: 8}] = Rook{Color: types.Black}
} }
func (b *Board) CheckAndPlay(move types.Move) (bool, Violation) { func (b *Board) CheckAndPlay(move *types.Move) (bool, Violation) {
tempBoard := b.getCopyOfBoard() tempBoard := b.getCopyOfBoard()
//Check start square of move //Check start square of move
@ -80,13 +80,13 @@ func (b *Board) CheckAndPlay(move types.Move) (bool, Violation) {
} }
} }
wasSpecialMove, err := tempBoard.handleSpecialMove(move) wasSpecialMove, err := tempBoard.handleSpecialMove(*move)
if err != nil { if err != nil {
return false, InvalidMove return false, InvalidMove
} }
if !wasSpecialMove { if !wasSpecialMove {
allMovesExceptBlocked := pieceAtStartSquare.GetAllNonBlockedMoves(tempBoard, move.StartSquare) allMovesExceptBlocked := pieceAtStartSquare.GetAllNonBlockedSquares(tempBoard, move.StartSquare)
legal := lo.Contains(allMovesExceptBlocked, move.EndSquare) legal := lo.Contains(allMovesExceptBlocked, move.EndSquare)
if !legal { if !legal {
return false, InvalidMove return false, InvalidMove
@ -109,7 +109,7 @@ func (b *Board) CheckAndPlay(move types.Move) (bool, Violation) {
b.position = tempBoard.position b.position = tempBoard.position
b.history = tempBoard.history b.history = tempBoard.history
b.colorToMove = b.colorToMove.Opposite() b.colorToMove = b.colorToMove.Opposite()
b.appendMoveToHistory(move) b.appendMoveToHistory(*move)
pieceAtStartSquare.AfterMoveAction(b, move.StartSquare) pieceAtStartSquare.AfterMoveAction(b, move.StartSquare)
@ -204,3 +204,66 @@ func (b *Board) handleSpecialMove(move types.Move) (bool, error) {
} }
return was, err 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
}

View File

@ -20,7 +20,7 @@ func Test_CheckMove_validPawnMove(t *testing.T) {
EndSquare: types.Coordinate{Col: 1, Row: 3}, EndSquare: types.Coordinate{Col: 1, Row: 3},
} }
good, _ := board.CheckAndPlay(move) good, _ := board.CheckAndPlay(&move)
assert.True(t, good) assert.True(t, good)
//we take the pawn //we take the pawn
@ -28,7 +28,7 @@ func Test_CheckMove_validPawnMove(t *testing.T) {
StartSquare: types.Coordinate{Col: 2, Row: 4}, StartSquare: types.Coordinate{Col: 2, Row: 4},
EndSquare: types.Coordinate{Col: 1, Row: 3}, EndSquare: types.Coordinate{Col: 1, Row: 3},
} }
good, _ = board.CheckAndPlay(secondMove) good, _ = board.CheckAndPlay(&secondMove)
assert.True(t, good) assert.True(t, good)
} }
@ -45,7 +45,7 @@ func Test_CheckMove_enPassant(t *testing.T) {
EndSquare: types.Coordinate{Col: 5, Row: 4}, EndSquare: types.Coordinate{Col: 5, Row: 4},
} }
good, reason := board.CheckAndPlay(move) good, reason := board.CheckAndPlay(&move)
assert.True(t, good) assert.True(t, good)
assert.Empty(t, reason) assert.Empty(t, reason)
assert.Equal(t, Pawn{Color: types.White}, board.position[types.Coordinate{Col: 5, Row: 4}]) assert.Equal(t, Pawn{Color: types.White}, board.position[types.Coordinate{Col: 5, Row: 4}])
@ -54,7 +54,7 @@ func Test_CheckMove_enPassant(t *testing.T) {
StartSquare: types.Coordinate{Col: 6, Row: 4}, StartSquare: types.Coordinate{Col: 6, Row: 4},
EndSquare: types.Coordinate{Col: 5, Row: 3}, EndSquare: types.Coordinate{Col: 5, Row: 3},
} }
good, reason = board.CheckAndPlay(newMove) good, reason = board.CheckAndPlay(&newMove)
assert.True(t, good) assert.True(t, good)
assert.Empty(t, reason) assert.Empty(t, reason)
// the black pawn is on its correct square // the black pawn is on its correct square
@ -77,7 +77,7 @@ func Test_CheckMove_invalidPawnMoves(t *testing.T) {
StartSquare: types.Coordinate{Col: 2, Row: 5}, StartSquare: types.Coordinate{Col: 2, Row: 5},
EndSquare: types.Coordinate{Col: 2, Row: 6}, EndSquare: types.Coordinate{Col: 2, Row: 6},
} }
legalMove, _ := board.CheckAndPlay(move) legalMove, _ := board.CheckAndPlay(&move)
assert.False(t, legalMove) assert.False(t, legalMove)
}) })
@ -93,7 +93,7 @@ func Test_CheckMove_invalidPawnMoves(t *testing.T) {
StartSquare: types.Coordinate{Col: 2, Row: 5}, StartSquare: types.Coordinate{Col: 2, Row: 5},
EndSquare: types.Coordinate{Col: 3, Row: 5}, EndSquare: types.Coordinate{Col: 3, Row: 5},
} }
legal, _ := board.CheckAndPlay(move) legal, _ := board.CheckAndPlay(&move)
assert.False(t, legal) assert.False(t, legal)
assert.Equal(t, boardBeforeMove, board) assert.Equal(t, boardBeforeMove, board)
@ -102,7 +102,7 @@ func Test_CheckMove_invalidPawnMoves(t *testing.T) {
StartSquare: types.Coordinate{Col: 2, Row: 5}, StartSquare: types.Coordinate{Col: 2, Row: 5},
EndSquare: types.Coordinate{Col: 1, Row: 5}, EndSquare: types.Coordinate{Col: 1, Row: 5},
} }
legal, _ = board.CheckAndPlay(move) legal, _ = board.CheckAndPlay(&move)
assert.False(t, legal) assert.False(t, legal)
assert.Equal(t, boardBeforeMove, board) assert.Equal(t, boardBeforeMove, board)
@ -111,7 +111,7 @@ func Test_CheckMove_invalidPawnMoves(t *testing.T) {
StartSquare: types.Coordinate{Col: 2, Row: 5}, StartSquare: types.Coordinate{Col: 2, Row: 5},
EndSquare: types.Coordinate{Col: 6, Row: 5}, EndSquare: types.Coordinate{Col: 6, Row: 5},
} }
legal, _ = board.CheckAndPlay(move) legal, _ = board.CheckAndPlay(&move)
assert.False(t, legal) assert.False(t, legal)
assert.Equal(t, boardBeforeMove, board) assert.Equal(t, boardBeforeMove, board)
@ -130,7 +130,7 @@ func Test_CheckMove_invalidPawnMoves(t *testing.T) {
StartSquare: types.Coordinate{Col: 2, Row: 6}, StartSquare: types.Coordinate{Col: 2, Row: 6},
EndSquare: types.Coordinate{Col: 2, Row: 7}, EndSquare: types.Coordinate{Col: 2, Row: 7},
} }
good, _ := board.CheckAndPlay(move) good, _ := board.CheckAndPlay(&move)
assert.False(t, good) assert.False(t, good)
assert.Equal(t, boardBeforeMove, board) assert.Equal(t, boardBeforeMove, board)
@ -151,7 +151,7 @@ func Test_CheckMove_validPromotion(t *testing.T) {
EndSquare: types.Coordinate{Col: 1, Row: 8}, EndSquare: types.Coordinate{Col: 1, Row: 8},
PromotionToPiece: &shortName, PromotionToPiece: &shortName,
} }
good, reason := board.CheckAndPlay(move) good, reason := board.CheckAndPlay(&move)
assert.Empty(t, reason) assert.Empty(t, reason)
assert.True(t, good) assert.True(t, good)
@ -179,13 +179,13 @@ func Test_CheckMove_HistoryWorks(t *testing.T) {
EndSquare: types.Coordinate{Col: 1, Row: 4}, EndSquare: types.Coordinate{Col: 1, Row: 4},
} }
good, _ := board.CheckAndPlay(firstMove) good, _ := board.CheckAndPlay(&firstMove)
assert.True(t, good) assert.True(t, good)
good, _ = board.CheckAndPlay(secondMove) good, _ = board.CheckAndPlay(&secondMove)
assert.True(t, good) assert.True(t, good)
good, _ = board.CheckAndPlay(thirdMove) good, _ = board.CheckAndPlay(&thirdMove)
assert.True(t, good) assert.True(t, good)
expectedHistory := []types.Move{ expectedHistory := []types.Move{
@ -221,13 +221,13 @@ func Test_Promotion_BlackKing(t *testing.T) {
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black} board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
//Make dummy move for white //Make dummy move for white
board.CheckAndPlay(types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}}) board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{ move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8}, StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 7, Row: 8}, EndSquare: types.Coordinate{Col: 7, Row: 8},
} }
good, reason := board.CheckAndPlay(move) good, reason := board.CheckAndPlay(&move)
assert.True(t, good) assert.True(t, good)
assert.Empty(t, reason) assert.Empty(t, reason)
@ -246,13 +246,13 @@ func Test_Promotion_BlackKing(t *testing.T) {
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black} board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
//Make dummy move for white //Make dummy move for white
board.CheckAndPlay(types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}}) board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{ move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8}, StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8}, EndSquare: types.Coordinate{Col: 3, Row: 8},
} }
good, reason := board.CheckAndPlay(move) good, reason := board.CheckAndPlay(&move)
assert.True(t, good) assert.True(t, good)
assert.Empty(t, reason) assert.Empty(t, reason)
@ -272,25 +272,25 @@ func Test_Promotion_BlackKing(t *testing.T) {
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black} board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
//Make dummy move for white //Make dummy move for white
board.CheckAndPlay(types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}}) board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
//Move black //Move black
board.CheckAndPlay(types.Move{StartSquare: types.Coordinate{Col: 5, Row: 8}, EndSquare: types.Coordinate{Col: 4, Row: 8}}) board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 8}, EndSquare: types.Coordinate{Col: 4, Row: 8}})
//Make dummy move for white //Make dummy move for white
board.CheckAndPlay(types.Move{StartSquare: types.Coordinate{Col: 5, Row: 2}, EndSquare: types.Coordinate{Col: 5, Row: 1}}) board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 2}, EndSquare: types.Coordinate{Col: 5, Row: 1}})
//Move black back //Move black back
board.CheckAndPlay(types.Move{StartSquare: types.Coordinate{Col: 4, Row: 8}, EndSquare: types.Coordinate{Col: 5, Row: 8}}) board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 4, Row: 8}, EndSquare: types.Coordinate{Col: 5, Row: 8}})
//Make dummy move for white //Make dummy move for white
board.CheckAndPlay(types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}}) board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{ move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8}, StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8}, EndSquare: types.Coordinate{Col: 3, Row: 8},
} }
good, _ := board.CheckAndPlay(move) good, _ := board.CheckAndPlay(&move)
assert.False(t, good) assert.False(t, good)
}) })
@ -304,13 +304,13 @@ func Test_Promotion_BlackKing(t *testing.T) {
board.position[types.Coordinate{Col: 7, Row: 8}] = Bishop{Color: types.Black} board.position[types.Coordinate{Col: 7, Row: 8}] = Bishop{Color: types.Black}
//Make dummy move for white //Make dummy move for white
board.CheckAndPlay(types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}}) board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{ move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8}, StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 7, Row: 8}, EndSquare: types.Coordinate{Col: 7, Row: 8},
} }
good, _ := board.CheckAndPlay(move) good, _ := board.CheckAndPlay(&move)
assert.False(t, good) assert.False(t, good)
}) })
@ -324,13 +324,13 @@ func Test_Promotion_BlackKing(t *testing.T) {
board.position[types.Coordinate{Col: 4, Row: 8}] = Bishop{Color: types.Black} board.position[types.Coordinate{Col: 4, Row: 8}] = Bishop{Color: types.Black}
//Make dummy move for white //Make dummy move for white
board.CheckAndPlay(types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}}) board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{ move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8}, StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8}, EndSquare: types.Coordinate{Col: 3, Row: 8},
} }
good, _ := board.CheckAndPlay(move) good, _ := board.CheckAndPlay(&move)
assert.False(t, good) assert.False(t, good)
}) })
@ -344,13 +344,13 @@ func Test_Promotion_BlackKing(t *testing.T) {
board.position[types.Coordinate{Col: 3, Row: 8}] = Bishop{Color: types.Black} board.position[types.Coordinate{Col: 3, Row: 8}] = Bishop{Color: types.Black}
//Make dummy move for white //Make dummy move for white
board.CheckAndPlay(types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}}) board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{ move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8}, StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8}, EndSquare: types.Coordinate{Col: 3, Row: 8},
} }
good, _ := board.CheckAndPlay(move) good, _ := board.CheckAndPlay(&move)
assert.False(t, good) assert.False(t, good)
}) })
@ -364,13 +364,88 @@ func Test_Promotion_BlackKing(t *testing.T) {
board.position[types.Coordinate{Col: 2, Row: 8}] = Bishop{Color: types.Black} board.position[types.Coordinate{Col: 2, Row: 8}] = Bishop{Color: types.Black}
//Make dummy move for white //Make dummy move for white
board.CheckAndPlay(types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}}) board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{ move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8}, StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8}, EndSquare: types.Coordinate{Col: 3, Row: 8},
} }
good, _ := board.CheckAndPlay(move) good, _ := board.CheckAndPlay(&move)
assert.False(t, good) assert.False(t, good)
}) })
} }
func Test_Board_HasGameEnded(t *testing.T) {
board := newBoard()
t.Run("no checkmate yet", func(t *testing.T) {
board.position[types.Coordinate{Col: 1, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 7, Row: 6}] = Queen{Color: types.White}
board.position[types.Coordinate{Col: 7, Row: 7}] = Queen{Color: types.White}
board.position[types.Coordinate{Col: 2, Row: 8}] = King{Color: types.Black}
whiteMove := types.Move{
StartSquare: types.Coordinate{Col: 7, Row: 6},
EndSquare: types.Coordinate{Col: 8, Row: 6},
ColorMoved: types.White,
}
valid, violation := board.CheckAndPlay(&whiteMove)
assert.True(t, valid)
assert.Empty(t, violation)
gameEnded, reason := board.HasGameEnded(whiteMove)
assert.False(t, gameEnded)
assert.Equal(t, NoReason, reason)
})
t.Run("checkmate move is made", func(t *testing.T) {
blackMove := types.Move{
StartSquare: types.Coordinate{Col: 2, Row: 8},
EndSquare: types.Coordinate{Col: 1, Row: 8},
ColorMoved: types.Black,
}
valid, violation := board.CheckAndPlay(&blackMove)
assert.True(t, valid)
assert.Empty(t, violation)
checkmateMove := types.Move{
StartSquare: types.Coordinate{Col: 8, Row: 6},
EndSquare: types.Coordinate{Col: 8, Row: 8},
ColorMoved: types.White,
}
valid, violation = board.CheckAndPlay(&checkmateMove)
assert.True(t, valid)
assert.Empty(t, violation)
gameEnded, reason := board.HasGameEnded(checkmateMove)
assert.True(t, gameEnded)
assert.Equal(t, BlackIsCheckmated, reason)
})
}
func Test_Board_HasGameEnded_RookBlocks(t *testing.T) {
board := newBoard()
board.position[types.Coordinate{Col: 1, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 8, Row: 7}] = Queen{Color: types.White}
board.position[types.Coordinate{Col: 7, Row: 7}] = Queen{Color: types.White}
board.position[types.Coordinate{Col: 1, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 2, Row: 2}] = Rook{Color: types.Black}
whiteMove := types.Move{
StartSquare: types.Coordinate{Col: 8, Row: 7},
EndSquare: types.Coordinate{Col: 8, Row: 8},
ColorMoved: types.White,
}
valid, violation := board.CheckAndPlay(&whiteMove)
assert.True(t, valid)
assert.Empty(t, violation)
gameEnded, reason := board.HasGameEnded(whiteMove)
assert.False(t, gameEnded)
assert.Equal(t, NoReason, reason)
}

View File

@ -27,6 +27,7 @@ const (
PlayerToMove PlayerToMove
CheckMove CheckMove
CheckPlayerChange CheckPlayerChange
GameEnded
) )
func NewGame() *Game { func NewGame() *Game {
@ -85,6 +86,7 @@ func (game *Game) Handle() {
defer game.killGame() defer game.killGame()
var receivedMove types.Move var receivedMove types.Move
var gameEndReason GameEndedReason
var err error var err error
for { for {
@ -108,11 +110,9 @@ func (game *Game) Handle() {
game.gameState = CheckMove game.gameState = CheckMove
case CheckMove: case CheckMove:
valid, ruleViolation := game.board.CheckAndPlay(receivedMove) valid, ruleViolation := game.board.CheckAndPlay(&receivedMove)
if valid { if !valid {
game.gameState = CheckPlayerChange
} else {
invalidMoveMessage, err := api.GetInvalidMoveMessage(receivedMove, ruleViolation.String()) invalidMoveMessage, err := api.GetInvalidMoveMessage(receivedMove, ruleViolation.String())
if err != nil { if err != nil {
log.Println("Error marshalling 'colorDetermined' message for player 1", err) log.Println("Error marshalling 'colorDetermined' message for player 1", err)
@ -120,7 +120,11 @@ func (game *Game) Handle() {
} }
game.currentTurnPlayer.writeMessage(invalidMoveMessage) game.currentTurnPlayer.writeMessage(invalidMoveMessage)
game.gameState = PlayerToMove game.gameState = PlayerToMove
continue
} }
game.gameState = CheckPlayerChange
case CheckPlayerChange: case CheckPlayerChange:
if game.currentTurnPlayer.Uuid == game.players[0].Uuid { if game.currentTurnPlayer.Uuid == game.players[0].Uuid {
game.currentTurnPlayer = game.players[1] game.currentTurnPlayer = game.players[1]
@ -134,7 +138,17 @@ func (game *Game) Handle() {
return return
} }
if gameEnded, reason := game.board.HasGameEnded(receivedMove); gameEnded {
gameEndReason = reason
game.gameState = GameEnded
continue
}
game.gameState = PlayerToMove game.gameState = PlayerToMove
case GameEnded:
game.broadcastGameEnd(gameEndReason)
return
} }
log.Println("GameState = ", game.gameState) log.Println("GameState = ", game.gameState)
} }
@ -181,6 +195,20 @@ func (game Game) broadcastMove(move types.Move) error {
return nil return nil
} }
func (game Game) broadcastGameEnd(reason GameEndedReason) error {
err := game.GetPlayer1().SendGameEnded(reason)
if err != nil {
return err
}
err = game.GetPlayer2().SendGameEnded(reason)
if err != nil {
return err
}
return nil
}
func (game *Game) playerDisconnected(p *Player) { func (game *Game) playerDisconnected(p *Player) {
log.Println(string(p.color), " disconnected") log.Println(string(p.color), " disconnected")
playerStillInGame := lo.Filter(game.players, func(player *Player, _ int) bool { playerStillInGame := lo.Filter(game.players, func(player *Player, _ int) bool {

View File

@ -9,14 +9,14 @@ type King struct {
} }
func (k King) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate { func (k King) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return k.GetAllNonBlockedMoves(board, fromSquare) return k.GetAllNonBlockedSquares(board, fromSquare)
} }
func (k King) GetColor() types.ChessColor { func (k King) GetColor() types.ChessColor {
return k.Color return k.Color
} }
func (k King) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate { func (k King) GetAllNonBlockedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return board.GetNonBlockedKingMoves(fromSquare) return board.GetNonBlockedKingMoves(fromSquare)
} }

View File

@ -9,14 +9,14 @@ type Knight struct {
} }
func (n Knight) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate { func (n Knight) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return n.GetAllNonBlockedMoves(board, fromSquare) return n.GetAllNonBlockedSquares(board, fromSquare)
} }
func (n Knight) GetColor() types.ChessColor { func (n Knight) GetColor() types.ChessColor {
return n.Color return n.Color
} }
func (n Knight) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate { func (n Knight) GetAllNonBlockedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return board.GetNonBlockedKnightMoves(fromSquare) return board.GetNonBlockedKnightMoves(fromSquare)
} }

View File

@ -12,7 +12,7 @@ type Pawn struct {
func (p Pawn) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate { func (p Pawn) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
attackingMoves := make([]types.Coordinate, 0, 2) attackingMoves := make([]types.Coordinate, 0, 2)
allMoves := p.GetAllNonBlockedMoves(board, fromSquare) allMoves := p.GetAllNonBlockedSquares(board, fromSquare)
for _, move := range allMoves { for _, move := range allMoves {
if move.Col != fromSquare.Col { if move.Col != fromSquare.Col {
attackingMoves = append(attackingMoves, move) attackingMoves = append(attackingMoves, move)
@ -21,7 +21,7 @@ func (p Pawn) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []
return attackingMoves return attackingMoves
} }
func (p Pawn) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate { func (p Pawn) GetAllNonBlockedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
theoreticalSquares := p.getAllMoves(fromSquare) theoreticalSquares := p.getAllMoves(fromSquare)
legalSquares := p.filterBlockedSquares(board, fromSquare, theoreticalSquares) legalSquares := p.filterBlockedSquares(board, fromSquare, theoreticalSquares)

View File

@ -6,7 +6,7 @@ import (
) )
type Piece interface { type Piece interface {
GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate GetAllNonBlockedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate
GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate
GetColor() types.ChessColor GetColor() types.ChessColor
AfterMoveAction(board *Board, fromSquare types.Coordinate) AfterMoveAction(board *Board, fromSquare types.Coordinate)

View File

@ -107,6 +107,24 @@ func (p *Player) SendMoveAndPosition(move types.Move, boardPosition string) erro
return nil return nil
} }
func (p *Player) SendGameEnded(reason GameEndedReason) error {
reasonToSend := reason.String()
messageToSend, err := json.Marshal(api.WebsocketMessage{
Type: api.GameEnded,
Reason: &reasonToSend,
})
if err != nil {
log.Println("Error while marshalling: ", err)
return err
}
err = p.writeMessage(messageToSend)
if err != nil {
log.Println("Error during message writing:", err)
return err
}
return nil
}
func (p *Player) writeMessage(msg []byte) error { func (p *Player) writeMessage(msg []byte) error {
return p.Conn.Write(msg) return p.Conn.Write(msg)
} }

View File

@ -9,14 +9,14 @@ type Queen struct {
} }
func (q Queen) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate { func (q Queen) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return q.GetAllNonBlockedMoves(board, fromSquare) return q.GetAllNonBlockedSquares(board, fromSquare)
} }
func (q Queen) GetColor() types.ChessColor { func (q Queen) GetColor() types.ChessColor {
return q.Color return q.Color
} }
func (q Queen) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate { func (q Queen) GetAllNonBlockedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
squares := board.GetNonBlockedRowAndColumn(fromSquare) squares := board.GetNonBlockedRowAndColumn(fromSquare)
squares = append(squares, board.GetNonBlockedDiagonals(fromSquare)...) squares = append(squares, board.GetNonBlockedDiagonals(fromSquare)...)
return squares return squares

View File

@ -9,14 +9,14 @@ type Rook struct {
} }
func (r Rook) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate { func (r Rook) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return r.GetAllNonBlockedMoves(board, fromSquare) return r.GetAllNonBlockedSquares(board, fromSquare)
} }
func (r Rook) GetColor() types.ChessColor { func (r Rook) GetColor() types.ChessColor {
return r.Color return r.Color
} }
func (r Rook) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate { func (r Rook) GetAllNonBlockedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return board.GetNonBlockedRowAndColumn(fromSquare) return board.GetNonBlockedRowAndColumn(fromSquare)
} }

View File

@ -14,7 +14,7 @@ func Test_Rook_GetNonBlockedSquares(t *testing.T) {
rook := Rook{Color: types.Black} rook := Rook{Color: types.Black}
board.position[types.Coordinate{Col: 5, Row: 5}] = rook board.position[types.Coordinate{Col: 5, Row: 5}] = rook
squares := rook.GetAllNonBlockedMoves(board, types.Coordinate{Col: 5, Row: 5}) squares := rook.GetAllNonBlockedSquares(board, types.Coordinate{Col: 5, Row: 5})
assert.Len(t, squares, 14) assert.Len(t, squares, 14)
}) })
@ -28,7 +28,7 @@ func Test_Rook_GetNonBlockedSquares(t *testing.T) {
board.position[types.Coordinate{Col: 5, Row: 6}] = Pawn{Color: types.White} board.position[types.Coordinate{Col: 5, Row: 6}] = Pawn{Color: types.White}
board.position[types.Coordinate{Col: 6, Row: 5}] = Pawn{Color: types.Black} board.position[types.Coordinate{Col: 6, Row: 5}] = Pawn{Color: types.Black}
squares := rook.GetAllNonBlockedMoves(board, rookCoordinate) squares := rook.GetAllNonBlockedSquares(board, rookCoordinate)
squaresOnLeft := lo.Filter(squares, func(square types.Coordinate, _ int) bool { squaresOnLeft := lo.Filter(squares, func(square types.Coordinate, _ int) bool {
return square.Row == rookCoordinate.Row && square.Col < rookCoordinate.Col return square.Row == rookCoordinate.Row && square.Col < rookCoordinate.Col

View File

@ -96,7 +96,7 @@ func (conn *Connection) Read() ([]byte, error) {
msg, err := conn.buffer.Get() msg, err := conn.buffer.Get()
if err != nil { if err != nil {
conn.ws = nil conn.ws = nil
return nil, err // Tell game-handler that connection was lost return nil, err // TODO: Tell game-handler that connection was lost
} }
return []byte(msg), err return []byte(msg), err

View File

@ -3,8 +3,9 @@ package types
type ChessColor string type ChessColor string
const ( const (
White ChessColor = "white" NoColor ChessColor = "no_color"
Black ChessColor = "black" White ChessColor = "white"
Black ChessColor = "black"
) )
func (c ChessColor) Opposite() ChessColor { func (c ChessColor) Opposite() ChessColor {