-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Calculate legal moves in Chess Golf, for #27.
- Loading branch information
Showing
4 changed files
with
369 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import typing | ||
from collections import Counter | ||
from textwrap import dedent | ||
|
||
import chess | ||
|
||
from board_parser import parse_board | ||
|
||
|
||
class GolfState: | ||
def __init__(self, text: str): | ||
board_lines = text.splitlines() | ||
board_text = '\n'.join(board_lines[:8]) | ||
self.board = parse_board(board_text) | ||
self.taking = None | ||
self.taken = Counter() | ||
self.chosen = Counter() | ||
for line in board_lines[8:]: | ||
if line.startswith('chosen:'): | ||
chosen_text = line[7:].strip() | ||
self.chosen = Counter(chess.Piece.from_symbol(c) | ||
for c in chosen_text) | ||
elif line.startswith('taking:'): | ||
self.taking = chess.parse_square(line[7:].strip()) | ||
elif line.startswith('taken:'): | ||
taken_text = line[6:].strip() | ||
self.taken = Counter(chess.Piece.from_symbol(c) | ||
for c in taken_text) | ||
else: | ||
label = line.split(':')[0] | ||
raise ValueError(f'Unknown golf label: {label!r}.') | ||
|
||
def find_moves(self): | ||
for square, piece in self.board.piece_map().items(): | ||
board_copy = self.board.copy() | ||
moving_piece = board_copy.piece_at(square) | ||
can_capture = moving_piece in self.chosen | ||
if can_capture and self.taking is not None: | ||
can_capture = square == self.taking | ||
if not can_capture: | ||
target_counts = None | ||
else: | ||
target_counts = self.chosen - self.taken | ||
target_counts[moving_piece] -= 1 | ||
for neighbour_type in get_neighbour_types(self.board, square): | ||
for turn in (chess.WHITE, chess.BLACK): | ||
fake_moving_piece = chess.Piece(neighbour_type, turn) | ||
board_copy.set_piece_at(square, fake_moving_piece) | ||
board_copy.turn = turn | ||
from_bitboard = 1 << square | ||
for move in board_copy.generate_pseudo_legal_moves( | ||
from_bitboard): | ||
is_capture = board_copy.is_capture(move) | ||
if not is_capture: | ||
if turn == chess.BLACK: | ||
continue | ||
else: | ||
if not can_capture: | ||
continue | ||
captured_piece = self.board.piece_at(move.to_square) | ||
if target_counts[captured_piece] <= 0: | ||
continue | ||
|
||
yield move | ||
|
||
|
||
def get_neighbour_types(board: chess.Board, | ||
square: chess.Square) -> typing.Set[chess.PieceType]: | ||
neighbour_types = set() | ||
king_board = chess.Board() | ||
king_board.set_piece_at(square, chess.Piece(chess.KING, chess.WHITE)) | ||
start_piece = board.piece_at(square) | ||
for neighbour_square in king_board.attacks(square): | ||
neighbour = board.piece_at(neighbour_square) | ||
if neighbour is None: | ||
continue | ||
if neighbour.color != start_piece.color: | ||
continue | ||
neighbour_types.add(neighbour.piece_type) | ||
return neighbour_types | ||
|
||
|
||
def main(): | ||
board = parse_board(dedent("""\ | ||
. . B . . . R . | ||
. . . . . k . . | ||
. r . n . N . . | ||
. r . . b . . . | ||
R . . . . . q . | ||
. B . n . N . . | ||
b . . . . . . K | ||
. . . . . Q . .""")) | ||
for square, piece in board.piece_map().items(): | ||
print(square, piece) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
from collections import Counter | ||
from textwrap import dedent | ||
|
||
import chess | ||
import pytest | ||
|
||
from board_parser import parse_board | ||
from golf import get_neighbour_types, GolfState | ||
|
||
|
||
def test_get_neighbour_types(): | ||
board = parse_board(dedent("""\ | ||
. . B . . . R . | ||
. . . . . . . . | ||
. r . n . Q . . | ||
. r . . b k . . | ||
R . . . . . q . | ||
. B . n . N . . | ||
b . . . . . . K | ||
. . . . . N . .""")) | ||
expected_neighbour_types = {chess.KNIGHT, chess.KING} | ||
|
||
neighbour_types = get_neighbour_types(board, chess.E5) | ||
|
||
assert board.piece_at(chess.E5) == chess.Piece(chess.BISHOP, chess.BLACK) | ||
assert neighbour_types == expected_neighbour_types | ||
|
||
|
||
def test_new_golf_state(): | ||
start_text = dedent("""\ | ||
. . B . . . R . | ||
. . . . . . . . | ||
. r . n . Q . . | ||
. r . . b k . . | ||
R . . . . . q . | ||
. B . n . N . . | ||
b . . . . . . K | ||
. . . . . N . . | ||
chosen: Bbq""") | ||
expected_board = parse_board(start_text.split('chosen')[0]) | ||
|
||
state = GolfState(start_text) | ||
|
||
assert state.board == expected_board | ||
assert state.taking is None | ||
assert state.taken == Counter() | ||
assert state.chosen == Counter([chess.Piece(chess.BISHOP, chess.WHITE), | ||
chess.Piece(chess.BISHOP, chess.BLACK), | ||
chess.Piece(chess.QUEEN, chess.BLACK)]) | ||
|
||
|
||
def test_captured_golf_state(): | ||
start_text = dedent("""\ | ||
. . B . . . R . | ||
. . . . . . . . | ||
. r . n . Q . . | ||
. r . . b k . . | ||
R . . . . . q . | ||
. . . n . N . . | ||
B . . . . . . K | ||
. . . . . N . . | ||
chosen: Bbq | ||
taking: a2 | ||
taken: b""") | ||
|
||
state = GolfState(start_text) | ||
|
||
assert state.taking == chess.A2 | ||
assert state.taken == Counter([chess.Piece(chess.BISHOP, chess.BLACK)]) | ||
assert state.chosen == Counter([chess.Piece(chess.BISHOP, chess.WHITE), | ||
chess.Piece(chess.BISHOP, chess.BLACK), | ||
chess.Piece(chess.QUEEN, chess.BLACK)]) | ||
|
||
|
||
def test_bogus_golf_state(): | ||
start_text = dedent("""\ | ||
. . B . . . R . | ||
. . . . . . . . | ||
. r . n . Q . . | ||
. r . . b k . . | ||
R . . . . . q . | ||
. . . n . N . . | ||
B . . . . . . K | ||
. . . . . N . . | ||
bogus: Bbq""") | ||
|
||
with pytest.raises(ValueError, match=r"Unknown golf label: 'bogus'."): | ||
GolfState(start_text) | ||
|
||
|
||
def test_find_moves_not_chosen_taker(): | ||
state = GolfState(dedent("""\ | ||
n k . . . . R N | ||
. . . B r . n . | ||
. . q . . B . . | ||
. . . . . . . b | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
chosen: Bb""")) | ||
|
||
expected_moves = {chess.Move(chess.A8, chess.A7), # knight uses king | ||
chess.Move(chess.A8, chess.B7), | ||
chess.Move(chess.B8, chess.A6), # king uses knight | ||
chess.Move(chess.G8, chess.H6), # rook uses knight | ||
chess.Move(chess.H8, chess.H7), # knight uses rook | ||
chess.Move(chess.H8, chess.H6)} | ||
|
||
moves = list(state.find_moves()) | ||
|
||
assert len(moves) == len(expected_moves) | ||
assert set(moves) == expected_moves | ||
|
||
|
||
# noinspection DuplicatedCode | ||
def test_find_moves_not_chosen_taken(): | ||
state = GolfState(dedent("""\ | ||
. . . . . . R B | ||
. . . . . b . . | ||
. . . . . . . . | ||
. . . . . . . n | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
chosen: Bb""")) | ||
|
||
expected_moves = {chess.Move(chess.H8, chess.H7), # bishop uses rook | ||
chess.Move(chess.H8, chess.H6), | ||
chess.Move(chess.G8, chess.H7)} # rook uses bishop | ||
|
||
moves = list(state.find_moves()) | ||
|
||
assert len(moves) == len(expected_moves) | ||
assert set(moves) == expected_moves | ||
|
||
|
||
# noinspection DuplicatedCode | ||
def test_find_moves_both_chosen(): | ||
state = GolfState(dedent("""\ | ||
. . . . . . R B | ||
. . . . . n . . | ||
. . . . . . . . | ||
. . . . . . . b | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
chosen: Bb""")) | ||
|
||
expected_moves = {chess.Move(chess.H8, chess.H7), # bishop uses rook | ||
chess.Move(chess.H8, chess.H6), | ||
chess.Move(chess.H8, chess.H5), | ||
chess.Move(chess.G8, chess.H7)} # rook uses bishop | ||
|
||
moves = list(state.find_moves()) | ||
|
||
assert len(moves) == len(expected_moves) | ||
assert set(moves) == expected_moves | ||
|
||
|
||
# noinspection DuplicatedCode | ||
def test_find_moves_same_type_not_allowed(): | ||
state = GolfState(dedent("""\ | ||
. . . . . . R B | ||
. . . . . b . . | ||
. . . . . . . . | ||
. . . . . . . B | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
chosen: Bb""")) | ||
|
||
expected_moves = {chess.Move(chess.H8, chess.H7), # bishop uses rook | ||
chess.Move(chess.H8, chess.H6), | ||
chess.Move(chess.G8, chess.H7)} # rook uses bishop | ||
|
||
moves = list(state.find_moves()) | ||
|
||
assert len(moves) == len(expected_moves) | ||
assert set(moves) == expected_moves | ||
|
||
|
||
# noinspection DuplicatedCode | ||
def test_find_moves_same_type_allowed(): | ||
state = GolfState(dedent("""\ | ||
. . . . . . R B | ||
. . . . . b . . | ||
. . . . . . . . | ||
. . . . . . . B | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
chosen: BB""")) | ||
|
||
expected_moves = {chess.Move(chess.H8, chess.H7), # bishop uses rook | ||
chess.Move(chess.H8, chess.H6), | ||
chess.Move(chess.H8, chess.H5), | ||
chess.Move(chess.G8, chess.H7)} # rook uses bishop | ||
|
||
moves = list(state.find_moves()) | ||
|
||
assert len(moves) == len(expected_moves) | ||
assert set(moves) == expected_moves | ||
|
||
|
||
# noinspection DuplicatedCode | ||
def test_find_moves_single_piece_may_capture(): | ||
state = GolfState(dedent("""\ | ||
B R . . . . R B | ||
n . q . . k . n | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
. . . . . . . . | ||
chosen: Bbn | ||
taking: a8 | ||
taken: b""")) | ||
|
||
expected_moves = {chess.Move(chess.A8, chess.A7)} # bishop uses rook | ||
|
||
moves = list(state.find_moves()) | ||
|
||
assert len(moves) == len(expected_moves) | ||
assert set(moves) == expected_moves | ||
|
||
|
||
# noinspection DuplicatedCode | ||
def test_find_moves(): | ||
state = GolfState(dedent("""\ | ||
B R . . . . . . | ||
r . . . . . . . | ||
. . . k . . . . | ||
. . . . . . . . | ||
. . . . . q . . | ||
. . . . K . . . | ||
. . . . . . b . | ||
. . . . Q . . n | ||
chosen: Bbr | ||
taking: a8 | ||
taken: r""")) | ||
|
||
expected_moves = {chess.Move(chess.B8, chess.C7), # rook uses bishop | ||
chess.Move(chess.G2, chess.H4)} # bishop uses knight | ||
|
||
moves = list(state.find_moves()) | ||
|
||
assert len(moves) == len(expected_moves) | ||
assert set(moves) == expected_moves |