get path-finding.sml to use function in build-graph.sml to find reachable platforms, and remove now-dead code from path-finding.sml

This commit is contained in:
2025-01-30 13:06:32 +00:00
parent 755e5da7f7
commit bbb957f016
3 changed files with 98 additions and 372 deletions

View File

@@ -1,64 +1,60 @@
structure BuildGraph =
struct
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)
val _ = print "UPDATE foldPlatID\n"
in
(eVals, q)
end
else
(* return existing *)
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
(* key not explored, so add to queue *)
let
val insRecord =
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
val insPos = DistVec.findInsPos (insRecord, q)
val q = DistVec.insert (q, insRecord, insPos)
val _ = print "INSERT NEW PLAT\n"
in
end
else
(* return existing *)
(eVals, q)
end
end
else
(* key not explored, so add to queue *)
let
val insRecord =
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
val insPos = DistVec.findInsPos (insRecord, q)
val q = DistVec.insert (q, insRecord, insPos)
val _ = print "UPDATE foldPlatID\n"
in
(eVals, q)
end
end
end
else
(* key not explored, so add to queue *)
let
val insRecord =
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
val insPos = DistVec.findInsPos (insRecord, q)
val q = DistVec.insert (q, insRecord, insPos)
in
(eVals, q)
end
end
else
(* key not explored, so add to queue *)
let
val insRecord =
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
val insPos = DistVec.findInsPos (insRecord, q)
val q = DistVec.insert (q, insRecord, insPos)
in
(eVals, q)
end
end
type env =
{ platforms: GameType.platform vector
, currentPlat: GameType.platform
, eKeys: IntSet.elem vector
, distSoFar: int
}
type env =
{ platforms: GameType.platform vector
, currentPlat: GameType.platform
, eKeys: IntSet.elem vector
, distSoFar: int
}
(* adds platforms to queue if they have not been explored
* or, if they have been explored and distance is smaller than previous,
@@ -69,7 +65,7 @@ struct
structure Vertical =
MakeQuadTreeFold
(struct
type env = env
type env = env
type state = ValSet.elem vector * DistVec.elem vector
@@ -77,28 +73,15 @@ struct
let
val {platforms, currentPlat, eKeys, distSoFar} = env
val _ = print ("foldPlatID = " ^ Int.toString foldPlatID ^ "\n")
val {y = foldPlatY, ...} = Platform.find (foldPlatID, platforms)
val {y = currentPlatY, id = fromPlatID, ...} = currentPlat
val newDist = abs (foldPlatY - currentPlatY) + distSoFar
in
insertIfNotExistsOrShorter
(newDist, eKeys, eVals, foldPlatID, q, fromPlatID)
(newDist, eKeys, eVals, foldPlatID, q, fromPlatID)
end
end)
fun helpPrint (pos, vec) =
if pos = Vector.length vec then
()
else
let
val {id, distance, comesFrom} = DistVec.sub (vec, pos)
val _ = print ("contains (id = " ^ Int.toString id ^")\n")
in
helpPrint (pos + 1, vec)
end
fun start (currentPlat: GameType.platform, env: env, state, platformTree) =
let
val {x, y, width, ...} = currentPlat
@@ -107,14 +90,8 @@ struct
val searchY = y - Constants.jumpLimit
val height = Constants.worldHeight - searchY
val _ = print "start fold\n"
val (eVals, q) =
Vertical.foldRegion (x, searchY, width, height, env, state, platformTree)
val _ = print "finish fold\n"
val _ = print "BuildGraph q contains IDs:\n"
val _ = helpPrint (0, q)
val _ = print "\n"
val (eVals, q) = Vertical.foldRegion
(x, searchY, width, height, env, state, platformTree)
in
(eVals, q)
end

View File

@@ -1,235 +1,5 @@
structure PathFinding =
struct
(* functor for adding reachable platforms to queue *)
structure FindReachable =
struct
open GameType
type env =
{ platforms: GameType.platform vector
, currentPlat: GameType.platform
, eKeys: IntSet.elem vector
, distSoFar: int
}
type state = ValSet.elem vector * DistVec.elem vector
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 + 155 >= 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, 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
val insRecord =
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
val insPos = DistVec.findInsPos (insRecord, q)
val q = DistVec.insert (q, insRecord, insPos)
in
(eVals, q)
end
end
else
(* key not explored, so add to queue *)
let
val insRecord =
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
val insPos = DistVec.findInsPos (insRecord, q)
val q = DistVec.insert (q, insRecord, insPos)
in
(eVals, q)
end
end
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)
val dist = dist + distSoFar
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
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
(* divide by 2 so we prefer straight horizontal paths over
* diagonal ones by giving horizontal ones a lower cost *)
Int.min (min, d4) + distSoFar
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 + distSoFar
end
in
insertIfNotExistsOrShorter
(dist, eKeys, eVals, foldPlatID, q, curPlatID)
end
else
(eVals, q)
end
end
fun filterMinDuplicates (q, eKeys) =
if DistVec.isEmpty q then
q
@@ -265,73 +35,51 @@ struct
fun getPathList (pID, eID, eKeys, eVals) =
helpGetPathList (pID, eID, eKeys, eVals, [])
fun addPlatforms (pos, (eVals, q), env) =
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
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
if IntSet.contains (pID, eKeys) then
(* return path if we explored pid *)
getPathList (pID, eID, eKeys, eVals)
else (* continue dijkstra's algorithm *) if DistVec.isEmpty q then
(* return empty list to signify that there is no path *)
[]
else
(* find reachable values from min in quad tree *)
let
val {distance = distSoFar, id = minID, comesFrom} = DistVec.findMin q
val plat = Platform.find (minID, platforms)
if IntSet.contains (pID, eKeys) then
(* return path if we explored pid *)
getPathList (pID, eID, eKeys, eVals)
else
(* continue dijkstra's algorithm *)
let
(* filtering duplicates because we have no decrease-key operation *)
val q = filterMinDuplicates (q, eKeys)
in
if DistVec.isEmpty q then
(* return empty list to signify that there is no path *)
[]
else
(* find reachable values from min in quad tree *)
let
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)
(* 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)
(* on each loop, increment distSoFar by 15.
* Result: paths that require jumps to fewer platforms are
* incentivised a little bit. *)
val env =
{ platforms = platforms
, currentPlat = plat
, eKeys = eKeys
, distSoFar = distSoFar + 15
}
(* on each loop, increment distSoFar by 15.
* Result: paths that require jumps to fewer platforms are
* incentivised a little bit. *)
val env =
{ platforms = platforms
, currentPlat = plat
, eKeys = eKeys
, distSoFar = distSoFar + 15
}
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)
in
loop (pID, eID, platforms, platformTree, q, eKeys, eVals)
end
end
(* fold over quad tree, updating any distances
* we find the shortest path for *)
val (eVals, q) =
BuildGraph.start (plat, env, (eVals, q), platformTree)
in
loop (pID, eID, platforms, platformTree, q, eKeys, eVals)
end
end
fun start (pID, eID, platforms, platformTree) =
let