Skip to content

Commit d5c5f45

Browse files
Added the core board loading functionality.
1 parent 0c066ff commit d5c5f45

File tree

4 files changed

+153
-23
lines changed

4 files changed

+153
-23
lines changed

pacai/boards/medium-classic.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
{
2+
"class": "pacai.pacman.board.Board"
3+
}
4+
---
15
%%%%%%%%%%%%%%%%%%%%
26
%o...%........%....%
37
%.%%.%.%%%%%%.%.%%.%

pacai/core/board.py

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@
1010

1111
SEPARATOR_PATTERN: re.Pattern = re.compile(r'^\s*-{3,}\s*$')
1212

13-
DEFAULT_BOARD_CLASS = 'pacai.core.board.Board'
13+
DEFAULT_BOARD_CLASS: str = 'pacai.core.board.Board'
14+
15+
DEFAULT_MARKER_EMPTY: str = ' '
16+
DEFAULT_MARKER_WALL: str = '%'
17+
18+
class Marker(str):
19+
"""
20+
A marker represents something that can appear on a board.
21+
These are similar to a game piece of token in a traditional board game (like the top hat or dog in Monolopy).
22+
"""
23+
24+
pass
1425

1526
class Board:
1627
"""
@@ -26,9 +37,73 @@ class Board:
2637
The specific board class (usually specified by the board options) should know how to interpret the text-based board.
2738
"""
2839

29-
def __init__(self, marker_wall = '%', **kwargs) -> None:
30-
# TEST
31-
pass
40+
def __init__(self,
41+
board_text: str,
42+
marker_empty: str = DEFAULT_MARKER_EMPTY,
43+
marker_wall: str = DEFAULT_MARKER_WALL,
44+
extra_markers: list[str] = [],
45+
strip: bool = True,
46+
**kwargs) -> None:
47+
self._markers: dict[str, Marker] = {
48+
marker_empty: Marker(marker_empty),
49+
marker_wall: Marker(marker_wall),
50+
}
51+
""" Map the text for a marker to the actual marker. """
52+
53+
for marker in extra_markers:
54+
self._markers[marker] = Marker(marker)
55+
56+
height, width, locations = self._process_text(board_text, strip = strip)
57+
58+
self.height: int = height
59+
""" The height (number of rows, "y") of the board. """
60+
61+
self.width: int = width
62+
""" The width (number of columns, "x") of the board. """
63+
64+
self._locations: list[Marker] = locations
65+
""" The full content of the board as a single list. """
66+
67+
def _process_text(self, board_text: str, strip: bool = True) -> tuple[int, int, list[Marker]]:
68+
"""
69+
Create a board from a string.
70+
"""
71+
72+
if (strip):
73+
board_text = board_text.strip()
74+
75+
if (len(board_text) == 0):
76+
raise ValueError('A board cannot be empty.')
77+
78+
lines = board_text.split("\n")
79+
80+
height: int = len(lines)
81+
width: int = -1
82+
locations: list[Marker] = []
83+
84+
for row in range(len(lines)):
85+
line = lines[row]
86+
if (strip):
87+
line = line.strip()
88+
89+
if (width == -1):
90+
width = len(line)
91+
92+
if (width != len(line)):
93+
raise ValueError(f"Unexpected width ({len(line)}) for row at index {row}. Expected {width}.")
94+
95+
for col in range(len(line)):
96+
marker = self._markers.get(line[col], None)
97+
if (marker is None):
98+
raise ValueError(f"Unknown marker '{line[col]}' found at location ({row}, {col}).")
99+
100+
locations.append(marker)
101+
102+
if (width == 0):
103+
raise ValueError("A board must have at least one column.")
104+
105+
return height, width, locations
106+
32107

33108
def load_path(path: str) -> Board:
34109
""" Load a board from a file. """
@@ -62,4 +137,4 @@ def load_string(text: str) -> Board:
62137
options = pacai.util.json.loads(options_text)
63138

64139
board_class = options.get('class', DEFAULT_BOARD_CLASS)
65-
return pacai.util.reflection.new_object(board_class, **options)
140+
return pacai.util.reflection.new_object(board_class, board_text, **options)

pacai/core/test_board.py

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,21 @@ def test_load_default_boards(self):
1313
def test_load_test_boards(self):
1414
# [(board, expected error substring), ...]
1515
test_cases = [
16-
('', None),
1716
(TEST_BOARD_NO_SEP, None),
1817
(TEST_BOARD_OPTIONS, None),
1918
(TEST_BOARD_SEP_EMPTY_OPTIONS, None),
20-
(TEST_BOARD_EMPTY_BOARD, None),
21-
(TEST_BOARD_FULL_EMPTY, None),
22-
(TEST_BOARD_FULL_EMPTY_SEP, None),
19+
20+
('', 'A board cannot be empty.'),
21+
(TEST_BOARD_ERROR_EMPTY_BOARD, 'A board cannot be empty.'),
22+
(TEST_BOARD_ERROR_FULL_EMPTY, 'A board cannot be empty.'),
23+
(TEST_BOARD_ERROR_FULL_EMPTY_SEP, 'A board cannot be empty.'),
2324

2425
(TEST_BOARD_ERROR_BAD_CLASS, "Cannot find class 'ZZZ' in module 'pacai.core.board'."),
26+
27+
(TEST_BOARD_ERROR_WIDTH_ZERO, 'A board must have at least one column.'),
28+
(TEST_BOARD_ERROR_INCONSISTENT_WIDTH, 'Unexpected width'),
29+
30+
(TEST_BOARD_ERROR_UNKNOWN_MARKER, 'Unknown marker'),
2531
]
2632

2733
for i in range(len(test_cases)):
@@ -40,42 +46,60 @@ def test_load_test_boards(self):
4046
self.fail(f"Did not get expected error: '{error_substring}'.")
4147

4248
TEST_BOARD_NO_SEP = '''
43-
%%%%%
44-
%. P%
45-
%%%%%
49+
%%%
50+
% %
51+
%%%
4652
'''
4753

4854
TEST_BOARD_OPTIONS = '''
4955
{"marker_wall": "#"}
5056
---
51-
#####
52-
#. P#
53-
#####
57+
###
58+
# #
59+
###
5460
'''
5561

5662
TEST_BOARD_SEP_EMPTY_OPTIONS = '''
5763
---
58-
%%%%%
59-
%. P%
60-
%%%%%
64+
%%%
65+
% %
66+
%%%
6167
'''
6268

63-
TEST_BOARD_EMPTY_BOARD = '''
69+
TEST_BOARD_ERROR_EMPTY_BOARD = '''
6470
{"marker_wall": "#"}
6571
---
6672
'''
6773

68-
TEST_BOARD_FULL_EMPTY = '''
74+
TEST_BOARD_ERROR_FULL_EMPTY = '''
6975
'''
7076

71-
TEST_BOARD_FULL_EMPTY_SEP = '''
77+
TEST_BOARD_ERROR_FULL_EMPTY_SEP = '''
7278
---
7379
'''
7480

7581
TEST_BOARD_ERROR_BAD_CLASS = '''
7682
{"class": "pacai.core.board.ZZZ"}
7783
---
84+
%%%
85+
% %
86+
%%%
87+
'''
88+
89+
TEST_BOARD_ERROR_WIDTH_ZERO = '''
90+
{"strip": false}
91+
---
92+
93+
'''
94+
95+
TEST_BOARD_ERROR_INCONSISTENT_WIDTH = '''
96+
%%%
97+
% %%
7898
%%%%%
79-
%. P%
80-
%%%%%
99+
'''
100+
101+
TEST_BOARD_ERROR_UNKNOWN_MARKER = '''
102+
%%%
103+
%?%
104+
%%%
81105
'''

pacai/pacman/board.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import pacai.core.board
2+
3+
MARKER_PELLET: pacai.core.board.Marker = pacai.core.board.Marker('.')
4+
MARKER_CAPSULE: pacai.core.board.Marker = pacai.core.board.Marker('o')
5+
MARKER_GHOST: pacai.core.board.Marker = pacai.core.board.Marker('G')
6+
MARKER_PACMAN: pacai.core.board.Marker = pacai.core.board.Marker('P')
7+
8+
class Board(pacai.core.board.Board):
9+
"""
10+
A board for Pacman.
11+
12+
In addition to walls, Pacman boards also have:
13+
pellets ('.'),
14+
capsules ('o'),
15+
ghosts ('G'),
16+
and Pacman ('P').
17+
"""
18+
19+
def __init__(self, board_text: str, extra_markers: list[str] = [], **kwargs) -> None:
20+
extra_markers += [
21+
MARKER_PELLET,
22+
MARKER_CAPSULE,
23+
MARKER_GHOST,
24+
MARKER_PACMAN,
25+
]
26+
27+
super().__init__(board_text, extra_markers = extra_markers, **kwargs)

0 commit comments

Comments
 (0)