Skip to content

Commit a83b082

Browse files
committed
Add and test a runtime environment
1 parent 35c83db commit a83b082

File tree

2 files changed

+249
-0
lines changed

2 files changed

+249
-0
lines changed

herbert/level.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,19 @@ def __init__(self, field, max_bytes, nrows, ncols):
6969
self.ncols = ncols
7070
self._parse(field)
7171

72+
def __call__(self):
73+
white_buttons = {}
74+
for r, c in self.white_buttons:
75+
white_buttons[(r, c)] = WhiteButton(r, c)
76+
77+
gray_buttons = {}
78+
for r, c in self.gray_buttons:
79+
gray_buttons[(r, c)] = GrayButton(r, c, white_buttons.values())
80+
81+
robot = Robot(*self.robot)
82+
83+
return RuntimeEnvironment(self, robot, gray_buttons, white_buttons)
84+
7285
def _parse(self, field):
7386
# Step 1: Convert the field to a grid
7487
grid = []
@@ -182,3 +195,106 @@ class VWall(Wall):
182195
def __iter__(self):
183196
for i in range(self.extent + 1):
184197
yield (self.row + i, self.col)
198+
199+
200+
class Robot:
201+
MOVEMENT_DELTAS = (
202+
(-1, 0), # up
203+
(0, 1), # right
204+
(1, 0), # down
205+
(0, -1) # left
206+
)
207+
208+
def __init__(self, row, col, direction):
209+
self.row = row
210+
self.col = col
211+
self.heading = ROBOT_DIRECTIONS.index(direction)
212+
self.trail = [(row, col)]
213+
214+
def isup(self):
215+
return self.heading == 0
216+
217+
def isright(self):
218+
return self.heading == 1
219+
220+
def isdown(self):
221+
return self.heading == 2
222+
223+
def isleft(self):
224+
return self.heading == 3
225+
226+
def turn_left(self):
227+
self.heading = (self.heading - 1) % 4
228+
229+
def turn_right(self):
230+
self.heading = (self.heading + 1) % 4
231+
232+
def position_after_move(self):
233+
dr, dc = self.MOVEMENT_DELTAS[self.heading]
234+
235+
return (self.row + dr, self.col + dc)
236+
237+
def move_to(self, row, col):
238+
self.row = row
239+
self.col = col
240+
self.trail.append((row, col))
241+
242+
243+
class GrayButton:
244+
def __init__(self, row, col, white_buttons):
245+
self.row = row
246+
self.col = col
247+
self.white_buttons = white_buttons
248+
249+
def press(self):
250+
for white_button in self.white_buttons:
251+
white_button.unpress()
252+
253+
254+
class WhiteButton:
255+
def __init__(self, row, col):
256+
self.row = row
257+
self.col = col
258+
self.pressed = False
259+
260+
def press(self):
261+
self.pressed = True
262+
263+
def unpress(self):
264+
self.pressed = False
265+
266+
267+
class RuntimeEnvironment:
268+
def __init__(self, level, robot, gray_buttons, white_buttons):
269+
self.level = level
270+
self.robot = robot
271+
self.gray_buttons = gray_buttons
272+
self.white_buttons = white_buttons
273+
self.npressed = 0 # the number of white buttons pressed
274+
self.max_npressed = 0 # the maximum number of white buttons pressed
275+
self.completed = False # True iff all the white buttons have been pressed
276+
277+
def step(self, command):
278+
if command == 's':
279+
row, col = pos = self.robot.position_after_move()
280+
281+
if 0 <= row < self.level.nrows and 0 <= col < self.level.ncols and pos not in self.level.inaccessible_spots:
282+
self.robot.move_to(row, col)
283+
284+
if pos in self.gray_buttons:
285+
self.gray_buttons[pos].press()
286+
self.npressed = 0
287+
elif pos in self.white_buttons:
288+
self.white_buttons[pos].press()
289+
self.npressed += 1
290+
if self.npressed > self.max_npressed:
291+
self.max_npressed = self.npressed
292+
293+
if not self.completed and self.white_buttons and self.npressed == len(self.white_buttons):
294+
self.completed = True
295+
elif command == 'l':
296+
self.robot.turn_left()
297+
elif command == 'r':
298+
self.robot.turn_right()
299+
else:
300+
raise ValueError('not a command: %s' % command)

tests/test_runtime_environment.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import io
2+
import unittest
3+
4+
from herbert.level import Level
5+
6+
7+
class StepTestCase(unittest.TestCase):
8+
def setUp(self):
9+
file = self.file = io.StringIO()
10+
file.write('..........\n')
11+
file.write('.***......\n')
12+
file.write('.*r.w.g.w.\n')
13+
file.write('.***......\n')
14+
file.write('..........\n')
15+
file.write('11')
16+
file.seek(0)
17+
18+
level = Level.fromfile(file, nrows=5, ncols=10)
19+
20+
# N.B. The sequence of commands "sslsrssssrs"
21+
# can be used to complete the level.
22+
self.re = level()
23+
24+
def tearDown(self):
25+
self.file.close()
26+
27+
def test_lss(self):
28+
self.re.step('l')
29+
self.re.step('s')
30+
self.re.step('s')
31+
32+
self.assertEqual(self.re.robot.row, 2)
33+
self.assertEqual(self.re.robot.col, 2)
34+
self.assertTrue(self.re.robot.isup())
35+
self.assertEqual(self.re.npressed, 0)
36+
self.assertFalse(self.re.white_buttons[(2, 4)].pressed)
37+
self.assertFalse(self.re.white_buttons[(2, 8)].pressed)
38+
self.assertEqual(self.re.max_npressed, 0)
39+
self.assertFalse(self.re.completed)
40+
41+
def test_ss(self):
42+
self.re.step('s')
43+
self.re.step('s')
44+
45+
self.assertEqual(self.re.robot.row, 2)
46+
self.assertEqual(self.re.robot.col, 4)
47+
self.assertTrue(self.re.robot.isright())
48+
self.assertEqual(self.re.npressed, 1)
49+
self.assertTrue(self.re.white_buttons[(2, 4)].pressed)
50+
self.assertFalse(self.re.white_buttons[(2, 8)].pressed)
51+
self.assertEqual(self.re.max_npressed, 1)
52+
self.assertFalse(self.re.completed)
53+
54+
def test_sslsss(self):
55+
self.re.step('s')
56+
self.re.step('s')
57+
self.re.step('l')
58+
self.re.step('s')
59+
self.re.step('s')
60+
self.re.step('s')
61+
62+
self.assertEqual(self.re.robot.row, 0)
63+
self.assertEqual(self.re.robot.col, 4)
64+
self.assertTrue(self.re.robot.isup())
65+
self.assertEqual(self.re.npressed, 1)
66+
self.assertTrue(self.re.white_buttons[(2, 4)].pressed)
67+
self.assertFalse(self.re.white_buttons[(2, 8)].pressed)
68+
self.assertEqual(self.re.max_npressed, 1)
69+
self.assertFalse(self.re.completed)
70+
71+
def test_ssssss(self):
72+
self.re.step('s')
73+
self.re.step('s')
74+
self.re.step('s')
75+
self.re.step('s')
76+
self.re.step('s')
77+
self.re.step('s')
78+
79+
self.assertEqual(self.re.robot.row, 2)
80+
self.assertEqual(self.re.robot.col, 8)
81+
self.assertTrue(self.re.robot.isright())
82+
self.assertEqual(self.re.npressed, 1)
83+
self.assertFalse(self.re.white_buttons[(2, 4)].pressed)
84+
self.assertTrue(self.re.white_buttons[(2, 8)].pressed)
85+
self.assertEqual(self.re.max_npressed, 1)
86+
self.assertFalse(self.re.completed)
87+
88+
def test_sslsrssssrs(self):
89+
self.re.step('s')
90+
self.re.step('s')
91+
self.re.step('l')
92+
self.re.step('s')
93+
self.re.step('r')
94+
self.re.step('s')
95+
self.re.step('s')
96+
self.re.step('s')
97+
self.re.step('s')
98+
self.re.step('r')
99+
self.re.step('s')
100+
101+
self.assertEqual(self.re.robot.row, 2)
102+
self.assertEqual(self.re.robot.col, 8)
103+
self.assertTrue(self.re.robot.isdown())
104+
self.assertEqual(self.re.npressed, 2)
105+
self.assertTrue(self.re.white_buttons[(2, 4)].pressed)
106+
self.assertTrue(self.re.white_buttons[(2, 8)].pressed)
107+
self.assertEqual(self.re.max_npressed, 2)
108+
self.assertTrue(self.re.completed)
109+
110+
def test_sslsrssssrsrss(self):
111+
self.re.step('s')
112+
self.re.step('s')
113+
self.re.step('l')
114+
self.re.step('s')
115+
self.re.step('r')
116+
self.re.step('s')
117+
self.re.step('s')
118+
self.re.step('s')
119+
self.re.step('s')
120+
self.re.step('r')
121+
self.re.step('s')
122+
self.re.step('r')
123+
self.re.step('s')
124+
self.re.step('s')
125+
126+
self.assertEqual(self.re.robot.row, 2)
127+
self.assertEqual(self.re.robot.col, 6)
128+
self.assertTrue(self.re.robot.isleft())
129+
self.assertEqual(self.re.npressed, 0)
130+
self.assertFalse(self.re.white_buttons[(2, 4)].pressed)
131+
self.assertFalse(self.re.white_buttons[(2, 8)].pressed)
132+
self.assertEqual(self.re.max_npressed, 2)
133+
self.assertTrue(self.re.completed)

0 commit comments

Comments
 (0)