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