X-Git-Url: https://git.ctpug.org.za/?p=erdslangetjie.git;a=blobdiff_plain;f=erdslangetjie%2Flevel.py;h=9ebb6f4912525a3f0f34c7589badbb80cf3525af;hp=e1c534d71f064b75b6917e442b791504e3a3892b;hb=HEAD;hpb=7d7d155e3111824731f6e342be62e544b46df25d diff --git a/erdslangetjie/level.py b/erdslangetjie/level.py index e1c534d..9ebb6f4 100644 --- a/erdslangetjie/level.py +++ b/erdslangetjie/level.py @@ -3,6 +3,9 @@ import os from data import load_image, load, filepath + +from kivy.logger import Logger + WALL = '.' FLOOR = ' ' ENTRY = 'E' @@ -13,7 +16,7 @@ BUTTON = 'B' class Level(object): - def __init__(self, levelfile): + def __init__(self, levelfile, name): self._data = [] self.exit_pos = [] self.enter_pos = None @@ -21,6 +24,7 @@ class Level(object): self._changed = [] self._gates = {} self._buttons = {} + self._name = name # Because of how kivy's coordinate system works, # we reverse the lines so things match up between # the file and the display (top of file == top of display) @@ -29,6 +33,7 @@ class Level(object): def load_tiles(self): """Load the list of tiles for the level""" + Logger.info('%s: load tiles' % self._name) self._tiles = [] self._gates = {} self._buttons = {} @@ -38,40 +43,124 @@ class Level(object): for j, line in enumerate(self._data): tile_line = [] for i, c in enumerate(line): - if c == FLOOR: - tile_line.append(load_image('tiles/floor.png')) - elif c == WALL: - tile_line.append(self._get_wall_tile(i, j)) - elif c == ENTRY: - if self.enter_pos: - raise RuntimeError('Multiple entry points') - self.enter_pos = (i, j) - tile_line.append(load_image('tiles/entry.png')) - elif c == EXIT: - self.exit_pos.append((i, j)) - tile_line.append(load_image('tiles/door.png')) - elif c == GATE: - tile_line.append('tiles/gate_down.png') - self._gates[(i, j)] = -1 # down - elif c == BUTTON: - tile_line.append('tiles/button.png') - self._buttons[(i, j)] = 'active' + tile_image = self._get_tile_image((i, j), c) + tile_line.append(tile_image) self._tiles.append(tile_line) + def _get_tile_image(self, pos, c): + image = None + if c == FLOOR: + image = load_image('tiles/floor.png') + elif c == WALL: + image = self._get_wall_tile(pos) + elif c == ENTRY: + self.enter_pos = pos + image = load_image('tiles/entry.png') + elif c == EXIT: + self.exit_pos.append(pos) + image = load_image('tiles/door.png') + elif c == GATE: + if pos not in self._gates: + self._gates[pos] = -1 # down + image = load_image('tiles/gate_down.png') + else: + state = self._gates[pos] + if state == -1: + image = load_image('tiles/gate_down.png') + elif state == 0: + # destroyed + image = load_image('tiles/floor.png') + elif state == 1: + # badly damaged + image = load_image('tiles/gate_dented.png') + elif state == 2: + # lightly damaged + image = load_image('tiles/gate_bent.png') + else: + # gate up + image = load_image('tiles/gate_up.png') + elif c == BUTTON: + if not pos in self._buttons: + image = load_image('tiles/button.png') + self._buttons[pos] = 'active' + elif self._buttons[pos] == 'active': + image = load_image('tiles/button.png') + else: + image = load_image('tiles/floor.png') + if image is None: + raise RuntimeError('Unknown tile type %s at %s' % (c, pos)) + return image + + def validate(self): + entry_points = 0 + exit_points = 0 + gates = 0 + buttons = 0 + for line in self._data: + if ENTRY in line: + entry_points += line.count(ENTRY) + if EXIT in line: + exit_points += line.count(EXIT) + if BUTTON in line: + buttons += line.count(BUTTON) + if GATE in line: + gates += line.count(GATE) + if entry_points == 0: + raise RuntimeError('No entry point') + if entry_points > 1: + raise RuntimeError('Multiple entry points') + if exit_points == 0: + raise RuntimeError('No exit') + if gates != buttons: + raise RuntimeError('The number of buttons and gates differ') + def get_tiles(self): return self._tiles def get_single_tile(self, pos): return self._tiles[pos[1]][pos[0]] + def get_tile_type(self, pos): + return self._data[pos[1]][pos[0]] + + def set_tile_type(self, pos, new_type): + # Setting the type resets any state anyway, so + if pos in self._gates: + del self._gates[pos] + if pos in self._buttons: + del self._buttons[pos] + self._data[pos[1]][pos[0]] = new_type + new_tile = self._get_tile_image(pos, new_type) + self._tiles[pos[1]][pos[0]] = new_tile + self._changed.append((pos, new_tile)) + # Also update neighbourhood for wall types, etc. + for new_pos in [(pos[0] - 1, pos[1]), (pos[0] + 1, pos[1]), + (pos[0] - 1, pos[1] - 1), (pos[0] + 1, pos[1] + 1), + (pos[0], pos[1] - 1), (pos[0], pos[1] + 1), + (pos[0] - 1, pos[1] + 1), (pos[0] + 1, pos[1] - 1)]: + if not self._in_limits(new_pos): + continue + # Update display to changed status + self._fix_tile(new_pos) + + def _fix_tile(self, pos): + tile = self._data[pos[1]][pos[0]] + new_tile = self._get_tile_image(pos, tile) + self._tiles[pos[1]][pos[0]] = new_tile + self._changed.append((pos, new_tile)) + def get_size(self): return len(self._tiles[0]), len(self._tiles) def at_exit(self, pos): return pos in self.exit_pos - def _get_wall_tile(self, x, y): + def get_level_data(self): + return '\n'.join(reversed([''.join(x) for x in self._data])) + + def _get_wall_tile(self, pos): # Is the neighbour in this direction also a wall? + x, y = pos left = right = top = bottom = False if x == 0: left = True @@ -121,6 +210,17 @@ class Level(object): return load_image('tiles/end_left.png') return load_image('tiles/pillar.png') + def _in_limits(self, pos): + if pos[0] < 0: + return False + if pos[1] < 0: + return False + try: + self._data[pos[1]][pos[0]] + except IndexError: + return False + return True + def blocked(self, pos): if pos[0] < 0: return True @@ -133,36 +233,54 @@ class Level(object): if tile == WALL or tile == ENTRY: return True if tile == GATE: - if self._gates[pos] != 'down': + if self._gates[pos] > 0: return True return False + def calc_dist(self, pos1, pos2): + return abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1]) + def is_gate(self, pos): + if not self._in_limits(pos): + return False return self._data[pos[1]][pos[0]] == GATE def is_button(self, pos): + if not self._in_limits(pos): + return False return self._data[pos[1]][pos[0]] == BUTTON + def is_wall(self, pos): + if not self._in_limits(pos): + return True + return self._data[pos[1]][pos[0]] == WALL + def trigger_button(self, pos): if not self.is_button(pos): return False if not self._buttons[pos] == 'active': return False # Find the closest gate down gate and trigger it - gate_pos = pos - - self._changed.append((pos, self.get_single_tile(pos))) - self._changed.append((gate_pos, self.get_single_tile(pos))) + mindist = 99999 + gate_pos = None + for cand in self._gates: + dist = self.calc_dist(pos, cand) + if dist < mindist: + gate_pos = cand + mindist = dist + if gate_pos: + self._buttons[pos] = 'pressed' + self._gates[gate_pos] = 3 # Raise gate + self._fix_tile(pos) + self._fix_tile(gate_pos) def damage_gate(self, pos): if not self.is_gate(pos): - return False + return if self._gates[pos] == -1 or self._gates[pos] == 0: - return False + return self._gates[pos] = self._gates[pos] - 1 - self._fix_gate_tile(pos) - self._changed.append((pos, self.get_single_tile(pos))) - return True + self._fix_tile(pos) def get_changed_tiles(self): ret = self._changed[:] @@ -175,25 +293,45 @@ class LevelList(object): LEVELS = 'level_list' def __init__(self): - self.levels = [] + self._levels = [] + self._level_names = [] + self._errors = [] level_list = load(self.LEVELS) for line in level_list: line = line.strip() if os.path.exists(filepath(line)): level_file = load(line) - self.levels.append(Level(level_file)) + level = Level(level_file, line) level_file.close() + try: + level.validate() + self._levels.append(level) + self._level_names.append(line) + except RuntimeError as err: + self._errors.append( + 'Invalid level %s in level_list: %s' % (line, err)) else: - print 'Level list includes non-existant level %s' % line + self._errors.append( + 'Level list includes non-existant level %s' % line) level_list.close() self._cur_level = 0 def get_current_level(self): - if self._cur_level < len(self.levels): - return self.levels[self._cur_level] + if self._cur_level < len(self._levels): + return self._levels[self._cur_level] else: return None + def get_errors(self): + return self._errors + + def get_level_names(self): + return self._level_names + + def set_level_to(self, level_name): + if level_name in self._level_names: + self._cur_level = self._level_names.index(level_name) + def advance_to_next_level(self): self._cur_level += 1 return self.get_current_level()