Skip to content

Commit 35c83db

Browse files
committed
Initial commit
0 parents  commit 35c83db

17 files changed

+3415
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__

.python-version

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.7.0

herbert/__init__.py

Whitespace-only changes.

herbert/level.py

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import re
2+
3+
from .util import pluralize
4+
5+
6+
EMPTY = '.'
7+
WALL = '*'
8+
GRAY_BUTTON = 'g'
9+
WHITE_BUTTON = 'w'
10+
ROBOT_DIRECTIONS = ('u', 'r', 'd', 'l')
11+
ALL_SYMBOLS = (EMPTY, WALL, GRAY_BUTTON, WHITE_BUTTON) + ROBOT_DIRECTIONS
12+
13+
14+
class Level:
15+
LINE_PATTERN = re.compile(r'[.*gwurdl]+\n')
16+
NUMBER_PATTERN = re.compile(r'\d+')
17+
18+
@classmethod
19+
def fromfile(cls, file, *, nrows=25, ncols=25):
20+
if nrows < 1:
21+
raise ValueError('the number of rows must be greater than or equal to 1: %s' % nrows)
22+
23+
if ncols < 1:
24+
raise ValueError('the number of columns must be greater than or equal to 1: %s' % ncols)
25+
26+
field = ''
27+
28+
for i in range(nrows):
29+
line = file.readline(ncols + 1)
30+
match = cls.LINE_PATTERN.fullmatch(line)
31+
32+
if match and match.end() == ncols + 1:
33+
field += line
34+
else:
35+
if match is None:
36+
if line == '':
37+
raise ValueError('expected %d %s but got %d' % (nrows, pluralize(nrows, 'line', 'lines'), i))
38+
elif line == '\n':
39+
raise ValueError('line %d is an empty line' % (i + 1))
40+
else:
41+
if re.fullmatch('[.*gwurdl]+', line):
42+
raise ValueError('line %d is not %d %s long' % (i + 1, ncols, pluralize(ncols, 'character', 'characters')))
43+
else:
44+
raise ValueError('illegal character found at line %d' % (i + 1))
45+
else:
46+
raise ValueError('line %d is not %d %s long' % (i + 1, ncols, pluralize(ncols, 'character', 'characters')))
47+
48+
field = field[:-1]
49+
50+
line = file.readline(5)
51+
if line.endswith('\n'):
52+
line = line[:-1]
53+
54+
match = cls.NUMBER_PATTERN.fullmatch(line)
55+
error = ValueError('expected a positive integer in the range [1, 1000) at line %d: %s' % (nrows + 1, line))
56+
57+
if match:
58+
max_bytes = int(line)
59+
if max_bytes < 1 or max_bytes >= 1000:
60+
raise error
61+
else:
62+
raise error
63+
64+
return cls(field, max_bytes, nrows, ncols)
65+
66+
def __init__(self, field, max_bytes, nrows, ncols):
67+
self.max_bytes = max_bytes
68+
self.nrows = nrows
69+
self.ncols = ncols
70+
self._parse(field)
71+
72+
def _parse(self, field):
73+
# Step 1: Convert the field to a grid
74+
grid = []
75+
76+
lines = field.splitlines()
77+
assert len(lines) == self.nrows
78+
79+
for line in lines:
80+
assert len(line) == self.ncols
81+
grid.append(list(line))
82+
83+
# Step 2: Get the robot, buttons and walls
84+
robot = None
85+
gray_buttons = []
86+
white_buttons = []
87+
walls = []
88+
inaccessible_spots = set()
89+
hseen = set()
90+
vseen = set()
91+
92+
for r in range(self.nrows):
93+
for c in range(self.ncols):
94+
ch = grid[r][c]
95+
96+
if ch in ROBOT_DIRECTIONS:
97+
if robot is None:
98+
robot = (r, c, ch)
99+
else:
100+
raise ValueError('too many robots, found another one at (%d, %d)' % (r + 1, c + 1))
101+
elif ch == GRAY_BUTTON:
102+
gray_buttons.append((r, c))
103+
elif ch == WHITE_BUTTON:
104+
white_buttons.append((r, c))
105+
elif ch == WALL:
106+
proper_wall = False
107+
108+
if (r, c) in hseen:
109+
proper_wall = True
110+
else:
111+
extent = _extend_horizontally(grid, hseen, r, c, self.nrows, self.ncols)
112+
if extent > 0:
113+
proper_wall = True
114+
wall = HWall(r, c, extent)
115+
walls.append(wall)
116+
inaccessible_spots.update(wall)
117+
118+
if (r, c) in vseen:
119+
proper_wall = True
120+
else:
121+
extent = _extend_vertically(grid, vseen, r, c, self.nrows, self.ncols)
122+
if extent > 0:
123+
proper_wall = True
124+
wall = VWall(r, c, extent)
125+
walls.append(wall)
126+
inaccessible_spots.update(wall)
127+
128+
if not proper_wall:
129+
raise ValueError('improper wall at (%d, %d)' % (r + 1, c + 1))
130+
else:
131+
assert ch == EMPTY
132+
133+
if robot is None:
134+
raise ValueError('no robot found')
135+
136+
self.grid = grid
137+
self.robot = robot
138+
self.gray_buttons = gray_buttons
139+
self.white_buttons = white_buttons
140+
self.walls = walls
141+
self.inaccessible_spots = inaccessible_spots
142+
143+
144+
def _extend_horizontally(grid, hseen, row, col, nrows, ncols):
145+
len = 0
146+
hseen.add((row, col))
147+
148+
while col+1 < ncols and grid[row][col+1] == WALL:
149+
col += 1
150+
len += 1
151+
hseen.add((row, col))
152+
153+
return len
154+
155+
156+
def _extend_vertically(grid, vseen, row, col, nrows, ncols):
157+
len = 0
158+
vseen.add((row, col))
159+
160+
while row+1 < nrows and grid[row+1][col] == WALL:
161+
row += 1
162+
len += 1
163+
vseen.add((row, col))
164+
165+
return len
166+
167+
168+
class Wall:
169+
def __init__(self, row, col, extent):
170+
self.row = row
171+
self.col = col
172+
self.extent = extent
173+
174+
175+
class HWall(Wall):
176+
def __iter__(self):
177+
for i in range(self.extent + 1):
178+
yield (self.row, self.col + i)
179+
180+
181+
class VWall(Wall):
182+
def __iter__(self):
183+
for i in range(self.extent + 1):
184+
yield (self.row + i, self.col)

herbert/util.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def pluralize(n, singular, plural):
2+
assert n >= 0
3+
return singular if n == 1 else plural

0 commit comments

Comments
 (0)