137 lines
4.6 KiB
Python
137 lines
4.6 KiB
Python
import math
|
|
import sys
|
|
|
|
from .constants import tileSize
|
|
from .sim.world import World
|
|
from .sim.inputCommand import InputCommand
|
|
from .sim.builtinLevels import buildMeadow
|
|
from .sim.gameState import Game, MENU, PLAYING, PAUSED
|
|
from .sim import physics
|
|
|
|
|
|
def groundSurfaceY(level):
|
|
for row in range(level.rows):
|
|
for col in range(level.cols):
|
|
if level.solidAt(col, row):
|
|
return row * tileSize
|
|
return level.pixelHeight()
|
|
|
|
|
|
def penetratesSolid(world):
|
|
shrunk = world.player.aabb.inflated(-0.75, -0.75)
|
|
for tileBox in physics.solidTilesOverlapping(world.level, shrunk):
|
|
if shrunk.intersects(tileBox):
|
|
return True
|
|
return False
|
|
|
|
|
|
def driveInput(world, frame, heading):
|
|
player = world.player
|
|
jump = frame % 22 == 0 or player.onWallLeft or player.onWallRight
|
|
dash = frame % 75 == 0
|
|
command = InputCommand(
|
|
left=heading < 0,
|
|
right=heading > 0,
|
|
jump=jump,
|
|
jumpHeld=(frame % 22) < 10,
|
|
dash=dash,
|
|
run=True,
|
|
)
|
|
return command
|
|
|
|
|
|
def runSmoke(totalFrames=640):
|
|
world = World(buildMeadow())
|
|
floorTop = groundSurfaceY(world.level)
|
|
heading = 1
|
|
stats = {
|
|
"stomps": 0,
|
|
"maxBottom": world.player.bottom,
|
|
"minHp": world.player.hp,
|
|
"damageTaken": 0,
|
|
"coins": 0,
|
|
"framesRun": 0,
|
|
}
|
|
previousScoreFromStomp = 0
|
|
previousHp = world.player.hp
|
|
|
|
for frame in range(totalFrames):
|
|
if world.player.onWallRight and heading > 0:
|
|
heading = -1
|
|
elif world.player.onWallLeft and heading < 0:
|
|
heading = 1
|
|
if frame % 160 == 159:
|
|
heading *= -1
|
|
|
|
command = driveInput(world, frame, heading)
|
|
world.step(command)
|
|
stats["framesRun"] += 1
|
|
|
|
player = world.player
|
|
if not math.isfinite(player.x) or not math.isfinite(player.y):
|
|
raise AssertionError("player position not finite at frame %d" % frame)
|
|
if not (0 <= player.hp <= player.maxHp):
|
|
raise AssertionError("hp %d out of range at frame %d" % (player.hp, frame))
|
|
if player.alive and world.active and penetratesSolid(world):
|
|
raise AssertionError("player penetrated solid tile at frame %d" % frame)
|
|
if player.alive and world.active and player.bottom > floorTop + tileSize * 4 + 2:
|
|
raise AssertionError("player fell through floor at frame %d bottom=%.1f" % (frame, player.bottom))
|
|
if player.bottom > world.level.pixelHeight() + tileSize * 3:
|
|
raise AssertionError("player left world at frame %d" % frame)
|
|
|
|
if player.hp < previousHp:
|
|
stats["damageTaken"] += previousHp - player.hp
|
|
previousHp = player.hp if player.alive else player.maxHp
|
|
stats["maxBottom"] = max(stats["maxBottom"], player.bottom)
|
|
stats["minHp"] = min(stats["minHp"], player.hp)
|
|
|
|
stats["coins"] = world.coins
|
|
stats["enemiesLeft"] = len(world.enemies)
|
|
stats["enemiesStart"] = len(world.level.enemies)
|
|
stats["score"] = world.score
|
|
stats["sounds"] = world.audio.totalEmitted
|
|
stats["lives"] = world.lives
|
|
stats["gameOver"] = world.gameOver
|
|
stats["floorTop"] = floorTop
|
|
return stats
|
|
|
|
|
|
def checkFsm():
|
|
game = Game([buildMeadow()])
|
|
transitions = []
|
|
assert game.mode == MENU
|
|
game.update(InputCommand(confirm=True))
|
|
assert game.mode == PLAYING
|
|
transitions.append("menu->playing")
|
|
for _ in range(30):
|
|
game.update(InputCommand(right=True))
|
|
game.update(InputCommand(pause=True))
|
|
assert game.mode == PAUSED
|
|
transitions.append("playing->paused")
|
|
game.update(InputCommand(pause=True))
|
|
assert game.mode == PLAYING
|
|
transitions.append("paused->playing")
|
|
return transitions
|
|
|
|
|
|
def main():
|
|
stats = runSmoke()
|
|
fsm = checkFsm()
|
|
print("SMOKE RESULT")
|
|
print(" frames run : %d" % stats["framesRun"])
|
|
print(" enemies start/left : %d / %d" % (stats["enemiesStart"], stats["enemiesLeft"]))
|
|
print(" score : %d" % stats["score"])
|
|
print(" coins collected : %d" % stats["coins"])
|
|
print(" damage taken (hp) : %d" % stats["damageTaken"])
|
|
print(" min hp / lives : %d / %d" % (stats["minHp"], stats["lives"]))
|
|
print(" max player.bottom : %.1f (floorTop=%d)" % (stats["maxBottom"], stats["floorTop"]))
|
|
print(" sound events : %d" % stats["sounds"])
|
|
print(" fsm transitions : %s" % ", ".join(fsm))
|
|
print("INVARIANTS HELD: hp in range, no solid penetration, no fall-through, positions finite")
|
|
print("OK")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|