2025-01-19 22:45:51 +00:00
|
|
|
structure PathFinding =
|
|
|
|
|
struct
|
2025-01-19 23:56:52 +00:00
|
|
|
(* functor for adding reachable platforms to queue *)
|
|
|
|
|
structure FindReachable =
|
2025-01-21 22:46:04 +00:00
|
|
|
struct
|
|
|
|
|
open GameType
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
type env =
|
|
|
|
|
{ platforms: GameType.platform vector
|
|
|
|
|
, currentPlat: GameType.platform
|
|
|
|
|
, eKeys: IntSet.elem vector
|
|
|
|
|
, distSoFar: int
|
|
|
|
|
}
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-22 18:22:34 +00:00
|
|
|
type state = ValSet.elem vector * DistVec.elem vector
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
fun isBetween (p1, check, p2) = check >= p1 andalso check <= p2
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
fun canJumpUpTo (prevPlat: platform, currentPlat: platform) =
|
|
|
|
|
let
|
|
|
|
|
val {x = prevX, y = prevY, width = prevWidth, ...} = prevPlat
|
|
|
|
|
val {x = curX, y = curY, width = curWidth, ...} = currentPlat
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
val prevFinishX = prevX + prevWidth
|
|
|
|
|
val curFinishX = curX + curWidth
|
|
|
|
|
in
|
|
|
|
|
(isBetween (prevX, curX, prevFinishX)
|
|
|
|
|
orelse
|
|
|
|
|
isBetween (prevX, curFinishX, prevFinishX) andalso prevY + 155 >= curY)
|
|
|
|
|
end
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
fun canDropDownTo (prevPlat: platform, currentPlat: platform) =
|
|
|
|
|
let
|
|
|
|
|
val {x = prevX, y = prevY, width = prevWidth, ...} = prevPlat
|
|
|
|
|
val {x = curX, y = curY, width = curWidth, ...} = currentPlat
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
val prevFinishX = prevX + prevWidth
|
|
|
|
|
val curFinishX = curX + curWidth
|
|
|
|
|
in
|
|
|
|
|
(isBetween (prevX, curX, prevFinishX)
|
|
|
|
|
orelse isBetween (prevX, curFinishX, prevFinishX) andalso prevY <= curY)
|
|
|
|
|
end
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
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
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
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
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
val curFinishX = curX + curWidth
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
val diffApexY = enemyApexY - curY
|
|
|
|
|
val diffApexX = enemyApexX - curFinishX
|
|
|
|
|
in
|
|
|
|
|
diffApexX <= diffApexY orelse diffApexY <= 0
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
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
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
(* last x coordinate where enemy can fully fit on prevPlat *)
|
|
|
|
|
val enemyX = prevX + prevWidth - Constants.enemySize
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
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
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
val diffApexY = enemyApexY - curY
|
|
|
|
|
val diffApexX = enemyApexX - curX
|
|
|
|
|
in
|
|
|
|
|
diffApexY <= 0 orelse diffApexX <= diffApexY
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
fun insertIfNotExistsOrShorter
|
|
|
|
|
(dist, eKeys, eVals, foldPlatID, q, fromPlatID) =
|
|
|
|
|
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 = fromPlatID}, pos)
|
|
|
|
|
in
|
|
|
|
|
(eVals, q)
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
(* return existing *)
|
|
|
|
|
(eVals, q)
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
(* key not explored, so add to queue *)
|
|
|
|
|
let
|
2025-01-22 18:22:34 +00:00
|
|
|
val insRecord =
|
|
|
|
|
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
|
|
|
|
|
val insPos = DistVec.findInsPos (insRecord, q)
|
|
|
|
|
val q = DistVec.insert (q, insRecord, insPos)
|
2025-01-21 22:46:04 +00:00
|
|
|
in
|
|
|
|
|
(eVals, q)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
(* key not explored, so add to queue *)
|
|
|
|
|
let
|
2025-01-22 18:22:34 +00:00
|
|
|
val insRecord =
|
|
|
|
|
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
|
|
|
|
|
val insPos = DistVec.findInsPos (insRecord, q)
|
|
|
|
|
val q = DistVec.insert (q, insRecord, insPos)
|
2025-01-21 22:46:04 +00:00
|
|
|
in
|
|
|
|
|
(eVals, q)
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
fun fState ((eVals, q), env, foldPlatID) =
|
|
|
|
|
let
|
|
|
|
|
val {platforms, currentPlat, eKeys, distSoFar} = env
|
|
|
|
|
val curPlatID = #id currentPlat
|
|
|
|
|
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)
|
2025-01-25 13:55:58 +00:00
|
|
|
|
2025-01-25 18:36:30 +00:00
|
|
|
val dist = dist + distSoFar
|
2025-01-21 22:46:04 +00:00
|
|
|
in
|
|
|
|
|
insertIfNotExistsOrShorter
|
|
|
|
|
(dist, eKeys, eVals, foldPlatID, q, curPlatID)
|
|
|
|
|
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
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
val pFinishX = px + pw
|
|
|
|
|
val cFinishX = cx + cw
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
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)
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
val min = Int.min (d1, d2)
|
|
|
|
|
val min = Int.min (min, d3)
|
|
|
|
|
in
|
2025-01-25 13:55:58 +00:00
|
|
|
(* divide by 2 so we prefer straight horizontal paths over
|
|
|
|
|
* diagonal ones by giving horizontal ones a lower cost *)
|
2025-01-25 18:36:30 +00:00
|
|
|
Int.min (min, d4) + distSoFar
|
2025-01-21 22:46:04 +00:00
|
|
|
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)
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
val x = Int.min (x1, x2)
|
|
|
|
|
val x = Int.min (x, x3)
|
|
|
|
|
val x = Int.min (x, x4)
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
(* there is only one y coordinate for platform
|
|
|
|
|
* so don't need to 'minimise' it
|
|
|
|
|
* *)
|
|
|
|
|
val y = abs (py - cy)
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
(* pythagoras *)
|
|
|
|
|
val xsq = x * x
|
|
|
|
|
val ysq = y * y
|
|
|
|
|
val hypsq = xsq + ysq
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-21 22:46:04 +00:00
|
|
|
(* square root to find diagonal length *)
|
|
|
|
|
val dg = Real.fromInt hypsq
|
|
|
|
|
val dg = Math.sqrt dg
|
|
|
|
|
in
|
2025-01-25 13:55:58 +00:00
|
|
|
Real.toInt IEEEReal.TO_NEAREST dg + distSoFar
|
2025-01-21 22:46:04 +00:00
|
|
|
end
|
|
|
|
|
in
|
|
|
|
|
insertIfNotExistsOrShorter
|
|
|
|
|
(dist, eKeys, eVals, foldPlatID, q, curPlatID)
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
(eVals, q)
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-01-19 23:56:52 +00:00
|
|
|
|
2025-01-19 22:45:51 +00:00
|
|
|
fun filterMinDuplicates (q, eKeys) =
|
2025-01-22 18:22:34 +00:00
|
|
|
if DistVec.isEmpty q then
|
|
|
|
|
q
|
|
|
|
|
else
|
|
|
|
|
let
|
|
|
|
|
val {id = min, ...} = DistVec.findMin q
|
|
|
|
|
|
|
|
|
|
val pos = IntSet.findInsPos (min, eKeys)
|
|
|
|
|
in
|
|
|
|
|
if IntSet.contains (min, eKeys) then
|
|
|
|
|
let val q = DistVec.deleteMin q
|
|
|
|
|
in filterMinDuplicates (q, eKeys)
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
q
|
|
|
|
|
end
|
2025-01-19 22:45:51 +00:00
|
|
|
|
|
|
|
|
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)
|
2025-01-20 02:38:22 +00:00
|
|
|
|
2025-01-21 00:19:38 +00:00
|
|
|
val {from, distance, ...} = ValSet.sub (eVals, pos)
|
2025-01-19 22:45:51 +00:00
|
|
|
in
|
2025-01-20 11:17:00 +00:00
|
|
|
helpGetPathList (from, eID, eKeys, eVals, acc)
|
2025-01-19 22:45:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
fun getPathList (pID, eID, eKeys, eVals) =
|
|
|
|
|
helpGetPathList (pID, eID, eKeys, eVals, [])
|
|
|
|
|
|
2025-01-21 21:20:57 +00:00
|
|
|
fun addPlatforms (pos, (eVals, q), env) =
|
2025-01-21 22:46:04 +00:00
|
|
|
let
|
|
|
|
|
val {platforms, ...} = env
|
|
|
|
|
in
|
|
|
|
|
if pos = Vector.length platforms then
|
|
|
|
|
(eVals, q)
|
|
|
|
|
else
|
|
|
|
|
let
|
|
|
|
|
val foldPlat = Vector.sub (platforms, pos)
|
|
|
|
|
val foldPlatID = #id foldPlat
|
|
|
|
|
val (eVals, q) = FindReachable.fState ((eVals, q), env, foldPlatID)
|
|
|
|
|
in
|
|
|
|
|
addPlatforms (pos + 1, (eVals, q), env)
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-01-21 21:20:57 +00:00
|
|
|
|
2025-01-19 22:45:51 +00:00
|
|
|
fun loop (pID, eID, platforms, platformTree, q, eKeys, eVals) =
|
|
|
|
|
let
|
|
|
|
|
(* filtering duplicates because we have no decrease-key operation *)
|
|
|
|
|
val q = filterMinDuplicates (q, eKeys)
|
|
|
|
|
in
|
2025-01-20 02:38:22 +00:00
|
|
|
if IntSet.contains (pID, eKeys) then
|
2025-01-20 01:25:09 +00:00
|
|
|
(* return path if we explored pid *)
|
2025-01-19 22:45:51 +00:00
|
|
|
getPathList (pID, eID, eKeys, eVals)
|
2025-01-22 18:22:34 +00:00
|
|
|
else (* continue dijkstra's algorithm *) if DistVec.isEmpty q then
|
|
|
|
|
(* return empty list to signify that there is no path *)
|
|
|
|
|
[]
|
2025-01-19 22:45:51 +00:00
|
|
|
else
|
2025-01-22 18:22:34 +00:00
|
|
|
(* find reachable values from min in quad tree *)
|
2025-01-19 22:45:51 +00:00
|
|
|
let
|
2025-01-22 18:22:34 +00:00
|
|
|
val {distance = distSoFar, id = minID, comesFrom} = DistVec.findMin q
|
|
|
|
|
val plat = Platform.find (minID, platforms)
|
|
|
|
|
|
|
|
|
|
(* add explored *)
|
|
|
|
|
val insPos = IntSet.findInsPos (minID, eKeys)
|
|
|
|
|
val eKeys = IntSet.insert (eKeys, minID, insPos)
|
|
|
|
|
val eVals =
|
|
|
|
|
ValSet.insert
|
|
|
|
|
(eVals, {distance = distSoFar, from = comesFrom}, insPos)
|
|
|
|
|
|
2025-01-25 18:36:30 +00:00
|
|
|
(* on each loop, increment distSoFar by 15.
|
|
|
|
|
* Result: paths that require jumps to fewer platforms are
|
|
|
|
|
* incentivised a little bit. *)
|
2025-01-22 18:22:34 +00:00
|
|
|
val env =
|
|
|
|
|
{ platforms = platforms
|
|
|
|
|
, currentPlat = plat
|
|
|
|
|
, eKeys = eKeys
|
2025-01-25 18:36:30 +00:00
|
|
|
, distSoFar = distSoFar + 15
|
2025-01-22 18:22:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val state = (eVals, q)
|
|
|
|
|
|
|
|
|
|
(* calculate area to fold over quad tree *)
|
|
|
|
|
val ww = Constants.worldWidth
|
|
|
|
|
val wh = Constants.worldHeight
|
|
|
|
|
|
|
|
|
|
val {x, y, width, ...} = plat
|
|
|
|
|
val y = y - Constants.jumpLimit
|
|
|
|
|
val height = wh - y
|
|
|
|
|
|
|
|
|
|
(* fold over quad tree, updating any distances
|
|
|
|
|
* we find the shortest path for *)
|
|
|
|
|
val (eVals, q) = addPlatforms (0, (eVals, q), env)
|
2025-01-20 01:25:09 +00:00
|
|
|
in
|
2025-01-22 18:22:34 +00:00
|
|
|
loop (pID, eID, platforms, platformTree, q, eKeys, eVals)
|
2025-01-20 01:25:09 +00:00
|
|
|
end
|
|
|
|
|
end
|
2025-01-19 22:45:51 +00:00
|
|
|
|
|
|
|
|
fun start (pID, eID, platforms, platformTree) =
|
|
|
|
|
let
|
|
|
|
|
(* initialise data structures: the priority queue and the explored map *)
|
2025-01-22 18:22:34 +00:00
|
|
|
val q = DistVec.fromList [{distance = 0, id = eID, comesFrom = eID}]
|
2025-01-19 22:45:51 +00:00
|
|
|
|
2025-01-20 02:38:22 +00:00
|
|
|
(* explored keys and values *)
|
2025-01-20 01:25:09 +00:00
|
|
|
val eKeys = IntSet.empty
|
|
|
|
|
val eVals = ValSet.empty
|
2025-01-19 22:45:51 +00:00
|
|
|
|
2025-01-20 02:38:22 +00:00
|
|
|
(* 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.
|
|
|
|
|
* *)
|
2025-01-19 22:45:51 +00:00
|
|
|
in
|
|
|
|
|
loop (pID, eID, platforms, platformTree, q, eKeys, eVals)
|
|
|
|
|
end
|
|
|
|
|
end
|