From ccefc6afbcb1013dbd2010d329ac19cf90246386 Mon Sep 17 00:00:00 2001 From: Mateusz Dokowicz Date: Thu, 20 Apr 2023 03:38:55 +0200 Subject: [PATCH] AI move from position A to B --- AI_brain/movement.py | 80 ++++++++++++++++++++++++++ config.ini | 3 +- domain/commands/vacuum_move_command.py | 11 +--- domain/world.py | 14 +++++ main.py | 44 +++++++++++++- 5 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 AI_brain/movement.py diff --git a/AI_brain/movement.py b/AI_brain/movement.py new file mode 100644 index 0000000..acd4063 --- /dev/null +++ b/AI_brain/movement.py @@ -0,0 +1,80 @@ +from domain.commands.vacuum_move_command import VacuumMoveCommand +from domain.world import World + + +class State: + def __init__(self, x, y): + self.x = x + self.y = y + + def __hash__(self): + return hash((self.x, self.y)) + + def __eq__(self, other): + return self.x == other.x and self.y == other.y + + +class StateGraphSearchBFS: + def __init__(self, world: World, start_state: State, goal_state: State): + self.start_state = start_state + self.goal_state = goal_state + self.visited = set() + self.parent = {} + self.actions = [] + self.path = [] + self.world = world + self.queue = [] + + def search(self): + self.queue.append(self.start_state) + self.visited.add(self.start_state) + while self.queue: + state = self.queue.pop(0) + if state == self.goal_state: + self.actions = self.get_actions() + self.path = self.get_path() + return True + for successor in self.successors(state): + if successor not in self.visited: + self.visited.add(successor) + self.parent[successor] = state + self.queue.append(successor) + return False + + def successors(self, state): + new_successors = [ + State(state.x + dx, state.y + dy) + for dx, dy in [(1, 0), (0, 1), (-1, 0), (0, -1)] + if self.world.accepted_move(state.x + dx, state.y + dy) + ] + + return new_successors + + def get_actions(self): + actions = [] + state = self.goal_state + while state != self.start_state: + parent_state = self.parent[state] + dx = state.x - parent_state.x + dy = state.y - parent_state.y + if dx == 1: + actions.append("RIGHT") + elif dx == -1: + actions.append("LEFT") + elif dy == 1: + actions.append("DOWN") + elif dy == -1: + actions.append("UP") + state = parent_state + actions.reverse() + return actions + + def get_path(self): + path = [] + state = self.goal_state + while state != self.start_state: + path.append((state.x, state.y)) + state = self.parent[state] + path.append((self.start_state.x, self.start_state.y)) + path.reverse() + return path diff --git a/config.ini b/config.ini index c1dc339..1b451b6 100644 --- a/config.ini +++ b/config.ini @@ -1,3 +1,4 @@ [APP] cat = False -movment = human #(human, movment) \ No newline at end of file +movement = robot +#accept: human, robot \ No newline at end of file diff --git a/domain/commands/vacuum_move_command.py b/domain/commands/vacuum_move_command.py index d150c2a..4707ca1 100644 --- a/domain/commands/vacuum_move_command.py +++ b/domain/commands/vacuum_move_command.py @@ -18,16 +18,7 @@ class VacuumMoveCommand(Command): def run(self): end_x = self.vacuum.x + self.dx end_y = self.vacuum.y + self.dy - - if ( - end_x > self.world.width - 1 - or end_y > self.world.height - 1 - or end_x < 0 - or end_y < 0 - ): - return - - if self.world.is_obstacle_at(end_x, end_y): + if not self.world.accepted_move(end_x, end_y): return if self.world.is_garbage_at(end_x, end_y): diff --git a/domain/world.py b/domain/world.py index d2b3f02..def2082 100644 --- a/domain/world.py +++ b/domain/world.py @@ -33,3 +33,17 @@ class World: def is_docking_station_at(self, x: int, y: int) -> bool: return bool(self.doc_station.x == x and self.doc_station.y == y) + + def accepted_move(self, checking_x, checking_y): + if ( + checking_x > self.width - 1 + or checking_y > self.height - 1 + or checking_x < 0 + or checking_y < 0 + ): + return False + + if self.is_obstacle_at(checking_x, checking_y): + return False + + return True diff --git a/main.py b/main.py index 6d80ee8..8da28a6 100644 --- a/main.py +++ b/main.py @@ -11,6 +11,7 @@ from domain.entities.vacuum import Vacuum from domain.entities.docking_station import Doc_Station from domain.world import World from view.renderer import Renderer +from AI_brain.movement import StateGraphSearchBFS, State config = configparser.ConfigParser() @@ -41,6 +42,43 @@ class Main: pygame.quit() + def run_robot(self): + self.renderer.render(self.world) + + start_state = State(self.world.vacuum.x, self.world.vacuum.y) + end_state = State(self.world.doc_station.x, self.world.doc_station.y) + + SGS_BFS = StateGraphSearchBFS(self.world, start_state, end_state) + if not SGS_BFS.search(): + print("No solution") + exit(0) + + SGS_BFS.actions.reverse() + while self.running: + self.renderer.render(self.world) + self.clock.tick(5) + if len(SGS_BFS.actions) > 0: + action_direction = SGS_BFS.actions.pop() + if action_direction == "UP": + self.commands.append( + VacuumMoveCommand(self.world, self.world.vacuum, (0, -1)) + ) + elif action_direction == "DOWN": + self.commands.append( + VacuumMoveCommand(self.world, self.world.vacuum, (0, 1)) + ) + elif action_direction == "LEFT": + self.commands.append( + VacuumMoveCommand(self.world, self.world.vacuum, (-1, 0)) + ) + elif action_direction == "RIGHT": + self.commands.append( + VacuumMoveCommand(self.world, self.world.vacuum, (1, 0)) + ) + self.update() + + pygame.quit() + def process_input(self): for event in pygame.event.get(): if event.type == pygame.QUIT: @@ -79,7 +117,6 @@ def generate_world(tiles_x: int, tiles_y: int) -> World: world.add_entity(Entity(temp_x, temp_y, "PEEL")) world.vacuum = Vacuum(1, 1) world.doc_station = Doc_Station(9, 8) - print(config.getboolean("APP", "cat")) if config.getboolean("APP", "cat"): world.cat = Cat(7, 8) world.add_entity(world.cat) @@ -93,4 +130,7 @@ def generate_world(tiles_x: int, tiles_y: int) -> World: if __name__ == "__main__": app = Main() - app.run() + if config["APP"]["movement"] == "human": + app.run() + elif config["APP"]["movement"] == "robot": + app.run_robot()