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.Interactable import Interactable
|
||||
from src.entities.Pickupable import Pickupable
|
||||
@ -213,3 +214,194 @@ class AutomaticMovement:
|
||||
newState = (newX, newY, newRotation)
|
||||
|
||||
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