test: cover tests.amuletSpawnsOnFinalFloorOnly

This commit is contained in:
dualfroz committed 2026-09-08 21:00:13 +02:00
1 parent abc27cd5e1
commit e7b64c1e92
1 file changed
+173
+173
View File
@@ -2034,6 +2034,179 @@ function tests.autoplayDescends()
eq(again.turn, run.turn, "the autopilot takes the same number of turns")
end
function tests.amuletSpawnsOnFinalFloorOnly()
local finalLevel = Builder.build(Builder.MAX_DEPTH, Rng.new(999), {})
local finalCount = 0
for _, ground in ipairs(finalLevel.floorItems) do
if ground.item.kind == Items.AMULET then finalCount = finalCount + 1 end
end
eq(finalCount, 1, "exactly one amulet is placed on the final floor")
local shallowLevel = Builder.build(Builder.MAX_DEPTH - 1, Rng.new(999), {})
local shallowCount = 0
for _, ground in ipairs(shallowLevel.floorItems) do
if ground.item.kind == Items.AMULET then shallowCount = shallowCount + 1 end
end
eq(shallowCount, 0, "no amulet appears above the final floor")
end
function tests.descendIsBlockedAtTheFinalFloor()
local game = Game.new(555)
game.depth = game.maxDepth
game.level = game.dungeon:get(game.maxDepth)
game.player.x, game.player.y = game.level.downX, game.level.downY
local result = game:descend()
eq(result, false, "descend fails once at the final floor")
eq(game.depth, game.maxDepth, "depth is unchanged after a blocked descend")
ok(contains(game.log:last(), "rock"), "a message explains why the descent is blocked")
end
function tests.ascendWithoutAmuletDoesNotWin()
local game = Game.new(7)
game.player.x, game.player.y = game.level.upX, game.level.upY
local result = game:ascend()
eq(result, true, "climbing out at depth 1 is still a valid move")
eq(game.won, false, "climbing out without the amulet does not win the game")
end
function tests.ascendWithAmuletWinsTheGame()
local game = Game.new(7)
game.player.inventory[#game.player.inventory + 1] = Items.amulet()
game.player.x, game.player.y = game.level.upX, game.level.upY
local result = game:ascend()
eq(result, true, "climbing out at depth 1 with the amulet succeeds")
eq(game.won, true, "carrying the amulet to the surface wins the game")
ok(contains(game.log:last(), "win"), "the victory is logged")
end
function tests.wonGameHaltsTheAutopilot()
local game = Game.new(7)
game.won = true
local turnBefore = game.turn
game:autoStep()
eq(game.turn, turnBefore, "autoStep is a no-op once the game is already won")
end
function tests.autopilotFetchesTheAmuletAndWins()
local function playout(seed)
local game = Game.new(seed, { maxDepth = 3 })
for _ = 1, 900 do
if not game.player:isAlive() or game.won then break end
game:autoStep()
end
return game
end
local run = playout(67)
ok(run.player:isAlive(), "the autopilot survives the round trip on this seed")
eq(run.won, true, "the autopilot retrieves the amulet and escapes")
eq(run.depth, 1, "winning leaves the player back at depth 1")
ok(run:hasAmulet(), "the amulet is still in the winning player's inventory")
local again = playout(67)
eq(again.turn, run.turn, "the winning playthrough takes the same number of turns on the same seed")
end
function tests.shopPriceCatalogScalesWithPower()
local base = Shop.priceOf(Items.weapon("dagger", 2))
local strong = Shop.priceOf(Items.weapon("battle axe", 5))
ok(strong > base, "a more powerful weapon costs more at the shop")
eq(Shop.priceOf(Items.potionOf("heal")), 15, "a catalogued potion has its fixed price")
eq(Shop.sellValue(Items.potionOf("heal")), 7, "selling nets half the catalog price, rounded down")
end
function tests.shopStockRollsAffordableWares()
local wares = Shop.stock(Rng.new(7))
ok(#wares >= 3 and #wares <= 5, "a shop rolls three to five wares")
for _, item in ipairs(wares) do
ok(Shop.priceOf(item) > 0, "every ware has a positive price")
end
end
function tests.goldPickupAddsToWalletNotInventory()
local game = stagedGame(1, arena(21, 11), 3, 3)
local before = #game.player.inventory
local goldBefore = game.player.gold
game.level.floorItems = { { x = 4, y = 3, item = Items.gold(35) } }
game:movePlayer(1, 0)
eq(game.player.gold, goldBefore + 35, "walking onto gold adds it to the wallet")
eq(#game.player.inventory, before, "gold never occupies an inventory slot")
eq(#game.level.floorItems, 0, "the gold pile is removed from the floor")
end
function tests.affordableWareIsBoughtAutomaticallyOnPickup()
local game = stagedGame(1, arena(21, 11), 3, 3)
local keeper = Monsters.create(Monsters.byKey.shopkeeper, 1, 10, 10, 4)
game.level.entities = { keeper }
game.player.gold = 50
local before = #game.player.inventory
game.level.floorItems = { { x = 4, y = 3, item = Items.potionOf("heal"), price = 15, shopId = keeper.id } }
game:movePlayer(1, 0)
eq(game.player.gold, 35, "the ware's stamped price is deducted from the wallet")
eq(#game.player.inventory, before + 1, "the bought ware joins the inventory")
eq(#game.level.floorItems, 0, "the purchased ware leaves the ground")
end
function tests.unaffordableWareStaysOnTheGround()
local game = stagedGame(1, arena(21, 11), 3, 3)
local keeper = Monsters.create(Monsters.byKey.shopkeeper, 1, 10, 10, 4)
game.level.entities = { keeper }
game.player.gold = 5
local before = #game.player.inventory
game.level.floorItems = { { x = 4, y = 3, item = Items.potionOf("heal"), price = 15, shopId = keeper.id } }
game:movePlayer(1, 0)
eq(game.player.gold, 5, "an unaffordable ware is never bought, so no gold changes hands")
eq(#game.player.inventory, before, "the ware never reaches the inventory")
eq(#game.level.floorItems, 1, "the unbought ware is left in place")
end
function tests.deadKeeperMakesWaresFreeToLoot()
local game = stagedGame(1, arena(21, 11), 3, 3)
local keeper = Monsters.create(Monsters.byKey.shopkeeper, 1, 10, 10, 4)
keeper.hp = 0
game.level.entities = { keeper }
game.player.gold = 0
local before = #game.player.inventory
game.level.floorItems = { { x = 4, y = 3, item = Items.potionOf("heal"), price = 15, shopId = keeper.id } }
game:movePlayer(1, 0)
eq(game.player.gold, 0, "looting a dead keeper's stock costs nothing")
eq(#game.player.inventory, before + 1, "the ware is picked up normally once its keeper is dead")
end
function tests.stealingFromShopTurnsTheKeeperHostile()
local game = stagedGame(1, arena(21, 11), 3, 3)
local keeper = Monsters.create(Monsters.byKey.shopkeeper, 1, 10, 10, 4)
game.level.entities = { keeper }
game.player.gold = 0
local before = #game.player.inventory
game.level.floorItems = { { x = 4, y = 3, item = Items.scroll("identify"), price = 18, shopId = keeper.id } }
local stolen = game:stealFromShop(1)
eq(stolen, true, "stealing succeeds against a live keeper's ware")
eq(game.player.gold, 0, "theft costs no gold")
eq(#game.player.inventory, before + 1, "the stolen item joins the inventory")
eq(keeper.hostile, true, "the keeper turns hostile after the theft")
ok(contains(game.log:last(), "steal"), "the theft is logged")
end
function tests.hostileShopkeeperChasesLikeAnyMonster()
local game = stagedGame(1, arena(21, 11), 3, 6)
local keeper = Monsters.create(Monsters.byKey.shopkeeper, 1, 8, 6, 4)
keeper.hostile = true
game.level.entities = { keeper }
Ai.act(game, keeper)
eq(keeper.x, 7, "a hostile shopkeeper closes in on the player like a chaser")
end
function tests.passiveShopkeeperIgnoresTheAdjacentPlayer()
local game = stagedGame(1, arena(21, 11), 3, 6)
local keeper = Monsters.create(Monsters.byKey.shopkeeper, 1, 4, 6, 4)
game.level.entities = { keeper }
local hpBefore = game.player.hp
Ai.act(game, keeper)
eq(keeper.x, 4, "an un-robbed shopkeeper does not move")
eq(game.player.hp, hpBefore, "an un-robbed shopkeeper does not attack even when adjacent")
end
end
local order = {
"rngDeterminism", "mapDeterminism", "tilesRegistry", "fovKnownScene", "losSightLine",
"astarPathing", "combatResolution", "itemsAndEquipment", "affixes", "statusEffects",