From f5e4010794684d076fa7a0722a8835e1947d7a4f Mon Sep 17 00:00:00 2001 From: Brandon Rodriguez <brodriguez8774@gmail.com> Date: Tue, 2 Nov 2021 19:15:32 -0400 Subject: [PATCH] Implement basic "bump sensor" movement system --- src/misc.py | 2 +- src/systems.py | 265 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 196 insertions(+), 71 deletions(-) diff --git a/src/misc.py b/src/misc.py index 8d2debf..e515348 100644 --- a/src/misc.py +++ b/src/misc.py @@ -46,7 +46,7 @@ class DataManager: self.tile_set = None self.roomba = None self.ai_active = False - self.roomba_vision_range = 1 + self.roomba_vision = 1 self.ideal_trash_paths = None self.graph = networkx.Graph() self.graph.data = { diff --git a/src/systems.py b/src/systems.py index 97b3b87..6558fb7 100644 --- a/src/systems.py +++ b/src/systems.py @@ -4,6 +4,7 @@ These are subsystems added to the "world manager" object, that basically control """ # System Imports. +import random import sdl2.ext from abc import ABC @@ -38,18 +39,26 @@ class AbstractMovementSystem(ABC): :param sprite: Entity sprite data. :return: True on movement success | False otherwise. """ - # Set new sprite location, for movement attempt. orig_location = sprite.y - sprite.y -= 50 + orig_x, orig_y = sprite.tile - # Verify that sprite's new location is within north (upper) bounds. - # We use the more restrictive of either "the provided limit class limit" or "defined window limit". - north_max = max(self.min_x, self.data_manager.tile_data['max_pixel_north']) - sprite.y = max(north_max, sprite.y) + # Get corresponding tile wall data. + tile_walls = self.data_manager.tile_set.tiles[orig_y][orig_x].walls + + # Check if direction is free of obstructions. + if not tile_walls.has_wall_north: + # Set new sprite location, for movement attempt. + sprite.y -= 50 + + # Verify that sprite's new location is within north (upper) bounds. + # We use the more restrictive of either "the provided limit class limit" or "defined window limit". + north_max = max(self.min_x, self.data_manager.tile_data['max_pixel_north']) + sprite.y = max(north_max, sprite.y) # Check if movement occurred. if orig_location != sprite.y: # Call general "movement" logic, for entity having moved in any direction at all. + logger.debug('Moved north.') self._handle_move(sprite) # Movement successful. @@ -64,23 +73,31 @@ class AbstractMovementSystem(ABC): :param sprite: Entity sprite data. :return: True on movement success | False otherwise. """ - # Set new sprite location, for movement attempt. orig_location = sprite.x - sprite.x += 50 + orig_x, orig_y = sprite.tile + + # Get corresponding tile wall data. + tile_walls = self.data_manager.tile_set.tiles[orig_y][orig_x].walls + + # Check if direction is free of obstructions. + if not tile_walls.has_wall_east: + # Set new sprite location, for movement attempt. + sprite.x += 50 - # Get sprite size in pixels. - sprite_width, sprite_height = sprite.size + # Get sprite size in pixels. + sprite_width, sprite_height = sprite.size - # Verify that sprite's new location is within east (right) bounds. - # We use the more restrictive of either "the provided limit class limit" or "defined window limit". - sprite_right = sprite.x + sprite_width - east_max = min(self.max_x, self.data_manager.tile_data['max_pixel_east']) - if sprite_right > east_max: - sprite.x = east_max - sprite_width + # Verify that sprite's new location is within east (right) bounds. + # We use the more restrictive of either "the provided limit class limit" or "defined window limit". + sprite_right = sprite.x + sprite_width + east_max = min(self.max_x, self.data_manager.tile_data['max_pixel_east']) + if sprite_right > east_max: + sprite.x = east_max - sprite_width # Check if movement occurred. if orig_location != sprite.x: # Call general "movement" logic, for entity having moved in any direction at all. + logger.debug('Moved east.') self._handle_move(sprite) # Movement successful. @@ -95,23 +112,31 @@ class AbstractMovementSystem(ABC): :param sprite: Entity sprite data. :return: True on movement success | False otherwise. """ - # Set new sprite location, for movement attempt. orig_location = sprite.y - sprite.y += 50 + orig_x, orig_y = sprite.tile - # Get sprite size in pixels. - sprite_width, sprite_height = sprite.size + # Get corresponding tile wall data. + tile_walls = self.data_manager.tile_set.tiles[orig_y][orig_x].walls - # Verify that sprite's new location is within south (bottom) bounds. - # We use the more restrictive of either "the provided limit class limit" or "defined window limit". - sprite_lower = sprite.y + sprite_height - south_max = min(self.max_y, self.data_manager.tile_data['max_pixel_south']) - if sprite_lower > south_max: - sprite.y = south_max - sprite_height + # Check if direction is free of obstructions. + if not tile_walls.has_wall_south: + # Set new sprite location, for movement attempt. + sprite.y += 50 + + # Get sprite size in pixels. + sprite_width, sprite_height = sprite.size + + # Verify that sprite's new location is within south (bottom) bounds. + # We use the more restrictive of either "the provided limit class limit" or "defined window limit". + sprite_lower = sprite.y + sprite_height + south_max = min(self.max_y, self.data_manager.tile_data['max_pixel_south']) + if sprite_lower > south_max: + sprite.y = south_max - sprite_height # Check if movement occurred. if orig_location != sprite.y: # Call general "movement" logic, for entity having moved in any direction at all. + logger.debug('Moved south.') self._handle_move(sprite) # Movement successful. @@ -126,18 +151,26 @@ class AbstractMovementSystem(ABC): :param sprite: Entity sprite data. :return: True on movement success | False otherwise. """ - # Set new sprite location, for movement attempt. orig_location = sprite.x - sprite.x -= 50 + orig_x, orig_y = sprite.tile + + # Get corresponding tile wall data. + tile_walls = self.data_manager.tile_set.tiles[orig_y][orig_x].walls + + # Check if direction is free of obstructions. + if not tile_walls.has_wall_west: + # Set new sprite location, for movement attempt. + sprite.x -= 50 - # Verify that sprite's new location is within west (left) bounds. - # We use the more restrictive of either "the provided limit class limit" or "defined window limit". - west_max = max(self.min_x, self.data_manager.tile_data['max_pixel_west']) - sprite.x = max(west_max, sprite.x) + # Verify that sprite's new location is within west (left) bounds. + # We use the more restrictive of either "the provided limit class limit" or "defined window limit". + west_max = max(self.min_x, self.data_manager.tile_data['max_pixel_west']) + sprite.x = max(west_max, sprite.x) # Check if movement occurred. if orig_location != sprite.x: # Call general "movement" logic, for entity having moved in any direction at all. + logger.debug('Moved west.') self._handle_move(sprite) # Movement successful. @@ -235,6 +268,7 @@ class AISystem(sdl2.ext.Applicator, AbstractMovementSystem): self.min_y = min_y self.max_x = max_x self.max_y = max_y + self.prev_direction = 'north' def process(self, world, componenttypes): """ @@ -243,41 +277,132 @@ class AISystem(sdl2.ext.Applicator, AbstractMovementSystem): :param componenttypes: Tuple of relevant tuples for system. """ for ai_tick, sprite in componenttypes: - # print('ai_tick: {0}'.format(ai_tick)) - # print('sprite: {0}'.format(sprite)) - - if ai_tick.active and ai_tick.check_counter(): - print('\n') - print('ai_tick: {0}'.format(ai_tick)) - print('sprite: {0}'.format(sprite)) - - # Determine optimal distance to reach tiles, excluding inclusion of walls. - ai_tick.search_optimal_distance() - - # # Move roomba. - # self.move_east(sprite) - - # for ai_tick, search_tick, sprite in componenttypes: - # - # print('ai_tick: {0}'.format(ai_tick)) - # print('search_tick: {0}'.format(search_tick)) - # print('sprite: {0}'.format(sprite)) - # - # if ai_tick.active and ai_tick.check_counter(): - # # Move roomba. - # self.move_east(sprite) - - # def process(self, world, componenttypes): - # """ - # System handling during a single world processing tick. - # :param world: World instance calling the process tick. - # :param componenttypes: Tuple of relevant tuples for system. - # """ - # for ai_tick, search, sprite in componenttypes: - # - # if ai_tick.active and ai_tick.check_counter(): - # # Calculate optimal distance. - # search.search_optimal_distance() - # - # # Move roomba. - # self.move_east(sprite) + + # Proceed if tick rate is met and ai is set to be active.. + if ai_tick.active and ai_tick.check_counter() and self.data_manager.ai_active: + # AI is active and tick event is occurring. Move roomba, based on current setting. + + # Check vision range. + if self.data_manager.roomba_vision == 0: + # Roomba has no vision range. Acting as bump sensor. + logger.info('Moving with "bump sensor".') + self.move_bump_sensor(sprite) + + elif self.data_manager.roomba_vision == -1: + # Roomba has full tile sight. + logger.info('Moving with "full tile sight".') + self.move_full_sight(sprite) + + else: + # Roomba has limited tile range. + logger.info('Moving with "limited tile range".') + self.move_limited_vision(sprite) + + def move_bump_sensor(self, sprite): + """ + Move roomba with "bump sensor" setting. + + Roomba will attempt to "continue in the same direction" until it hits a wall. + At such a point, it will choose a random non-backtracking direction and attempt that. + Only backtracks when no other valid options exist. + :param sprite: Roomba sprite entity. + """ + orig_x, orig_y = sprite.tile + logger.debug('current_location: ({0}, {1})'.format(orig_x, orig_y)) + + # First check previous direction. Attempt to continue going that way, if possible. + has_moved = False + if self.prev_direction == 'north': + # Attempt to move roomba. + logger.debug('Attempting to continue north.') + has_moved = self.move_north(sprite) + elif self.prev_direction == 'east': + # Attempt to move roomba. + logger.debug('Attempting to continue east.') + has_moved = self.move_east(sprite) + elif self.prev_direction == 'south': + # Attempt to move roomba. + logger.debug('Attempting to continue south.') + has_moved = self.move_south(sprite) + elif self.prev_direction == 'west': + # Attempt to move roomba. + logger.debug('Attempting to continue west.') + has_moved = self.move_west(sprite) + + # Check if roomba has moved. If not, a barrier was in the way. Choose a random direction. + if not has_moved: + # Failed to move. Get new direction. + prev_direction = self.prev_direction + viable_directions = ['north', 'east', 'south', 'west'] + logger.debug('prev_direction: {0}'.format(prev_direction)) + logger.debug('viable_directions: {0}'.format(viable_directions)) + if self.prev_direction in ['north', 'south']: + viable_directions.remove('north') + viable_directions.remove('south') + elif self.prev_direction in ['east', 'west']: + viable_directions.remove('east') + viable_directions.remove('west') + + # Attempt one of the other directions. + while not has_moved and len(viable_directions) > 0: + logger.debug('viable_directions: {0}'.format(viable_directions)) + new_dir_index = random.randint(0, len(viable_directions) - 1) + new_dir = viable_directions.pop(new_dir_index) + logger.debug('new_dir: {0}'.format(new_dir)) + + if new_dir == 'north': + # Attempt to move roomba. + has_moved = self.move_north(sprite) + prev_direction = 'north' + elif new_dir == 'east': + # Attempt to move roomba. + has_moved = self.move_east(sprite) + prev_direction = 'east' + elif new_dir == 'south': + # Attempt to move roomba. + has_moved = self.move_south(sprite) + prev_direction = 'south' + elif new_dir == 'west': + # Attempt to move roomba. + has_moved = self.move_west(sprite) + prev_direction = 'west' + + # Check one last time if roomba has moved. + if not has_moved: + logger.debug('Still has not moved, backtracking.') + # Roomba still has not moved. That means three walls are blocked off on tile, and the only way + # to move is by backtracking. + if self.prev_direction == 'north': + # Attempt to move roomba. + has_moved = self.move_south(sprite) + prev_direction = 'south' + elif self.prev_direction == 'east': + # Attempt to move roomba. + has_moved = self.move_west(sprite) + prev_direction = 'west' + elif self.prev_direction == 'south': + # Attempt to move roomba. + has_moved = self.move_north(sprite) + prev_direction = 'north' + elif self.prev_direction == 'west': + # Attempt to move roomba. + has_moved = self.move_east(sprite) + prev_direction = 'east' + + # Final validation that roomba has moved. If not, then logic error occurred. + if not has_moved: + raise RuntimeError('Roomba failed to move. Logic error occurred.') + + # Update for new movement direction. + logger.debug('Setting "prev_direction" to {0}'.format(prev_direction)) + self.prev_direction = prev_direction + + def move_full_sight(self, sprite): + """""" + # Move roomba. + self.move_east(sprite) + + def move_limited_vision(self, sprite): + """""" + # Move roomba. + self.move_east(sprite) -- GitLab