class Level(): def __init__(self, file_path, logger=None, testing=False): if logger is None: logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s') logger = logging.getLogger(__name__) self.logger=logger self.logger.info("Loading Level" ) self.terrain = Terrain(TERRAIN_TO_FILE, NODE_SIZE, BACKGROUND) with open(file_path) as f: lines = f.read().split("\n") dimensions = lines[0] row,column = self.parse_dimensions(dimensions) self.rows = row self.columns = column self.graph = Graph(row, column, self.logger) cond1 = len(lines[1:]) == row or len(lines[1:]) - 1 == row assert cond1 ,'File has mismatching dimensions' r = 0 for line in lines[1:]: if line != "": nodes = line.split(",") cond2 = len(nodes) == column assert cond2, 'File has mismatching dimensions' c = 0 for node in nodes: n = Node(string_object=node, images=self.terrain, logger=self.logger) self.graph.set_node(r, c, n) c += 1 r += 1 self.logger.info("Done loading level") if not testing: sys.setrecursionlimit(self.graph.columns * self.graph.rows) def save(self, file_path): ''' a method to save the level to a specific file Parameters: file_path: where to save the file (os.path) Returns: None ''' self.logger.info("Saving file %s" % file_path) with open(file_path, 'w') as f: f.write(str(self.rows) + 'X' + str(self.columns) + '\n') for row in range(0, self.rows): line = [] for node in self.graph.iterate(row): line.append(str(node)) self.logger.debug(line) f.write(",".join(line) + '\n') self.logger.info("Done Saving file %s" % file_path) return def parse_dimensions(self, dimensions): ''' a method to parse the dimensions string Parameters: dimensions: the dimensions (string) Returns: row: the number of nodes in each row (int) column: the number of node in each column (int) ''' self.logger.debug(dimensions) dimensions = dimensions.split("X") self.logger.debug(dimensions) assert len(dimensions) == 2, 'Invalid Dimensions of file' row = int(dimensions[0]) column = int(dimensions[1]) return (row, column) def draw(self, surface): ''' a method that draws the level Parameters: surface: the pygame display screen (surface) Returns: None ''' self.graph.draw(surface) def update_node(self,x, y, terrain, player=None): ''' a method that given the (x,y) determines updates the node at that position to given terrain type Parameters: x: the x position on the screen (int) y: the y position on the screen (int) terrain: the terrain type (int) player: the player the node belongs to (Player) Returns: None ''' x = x - x % NODE_SIZE y = y - y % NODE_SIZE if player is not None: player = player.get_id() n = Node(x=x, y=y, terrain=terrain, player=player, images=self.terrain) row = y // NODE_SIZE column = x // NODE_SIZE self.graph.set_node(row, column, n) return def add_castle(self, x, y, player): ''' a method to add a castle Parameters: x: the position (int) y: the y position (int) player: the player who the castle belongs to (player) Returns: added: True if able to add castle False otherwise (boolean) ''' x = x - x % NODE_SIZE y = y -y % NODE_SIZE row = y // NODE_SIZE column = x // NODE_SIZE added = False row_cond = row > 2 and row < self.rows - 2 col_cond = column > 2 and column < self.columns - 2 outer_edge = row_cond and col_cond if player is not None: p = player.get_id() self.logger.info("Player's Castle: %i" % p) self.logger.info("Outer edge condition: %s" % outer_edge) if player is not None and outer_edge and self.check_castle(row, column): nodes = [] i = 0 for (r, c) in castle_spot(row, column): nodes.append(Node(x=c*NODE_SIZE, y=r*NODE_SIZE, terrain=CASTLE, player=p, images=self.terrain)) self.graph.set_node(r, c, nodes[i]) i += 1 added = True player.add_castle(nodes[0]) return added def check_castle(self, row, column): ''' a method to check if can add the castle to that spot Parameters: row: the row index (int) column: the column index (int) Return: valid: True if castle is valid, False otherwise (boolean) ''' valid = True for (r,c) in castle_spot(row, column): try: node_id = self.graph.get_node_id(r, c) if not self.graph.available(node_id): self.logger.info("Node not available: %d" % node_id) valid = False except: self.logger.info("Invalid node id") valid = False if not valid: break self.logger.info("Add castle: %s" % valid) return valid def add_cannon(self, x, y, player, game=True): ''' a method to add a cannon to the level and the player Parameters: x: the x position (int) y: the y position (int) player: the player cannon (player) game: True if part of game, False if want to add manually Returns: True if cannon was added False otherwise ''' x = x - x % NODE_SIZE y = y - y % NODE_SIZE add = True try: cannon = Node(x=x, y=y, terrain=CANNON, images=self.terrain,player=player.get_id()) row = y // NODE_SIZE column = x // NODE_SIZE node = self.graph.get_node(row, column) if node.get_type() not in CANBUILD: add = False if game and node.is_painted(): add = False except: add = False if add: self.graph.set_node(row, column, cannon) player.add_cannon(cannon) return add def destroy_node(self, x, y): ''' a method that destroys the node Parameters: x: the x position (int) y: the y position (int) Returns: None ''' row = (y) // NODE_SIZE column = (x) // NODE_SIZE self.graph.destroy_node(row, column) def add_piece(self, player): ''' a method used to add the piece of the player to the level Parameters: piece: the building piece to add (Piece) Returns: True if able to add piece, False otherwise (boolean) ''' added = False # check if able to add piece valid = True piece = player.get_piece() for point in piece.return_points(): column = point[0] row = point[1] column = column // NODE_SIZE row = row // NODE_SIZE try: node = self.graph.get_node(row, column) if node.get_type() not in CANBUILD: valid = False break except AssertionError: valid = False break self.logger.debug(" Able to add piece: %s" % valid) if valid: added = True for point in piece.return_points(): column = point[0] row = point[1] x = column - column % NODE_SIZE y = row - row % NODE_SIZE column = column // NODE_SIZE row = row // NODE_SIZE add_node = Node(x=x, y=y, terrain=BLOCK, images=self.terrain, player=player.get_id()) self.graph.set_node(row, column, add_node) return added def update(self): ''' a method to update the level Parameters: None: Returns: None ''' self.graph.paint() def add_players_castles(self, player): ''' a method that takes the levels castles and adds them to the player Parameters: player: the player to add to (Player) Returns: None ''' pid = player.get_id() self.logger.info("Adding Castles to player: %d" % pid) for node in self.graph.iterate(): right_player = node.get_player() == pid if node.get_type() == CASTLE and right_player: player.add_castle(node) return def cleanup(self): ''' a method that cleanup the level of all destroyed squares Parameters: None Returns: None ''' for node in self.graph.iterate(): if node.get_state() == DESTROYED: (x, y) = node.get() self.update_node(x, y, GRASS)