diff --git a/api/move.go b/api/move.go index 14db174..be4b447 100644 --- a/api/move.go +++ b/api/move.go @@ -18,6 +18,7 @@ const ( MoveMessage MessageType = "move" InvalidMoveMessage MessageType = "invalidMove" ColorDetermined MessageType = "colorDetermined" + TakenEnPassant MessageType = "takenEnPassant" ) func (m WebsocketMessage) IsValidMoveMessage() bool { diff --git a/chess/bishop.go b/chess/bishop.go index c0fbab5..30d9dde 100644 --- a/chess/bishop.go +++ b/chess/bishop.go @@ -19,3 +19,5 @@ func (b Bishop) GetColor() types.ChessColor { func (b Bishop) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate { return board.GetNonBlockedDiagonals(fromSquare) } + +func (b Bishop) AfterMoveAction(board *Board, fromSquare types.Coordinate) {} diff --git a/chess/board.go b/chess/board.go index e3fb693..0695c34 100644 --- a/chess/board.go +++ b/chess/board.go @@ -13,6 +13,7 @@ type Board struct { position Position history []types.Move colorToMove types.ChessColor + state types.AdditionalState } func newBoard() Board { @@ -20,6 +21,7 @@ func newBoard() Board { position: make(Position), history: make([]types.Move, 0), colorToMove: types.White, + state: types.AdditionalState{}, } } @@ -122,6 +124,9 @@ func (b *Board) CheckAndPlay(move types.Move) (bool, string) { b.history = tempBoard.history b.colorToMove = b.colorToMove.Opposite() b.appendMoveToHistory(move) + + pieceAtStartSquare.AfterMoveAction(b, move.StartSquare) + return true, "" } @@ -184,6 +189,7 @@ func (b Board) getCopyOfBoard() Board { position: b.position.getCopyOfPosition(), history: b.history, colorToMove: b.colorToMove, + state: b.state, } } @@ -206,6 +212,8 @@ func (b *Board) handleSpecialMove(move types.Move) bool { if !was { was = piece.HandleEnPassant(b, move, b.getLastMove()) } + case King: + was = piece.HandleCastling(b, move) } return was } diff --git a/chess/board_test.go b/chess/board_test.go index a993983..663c927 100644 --- a/chess/board_test.go +++ b/chess/board_test.go @@ -210,3 +210,167 @@ func Test_CheckMove_HistoryWorks(t *testing.T) { } 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) + }) +} diff --git a/chess/king.go b/chess/king.go index fed3f7f..2891db2 100644 --- a/chess/king.go +++ b/chess/king.go @@ -17,5 +17,114 @@ func (k King) GetColor() types.ChessColor { } 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} + } } diff --git a/chess/knight.go b/chess/knight.go index a8b4f7e..4f31174 100644 --- a/chess/knight.go +++ b/chess/knight.go @@ -8,14 +8,16 @@ type Knight struct { Color types.ChessColor } -func (k Knight) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate { - return k.GetAllNonBlockedMoves(board, fromSquare) +func (n Knight) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate { + return n.GetAllNonBlockedMoves(board, fromSquare) } -func (k Knight) GetColor() types.ChessColor { - return k.Color +func (n Knight) GetColor() types.ChessColor { + 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) } + +func (n Knight) AfterMoveAction(board *Board, fromSquare types.Coordinate) {} diff --git a/chess/pawn.go b/chess/pawn.go index ceb8f94..916363f 100644 --- a/chess/pawn.go +++ b/chess/pawn.go @@ -187,3 +187,5 @@ func (p Pawn) filterBlockedSquares(board Board, fromSquare types.Coordinate, squ } return lo.Intersect(nonBlockedSquares, squaresToBeFiltered) } + +func (p Pawn) AfterMoveAction(board *Board, fromSquare types.Coordinate) {} diff --git a/chess/piece_interface.go b/chess/piece_interface.go index b3aa093..a79d6f5 100644 --- a/chess/piece_interface.go +++ b/chess/piece_interface.go @@ -8,6 +8,7 @@ type Piece interface { GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate GetColor() types.ChessColor + AfterMoveAction(board *Board, fromSquare types.Coordinate) } func GetPieceForShortName(name types.PieceShortName, color types.ChessColor) Piece { diff --git a/chess/queen.go b/chess/queen.go index 5fc8c4f..5b31d75 100644 --- a/chess/queen.go +++ b/chess/queen.go @@ -21,3 +21,5 @@ func (q Queen) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) [ squares = append(squares, board.GetNonBlockedDiagonals(fromSquare)...) return squares } + +func (q Queen) AfterMoveAction(board *Board, fromSquare types.Coordinate) {} diff --git a/chess/rook.go b/chess/rook.go index 5f4ee42..59bde33 100644 --- a/chess/rook.go +++ b/chess/rook.go @@ -12,9 +12,6 @@ func (r Rook) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) [] return r.GetAllNonBlockedMoves(board, fromSquare) } -func (r Rook) AfterMoveAction() { -} - func (r Rook) GetColor() types.ChessColor { return r.Color } @@ -22,3 +19,22 @@ func (r Rook) GetColor() types.ChessColor { func (r Rook) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate { 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 + } + } +} diff --git a/types/common.go b/types/common.go index e0b2145..f7c7f2c 100644 --- a/types/common.go +++ b/types/common.go @@ -1,7 +1,5 @@ package types - - type ChessColor string const ( @@ -16,3 +14,12 @@ func (c ChessColor) Opposite() ChessColor { return White } } + +type AdditionalState struct { + BlackKingMoved bool + WhiteKingMoved bool + BlackHRookMoved bool + BlackARookMoved bool + WhiteHRookMoved bool + WhiteARookMoved bool +} diff --git a/utils/passphrase.go b/utils/passphrase.go index 272a084..c2bd83c 100644 --- a/utils/passphrase.go +++ b/utils/passphrase.go @@ -13,7 +13,7 @@ func NewPassphrase() Passphrase { var retries int var word string var words int = 2 -//TODO make sure passphrases are unique + //TODO make sure passphrases are unique for words > 0 { retries = 20 for { @@ -54,6 +54,8 @@ func isProfanity(s string) bool { contains := []string{ "nigg", "fag", + "ass", + "bitch", } startsWith := []string{ "spic",