Files
sml-projects/fcore/path-finding.sml

297 lines
10 KiB
Standard ML
Raw Normal View History

structure PathFinding =
struct
(* functor for adding reachable platforms to queue *)
structure FindReachable =
MakeQuadFolder
(struct
open GameType
type env =
{ platforms: GameType.platform vector
, currentPlat: GameType.platform
, eKeys: IntSet.elem vector
}
type state = ValSet.elem vector * DistHeap.t
fun isBetween (p1, check, p2) = check >= p1 andalso check <= p2
fun canJumpUpTo (prevPlat: platform, currentPlat: platform) =
let
val {x = prevX, y = prevY, width = prevWidth, ...} = prevPlat
val {x = curX, y = curY, width = curWidth, ...} = currentPlat
val prevFinishX = prevX + prevWidth
val curFinishX = curX + curWidth
in
(isBetween (prevX, curX, prevFinishX)
orelse
isBetween (prevX, curFinishX, prevFinishX)
andalso prevY + Constants.jumpLimit >= curY)
end
fun canDropDownTo (prevPlat: platform, currentPlat: platform) =
let
val {x = prevX, y = prevY, width = prevWidth, ...} = prevPlat
val {x = curX, y = curY, width = curWidth, ...} = currentPlat
val prevFinishX = prevX + prevWidth
val curFinishX = curX + curWidth
in
(isBetween (prevX, curX, prevFinishX)
orelse
isBetween (prevX, curFinishX, prevFinishX) andalso prevY <= curY)
end
fun isReachableFromLeft (prevPlat, currentPlat) =
(* prev = right/from, current = left/to *)
let
val {x = prevX, y = prevY, width = prevWidth, ...} = prevPlat
val {x = curX, y = curY, width = curWidth, ...} = currentPlat
val enemyX = prevX
val xDiff = prevX - curX
in
if xDiff <= Constants.jumpLimit then
true
else
let
val enemyApexX = enemyX - Constants.jumpLimit
val enemyApexY = prevY + Constants.jumpLimit
val curFinishX = curX + curWidth
val diffApexY = enemyApexY - curY
val diffApexX = enemyApexX - curFinishX
in
diffApexX <= diffApexY orelse diffApexY <= 0
end
end
fun isReachableFromRight (prevPlat, currentPlat) =
(* prev = left/from, current = right/to *)
let
val {x = prevX, y = prevY, width = prevWidth, ...} = prevPlat
val {x = curX, y = curY, width = curWidth, ...} = currentPlat
(* last x coordinate where enemy can fully fit on prevPlat *)
val enemyX = prevX + prevWidth - Constants.enemySize
val xDiff = curX - prevX
in
if xDiff <= Constants.jumpLimit then
(* platform is possible to jump to without falling *)
true
else
let
val enemyApexX = enemyX + Constants.jumpLimit
val enemyApexY = prevY + Constants.jumpLimit
val diffApexY = enemyApexY - curY
val diffApexX = enemyApexX - curX
in
diffApexY <= 0 orelse diffApexX <= diffApexY
end
end
fun insertIfNotExistsOrShorter (dist, eKeys, eVals, foldPlatID, q) =
let
val pos = IntSet.findInsPos (foldPlatID, eKeys)
in
if pos <> ~1 andalso pos <> Vector.length eKeys then
let
val key = IntSet.sub (eKeys, pos)
in
if pos = key then
(* may need to update record in eVals if it is shorter *)
let
val {distance = oldDist, ...} = ValSet.sub (eVals, pos)
in
if dist < oldDist then
(* update values as we found a shorter path *)
let
val eVals = ValSet.updateAtIdx
( eVals
, {distance = dist, from = Char.chr foldPlatID}
, pos
)
in
(eVals, q)
end
else
(* return existing *)
(eVals, q)
end
else
(* key not explored, so add to queue *)
let
val q =
DistHeap.insert ({distance = dist, id = foldPlatID}, q)
in
(eVals, q)
end
end
else
(* key not explored, so add to queue *)
let
val q = DistHeap.insert ({distance = dist, id = foldPlatID}, q)
in
(eVals, q)
end
end
fun fState ((eVals, q), env, foldPlatID) =
let
val {platforms, currentPlat, eKeys} = env
val foldPlat = Platform.find (foldPlatID, platforms)
in
if
canJumpUpTo (currentPlat, foldPlat)
orelse canDropDownTo (currentPlat, foldPlat)
then
let
(* only need to calculate vertical distance *)
val {y = py, ...} = currentPlat
val {y = cy, ...} = foldPlat
val dist = abs (py - cy)
in
insertIfNotExistsOrShorter (dist, eKeys, eVals, foldPlatID, q)
end
else if
isReachableFromLeft (currentPlat, foldPlat)
orelse isReachableFromRight (currentPlat, foldPlat)
then
let
val {x = px, y = py, width = pw, ...} = currentPlat
val {x = cx, y = cy, width = cw, ...} = foldPlat
val pFinishX = px + pw
val cFinishX = cx + cw
val dist =
if py = cy then
let
(* if on same y coordinate,
* only need to calculate horizontal distance *)
val d1 = abs (px - cx)
val d2 = abs (px - cFinishX)
val d3 = abs (pFinishX - cx)
val d4 = abs (pFinishX - cFinishX)
val min = Int.min (d1, d2)
val min = Int.min (min, d3)
in
Int.min (min, d4)
end
else
let
(* if they have different y coordinate,
* need to calculate diagonal length/hypotenuse by pythagoras
* *)
val x1 = abs (px - cx)
val x2 = abs (px - cFinishX)
val x3 = abs (pFinishX - cx)
val x4 = abs (pFinishX - cFinishX)
val x = Int.min (x1, x2)
val x = Int.min (x, x3)
val x = Int.min (x, x4)
(* there is only one y coordinate for platform
* so don't need to 'minimise' it
* *)
val y = abs (py - cy)
(* pythagoras *)
val xsq = x * x
val ysq = y * y
val hypsq = xsq + ysq
(* square root to find diagonal length *)
val dg = Real.fromInt hypsq
val dg = Math.sqrt dg
in
Real.toInt IEEEReal.TO_NEAREST dg
end
in
insertIfNotExistsOrShorter (dist, eKeys, eVals, foldPlatID, q)
end
else
(eVals, q)
end
end)
fun filterMinDuplicates (q, eKeys) =
let
val min = DistHeap.findMin q
in
if IntSet.contains (min, eKeys) then
let val q = DistHeap.deleteMin q
in filterMinDuplicates (q, eKeys)
end
else
q
end
fun helpGetPathList (curID, eID, eKeys, eVals, acc) =
if curID = eID then
(* reached starting ID of platform so return *)
acc
else
(* cons curID and traverse links backwards to reconstruct path *)
let
val acc = curID :: acc
val pos = IntSet.findInsPos (curID, eKeys)
val {from, ...} = IntSet.sub (eVals, pos)
in
helpGetPathList (from, eID, eKeys, eVals, acc)
end
fun getPathList (pID, eID, eKeys, eVals) =
helpGetPathList (pID, eID, eKeys, eVals, [])
fun loop (pID, eID, platforms, platformTree, q, eKeys, eVals) =
let
(* filtering duplicates because we have no decrease-key operation *)
val q = filterMinDuplicates (q, eKeys)
val min = DistHeap.findMin q
in
if min = ~1 then
(* return empty list to signify that there is no path *)
[]
else if min = pID then
(* found path to destination so reconstruct path and return *)
getPathList (pID, eID, eKeys, eVals)
else
(* find reachable values from min in quad tree *)
let
val plat = Platform.find (min, platforms)
fun start (pID, eID, platforms, platformTree) =
let
(* initialise data structures: the priority queue and the explored map *)
val q = DistHeap.empty
val q = DistHeap.insert ({distance = 0, id = eID}, q)
val exploredKeys = IntSet.empty
val exploredVals = ValSet.empty
val insPos = IntSet.findInsPos (eID, exploredKeys)
val exploredKeys = IntSet.insert (exploredKeys, eID, insPos)
(* important: starting node will have a key that points to itself.
* For example, if starting node is #"e", then the record will say
* the "from" field is #"e".
* This is different from the other nodes, where the "from" field
* will be the ID of the previous node which led to the current one.
* This is our terminating condition when reconstructing paths:
* If the key matching this value is the same as the "from" node,
* then we're done reconstructing the path and can return the path list.
* *)
val eVal = {distance = 0, from = Char.chr eID}
val exploredVals = ValSet.insert (exploredVals, eVal, insPos)
in
loop (pID, eID, platforms, platformTree, q, eKeys, eVals)
end
end