test: cover tests.gameManualStepEndsTheTurnOnlyWhenTheCommandActually...

This commit is contained in:
dualfroz committed 2026-09-13 14:46:50 +02:00
1 parent 808016f925
commit 851e57ec97
1 file changed
+173
+173
View File
@@ -13796,6 +13796,179 @@ end
-- Game:manualStep - the turn-cycle integration around commands.lua.
function tests.gameManualStepEndsTheTurnOnlyWhenTheCommandActuallySucceeds()
local game = stagedGame(1, arena(21, 11), 5, 5)
local turnBefore = game.turn
local consumesTurn = game:manualStep("north")
eq(consumesTurn, true, "a valid move spends the turn")
eq(game.turn, turnBefore + 1, "Game:manualStep advances the turn counter on a successful command")
end
function tests.gameManualStepLeavesTheTurnCounterUntouchedOnARejectedCommand()
local game = stagedGame(1, arena(21, 11), 5, 5)
local turnBefore = game.turn
local consumesTurn, message = game:manualStep("nonsense")
eq(consumesTurn, false, "an unrecognized command reports failure")
ok(message ~= nil, "the rejection carries a reason")
eq(game.turn, turnBefore, "the turn counter never advances on a rejected command")
end
function tests.gameManualStepIsIdempotentAcrossRepeatedRejectedCommandsWithinOneUnspentTurn()
local game = stagedGame(1, arena(21, 11), 5, 5)
Status.apply(game.player, Status.POISON, 5, 5)
game:manualStep("nonsense")
local hpAfterFirst = game.player.hp
local durationAfterFirst = game.player.statuses[1].duration
game:manualStep("gibberish")
eq(game.player.hp, hpAfterFirst,
"a second rejected command within the same unspent turn does not re-tick poison damage")
eq(game.player.statuses[1].duration, durationAfterFirst,
"...nor does it re-decrement the poison's own remaining duration")
end
function tests.gameManualStepStunnedPlayerIgnoresTheTypedCommandButStillSpendsTheTurn()
local game = stagedGame(1, arena(21, 11), 5, 5)
-- Duration 2, not 1: Status.tick (run inside beginTurn, before the skip
-- check) decrements duration first, so a duration-1 stun would already
-- have expired by the time Status.skipsTurn looks for it this same turn.
Status.apply(game.player, Status.STUN, 2, 0)
local turnBefore = game.turn
local px, py = game.player.x, game.player.y
local consumesTurn = game:manualStep("north")
eq(consumesTurn, true, "a stunned turn is spent regardless of the typed command")
eq(game.player.x, px, "the player never actually moves while stunned")
eq(game.player.y, py, "the player never actually moves while stunned")
eq(game.turn, turnBefore + 1, "the stunned turn still advances the turn counter")
end
function tests.gameManualStepHaltsWithoutSpendingATurnOnceTheRunIsAlreadyWon()
local game = stagedGame(1, arena(21, 11), 5, 5)
game.won = true
local turnBefore = game.turn
local consumesTurn = game:manualStep("north")
eq(consumesTurn, false, "a manual command after victory spends no turn")
eq(game.turn, turnBefore, "the turn counter never advances once the run is already won")
end
-- Game:autoExplore / commands.lua's "explore" - autoexplore, repeating
-- Game:exploreStep turn after turn inside a single call instead of forcing
-- the player to retype "explore" once per step, and stopping itself the
-- instant something worth their attention shows up.
function tests.gameAutoExploreWalksTowardUnexploredTerrainUntilTheWholeReachableAreaIsSeen()
local level = arena(25, 15)
local game = stagedGame(1, level, 3, 3)
local turnBefore = game.turn
local consumesTurn, message = game:autoExplore()
eq(consumesTurn, true, "a run with real ground left to cover spends turns")
eq(message, "you have finished exploring", "a completed run reports it finished")
ok(game.turn > turnBefore, "autoExplore actually advances the turn counter")
local map = level.map
local allSeen = true
for y = 2, 14 do
for x = 2, 24 do
if not level.explored[map:index(x, y)] then allSeen = false end
end
end
ok(allSeen, "every walkable cell in the reachable room ends up explored")
end
function tests.gameAutoExploreReportsNothingLeftWithoutSpendingATurnWhenAlreadyFullyExplored()
local game = stagedGame(1, arena(11, 9), 5, 5)
local turnBefore = game.turn
local consumesTurn, message = game:autoExplore()
eq(consumesTurn, false, "a room already fully visible from the start spends no turn")
eq(message, "there is nothing left to explore", "the refusal explains why")
eq(game.turn, turnBefore, "the turn counter never advances")
end
function tests.gameAutoExploreHaltsWithoutSpendingATurnOnceTheRunIsAlreadyWon()
local game = stagedGame(1, arena(21, 11), 5, 5)
game.won = true
local turnBefore = game.turn
local consumesTurn, message = game:autoExplore()
eq(consumesTurn, false, "an autoexplore attempt after victory spends no turn")
eq(message, "there is nothing left to do", "the refusal matches Game:manualStep's own halted message")
eq(game.turn, turnBefore, "the turn counter never advances once the run is already won")
end
function tests.gameAutoExploreStopsTheInstantThePlayerTakesDamage()
local game = stagedGame(1, arena(25, 15), 3, 3)
Status.apply(game.player, Status.POISON, 5, 4)
local turnBefore = game.turn
local hpBefore = game.player.hp
local consumesTurn, message = game:autoExplore()
eq(consumesTurn, true, "the interrupted turn itself still spends")
eq(message, "you stop exploring - you have taken damage", "the interrupt explains itself")
eq(game.turn, turnBefore + 1, "exactly the one turn that took the damage was spent")
eq(game.player.hp, hpBefore - 4, "the poison tick's own damage lands normally before the interrupt fires")
end
function tests.gameAutoExploreRefusesToStartWhileAHostileIsAlreadyInSight()
local level = arena(21, 11)
local game = stagedGame(1, level, 5, 5)
local orc = Monsters.create(Monsters.byKey.orc, 1, 7, 5, 1)
orc.awareness = Stealth.DORMANT
level.entities = { orc }
game:computeVisible()
local turnBefore = game.turn
local consumesTurn, message = game:autoExplore()
eq(consumesTurn, false, "a hostile already visible refuses to even start")
eq(message, "a monster is already in sight", "the refusal names the reason")
eq(game.turn, turnBefore, "the turn counter never advances")
end
-- The room is wide open (not a corridor) on purpose: Game:exploreStep's own
-- BFS treats an entity's tile as unreachable (see game.lua), so a monster
-- sitting in a one-wide corridor would simply wall off the far end and
-- "nothing left to explore" would fire before the player ever got close
-- enough to see it - a real, separately-confirmed property of exploreStep,
-- not the thing this test means to exercise. A big room lets the BFS route
-- around the orc's single tile instead, so sight is what actually stops
-- this run. Both the room's height and the orc's row sit well within the
-- player's initial vision radius (8), so the whole approach plays out along
-- a single row and Stealth.WAKE_RADIUS (6, smaller than that same vision
-- radius) guarantees the orc is seen before it is ever close enough to roll
-- a wake of its own (see stealth.lua's canSpot) - the interrupt below is
-- proven to fire on sight alone, never on contact, without relying on RNG.
function tests.gameAutoExploreStopsTheMomentAHostileComesIntoSightWithoutEngagingIt()
local level = arena(30, 11)
local game = stagedGame(2, level, 3, 6)
local orc = Monsters.create(Monsters.byKey.orc, 1, 25, 6, 1)
orc.awareness = Stealth.DORMANT
level.entities = { orc }
local turnBefore = game.turn
local hpBefore = game.player.hp
local orcHpBefore = orc.hp
local consumesTurn, message = game:autoExplore()
eq(consumesTurn, true, "real ground was covered before the interrupt fired")
eq(message, "you stop exploring - a monster is in sight", "the interrupt names a sighted hostile")
eq(game.turn, turnBefore + 14, "exactly the steps needed to bring the orc within vision range 8 were taken")
eq(game.player.hp, hpBefore, "no damage was taken - explore stops on sight, not on contact")
eq(orc.hp, orcHpBefore, "the orc is only spotted, never engaged")
ok(game.visible[level.map:index(orc.x, orc.y)], "the orc really is the thing that got seen")
eq(orc.x, 25, "the still-dormant orc never took a single step of its own")
eq(orc.awareness, Stealth.DORMANT, "and never woke up at all")
end
function tests.gameManualStepDrivenExploreSpendsExactlyAsManyTurnsAsCallingGameAutoExploreDirectly()
local direct = stagedGame(9, arena(25, 15), 3, 3)
direct:autoExplore()
local viaManualStep = stagedGame(9, arena(25, 15), 3, 3)
viaManualStep:manualStep("explore")
eq(viaManualStep.turn, direct.turn,
"routing explore through Game:manualStep must not end the turn a second time on top of what " ..
"autoExplore already closed itself")
end
function tests.commandsApplyExploreDrivesGameAutoExploreAndReturnsItsResult()
local game = stagedGame(1, arena(11, 9), 5, 5)
local consumesTurn, message = Commands.apply(game, "explore")
eq(consumesTurn, false, "a room already fully visible from the start spends no turn")
eq(message, "there is nothing left to explore", "the command surfaces Game:autoExplore's own message verbatim")
end
function tests.commandsApplyHelpMentionsExplore()
end
local order = {