4 from data import load_image, load, filepath
7 from kivy.logger import Logger
19 def __init__(self, levelfile, name):
28 # Because of how kivy's coordinate system works,
29 # we reverse the lines so things match up between
30 # the file and the display (top of file == top of display)
31 for line in reversed(levelfile.readlines()):
32 self._data.append(list(line.strip('\n')))
35 """Load the list of tiles for the level"""
36 Logger.info('%s: load tiles' % self._name)
43 for j, line in enumerate(self._data):
45 for i, c in enumerate(line):
46 tile_image = self._get_tile_image((i, j), c)
47 tile_line.append(tile_image)
48 self._tiles.append(tile_line)
50 def _get_tile_image(self, pos, c):
53 image = load_image('tiles/floor.png')
55 image = self._get_wall_tile(pos)
58 image = load_image('tiles/entry.png')
60 self.exit_pos.append(pos)
61 image = load_image('tiles/door.png')
63 if pos not in self._gates:
64 self._gates[pos] = -1 # down
65 image = load_image('tiles/gate_down.png')
67 state = self._gates[pos]
69 image = load_image('tiles/gate_down.png')
72 image = load_image('tiles/floor.png')
75 image = load_image('tiles/gate_dented.png')
78 image = load_image('tiles/gate_bent.png')
81 image = load_image('tiles/gate_up.png')
83 if not pos in self._buttons:
84 image = load_image('tiles/button.png')
85 self._buttons[pos] = 'active'
86 elif self._buttons[pos] == 'active':
87 image = load_image('tiles/button.png')
89 image = load_image('tiles/floor.png')
91 raise RuntimeError('Unknown tile type %s at %s' % (c, pos))
99 for line in self._data:
101 entry_points += line.count(ENTRY)
103 exit_points += line.count(EXIT)
105 buttons += line.count(BUTTON)
107 gates += line.count(GATE)
108 if entry_points == 0:
109 raise RuntimeError('No entry point')
111 raise RuntimeError('Multiple entry points')
113 raise RuntimeError('No exit')
115 raise RuntimeError('The number of buttons and gates differ')
120 def get_single_tile(self, pos):
121 return self._tiles[pos[1]][pos[0]]
123 def get_tile_type(self, pos):
124 return self._data[pos[1]][pos[0]]
126 def set_tile_type(self, pos, new_type):
127 # Setting the type resets any state anyway, so
128 if pos in self._gates:
130 if pos in self._buttons:
131 del self._buttons[pos]
132 self._data[pos[1]][pos[0]] = new_type
133 new_tile = self._get_tile_image(pos, new_type)
134 self._tiles[pos[1]][pos[0]] = new_tile
135 self._changed.append((pos, new_tile))
136 # Also update neighbourhood for wall types, etc.
137 for new_pos in [(pos[0] - 1, pos[1]), (pos[0] + 1, pos[1]),
138 (pos[0] - 1, pos[1] - 1), (pos[0] + 1, pos[1] + 1),
139 (pos[0], pos[1] - 1), (pos[0], pos[1] + 1),
140 (pos[0] - 1, pos[1] + 1), (pos[0] + 1, pos[1] - 1)]:
141 if not self._in_limits(new_pos):
143 # Update display to changed status
144 self._fix_tile(new_pos)
146 def _fix_tile(self, pos):
147 tile = self._data[pos[1]][pos[0]]
148 new_tile = self._get_tile_image(pos, tile)
149 self._tiles[pos[1]][pos[0]] = new_tile
150 self._changed.append((pos, new_tile))
153 return len(self._tiles[0]), len(self._tiles)
155 def at_exit(self, pos):
156 return pos in self.exit_pos
158 def get_level_data(self):
159 return '\n'.join(reversed([''.join(x) for x in self._data]))
161 def _get_wall_tile(self, pos):
162 # Is the neighbour in this direction also a wall?
164 left = right = top = bottom = False
167 elif self._data[y][x - 1] == WALL:
169 if x == len(self._data[0]) - 1:
171 elif self._data[y][x + 1] == WALL:
175 elif self._data[y - 1][x] == WALL:
177 if y == len(self._data) - 1:
179 elif self._data[y + 1][x] == WALL:
181 if top and bottom and left and right:
182 return load_image('tiles/cwall.png')
183 elif bottom and left and right:
184 return load_image('tiles/bottom_wall.png')
185 elif top and left and right:
186 return load_image('tiles/top_wall.png')
187 elif top and bottom and right:
188 return load_image('tiles/left_wall.png')
189 elif top and bottom and left:
190 return load_image('tiles/right_wall.png')
192 return load_image('tiles/vert_wall.png')
194 return load_image('tiles/horiz_wall.png')
196 return load_image('tiles/corner_lt.png')
197 elif left and bottom:
198 return load_image('tiles/corner_lb.png')
200 return load_image('tiles/corner_rt.png')
201 elif right and bottom:
202 return load_image('tiles/corner_rb.png')
204 return load_image('tiles/end_top.png')
206 return load_image('tiles/end_bottom.png')
208 return load_image('tiles/end_right.png')
210 return load_image('tiles/end_left.png')
211 return load_image('tiles/pillar.png')
213 def _in_limits(self, pos):
219 self._data[pos[1]][pos[0]]
224 def blocked(self, pos):
230 tile = self._data[pos[1]][pos[0]]
233 if tile == WALL or tile == ENTRY:
236 if self._gates[pos] > 0:
240 def calc_dist(self, pos1, pos2):
241 return abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1])
243 def is_gate(self, pos):
244 if not self._in_limits(pos):
246 return self._data[pos[1]][pos[0]] == GATE
248 def is_button(self, pos):
249 if not self._in_limits(pos):
251 return self._data[pos[1]][pos[0]] == BUTTON
253 def is_wall(self, pos):
254 if not self._in_limits(pos):
256 return self._data[pos[1]][pos[0]] == WALL
258 def trigger_button(self, pos):
259 if not self.is_button(pos):
261 if not self._buttons[pos] == 'active':
263 # Find the closest gate down gate and trigger it
266 for cand in self._gates:
267 dist = self.calc_dist(pos, cand)
272 self._buttons[pos] = 'pressed'
273 self._gates[gate_pos] = 3 # Raise gate
275 self._fix_tile(gate_pos)
277 def damage_gate(self, pos):
278 if not self.is_gate(pos):
280 if self._gates[pos] == -1 or self._gates[pos] == 0:
282 self._gates[pos] = self._gates[pos] - 1
285 def get_changed_tiles(self):
286 ret = self._changed[:]
291 class LevelList(object):
293 LEVELS = 'level_list'
297 self._level_names = []
299 level_list = load(self.LEVELS)
300 for line in level_list:
302 if os.path.exists(filepath(line)):
303 level_file = load(line)
304 level = Level(level_file, line)
308 self._levels.append(level)
309 self._level_names.append(line)
310 except RuntimeError as err:
312 'Invalid level %s in level_list: %s' % (line, err))
315 'Level list includes non-existant level %s' % line)
319 def get_current_level(self):
320 if self._cur_level < len(self._levels):
321 return self._levels[self._cur_level]
325 def get_errors(self):
328 def get_level_names(self):
329 return self._level_names
331 def set_level_to(self, level_name):
332 if level_name in self._level_names:
333 self._cur_level = self._level_names.index(level_name)
335 def advance_to_next_level(self):
337 return self.get_current_level()