def test_moving_the_king_should_disallow_any_further_castling(self): with self.subTest('white'): board = Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'R...K..R' # 1 # bcdefgh ]) white_king = board[Rank.ONE][File.E] moves = white_king.generate_moves(board, P('e1')) self.assertIn(P('c1'), moves) self.assertIn(P('g1'), moves) # move the king and then return it in it's original position board.move(from_pos=P('e1'), to_pos=P('f1')) board.move(from_pos=P('f1'), to_pos=P('e1')) moves = white_king.generate_moves(board, P('e1')) self.assertNotIn(P('c1'), moves) self.assertNotIn(P('g1'), moves) with self.subTest('black'): board = Board.from_strings([ # bcdefgh 'r...k..r', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 # bcdefgh ]) black_king = board[Rank.EIGHT][File.E] moves = black_king.generate_moves(board, P('e8')) self.assertIn(P('c8'), moves) self.assertIn(P('g8'), moves) board.move(from_pos=P('e8'), to_pos=P('f8')) board.move(from_pos=P('f8'), to_pos=P('e8')) moves = black_king.generate_moves(board, P('e8')) self.assertNotIn(P('c8'), moves) self.assertNotIn(P('g8'), moves)
def test_double_check_no_other_pieces_other_than_king_should_move(self): for figure, figure_name in ( ('q', 'queen'), ('n', 'knight'), ('b', 'bishop'), ('p', 'pawn'), ('r', 'rook'), ): test_case = { 'name': f'{figure_name}_is_blocking_double_check_and_should_not_be_able_to_move', 'board': Board.from_strings( os.linesep.join([ # bcdefgh '....q...', # 8 '.q......', # 7 '..X.....', # 6 '.K......', # 5 '.......Q', # 4 '....Q...', # 3 '.....x..', # 2 '....k...' # 1 ]).replace('x', figure).replace('X', figure.upper()).split( os.linesep)), 'want': { 'white': { P('c6'): {} }, 'black': { P('f2'): {} } } } self.runMoveGenerationTest(test_case)
def test_get_attackers_detects_king_attacks(self): """The placement of this test must seem off The bulk of the use cases are get_attackers are tested inderectly in the check awareness tests The only use case that cannot be tested with check awareness is when the king attacks something """ board = Board.from_strings([ # bcdefgh "p.......", # 8 "K.......", # 7 "........", # 6 "........", # 5 "........", # 4 "........", # 3 "k.......", # 2 "P......." # 1 ]) with self.subTest('white pawn attacked'): attackers = board.get_attackers(P('a1'), Color.WHITE) self.assertEqual([P('a2')], list(attackers)) with self.subTest('black pawn attacked'): attackers = board.get_attackers(P('a8'), Color.BLACK) self.assertEqual([P('a7')], list(attackers))
def test_en_passant_square_is_set_on_the_board_object(self): board = Board.from_strings([ # bcdefgh '........', # 8 '.p......', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '.P......', # 2 '........' # 1 ]) with self.subTest('white'): en_passant_want = Position(Rank.THREE, File.B) f, t = Position(Rank.TWO, File.B), Position(Rank.FOUR, File.B) board.move(from_pos=f, to_pos=t) self.assertEqual(board.en_passant_pos, en_passant_want) with self.subTest('black'): en_passant_want = Position(Rank.SIX, File.B) f, t = Position(Rank.SEVEN, File.B), Position(Rank.FIVE, File.B) board.move(from_pos=f, to_pos=t) self.assertEqual(board.en_passant_pos, en_passant_want)
def test_promotion_callback_should_be_called(self): board = Board.from_strings([ # bcdefgh '........', # 8 '.P......', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '.p......', # 2 '........' # 1 ]) board.promotion_cb = unittest.mock.Mock() mock_piece = board.promotion_cb.return_value with self.subTest('white'): f, t = Position(Rank.SEVEN, File.B), Position(Rank.EIGHT, File.B) board.move(from_pos=f, to_pos=t) board.promotion_cb.assert_called_once() mock_piece.assert_called_once_with(Color.WHITE) board.promotion_cb.reset_mock() mock_piece = board.promotion_cb.return_value with self.subTest('black'): f, t = Position(Rank.TWO, File.B), Position(Rank.ONE, File.B) board.move(from_pos=f, to_pos=t) board.promotion_cb.assert_called_once() mock_piece.assert_called_once_with(Color.BLACK)
def test_en_passant_is_unavailable_after_making_another_move(self): with self.subTest('white'): board = Board.from_strings([ # bcdefgh '........', # 8 '..p.....', # 7 '........', # 6 '...P....', # 5 '........', # 4 '........', # 3 '....P...', # 2 '........' # 1 # bcdefgh ]) board.move(from_pos=P('c7'), to_pos=P('c5')) self.assertIsInstance(board[Rank.FIVE][File.D], Pawn) white_pawn = board[Rank.FIVE][File.D] self.assertIn(P('c6'), white_pawn.generate_moves(board, P('d5'))) board.move(from_pos=P('e2'), to_pos=P('e3')) self.assertNotIn(P('c6'), white_pawn.generate_moves(board, P('d5'))) with self.subTest('black'): board = Board.from_strings([ # bcdefgh '........', # 8 '...p....', # 7 '........', # 6 '........', # 5 '..p.....', # 4 '........', # 3 '...P....', # 2 '........' # 1 # bcdefgh ]) board.move(from_pos=P('d2'), to_pos=P('d4')) self.assertIsInstance(board[Rank.FOUR][File.C], Pawn) black_pawn = board[Rank.FOUR][File.C] self.assertIn(P('d3'), black_pawn.generate_moves(board, P('c4'))) board.move(from_pos=P('d7'), to_pos=P('d6')) self.assertNotIn(P('d3'), black_pawn.generate_moves(board, P('c4')))
def test_when_king_is_in_check_he_should_be_aware_of_his_own_position( self): test_table = [ *self.all_board_rotations_of({ 'comment': ''' when a possible position is evaluated, the code should not consider it's old position as a blocker of an attacker In this situation: v a b c d e f g h 1 . Q . k . . . . 1 F1(marked with `v`) should not be a valid move ''', 'board': Board.from_strings([ # bcdefgh 'R..k....', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'r..K....' # 1 ]), 'want': { 'white': { 'assert': self.assertNotIn, Position(Rank.ONE, File.D): Position(Rank.ONE, File.E), }, 'black': { 'assert': self.assertNotIn, Position(Rank.EIGHT, File.D): Position( Rank.EIGHT, File.E), }, }, 'name': '', }), ] for test_case in test_table: self.runMoveGenerationTest(test_case)
def test_board_constructor_works_for_rooks(self): board = Board.from_strings([ # bcdefgh '........', # 8 '.r......', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '.R......', # 2 '........' # 1 ]) self.assertEqual(board[Rank.TWO][File.B].type, Type.ROOK) self.assertEqual(board[Rank.TWO][File.B].color, Color.WHITE) self.assertEqual(board[Rank.SEVEN][File.B].type, Type.ROOK) self.assertEqual(board[Rank.SEVEN][File.B].color, Color.BLACK)
def test_board_constructor_works_for_queen(self): board = Board.from_strings([ # bcdefgh '........', # 8 '.q......', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '.Q......', # 2 '........' # 1 ]) self.assertEqual(board[Rank.TWO][File.B].figure_type, Type.QUEEN) self.assertEqual(board[Rank.TWO][File.B].color, Color.WHITE) self.assertEqual(board[Rank.SEVEN][File.B].figure_type, Type.QUEEN) self.assertEqual(board[Rank.SEVEN][File.B].color, Color.BLACK)
def test_king_should_not_be_able_to_step_on_attacked_square(self): test_table = [ *self.all_board_rotations_of({ 'name': 'should_not_be_able_to_step_on_attacked_squares', 'board': Board.from_strings([ # bcdefgh '...k....', # 8 'R.......', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 'r.......', # 2 '...K....' # 1 ]), 'want': { 'white': { Position(Rank.ONE, File.D): { Position(Rank.TWO, File.C), Position(Rank.TWO, File.D), Position(Rank.TWO, File.E), }, 'assert': lambda want, actual, **_: [self.assertNotIn(p, actual) for p in want] }, 'black': { Position(Rank.EIGHT, File.D): { Position(Rank.SEVEN, File.C), Position(Rank.SEVEN, File.D), Position(Rank.SEVEN, File.E), }, 'assert': lambda want, actual, **_: [self.assertNotIn(p, actual) for p in want] }, } }) ] for test_case in test_table: self.runMoveGenerationTest(test_case)
def test_last_rank_should_be_with_changed_piece_type(self): board = Board.from_strings([ # bcdefgh '........', # 8 '.P......', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 ]) f, t = Position(Rank.SEVEN, File.B), Position(Rank.EIGHT, File.B) board.promotion_cb = unittest.mock.Mock() piece_cls_mock = board.promotion_cb.return_value board.move(from_pos=f, to_pos=t) self.assertEqual(board[t.rank][t.file], piece_cls_mock.return_value)
def test_move_generation(self): test_table = [ { 'name': 'friendly_on_diagonals_should_not_be_capturable', 'board': Board.from_strings([ # bcdefgh 'b.b.....', # 8 '.b......', # 7 'b.b.....', # 6 '........', # 5 '........', # 4 'B.B.....', # 3 '.B......', # 2 'B.B.....' # 1 ]), 'want': { 'white': { Position(Rank.TWO, File.B): {} }, 'black': { Position(Rank.TWO, File.B): {} }, } }, { 'name': 'enemies_on_diagonals_should_be_capturable', 'board': Board.from_strings([ # bcdefgh '..B...B.', # 8 '........', # 7 '....b...', # 6 'b...b...', # 5 '..B...B.', # 4 '..B.....', # 3 '........', # 2 'b...b...' # 1 ]), 'want': { 'white': target_board([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 'X...X...', # 5 '.x.x....', # 4 '..T.....', # 3 '.x.x....', # 2 'X...X...' # 1 ]), 'black': target_board([ # bcdefgh '..X...X.', # 8 '...x.x..', # 7 '....T...', # 6 '...x.x..', # 5 '..X...X.', # 4 '........', # 3 '........', # 2 '........' # 1 ]) } }, { 'name': 'should_not_be_able_to_go_over_pieces', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '...bB...', # 5 '........', # 4 '..b..B..', # 3 '........', # 2 'B......b' # 1 ]), 'want': { 'white': { Position(Rank.ONE, File.A): { (+1, +1), (+2, +2), # up right } }, 'black': { Position(Rank.ONE, File.H): { (+1, -1), (+2, -2), # up left diagonal } } } }, ] for test_case in test_table: self.runMoveGenerationTest(test_case)
def test_pawn_en_passant_legal_scenarios(self): test_table = [ { # white captures 'name': 'en_passant_square_is_legal_move', 'board': Board.from_strings([ # bcdefgh '........', # 8 '.p......', # 7 '........', # 6 'P.P.....', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 ]), 'move_before': { 'from': P('b7'), 'to': P('b5') }, 'want': { 'white': { # given: {want...} P('c5'): {P('c6'), P('b6')}, P('a5'): {P('a6'), P('b6')} } } }, { # black captures 'name': 'en_passant_position_is_in_legal_moves', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 'p.p.....', # 4 '........', # 3 '.P......', # 2 '........' # 1 ]), 'move_before': { 'from': P('b2'), 'to': P('b4') }, 'want': { 'black': { P('c4'): {P('c3'), P('b3')}, P('a4'): {P('a3'), P('b3')} } } } ] for test_case in test_table: board = test_case['board'] f, t = test_case['move_before']['from'], test_case['move_before'][ 'to'] board.move(from_pos=f, to_pos=t) self.assertEqual(board[t.rank][t.file].figure_type, Type.PAWN) self.runMoveGenerationTest(test_case)
def test_move_generation(self): test_table = [ *self.all_board_rotations_of({ 'name': 'friendly_on_diagonals_should_not_be_capturable', 'board': Board.from_strings([ # bcdefgh '....q..q', # 8 '........', # 7 '........', # 6 '....q..q', # 5 'Q..Q....', # 4 '........', # 3 '........', # 2 'Q..Q....' # 1 ]), 'want': { 'white': target_board([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 'Q..Q....', # 4 'x.x.....', # 3 'xx......', # 2 'TxxQ....' # 1 # bcdefgh ]), 'black': target_board([ # bcdefgh '....qxxT', # 8 '......xx', # 7 '.....x.x', # 6 '....q..q', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 # bcdefgh ]), } }), *self.all_board_rotations_of({ 'name': 'should_go_no_further_than_the_enemy', 'board': Board.from_strings([ # bcdefgh '....Q..q', # 8 '........', # 7 '........', # 6 '....Q..Q', # 5 'q..q....', # 4 '........', # 3 '........', # 2 'Q..q....' # 1 ]), 'want': { 'white': target_board([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 'x..x....', # 4 'x.x.....', # 3 'xx......', # 2 'Txxx....' # 1 # bcdefgh ]), 'black': target_board([ # bcdefgh '....xxxT', # 8 '......xx', # 7 '.....x.x', # 6 '....x..x', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 # bcdefgh ]), } }), { 'name': 'clear_board_all_positions', 'board': Board.from_strings([ # bcdefgh '.......K', # 8 '........', # 7 '........', # 6 '...Q....', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 ]), 'want': { 'white': target_board([ # bcdefgh 'x..x..x.', # 8 '.x.x.x..', # 7 '..xxx...', # 6 'xxxTxxxx', # 5 '..xxx...', # 4 '.x.x.x..', # 3 'x..x..x.', # 2 '...x...x' # 1 ]), } }, { 'name': 'clear_board_all_positions', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '...q....', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 ]), 'want': { 'black': target_board([ # bcdefgh 'x..x..x.', # 8 '.x.x.x..', # 7 '..xxx...', # 6 'xxxTxxxx', # 5 '..xxx...', # 4 '.x.x.x..', # 3 'x..x..x.', # 2 '...x...x' # 1 ]), } }, ] for test_case in test_table: self.runMoveGenerationTest(test_case)
def test_en_passant_should_capture_opposing_pawn(self): with self.subTest('black_captures_left'): board = Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 '..p.....', # 4 '........', # 3 '.P......', # 2 '........' # 1 # bcdefgh ]) board.move(from_pos=P('b2'), to_pos=P('b4')) self.assertIsInstance(board[Rank.FOUR][File.C], Pawn) black_pawn = board[Rank.FOUR][File.C] self.assertIn(P('b3'), black_pawn.generate_moves(board, P('c4'))) self.assertIsInstance(board[Rank.FOUR][File.B], Pawn) board.move(from_pos=P('c4'), to_pos=P('b3')) self.assertIsNone(board[Rank.FOUR][File.B]) with self.subTest('white_captures_left'): board = Board.from_strings([ # bcdefgh '........', # 8 '..p.....', # 7 '........', # 6 '.P......', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 ]) board.move(from_pos=P('c7'), to_pos=P('c5')) self.assertIsInstance(board[Rank.FIVE][File.B], Pawn) white_pawn = board[Rank.FIVE][File.B] self.assertIn(P('c6'), white_pawn.generate_moves(board, P('b5'))) board.move(from_pos=P('b5'), to_pos=P('c6')) self.assertIsNone(board[Rank.FIVE][File.C]) with self.subTest('black_captures_right'): board = Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 '..p.....', # 4 '........', # 3 '...P....', # 2 '........' # 1 # bcdefgh ]) board.move(from_pos=P('d2'), to_pos=P('d4')) self.assertIsInstance(board[Rank.FOUR][File.C], Pawn) black_pawn = board[Rank.FOUR][File.C] self.assertIn(P('d3'), black_pawn.generate_moves(board, P('c4'))) self.assertIsInstance(board[Rank.FOUR][File.D], Pawn) board.move(from_pos=P('c4'), to_pos=P('d3')) self.assertIsNone(board[Rank.FOUR][File.D]) with self.subTest('white_captures_right'): board = Board.from_strings([ # bcdefgh '........', # 8 '..p.....', # 7 '........', # 6 '...P....', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 ]) board.move(from_pos=P('c7'), to_pos=P('c5')) self.assertIsInstance(board[Rank.FIVE][File.D], Pawn) white_pawn = board[Rank.FIVE][File.D] self.assertIn(P('c6'), white_pawn.generate_moves(board, P('d5'))) board.move(from_pos=P('d5'), to_pos=P('c6')) self.assertIsNone(board[Rank.FIVE][File.C])
def test_castling_scenarios(self): test_table = [ { 'name': 'should_be_able_to_castle_both_sides', 'board': Board.from_strings([ # bcdefgh 'r...k..r', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'R...K..R' # 1 ]), 'want': { 'white_queen_side': { 'assert': self.assertIn, P('e1'): P('c1'), }, 'white_king_side': { 'assert': self.assertIn, P('e1'): P('g1'), }, 'black_queen_side': { 'assert': self.assertIn, P('e8'): P('c8'), }, 'black_king_side': { 'assert': self.assertIn, P('e8'): P('g8'), } } }, { 'name': 'should_be_able_to_castle_queen_side', 'board': Board.from_strings([ # bcdefgh 'r...k...', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'R...K...' # 1 ]), 'want': { 'white': { 'assert': self.assertIn, P('e1'): P('c1'), }, 'black': { 'assert': self.assertIn, P('e8'): P('c8'), }, } }, { 'name': 'should_be_able_to_castle_king_side', 'board': Board.from_strings([ # bcdefgh '....k..r', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 '....K..R' # 1 ]), 'want': { 'white': { 'assert': self.assertIn, P('e1'): P('g1'), }, 'black': { 'assert': self.assertIn, P('e8'): P('g8'), }, } }, { 'name': 'standard_configuration_should_not_be_able_to_castle_because_of_blockers', 'board': Board.from_strings([ # bcdefgh 'rnbqkbnr', # 8 'pppppppp', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 'PPPPPPPP', # 2 'RNBQKBNR' # 1 ]), 'want': { 'white': { P('e1'): {} }, 'black': { P('e8'): {} }, } }, { 'name': 'should_not_castle_if_file_is_blocked_by_friendly', 'board': Board.from_strings([ # bcdefgh 'rn..k.nr', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'RN..K.NR' # 1 ]), 'want': { 'white_queen_side': { 'assert': self.assertNotIn, P('e1'): P('c1'), }, 'white_king_side': { 'assert': self.assertNotIn, P('e1'): P('g1'), }, 'black_queen_side': { 'assert': self.assertNotIn, P('e8'): P('c8'), }, 'black_king_side': { 'assert': self.assertNotIn, P('e8'): P('g8'), } } }, { 'name': 'should_not_be_able_to_castle_if_king_is_in_check', 'board': Board.from_strings([ # bcdefgh 'r...k..r', # 8 '........', # 7 '....R...', # 6 '........', # 5 '........', # 4 '....r...', # 3 '........', # 2 'R...K..R' # 1 ]), 'want': { 'white_queen_side': { 'assert': self.assertNotIn, P('e1'): P('c1'), }, 'white_king_side': { 'assert': self.assertNotIn, P('e1'): P('g1'), }, 'black_queen_side': { 'assert': self.assertNotIn, P('e8'): P('c8'), }, 'black_king_side': { 'assert': self.assertNotIn, P('e8'): P('g8'), } } }, { 'name': 'should_not_be_able_to_castle_if_king_will_be_in_check_if_he_castles', 'board': Board.from_strings([ # bcdefgh 'r...k..r', # 8 '........', # 7 '..R...R.', # 6 '........', # 5 '........', # 4 '..r...r.', # 3 '........', # 2 'R...K..R' # 1 ]), 'want': { 'white_queen_side': { 'assert': self.assertNotIn, P('e1'): P('c1'), }, 'white_king_side': { 'assert': self.assertNotIn, P('e1'): P('g1'), }, 'black_queen_side': { 'assert': self.assertNotIn, P('e8'): P('c8'), }, 'black_king_side': { 'assert': self.assertNotIn, P('e8'): P('g8'), } } }, { 'name': 'should_not_be_able_to_castle_if_last_files_are_not_rooks', 'board': Board.from_strings([ # bcdefgh 'b...k..b', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'B...K..B' # 1 ]), 'want': { 'white_queen_side': { 'assert': self.assertNotIn, P('e1'): P('c1'), }, 'white_king_side': { 'assert': self.assertNotIn, P('e1'): P('g1'), }, 'black_queen_side': { 'assert': self.assertNotIn, P('e8'): P('c8'), }, 'black_king_side': { 'assert': self.assertNotIn, P('e8'): P('g8'), } } }, { 'name': 'should_not_be_able_to_castle_if_the_king_passes_through_a_check', 'board': Board.from_strings([ # bcdefgh 'b...k..b', # 8 '........', # 7 '...R.R..', # 6 '........', # 5 '........', # 4 '...r.r..', # 3 '........', # 2 'B...K..B' # 1 ]), 'want': { 'white_queen_side': { 'assert': self.assertNotIn, P('e1'): P('c1'), }, 'white_king_side': { 'assert': self.assertNotIn, P('e1'): P('g1'), }, 'black_queen_side': { 'assert': self.assertNotIn, P('e8'): P('c8'), }, 'black_king_side': { 'assert': self.assertNotIn, P('e8'): P('g8'), } } }, ] for test_case in test_table: self.runMoveGenerationTest(test_case)
def test_moving_rooks_should_disallow_future_castling_only_on_that_side( self): with self.subTest('white queen side'): board = Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'R...K..R' # 1 # bcdefgh ]) white_king = board[Rank.ONE][File.E] moves = white_king.generate_moves(board, P('e1')) self.assertIn(P('c1'), moves) self.assertIn(P('g1'), moves) # move the rook and then return it in it's original position board.move(from_pos=P('h1'), to_pos=P('g1')) board.move(from_pos=P('g1'), to_pos=P('h1')) moves = white_king.generate_moves(board, P('e1')) self.assertNotIn(P('g1'), moves) self.assertIn(P('c1'), moves) with self.subTest('white king side'): board = Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'R...K..R' # 1 # bcdefgh ]) white_king = board[Rank.ONE][File.E] moves = white_king.generate_moves(board, P('e1')) self.assertIn(P('g1'), moves) self.assertIn(P('c1'), moves) # move the right rook and then return it in it's original position board.move(from_pos=P('a1'), to_pos=P('b1')) board.move(from_pos=P('b1'), to_pos=P('a1')) moves = white_king.generate_moves(board, P('e1')) self.assertNotIn(P('c1'), moves) self.assertIn(P('g1'), moves) with self.subTest('black queen side'): board = Board.from_strings([ # bcdefgh 'r...k..r', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 # bcdefgh ]) white_king = board[Rank.EIGHT][File.E] moves = white_king.generate_moves(board, P('e8')) self.assertIn(P('c8'), moves) # move the rook and then return it in it's original position board.move(from_pos=P('a8'), to_pos=P('b8')) board.move(from_pos=P('b8'), to_pos=P('a8')) moves = white_king.generate_moves(board, P('e8')) self.assertNotIn(P('c8'), moves) self.assertIn(P('g8'), moves) with self.subTest('black king side'): board = Board.from_strings([ # bcdefgh 'r...k..r', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 # bcdefgh ]) white_king = board[Rank.EIGHT][File.E] moves = white_king.generate_moves(board, P('e8')) self.assertIn(P('g8'), moves) self.assertIn(P('c8'), moves) # move the rook and then return it in it's original position board.move(from_pos=P('h8'), to_pos=P('g8')) board.move(from_pos=P('g8'), to_pos=P('h8')) moves = white_king.generate_moves(board, P('e8')) self.assertNotIn(P('g8'), moves) self.assertIn(P('c8'), moves)
def test_knight_move_geneneration(self): test_table = [ { 'name': 'clear_board_should_have_all_surrounding_positions_white', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 '....N...', # 4 '........', # 3 '........', # 2 '........' # 1 ]), 'want': { 'white': target_board([ # bcdefgh '........', # 8 '........', # 7 '...x.x..', # 6 '..x...x.', # 5 '....T...', # 4 '..x...x.', # 3 '...x.x..', # 2 '........' # 1 ]), }, }, { 'name': 'clear_board_should_have_all_surrounding_positions_black', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 '....n...', # 4 '........', # 3 '........', # 2 '........' # 1 ]), 'want': { 'white': target_board([ # bcdefgh '........', # 8 '........', # 7 '...x.x..', # 6 '..x...x.', # 5 '....T...', # 4 '..x...x.', # 3 '...x.x..', # 2 '........' # 1 ]), }, }, *self.all_board_rotations_of({ 'name': 'should_be_bounds_aware', 'board': Board.from_strings([ # bcdefgh '.......N', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'n.......' # 1 ]), 'want': { 'white': target_board([ # bcdefgh '.......T', # 8 '.....x..', # 7 '......x.', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'n.......' # 1 ]), 'black': target_board([ # bcdefgh '.......N', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '.x......', # 3 '..x.....', # 2 'T.......' # 1 ]), }, }), *self.all_board_rotations_of({ 'name': 'should_not_be_able_to_capture_friendlies', 'board': Board.from_strings([ # bcdefgh '.......N', # 8 '.....N..', # 7 '......N.', # 6 '........', # 5 '........', # 4 '.n......', # 3 '..n.....', # 2 'n.......' # 1 ]), 'want': { 'white': target_board([ # bcdefgh '.......T', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'n.......' # 1 ]), 'black': target_board([ # bcdefgh '.......N', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'T.......' # 1 ]), }, }), *self.all_board_rotations_of({ 'name': 'should_be_able_to_capture_enemies', 'board': Board.from_strings([ # bcdefgh '.......N', # 8 '.....n..', # 7 '......n.', # 6 '........', # 5 '........', # 4 '.N......', # 3 '..N.....', # 2 'n.......' # 1 ]), 'want': { 'white': target_board([ # bcdefgh '.......T', # 8 '.....x..', # 7 '......x.', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'n.......' # 1 ]), 'black': target_board([ # bcdefgh '.......N', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '.x......', # 3 '..x.....', # 2 'T.......' # 1 ]), }, }), ] for test_case in test_table: self.runMoveGenerationTest(test_case)
def test_should_move_rook_when_king_castles(self): with self.subTest('black king side'): board = Board.from_strings([ # bcdefgh 'r...k..r', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 # bcdefgh ]) black_king = board[Rank.EIGHT][File.E] self.assertIn(P('g8'), black_king.generate_moves(board, P('e8'))) board.move(from_pos=P('e8'), to_pos=P('g8')) self.assertIsInstance(board[Rank.EIGHT][File.F], Rook) # rook has moved with self.subTest('black queen side'): board = Board.from_strings([ # bcdefgh 'r...k..r', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 ]) black_king = board[Rank.EIGHT][File.E] self.assertIn(P('c8'), black_king.generate_moves(board, P('e8'))) board.move(from_pos=P('e8'), to_pos=P('c8')) self.assertIsInstance(board[Rank.EIGHT][File.D], Rook) # rook has moved with self.subTest('white king side'): board = Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'R...K..R' # 1 ]) white_king = board[Rank.ONE][File.E] self.assertIn(P('c1'), white_king.generate_moves(board, P('e1'))) board.move(from_pos=P('e1'), to_pos=P('g1')) self.assertIsInstance(board[Rank.ONE][File.F], Rook) # rook has moved with self.subTest('white queen side'): board = Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'R...K..R' # 1 ]) white_king = board[Rank.ONE][File.E] self.assertIn(P('c1'), white_king.generate_moves(board, P('e1'))) board.move(from_pos=P('e1'), to_pos=P('c1')) self.assertIsInstance(board[Rank.ONE][File.D], Rook) # rook has moved
def test_pieces_should_body_block_checks(self): """ Even though there may be other possible moves if something body blocks a check, it should be able to make only such moves that keep blocking the check """ test_table = [ { 'name': 'white_diagonal_pieces_should_not_be_able_to_move_from_diagonals', 'board': Board.from_strings([ # bcdefgh '.b.....q', # 8 '........', # 7 '...Q.Q..', # 6 '....K...', # 5 '...Q.Q..', # 4 '........', # 3 '.q.....b', # 2 '........' # 1 # bcdefgh ]), 'want': { 'white': { P('f6'): {P('g7'), P('h8')}, P('f4'): {P('g3'), P('h2')}, P('d6'): {P('c7'), P('b8')}, P('d4'): {P('c3'), P('b2')}, } }, 'expect_same_behaviour_for': ['bishop'], }, { 'name': 'black_diagonal_pieces_should_not_be_able_to_move_from_diagonals', 'board': Board.from_strings([ # bcdefgh '.B.....Q', # 8 '........', # 7 '...q.q..', # 6 '....k...', # 5 '...b.b..', # 4 '........', # 3 '.Q.....B', # 2 '........' # 1 # bcdefgh ]), 'want': { 'black': { P('f6'): {P('g7'), P('h8')}, P('f4'): {P('g3'), P('h2')}, P('d6'): {P('c7'), P('b8')}, P('d4'): {P('c3'), P('b2')}, } }, 'expect_same_behaviour_for': ['bishop'], }, { 'name': 'white_should_not_be_able_to_move_from_straight', 'board': Board.from_strings([ # bcdefgh '....q...', # 8 '........', # 7 '....Q...', # 6 '.q.QKQ.q', # 5 '....Q...', # 4 '........', # 3 '....q...', # 2 '........' # 1 ]), 'want': { 'white': { P('f5'): {P('g5'), P('h5')}, P('d5'): {P('c5'), P('b5')}, P('e6'): {P('e7'), P('e8')}, P('e4'): {P('e3'), P('e2')}, } }, 'expect_same_behaviour_for': ['rook'], }, { 'name': 'black_figure_should_not_be_able_to_move_from_straight', 'board': Board.from_strings([ # bcdefgh '....Q...', # 8 '........', # 7 '....q...', # 6 '.Q.qkq.Q', # 5 '....q...', # 4 '........', # 3 '....Q...', # 2 '........' # 1 ]), 'want': { 'black': { P('f5'): {P('g5'), P('h5')}, P('d5'): {P('c5'), P('b5')}, P('e6'): {P('e7'), P('e8')}, P('e4'): {P('e3'), P('e2')}, } }, 'expect_same_behaviour_from': ['rook'], }, { 'name': 'knight_should_be_allowed_only_to_block', 'board': Board.from_strings([ # bcdefgh 'N.....n.', # 8 '.q.....Q', # 7 '........', # 6 '.K.....k', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 ]), 'want': { 'white': { P('a8'): {P('b6')} }, 'black': { P('g8'): {P('h6')} } } }, { 'name': 'pawn_should_be_allowed_only_to_block', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '......q.', # 5 '........', # 4 '........', # 3 '.....P..', # 2 '..K.....' # 1 ]), 'want': { 'white': { P('f2'): {P('f4')} }, } }, { 'name': 'should_only_be_able_to_capture_when_knight_checks', 'board': Board.from_strings([ # bcdefgh '.....k..', # 8 '....q..N', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 'n..Q....', # 2 '..K.....' # 1 # bcdefgh ]), 'want': { 'white': target_board([ # bcdefgh '........', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 'x..T....', # 2 '..K.....' # 1 # bcdefgh ]), 'black': target_board([ # bcdefgh '.....k..', # 8 '....T..x', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 '........' # 1 ]), } }, { 'name': 'pieces_that_cannot_protect_king_should_have_no_moves', 'board': Board.from_strings([ # bcdefgh '.....k..', # 8 '.......N', # 7 '...q....', # 6 '........', # 5 '........', # 4 '...Q....', # 3 'n.......', # 2 '..K.....' # 1 # bcdefgh ]), 'want': { 'white': { P('d3'): {} }, 'black': { P('d6'): {} }, } }, ] for test_case in test_table: self.runMoveGenerationTest(test_case)
def test_move_generation(self): test_table = [ *self.all_board_rotations_of({ 'name': 'should_have_two_straights_available', 'board': Board.from_strings([ # bcdefgh '.......r', # 8 '........', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '........', # 2 'R.......' # 1 ]), 'want': { 'white': target_board([ # bcdefgh 'x......r', # 8 'x.......', # 7 'x.......', # 6 'x.......', # 5 'x.......', # 4 'x.......', # 3 'x.......', # 2 'Txxxxxxx' # 1 ]), 'black': target_board([ # bcdefgh 'xxxxxxxT', # 8 '.......x', # 7 '.......x', # 6 '.......x', # 5 '.......x', # 4 '.......x', # 3 '.......x', # 2 'R......x' # 1 ]), } }), *self.all_board_rotations_of({ 'name': 'should_stop_at_first_friendly', 'board': Board.from_strings([ # bcdefgh '....r..r', # 8 '........', # 7 '........', # 6 '.......r', # 5 'R.......', # 4 '........', # 3 '........', # 2 'R..R....' # 1 ]), 'want': { 'white': target_board([ # bcdefgh '....r..r', # 8 '........', # 7 '........', # 6 '.......r', # 5 'R.......', # 4 'x.......', # 3 'x.......', # 2 'TxxR....' # 1 ]), 'black': target_board([ # bcdefgh '....rxxT', # 8 '.......x', # 7 '.......x', # 6 '.......r', # 5 'R.......', # 4 '........', # 3 '........', # 2 'R..R....' # 1 ]), } }), { 'name': 'should_stop_on_top_of_first_enemy', 'board': Board.from_strings([ # bcdefgh '....R..r', # 8 '........', # 7 '........', # 6 '.......R', # 5 'r.......', # 4 '........', # 3 '........', # 2 'R..r....' # 1 ]), 'want': { 'white': target_board([ # bcdefgh '....xxxT', # 8 '.......x', # 7 '.......x', # 6 '.......x', # 5 'r.......', # 4 '........', # 3 '........', # 2 'R..r....' # 1 ]), 'black': target_board([ # bcdefgh '....R..r', # 8 '........', # 7 '........', # 6 '.......R', # 5 'x.......', # 4 'x.......', # 3 'x.......', # 2 'Txxx....' # 1 ]), } } ] for test_case in test_table: self.runMoveGenerationTest(test_case)
def test_pawn_move_generation(self): test_table = [ { 'name': 'when_friendly_is_blocking', 'board': Board.from_strings([ # bcdefgh '........', # 8 '.p......', # 7 '........', # 6 '.p......', # 5 '.P......', # 4 '........', # 3 '.P......', # 2 '........' # 1 ]), 'want': { 'white': { Position(Rank.TWO, File.B): {Position(Rank.THREE, File.B)} }, 'black': { Position(Rank.SEVEN, File.B): {Position(Rank.SIX, File.B)} }, } }, { 'name': 'given_starting_position_enemy_is_blocking_two_squares_ahead', 'board': Board.from_strings([ # bcdefgh '........', # 8 '.p......', # 7 '........', # 6 '.P......', # 5 '.p......', # 4 '........', # 3 '.P......', # 2 '........' # 1 ]), 'want': { 'white': { Position(Rank.TWO, File.B): {Position(Rank.THREE, File.B)} }, 'black': { Position(Rank.SEVEN, File.B): {Position(Rank.SIX, File.B)} }, } }, { 'name': 'should_capture_left', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '.p......', # 6 'P.......', # 5 'p.......', # 4 '.P......', # 3 '........', # 2 '........' # 1 ]), 'want': { 'white': { Position(Rank.THREE, File.B): { Position(Rank.FOUR, File.A), Position(Rank.FOUR, File.B), } }, 'black': { Position(Rank.SIX, File.B): { Position(Rank.FIVE, File.A), Position(Rank.FIVE, File.B), } }, } }, { 'name': 'should_not_capture_left_friendly', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '.p......', # 6 'p.......', # 5 'P.......', # 4 '.P......', # 3 '........', # 2 '........' # 1 ]), 'want': { 'white': { Position(Rank.THREE, File.B): { Position(Rank.FOUR, File.B), } }, 'black': { Position(Rank.SIX, File.B): { Position(Rank.FIVE, File.B), } }, } }, { 'name': 'should_capture_right', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '.p......', # 6 '..P.....', # 5 '..p.....', # 4 '.P......', # 3 '........', # 2 '........' # 1 ]), 'want': { 'white': { Position(Rank.THREE, File.B): { Position(Rank.FOUR, File.B), Position(Rank.FOUR, File.C), } }, 'black': { Position(Rank.SIX, File.B): { Position(Rank.FIVE, File.B), Position(Rank.FIVE, File.C), } }, } }, { 'name': 'should_not_capture_right_friendly', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '.p......', # 6 '..p.....', # 5 '..P.....', # 4 '.P......', # 3 '........', # 2 '........' # 1 ]), 'want': { 'white': { Position(Rank.THREE, File.B): { Position(Rank.FOUR, File.B), } }, 'black': { Position(Rank.SIX, File.B): { Position(Rank.FIVE, File.B), } }, } }, { 'name': 'should_be_able_to_mode_two_ahead_from_start', 'board': Board.from_strings([ # bcdefgh '........', # 8 '.p......', # 7 '........', # 6 '........', # 5 '........', # 4 '........', # 3 '.P......', # 2 '........' # 1 ]), 'want': { 'white': { Position(Rank.TWO, File.B): { Position(Rank.THREE, File.B), Position(Rank.FOUR, File.B), } }, 'black': { Position(Rank.SEVEN, File.B): { Position(Rank.SIX, File.B), Position(Rank.FIVE, File.B), } }, } }, { 'name': 'should_have_all_positions', 'board': Board.from_strings([ # bcdefgh '........', # 8 '.p......', # 7 'P.P.....', # 6 '........', # 5 '........', # 4 'p.p.....', # 3 '.P......', # 2 '........' # 1 ]), 'want': { 'white': { Position(Rank.TWO, File.B): { Position(Rank.THREE, File.A), Position(Rank.THREE, File.B), Position(Rank.THREE, File.C), Position(Rank.FOUR, File.B), } }, 'black': { Position(Rank.SEVEN, File.B): { Position(Rank.SIX, File.A), Position(Rank.SIX, File.B), Position(Rank.SIX, File.C), Position(Rank.FIVE, File.B), } }, } }, { 'name': 'clear_file_not_starting_square_should_have_only_one_ahead', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '.p......', # 6 '........', # 5 '........', # 4 '.P......', # 3 '........', # 2 '........' # 1 ]), 'want': { 'white': { Position(Rank.THREE, File.B): { Position(Rank.FOUR, File.B), } }, 'black': { Position(Rank.SIX, File.B): { Position(Rank.FIVE, File.B), } }, } }, { 'name': 'friendly_blocker_and_no_captures_should_have_no_moves', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '.p......', # 6 '.p......', # 5 '.P......', # 4 '.P......', # 3 '........', # 2 '........' # 1 ]), 'want': { 'white': { Position(Rank.THREE, File.B): {} }, 'black': { Position(Rank.SIX, File.B): {} }, } }, { 'name': 'enemy_blocker_and_no_captures_should_have_no_moves', 'board': Board.from_strings([ # bcdefgh '........', # 8 '........', # 7 '.p......', # 6 '.P......', # 5 '.p......', # 4 '.P......', # 3 '........', # 2 '........' # 1 ]), 'want': { 'white': { Position(Rank.THREE, File.B): {} }, 'black': { Position(Rank.SIX, File.B): {} }, } }, ] for test_case in test_table: self.runMoveGenerationTest(test_case)