mchess-server/chess/board_test.go

452 lines
16 KiB
Go

package chess
import (
"mchess_server/types"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_CheckMove_validPawnMove(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 1, Row: 2}] = Pawn{Color: types.White}
board.position[types.Coordinate{Col: 2, Row: 4}] = Pawn{Color: types.Black}
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
move := types.Move{
StartSquare: types.Coordinate{Col: 1, Row: 2},
EndSquare: types.Coordinate{Col: 1, Row: 3},
}
good, _ := board.CheckAndPlay(&move)
assert.True(t, good)
//we take the pawn
secondMove := types.Move{
StartSquare: types.Coordinate{Col: 2, Row: 4},
EndSquare: types.Coordinate{Col: 1, Row: 3},
}
good, _ = board.CheckAndPlay(&secondMove)
assert.True(t, good)
}
func Test_CheckMove_enPassant(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 6, Row: 4}] = Pawn{Color: types.Black}
board.position[types.Coordinate{Col: 5, Row: 2}] = Pawn{Color: types.White}
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 2},
EndSquare: types.Coordinate{Col: 5, Row: 4},
}
good, reason := board.CheckAndPlay(&move)
assert.True(t, good)
assert.Empty(t, reason)
assert.Equal(t, Pawn{Color: types.White}, board.position[types.Coordinate{Col: 5, Row: 4}])
newMove := types.Move{
StartSquare: types.Coordinate{Col: 6, Row: 4},
EndSquare: types.Coordinate{Col: 5, Row: 3},
}
good, reason = board.CheckAndPlay(&newMove)
assert.True(t, good)
assert.Empty(t, reason)
// the black pawn is on its correct square
assert.Equal(t, Pawn{Color: types.Black}, board.position[types.Coordinate{Col: 5, Row: 3}])
//the white pawn is gone
assert.Nil(t, board.position[types.Coordinate{Col: 5, Row: 4}])
}
func Test_CheckMove_invalidPawnMoves(t *testing.T) {
t.Run("pawn is blocked", func(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 2, Row: 5}] = Pawn{Color: types.White}
board.position[types.Coordinate{Col: 1, Row: 5}] = King{Color: types.White}
board.position[types.Coordinate{Col: 7, Row: 5}] = Queen{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 5}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 2, Row: 6}] = Pawn{Color: types.Black}
move := types.Move{
StartSquare: types.Coordinate{Col: 2, Row: 5},
EndSquare: types.Coordinate{Col: 2, Row: 6},
}
legalMove, _ := board.CheckAndPlay(&move)
assert.False(t, legalMove)
})
t.Run("pawn moves to the side", func(t *testing.T) {
var board = newBoard()
boardBeforeMove := board
board.position[types.Coordinate{Col: 1, Row: 5}] = King{Color: types.White}
board.position[types.Coordinate{Col: 8, Row: 5}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 2, Row: 5}] = Pawn{Color: types.White}
move := types.Move{
StartSquare: types.Coordinate{Col: 2, Row: 5},
EndSquare: types.Coordinate{Col: 3, Row: 5},
}
legal, _ := board.CheckAndPlay(&move)
assert.False(t, legal)
assert.Equal(t, boardBeforeMove, board)
move = types.Move{
StartSquare: types.Coordinate{Col: 2, Row: 5},
EndSquare: types.Coordinate{Col: 1, Row: 5},
}
legal, _ = board.CheckAndPlay(&move)
assert.False(t, legal)
assert.Equal(t, boardBeforeMove, board)
move = types.Move{
StartSquare: types.Coordinate{Col: 2, Row: 5},
EndSquare: types.Coordinate{Col: 6, Row: 5},
}
legal, _ = board.CheckAndPlay(&move)
assert.False(t, legal)
assert.Equal(t, boardBeforeMove, board)
})
t.Run("king of moving color is in check after move", func(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 1, Row: 6}] = King{Color: types.White}
board.position[types.Coordinate{Col: 2, Row: 6}] = Pawn{Color: types.White}
board.position[types.Coordinate{Col: 7, Row: 6}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 8}] = King{Color: types.Black}
boardBeforeMove := board
move := types.Move{
StartSquare: types.Coordinate{Col: 2, Row: 6},
EndSquare: types.Coordinate{Col: 2, Row: 7},
}
good, _ := board.CheckAndPlay(&move)
assert.False(t, good)
assert.Equal(t, boardBeforeMove, board)
})
}
func Test_CheckMove_validPromotion(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 1, Row: 7}] = Pawn{Color: types.White}
board.position[types.Coordinate{Col: 1, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 7, Row: 7}] = King{Color: types.Black}
shortName := types.WhiteQueenShortName.String()
move := types.Move{
StartSquare: types.Coordinate{Col: 1, Row: 7},
EndSquare: types.Coordinate{Col: 1, Row: 8},
PromotionToPiece: &shortName,
}
good, reason := board.CheckAndPlay(&move)
assert.Empty(t, reason)
assert.True(t, good)
assert.Equal(t, Queen{Color: types.White}, board.getPieceAt(types.Coordinate{Row: 8, Col: 1}))
}
func Test_CheckMove_HistoryWorks(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 3, Row: 7}] = Pawn{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 2}] = Pawn{Color: types.White}
board.position[types.Coordinate{Col: 1, Row: 5}] = King{Color: types.White}
board.position[types.Coordinate{Col: 8, Row: 5}] = King{Color: types.Black}
firstMove := types.Move{
StartSquare: types.Coordinate{Col: 1, Row: 2},
EndSquare: types.Coordinate{Col: 1, Row: 3},
}
secondMove := types.Move{
StartSquare: types.Coordinate{Col: 3, Row: 7},
EndSquare: types.Coordinate{Col: 3, Row: 5},
}
thirdMove := types.Move{
StartSquare: types.Coordinate{Col: 1, Row: 3},
EndSquare: types.Coordinate{Col: 1, Row: 4},
}
good, _ := board.CheckAndPlay(&firstMove)
assert.True(t, good)
good, _ = board.CheckAndPlay(&secondMove)
assert.True(t, good)
good, _ = board.CheckAndPlay(&thirdMove)
assert.True(t, good)
expectedHistory := []types.Move{
{
StartSquare: types.Coordinate{Col: 1, Row: 2},
EndSquare: types.Coordinate{Col: 1, Row: 3},
PieceMoved: "P",
ColorMoved: "white",
},
{
StartSquare: types.Coordinate{Col: 3, Row: 7},
EndSquare: types.Coordinate{Col: 3, Row: 5},
PieceMoved: "p",
ColorMoved: "black",
},
{
StartSquare: types.Coordinate{Col: 1, Row: 3},
EndSquare: types.Coordinate{Col: 1, Row: 4},
PieceMoved: "P",
ColorMoved: "white",
},
}
assert.Equal(t, expectedHistory, board.history)
}
func Test_Promotion_BlackKing(t *testing.T) {
t.Run("valid promotion to the right", func(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
//Make dummy move for white
board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 7, Row: 8},
}
good, reason := board.CheckAndPlay(&move)
assert.True(t, good)
assert.Empty(t, reason)
assert.Equal(t, King{Color: types.Black}, board.position[types.Coordinate{Col: 7, Row: 8}])
assert.Equal(t, Rook{Color: types.Black}, board.position[types.Coordinate{Col: 6, Row: 8}])
assert.Empty(t, board.position[types.Coordinate{Col: 5, Row: 8}])
assert.Empty(t, board.position[types.Coordinate{Col: 8, Row: 8}])
})
t.Run("valid promotion to the left", func(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
//Make dummy move for white
board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8},
}
good, reason := board.CheckAndPlay(&move)
assert.True(t, good)
assert.Empty(t, reason)
assert.Equal(t, King{Color: types.Black}, board.position[types.Coordinate{Col: 3, Row: 8}])
assert.Equal(t, Rook{Color: types.Black}, board.position[types.Coordinate{Col: 4, Row: 8}])
assert.Empty(t, board.position[types.Coordinate{Col: 1, Row: 8}])
assert.Empty(t, board.position[types.Coordinate{Col: 2, Row: 8}])
assert.Empty(t, board.position[types.Coordinate{Col: 5, Row: 8}])
})
t.Run("king moved already", func(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
//Make dummy move for white
board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
//Move black
board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 8}, EndSquare: types.Coordinate{Col: 4, Row: 8}})
//Make dummy move for white
board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 2}, EndSquare: types.Coordinate{Col: 5, Row: 1}})
//Move black back
board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 4, Row: 8}, EndSquare: types.Coordinate{Col: 5, Row: 8}})
//Make dummy move for white
board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8},
}
good, _ := board.CheckAndPlay(&move)
assert.False(t, good)
})
t.Run("promotion right with piece in between", func(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 7, Row: 8}] = Bishop{Color: types.Black}
//Make dummy move for white
board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 7, Row: 8},
}
good, _ := board.CheckAndPlay(&move)
assert.False(t, good)
})
t.Run("promotion left with piece next to king", func(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 4, Row: 8}] = Bishop{Color: types.Black}
//Make dummy move for white
board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8},
}
good, _ := board.CheckAndPlay(&move)
assert.False(t, good)
})
t.Run("promotion left with piece on castling square", func(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 3, Row: 8}] = Bishop{Color: types.Black}
//Make dummy move for white
board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8},
}
good, _ := board.CheckAndPlay(&move)
assert.False(t, good)
})
t.Run("promotion left with piece next to rook", func(t *testing.T) {
var board = newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 2, Row: 8}] = Bishop{Color: types.Black}
//Make dummy move for white
board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 1}, EndSquare: types.Coordinate{Col: 5, Row: 2}})
move := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8},
}
good, _ := board.CheckAndPlay(&move)
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)
}