Less buggy Kivy 1.7 hackery
[erdslangetjie.git] / erdslangetjie / level.py
index 73878aa927bf438439d6b6c08d06dabf2b021a25..9ebb6f4912525a3f0f34c7589badbb80cf3525af 100644 (file)
@@ -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 = {}
@@ -43,10 +48,6 @@ class Level(object):
             self._tiles.append(tile_line)
 
     def _get_tile_image(self, pos, c):
-        if pos in self._gates:
-            del self._gates[pos]
-        if pos in self._buttons:
-            del self._buttons[pos]
         image = None
         if c == FLOOR:
             image = load_image('tiles/floor.png')
@@ -59,11 +60,33 @@ class Level(object):
             self.exit_pos.append(pos)
             image = load_image('tiles/door.png')
         elif c == GATE:
-            image = load_image('tiles/gate_down.png')
-            self._gates[pos] = -1  # down
+            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:
-            image = load_image('tiles/button.png')
-            self._buttons[pos] = 'active'
+            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
@@ -71,17 +94,25 @@ class Level(object):
     def validate(self):
         entry_points = 0
         exit_points = 0
+        gates = 0
+        buttons = 0
         for line in self._data:
             if ENTRY in line:
-                entry_points += 1
+                entry_points += line.count(ENTRY)
             if EXIT in line:
-                exit_points += 1
+                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
@@ -93,11 +124,12 @@ class Level(object):
         return self._data[pos[1]][pos[0]]
 
     def set_tile_type(self, pos, new_type):
-        print '\n'.join([''.join(x) for x in self._data])
+        # 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
-        print
-        print '\n'.join([''.join(x) for x in self._data])
-        print pos, self._in_limits(pos)
         new_tile = self._get_tile_image(pos, new_type)
         self._tiles[pos[1]][pos[0]] = new_tile
         self._changed.append((pos, new_tile))
@@ -108,11 +140,14 @@ class Level(object):
                 (pos[0] - 1, pos[1] + 1), (pos[0] + 1, pos[1] - 1)]:
             if not self._in_limits(new_pos):
                 continue
-            tile = self._data[new_pos[1]][new_pos[0]]
-            print new_pos, tile
-            new_tile = self._get_tile_image(new_pos, tile)
-            self._tiles[new_pos[1]][new_pos[0]] = new_tile
-            self._changed.append((new_pos, new_tile))
+            # 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)
@@ -120,6 +155,9 @@ class Level(object):
     def at_exit(self, pos):
         return pos in self.exit_pos
 
+    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
@@ -181,7 +219,6 @@ class Level(object):
             self._data[pos[1]][pos[0]]
         except IndexError:
             return False
-        print pos, self._data[pos[1]][pos[0]]
         return True
 
     def blocked(self, pos):
@@ -196,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[:]
@@ -238,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()