4 from data import load_image, load, filepath
16 def __init__(self, levelfile):
24 # Because of how kivy's coordinate system works,
25 # we reverse the lines so things match up between
26 # the file and the display (top of file == top of display)
27 for line in reversed(levelfile.readlines()):
28 self._data.append(list(line.strip('\n')))
31 """Load the list of tiles for the level"""
38 for j, line in enumerate(self._data):
40 for i, c in enumerate(line):
41 tile_image = self._get_tile_image((i, j), c)
42 tile_line.append(tile_image)
43 self._tiles.append(tile_line)
45 def _get_tile_image(self, pos, c):
48 image = load_image('tiles/floor.png')
50 image = self._get_wall_tile(pos)
53 image = load_image('tiles/entry.png')
55 self.exit_pos.append(pos)
56 image = load_image('tiles/door.png')
58 if pos not in self._gates:
59 self._gates[pos] = -1 # down
60 image = load_image('tiles/gate_down.png')
62 state = self._gates[pos]
64 image = load_image('tiles/gate_down.png')
67 image = load_image('tiles/floor.png')
70 image = load_image('tiles/gate_dented.png')
73 image = load_image('tiles/gate_bent.png')
76 image = load_image('tiles/gate_up.png')
78 if not pos in self._buttons:
79 image = load_image('tiles/button.png')
80 self._buttons[pos] = 'active'
81 elif self._buttons[pos] == 'active':
82 image = load_image('tiles/button.png')
84 image = load_image('tiles/floor.png')
86 raise RuntimeError('Unknown tile type %s at %s' % (c, pos))
94 for line in self._data:
96 entry_points += line.count(ENTRY)
98 exit_points += line.count(EXIT)
100 buttons += line.count(BUTTON)
102 gates += line.count(GATE)
103 if entry_points == 0:
104 raise RuntimeError('No entry point')
106 raise RuntimeError('Multiple entry points')
108 raise RuntimeError('No exit')
110 raise RuntimeError('The number of buttons and gates differ')
115 def get_single_tile(self, pos):
116 return self._tiles[pos[1]][pos[0]]
118 def get_tile_type(self, pos):
119 return self._data[pos[1]][pos[0]]
121 def set_tile_type(self, pos, new_type):
122 # Setting the type resets any state anyway, so
123 if pos in self._gates:
125 if pos in self._buttons:
126 del self._buttons[pos]
127 self._data[pos[1]][pos[0]] = new_type
128 new_tile = self._get_tile_image(pos, new_type)
129 self._tiles[pos[1]][pos[0]] = new_tile
130 self._changed.append((pos, new_tile))
131 # Also update neighbourhood for wall types, etc.
132 for new_pos in [(pos[0] - 1, pos[1]), (pos[0] + 1, pos[1]),
133 (pos[0] - 1, pos[1] - 1), (pos[0] + 1, pos[1] + 1),
134 (pos[0], pos[1] - 1), (pos[0], pos[1] + 1),
135 (pos[0] - 1, pos[1] + 1), (pos[0] + 1, pos[1] - 1)]:
136 if not self._in_limits(new_pos):
138 # Update display to changed status
139 self._fix_tile(new_pos)
141 def _fix_tile(self, pos):
142 tile = self._data[pos[1]][pos[0]]
143 new_tile = self._get_tile_image(pos, tile)
144 self._tiles[pos[1]][pos[0]] = new_tile
145 self._changed.append((pos, new_tile))
148 return len(self._tiles[0]), len(self._tiles)
150 def at_exit(self, pos):
151 return pos in self.exit_pos
153 def get_level_data(self):
154 return '\n'.join(reversed([''.join(x) for x in self._data]))
156 def _get_wall_tile(self, pos):
157 # Is the neighbour in this direction also a wall?
159 left = right = top = bottom = False
162 elif self._data[y][x - 1] == WALL:
164 if x == len(self._data[0]) - 1:
166 elif self._data[y][x + 1] == WALL:
170 elif self._data[y - 1][x] == WALL:
172 if y == len(self._data) - 1:
174 elif self._data[y + 1][x] == WALL:
176 if top and bottom and left and right:
177 return load_image('tiles/cwall.png')
178 elif bottom and left and right:
179 return load_image('tiles/bottom_wall.png')
180 elif top and left and right:
181 return load_image('tiles/top_wall.png')
182 elif top and bottom and right:
183 return load_image('tiles/left_wall.png')
184 elif top and bottom and left:
185 return load_image('tiles/right_wall.png')
187 return load_image('tiles/vert_wall.png')
189 return load_image('tiles/horiz_wall.png')
191 return load_image('tiles/corner_lt.png')
192 elif left and bottom:
193 return load_image('tiles/corner_lb.png')
195 return load_image('tiles/corner_rt.png')
196 elif right and bottom:
197 return load_image('tiles/corner_rb.png')
199 return load_image('tiles/end_top.png')
201 return load_image('tiles/end_bottom.png')
203 return load_image('tiles/end_right.png')
205 return load_image('tiles/end_left.png')
206 return load_image('tiles/pillar.png')
208 def _in_limits(self, pos):
214 self._data[pos[1]][pos[0]]
219 def blocked(self, pos):
225 tile = self._data[pos[1]][pos[0]]
228 if tile == WALL or tile == ENTRY:
231 if self._gates[pos] > 0:
235 def calc_dist(self, pos1, pos2):
236 return abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1])
238 def is_gate(self, pos):
239 if not self._in_limits(pos):
241 return self._data[pos[1]][pos[0]] == GATE
243 def is_button(self, pos):
244 if not self._in_limits(pos):
246 return self._data[pos[1]][pos[0]] == BUTTON
248 def is_wall(self, pos):
249 if not self._in_limits(pos):
251 return self._data[pos[1]][pos[0]] == WALL
253 def trigger_button(self, pos):
254 if not self.is_button(pos):
256 if not self._buttons[pos] == 'active':
258 # Find the closest gate down gate and trigger it
261 for cand in self._gates:
262 dist = self.calc_dist(pos, cand)
267 self._buttons[pos] = 'pressed'
268 self._gates[gate_pos] = 3 # Raise gate
270 self._fix_tile(gate_pos)
272 def damage_gate(self, pos):
273 if not self.is_gate(pos):
275 if self._gates[pos] == -1 or self._gates[pos] == 0:
277 self._gates[pos] = self._gates[pos] - 1
280 def get_changed_tiles(self):
281 ret = self._changed[:]
286 class LevelList(object):
288 LEVELS = 'level_list'
293 level_list = load(self.LEVELS)
294 for line in level_list:
296 if os.path.exists(filepath(line)):
297 level_file = load(line)
298 level = Level(level_file)
302 self.levels.append(level)
303 except RuntimeError as err:
305 'Invalid level %s in level_list: %s' % (line, err))
308 'Level list includes non-existant level %s' % line)
312 def get_current_level(self):
313 if self._cur_level < len(self.levels):
314 return self.levels[self._cur_level]
318 def get_errors(self):
321 def advance_to_next_level(self):
323 return self.get_current_level()