From a82a1c883da8db626c248696225ad0cd694f2299 Mon Sep 17 00:00:00 2001 From: Brandon Rodriguez <brodriguez8774@gmail.com> Date: Tue, 2 Nov 2021 09:52:51 -0400 Subject: [PATCH] Implement initial traveling salesman algorithm Based on the loose concept as described in class. Doesn't seem to be the most optimal though. Might need to look up a proper algorithm. --- src/entities/object_entities.py | 3 +- src/misc.py | 216 +++++++++++++++++++++++++++++++- 2 files changed, 217 insertions(+), 2 deletions(-) diff --git a/src/entities/object_entities.py b/src/entities/object_entities.py index 6b1d339..6bfac7d 100644 --- a/src/entities/object_entities.py +++ b/src/entities/object_entities.py @@ -10,7 +10,7 @@ import sdl2.ext # User Imports. from .system_entities import AI, Movement, TrashPile, Search, Walls from src.logging import init_logging -from src.misc import calc_trash_distances +from src.misc import calc_trash_distances, calc_traveling_salesman # Initialize logger. @@ -279,6 +279,7 @@ class TileSet: # Recalculate trash distances for new wall setup. self.data_manager.ideal_trash_paths = calc_trash_distances(self.data_manager) + calc_traveling_salesman(self.data_manager) def randomize_trash(self): """ diff --git a/src/misc.py b/src/misc.py index 945885e..9916d1f 100644 --- a/src/misc.py +++ b/src/misc.py @@ -12,7 +12,7 @@ Sprite Depth Values (0 is lowest. Higher values will display ontop of lower ones # System Imports. import sdl2.ext -import networkx +import networkx, random # User Imports. from src.logging import init_logging @@ -656,6 +656,220 @@ def calc_trash_distances(data_manager): return _calc_trash_distances() +def calc_traveling_salesman(data_manager, debug=False): + """ + Calculates the approximately-ideal overall path to visit all trash tiles. + :param data_manager: + :return: + """ + debug = True + + # Clear all debug entities. + clear_debug_entities(data_manager) + + roomba_x, roomba_y = data_manager.roomba.sprite.tile + roomba_tile_id = get_id_from_coord(roomba_x, roomba_y) + 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)) + + # Initialize path by just going to trash tiles in original ordering. + calculated_path = { + 'ordering': [roomba_tile_id], + 'total_cost': 999999, + } + start_tile_id = None + end_tile_id = None + for tile_id in trash_tile_set: + start_tile_id = end_tile_id + end_tile_id = tile_id + + # Add first trash tile. + if not start_tile_id: + calculated_path['ordering'].append(end_tile_id) + + # Add all other trash tiles after first one. + if start_tile_id and end_tile_id: + calculated_path['ordering'].append(end_tile_id) + + print('') + print('calculated_paths: {0}'.format(calculated_path['ordering'])) + print('') + + # Run ( "length of trash tile set" * 10 ) iterations. + # For each, we randomly grab two sets of connected points, then swap them with each other to see if improvement + # occurs. If swap leads to overall distance improvement, we save. Otherwise revert and try next iteration. + for index_counter in range(len(trash_tile_set) * 20): + # Grab first set of points. + conn_1_index_0 = random.randint(1, len(calculated_path['ordering']) - 2) + conn_1_index_1 = conn_1_index_0 + 1 + + # Grab second set of points. + conn_2_index_0 = random.randint(1, len(calculated_path['ordering']) - 2) + conn_2_index_1 = conn_2_index_0 + 1 + temp_counter = 0 + # Make sure sets of point are actually different. + while ( + temp_counter < 10 and # If it fails 10 times, then we probably don't have enough indexes to actually swap. + ( + conn_2_index_0 in [conn_1_index_0, conn_1_index_1] or + conn_2_index_1 == [conn_1_index_0, conn_1_index_1] + ) + ): + conn_2_index_0 = random.randint(1, len(calculated_path['ordering']) - 2) + conn_2_index_1 = conn_2_index_0 + 1 + + # conn_1_index_2 = conn_1_index_1 + 1 + # conn_2_index_2 = conn_2_index_1 + 1 + + print('conn_1_index_0: {0}'.format(conn_1_index_0)) + print('conn_1_index_1: {0}'.format(conn_1_index_1)) + # print('conn_1_index_2: {0}'.format(conn_1_index_2)) + print('conn_2_index_0: {0}'.format(conn_2_index_0)) + print('conn_2_index_1: {0}'.format(conn_2_index_1)) + # print('conn_2_index_2: {0}'.format(conn_2_index_2)) + + # Get respective id's for selected indexes. + conn_1_id_0 = calculated_path['ordering'][conn_1_index_0] + conn_1_id_1 = calculated_path['ordering'][conn_1_index_1] + # conn_1_id_2 = None + # if conn_1_index_2 < len(calculated_path['ordering']): + # conn_1_id_2 = calculated_path['ordering'][conn_1_index_2] + conn_2_id_0 = calculated_path['ordering'][conn_2_index_0] + conn_2_id_1 = calculated_path['ordering'][conn_2_index_1] + # conn_2_id_2 = None + # if conn_2_index_2 < len(calculated_path['ordering']): + # conn_2_id_2 = calculated_path['ordering'][conn_2_index_2] + + # Verify swapping won't result in trying to travel from a tile to itself. + if conn_1_id_0 == conn_2_id_1 or conn_2_id_0 == conn_1_id_1: + # Would lead to bad path. Skip current iteration. + continue + + # Calculate distance travelled for current selection. + print('') + print('conn_1_id_0: {0}'.format(conn_1_id_0)) + print('conn_1_id_1: {0}'.format(conn_1_id_1)) + # print('conn_1_id_2: {0}'.format(conn_1_id_2)) + print('conn_2_id_0: {0}'.format(conn_2_id_0)) + print('conn_2_id_1: {0}'.format(conn_2_id_1)) + # print('conn_2_id_2: {0}'.format(conn_2_id_2)) + print('trash_paths[conn_1_id_0]: {0}'.format(trash_paths[conn_1_id_0])) + print('trash_paths[conn_1_id_0][conn_1_id_1]: {0}'.format(trash_paths[conn_1_id_0][conn_1_id_1])) + conn_1_dist = len(trash_paths[conn_1_id_0][conn_1_id_1]) - 1 + # conn_1_dist_2 = 0 + # if conn_1_id_2: + # print('trash_paths[conn_1_id_1][conn_1_id_2]: {0}'.format(trash_paths[conn_1_id_1][conn_1_id_2])) + # conn_1_dist_2 = len(trash_paths[conn_1_id_1][conn_1_id_2]) - 1 + # conn_1_dist = conn_1_dist_1 + conn_1_dist_2 + print('trash_paths[conn_2_id_0]: {0}'.format(trash_paths[conn_2_id_0])) + print('trash_paths[conn_2_id_0][conn_2_id_1]: {0}'.format(trash_paths[conn_2_id_0][conn_2_id_1])) + conn_2_dist = len(trash_paths[conn_2_id_0][conn_2_id_1]) - 1 + # conn_2_dist_2 = 0 + # if conn_2_id_2: + # print('trash_paths[conn_2_id_1][conn_2_id_2]: {0}'.format(trash_paths[conn_2_id_1][conn_2_id_2])) + # conn_2_dist_2 = len(trash_paths[conn_2_id_1][conn_2_id_2]) - 1 + # conn_2_dist = conn_2_dist_1 + conn_2_dist_2 + orig_total_dist = conn_1_dist + conn_2_dist + + # print('conn_1_dist_1: {0}'.format(conn_1_dist_1)) + # print('conn_1_dist_2: {0}'.format(conn_1_dist_2)) + print('conn_1_dist: {0}'.format(conn_1_dist)) + # print('conn_2_dist_1: {0}'.format(conn_2_dist_1)) + # print('conn_2_dist_2: {0}'.format(conn_2_dist_2)) + print('conn_2_dist: {0}'.format(conn_2_dist)) + + # Swap and recalculate distance. + swapped_1_id_1 = conn_2_id_1 + swapped_2_id_1 = conn_1_id_1 + print('') + print('conn_1_id_0: {0}'.format(conn_1_id_0)) + print('swapped_1_id_1: {0}'.format(swapped_1_id_1)) + print('conn_2_id_0: {0}'.format(conn_2_id_0)) + print('swapped_2_id_1: {0}'.format(swapped_2_id_1)) + print('trash_paths[conn_1_id_0]: {0}'.format(trash_paths[conn_1_id_0])) + print('trash_paths[conn_1_id_0][swapped_1_id_1]: {0}'.format(trash_paths[conn_1_id_0][swapped_1_id_1])) + swapped_1_dist = len(trash_paths[conn_1_id_0][swapped_1_id_1]) - 1 + # swapped_1_dist_2 = 0 + # if conn_1_id_2: + # print('trash_paths[swapped_1_id_1][conn_1_id_2]: {0}'.format(trash_paths[swapped_1_id_1][conn_1_id_2])) + # swapped_1_dist_2 = len(trash_paths[swapped_1_id_1][conn_1_id_2]) - 1 + # swapped_1_dist = swapped_1_dist_1 + swapped_1_dist_2 + print('trash_paths[conn_2_id_0]: {0}'.format(trash_paths[conn_2_id_0])) + print('trash_paths[conn_2_id_0][swapped_2_id_1]: {0}'.format(trash_paths[conn_2_id_0][swapped_2_id_1])) + swapped_2_dist = len(trash_paths[conn_2_id_0][swapped_2_id_1]) - 1 + # swapped_2_dist_2 = 0 + # if conn_2_id_2: + # print('trash_paths[swapped_2_id_1][conn_2_id_2]: {0}'.format(trash_paths[swapped_2_id_1][conn_2_id_2])) + # swapped_2_dist_2 = len(trash_paths[swapped_2_id_1][conn_2_id_2]) - 1 + # swapped_2_dist = swapped_2_dist_1 + swapped_2_dist_2 + + # print('swapped_1_dist_1: {0}'.format(swapped_1_dist_1)) + # print('swapped_1_dist_2: {0}'.format(swapped_1_dist_2)) + print('swapped_1_dist: {0}'.format(swapped_1_dist)) + # print('swapped_2_dist_1: {0}'.format(swapped_2_dist_1)) + # print('swapped_2_dist_2: {0}'.format(swapped_2_dist_2)) + print('swapped_2_dist: {0}'.format(swapped_2_dist)) + swapped_total_dist = swapped_1_dist + swapped_2_dist + + # Check if swapping sets will decrease overall distance travelled. + print('') + print('orig_total_dist: {0}'.format(orig_total_dist)) + print('swapped_total_dist: {0}'.format(swapped_total_dist)) + print('') + print('orig calculated_path[ordering]: {0}'.format(calculated_path['ordering'])) + if swapped_total_dist < orig_total_dist: + print('SWAPPING') + # conn_1_index_0 + # conn_1_index_1 + # conn_2_index_0 + # conn_2_index_1 + # if conn_1_id_0 == conn_2_id_1 or conn_2_id_0 == conn_1_id_1: + calculated_path['ordering'][conn_1_index_1] = conn_2_id_1 + calculated_path['ordering'][conn_2_index_1] = conn_1_id_1 + + print('') + print('new calculated_path[ordering]: {0}'.format(calculated_path['ordering'])) + + # Optionally display debug tile sprites. + if debug: + from src.entities.object_entities import DebugTile + + # Loop through all found tiles in final path. Display debug sprites for each. + start_tile_id = None + end_tile_id = None + for index in range(len(calculated_path['ordering'])): + start_tile_id = end_tile_id + end_tile_id = calculated_path['ordering'][index] + + # Only proceed if both id's are present (aka, skip first index). + if start_tile_id and end_tile_id: + + # Loop through all tiles in path connecting the given trash entities. + print('\n\n\ntrash_paths: {0}'.format(trash_paths)) + if start_tile_id == roomba_tile_id: + full_path = trash_paths['roomba'][end_tile_id] + else: + full_path = trash_paths[start_tile_id][end_tile_id] + for tile_id in full_path: + tile_x, tile_y = get_tile_coord_from_id(tile_id) + debug_tile_sprite = data_manager.sprite_factory.from_image( + RESOURCES.get_path('search_overlay.png') + ) + debug_entity = DebugTile( + data_manager.world, + debug_tile_sprite, + data_manager, + tile_x, + tile_y, + ) + data_manager.debug_entities.append(debug_entity) + + def clear_debug_entities(data_manager): """ Removes all debug entities, so that the screen does not become cluttered with redundant/overlapping debug info. -- GitLab