From 2498a1129bd88817950c4664230ab810620b58cb Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 20:05:24 +0200 Subject: [PATCH] test: cover tests.chestsOpenRefusesALockedChestAndLeavesItsLootUntouc... --- tests/run.lua | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/tests/run.lua b/tests/run.lua index 6581fc3..d76d7fa 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -9471,6 +9471,179 @@ function tests.chestsOpenRefusesAnAlreadyEmptiedChest() local success, line = Chests.open(player, chest) eq(success, false, "opening an already-emptied chest fails") eq(#player.inventory, 0, "nothing is granted from an already-emptied chest") + ok(contains(line, "empty"), "the refusal explains the chest is already empty") +end + +function tests.chestsOpenRefusesALockedChestAndLeavesItsLootUntouched() + local player = { inventory = {} } + local chest = { locked = true, trapped = false, opened = false, loot = { Items.gold(5) } } + local success, line = Chests.open(player, chest) + eq(success, false, "opening a locked chest outright fails") + eq(#player.inventory, 0, "a refused open grants nothing") + eq(#chest.loot, 1, "a refused open leaves the chest's loot untouched") + ok(contains(line, "locked"), "the refusal explains the chest is locked") +end + +function tests.chestsUnlockWithKeySpendsExactlyOneKeyAndGrantsTheHoard() + local player = { inventory = { Items.key(), Items.key() } } + local chest = { locked = true, trapped = false, opened = false, loot = { Items.gold(5) } } + local success, line = Chests.unlockWithKey(player, chest) + ok(success, "unlocking with a carried key succeeds") + eq(#player.inventory, 2, "exactly one key is spent, leaving the spare behind") + eq(player.inventory[1].kind, Items.KEY, "the surviving inventory item is still a key") + eq(chest.locked, false, "the chest's own lock flag is cleared") + eq(chest.opened, true, "the chest is opened in the same stroke") + ok(contains(line, "unlock"), "the unlock is reported") +end + +function tests.chestsUnlockWithKeyFailsWithoutAKeyAndLeavesTheChestLocked() + local player = { inventory = {} } + local chest = { locked = true, trapped = false, opened = false, loot = { Items.gold(5) } } + local success, line = Chests.unlockWithKey(player, chest) + eq(success, false, "unlocking without a key fails") + eq(chest.locked, true, "the chest stays locked") + eq(#chest.loot, 1, "nothing is granted without a key") + ok(contains(line, "no key"), "the refusal explains there is no key") +end + +function tests.chestsUnlockWithKeyRefusesAChestThatIsNotLocked() + local player = { inventory = { Items.key() } } + local chest = { locked = false, trapped = false, opened = false, loot = { Items.gold(5) } } + local success = Chests.unlockWithKey(player, chest) + eq(success, false, "a key is never spent forcing a lock that was never there") + eq(#player.inventory, 1, "the key is not spent") +end + +function tests.chestsPickFailsImmediatelyWithoutALockpickAndSpendsNothing() + local game = stagedGame(1, arena(21, 11), 3, 6) + local chest = { locked = true, trapped = false, opened = false, loot = { Items.gold(5) } } + local success, line = Chests.pick(game, chest) + eq(success, false, "picking without a lockpick fails") + eq(chest.locked, true, "the lock is untouched without a lockpick") + eq(#chest.loot, 1, "nothing is granted without a lockpick") + ok(contains(line, "no lockpicks"), "the refusal explains there are no lockpicks") +end + +function tests.chestsPickOnSuccessOpensTheChestAndGrantsTheHoard() + local succeeded = false + for seed = 1, 100 do + local game = stagedGame(seed, arena(21, 11), 3, 6) + game.player.inventory = { Items.lockpick() } + local chest = { locked = true, trapped = false, opened = false, loot = { Items.gold(5) } } + local success, line = Chests.pick(game, chest) + if success then + eq(chest.opened, true, "a successful pick opens the chest") + eq(#game.player.inventory, 1, "the hoard lands in the inventory alongside the spent pick's empty slot") + ok(contains(line, "click"), "the success is reported") + succeeded = true + break + end + end + ok(succeeded, "picking eventually succeeds across a handful of seeds") +end + +function tests.chestsPickAlwaysSpendsTheLockpickWhetherItWinsOrLoses() + for seed = 1, 20 do + local game = stagedGame(seed, arena(21, 11), 3, 6) + game.player.inventory = { Items.lockpick() } + local chest = { locked = true, trapped = false, opened = false, loot = { Items.gold(5) } } + Chests.pick(game, chest) + local stillHasPick = false + for _, item in ipairs(game.player.inventory) do + if item.kind == Items.LOCKPICK then stillHasPick = true end + end + ok(not stillHasPick, "a pick attempt spends the lockpick regardless of outcome") + end +end + +function tests.chestsPickOnFailureAgainstATrappedChestSpringsItAndLeavesTheLockStanding() + local failed = false + for seed = 1, 100 do + local game = stagedGame(seed, arena(21, 11), 3, 6) + game.player.inventory = { Items.lockpick() } + local chest = { locked = true, trapped = true, opened = false, loot = { Items.gold(5) } } + local hpBefore, poisonBefore = game.player.hp, #game.player.statuses + local success, line = Chests.pick(game, chest) + if not success then + eq(chest.locked, true, "a failed pick leaves the lock standing") + eq(chest.trapped, false, "a sprung trap is spent for good") + ok(game.player.hp < hpBefore or #game.player.statuses > poisonBefore, + "a trapped chest's failed pick actually harms the player") + ok(contains(line, "trap") or contains(line, "poison") or contains(line, "needle"), + "the trap's bite is reported") + failed = true + break + end + end + ok(failed, "picking a trapped chest eventually fails across a handful of seeds") +end + +function tests.chestsPickOnFailureAgainstAnUntrappedChestJustWastesThePick() + local failed = false + for seed = 1, 100 do + local game = stagedGame(seed, arena(21, 11), 3, 6) + game.player.inventory = { Items.lockpick() } + local chest = { locked = true, trapped = false, opened = false, loot = { Items.gold(5) } } + local hpBefore = game.player.hp + local success, line = Chests.pick(game, chest) + if not success then + eq(chest.locked, true, "a failed pick leaves the lock standing") + eq(game.player.hp, hpBefore, "an untrapped chest's failed pick costs nothing but the pick itself") + ok(contains(line, "slips"), "the failure is reported") + failed = true + break + end + end + ok(failed, "picking an untrapped chest eventually fails across a handful of seeds") +end + +function tests.chestsOddsForClimbsWithLevelButNeverReachesTheDenominator() + local num1, den1 = Chests.oddsFor({ level = 1 }) + eq(num1, Chests.PICK_BASE_NUMERATOR, "a fresh level-one player rolls the catalog's base odds") + local num2, den2 = Chests.oddsFor({ level = 100 }) + eq(den2, den1, "the denominator never changes with level") + ok(num2 < den2, "even an extremely high level never reaches a guaranteed pick") + eq(num2, Chests.PICK_BASE_DENOMINATOR - 1, "the numerator caps a single point shy of the denominator") +end + +function tests.gameOpenChestOpensAnUnlockedChestOnSightAndCountsIt() + local game = stagedGame(1, arena(21, 11), 3, 6) + local chest = { x = 3, y = 6, locked = false, trapped = false, opened = false, loot = { Items.gold(5) } } + game.level.chests = { chest } + local before = game.stats.chestsOpened + local success = game:openChest() + ok(success, "opening an unlocked chest under the player succeeds") + eq(chest.opened, true, "the chest is marked opened") + eq(game.stats.chestsOpened, before + 1, "a successful open is tallied for meta scoring") +end + +function tests.gameOpenChestUnlocksWithACarriedKeyBeforeEverTryingALockpick() + local game = stagedGame(1, arena(21, 11), 3, 6) + local chest = { x = 3, y = 6, locked = true, trapped = true, opened = false, loot = { Items.gold(5) } } + game.level.chests = { chest } + game.player.inventory = { Items.key(), Items.lockpick() } + local success = game:openChest() + ok(success, "opening a locked chest with a key on hand succeeds") + eq(chest.opened, true, "the chest opens") + local stillHasPick, stillHasKey = false, false + for _, item in ipairs(game.player.inventory) do + if item.kind == Items.LOCKPICK then stillHasPick = true end + if item.kind == Items.KEY then stillHasKey = true end + end + ok(stillHasPick, "the lockpick is never touched once a key is on hand") + ok(not stillHasKey, "the key is the one actually spent") +end + +function tests.gameOpenChestFallsBackToALockpickWhenNoKeyIsCarried() + local game = stagedGame(1, arena(21, 11), 3, 6) + local chest = { x = 3, y = 6, locked = true, trapped = false, opened = false, loot = { Items.gold(5) } } + game.level.chests = { chest } + game.player.inventory = { Items.lockpick() } + game:openChest() + local stillHasPick = false + for _, item in ipairs(game.player.inventory) do + if item.kind == Items.LOCKPICK then stillHasPick = true end + end end local order = {