diff --git a/main.py b/main.py index 6a3ed43c5b838bc873135fd59571f85a9dc5fd46..b9cb26cbacb69021528bfde9b2d57da53910b2ad 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,7 @@ import sdl2.ext # User Imports. from src.entities import GuiCore, Roomba, TileSet from src.logging import init_logging -from src.misc import DataManager, handle_key_press, handle_mouse_click +from src.misc import calc_trash_distances, calc_traveling_salesman, DataManager, handle_key_press, handle_mouse_click from src.systems import AISystem, MovementSystem, SoftwareRendererSystem @@ -23,6 +23,8 @@ RESOURCES = sdl2.ext.Resources(__file__, './src/images/') # Initialize window width/height. WINDOW_WIDTH = 640 WINDOW_HEIGHT = 480 +# WINDOW_WIDTH = 400 +# WINDOW_HEIGHT = 200 WINDOW_WIDTH_MIN = 400 WINDOW_HEIGHT_MIN = 200 @@ -52,7 +54,7 @@ def main(): movement = MovementSystem(data_manager, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT) # Add subsystems to world manager. - # world.add_system(ai) + world.add_system(ai) world.add_system(movement) world.add_system(sprite_renderer) @@ -220,6 +222,10 @@ def initialize_data(): # Initialize GUI object data. data_manager.gui = GuiCore(data_manager) + # Calculate path distances for initial setup. + data_manager.ideal_trash_paths = calc_trash_distances(data_manager) + calc_traveling_salesman(data_manager) + # Return generated window object. return data_manager diff --git a/src/entities/object_entities.py b/src/entities/object_entities.py index 6bfac7ddcd55504a925a2b483d400e99aeeff4a1..d75580ddb0066a808561d22134656e361d7187cb 100644 --- a/src/entities/object_entities.py +++ b/src/entities/object_entities.py @@ -277,7 +277,7 @@ class TileSet: # Ensure all paths are accessible by roomba. self.tiles[0][0].walls.bipartite_color_validation() - # Recalculate trash distances for new wall setup. + # Recalculate path distances for new wall setup. self.data_manager.ideal_trash_paths = calc_trash_distances(self.data_manager) calc_traveling_salesman(self.data_manager) @@ -302,8 +302,9 @@ class TileSet: if self.tiles[row_index][col_index].trashpile.exists: self.tiles[row_index][col_index].trashpile.clean() - # Recalculate trash distances for new trash pile setup. + # Recalculate path distances for new trash pile setup. self.data_manager.ideal_trash_paths = calc_trash_distances(self.data_manager) + calc_traveling_salesman(self.data_manager) class Trash(sdl2.ext.Entity): diff --git a/src/misc.py b/src/misc.py index e5153482a91d1fa757c68038b80004796e3991cf..ff57c5553c64519b6ba4f2966404819584435b0b 100644 --- a/src/misc.py +++ b/src/misc.py @@ -48,6 +48,7 @@ class DataManager: self.ai_active = False self.roomba_vision = 1 self.ideal_trash_paths = None + self.ideal_overall_path = None self.graph = networkx.Graph() self.graph.data = { 'trash_tiles': [] @@ -164,8 +165,9 @@ def handle_mouse_click(data_manager, button_state, pos_x, pos_y): logger.info(' Decrementing tile walls.') tile.walls.decrement_wall_state() - # Recalculate trash distances for new tile wall setup. + # Recalculate path distances for new tile wall setup. data_manager.ideal_trash_paths = calc_trash_distances(data_manager) + calc_traveling_salesman(data_manager) # endregion GUI Logic Functions @@ -663,8 +665,6 @@ def calc_traveling_salesman(data_manager, debug=False): Calculates the approximately-ideal overall path to visit all trash tiles. :param data_manager: Data manager data structure. Consolidates useful program data to one location. """ - debug = True - # Clear all debug entities. clear_debug_entities(data_manager) @@ -673,10 +673,10 @@ def calc_traveling_salesman(data_manager, debug=False): trash_tile_set = data_manager.graph.data['trash_tiles'] trash_paths = data_manager.ideal_trash_paths - print('\n\n\n\n') - print(' ==== TRAVELING SALESMAN ===== ') - print('\n') - print('trash_paths: {0}'.format(trash_paths)) + logger.info('\n\n\n\n') + logger.info(' ==== TRAVELING SALESMAN ===== ') + logger.info('\n') + logger.info('trash_paths: {0}'.format(trash_paths)) # Initialize path by just going to trash tiles in original ordering. curr_total_dist = 999999 @@ -773,6 +773,9 @@ def calc_traveling_salesman(data_manager, debug=False): calculated_path['ordering'][conn_1_index_1] = conn_2_id_1 calculated_path['ordering'][conn_2_index_1] = conn_1_id_1 + # Save found path. + data_manager.ideal_overall_path = calculated_path + # Optionally display debug tile sprites. if debug: from src.entities.object_entities import DebugTile diff --git a/src/systems.py b/src/systems.py index 6558fb7c0964c944536a051e53db96d3b0eaade0..8ff392c102210f471d95b491e40f286f7962be94 100644 --- a/src/systems.py +++ b/src/systems.py @@ -11,6 +11,7 @@ from abc import ABC # User Imports. from src.entities.system_entities import AI, Movement, SearchOptimalDistance from src.logging import init_logging +from src.misc import calc_trash_distances, calc_traveling_salesman, get_tile_coord_from_id # Initialize logger. @@ -196,6 +197,10 @@ class AbstractMovementSystem(ABC): if roomba_location[0] == tile_x and roomba_location[1] == tile_y and curr_tile.trashpile.exists: curr_tile.trashpile.clean() + # Recalculate path distances for new roomba location. + self.data_manager.ideal_trash_paths = calc_trash_distances(self.data_manager) + calc_traveling_salesman(self.data_manager) + class MovementSystem(sdl2.ext.Applicator, AbstractMovementSystem): """ @@ -398,9 +403,50 @@ class AISystem(sdl2.ext.Applicator, AbstractMovementSystem): self.prev_direction = prev_direction def move_full_sight(self, sprite): - """""" - # Move roomba. - self.move_east(sprite) + """ + Move roomba with "full sight" setting. + + Assumes some "outside entity" knows what the full environment setup is, and is feeding the roomba this + information. Roomba intelligently attempts to take the "most efficient path" to get to all trash piles. + :param sprite: Roomba sprite entity. + """ + # Get first set in "calculated ideal path". + end_tile_group_id = self.data_manager.ideal_overall_path['ordering'][1] + path_set = self.data_manager.ideal_trash_paths['roomba'][end_tile_group_id] + print('path_set: {0}'.format(path_set)) + + # Get first tile in path set. + curr_tile_id = path_set[0] + desired_next_tile_id = path_set[1] + curr_tile_x, curr_tile_y = get_tile_coord_from_id(curr_tile_id) + desired_tile_x, desired_tile_y = get_tile_coord_from_id(desired_next_tile_id) + + print('curr_tile: ({0}, {1})'.format(curr_tile_x, curr_tile_y)) + print('desired_tile: ({0}, {1})'.format(desired_tile_x, desired_tile_y)) + + # Determine which direction we move, in order to reach desired tile. + if curr_tile_x != desired_tile_x: + # Moving east/west. + if curr_tile_x < desired_tile_x: + # Move east. + self.move_east(sprite) + self.prev_direction = 'east' + else: + # Move west. + self.move_west(sprite) + self.prev_direction = 'west' + elif curr_tile_y != desired_tile_y: + # Moving north/south. + if curr_tile_y < desired_tile_y: + # Move south. + self.move_south(sprite) + self.prev_direction = 'south' + else: + # Move north. + self.move_north(sprite) + self.prev_direction = 'north' + else: + raise RuntimeError('Unable to determine where to move.') def move_limited_vision(self, sprite): """"""