def __init__(self, arena): """ Some arguments are needed to decide what arena should be initialized and how should the enemy tanks behave. """ self.arena = arena self.enemies = [] # to move the player's tank or turn it left/right, just change the state # so that it will behave accordingly at next update self.player = Tank(0, 0, Arena.DIR_SOUTH, self) self._missiles = [] # a queue that store game events that provide information to the game # thread to produce game effects accordingly self.game_events = deque()
class GameModel(object): """ Controls the game logic and records the game state. It initializes an arena with tanks and starts the game. The game is updated whenever the update method is called. """ def __init__(self, arena): """ Some arguments are needed to decide what arena should be initialized and how should the enemy tanks behave. """ self.arena = arena self.enemies = [] # to move the player's tank or turn it left/right, just change the state # so that it will behave accordingly at next update self.player = Tank(0, 0, Arena.DIR_SOUTH, self) self._missiles = [] # a queue that store game events that provide information to the game # thread to produce game effects accordingly self.game_events = deque() def update(self): """ Update the state of the enemy tanks according to their behaviors. Update the locations of the missiles if their new locations are valid. Update the list of tanks if any is hit by a missile. Update the list of missiles if any hits obstacles (tank/brick/iron). Update the game state if necessary. """ self.player.update() for aTank in self.enemies: aTank.update() # iterate a copy so we can modify the original during iteration for aMissile in self._missiles[:]: aMissile.forward() tile_x = int(math.floor(aMissile.x)) tile_y = int(math.floor(aMissile.y)) # remove missile if it is out of the arena if not 0 <= tile_x < self.arena.get_width(): if not 0 <= tile_y < self.arena.get_height(): self._missiles.remove(aMissile) continue # check if the missile hit an obstacle terrain = self.arena.tiles.get_terrain(tile_x, tile_y) if terrain.obstacle == Tile.OBS_IRON: self._missiles.remove(aMissile) event = GameEvent(event_type=GameEvent.TYPE_IRON_HIT, source=terrain) self.game_events.put(event) continue if terrain.obstacle == Tile.OBS_BRICK: self._missiles.remove(aMissile) # brick disappear after being hit by a missile terrain.obstacle = None event = GameEvent(event_type=GameEvent.TYPE_BRICK_HIT, source=terrain) self.game_events.put(event) continue if aMissile.from_enemy: # game over if the player is hit if self.player.x < aMissile.x < self.player.x + 1: if self.player.y < aMissile.y < self.player.y + 1: event = GameEvent(event_type=GameEvent.TYPE_GAME_OVER, source=self.player) self.game_events.put(event) return continue # check whether the player's missile hit an enemy tank for aTank in self.enemies[:]: if aTank.x < aMissile.x < aTank.x + 1: if aTank.y < aMissile.y < aTank.y + 1: self.enemies.remove(aTank) self._missile.remove(aMissile) event = GameEvent(GameEvent.TYPE_TANK_HIT, aTank) self.game_events.put(event) break def add_missile(self, missile): """ Add a missile to the missile list. """ self._missiles.append(missile) return def get_tanks(self): """ Return a copy of the tank list. """ return self.enemies[:] def get_missiles(self): """ Return a copy of the missile list. """ return self._missiles[:] def is_valid_move(self, tank, next_x, next_y): """ tank: the tank that is moving next_loc: the next location that the tank is moving into Return if the the move is valid (not blocking by terrain or other tanks etc.). """ # not moving is always valid if (tank.x, tank.y) == (next_x, next_y): return True # a tank can only move forward or backward if tank.x + tank.base_dir[0] != next_x or tank.y + tank.base_dir[1] != next_y: if tank.x - tank.base_dir[0] != next_x or tank.y - tank.base_dir[1] != next_y: print "can only move forward or backward" return False # a tank cannot move to a location (going to be) occupied by another tank if (next_x, next_y) == (self.player.next_x, self.player.next_y): print "cannot move to a location (going to be) occupied by another tank" return False for aTank in self.enemies: # no need to check aTank == tank here if (next_x, next_y) == (aTank.next_x, aTank.next_y): print "cannot move to a location (going to be) occupied by another tank" return False move_dir = (next_x - tank.x, next_y - tank.y) return self.arena.can_move(from_x=tank.x, from_y=tank.y, move_dir=move_dir)