· 9 years ago · Nov 17, 2016, 04:34 AM
1"""
2Bomberman simulation program.
3Created on 09.12.2014.
4
5@version: 0.1 - initial version
6@version: 0.2 - added interactive bomberman
7@version: 0.3 - added required BombermanIdentificationNumber, plus Passive and Suicide bomberman implementations
8@version: 0.4 - bomb explosion chaining fixed, added bomb residue to show where the explosion was
9@version: 0.5 - bombs are resolved in random order (like players), fixed InteractiveBomberman's input validation
10
11TODO list:
121) timing the action made by bomberman implementation
132) configurable delay between simulation turns
143) better world generation and/or loading it from a file
15"""
16import random
17import re
18
19
20class Constants:
21 """Make use of the constants defined in this class.
22
23 If you happen to use return values that are not defined in this class,
24 they will be ignored and your player will lose one turn as a consequence.
25 """
26 LEFT = "left"
27 RIGHT = "right"
28 UP = "up"
29 DOWN = "down"
30 PASS = "pass"
31 MIN_DELAY = 1
32 MAX_DELAY = 5
33
34 EMPTY = set([" ", ".", "*"]) # bomb explosion residue is safe to step onto and use as a bomb location
35 DEFAULT_EMPTY = " "
36 BOMB_EXPLOSION_RESIDUE = "*"
37 PLAYER = set(map(chr, range(0x41, 0x5A + 1))) # "A".."Z"
38 BOMB = set(map(chr, range(0x30, 0x39 + 1))) # "0".."9"
39 EXPLODING_BOMB = chr(0x30) # "0"
40
41 MIN_WORLD_HEIGHT = 4
42 MAX_WORLD_HEIGHT = 64
43 MIN_WORLD_WIDTH = 4
44 MAX_WORLD_WIDTH = 64
45
46 class __WallConfig:
47 def __init__(self, ignore_damage_less_than, reduce_blast_range_by, next_wall):
48 self.ignore_damage_less_than = ignore_damage_less_than
49 self.reduce_blast_range_by = reduce_blast_range_by
50 self.next_wall = next_wall
51
52 class __BombConfig:
53 def __init__(self, blast_range):
54 self.blast_range = blast_range
55
56 # wall configurations
57 WALL_CONFIGS = {
58 # "#" wall needs at least range 3 blast to take damage, reduces the blast range by 100, and crumble (ie turn to empty space)
59 "#": __WallConfig(3, -4, BOMB_EXPLOSION_RESIDUE),
60 # empty space "needs" blast range 1 and reduces range by 1
61 " ": __WallConfig(0, -1, BOMB_EXPLOSION_RESIDUE),
62 ".": __WallConfig(0, -1, BOMB_EXPLOSION_RESIDUE),
63 "*": __WallConfig(0, -1, BOMB_EXPLOSION_RESIDUE),
64 # if bomb chaining occurs, it "needs" blast range 1 and reduces range by 1
65 "0": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
66 "1": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
67 "2": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
68 "3": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
69 "4": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
70 "5": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
71 "6": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
72 "7": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
73 "8": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
74 "9": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
75 # player needs at least blast range 1 and reduces range by 1
76 "A": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
77 "B": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
78 "C": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
79 "D": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
80 "E": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
81 "F": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
82 "G": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
83 "H": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
84 "I": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
85 "J": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
86 "K": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
87 "L": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
88 "M": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
89 "N": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
90 "O": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
91 "P": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
92 "Q": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
93 "R": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
94 "S": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
95 "T": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
96 "U": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
97 "V": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
98 "W": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
99 "X": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
100 "Y": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE),
101 "Z": __WallConfig(1, -1, BOMB_EXPLOSION_RESIDUE)
102 }
103
104 # bomb configurations
105 BOMB_CONFIG = __BombConfig(3)
106
107
108
109class Bomberman:
110 """A base class representing a player in the bomberman world (I'm a bomber man, in a bomber world.). This is the one to be sub-classed.
111
112 The objective of the bomberman is to eliminate any other existing bomberman on the board.
113 """
114
115 def __init__(self, bomberman_id):
116 """
117 The only thing a player knows about itself is its id, which is a upper-case letter "A".."Z".
118
119 Args:
120 bomberman_id (string): upper-case letter "A".."Z" representing this player's id.
121 """
122 if bomberman_id not in Constants.PLAYER:
123 raise ValueError("Illegal bomberman id \"%s\", must be one from %s" % (bomberman_id, sorted(list(Constants.PLAYER))))
124
125 self.bomberman_id = bomberman_id
126
127 def __repr__(self):
128 """Default to string implementation. Do not touch this one anymore."""
129 return "\"%s\" (%s)" % (self.bomberman_id, self.bombermanIdentificationNumber())
130
131 def bombermanIdentificationNumber(self):
132 """Provide the Bomberman Identification Number, a code that identifies the maker of the bomberman impementation. A good idea is to
133 use your student code as well as name.
134
135 Default implementation raises ValueError exception forcing the subclass to override the method.
136
137 Returns:
138 string - BIN string value
139
140 Raises:
141 ValueError - needs overriding.
142 """
143 raise ValueError("Override me!")
144
145 def action(self, world):
146 """
147 This gets called by the simulator to let a player determine it's next move. Beware, he who is slow shall be timed.
148
149 The default implementation of this method does nothing and returns Constants.PASS as the resulting action.
150
151 Args:
152 world: a 2-dimensional (MxN) array of characters representing the world in a top-down, left-right way.
153 First index of the array represents rows, second index columns.
154 First row represents the top row of the board, last row represents the bottom row of the board.
155 Columns are from left to right.
156 Symbols used on the board:
157 " " or "." - is empty location (all symbols in the set Constants.EMPTY);
158 "A".."Z" - is another player (all symbols in the set Constants.PLAYER);
159 "0".."9" - is a bomb, number meaning a delay until the bomb goes off (all symbols in the set Constants.BOMB).
160 It basically shows how many moves a player can make before the bomb goes off. The bomb damages everything
161 within it's radius of 3 squares in four directions unless there's a wall to stop it. A wall next to the
162 bomb gets demolished (or possibly damaged in the future). If a player happens to be in the line of fire
163 close enough and the bomb shows 0, it means he's going to die before completing his next move.
164 anything else - is a wall (for example "#")
165
166
167 Returns:
168 string: one of (Constants.LEFT, Constants.RIGHT, Constants.UP, Constants.DOWN, Constants.PASS) which tells the player wants to move in said direction.
169 Constants.LEFT - -1 to current column index;
170 Constants.RIGHT - +1 to current column index;
171 Constants.UP - -1 to current row index;
172 Constants.DOWN - +1 to current row index;
173 Constants.PASS - no change to current location;
174 (string, integer): A tuple representing where the player wants to plant a bomb.
175 A bomb is planted next to the player with the number representing the delay for the bomb.
176 String can be one of (Constants.LEFT, Constants.RIGHT, Constants.UP, Constants.DOWN) which tells the direction in which to put the bomb-
177 Integer cane be between and including Constants.MIN_DELAY and Constants.MAX_DELAY.
178 """
179 raise ValueError("Override me!")
180
181
182
183class PassiveBomberman(Bomberman):
184 """Bomberman implementation that does nothing."""
185
186 def __init__(self, bomberman_id):
187 Bomberman.__init__(self, bomberman_id)
188
189 def bombermanIdentificationNumber(self):
190 return "PassiveBomberman"
191
192 def action(self, world):
193 return Constants.PASS
194
195
196
197class InteractiveBomberman(Bomberman):
198 """Bomberman implementation that asks user for the action."""
199
200 def __init__(self, bomberman_id):
201 Bomberman.__init__(self, bomberman_id)
202
203 def bombermanIdentificationNumber(self):
204 return "InteractiveBomberman"
205
206 def action(self, world):
207 while True:
208 action = input("Bomberman \"%s\", state your action: " % (self.bomberman_id))
209
210 rx = r"^((%s|%s|%s|%s|%s)|((%s|%s|%s|%s) \d))$" % \
211 (Constants.UP, Constants.RIGHT, Constants.DOWN, Constants.LEFT, Constants.PASS, \
212 Constants.UP, Constants.RIGHT, Constants.DOWN, Constants.LEFT)
213
214 if re.match(rx, action) is None:
215 print("Incorrect input command \"%s\"" % (action))
216 print("Use commands:")
217 print(" <action> where action is one of (%s, %s, %s, %s, %s)" % \
218 (Constants.UP, Constants.RIGHT, Constants.DOWN, Constants.LEFT, Constants.PASS))
219 print(" <direction delay> where direction is one of (%s, %s, %s, %s) and delay is between %d and %d" % \
220 (Constants.UP, Constants.RIGHT, Constants.DOWN, Constants.LEFT, \
221 Constants.MIN_DELAY, Constants.MAX_DELAY))
222 print("Please enter a correct command.\n")
223 continue
224
225 if " " not in action:
226 return action
227 else:
228 return (action[:-2:], int(action[len(action) - 1]))
229
230
231class SuicideBomberman(Bomberman):
232 """Bomberman implementation that dies."""
233
234 def __init__(self, bomberman_id):
235 Bomberman.__init__(self, bomberman_id)
236
237 def bombermanIdentificationNumber(self):
238 return "SuicideBomberman"
239
240 def action(self, world):
241 raise ValueError("Life is nothing but a pain...")
242
243
244class World:
245 def __init__(self, height, width):
246 """
247 Defines an empty world of specified height and width, ie the world will have height rows and width columns.
248
249 Args:
250 height (integer): height of the world
251 width (integer): width of the world
252 """
253 if (type(height) != int) or (height < Constants.MIN_WORLD_HEIGHT) or (height > Constants.MAX_WORLD_HEIGHT):
254 raise ValueError("height (%s) must be an integer between %d and %d" % (height, Constants.MIN_WORLD_HEIGHT, Constants.MAX_WORLD_HEIGHT))
255 if (type(width) != int) or (width < Constants.MIN_WORLD_WIDTH) or (width > Constants.MAX_WORLD_WIDTH):
256 raise ValueError("width (%s) must be an integer between %d and %d" % (width, Constants.MIN_WORLD_WIDTH, Constants.MAX_WORLD_WIDTH))
257
258 self.__world = [[Constants.DEFAULT_EMPTY for _ in range(width)] for _ in range(height)]
259 self.__turn = 0
260 self.__height = height
261 self.__width = width
262 self.__bombermans = { }
263 self.__dead_bombermans = { }
264
265 def print_world(self):
266 """Prints the current turn identifier and the world, replacing empty locations denoted by space (" ") with dots (".")."""
267 print("\nTurn: %s" % (self.__turn))
268 for r in self.__world:
269 print("".join(map(lambda x: "." if x == " " else x, r)))
270
271 def add_bomberman(self, bomberman):
272 """Adds a new bomberman player to the world at random location.
273
274 Args:
275 bomberman (Bomberman): a new bomberman player to add to the world
276
277 Raises:
278 ValueError - when no instance of bomberman was given, it has illegal player identifier,
279 or if a player with such identifier already exists on the board.
280 """
281 if not isinstance(bomberman, Bomberman):
282 raise ValueError("bomberman must be an instance of Bomberman class")
283 if bomberman.bomberman_id not in Constants.PLAYER:
284 raise ValueError("Illegal bomberman id \"%s\", must be one from %s" % (bomberman.bomberman_id, sorted(list(Constants.PLAYER))))
285 if bomberman.bomberman_id in self.__bombermans.keys():
286 raise ValueError("bomberman with id \"%s\" already exists" % (bomberman.bomberman_id))
287
288 try:
289 # check that identification number is provided
290 bomberman.bombermanIdentificationNumber()
291 except ValueError:
292 print("Bomberman with id \"%s\" failed to provide Bomberman Identification Number, it has not been added to the world" \
293 % (bomberman.bomberman_id))
294 return
295
296 while True:
297 y = random.randint(0, self.__height - 1)
298 x = random.randint(0, self.__width - 1)
299 if (self.__world[y][x] in Constants.EMPTY):
300 break
301
302 self.__world[y][x] = bomberman.bomberman_id
303 self.__bombermans[bomberman.bomberman_id] = bomberman
304
305 print("Bomberman %s was added to the world" % (bomberman))
306
307 def simulate(self, turns):
308 """Runs the bomberman simulation.
309
310 Args:
311 truns (integer): number of turns to simulate.
312
313 Raises:
314 ValueError - when illegal number of turns was given.
315 """
316 if (type(turns) != int) or (turns < 1):
317 raise ValueError("turns (%s) must be an integer greater than 0" % (turns))
318
319 # before simulation generate walls in the world
320 self.__generate_walls()
321
322 # run the simulation
323 for self.__turn in range(turns):
324 self.print_world()
325
326 # remove previous explosion residue
327 self.__remove_bomb_explosion_residue()
328
329 # kill all humans aka set off the bombs that need setting off and decrease delay counters
330 self.__tick_bombs()
331
332 # check for a highlander for there can only be one (or none)
333 if len(self.__bombermans.keys()) <= 1:
334 break
335
336 # any remaining bombermans get a choice of action
337 actions = { }
338 for bomberman_id, bomberman in list(self.__bombermans.items()): # note that we create a copy so we can change the underlying dictionary
339 try:
340 actions[bomberman_id] = bomberman.action([[self.__world[y][x] for x in range(self.__width)] for y in range(self.__height)])
341 except Exception as error:
342 # the bomberman is unable to make a decent action, it just, well, dies, remove it from the world
343 print("Bomberman %s died in action with a suicide note \"%s\"" % (bomberman, error))
344 self.__remove_dead_bomberman(bomberman_id)
345
346
347 # let the actions be done in the random order
348 random_ordered_bombermans = list(actions.keys())
349 random.shuffle(random_ordered_bombermans)
350
351 for bomberman in random_ordered_bombermans:
352 self.__make_bomberman_action(bomberman, actions[bomberman])
353
354 self.__turn = "END"
355 self.print_world()
356
357 def __remove_bomb_explosion_residue(self):
358 """Remove traces from previous explosions."""
359 for y in range(self.__height):
360 for x in range(self.__width):
361 if self.__world[y][x] == Constants.BOMB_EXPLOSION_RESIDUE:
362 self.__world[y][x] = Constants.DEFAULT_EMPTY
363
364 def __remove_dead_bomberman(self, bomberman):
365 """The bomberman died making its turn, so remove it from the table.
366
367 Args:
368 bomberman_id - id of the bomberman to remove
369 """
370 y, x = self.__find_bomberman(bomberman)
371
372 self.__world[y][x] = Constants.DEFAULT_EMPTY
373 self.__kill_bomberman(bomberman)
374
375 def __generate_walls(self):
376 """Generate walls in the world.
377 TODO: try for a better algorithm or maybe load a world from a file?
378 """
379 for y in range(self.__height):
380 for x in range(self.__width):
381 self.__generate_wall(y, x)
382
383 def __generate_wall(self, y, x):
384 """Generate wall in the specified location.
385 TODO: try for a better algorithm or maybe load a world from a file?
386
387 Args:
388 y, x - location
389 """
390 if self.__world[y][x] not in Constants.PLAYER and \
391 ((y == 0) or ((y > 0) and (self.__world[y - 1][x] not in Constants.PLAYER))) and \
392 ((x == self.__width - 1) or ((x < self.__width - 1) and (self.__world[y][x + 1] not in Constants.PLAYER))) and \
393 ((y == self.__height - 1) or ((y < self.__height - 1) and (self.__world[y + 1][x] not in Constants.PLAYER))) and \
394 ((x == 0) or ((x > 0) and (self.__world[y][x - 1] not in Constants.PLAYER))):
395 # it's not a player and it's not near a player, now let's see if it's a wall or an empty square
396 # simple algorithm - 70% walls
397 w = random.randint(0, 100)
398
399 if w >= 30:
400 self.__world[y][x] = "#"
401
402 def __make_bomberman_action(self, bomberman, action):
403 """Make a bomberman action.
404
405 Args:
406 bomberman - bomberman id
407 action - return value of Bomberman.action
408
409 Returns:
410 True - if the action was successful for this bomberman
411 False - if the action was unsuccessful or failed validation for this bomberman
412 """
413 y, x = self.__find_bomberman(bomberman)
414
415 if self.__is_action_valid(y, x, action):
416 # only valid actions are accepted
417 if type(action) is not tuple:
418 # this bomberman moves or remains passive
419 if action != Constants.PASS:
420 # remove bomberman from current location if he isn't passing
421 self.__world[y][x] = Constants.DEFAULT_EMPTY
422
423 if action == Constants.UP:
424 self.__world[y - 1][x] = bomberman
425 elif action == Constants.RIGHT:
426 self.__world[y][x + 1] = bomberman
427 elif action == Constants.DOWN:
428 self.__world[y + 1][x] = bomberman
429 elif action == Constants.LEFT:
430 self.__world[y][x - 1] = bomberman
431 elif action != Constants.PASS:
432 raise ValueError("Ugh, what?! This action (%s) failed validation." % (action))
433 else:
434 # so this bomberman plants a bomb
435 if action[0] == Constants.UP:
436 self.__world[y - 1][x] = str(action[1])
437 elif action[0] == Constants.RIGHT:
438 self.__world[y][x + 1] = str(action[1])
439 elif action[0] == Constants.DOWN:
440 self.__world[y + 1][x] = str(action[1])
441 elif action[0] == Constants.LEFT:
442 self.__world[y][x - 1] = str(action[1])
443 else:
444 raise ValueError("Ugh, what?! This action (%s) failed validation." % (action))
445
446 # successful action
447 return True
448 else:
449 # unsuccessful action
450 return False
451
452 def __is_action_valid(self, y, x, action):
453 """Check if the action the bomberman wants to make is valid.
454
455 Args:
456 y, x - current bomberman location on board
457 action - either direction string or tuple consisting of direction and bomb delay
458
459 Returns:
460 True - if action is valid
461 False - if action is invalid
462 """
463 direction = ""
464 if type(action) is tuple:
465 # check if tuple has correct length
466 if len(action) != 2:
467 return False
468
469 # check tuple element types
470 if not isinstance(action[0], str) or (type(action[1]) != int):
471 return False
472
473 # check bomb delay limits
474 if (action[1] < Constants.MIN_DELAY) or (action[1] > Constants.MAX_DELAY):
475 return False
476
477 direction = action[0]
478 else:
479 direction = action
480
481 # check if the bomberman is passive aggressive
482 if direction == Constants.PASS:
483 return True
484
485 # check if this bomberman is one unfortunate cookie or a dumb-ass and thus can't make the action
486 if (direction == Constants.UP) and ((y == 0) or (self.__world[y - 1][x] not in Constants.EMPTY)):
487 return False
488
489 if (direction == Constants.RIGHT) and ((x == self.__width - 1) or (self.__world[y][x + 1] not in Constants.EMPTY)):
490 return False
491
492 if (direction == Constants.DOWN) and ((y == self.__height - 1) or (self.__world[y + 1][x] not in Constants.EMPTY)):
493 return False
494
495 if (direction == Constants.LEFT) and ((x == 0) or (self.__world[y][x - 1] not in Constants.EMPTY)):
496 return False
497
498 # seems that this bomberman knows what it wants
499 return True
500
501 def __find_bomberman(self, bomberman):
502 """Find the given bomberman on the board.
503
504 Args:
505 bomberman - id of the bomberman to find.
506 """
507 for y in range(self.__height):
508 for x in range(self.__width):
509 if self.__world[y][x] == bomberman:
510 return (y, x)
511 # this really shouldn't happen
512 raise ValueError("Ugh, what?! Where's that bomberman \"%s\" now?" % (bomberman))
513
514 def __tick_bombs(self):
515 """Decrease delays on all bombs, set off bombs that need setting off, and remove players that are down on their luck."""
516 # resolve bombs in random order
517 bombs = [(y, x) for y in range(self.__height) for x in range(self.__width) if self.__world[y][x] in Constants.BOMB]
518 random.shuffle(bombs)
519
520 for y, x in bombs:
521 self.__tick_bomb(y, x)
522
523 def __tick_bomb(self, y, x):
524 """Decrease delay on the bomb, set off bombs if necessary, and remove players that are down on their luck."""
525 # first make sure it's still a bomb
526 if self.__world[y][x] in Constants.BOMB:
527 if self.__world[y][x] == Constants.EXPLODING_BOMB:
528 self.__explode_bomb(y, x)
529 else:
530 self.__world[y][x] = str(int(self.__world[y][x]) - 1)
531
532 def __explode_bomb(self, y, x):
533 """Explode the bomb, wreak havoc, destruction and mayhem, and let the unworthy wallow in excruciating pain and agony until death takes them.
534
535 Args:
536 y, x - starting location
537 """
538 # clear the bomb area
539 self.__world[y][x] = Constants.BOMB_EXPLOSION_RESIDUE
540
541 # wreak havoc and mayhem in four directions
542 self.__wreak_havoc_and_mayhem(y, x, -1, 0) # up
543 self.__wreak_havoc_and_mayhem(y, x, 0, 1) # right
544 self.__wreak_havoc_and_mayhem(y, x, 1, 0) # down
545 self.__wreak_havoc_and_mayhem(y, x, 0, -1) # left
546
547 def __wreak_havoc_and_mayhem(self, y, x, dy, dx):
548 """Do the bomb damage in one direction.
549
550 Args:
551 y, x - starting location
552 dy, dx - respective movement delta
553 """
554 i, yn, xn = Constants.BOMB_CONFIG.blast_range, y + dy, x + dx
555 while (i > 0) and (yn >= 0) and (yn < self.__height) and (xn >= 0) and (xn < self.__width):
556 location = self.__world[yn][xn]
557
558 # if the player is in the line of fire kill him off
559 if location in Constants.PLAYER:
560 self.__kill_bomberman(location)
561
562 # the bomb is in the line of fire, explode it recursively to wreak more havoc and mayhem
563 if location in Constants.BOMB:
564 self.__explode_bomb(yn, xn)
565
566 # check if the location can take the damage
567 if i >= Constants.WALL_CONFIGS[location].ignore_damage_less_than:
568 # yes it can, let the damage be done and turn the location into another type (ie check the mappings)
569 self.__world[yn][xn] = Constants.WALL_CONFIGS[location].next_wall
570
571 # reduce the blast range
572 i += Constants.WALL_CONFIGS[location].reduce_blast_range_by
573
574 yn += dy
575 xn += dx
576
577 def __kill_bomberman(self, bomberman):
578 """Turn bomberman into bomberunman.
579
580 Args:
581 bomberman - bomberman to kill off
582 """
583 print("Bomberman %s died on turn %s" % (self.__bombermans[bomberman], self.__turn))
584
585 self.__dead_bombermans[bomberman] = self.__bombermans[bomberman]
586 self.__bombermans.pop(bomberman)
587
588 def list_live_bombermans(self):
589 """Return a list of bombermans still alive.
590
591 Returns:
592 list - list of bombermans still alive
593 """
594 return list(self.__bombermans.values())
595
596 def list_dead_bombermans(self):
597 """Return a list of bombermans that perished valiantly in a noble battle.
598
599 Returns:
600 list - list of bombermans that perished
601 """
602 return list(self.__dead_bombermans.values())
603
604if __name__ == "__main__":
605 print(Constants.EMPTY)
606 print(Constants.PLAYER)
607 print(Constants.BOMB)
608
609 world = World(5, 10)
610 world.add_bomberman(InteractiveBomberman("A"))
611 world.add_bomberman(PassiveBomberman("B"))
612 world.add_bomberman(SuicideBomberman("C"))
613 world.print_world()
614
615 world.simulate(5)
616
617 print("alive: %s" % (world.list_live_bombermans()))
618 print("dead: %s" % (world.list_dead_bombermans()))