Skip to content

Commit 8fbd6f4

Browse files
committed
reformat: migrated board to cython
1 parent 37127c0 commit 8fbd6f4

File tree

9 files changed

+228
-136
lines changed

9 files changed

+228
-136
lines changed

.github/workflows/python-app.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ jobs:
2323
with:
2424
python-version: "3.10"
2525
architecture: "x64"
26-
env:
27-
AGENT_TOOLSDIRECTORY: /opt/hostedtoolcache
2826
- name: Install dependencies
2927
run: |
3028
python -m pip install --upgrade pip
3129
pip install flake8 pytest
3230
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
31+
- name: Cython
32+
run: |
33+
cd lab02
34+
python setup.py build_ext --inplace
3335
#- name: Lint with flake8
3436
# run: |
3537
# # stop the build if there are Python syntax errors or undefined names
@@ -38,4 +40,5 @@ jobs:
3840
# flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
3941
- name: Test with pytest
4042
run: |
43+
cd lab02
4144
pytest

lab02/clobber/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.c

lab02/clobber/board.pxd

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
cdef class Board:
2+
cdef public int n
3+
cdef public int m
4+
cdef public int turn
5+
cdef public list state
6+
7+
cpdef get_piece_at(self, tuple position)
8+
cpdef replace_piece_at(self, tuple position, object new_piece)
9+
cpdef list get_neighbours_positions_filtered(self, tuple position, object piece_filter)
10+
cpdef list get_all_pieces(self, object piece)
11+
cpdef bint has_moves(self, bint white)

lab02/clobber/board.py

Lines changed: 0 additions & 132 deletions
This file was deleted.

lab02/clobber/board.pyi

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from typing import Callable, List, Tuple, Dict, Any
2+
from clobber.types import piece_type, move
3+
4+
5+
class Board:
6+
n: int
7+
m: int
8+
turn: int
9+
state: List[str]
10+
11+
def __init__(self, n: int, m: int,
12+
state: List[str], turn: int = 0) -> None: ...
13+
14+
@staticmethod
15+
def initialize_board(
16+
n: int, m: int) -> 'Board': ...
17+
18+
def copy(self) -> 'Board': ...
19+
20+
def __str__(self) -> str: ...
21+
def pretty(self) -> str: ...
22+
23+
def get_piece_at(self, position: Tuple[int, int]) -> piece_type: ...
24+
25+
def replace_piece_at(
26+
self, position: Tuple[int, int], new_piece: piece_type) -> piece_type: ...
27+
28+
def get_neighbours_positions(
29+
self, position: Tuple[int, int]) -> List[Tuple[int, int]]: ...
30+
def get_neighbours_positions_filtered(self, position: Tuple[int, int], piece_filter: Callable[[
31+
piece_type], bool]) -> List[Tuple[int, int]]: ...
32+
33+
def make_move(self, new_color: piece_type, m: move) -> None: ...
34+
def move_assuming_correct(
35+
self, new_color: piece_type, m: move) -> None: ...
36+
37+
def get_all_pieces(self, piece: piece_type) -> List[Tuple[int, int]]: ...
38+
def generate_moves(
39+
self, for_white: bool) -> Dict[Tuple[int, int], List[Tuple[int, int]]]: ...
40+
41+
def has_moves(self, white: bool) -> bool: ...

lab02/clobber/board.pyx

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
from typing import Callable
2+
3+
from clobber.types cimport move
4+
5+
cdef class Board:
6+
7+
def __init__(self, int n, int m, list state, int turn=0):
8+
self.n = n
9+
self.m = m
10+
self.state = list(state)
11+
self.turn = turn
12+
13+
@staticmethod
14+
def initialize_board(int n, int m):
15+
state_py = []
16+
cdef int y
17+
cdef bint white_py
18+
19+
for y in range(n):
20+
line_py = []
21+
white_py = (y % 2 == 0)
22+
for _ in range(m):
23+
line_py.append("W" if white_py else "B")
24+
white_py = not white_py
25+
state_py.append(" ".join(line_py))
26+
27+
board = Board(n, m, state_py)
28+
return board
29+
30+
def copy(self):
31+
return Board(self.n, self.m, [row for row in self.state], self.turn)
32+
33+
def __str__(self):
34+
return "\n".join(self.state)
35+
36+
def pretty(self):
37+
result_py = [" " + " ".join(map(str, range(self.m)))]
38+
cdef int y
39+
for y in range(self.n):
40+
result_py.append(str(y) + " " + str(self.state[y]))
41+
return "\n".join(result_py)
42+
43+
cpdef get_piece_at(self, tuple position):
44+
cdef int x, y
45+
x, y = position
46+
47+
if x < 0 or y < 0 or y >= self.n or x >= self.m:
48+
return 'outside'
49+
50+
row_str = <str>self.state[y]
51+
pieces = row_str.split(' ')
52+
return pieces[x]
53+
54+
55+
cpdef replace_piece_at(self, tuple position, object new_piece):
56+
cdef int x, y
57+
x, y = position
58+
59+
if x < 0 or y < 0 or y >= self.n or x >= self.m:
60+
return 'outside'
61+
62+
row_str = <str>self.state[y]
63+
pieces = row_str.split(' ')
64+
if x < len(pieces):
65+
pieces[x] = <str>new_piece
66+
self.state[y] = " ".join(pieces)
67+
return new_piece
68+
else:
69+
return 'error_index'
70+
71+
def get_neighbours_positions(self, tuple position):
72+
return self.get_neighbours_positions_filtered(position, lambda p: p in ["B", "W"])
73+
74+
cpdef list get_neighbours_positions_filtered(self, tuple position, object piece_filter):
75+
cdef int x, y
76+
cdef list neighbours_positions_py = []
77+
cdef tuple check_pos
78+
cdef object piece
79+
80+
x, y = position
81+
82+
positions_to_check = [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)]
83+
for check_pos in positions_to_check:
84+
piece = self.get_piece_at(check_pos)
85+
if piece_filter(piece):
86+
neighbours_positions_py.append(check_pos)
87+
88+
return neighbours_positions_py
89+
90+
def make_move(self, object new_color, move m):
91+
cdef tuple position_from, position_to
92+
position_from, position_to = m
93+
if self.get_piece_at(position_from) != new_color:
94+
raise Exception("make_move check failed: piece is not player's color")
95+
opponent_color = <object>("B" if new_color == 'W' else 'W')
96+
if self.get_piece_at(position_to) != opponent_color:
97+
raise Exception("make_move check failed: target is not opponent")
98+
99+
self.replace_piece_at(position_from, '_')
100+
self.replace_piece_at(position_to, new_color)
101+
self.turn += 1
102+
103+
def move_assuming_correct(self, object new_color, move m):
104+
cdef tuple position_from, position_to
105+
position_from, position_to = m
106+
self.replace_piece_at(position_from, '_')
107+
self.replace_piece_at(position_to, new_color)
108+
self.turn += 1
109+
110+
cpdef list get_all_pieces(self, object piece):
111+
cdef list positions_py = []
112+
cdef int x, y
113+
for y in range(self.n):
114+
for x in range(self.m):
115+
if self.get_piece_at((x, y)) == piece:
116+
positions_py.append((x, y))
117+
return positions_py
118+
119+
def generate_moves(self, bint for_white):
120+
cdef dict possible_moves_py = {}
121+
cdef object opponent, current_piece
122+
cdef int x, y
123+
cdef list neighbouring_opponents_py
124+
125+
opponent = <object>('B' if for_white else 'W')
126+
current_piece = <object>('W' if for_white else 'B')
127+
128+
for y in range(self.n):
129+
for x in range(self.m):
130+
if self.get_piece_at((x, y)) != current_piece:
131+
continue
132+
133+
def opponent_filter(p):
134+
return p == opponent
135+
136+
neighbouring_opponents_py = self.get_neighbours_positions_filtered(
137+
(x, y), opponent_filter)
138+
139+
if not neighbouring_opponents_py:
140+
continue
141+
142+
possible_moves_py[(x, y)] = neighbouring_opponents_py
143+
144+
return possible_moves_py
145+
146+
cpdef bint has_moves(self, bint white):
147+
return len(self.generate_moves(white)) > 0

lab02/clobber/board_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
@pytest.mark.parametrize(("dimensions", "expected"),
77
[
8-
((5, 6), "W_B_W_B_W_B\nB_W_B_W_B_W\nW_B_W_B_W_B\nB_W_B_W_B_W\nW_B_W_B_W_B"),
9-
((10, 10), "W_B_W_B_W_B_W_B_W_B\nB_W_B_W_B_W_B_W_B_W\nW_B_W_B_W_B_W_B_W_B\nB_W_B_W_B_W_B_W_B_W\nW_B_W_B_W_B_W_B_W_B\nB_W_B_W_B_W_B_W_B_W\nW_B_W_B_W_B_W_B_W_B\nB_W_B_W_B_W_B_W_B_W\nW_B_W_B_W_B_W_B_W_B\nB_W_B_W_B_W_B_W_B_W")
8+
((5, 6), "W B W B W B\nB W B W B W\nW B W B W B\nB W B W B W\nW B W B W B"),
9+
((10, 10), "W B W B W B W B W B\nB W B W B W B W B W\nW B W B W B W B W B\nB W B W B W B W B W\nW B W B W B W B W B\nB W B W B W B W B W\nW B W B W B W B W B\nB W B W B W B W B W\nW B W B W B W B W B\nB W B W B W B W B W")
1010
])
1111
def test_board_generation(dimensions: tuple[int, int], expected: str) -> None:
1212
board: Board = Board.initialize_board(*dimensions)

lab02/clobber/types.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ctypedef tuple move

lab02/setup.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from setuptools import setup, Extension
2+
from Cython.Build import cythonize # type: ignore
3+
import os
4+
5+
extensions = [
6+
Extension(
7+
"clobber.board",
8+
[os.path.join("clobber", "board.pyx")],
9+
),
10+
]
11+
12+
setup(
13+
name="ClobberGame",
14+
ext_modules=cythonize(
15+
extensions,
16+
compiler_directives={'language_level': "3"}
17+
),
18+
packages=['clobber'],
19+
zip_safe=False,
20+
)

0 commit comments

Comments
 (0)