Added non-objective implementation

This commit is contained in:
Marcin Kostrzewski 2020-05-13 18:08:22 +02:00
parent 11b467db32
commit e270360690

View File

@ -1,3 +1,4 @@
from game.Map import Map
from src.entities.Entity import Entity from src.entities.Entity import Entity
from src.entities.Interactable import Interactable from src.entities.Interactable import Interactable
from src.entities.Pickupable import Pickupable from src.entities.Pickupable import Pickupable
@ -213,3 +214,194 @@ class AutomaticMovement:
newState = (newX, newY, newRotation) newState = (newX, newY, newRotation)
return newState return newState
# As non-objective
def newStateAfterAction(movable, state, action: Movement):
"""
Returns a state after a given action
:type movable: Entity
:param movable: Movable entity
:param state: Current entity state
:param action: What did entity do
:return: A tuple of (x, y, rotation)
"""
newX = state[0]
newY = state[1]
newRotation = state[2]
if action == Movement.FORWARD:
if state[2] == Rotations.NORTH:
newY -= movable.rect.w
elif state[2] == Rotations.EAST:
newX += movable.rect.w
elif state[2] == Rotations.SOUTH:
newY += movable.rect.w
elif state[2] == Rotations.WEST:
newX -= movable.rect.w
elif action == Movement.ROTATE_L:
newRotation = Rotations((state[2].value - 1) % 4)
elif action == Movement.ROTATE_R:
newRotation = Rotations((state[2].value + 1) % 4)
newState = (newX, newY, newRotation)
return newState
def stepCost(terrainTile: TerrainTile):
"""
Gets the cost of a given tile
:param terrainTile:
:return: Step cost as int
"""
if terrainTile is None:
return 1000
return terrainTile.cost
def approximateDistanceFromTarget(tileX, tileY, target):
"""
Given X, Y and a target, approximate the distance.
:param tileX: X coord
:param tileY: Y coord
:param target: Target entity
:return: Distance as int
"""
return abs(tileX - target.rect.x) + abs(tileY - target.rect.y)
def priority(elem: AStarNode, map: Map, target):
"""
Gets the priority of the move.
:param elem: Node
:param map: Map object
:param target: Target goal
:return: Priority as int
"""
return approximateDistanceFromTarget(elem.state[0], elem.state[1], target) + stepCost(
map.getTileOnCoord((elem.state[0], elem.state[1])))
def successor(movable: Entity, elemState, map: Map, target):
"""
Successor function for a given movable object (Usually a player)
:type target: Entity
:param target:
:param elemState: [x, y, Rotation]
:return: list of (Movement, NewState)
"""
result = [(Movement.ROTATE_R, newStateAfterAction(movable, elemState, Movement.ROTATE_R)),
(Movement.ROTATE_L, newStateAfterAction(movable, elemState, Movement.ROTATE_L))]
stateAfterForward = newStateAfterAction(elemState, Movement.FORWARD)
if 0 <= stateAfterForward[0] <= map.width and 0 <= stateAfterForward[1] <= map.height:
facingEntity = map.getEntityOnCoord((stateAfterForward[0], stateAfterForward[1]))
if facingEntity is not None:
if isinstance(target, Entity):
if facingEntity.id == target.id:
result.append((Movement.FORWARD, stateAfterForward))
elif map.collision(stateAfterForward[0], stateAfterForward[1]) and \
target.rect.x == stateAfterForward[0] and target.rect.y == stateAfterForward[1]:
result.append((Movement.FORWARD, stateAfterForward))
elif not map.collision(target.rect.x, target.rect.y):
result.append((Movement.FORWARD, stateAfterForward))
return result
def goalTest(coords, target):
"""
Check whether the target has been reached.
:param coords: A tuple of X and Y coords
:param target: Target
:return: True, if the goal is reached
"""
if coords[0] == target.rect.x and coords[1] == target.rect.y:
return True
return False
def aStar(movable: Entity, target, map: Map):
"""
A* pathfinder function. Composes an array of moves to do in order to reach a target.
:param movable: An entity to move (Usually a player)
:param target: Target object
:param map: Map object
:return: Array of moves
"""
testCount = 0
fringe = PriorityQueue()
explored = []
startingState = (movable.rect.x, movable.rect.y, movable.rotation)
startingPriority = 0
fringe.put((startingPriority, testCount, AStarNode(None, None, startingState)))
testCount += 1
while True:
if fringe.empty():
# target is unreachable
print("PATH NOT FOUND")
return None
elem: AStarNode = fringe.get()[2]
if goalTest(elem.state, target):
print("PATH FOUND")
movesList = []
if isinstance(target, Entity) or target in map.collidables:
elem = elem.parent
while elem.action is not None:
movesList.append(elem.action)
elem = elem.parent
movesList.reverse()
return movesList
# debug
# print("DEBUG")
# print("ACTUAL STATE: {}".format(elem.state))
# print("HOW TO GET HERE:")
# temp = elem
# while temp.action is not None:
# print(temp.action)
# temp = temp.parent
#
# print("POSSIBLE MOVEMENTS FROM HERE:")
# for el in self.successor(elem.state):
# print(el)
#
# print("*" * 20)
explored.append(elem)
for (movement, newState) in successor(movable, elem.state, map, target):
newNode = AStarNode(elem, movement, newState)
newPriority = priority(newNode, map, target)
# Check if state is not in fringe queue ... # ... and is not in explored list
if not any(newNode.state == node[2].state for node in fringe.queue) \
and not any(newNode.state == node.state for node in explored):
# there can't be nodes with same priority
fringe.put((newPriority, testCount, newNode))
testCount += 1
# If state is in fringe queue ...
elif any(newNode.state == node[2].state for node in fringe.queue):
node: AStarNode
for (pr, count, node) in fringe.queue:
# Compare nodes
if node.state == newNode.state and node.action == newNode.action:
# ... and if it has priority > newPriority
if pr > newPriority:
# Replace it with new priority
fringe.queue.remove((pr, count, node))
fringe.put((newPriority, count, node))
testCount += 1
break