-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlevel.py
More file actions
335 lines (259 loc) · 9.87 KB
/
level.py
File metadata and controls
335 lines (259 loc) · 9.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
""" A level of the dungeon, usually filled with monsters and treasure.
"""
#TODO: import as necessary.
from turncounter import *
from wall import *
WHITE = Color("#FFFFFF")
class Level:
""" Level( int, int, int ) -> Level
A level exists in the context of a larger dungeon. It contains a two-dimensional
set of tiles which beings such as the player can traverse.
Attributes:
width and height are the dimensions of the Level in tiles.
depth is how far "underground" the level is, where 0 means it's on the surface.
A higher depth means a lower (and usually harder) level.
Multiple levels may be on different branches, but have the same depth.
level_map is a visual representation of the level, onto which is contents are displayed.
tiles are the individual, same-sized tiles that make up the level.
beings and monsters are the creatures that are currently on the level.
player is the being that the person playing the game is controlling.
turn_counter is an object that processes the passage of time on the level.
Different actions take different amounts of time and the turn_counter
manages this system.
"""
def __init__(self, width, height, depth): #TODO: other args if necessary
self.width, self.height = width, height
self.depth = depth
self.level_map = Surface((width * TILE_WIDTH, height * TILE_HEIGHT))
self.tiles = []
self.init_tiles()
self.beings = []
self.monsters = []
self.player = None
self.turn_counter = TurnCounter()
self.effects = []
self.active_projectiles = []
def init_tiles(self):
""" l.init_tiles( ) -> None
Create a bunch of empty tiles.
This is subject to change once we implement level generation.
"""
for y in range(self.height):
self.tiles.append([])
for x in range(self.width):
next_tile = Tile(self, x, y) #TODO: change if tiles get args
self.tiles[y].append(next_tile)
next_tile.update()
def level_map_section(self, x1, y1, x2, y2):
""" l.level_map_section( int, int, int, int ) -> Surface
Returns an image that is a section of the level with
(x1, y1) as its upper-left corner and
(x2, y2) as its lower-right corner.
"""
map_width, map_height = TILE_WIDTH * (x2 - x1), TILE_HEIGHT * (y2 - y1)
l_map = Surface((map_width, map_height))
x_start, y_start = x1 * TILE_WIDTH, y1 * TILE_HEIGHT
x_end, y_end = x2 * TILE_WIDTH, y2 * TILE_HEIGHT
section_x1, section_y1 = max(0, x_start), max(0, y_start)
section_x2, section_y2 = min(self.level_map.get_width() - section_x1, x_end), min(self.level_map.get_height() - section_y1, y_end)
section = self.level_map.subsurface(section_x1, section_y1, section_x2, section_y2)
blit_off_x, blit_off_y = TILE_WIDTH + max(0, -1 * x_start), TILE_HEIGHT + max(0, -1 * y_start)
l_map.blit(section, (blit_off_x, blit_off_y))
return l_map
def send_event(self, message): #TEMP. might not always send message right away.
""" l.send_event( str ) -> None
Display a message on the event pane.
"""
if(self.player != None):
self.player.send_event(message)
#def add_effect(self, symbol, x, y, color = WHITE): #might not need a method like this. Hard to tell right now.
# pass #TODO
#effect = Effect(symbol, x, y, color)
#self.effects.append(effect)
def clear_effects(self):
""" l.clear_effects( ) -> None
Clear all tile effects from all tiles.
"""
for row in self.tiles:
for t in row:
if t.effect:
t.effect = None
t.update()
def update_objects(self):
""" l.update_objects( ) -> None
Updates certain objects that are not affected by user input, such as active projectiles.
"""
self.update_projectiles()
def update_projectiles(self):
""" l.update_projectiles( ) -> None
Updates all active (moving) projectiles.
"""
for p in self.active_projectiles:
p.update()
def add_projectile(self, projectile):
""" l.add_projectile( Projectile ) -> None
Adds an active projectile to the level.
"""
self.active_projectiles.append(projectile)
def remove_projectile(self, projectile):
""" l.remove_projectile( Projectile ) -> None
Removes an active projectile from the level.
"""
self.active_projectiles.remove(projectile)
def plan_monster_turns(self): #NOTE: might need to do more than this
""" l.plan_monster_turns( ) -> None
Every monster on the level decides what it will do next.
"""
for m in self.monsters:
m.decide_next_turn()
def process_turns(self):
""" l.process_turns( ) -> None
Tell the turn counter to process the current turns of all Beings and Statuses on the level.
"""
self.turn_counter.process_turns()
def enqueue_delay(self, actor, delay):
""" l.enqueue_delay( Being/Status, int ) -> None
Give the turn counter some delay for an actor on the level.
The actor must wait this many units of time before doing something again.
"""
self.turn_counter.enqueue_delay(actor, delay)
def enqueue_player_delay(self, player, delay):
""" l.enqueue_player_delay( Player, int ) -> None
Tell the turn counter to make the player wait for the given number of units of time
before acting again.
"""
self.turn_counter.enqueue_player_delay(player, delay)
def clear_map(self):
""" l.clear_map( ) -> None
Overwrite the current level map, usually in preparation for redrawing it.
"""
self.level_map = Surface((self.width * TILE_WIDTH, self.height * TILE_HEIGHT))
def add_tile(self, tile, x, y): # not to be used aside from building the level (at least for now)
""" l.add_tile( Tile, int, int ) -> None
Place the given tile on the level at the given coordinates.
"""
self.tiles[y][x] = tile
def add_wall(self, x, y):
""" l.add_wall( int, int ) -> None
Place a wall on the level at the given coordinates.
"""
wall = Wall(self, x, y)
self.add_tile(wall, x, y)
wall.update()
def add_item(self, item, x, y):
""" l.add_item( Item, int, int ) -> None
Add an item to the level on the tile at the given coordinates.
"""
if(self.valid_tile(x, y)):
self.tile_at(x, y).add_item(item)
def add_player(self, player, x, y):
""" l.add_player( Player, int, int ) -> None
Set the level's player to the given Player and place him at the given coordinates.
"""
self.player = player
self.add_being(player, x, y)
def add_monster(self, monster, x, y):
""" l.add_monster( Monster, int, int ) -> None
Add the given monster to the level at the given coordinates.
"""
self.monsters.append(monster)
self.add_being(monster, x, y)
def remove_monster(self, monster):
""" l.remove_monster( Monster ) -> None
Remove the given monster from the level.
"""
if(monster in self. monsters):
self.monsters.remove(monster)
self.remove_being(monster)
def add_being(self, being, x, y):
""" l.add_being( Being, int, int ) -> None
Add the given being to the level at the given coordinates.
"""
self.beings.append(being)
if(self.valid_tile(x, y)):
being.current_level = self
self.tiles[y][x].set_being(being)
def remove_being(self, being):
""" l.remove_being( Being ) -> None
Remove the given Being from the level.
"""
if(being in self.beings):
self.beings.remove(being)
self.remove_actor(being)
being.current_tile.remove_being()
def remove_actor(self, actor):
""" l.remove_actor( Being/Status ) -> None
Stop the given actor from doing any more things on this level.
"""
self.turn_counter.remove_actor(actor)
def being_in_tile(self, x, y):
""" l.being_in_tile( int, int ) -> Being
If there is a being in the tile at the given coordinates, return it.
"""
if(self.valid_tile(x, y)):
return self.tile_at(x, y).current_being
return None
# TODO: get the order of the tiles correct
def tile_line(self, start_tile, end_tile):
""" l.tile_line( Tile, Tile ) -> [ Tile ]
Gives a list of tiles in a line between two tiles.
"""
def iround(x):
"""iround(number) -> integer
Round a number to the nearest integer."""
return int(round(x) - .5) + (x > 0)
if start_tile.in_range(end_tile, 1):
return [end_tile]
line_tiles = []
x_dist = end_tile.x - start_tile.x
y_dist = end_tile.y - start_tile.y
if abs(x_dist) > abs(y_dist): # x is the independent variable
slope = float( float(y_dist)/float(x_dist) )
increment = 1
if start_tile.x > end_tile.x:
increment = -1
current_x = start_tile.x + increment
start_y = start_tile.y
while current_x != end_tile.x:
x_off = current_x - start_tile.x
current_y = iround(float(x_off)*slope) + start_y
line_tiles.append(self.tile_at(current_x, current_y))
current_x += increment
else: # y is the independent variable
slope = float( float(x_dist)/float(y_dist) )
increment = 1
if start_tile.y > end_tile.y:
increment = -1
current_y = start_tile.y + increment
start_x = start_tile.x
while current_y != end_tile.y:
y_off = current_y - start_tile.y
current_x = iround(float(y_off)*slope) + start_x
line_tiles.append(self.tile_at(current_x, current_y))
current_y += increment
line_tiles.append(end_tile)
return line_tiles
def tile_at(self, x, y):
""" l.tile_at( int, int ) -> Tile
Return the tile at the given coordinates.
"""
#TODO: error checking? (might want it before this method is called in many cases.)
return self.tiles[y][x]
def open_tile(self, x, y):
""" l.open_tile( int, int ) -> bool
Check whether the tile at the given coordinates is passable (i.e., a being can move through it.)
"""
return self.valid_tile(x, y) and self.tile_at(x, y).passable()
def valid_tile(self, x, y):
""" l.valid_tile( int, int ) -> bool
Check whether the given coordinates correspond to a tile in this level.
"""
return x >= 0 and y >= 0 and x < self.width and y < self.height
def temp_place_being(self, being, x, y): #used for movement. TEMPORARY. (no docstring for temporary methods)
if(self.valid_tile(x, y)):
self.tiles[y][x].set_being(being)
def turn_count(self):
""" l.turn_count( ) -> int
Gives the amount of time that has passed since the start of the game.
"""
return self.turn_counter.turn_count