Make en passant and castling work.

This commit is contained in:
Marco 2023-07-05 21:15:01 +02:00
parent c8407881bf
commit 9c7bdf357d
12 changed files with 328 additions and 12 deletions

View File

@ -18,6 +18,7 @@ const (
MoveMessage MessageType = "move" MoveMessage MessageType = "move"
InvalidMoveMessage MessageType = "invalidMove" InvalidMoveMessage MessageType = "invalidMove"
ColorDetermined MessageType = "colorDetermined" ColorDetermined MessageType = "colorDetermined"
TakenEnPassant MessageType = "takenEnPassant"
) )
func (m WebsocketMessage) IsValidMoveMessage() bool { func (m WebsocketMessage) IsValidMoveMessage() bool {

View File

@ -19,3 +19,5 @@ func (b Bishop) GetColor() types.ChessColor {
func (b Bishop) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate { func (b Bishop) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
return board.GetNonBlockedDiagonals(fromSquare) return board.GetNonBlockedDiagonals(fromSquare)
} }
func (b Bishop) AfterMoveAction(board *Board, fromSquare types.Coordinate) {}

View File

@ -13,6 +13,7 @@ type Board struct {
position Position position Position
history []types.Move history []types.Move
colorToMove types.ChessColor colorToMove types.ChessColor
state types.AdditionalState
} }
func newBoard() Board { func newBoard() Board {
@ -20,6 +21,7 @@ func newBoard() Board {
position: make(Position), position: make(Position),
history: make([]types.Move, 0), history: make([]types.Move, 0),
colorToMove: types.White, colorToMove: types.White,
state: types.AdditionalState{},
} }
} }
@ -122,6 +124,9 @@ func (b *Board) CheckAndPlay(move types.Move) (bool, string) {
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)
return true, "" return true, ""
} }
@ -184,6 +189,7 @@ func (b Board) getCopyOfBoard() Board {
position: b.position.getCopyOfPosition(), position: b.position.getCopyOfPosition(),
history: b.history, history: b.history,
colorToMove: b.colorToMove, colorToMove: b.colorToMove,
state: b.state,
} }
} }
@ -206,6 +212,8 @@ func (b *Board) handleSpecialMove(move types.Move) bool {
if !was { if !was {
was = piece.HandleEnPassant(b, move, b.getLastMove()) was = piece.HandleEnPassant(b, move, b.getLastMove())
} }
case King:
was = piece.HandleCastling(b, move)
} }
return was return was
} }

View File

@ -210,3 +210,167 @@ func Test_CheckMove_HistoryWorks(t *testing.T) {
} }
assert.Equal(t, expectedHistory, board.history) 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)
})
}

View File

@ -19,3 +19,112 @@ func (k King) GetColor() types.ChessColor {
func (k King) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate { func (k King) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
return board.GetNonBlockedKingMoves(fromSquare) return board.GetNonBlockedKingMoves(fromSquare)
} }
func (k King) AfterMoveAction(board *Board, fromSquare types.Coordinate) {
switch k.Color {
case types.Black:
board.state.BlackKingMoved = true
case types.White:
board.state.WhiteKingMoved = true
}
}
func (k King) HandleCastling(board *Board, move types.Move) bool {
if k.hadMovedBefore(board) {
return false
}
valid, dir := k.movedOnValidCastlingSquare(board, move)
if !valid {
return false
}
switch k.Color {
case types.White:
board.state.WhiteKingMoved = true
case types.Black:
board.state.BlackKingMoved = true
}
k.playCastlingOnBoard(board, move, dir)
return true
}
func (k King) hadMovedBefore(b *Board) bool {
var hasMoved bool
switch k.Color {
case types.White:
hasMoved = b.state.WhiteKingMoved
case types.Black:
hasMoved = b.state.BlackKingMoved
}
return hasMoved
}
type CastlingDirection string
const (
CastlingRight CastlingDirection = "right"
CastlingLeft CastlingDirection = "left"
)
func (k King) movedOnValidCastlingSquare(b *Board, move types.Move) (bool, CastlingDirection) {
var valid bool = false
switch k.Color {
case types.White:
castlingSquareRight := types.Coordinate{Col: 7, Row: 1}
castlingSquareLeft := types.Coordinate{Col: 3, Row: 1}
if move.EndSquare == castlingSquareRight &&
b.getPieceAt(castlingSquareRight) == nil &&
b.getPieceAt(*castlingSquareRight.Left(1)) == nil &&
!b.state.WhiteHRookMoved {
return true, CastlingRight
}
if move.EndSquare == castlingSquareLeft &&
b.getPieceAt(castlingSquareLeft) == nil &&
b.getPieceAt(*castlingSquareLeft.Right(1)) == nil &&
b.getPieceAt(*castlingSquareLeft.Left(1)) == nil &&
!b.state.WhiteARookMoved {
return true, CastlingLeft
}
case types.Black:
castlingSquareRight := types.Coordinate{Col: 7, Row: 8}
castlingSquareLeft := types.Coordinate{Col: 3, Row: 8}
if move.EndSquare == castlingSquareRight &&
b.getPieceAt(castlingSquareRight) == nil &&
b.getPieceAt(*castlingSquareRight.Left(1)) == nil &&
!b.state.BlackHRookMoved {
return true, CastlingRight
}
if move.EndSquare == castlingSquareLeft &&
b.getPieceAt(castlingSquareLeft) == nil &&
b.getPieceAt(*castlingSquareLeft.Right(1)) == nil &&
b.getPieceAt(*castlingSquareLeft.Left(1)) == nil &&
!b.state.BlackARookMoved {
return true, CastlingLeft
}
}
return valid, CastlingRight
}
func (k King) playCastlingOnBoard(board *Board, move types.Move, dir CastlingDirection) {
onRow := move.StartSquare.Row
switch dir {
case CastlingRight:
delete(board.position, types.Coordinate{Col: 8, Row: onRow})
delete(board.position, types.Coordinate{Col: 5, Row: onRow})
board.position[types.Coordinate{Col: 7, Row: onRow}] = King{Color: k.Color}
board.position[types.Coordinate{Col: 6, Row: onRow}] = Rook{Color: k.Color}
case CastlingLeft:
delete(board.position, types.Coordinate{Col: 1, Row: onRow})
delete(board.position, types.Coordinate{Col: 5, Row: onRow})
board.position[types.Coordinate{Col: 3, Row: onRow}] = King{Color: k.Color}
board.position[types.Coordinate{Col: 4, Row: onRow}] = Rook{Color: k.Color}
}
}

View File

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

View File

@ -187,3 +187,5 @@ func (p Pawn) filterBlockedSquares(board Board, fromSquare types.Coordinate, squ
} }
return lo.Intersect(nonBlockedSquares, squaresToBeFiltered) return lo.Intersect(nonBlockedSquares, squaresToBeFiltered)
} }
func (p Pawn) AfterMoveAction(board *Board, fromSquare types.Coordinate) {}

View File

@ -8,6 +8,7 @@ type Piece interface {
GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate GetAllNonBlockedMoves(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)
} }
func GetPieceForShortName(name types.PieceShortName, color types.ChessColor) Piece { func GetPieceForShortName(name types.PieceShortName, color types.ChessColor) Piece {

View File

@ -21,3 +21,5 @@ func (q Queen) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) [
squares = append(squares, board.GetNonBlockedDiagonals(fromSquare)...) squares = append(squares, board.GetNonBlockedDiagonals(fromSquare)...)
return squares return squares
} }
func (q Queen) AfterMoveAction(board *Board, fromSquare types.Coordinate) {}

View File

@ -12,9 +12,6 @@ func (r Rook) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []
return r.GetAllNonBlockedMoves(board, fromSquare) return r.GetAllNonBlockedMoves(board, fromSquare)
} }
func (r Rook) AfterMoveAction() {
}
func (r Rook) GetColor() types.ChessColor { func (r Rook) GetColor() types.ChessColor {
return r.Color return r.Color
} }
@ -22,3 +19,22 @@ func (r Rook) GetColor() types.ChessColor {
func (r Rook) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate { func (r Rook) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
return board.GetNonBlockedRowAndColumn(fromSquare) return board.GetNonBlockedRowAndColumn(fromSquare)
} }
func (r Rook) AfterMoveAction(board *Board, fromSquare types.Coordinate) {
switch r.Color {
case types.Black:
if fromSquare.Col == types.RangeLastValid {
board.state.BlackHRookMoved = true
}
if fromSquare.Col == types.RangeFirstValid {
board.state.BlackARookMoved = true
}
case types.White:
if fromSquare.Col == types.RangeLastValid {
board.state.WhiteHRookMoved = true
}
if fromSquare.Col == types.RangeFirstValid {
board.state.WhiteARookMoved = true
}
}
}

View File

@ -1,7 +1,5 @@
package types package types
type ChessColor string type ChessColor string
const ( const (
@ -16,3 +14,12 @@ func (c ChessColor) Opposite() ChessColor {
return White return White
} }
} }
type AdditionalState struct {
BlackKingMoved bool
WhiteKingMoved bool
BlackHRookMoved bool
BlackARookMoved bool
WhiteHRookMoved bool
WhiteARookMoved bool
}

View File

@ -54,6 +54,8 @@ func isProfanity(s string) bool {
contains := []string{ contains := []string{
"nigg", "nigg",
"fag", "fag",
"ass",
"bitch",
} }
startsWith := []string{ startsWith := []string{
"spic", "spic",