def update(self, time_diff): """ Calls the super update function as well as check for if the package should be turned into a tile. """ if self.target_coords == [int(self.x), int(self.y)]: self.next_target_tile() if self.target_tile is not None: if (g.get_img(*self.target_tile).collides or (g.get_img(self.get_tile()[0], self.target_tile[1]).collides and g.get_img(self.target_tile[0], self.get_tile()[1]).collides)): if len(self.path) > 0: if self.deliver_tile: if not self.pathfind(self.deliver_tile): # If all else fails, destroy the robot if self.home_tile: self.come_home() self.return_request() else: self.pathfind(self.path[-1]) else: self.paths_end_func() self.path = [] super(PathingEntity, self).update(time_diff)
def paint(self): """ Paints the player and its aim indicator on the screen. Also paints the targets for the aimed at tile if it has any. """ super(Player, self).paint() if self.removing_tile: if g.in_map(*self.get_aim_tile()) and g.get_img(*self.get_aim_tile()).destroy is not None: aim = "remove_aim" else: aim = "remove_aim_fail" else: aim = "aim" x = ((self.last_aim_tile[0]*c.TILE_SIZE) + (c.TILE_SIZE - g.images[aim].get_size()[0]) / 2) y = ((self.last_aim_tile[1]*c.TILE_SIZE) + (c.TILE_SIZE - g.images[aim].get_size()[1]) / 2) g.screen.blit(g.images[aim].get(), (x, y)) # When you aim at a factory, display the set targets for that tile if g.in_map(*self.last_aim_tile) and g.get_img(*self.last_aim_tile).factory_output: x, y = self.last_aim_tile if g.map[x][y].good_targets: for good_target in list(g.map[x][y].good_targets.values()): g.screen.blit(g.images["tile_target_aim"].get(), (good_target[0]*c.TILE_SIZE + (c.TILE_SIZE - g.images["tile_target_aim"].get_size()[0]) / 2, good_target[1]*c.TILE_SIZE + (c.TILE_SIZE - g.images["tile_target_aim"].get_size()[1]) / 2)) # Show the direction the selected launcher tile is shooting if type(g.map[x][y]) == tiles.LauncherTile and g.map[x][y].shoot_direction != (0, 0): g.screen.blit(g.images["tile_target_aim"].get(), ((g.map[x][y].shoot_direction[0]+x)*c.TILE_SIZE + (c.TILE_SIZE - g.images["tile_target_aim"].get_size()[0]) / 2, ((g.map[x][y].shoot_direction[1]+y)*c.TILE_SIZE + (c.TILE_SIZE - g.images["tile_target_aim"].get_size()[1]) / 2)))
def _set_target_tile(good): """ Sets a custom target for a factory tile where it will try to send its goods first. """ x, y = g.special_entity_list["player"].get_aim_tile() if g.get_img(x, y).factory_output: # If it's aiming at itself, disable it. if (x, y) == tuple(g.tile_target_selection): if good in g.map[x][y].good_targets: del g.map[x][y].good_targets[good] print("Removed target") else: print("No change") else: g.map[x][y].good_targets[good] = tuple(g.tile_target_selection) g.special_entity_list["player"].browsing_menu = False g.tile_target_selection = None del g.special_entity_list["tile_target"] return True else: return False
def goods_pathfind(self, target_goods): """ Finds a path from the start tile, (the current tile of the entity) to the nearest factory tile that can recieve the passed-in goods type. "goods" should be a string with the type of goods that the entity will be carrying. returns True if a suitable tile was found and False if no suitable tile was found """ start = self.get_tile() # The open_dict and closed_dict both follow the format: # (key_x, key_y): [g, (came_from_x, came_from_y)] # This is a simplified version of the pathfind function. It doesn't use heuristics because it doesn't # know where to go yet. It only uses the G value, which is the distance to the tile it came from. if type(start) != tuple: raise Exception("Value given by self.get_tile() to PathingEntity.goods_pathfind() is not tuple") # The open dictionary is a dictionary keeping the currently checkable tiles # The starting tile is identified as not having a came_from-tuple. open_dict = {start: [0]} # The closed dictionary is a dicionary keeping the currently checked tiles closed_dict = {} current = None while len(open_dict.keys()) > 0: lowest_value = None # Get the lowest value for key in open_dict.keys(): value = open_dict[key][0] if lowest_value is None: lowest_value = value current = key elif value < lowest_value: lowest_value = value current = key # Move current from the open dictionary to the closed one closed_dict[current] = open_dict[current] del open_dict[current] # If we're done here done = False deliver_tiles = [] # This code block makes sure the items are delivered to the tile next to the tile instead of diagonally if (g.in_map(current[0]+1, current[1]) and g.get_img(current[0]+1, current[1]).factory_input): deliver_tiles.append((current[0]+1, current[1])) elif (g.in_map(current[0]-1, current[1]) and g.get_img(current[0]-1, current[1]).factory_input): deliver_tiles.append((current[0]-1, current[1])) elif (g.in_map(current[0], current[1]+1) and g.get_img(current[0], current[1]+1).factory_input): deliver_tiles.append((current[0], current[1]+1)) elif (g.in_map(current[0], current[1]-1) and g.get_img(current[0], current[1]-1).factory_input): deliver_tiles.append((current[0], current[1]-1)) if deliver_tiles: for deliver_tile in deliver_tiles: try: for good_name in list(g.map[deliver_tile[0]][deliver_tile[1]].requests.keys()): if good_name == target_goods: if g.map[deliver_tile[0]][deliver_tile[1]].requests[target_goods] > 0: done = True g.map[deliver_tile[0]][deliver_tile[1]].requests[target_goods] -= 1 break # Break out of the goods for loop, not the entire while loop if done: break except AttributeError: pass if done is True: full_path = [] self.deliver_tile = deliver_tile try: while current != start: full_path.append(current) current = closed_dict[current][1] except: print(closed_dict) print(full_path) # Trying to understand the heisenbug! D: import pdb, sys e, m, tb = sys.exc_info() pdb.post_mortem(tb) full_path.reverse() self.path = full_path self.next_target_tile() self.home_tile = self.get_tile() return True # Move through all neighbours for i in range(current[0]-1, current[0]+2): for j in range(current[1]-1, current[1]+2): neighbour = (i, j) if 0 <= neighbour[0] < len(g.map) and 0 <= neighbour[1] < len(g.map[0]): # Get the G score, the cost to go back to the start if neighbour[0] == current[0]: if neighbour[1] == current[1]: continue else: g_score = closed_dict[current][0] + 10 else: if neighbour[1] == current[1]: g_score = closed_dict[current][0] + 10 else: # Make sure the pathfinding doesn't try to go through blocks diagonally. if (g.get_img(neighbour[0], current[1]).collides and g.get_img(current[0], neighbour[1]).collides): # If the blocks on either side of the diagonal walk is collidable, skip this one if neighbour not in closed_dict: closed_dict[neighbour] = None continue # If it can travel diagonally, give it a cost of the square root of two. g_score = closed_dict[current][0] + 14 if c.IMAGES[g.map[neighbour[0]][neighbour[1]].type].collides is False or neighbour == start: # Check if this neighbour can be added the the open_dict and do so if so if neighbour not in closed_dict.keys(): if neighbour not in open_dict.keys() or g_score < open_dict[neighbour][0]: open_dict[neighbour] = [g_score, current] else: if neighbour not in closed_dict.keys() and neighbour not in open_dict.keys(): closed_dict[neighbour] = None self.path = [] self.stop_moving() return False
def pathfind(self, end): """ Finds a path from start to end tiles using A* algorithm "start" and "end" are tuples with x and y coordinates of a tile returns True if it succeded and False if it couldn't find a path """ start = self.get_tile() # The open_dict and closed_dict both follow the format: # (key_x, key_y): [f, g, h, (came_from_x, came_from_y)] # F, G and H. G is the length to the start tile, H is the Heuristics # estimate of the distance to the end tile and F = G + H if type(start) != tuple or type(end) != tuple: raise Exception("Value passed to PathingEntity.pathfind() is not tuple") # The open dictionary is a dictionary keeping the currently checkable tiles # The starting tile is identified as not having a came_from-tuple. open_dict = {start: [self._heuristic_cost_estimate(start, end), 0, self._heuristic_cost_estimate(start, end)]} # The closed dictionary is a dicionary keeping the currently checked tiles closed_dict = {} current = None while len(open_dict.keys()) > 0: lowest_value = None # Get the lowest value for key in open_dict.keys(): value = open_dict[key][0] if lowest_value is None: lowest_value = value current = key else: if value < lowest_value: lowest_value = value current = key # Move current from the open dictionary to the closed one closed_dict[current] = open_dict[current] del open_dict[current] # If we're done here deliver_tile = None if g.get_img(*end).collides and current != end: # Find a tile next to the target tile to stand on if it collides if (g.in_map(current[0] + 1, current[1]) and (current[0] + 1, current[1]) == end): deliver_tile = (current[0] + 1, current[1]) elif (g.in_map(current[0] - 1, current[1]) and (current[0] - 1, current[1]) == end): deliver_tile = (current[0] - 1, current[1]) elif (g.in_map(current[0], current[1] + 1) and (current[0], current[1] + 1) == end): deliver_tile = (current[0], current[1] + 1) elif (g.in_map(current[0], current[1] - 1) and (current[0], current[1] - 1) == end): deliver_tile = (current[0], current[1] - 1) elif current == end: deliver_tile = current if deliver_tile is not None: # closed_dict[current] full_path = [] while current != start: full_path.append(current) current = closed_dict[current][3] full_path.reverse() self.deliver_tile = deliver_tile self.path = full_path self.next_target_tile() return True # # Old version # if done is True: # full_path = [] # self.deliver_tile = deliver_tile # while len(closed_dict[current]) > 1: # full_path.append(current) # current = closed_dict[current][1] # full_path.reverse() # self.path = full_path # self.next_target_tile() # self.home_tile = self.get_tile() # return # Move through all neighbours for i in range(current[0]-1, current[0]+2): for j in range(current[1]-1, current[1]+2): neighbour = (i, j) if 0 <= neighbour[0] < len(g.map) and 0 <= neighbour[1] < len(g.map[0]): # Get the G score, the cost to go back to the start if neighbour[0] == current[0]: if neighbour[1] == current[1]: continue else: g_score = closed_dict[current][1] + 10 else: if neighbour[1] == current[1]: g_score = closed_dict[current][1] + 10 else: # Make sure the pathfinding doesn't try to go through blocks diagonally. if (g.get_img(neighbour[0], current[1]).collides and g.get_img(current[0], neighbour[1]).collides): # If the blocks on either side of the diagonal walk is collidable, skip this one closed_dict[neighbour] = None continue # If it can travel diagonally, give it a cost of the square root of two. g_score = closed_dict[current][1] + 14 if c.IMAGES[g.map[neighbour[0]][neighbour[1]].type].collides is False: # Check if this neighbour can be added the the open_dict and do so if so if neighbour not in closed_dict.keys(): if neighbour not in open_dict.keys() or g_score < open_dict[neighbour][1]: h_score = self._heuristic_cost_estimate(neighbour, end) f_score = g_score + h_score open_dict[neighbour] = [f_score, g_score, h_score, current] else: closed_dict[neighbour] = None self.path = [] self.stop_moving() return False
def event_check(): global launcher_dir for event in pygame.event.get(): # Quit code if event.type == pgl.QUIT: sys.exit() if event.type == pgl.KEYDOWN or event.type == pgl.KEYUP: # Create beetle with (default) a if event.type == pgl.KEYDOWN and event.key == g.key_dict["spawn_beetle"][0]: g.entity_list.append(units.Beetle(g.special_entity_list["player"].x, g.special_entity_list["player"].y)) # Duplicate all beetles with (default) D elif event.type == pgl.KEYDOWN and event.key == g.key_dict["duplicate_beetles"][0]: # Make an empty list to temporarily store the added beetles, so no infinite loop appears temp_entity_list = [] for entity in g.entity_list: if type(entity) == units.Beetle: temp_entity_list.append(units.Beetle(entity.x, entity.y)) g.entity_list.extend(temp_entity_list) # Remove all beetles elif event.type == pgl.KEYDOWN and event.key == g.key_dict["remove_beetles"][0]: # Loop backwards through the g.entity_list for i in range(len(g.entity_list) - 1, -1, -1): if type(g.entity_list[i]) == units.Beetle: del g.entity_list[i] g.force_update = True # Key configuration elif event.type == pgl.KEYDOWN and event.key == c.CONFIG_KEYS_KEY: skip_cycle = g.force_update = True interface.key_reconfig() elif event.key == g.key_dict["move_up"][0]: _move(event, (0, -1)) elif event.key == g.key_dict["move_down"][0]: _move(event, (0, 1)) elif event.key == g.key_dict["move_left"][0]: _move(event, (-1, 0)) elif event.key == g.key_dict["move_right"][0]: _move(event, (1, 0)) elif event.key == g.key_dict["place_tile"][0]: g.special_entity_list["player"].placing_tile = _if_down(event.type) elif event.key == g.key_dict["remove_tile"][0]: g.special_entity_list["player"].removing_tile = _if_down(event.type) elif (event.key == g.key_dict["pick_up_tile"][0] and event.type == pgl.KEYDOWN): # This is handled in g.special_entity_list["player"].update() if not g.special_entity_list["player"].browsing_menu: g.special_entity_list["player"].toggle_grab = True elif (event.key == g.key_dict["build_menu"][0] and event.type == pgl.KEYUP): # Shows the build menu if g.tile_target_selection is None: g.force_update = True g.special_entity_list["player"].y_minus = g.special_entity_list["player"].y_plus =\ g.special_entity_list["player"].x_minus = g.special_entity_list["player"].x_plus = False if "menu" not in g.non_entity_list.keys(): g.non_entity_list["menu"] = interface.BuildMenu() g.special_entity_list["player"].browsing_menu = True else: del g.non_entity_list["menu"] g.special_entity_list["player"].browsing_menu = False elif (event.key == g.key_dict["select"][0] or event.key == g.key_dict["select2"][0] and event.type == pgl.KEYDOWN): # Selects the current menu item if "menu" in g.non_entity_list.keys(): if g.non_entity_list["menu"].select(): del g.non_entity_list["menu"] g.special_entity_list["player"].browsing_menu = False elif "tile_target" in g.special_entity_list: x, y = g.special_entity_list["player"].get_aim_tile() if type(g.map[x][y]) == tiles.LauncherTile: g.map[x][y].shoot_direction = launcher_dir g.tile_target_selection = None del g.special_entity_list["tile_target"] g.special_entity_list["player"].browsing_menu = False elif g.get_img(x, y).factory_output: good_names = [] for good in g.get_img(x, y).factory_output: good_names.append(good[0]) g.non_entity_list["menu"] = interface.TileTargetMenu(good_names) else: g.tile_target_selection = None del g.special_entity_list["tile_target"] g.special_entity_list["player"].browsing_menu = False g.force_update = True elif (event.key == g.key_dict["change_target"][0] and event.type == pgl.KEYDOWN): if not g.special_entity_list["player"].browsing_menu: if g.get_img(*g.special_entity_list["player"].get_aim_tile()).factory_output: g.special_entity_list["player"].browsing_menu = True g.special_entity_list["player"].y_minus = g.special_entity_list["player"].y_plus = \ g.special_entity_list["player"].x_minus = g.special_entity_list["player"].x_plus = False g.tile_target_selection = list(g.special_entity_list["player"].get_aim_tile()) x, y = g.special_entity_list["player"].get_aim_tile() g.special_entity_list["tile_target"] =\ entities.Entity(x*c.TILE_SIZE, y*c.TILE_SIZE, "tile_target_aim", 0, rotates=False, collides=False) if type(g.map[x][y]) == tiles.LauncherTile: launcher_dir = (0, 0) else: g.menu_selection = [0, 0] elif g.tile_target_selection is not None: g.special_entity_list["player"].browsing_menu = False g.tile_target_selection = None del g.special_entity_list["tile_target"] if "menu" in g.non_entity_list: del g.non_entity_list["menu"] g.force_update = True
def make_tile(tile_type, x, y, target=None): """ Function to create a tile of the appropriate type (Standard, Random, multi-tile and microtiles) Should be used instead of directly creating a specific tile unless it is certain which type is needed. "type" should be a string identifier from IMAGES. If it is a random tile, it should be the base form of the identifier (for example, "tree" and not "tree1" "x" and "y" are the indices of the tile in the "g.map" array "target" should be a tuple of coordinates in the tile array if the tile being created is a pointer. It should be left empty if the tile isn't a multi-tile pointer. """ during_generation = False # Check if where you're placing the tile is subject to a special tile. if g.map[x][y]: if c.SPECIAL_PLACE_TILES.__contains__(tile_type + "+" + str(g.map[x][y].type)): return make_tile(c.SPECIAL_PLACE_TILES[tile_type + "+" + str(g.map[x][y].type)], x, y) else: # If the tile didn't exist before, the entire map is currently being generated during_generation = True # If it is a multi-tile if c.IMAGES[tile_type].multi_tile is not None: width, height = c.IMAGES[tile_type].multi_tile if not area_is_free(x, y, width, height): raise AreaNotFreeException("The area at x " + x + ", y " + y + ", with the width " + width + " and the height " + height + " was not placeable. Please check the area " + "before attempting to create a multi-tile.") # Create pointers for i in range(x, x + width): for j in range(y, y + height): # If it's the top-left tile, skip it if x == i and y == j: continue # On all others, make pointers if c.IMAGES[tile_type].collides: make_tile("collide_pointer", i, j, (x, y)) else: make_tile("pointer", i, j, (x, y)) tile = MultiTileHead(tile_type, x, y, width, height) else: # Remove all robots if it's a robot sending factory tile. if g.map[x][y] is not None and g.get_img(x, y).factory_output and type(g.map[x][y]) is not LauncherTile: for robot in g.map[x][y].robots: if type(robot) is not int: robot.delete = True robot.return_request() # Check if target was specified. If so, this tile is a pointer. if target is not None: tile = MultiTilePointer(tile_type, x, y, *target) elif tile_type == "launcher": tile = LauncherTile(tile_type, x, y) elif c.IMAGES[tile_type].factory_input or c.IMAGES[tile_type].factory_output: tile = FactoryTile(tile_type, x, y) elif c.IMAGES[tile_type].microtiles: tile = MicroTile(tile_type, x, y) else: tile = Tile(tile_type, x, y) # Change and update the map g.map[x][y] = tile g.update_map = True # Make sure the player doesn't have to move to update to remove a newly placed package if "player" in g.special_entity_list: if g.special_entity_list["player"].get_aim_tile() == (x, y): g.special_entity_list["player"].update_aim_tile = True if not during_generation: # Make sure microtiles update for relative_x in range(-1, 2): for relative_y in range(-1, 2): if type(g.map[x + relative_x][y + relative_y]) == MicroTile: g.map[x + relative_x][y + relative_y].update_microtile = True return tile
def send_goods(self): """ Sends its goods with a pathfinding robot to the nearest applicable factory if the corresponding robot is "home", that is, not outside the building. Should be called about every tick """ i = -1 for good in c.IMAGES[self.type].factory_output: i += 1 if good: good_name, good_amount = good if c.IMAGES[self.type].factory_input: if not (good_name in self.inventory and self.inventory[good_name] > 0): continue # Is the robot home? if len(self.robots) > i: if type(self.robots[i]) is int and self.robots[i] >= 0: self.robots[i] -= 1 # If it's zero, all the below code happens. if self.robots[i] != 0: continue else: self.robots.append(c.ROBOT_RETRY_TIME) robot = units.Robot(self.x * c.TILE_SIZE, self.y * c.TILE_SIZE, c.GOODS[good_name][0], c.ROBOT_MOVEMENT_SPEED) # used_last_path = False # # Use last path # if i in self.last_delivery_tiles and g.get_img(*self.last_delivery_tiles[i]).factory_input: # if (good_name in self.good_targets and # self.good_targets[good_name] == self.last_delivery_tiles[i]): # # Check last path # for tile in self.last_paths[i]: # if g.get_img(*tile).collides: # used_last_path = True # break # # Valid path # else: # robot.path = self.last_paths[i] # robot.deliver_tile = self.last_delivery_tiles[i] # robot.next_target_tile() # print("Used last path") # if used_last_path is False: # Straight pathfind can_recieve = False if good_name in self.good_targets: for reciever_good in g.get_img(*self.good_targets[good_name]).factory_input: if reciever_good[0] == good_name: can_recieve = True if can_recieve: if robot.pathfind(self.good_targets[good_name], good_name): robot.home_tile = (self.x, self.y) else: can_recieve = False # Circular pathfind if not can_recieve and not robot.goods_pathfind(good_name): robot.delete = True self.robots[i] = c.ROBOT_RETRY_TIME continue # Save the path self.last_paths[i] = robot.path self.last_delivery_tiles[i] = robot.deliver_tile # If any of the pathfindings work self.robots[i] = robot robot.number = i robot.goods = good_name if c.IMAGES[self.type].factory_input: self.inventory[good_name] -= 1