diff --git a/chessington/engine/pieces.py b/chessington/engine/pieces.py index b3408fa..8eb9809 100644 --- a/chessington/engine/pieces.py +++ b/chessington/engine/pieces.py @@ -41,13 +41,81 @@ class Pawn(Piece): """ def get_available_moves(self, board) -> List[Square]: current_square = board.find_piece(self) - if self.player == Player.BLACK: - square_in_front = Square.at(current_square.row - 1, current_square.col) - return [square_in_front] - else: - square_in_front = Square.at(current_square.row + 1, current_square.col) - return [square_in_front] + moves = [] + if self.player == Player.BLACK: + # Black pawn moves down (decreasing row) + new_row = current_square.row - 1 + + # 1. FORWARD MOVEMENT + if new_row >= 0: + one_square = Square.at(new_row, current_square.col) + if board.get_piece(one_square) is None: + moves.append(one_square) + + # Two-square move from starting position + if current_square.row == 6: + two_square_row = current_square.row - 2 + if two_square_row >= 0: + two_squares = Square.at(two_square_row, current_square.col) + if board.get_piece(two_squares) is None: + moves.append(two_squares) + + # 2. DIAGONAL CAPTURE MOVES + if new_row >= 0: # Make sure we're not going off the board + # Check diagonal left (column - 1) + if current_square.col - 1 >= 0: # Don't go off left edge + diagonal_left = Square.at(new_row, current_square.col - 1) + piece_on_diagonal = board.get_piece(diagonal_left) + # Can capture if there's an enemy piece (different player) + if piece_on_diagonal is not None and piece_on_diagonal.player != self.player: + moves.append(diagonal_left) + + # Check diagonal right (column + 1) + if current_square.col + 1 <= 7: # Don't go off right edge + diagonal_right = Square.at(new_row, current_square.col + 1) + piece_on_diagonal = board.get_piece(diagonal_right) + # Can capture if there's an enemy piece (different player) + if piece_on_diagonal is not None and piece_on_diagonal.player != self.player: + moves.append(diagonal_right) + + else: # WHITE player + # White pawn moves up (increasing row) + new_row = current_square.row + 1 + + # 1. FORWARD MOVEMENT + if new_row <= 7: + one_square = Square.at(new_row, current_square.col) + if board.get_piece(one_square) is None: + moves.append(one_square) + + # Two-square move from starting position + if current_square.row == 1: + two_square_row = current_square.row + 2 + if two_square_row <= 7: + two_squares = Square.at(two_square_row, current_square.col) + if board.get_piece(two_squares) is None: + moves.append(two_squares) + + # 2. DIAGONAL CAPTURE MOVES + if new_row <= 7: # Make sure we're not going off the board + # Check diagonal left (column - 1) + if current_square.col - 1 >= 0: # Don't go off left edge + diagonal_left = Square.at(new_row, current_square.col - 1) + piece_on_diagonal = board.get_piece(diagonal_left) + # Can capture if there's an enemy piece (different player) + if piece_on_diagonal is not None and piece_on_diagonal.player != self.player: + moves.append(diagonal_left) + + # Check diagonal right (column + 1) + if current_square.col + 1 <= 7: # Don't go off right edge + diagonal_right = Square.at(new_row, current_square.col + 1) + piece_on_diagonal = board.get_piece(diagonal_right) + # Can capture if there's an enemy piece (different player) + if piece_on_diagonal is not None and piece_on_diagonal.player != self.player: + moves.append(diagonal_right) + + return moves class Knight(Piece): """ @@ -91,4 +159,4 @@ class King(Piece): """ def get_available_moves(self, board): - return [] \ No newline at end of file + return [] diff --git a/tests/test_pieces.py b/tests/test_pieces.py index 15cf3de..9fa0dc7 100644 --- a/tests/test_pieces.py +++ b/tests/test_pieces.py @@ -101,4 +101,236 @@ def test_black_pawn_cannot_move_down_two_squares_if_already_moved(): # Assert assert Square.at(4, 4) in moves - assert Square.at(3, 4) not in moves \ No newline at end of file + assert Square.at(3, 4) not in moves + + @staticmethod + def test_white_pawn_cannot_move_if_piece_in_front(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.WHITE) + pawn_square = Square.at(4, 4) + board.set_piece(pawn_square, pawn) + + obstructing_square = Square.at(5, 4) + obstruction = Pawn(Player.BLACK) + board.set_piece(obstructing_square, obstruction) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert len(moves) == 0 + + @staticmethod + def test_black_pawn_cannot_move_if_piece_in_front(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.BLACK) + pawn_square = Square.at(4, 4) + board.set_piece(pawn_square, pawn) + + obstructing_square = Square.at(3, 4) + obstruction = Pawn(Player.WHITE) + board.set_piece(obstructing_square, obstruction) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert len(moves) == 0 + + @staticmethod + def test_white_pawn_cannot_move_two_squares_if_piece_two_in_front(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.WHITE) + pawn_square = Square.at(1, 4) + board.set_piece(pawn_square, pawn) + + obstructing_square = Square.at(3, 4) + obstruction = Pawn(Player.BLACK) + board.set_piece(obstructing_square, obstruction) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert obstructing_square not in moves + + @staticmethod + def test_black_pawn_cannot_move_two_squares_if_piece_two_in_front(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.BLACK) + pawn_square = Square.at(6, 4) + board.set_piece(pawn_square, pawn) + + obstructing_square = Square.at(4, 4) + obstruction = Pawn(Player.WHITE) + board.set_piece(obstructing_square, obstruction) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert obstructing_square not in moves + + @staticmethod + def test_white_pawn_cannot_move_two_squares_if_piece_one_in_front(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.WHITE) + pawn_square = Square.at(1, 4) + board.set_piece(pawn_square, pawn) + + obstructing_square = Square.at(2, 4) + obstruction = Pawn(Player.BLACK) + board.set_piece(obstructing_square, obstruction) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert Square.at(3, 4) not in moves + + @staticmethod + def test_black_pawn_cannot_move_two_squares_if_piece_one_in_front(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.BLACK) + pawn_square = Square.at(6, 4) + board.set_piece(pawn_square, pawn) + + obstructing_square = Square.at(5, 4) + obstruction = Pawn(Player.WHITE) + board.set_piece(obstructing_square, obstruction) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert Square.at(4, 4) not in moves + + @staticmethod + def test_white_pawn_cannot_move_at_top_of_board(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.WHITE) + square = Square.at(7, 4) + board.set_piece(square, pawn) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert len(moves) == 0 + + @staticmethod + def test_black_pawn_cannot_move_at_bottom_of_board(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.BLACK) + square = Square.at(0, 4) + board.set_piece(square, pawn) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert len(moves) == 0 + + @staticmethod + def test_white_pawns_can_capture_diagonally(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.WHITE) + pawn_square = Square.at(3, 4) + board.set_piece(pawn_square, pawn) + + enemy1 = Pawn(Player.BLACK) + enemy1_square = Square.at(4, 5) + board.set_piece(enemy1_square, enemy1) + + enemy2 = Pawn(Player.BLACK) + enemy2_square = Square.at(4, 3) + board.set_piece(enemy2_square, enemy2) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert enemy1_square in moves + assert enemy2_square in moves + + @staticmethod + def test_black_pawns_can_capture_diagonally(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.BLACK) + pawn_square = Square.at(3, 4) + board.set_piece(pawn_square, pawn) + + enemy1 = Pawn(Player.WHITE) + enemy1_square = Square.at(2, 5) + board.set_piece(enemy1_square, enemy1) + + enemy2 = Pawn(Player.WHITE) + enemy2_square = Square.at(2, 3) + board.set_piece(enemy2_square, enemy2) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert enemy1_square in moves + assert enemy2_square in moves + + @staticmethod + def test_white_pawns_cannot_move_diagonally_except_to_capture(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.WHITE) + pawn_square = Square.at(3, 4) + board.set_piece(pawn_square, pawn) + + friendly = Pawn(Player.WHITE) + friendly_square = Square.at(4, 5) + board.set_piece(friendly_square, friendly) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert Square.at(4, 3) not in moves + assert Square.at(4, 5) not in moves + + @staticmethod + def test_black_pawns_cannot_move_diagonally_except_to_capture(): + + # Arrange + board = Board.empty() + pawn = Pawn(Player.BLACK) + pawn_square = Square.at(3, 4) + board.set_piece(pawn_square, pawn) + + friendly = Pawn(Player.BLACK) + friendly_square = Square.at(2, 5) + board.set_piece(friendly_square, friendly) + + # Act + moves = pawn.get_available_moves(board) + + # Assert + assert Square.at(2, 3) not in moves + assert Square.at(2, 5) not in moves