Added non-objective implementation
This commit is contained in:
parent
11b467db32
commit
e270360690
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user