test: cover tests.blocksUpdateGatesOpensOnlyWhileAPlatedBlockHoldsItA...
This commit is contained in:
1 parent
aaa221f6bf
commit
a1cacb21bb
1 file changed
+173
+173
@@ -11201,6 +11201,179 @@ function tests.blocksPushFailsAgainstAWallOrAnotherBlockOrAnEntity()
|
||||
eq(Blocks.push(level, block, 1, 0), false, "a shove into a wall fails outright")
|
||||
eq(block.x, 3, "a failed shove leaves the block exactly where it was")
|
||||
|
||||
level.map:set(4, 3, Tiles.FLOOR)
|
||||
level.blocks = { block, { x = 4, y = 3 } }
|
||||
eq(Blocks.push(level, block, 1, 0), false, "a shove into another block fails outright")
|
||||
|
||||
level.blocks = { block }
|
||||
level.entities = { Entity.new({ x = 4, y = 3, glyph = "r", name = "rat", kind = "monster", hp = 5, attack = 1, defense = 0 }) }
|
||||
eq(Blocks.push(level, block, 1, 0), false, "a shove into a living entity fails outright")
|
||||
end
|
||||
|
||||
function tests.blocksUpdateGatesOpensOnlyWhileAPlatedBlockHoldsItAndReseals()
|
||||
local level = arena(9, 9)
|
||||
level.map:set(6, 3, Tiles.GATE_CLOSED)
|
||||
level.gates = { { x = 6, y = 3, open = false } }
|
||||
level.plates = { { x = 4, y = 3, gateX = 6, gateY = 3 } }
|
||||
local block = { x = 3, y = 3 }
|
||||
level.blocks = { block }
|
||||
Blocks.updateGates(level)
|
||||
eq(level.map:get(6, 3), Tiles.GATE_CLOSED, "the gate stays sealed with no block on its plate")
|
||||
eq(level.gates[1].open, false, "the gate's own open field matches its sealed tile")
|
||||
|
||||
Blocks.push(level, block, 1, 0)
|
||||
Blocks.updateGates(level)
|
||||
eq(level.map:get(6, 3), Tiles.GATE_OPEN, "the gate swings open the instant its plate takes the block's weight")
|
||||
eq(level.gates[1].open, true, "the gate's own open field matches its opened tile")
|
||||
|
||||
Blocks.push(level, block, 1, 0)
|
||||
Blocks.updateGates(level)
|
||||
eq(level.map:get(6, 3), Tiles.GATE_CLOSED, "the gate reseals the instant the block is shoved off its plate")
|
||||
eq(level.gates[1].open, false, "the gate's own open field matches its resealed tile")
|
||||
end
|
||||
|
||||
function tests.movePlayerShovesABlockAheadAndTheGateItHoldsOpenReactsImmediately()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||
game.level.map:set(6, 6, Tiles.GATE_CLOSED)
|
||||
game.level.gates = { { x = 6, y = 6, open = false } }
|
||||
game.level.plates = { { x = 5, y = 6, gateX = 6, gateY = 6 } }
|
||||
game.level.blocks = { { x = 4, y = 6 } }
|
||||
game:movePlayer(1, 0)
|
||||
eq(game.player.x, 4, "the player steps into the cell the block just vacated")
|
||||
eq(game.level.blocks[1].x, 5, "the shove relocates the block by exactly one cell")
|
||||
eq(game.level.blocks[1].y, 6, "the shove does not drift off the push axis")
|
||||
eq(game.level.map:get(6, 6), Tiles.GATE_OPEN, "the linked gate opens the instant the block lands on its plate")
|
||||
ok(contains(game.log:last(), "shove"), "shoving the block is logged")
|
||||
end
|
||||
|
||||
function tests.movePlayerCannotShoveABlockIntoAWallAndDoesNotMoveEither()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||
game.level.map:set(5, 6, Tiles.WALL)
|
||||
game.level.blocks = { { x = 4, y = 6 } }
|
||||
local moved = game:movePlayer(1, 0)
|
||||
eq(moved, false, "the player's own step fails along with the blocked shove")
|
||||
eq(game.player.x, 3, "the player does not move when the shove it would trigger fails")
|
||||
eq(game.level.blocks[1].x, 4, "the block stays put when its shove fails")
|
||||
end
|
||||
|
||||
function tests.movePlayerCannotShoveOneBlockIntoAnother()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||
game.level.blocks = { { x = 4, y = 6 }, { x = 5, y = 6 } }
|
||||
local moved = game:movePlayer(1, 0)
|
||||
eq(moved, false, "shoving a block into another block fails the whole step")
|
||||
eq(game.player.x, 3, "the player does not move when the chained shove fails")
|
||||
end
|
||||
|
||||
function tests.movePlayerSwapsPlacesWithItsOwnCompanionInsteadOfAttackingIt()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local hound = Companions.summon(game, "hound")
|
||||
local hpBefore = hound.hp
|
||||
game:movePlayer(1, 0)
|
||||
eq(game.player.x, 6, "the player steps onto the cell its companion just vacated")
|
||||
eq(game.player.y, 5, "the player's row is unchanged by the swap")
|
||||
eq(hound.x, 5, "the companion is displaced onto the player's old cell")
|
||||
eq(hound.y, 5, "the companion's row is unchanged by the swap")
|
||||
eq(hound.hp, hpBefore, "swapping past a companion never damages it")
|
||||
ok(contains(game.log:last(), "swap"), "swapping past an ally is logged")
|
||||
end
|
||||
|
||||
function tests.movePlayerSwapsPlacesWithAHiredHirelingToo()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
local hpBefore = sellsword.hp
|
||||
game:movePlayer(1, 0)
|
||||
eq(sellsword.x, 5, "a hireling is displaced by a swap exactly like a companion, never attacked")
|
||||
eq(sellsword.hp, hpBefore, "swapping past a hireling never damages it")
|
||||
end
|
||||
|
||||
function tests.adjacentMonsterSkipsTheBondedCompanionAndFindsARealHostileInstead()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
Companions.summon(game, "hound")
|
||||
local rat = Monsters.create(Monsters.byKey.rat, 50, 5, 6, 1)
|
||||
game.level.entities[#game.level.entities + 1] = rat
|
||||
eq(game:adjacentMonster(), rat, "the autopilot's melee target skips its own companion and finds the hostile rat")
|
||||
end
|
||||
|
||||
function tests.adjacentMonsterReturnsNilWhenOnlyAnAllyStandsNextToThePlayer()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
Companions.summon(game, "hound")
|
||||
eq(game:adjacentMonster(), nil, "a bonded companion alone never counts as a melee target")
|
||||
end
|
||||
|
||||
function tests.adjacentMonsterAlsoSkipsAThrallStandingNextToThePlayer()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local thrall = Entity.new({ id = 99, x = 6, y = 5, glyph = "z", name = "raised thrall", kind = "thrall",
|
||||
hp = 5, attack = 2, defense = 0 })
|
||||
game.level.entities[#game.level.entities + 1] = thrall
|
||||
eq(game:adjacentMonster(), nil, "a raised thrall standing next to the player is never a melee target either")
|
||||
end
|
||||
|
||||
function tests.choosePlayerActionNeverAttacksItsOwnBondedCompanionEvenWithNothingElseToDo()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local hound = Companions.summon(game, "hound")
|
||||
local hpBefore = hound.hp
|
||||
game:choosePlayerAction()
|
||||
eq(hound.hp, hpBefore, "the autopilot never bumps its own companion into a melee attack")
|
||||
end
|
||||
|
||||
function tests.aStarNeverRoutesAMonsterThroughABlockedCell()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||
for y = 2, 10 do game.level.map:set(11, y, Tiles.WALL) end
|
||||
game.level.map:set(11, 6, Tiles.FLOOR)
|
||||
game.level.blocks = { { x = 11, y = 6 } }
|
||||
local orc = Monsters.create(Monsters.byKey.orc, 1, 13, 6, 1)
|
||||
game.level.entities = { orc }
|
||||
Ai.act(game, orc)
|
||||
ok(not (orc.x == 11 and orc.y == 6), "a chasing monster never steps onto a cell a block occupies")
|
||||
eq(orc.x, 13, "with the single gap blocked, the chaser has no route and holds its ground")
|
||||
eq(orc.y, 6, "the chaser's y is likewise unchanged with no route available")
|
||||
end
|
||||
|
||||
function tests.playerPathToNeverRoutesThroughABlockedCell()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||
for y = 2, 10 do game.level.map:set(11, y, Tiles.WALL) end
|
||||
game.level.map:set(11, 6, Tiles.FLOOR)
|
||||
game.level.blocks = { { x = 11, y = 6 } }
|
||||
local path = game:playerPathTo(15, 6)
|
||||
eq(path, nil, "no path is found when the only corridor cell is occupied by a block")
|
||||
end
|
||||
|
||||
function tests.gatesAndBlocksStayOffFloorsAboveDepthTwo()
|
||||
for seed = 1, 4 do
|
||||
for depth = 1, 2 do
|
||||
local level = Builder.build(depth, Rng.new(seed), {})
|
||||
eq(#level.gates, 0, "no gate appears above depth three")
|
||||
eq(#level.blocks, 0, "no pushable block appears above depth three")
|
||||
eq(#level.plates, 0, "no pressure plate appears above depth three")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Depth 5 cycles to the rooms generator (see gen/init.lua's Gen.order),
|
||||
-- the one that actually carves the doorway pinch points this puzzle needs;
|
||||
-- depths 3-4 cycle to cellular/drunkard caves that essentially never have
|
||||
-- one, so the gate's own depth >= 3 gate is exercised here rather than at
|
||||
-- depth three itself, exactly like vaults.lua's own test picks depth five.
|
||||
function tests.builderOccasionallyPlacesAGateBlockPuzzleOnceADoorwayIsAvailable()
|
||||
local found = false
|
||||
for seed = 1, 200 do
|
||||
local level = Builder.build(5, Rng.new(seed), {})
|
||||
if #level.gates == 1 then
|
||||
found = true
|
||||
eq(#level.plates, 1, "a placed gate always comes with exactly one linked plate")
|
||||
eq(#level.blocks, 1, "a placed gate always comes with exactly one pushable block")
|
||||
local gate, plate, block = level.gates[1], level.plates[1], level.blocks[1]
|
||||
eq(plate.gateX, gate.x, "the plate's gate link matches the placed gate's x")
|
||||
eq(plate.gateY, gate.y, "the plate's gate link matches the placed gate's y")
|
||||
eq(level.map:get(gate.x, gate.y), Tiles.GATE_CLOSED, "the gate starts sealed on its own tile")
|
||||
local dx, dy = plate.x - block.x, plate.y - block.y
|
||||
ok((dx == 0 and math.abs(dy) == 1) or (dy == 0 and math.abs(dx) == 1),
|
||||
"the block always starts exactly one cardinal step from its plate")
|
||||
local treasureFound
|
||||
for _, ground in ipairs(level.floorItems) do
|
||||
if ground.item.name and ground.item.name:find("gate%-forged", 1) then treasureFound = ground.item end
|
||||
end
|
||||
ok(treasureFound ~= nil, "the puzzle's guaranteed treasure is placed somewhere on the floor")
|
||||
end
|
||||
|
||||
local order = {
|
||||
|
||||
Reference in new issue
Block a user