Load tunnel tiles
[koperkapel.git] / koperkapel / loaders / levelloader.py
1 """Loader a level, using the pygame-zero ResourceLoader infrastructure"""
2
3 import os
4 import json
5
6 from pgzero.loaders import images, ResourceLoader
7 import os
8 import random
9 from pygame.transform import rotate
10
11 class Tile:
12     IMG = None
13     TILESET = None
14
15     @classmethod
16     def image(cls, neighbors):
17         if cls.IMG is None or cls.TILESET is None:
18             raise NotImplementedError()
19         return images.load(os.path.join(cls.TILESET, cls.IMG))
20
21 class OrientatedTile:
22     IMG = None
23     TILESET = None
24     ANGLE = None
25
26     @classmethod
27     def image(cls, neighbors):
28         if cls.IMG is None or cls.TILESET is None:
29             raise NotImplementedError()
30         img = images.load(os.path.join(cls.TILESET, cls.IMG))
31         if cls.ANGLE:
32             img = rotate(img, cls.ANGLE)
33         return img
34
35
36 class RandomizedTile(Tile):
37     IMGDIR = None
38     TILESET = None
39     ROTATE = None
40
41     @classmethod
42     def image(cls, neighbors):
43         if cls.IMGDIR is None or cls.TILESET is None:
44             raise NotImplementedError()
45
46         imgdir = os.path.join(os.path.dirname(__file__), '..', 'images',
47                 cls.TILESET, cls.IMGDIR)
48         imgpath = os.path.splitext(random.choice(os.listdir(imgdir)))[0]
49         img = images.load(os.path.join(cls.TILESET, cls.IMGDIR, imgpath))
50
51         if cls.ROTATE:
52             img = rotate(img, 90 * random.randint(0, 3))
53
54         return img
55
56 class Floor(RandomizedTile):
57     IMGDIR = "floor"
58
59 class Wall(RandomizedTile):
60     IMGDIR = "wall"
61
62 class Underground(RandomizedTile):
63     IMGDIR = "underground"
64
65 class Tunnel(OrientatedTile):
66
67     @classmethod
68     def image(cls, neighbors):
69         connections = [True if 'walk' in x['behaviour'] else False for x in neighbors]
70         conn_count = connections.count(True)
71         # simple cases
72         cls.ANGLE = 0
73         if conn_count == 0:
74             # return single point tunnel
75             cls.IMG = os.path.join('tunnel', 'tunnel_none')
76         elif conn_count == 4:
77             # crossroads
78             cls.IMG = os.path.join('tunnel', 'tunnel_crossroads')
79         elif conn_count == 1:
80             # 1 point connector, roatated correctly
81             cls.IMG = os.path.join('tunnel', 'tunnel_1way')
82             # because of the ordering of neighbors, we use this formulation
83             for x, angle in zip(connections, (90, 270, 0, 180)):
84                 if x:
85                     cls.ANGLE = angle
86                     break
87         elif conn_count == 3:
88             # 3 point connector, rotated correctly
89             cls.IMG = os.path.join('tunnel', 'tunnel_3way')
90             # find the missing connection.
91             for x, angle in zip(connections, (0, 180, 270, 90)):
92                 if not x:
93                     cls.ANGLE = angle
94                     break
95         elif conn_count == 2:
96             # Need to distinguish pass-through or corner, and
97             # rotate correctly
98             # neighbors is left, right then up, down
99             if connections[0] == connections[1]:
100                 cls.IMG = os.path.join('tunnel', 'tunnel_passthrough')
101                 if connections[0]:
102                     cls.ANGLE = 90
103             else:
104                 cls.IMG = os.path.join('tunnel', 'tunnel_corner')
105                 if connections[0]:
106                     if connections[2]:
107                         # left, up
108                         cls.ANGLE = 90
109                     else:
110                         # left, down
111                         cls.ANGLE = 180
112                 else:
113                     if connections[2]:
114                         # right, up
115                         cls.ANGLE = 0
116                     else:
117                         # right, down
118                         cls.ANGLE = 270
119
120         return super(Tunnel, cls).image(neighbors)
121         
122
123 TILES = {
124     "cwall": Wall, # rename this everywhere
125     "floor": Floor,
126     "tunnel": Tunnel,
127     "underground": Underground,
128 }
129
130 class LevelLoader(ResourceLoader):
131     """ Level loader. """
132
133     EXTNS = ['json']
134     TYPE = 'level'
135
136     def _load(self, level_path):
137         f = open(level_path, 'r')
138         level_data = json.load(f)
139         f.close()
140         self._height = len(level_data['tiles'])
141         self._width = len(level_data['tiles'][0])
142         self._tiles = level_data['tiles']
143         self._tileset = level_data['tileset']
144         # Consistency check, so we can assume things are correct
145         # in the level renderer
146         for row, row_data in enumerate(self._tiles):
147             if len(row_data) != self._width:
148                 raise RuntimeError("Incorrect len for row %d" % row)
149         for tile in TILES.values():
150             tile.TILESET = self._tileset
151         self._load_tile_images()
152         return level_data
153
154     def _load_tile_images(self):
155         """Load all the tile images"""
156         height = len(self._tiles)
157         width = len(self._tiles[0])
158         for y, row_data in enumerate(self._tiles):
159             for x, tile in enumerate(row_data):
160                 # simplist case
161                 # 4 -connected neighbors
162                 neighborhood = [self._tiles[y][x-1] if x > 0 else None,
163                                 self._tiles[y][x+1] if x < width - 1 else None,
164                                 self._tiles[y-1][x] if y > 0 else None,
165                                 self._tiles[y+1][x] if y < height- 1 else None,
166                                ]
167                 for layer in ['floor', 'tunnels']:
168                     neighbors = [x[layer] if x else None for x in neighborhood]
169                     tile['%s image' % layer] = \
170                             TILES[tile[layer]['base']].image(neighbors)
171
172
173 levels = LevelLoader('levels')