test: cover tests.questBoardRendersItsGlyphAndFlipsOnceClaimed

This commit is contained in:
dualfroz committed 2026-09-10 14:18:36 +02:00
1 parent 61296a4dd3
commit 24d2a4288f
1 file changed
+173
+173
View File
@@ -5146,6 +5146,179 @@ function tests.autopilotDoesNotStackASecondBountyWhileOneIsActive()
local held = Quests.generate(Rng.new(1), 4, 10)
game.player.quest = held
game.level.questBoards = { { x = 5, y = 5, claimed = false, quest = Quests.generate(Rng.new(2), 4, 10) } }
game:choosePlayerAction()
eq(game.player.quest, held, "the autopilot keeps its held bounty rather than swapping to a new one")
eq(game.level.questBoards[1].claimed, false, "a board it cannot bind to stays unclaimed for later")
end
function tests.questBoardRendersItsGlyphAndFlipsOnceClaimed()
local game = stagedGame(1, arena(21, 11), 3, 6)
local board = { x = 5, y = 6, claimed = false, quest = Quests.generate(Rng.new(2), 4, 10) }
game.level.questBoards = { board }
game:computeVisible()
ok(game:render():find("&", 1, true) ~= nil, "an unclaimed bounty board renders its glyph")
board.claimed = true
ok(game:render():find("=", 1, true) ~= nil, "a claimed bounty board renders a different glyph")
end
function tests.questFieldsSurviveSerialization()
local level = arena(21, 11)
level.questBoards = { { x = 4, y = 4, claimed = true, quest = Quests.generate(Rng.new(2), 4, 10) } }
local restored = Level.fromData(level:toData())
eq(#restored.questBoards, 1, "the quest board list survives a level round trip")
eq(restored.questBoards[1].claimed, true, "a board's claimed flag survives a round trip")
eq(restored.questBoards[1].quest.kind, Quests.CULL, "a board's bounty kind survives a round trip")
eq(restored.questBoards[1].quest.need, 4, "a board's bounty tally survives a round trip")
local game = Game.new(6)
game.player.quest = { kind = Quests.HOARD, need = 200, rewardGold = 50, rewardXp = 20 }
local path = os.tmpname()
game:save(path)
local restoredGame = Game.load(path)
os.remove(path)
eq(restoredGame.player.quest.kind, Quests.HOARD, "the player's own bound bounty survives a save/load round trip")
eq(restoredGame.player.quest.need, 200, "the bound bounty's target survives a save/load round trip")
end
function tests.builderOccasionallyPlacesAQuestBoardFromDepthTwo()
local level = Builder.build(2, Rng.new(11), {})
eq(#level.questBoards, 1, "this seed's depth-2 floor places a bounty board")
eq(level.questBoards[1].claimed, false, "a freshly built board starts unclaimed")
ok(level.questBoards[1].quest ~= nil, "a freshly built board already carries its one bounty")
end
function tests.questBoardsStayOffTheEntranceFloor()
for seed = 1, 5 do
local level = Builder.build(1, Rng.new(seed), {})
eq(#level.questBoards, 0, "no bounty board appears on the entrance floor")
end
end
function tests.metaFreshStateHasNoRenownRunsOrAchievements()
local meta = Meta.new()
eq(meta.renown, 0, "a fresh meta state banks no renown")
eq(meta.totalRuns, 0, "a fresh meta state has recorded no runs")
eq(meta.wins, 0, "a fresh meta state has recorded no wins")
eq(meta.bestDepth, 0, "a fresh meta state has no best depth yet")
eq(next(meta.achievements), nil, "a fresh meta state has unlocked nothing")
eq(next(meta.perks), nil, "a fresh meta state owns no perks")
end
function tests.metaRenownForScalesWithDepthBossesAndQuestsAndAddsAWinBonus()
local base = Meta.renownFor({ deepestDepth = 3, bossKills = 0, questsCompleted = 0, won = false })
eq(base, 12, "renown scales with deepest depth alone")
local withBosses = Meta.renownFor({ deepestDepth = 3, bossKills = 2, questsCompleted = 0, won = false })
eq(withBosses, base + 30, "each felled boss adds a fixed renown bonus")
local withQuests = Meta.renownFor({ deepestDepth = 3, bossKills = 0, questsCompleted = 4, won = false })
eq(withQuests, base + 32, "each resolved bounty adds a fixed renown bonus")
local won = Meta.renownFor({ deepestDepth = 3, bossKills = 0, questsCompleted = 0, won = true })
eq(won, base + 100, "escaping with the amulet adds a large flat bonus")
end
function tests.metaRecordRunBanksRenownAndAdvancesTallies()
local meta = Meta.new()
Meta.recordRun(meta, { deepestDepth = 4, kills = 2, bossKills = 0, questsCompleted = 0, artifactsFound = 0, won = false })
eq(meta.totalRuns, 1, "recordRun advances the run tally")
eq(meta.wins, 0, "an unwon run does not advance the win tally")
eq(meta.bestDepth, 4, "recordRun raises the best depth to this run's own")
eq(meta.renown, Meta.renownFor({ deepestDepth = 4, bossKills = 0, questsCompleted = 0, won = false }),
"recordRun banks exactly the renown this run's tally is worth")
Meta.recordRun(meta, { deepestDepth = 2, kills = 0, bossKills = 0, questsCompleted = 0, artifactsFound = 0, won = true })
eq(meta.totalRuns, 2, "a second run advances the run tally again")
eq(meta.wins, 1, "a won run advances the win tally")
eq(meta.bestDepth, 4, "a shallower later run does not lower the best depth")
end
function tests.metaRecordRunUnlocksMatchingAchievementsOnlyOnce()
local meta = Meta.new()
local stats = { deepestDepth = 5, kills = 1, bossKills = 1, questsCompleted = 3, artifactsFound = 1, won = true }
local unlocked = Meta.recordRun(meta, stats)
table.sort(unlocked)
local expected = { "Amulet Bearer", "Artificer", "Boss Slayer", "Bounty Hunter", "Deep Delver", "First Blood" }
table.sort(expected)
eq(table.concat(unlocked, ","), table.concat(expected, ","),
"a run meeting every threshold unlocks every achievement at once")
for _, key in ipairs({ "first_blood", "boss_slayer", "deep_delver", "bounty_hunter", "artificer", "amulet_bearer" }) do
eq(meta.achievements[key], true, key .. " is marked unlocked")
end
local again = Meta.recordRun(meta, stats)
eq(#again, 0, "an already-unlocked achievement is not re-announced on a later run")
end
function tests.metaPurchaseSpendsRenownAndMarksThePerkOwned()
local meta = Meta.new()
meta.renown = 30
local success, message = Meta.purchase(meta, "vigor")
eq(success, true, "purchase succeeds with enough banked renown")
eq(message, "unlocked Vigor", "purchase reports the perk it unlocked")
eq(meta.renown, 0, "purchase spends exactly the perk's cost")
eq(Meta.isOwned(meta, "vigor"), true, "the purchased perk is marked owned")
end
function tests.metaPurchaseFailsWithoutEnoughRenown()
local meta = Meta.new()
meta.renown = 5
local success, message = Meta.purchase(meta, "vigor")
eq(success, false, "purchase fails without enough banked renown")
eq(message, "not enough renown", "purchase explains why it failed")
eq(meta.renown, 5, "a failed purchase spends nothing")
end
function tests.metaPurchaseFailsForAnAlreadyOwnedPerk()
local meta = Meta.new()
meta.renown = 100
Meta.purchase(meta, "vigor")
local renownAfterFirst = meta.renown
local success, message = Meta.purchase(meta, "vigor")
eq(success, false, "purchase refuses to sell the same perk twice")
eq(message, "already owned", "purchase explains why it failed")
eq(meta.renown, renownAfterFirst, "a refused repurchase spends nothing further")
end
function tests.metaPurchaseFailsForAnUnknownPerk()
local meta = Meta.new()
meta.renown = 100
local success, message = Meta.purchase(meta, "not_a_real_perk")
eq(success, false, "purchase fails for a key outside the catalog")
eq(message, "no such perk", "purchase explains why it failed")
eq(meta.renown, 100, "a failed purchase spends nothing")
end
function tests.metaApplyAllAppliesEveryOwnedPerkToAFreshPlayer()
local meta = Meta.new()
meta.renown = 200
Meta.purchase(meta, "vigor")
Meta.purchase(meta, "purse")
local game = Game.new(1, { meta = meta })
eq(game.player.maxHp, 30 + 5, "the owned vigor perk raises max hp before class deltas")
eq(game.player.hp, 30 + 5, "the owned vigor perk raises current hp to match")
eq(game.player.gold, 20 + 25, "the owned deep pockets perk raises starting gold")
local plain = Game.new(1)
eq(plain.player.maxHp, 30, "a game started without a meta table gets no perk bonus")
end
function tests.metaAppliedPerksLandBeneathAClassDelta()
local meta = Meta.new()
meta.renown = 200
Meta.purchase(meta, "vigor")
local game = Game.new(1, { meta = meta, class = "fighter" })
eq(game.player.maxHp, 30 + 5 + 8, "a fighter's class bonus stacks on top of an owned perk's bonus")
end
function tests.metaLoadReturnsFreshStateForAMissingFile()
local meta = Meta.load("/tmp/warren_meta_does_not_exist_12345.sav")
eq(meta.renown, 0, "loading a missing meta file yields a fresh state")
eq(meta.totalRuns, 0, "a freshly defaulted meta state has recorded no runs")
end
function tests.metaSaveLoadRoundTrip()
local meta = Meta.new()
meta.renown = 42
Meta.recordRun(meta, { deepestDepth = 6, kills = 5, bossKills = 1, questsCompleted = 2, artifactsFound = 1, won = false })
Meta.purchase(meta, "focus")
local path = os.tmpname()
Meta.save(meta, path)
local restored = Meta.load(path)
end
local order = {