mchess-server/chess/board_test.go
Marco ff2ec599fe Introduce PGN helpers
In order to simplify special moves like en passant or castling for the
client, we want to deliver the board state after every move (and not
only start square and end square).

With PGN we can encode a chess position into a string.
This commit implies changes to logic of the pieces' shortnames. This
will break the client/server connection (at least for promotions).
2023-08-12 11:24:40 +02:00

377 lines
13 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)
})
}