From d513223a6d08e03099a40baebc47bb805bf097ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klupie=C4=87?= Date: Mon, 19 Apr 2021 17:20:40 +0200 Subject: [PATCH] Add graph search --- survival/components/position_component.py | 7 +- survival/graph_search.py | 88 +++++++++++++++++++ survival/systems/input_system.py | 5 +- .../systems/pathfinding_movement_system.py | 50 +++++++---- 4 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 survival/graph_search.py diff --git a/survival/components/position_component.py b/survival/components/position_component.py index fc69965..2838d6f 100644 --- a/survival/components/position_component.py +++ b/survival/components/position_component.py @@ -1,13 +1,12 @@ from survival.enums import Direction -from survival.settings import DIRECTION_CHANGE_DELAY class PositionComponent: - def __init__(self, pos, grid_pos): + def __init__(self, pos, grid_pos, direction=Direction.DOWN): self.position = pos self.grid_position = grid_pos - self.direction = Direction.DOWN - self.direction_change_timer = DIRECTION_CHANGE_DELAY + self.direction = direction + self.direction_change_timer = 0 def rotate_left(self): return Direction.rotate_left(self.direction) diff --git a/survival/graph_search.py b/survival/graph_search.py new file mode 100644 index 0000000..92054e7 --- /dev/null +++ b/survival/graph_search.py @@ -0,0 +1,88 @@ +from enum import Enum + +from survival import GameMap +from survival.components.position_component import PositionComponent +from survival.enums import Direction + + +class Action(Enum): + ROTATE_LEFT = 0 + ROTATE_RIGHT = 1 + MOVE = 2 + + +class State: + def __init__(self, position, direction): + self.position = position + self.direction = direction + + +class Node: + def __init__(self, state: State, parent=None, action=None): + self.state = state + self.parent = parent + self.action = action + + +def get_moved_position(position, direction): + vector = Direction.get_vector(direction) + return position[0] + vector[0], position[1] + vector[1] + + +def get_states(state: State, game_map: GameMap): + states = list() + + states.append((Action.ROTATE_LEFT, State(state.position, state.direction.rotate_left(state.direction)))) + states.append((Action.ROTATE_RIGHT, State(state.position, state.direction.rotate_right(state.direction)))) + + target_state = get_moved_position(state.position, state.direction) + if not game_map.is_colliding(target_state): + states.append((Action.MOVE, State(target_state, state.direction))) + + return states + + +def graph_search(game_map: GameMap, start: PositionComponent, goal: tuple): + fringe = list() + explored = list() + + explored_states = set() + fringe_states = set() + + start = State(start.grid_position, start.direction) + fringe.append(Node(start)) + fringe_states.add((tuple(start.position), start.direction)) + + while True: + # No solutions found + if not any(fringe): + return [] + + node = fringe.pop(0) + fringe_states.remove((tuple(node.state.position), node.state.direction)) + + # Check goal + if node.state.position == goal: + actions = [node.action] + parent = node.parent + + while parent is not None: + if parent.action is not None: + actions.append(parent.action) + parent = parent.parent + + actions.reverse() + return actions + + explored.append(node) + explored_states.add((tuple(node.state.position), node.state.direction)) + + # Get all possible states + for state in get_states(node.state, game_map): + sub_state = (tuple(state[1].position), state[1].direction) + if sub_state not in fringe_states and sub_state not in explored_states: + new_node = Node(state=state[1], + parent=node, + action=state[0]) + fringe.append(new_node) + fringe_states.add((tuple(new_node.state.position), new_node.state.direction)) diff --git a/survival/systems/input_system.py b/survival/systems/input_system.py index 09282fb..d4b6c23 100644 --- a/survival/systems/input_system.py +++ b/survival/systems/input_system.py @@ -19,9 +19,8 @@ class InputSystem(esper.Processor): if mouse[0] == 1: pos = pygame.mouse.get_pos() pos = (pos[0] - self.camera.camera.left, pos[1] - self.camera.camera.top) - if self.world.has_component(ent, PathfindingComponent): - self.world.remove_component(ent, PathfindingComponent) - self.world.add_component(ent, PathfindingComponent(pos)) + if not self.world.has_component(ent, PathfindingComponent): + self.world.add_component(ent, PathfindingComponent(pos)) if self.world.has_component(ent, MovingComponent): continue diff --git a/survival/systems/pathfinding_movement_system.py b/survival/systems/pathfinding_movement_system.py index a40d5bc..551abd4 100644 --- a/survival/systems/pathfinding_movement_system.py +++ b/survival/systems/pathfinding_movement_system.py @@ -4,6 +4,7 @@ from survival.components.movement_component import MovementComponent from survival.components.moving_component import MovingComponent from survival.components.position_component import PositionComponent from survival.enums import Direction +from survival.graph_search import graph_search, Action from survival.pathfinding import breadth_first_search from survival.systems.input_system import PathfindingComponent @@ -17,26 +18,45 @@ class PathfindingMovementSystem(esper.Processor): for ent, (pos, pathfinding, movement) in self.world.get_components(PositionComponent, PathfindingComponent, MovementComponent): if pathfinding.path is None: - pathfinding.path = breadth_first_search(self.game_map, pos.grid_position, pathfinding.target_grid_pos) + pathfinding.path = graph_search(self.game_map, pos, pathfinding.target_grid_pos) - if len(pathfinding.path) < 1 and pathfinding.current_target is None: + if len(pathfinding.path) < 1: self.world.remove_component(ent, PathfindingComponent) continue - if self.world.has_component(ent, MovingComponent): + if self.world.has_component(ent, MovingComponent) or self.world.has_component(ent, DirectionChangeComponent): continue - if pathfinding.current_target is None: - target = pathfinding.path.pop(0) + action = pathfinding.path.pop(0) + + if action == Action.ROTATE_LEFT: + self.world.add_component(ent, DirectionChangeComponent(Direction.rotate_left(pos.direction))) + elif action == Action.ROTATE_RIGHT: + self.world.add_component(ent, DirectionChangeComponent(Direction.rotate_right(pos.direction))) else: - target = pathfinding.current_target + self.world.add_component(ent, MovingComponent()) - vector = (target[0] - pos.grid_position[0], target[1] - pos.grid_position[1]) - direction = Direction.from_vector(vector) - if direction != pos.direction: - pathfinding.current_target = target - self.world.add_component(ent, DirectionChangeComponent(direction)) - continue - - pathfinding.current_target = None - self.world.add_component(ent, MovingComponent()) + # if pathfinding.path is None: + # pathfinding.path = breadth_first_search(self.game_map, pos.grid_position, pathfinding.target_grid_pos) + # + # if len(pathfinding.path) < 1 and pathfinding.current_target is None: + # self.world.remove_component(ent, PathfindingComponent) + # continue + # + # if self.world.has_component(ent, MovingComponent): + # continue + # + # if pathfinding.current_target is None: + # target = pathfinding.path.pop(0) + # else: + # target = pathfinding.current_target + # + # vector = (target[0] - pos.grid_position[0], target[1] - pos.grid_position[1]) + # direction = Direction.from_vector(vector) + # if direction != pos.direction: + # pathfinding.current_target = target + # self.world.add_component(ent, DirectionChangeComponent(direction)) + # continue + # + # pathfinding.current_target = None + # self.world.add_component(ent, MovingComponent())