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