From e23e2f09975b2f6c754fff06f90ec644577901ec Mon Sep 17 00:00:00 2001 From: Humza Shahid Date: Fri, 31 Jan 2025 04:32:53 +0000 Subject: [PATCH] precompute graph to be used for dijkstra's algorithm, resulting in CPU usage that never exceed 7/home/humza/Downloads/sml/oh-my-stars/fcore/path-finding.sml with 5 enemies and eliminating lag --- fcore/build-graph.sml | 244 -------------------------------------- fcore/enemy-behaviour.sml | 24 ++-- fcore/enemy.sml | 19 ++- fcore/game-type.sml | 39 ++++-- fcore/game-update.sml | 5 +- fcore/graph.sml | 23 ++-- fcore/path-finding.sml | 52 +------- fcore/player-enemy.sml | 5 + oms.mlb | 8 +- 9 files changed, 87 insertions(+), 332 deletions(-) delete mode 100644 fcore/build-graph.sml diff --git a/fcore/build-graph.sml b/fcore/build-graph.sml deleted file mode 100644 index dd94f3a..0000000 --- a/fcore/build-graph.sml +++ /dev/null @@ -1,244 +0,0 @@ -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) - 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 - - 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, - * updates their distance. - * Only intended for platforms which can be reached vertically - * (jumped to or dropped to without moving left or right at the same time). - * *) - structure Vertical = - MakeQuadTreeFold - (struct - type env = env - - type state = ValSet.elem vector * DistVec.elem vector - - fun fold (foldPlatID, env: env, (eVals, q)) = - let - val {platforms, currentPlat, eKeys, distSoFar} = env - - 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) - end - end) - - (* trace paths for movements: - * jump + move right, or drop + move right, - * jump + move left, drop + move right *) - structure Horizontal = - MakeQuadTreeFold - (struct - type env = env - - type state = ValSet.elem vector * DistVec.elem vector - - fun minWidth (p1: GameType.platform, p2: GameType.platform) = - let - val {x = p1x, width = p1w, ...} = p1 - val {x = p2x, width = p2w, ...} = p2 - - val p1fx = p1x + p1w - val p2fx = p2x + p2w - - val w1 = abs (p1fx - p2fx) - val w2 = abs (p1fx - p2x) - val w3 = abs (p1x - p2x) - val w4 = abs (p1x - p2fx) - - val min = Int.min (w1, w2) - val min = Int.min (min, w3) - in - Int.min (min, w4) - end - - fun pythagoras (width, height) = - let - val wsq = width * width - val hsq = height * height - val hypotenuseSq = wsq + hsq - val hypSq = Real.fromInt hypotenuseSq - val hyp = Math.sqrt hypSq - in - Real.toInt IEEEReal.TO_NEAREST hyp - end - - fun fold (foldPlatID, env: env, (eVals, q)) = - let - val {platforms, currentPlat, eKeys, distSoFar} = env - - val foldPlat = Platform.find (foldPlatID, platforms) - val foldPlatY = #y foldPlat - val {y = currentPlatY, id = fromPlatID, ...} = currentPlat - - val height = abs (foldPlatY - currentPlatY) - val width = minWidth (currentPlat, foldPlat) - - val newDist = pythagoras (width, height) + distSoFar - in - insertIfNotExistsOrShorter - (newDist, eKeys, eVals, foldPlatID, q, fromPlatID) - end - end) - - fun traceRightDescent (x, y, platTree, env, state) = - if x >= Constants.worldWidth orelse y >= Constants.worldHeight then - (* we hit bounds of screen and saw that there was - * no way to jump to next nextPlatID *) - state - else - let - val width = Constants.moveEnemyBy - val height = Constants.worldHeight - y - val state = Horizontal.foldRegion - (x, y, width, height, env, state, platTree) - - val nextX = x + Constants.moveEnemyBy - val nextY = y + Constants.moveEnemyBy - in - traceRightDescent (nextX, nextY, platTree, env, state) - end - - fun traceRightJumpAscent (x, y, remainingJump, platTree, env, state) = - if remainingJump >= Constants.jumpLimit - Constants.enemySize then - traceRightDescent (x, y, platTree, env, state) - else - let - val width = Constants.moveEnemyBy - val height = Constants.worldHeight - y - - val state = Horizontal.foldRegion - (x, y, width, height, env, state, platTree) - - val nextX = x + Constants.moveEnemyBy - val nextY = y - Constants.moveEnemyBy - val nextJump = remainingJump + Constants.moveEnemyBy - in - traceRightJumpAscent (nextX, nextY, nextJump, platTree, env, state) - end - - fun traceRightJump (currentPlat: GameType.platform, env, state, platTree) = - let - val {x, y, width, ...} = currentPlat - val x = x - Constants.enemySize + width - in - traceRightJumpAscent (x, y, 0, platTree, env, state) - end - - fun traceLeftDescent (x, y, platTree, env, state) = - if x <= 0 orelse y >= Constants.worldHeight then - state - else - let - val width = Constants.moveEnemyBy - val height = Constants.worldHeight - y - val state = Horizontal.foldRegion - (x, y, width, height, env, state, platTree) - - val nextX = x - Constants.moveEnemyBy - val nextY = y + Constants.moveEnemyBy - in - traceLeftDescent (nextX, nextY, platTree, env, state) - end - - fun traceLeftJumpAscent (x, y, remainingJump, platTree, env, state) = - if remainingJump >= Constants.jumpLimit - Constants.enemySize then - traceLeftDescent (x, y, platTree, env, state) - else - let - val width = Constants.moveEnemyBy - val height = Constants.worldHeight - y - - val state = Horizontal.foldRegion - (x, y, width, height, env, state, platTree) - - val nextX = x - Constants.moveEnemyBy - val nextY = y - Constants.moveEnemyBy - val nextJump = remainingJump + Constants.moveEnemyBy - in - traceLeftJumpAscent (nextX, nextY, nextJump, platTree, env, state) - end - - fun traceLeftJump (currentPlat: GameType.platform, env, state, platTree) = - let - val {x, y, ...} = currentPlat - val x = x + Constants.enemySize - in - traceLeftJumpAscent (x, y, 0, platTree, env, state) - end - - fun start (currentPlat: GameType.platform, env: env, state, platformTree) = - let - val {x, y, width, ...} = currentPlat - - (* calculate area to search in y axis *) - val searchY = y - Constants.jumpLimit - val height = Constants.worldHeight - searchY - - val state = Vertical.foldRegion - (x, searchY, width, height, env, state, platformTree) - - val state = traceRightJump (currentPlat, env, state, platformTree) - in - traceLeftJump (currentPlat, env, state, platformTree) - end -end diff --git a/fcore/enemy-behaviour.sml b/fcore/enemy-behaviour.sml index 719e205..020fecd 100644 --- a/fcore/enemy-behaviour.sml +++ b/fcore/enemy-behaviour.sml @@ -161,8 +161,8 @@ struct then (* can jump from same position enemy is at *) let - (* since we want to jump vertically and not risk falling off by - * jumping + moving either left or right, make enemy stay still *) + (* since we want to jump vertically and not risk falling off by + * jumping + moving either left or right, make enemy stay still *) in case eyAxis of ON_GROUND => EnemyPatch.W_Y_AXIS (JUMPING 0) :: acc @@ -208,8 +208,7 @@ struct isBetween (platX, ecx, platFinishX) then (* can jump from same position enemy is at *) - let - in + let in case eyAxis of ON_GROUND => EnemyPatch.W_Y_AXIS DROP_BELOW_PLATFORM :: acc | FALLING => EnemyPatch.W_Y_AXIS DROP_BELOW_PLATFORM :: acc @@ -284,14 +283,12 @@ struct (* if only one side in x direction overlaps with platform, * then move enemy left/right to make them fully overlap with platform *) - fun getHorizontalLandingPatches (enemy, nextPlatform, acc) = - acc + fun getHorizontalLandingPatches (enemy, nextPlatform, acc) = acc fun getFallingPatches (enemy, newPlatformID, platforms, acc) = - EnemyPatch.W_NEXT_PLAT_ID ~1 :: acc + EnemyPatch.W_NEXT_PLAT_ID ~1 :: acc - fun getJumpLandingPatches (enemy, nextPlatformID, platforms, acc) = - acc + fun getJumpLandingPatches (enemy, nextPlatformID, platforms, acc) = acc fun getLandingPatches (newPlatformID, platforms, enemy, acc) = case #yAxis enemy of @@ -314,7 +311,7 @@ struct | _ => getPatrollPatches (enemy, wallTree, platformTree, acc) fun getFollowPatches - (player: player, enemy, wallTree, platformTree, platforms, acc) = + (player: player, enemy, wallTree, platformTree, platforms, graph, acc) = let val pID = #platID player val eID = #platID enemy @@ -331,7 +328,8 @@ struct startPatrolPatches (player, enemy, wallTree, platformTree, acc) else let - val bestPath = PathFinding.start (pID, eID, platforms, platformTree) + val bestPath = PathFinding.start + (pID, eID, platforms, platformTree, graph) in case bestPath of nextPlatformID :: _ => @@ -355,7 +353,7 @@ struct end fun getVariantPatches - (enemy, walls, wallTree, platforms, platformTree, player, acc) = + (enemy, walls, wallTree, platforms, platformTree, player, graph, acc) = let open EnemyVariants in @@ -363,6 +361,6 @@ struct PATROL_SLIME => getPatrollPatches (enemy, wallTree, platformTree, acc) | FOLLOW_SIME => getFollowPatches - (player, enemy, wallTree, platformTree, platforms, acc) + (player, enemy, wallTree, platformTree, platforms, graph, acc) end end diff --git a/fcore/enemy.sml b/fcore/enemy.sml index 1c32d6d..3169e2e 100644 --- a/fcore/enemy.sml +++ b/fcore/enemy.sml @@ -28,6 +28,7 @@ struct , platforms , platformTree , player + , graph ) = let val {x, y, health, ...} = enemy @@ -47,7 +48,15 @@ struct (* get patches specific to this type of enemy *) val patches = EnemyBehaviour.getVariantPatches - (enemy, walls, wallTree, platforms, platformTree, player, []) + ( enemy + , walls + , wallTree + , platforms + , platformTree + , player + , graph + , [] + ) val enemy = EnemyPatch.withPatches (enemy, patches) val patches = EnemyPhysics.getPhysicsPatches enemy @@ -66,7 +75,7 @@ struct (* get patches specific to this type of enemy *) val patches = EnemyBehaviour.getVariantPatches - (enemy, walls, wallTree, platforms, platformTree, player, []) + (enemy, walls, wallTree, platforms, platformTree, player, graph, []) val enemy = EnemyPatch.withPatches (enemy, patches) val patches = EnemyPhysics.getPhysicsPatches enemy @@ -92,6 +101,7 @@ struct , platforms , platformTree , player + , graph ) = if pos < 0 then Vector.fromList acc @@ -107,6 +117,7 @@ struct , platforms , platformTree , player + , graph ) in filterProjectileCollisions @@ -119,6 +130,7 @@ struct , platforms , platformTree , player + , graph ) end @@ -136,6 +148,7 @@ struct , platforms , platformTree , player + , graph ) = if pos < 0 then Vector.fromList acc @@ -155,6 +168,7 @@ struct , platforms , platformTree , player + , graph ) in filterWhenAttacked @@ -168,6 +182,7 @@ struct , platforms , platformTree , player + , graph ) end diff --git a/fcore/game-type.sml b/fcore/game-type.sml index a4a5b73..964c6f1 100644 --- a/fcore/game-type.sml +++ b/fcore/game-type.sml @@ -66,6 +66,7 @@ sig , platforms: platform vector , platformTree: QuadTree.t , enemies: enemy vector + , graph: PlatSet.elem vector vector } val initial: game_type @@ -140,6 +141,7 @@ struct , platforms: platform vector , platformTree: QuadTree.t , enemies: enemy vector + , graph: PlatSet.elem vector vector } val initial: game_type = @@ -217,27 +219,49 @@ struct } val enemy2 = { id = 2 - , x = 555 - , y = 945 + , x = 777 + , y = 333 , health = 1 , xAxis = MOVE_LEFT , yAxis = FALLING - , variant = EnemyVariants.PATROL_SLIME + , variant = EnemyVariants.FOLLOW_SLIME , platID = ~1 , nextPlatID = ~1 } val enemy3 = { id = 3 - , x = 979 - , y = 945 + , x = 555 + , y = 135 , health = 1 , xAxis = MOVE_RIGHT , yAxis = FALLING - , variant = EnemyVariants.PATROL_SLIME + , variant = EnemyVariants.FOLLOW_SLIME , platID = ~1 , nextPlatID = ~1 } - val enemies = Vector.fromList [enemy1] + val enemy4 = + { id = 4 + , x = 555 + , y = 555 + , health = 1 + , xAxis = MOVE_RIGHT + , yAxis = FALLING + , variant = EnemyVariants.FOLLOW_SLIME + , platID = ~1 + , nextPlatID = ~1 + } + val enemy5 = + { id = 5 + , x = 199 + , y = 333 + , health = 1 + , xAxis = MOVE_RIGHT + , yAxis = FALLING + , variant = EnemyVariants.FOLLOW_SLIME + , platID = ~1 + , nextPlatID = ~1 + } + val enemies = Vector.fromList [enemy1, enemy2, enemy3, enemy4, enemy5] in { player = player , walls = walls @@ -245,6 +269,7 @@ struct , platforms = platforms , platformTree = platformTree , enemies = enemies + , graph = Graph.fromPlatforms (platforms, platformTree) } end end diff --git a/fcore/game-update.sml b/fcore/game-update.sml index cb390c3..6df3604 100644 --- a/fcore/game-update.sml +++ b/fcore/game-update.sml @@ -2,7 +2,8 @@ structure GameUpdate = struct fun update (game, input) = let - val {player, walls, wallTree, platforms, platformTree, enemies} = game + val {player, walls, wallTree, platforms, platformTree, enemies, graph} = + game val player = Player.runPhysicsAndInput (game, input) @@ -18,6 +19,7 @@ struct , wallTree , platforms , platformTree + , graph ) in { player = player @@ -26,6 +28,7 @@ struct , platforms = platforms , platformTree = platformTree , enemies = enemies + , graph = graph } end end diff --git a/fcore/graph.sml b/fcore/graph.sml index 56b8791..bc443b7 100644 --- a/fcore/graph.sml +++ b/fcore/graph.sml @@ -22,8 +22,11 @@ struct PlatSet.insert (platSet, insRecord, pos) end - type env = - {platforms: GameType.platform vector, currentPlat: GameType.platform} + (* duplicate of type alias declared in GameType. + * SML's structural type system is nice for allowing this. *) + type platform = {id: int, x: int, y: int, width: int} + + type env = {platforms: platform vector, currentPlat: platform} structure Vertical = MakeQuadTreeFold @@ -51,7 +54,7 @@ struct type state = PlatSet.elem vector - fun minWidth (p1: GameType.platform, p2: GameType.platform) = + fun minWidth (p1: platform, p2: platform) = let val {x = p1x, width = p1w, ...} = p1 val {x = p2x, width = p2w, ...} = p2 @@ -134,7 +137,7 @@ struct traceRightJumpAscent (nextX, nextY, nextJump, platTree, env, platSet) end - fun traceRightJump (currentPlat: GameType.platform, env, platSet, platTree) = + fun traceRightJump (currentPlat: platform, env, platSet, platTree) = let val {x, y, width, ...} = currentPlat val x = x - Constants.enemySize + width @@ -176,7 +179,7 @@ struct traceLeftJumpAscent (nextX, nextY, nextJump, platTree, env, platSet) end - fun traceLeftJump (currentPlat: GameType.platform, env, platSet, platTree) = + fun traceLeftJump (currentPlat: platform, env, platSet, platTree) = let val {x, y, ...} = currentPlat val x = x + Constants.enemySize @@ -184,7 +187,7 @@ struct traceLeftJumpAscent (x, y, 0, platTree, env, platSet) end - fun start (currentPlat: GameType.platform, env: env, platSet, platformTree) = + fun start (currentPlat: platform, env: env, platSet, platformTree) = let val {x, y, width, ...} = currentPlat @@ -201,12 +204,10 @@ struct end fun build (currentPlat, platforms, platformTree) = - let - val env = {currentPlat = currentPlat, platforms = platforms} - in - start (currentPlat, env, PlatSet.empty, platformTree) + let val env = {currentPlat = currentPlat, platforms = platforms} + in start (currentPlat, env, PlatSet.empty, platformTree) end - fun fromPlatforms (platforms: GameType.platform vector, platformTree) = + fun fromPlatforms (platforms: platform vector, platformTree) = Vector.map (fn plat => build (plat, platforms, platformTree)) platforms end diff --git a/fcore/path-finding.sml b/fcore/path-finding.sml index ff47dd9..b0c66ae 100644 --- a/fcore/path-finding.sml +++ b/fcore/path-finding.sml @@ -96,55 +96,7 @@ struct end end - (* dead loop function: remove after adding graph to game type - * and moving to other loop *) - fun loop (pID, eID, platforms, platformTree, q, eKeys, eVals) = - 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) - - (* 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 - } - - (* 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) = + fun start (pID, eID, platforms, platformTree, graph) = let (* initialise data structures: the priority queue and the explored map *) val q = DistVec.fromList [{distance = 0, id = eID, comesFrom = eID}] @@ -163,6 +115,6 @@ struct * then we're done reconstructing the path and can return the path list. * *) in - loop (pID, eID, platforms, platformTree, q, eKeys, eVals) + loop (pID, eID, platforms, platformTree, q, eKeys, eVals, graph) end end diff --git a/fcore/player-enemy.sml b/fcore/player-enemy.sml index 1a483d7..8735789 100644 --- a/fcore/player-enemy.sml +++ b/fcore/player-enemy.sml @@ -12,6 +12,7 @@ struct , wallTree , platforms , platformTree + , graph ) = let val {x, y, mainAttack, attacked, ...} = player @@ -35,6 +36,7 @@ struct , platforms , platformTree , player + , graph ) (* add collided enemies to player record, @@ -64,6 +66,7 @@ struct , platforms , platformTree , player + , graph ) in (player, enemies) @@ -89,6 +92,7 @@ struct , platforms , platformTree , player + , graph ) in (player, enemies) @@ -109,6 +113,7 @@ struct , platforms , platformTree , player + , graph ) in (player, enemies) diff --git a/oms.mlb b/oms.mlb index 97f1f2b..faa8a47 100644 --- a/oms.mlb +++ b/oms.mlb @@ -19,6 +19,10 @@ end fcore/wall.sml fcore/platform.sml + +fcore/graph.sml +fcore/path-finding.sml + fcore/enemy-variants.sml fcore/game-type.sml @@ -26,10 +30,6 @@ fcore/player-patch.sml fcore/enemy-patch.sml fcore/physics.sml -fcore/build-graph.sml -fcore/graph.sml -fcore/path-finding.sml - fcore/trace-jump.sml fcore/enemy-behaviour.sml fcore/enemy.sml