Introduce check if the game has ended (checkmate/stalemate)
Introduce checkmate check and send out 'gameEnded' message
This commit is contained in:
parent
946f4b632a
commit
af993876fd
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -3,6 +3,7 @@ package types
|
|||||||
type ChessColor string
|
type ChessColor string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
NoColor ChessColor = "no_color"
|
||||||
White ChessColor = "white"
|
White ChessColor = "white"
|
||||||
Black ChessColor = "black"
|
Black ChessColor = "black"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user