chroma_solstice/tests/test_wire.lua
2026-08-12 00:37:49 -04:00

359 lines
14 KiB
Lua

package.path = "./?.lua;" .. package.path
class = require "libs/middleclass"
local Wire = require "wire"
require "world"
gridWidth, gridHeight = 2, 2
local passed = 0
local function test(name, fn)
local ok, err = pcall(fn)
if not ok then
io.stderr:write("FAIL: " .. name .. "\n" .. err .. "\n")
os.exit(1)
end
passed = passed + 1
end
local function assertTrue(value, message)
if not value then error(message or "expected true", 2) end
end
local function assertFalse(value, message)
if value then error(message or "expected false", 2) end
end
local function switch(color, x, y)
local entity = {x = x, y = y, activated = false}
entity.color = {getname = function() return color end}
function entity:getGridPos() return {x = self.x, y = self.y} end
function entity:activate() self.activated = true end
function entity:deactivate() self.activated = false end
return entity
end
local function door(circuitId, x, y)
local entity = {circuitId = circuitId, x = x, y = y, powered = false}
function entity:getGridPos() return {x = self.x, y = self.y} end
function entity:setPowered(powered) self.powered = powered end
return entity
end
local function makeWorld(secondSwitch)
local blueSwitch = switch("blue", 2, 1)
local wiredDoor = door("circuit_a", 1, 1)
local roomA = {
name = "a", player = {}, wiredSwitches = {}, wiredDoors = {wiredDoor},
wires = {[1] = {
[1] = {circuit_a = Wire.EAST},
[2] = {circuit_a = Wire.WEST + Wire.EAST},
}},
}
local roomB = {
name = "b", player = {}, wiredSwitches = {blueSwitch}, wiredDoors = {},
wires = {[1] = {
[1] = {circuit_a = Wire.WEST + Wire.EAST},
[2] = {circuit_a = Wire.WEST},
}},
}
local function wireCircuitAt(room, cell)
local tracks = room.wires[cell.y] and room.wires[cell.y][cell.x]
for circuitId in pairs(tracks or {}) do return circuitId end
end
roomA.wireCircuitAt = wireCircuitAt
roomB.wireCircuitAt = wireCircuitAt
if secondSwitch then table.insert(roomB.wiredSwitches, secondSwitch) end
local instance = setmetatable({
levels = {a = roomA, b = roomB},
roomByName = {a = {name = "a", x = 1, y = 1}, b = {name = "b", x = 2, y = 1}},
collidableMatrices = {red = {}, green = {}, blue = {}},
}, {__index = World})
function instance:roomCell(cell)
if cell.x <= gridWidth then return roomA, {x = cell.x, y = cell.y} end
return roomB, {x = cell.x - gridWidth, y = cell.y}
end
local function wireTracksAt(room, cell, create)
local row = room.wires[cell.y]
if not row and create then row = {}; room.wires[cell.y] = row end
local tracks = row and row[cell.x]
if not tracks and create then tracks = {}; row[cell.x] = tracks end
return tracks
end
roomA.wireTracksAt = wireTracksAt
roomB.wireTracksAt = wireTracksAt
return instance, roomA, roomB, wiredDoor, blueSwitch
end
test("direction masks add and remove without duplicating bits", function()
local mask = Wire.add(0, Wire.EAST)
mask = Wire.add(mask, Wire.EAST)
assertTrue(mask == Wire.EAST)
assertTrue(Wire.has(mask, Wire.EAST))
assertFalse(Wire.has(mask, Wire.WEST))
assertTrue(Wire.remove(mask, Wire.EAST) == 0)
end)
test("orphaned and overlapping wire cells remain selectable without a door", function()
local world, roomA = makeWorld()
roomA.wires[1][1].orphan_b = Wire.SOUTH
local top = world:circuitAt({x = 1, y = 1})
assertTrue(top == "orphan_b", "top orphaned circuit was not selectable")
local preferred = world:circuitAt({x = 1, y = 1}, "circuit_a")
assertTrue(preferred == "circuit_a", "selected overlapping circuit was not preferred")
end)
test("a white switch powers every layer of a white door", function()
local world, roomA, roomB = makeWorld()
roomA.wiredDoors = {
door("circuit_a", 1, 1),
door("old_green_id", 1, 1),
door("old_blue_id", 1, 1),
}
roomB.wiredSwitches = {
switch("red", 2, 1),
switch("green", 2, 1),
switch("blue", 2, 1),
}
world.collidableMatrices.red[1] = {[4] = {}}
world.collidableMatrices.green[1] = {[4] = {}}
world.collidableMatrices.blue[1] = {[4] = {}}
world:resolveCircuits()
for _, wiredDoor in ipairs(roomA.wiredDoors) do
assertTrue(wiredDoor.circuitId == "circuit_a", "door layer did not adopt terminal circuit")
assertTrue(world.circuitState.doorPowered[wiredDoor], "white door layer remained locked")
end
local color = world:wireColorAt(roomA, {x = 1, y = 1}, "circuit_a")
assertTrue(color[1] == 1 and color[2] == 1 and color[3] == 1, "white switch did not produce white wire")
local redGreenOnly = world:wireColorAt(roomA, {x = 1, y = 1}, "circuit_a", {red = true, green = true})
assertTrue(redGreenOnly[1] == 1 and redGreenOnly[2] == 1 and redGreenOnly[3] == 0,
"white wire did not obey the room's visible player channels")
end)
test("world matrices join a circuit across a room boundary", function()
local world, _, _, wiredDoor, blueSwitch = makeWorld()
world.collidableMatrices.blue[1] = {[4] = {}}
world:resolveCircuits()
assertTrue(world.wireMatrix[1][2].circuit_a ~= nil)
assertTrue(world.wireMatrix[1][3].circuit_a ~= nil)
assertTrue(world.wiredDoorMatrix[1][1][1].entity == wiredDoor)
assertTrue(world.wiredSwitchMatrix[1][4][1].entity == blueSwitch)
assertTrue(world.circuitState.switchPressed[blueSwitch])
assertTrue(world.circuitState.doorPowered[wiredDoor])
assertTrue(wiredDoor.powered)
assertTrue(world:wireColorAt(world.levels.a, {x = 1, y = 1}, "circuit_a", {red = true, green = true}) == nil,
"blue wire leaked into a room without a blue player channel")
local visibleBlue = world:wireColorAt(world.levels.a, {x = 1, y = 1}, "circuit_a", {blue = true})
assertTrue(visibleBlue[1] == 0 and visibleBlue[2] == 0 and visibleBlue[3] == 1,
"blue wire did not render in a room with a blue player channel")
end)
test("wire color is re-derived when its terminal switch changes", function()
local world, roomA, roomB = makeWorld()
world:resolveCircuits()
local colors = world:wireSwitchColorsAt(roomA, {x = 1, y = 1}, "circuit_a")
assertTrue(colors.blue and not colors.red)
roomB.wiredSwitches = {switch("red", 2, 1)}
world:resolveCircuits()
colors = world:wireSwitchColorsAt(roomA, {x = 1, y = 1}, "circuit_a")
assertTrue(colors.red and not colors.blue, "wire retained the deleted terminal switch color")
end)
test("a switchless wire component has no gameplay color", function()
local world, roomA, roomB = makeWorld()
roomB.wiredSwitches = {}
world:resolveCircuits()
assertTrue(world:wireSwitchColorsAt(roomA, {x = 1, y = 1}, "circuit_a") == nil)
assertTrue(world:wireColorAt(roomA, {x = 1, y = 1}, "circuit_a") == nil)
end)
test("wire power follows its terminal switch", function()
local world, roomA = makeWorld()
world:resolveCircuits()
assertFalse(world:wirePoweredAt(roomA, {x = 1, y = 1}, "circuit_a"))
world.collidableMatrices.blue[1] = {[4] = {}}
world:resolveCircuits()
assertTrue(world:wirePoweredAt(roomA, {x = 1, y = 1}, "circuit_a"))
end)
test("a door requires every independent switch run at its terminal", function()
local greenSwitch = switch("green", 1, 2)
local world, roomA, _, wiredDoor = makeWorld()
table.insert(roomA.wiredSwitches, greenSwitch)
roomA.wires[1][1].circuit_b = Wire.SOUTH
roomA.wires[2] = {[1] = {circuit_b = Wire.NORTH}}
world.collidableMatrices.blue[1] = {[4] = {}}
world:resolveCircuits()
assertFalse(world.circuitState.doorPowered[wiredDoor])
world.collidableMatrices.green[2] = {[1] = {}}
world:resolveCircuits()
assertTrue(world.circuitState.doorPowered[wiredDoor])
end)
test("an orphaned stub at a door does not block a valid switch run", function()
local world, roomA, _, wiredDoor = makeWorld()
roomA.wires[1][1].abandoned_stroke = 0
world.collidableMatrices.blue[1] = {[4] = {}}
world:resolveCircuits()
assertTrue(world.circuitState.doorPowered[wiredDoor])
assertTrue(wiredDoor.powered)
end)
test("real wired and regular doors stay open while any player or collidable blocks the doorway", function()
local oldLoadSprite = loadSprite
loadSprite = function()
return {getWidth = function() return 16 end, getHeight = function() return 16 end}
end
require "color"
require "wired_door"
local function occupant(x, y)
local entity = {x = x, y = y}
function entity:occupiesCell(cell) return self.x == cell.x and self.y == cell.y end
function entity:canPassOver() return false end
return entity
end
local wired = WiredDoor:new(2, 2, "blue", "circuit_a")
local ordinary = Door:new(4, 2, "red")
local room = {
player = {green = occupant(2, 2)},
collideables = {
red = {ordinary}, green = {occupant(4, 2)}, blue = {wired},
},
}
local world = setmetatable({}, {__index = World})
wired:setPowered(true)
assertFalse(wired.locked, "wired door did not open")
assertFalse(world:doorCanClose(room, wired), "green player did not block blue wired door")
wired:setPowered(false, world:doorCanClose(room, wired))
assertFalse(wired.locked, "wired door shut on player")
ordinary:setColors({red = true})
assertFalse(ordinary.locked, "regular door did not open")
assertFalse(world:doorCanClose(room, ordinary), "green collidable did not block red regular door")
ordinary:setColors({red = false}, world:doorCanClose(room, ordinary))
assertFalse(ordinary.locked, "regular door shut on collidable")
room.player.green.x, room.player.green.y = 1, 1
room.collideables.green[1].x, room.collideables.green[1].y = 1, 1
wired:setPowered(false, world:doorCanClose(room, wired))
ordinary:setColors({red = false}, world:doorCanClose(room, ordinary))
assertTrue(wired.locked, "wired door did not close once clear")
assertTrue(ordinary.locked, "regular door did not close once clear")
loadSprite = oldLoadSprite
end)
test("circuit resolution passes doorway occupancy to a wired door", function()
local world, roomA, _, wiredDoor = makeWorld()
local player = {x = 1, y = 1}
function player:occupiesCell(cell) return self.x == cell.x and self.y == cell.y end
roomA.player.green = player
function wiredDoor:setPowered(powered, canClose)
self.powered, self.canClose = powered, canClose
end
world:resolveCircuits()
assertFalse(wiredDoor.powered, "unpressed switch unexpectedly powered door")
assertFalse(wiredDoor.canClose, "resolver omitted player doorway obstruction")
end)
test("selecting a parked colour changes to its room", function()
local function player()
local entity = {switched = false}
function entity:playSwitchSound(success) self.switched = success end
return entity
end
local function room(name, players, active)
local entity = {name = name, player = players, activeColors = active, colorsPlayerHas = {}}
for color in pairs(players) do table.insert(entity.colorsPlayerHas, color) end
function entity:getPossibleColors() return self.colorsPlayerHas end
function entity:setActiveColor(color, value) self.activeColors[color] = value end
function entity:getActiveColors()
local colors = {}
for color, value in pairs(self.activeColors) do if value then table.insert(colors, color) end end
return colors
end
return entity
end
local roomA = room("a", {red = player()}, {red = true, green = false, blue = false})
local roomB = room("b", {blue = player()}, {red = false, green = false, blue = false})
local world = setmetatable({
levels = {a = roomA, b = roomB},
roomByName = {a = {name = "a", x = 1, y = 1}, b = {name = "b", x = 2, y = 1}},
}, {__index = World})
world.flushRuntime = function(self) self.flushed = true end
local oldCurrentRoom, oldSetEditorRoomName = currentRoom, setEditorRoomName
currentRoom = roomA
setEditorRoomName = function() end
assertTrue(world:switchPlayerColor(roomA, "blue"))
assertTrue(currentRoom == roomB)
assertFalse(roomA.activeColors.red)
assertTrue(roomB.activeColors.blue)
assertTrue(roomB.player.blue.switched)
assertTrue(world.flushed)
currentRoom, setEditorRoomName = oldCurrentRoom, oldSetEditorRoomName
end)
test("co-located colours join the active player selection", function()
local function player(x, y)
local entity = {x = x, y = y, switched = false}
function entity:getGridPos() return {x = self.x, y = self.y} end
function entity:playSwitchSound(success) self.switched = success end
return entity
end
local room = {
name = "a",
player = {red = player(3, 4), green = player(3, 4), blue = player(6, 4)},
activeColors = {red = true, green = false, blue = false},
colorsPlayerHas = {"red", "green", "blue"},
}
function room:getPossibleColors() return self.colorsPlayerHas end
function room:getActiveColors()
local colors = {}
for _, color in ipairs(self.colorsPlayerHas) do
if self.activeColors[color] then table.insert(colors, color) end
end
return colors
end
function room:colorIsActive(color) return self.activeColors[color] end
function room:setActiveColor(color, value) self.activeColors[color] = value end
local world = setmetatable({
levels = {a = room}, roomByName = {a = {name = "a", x = 1, y = 1}},
}, {__index = World})
world.flushRuntime = function() end
assertTrue(world:switchPlayerColor(room, "green"))
assertTrue(room.activeColors.red and room.activeColors.green,
"co-located red and green did not become active together")
assertTrue(world:switchPlayerColor(room, "blue"))
assertTrue(room.activeColors.blue and not room.activeColors.red and not room.activeColors.green,
"separated blue player should still select by itself")
end)
test("one run cannot terminate at two different switches", function()
local second = switch("red", 1, 2)
local world, _, roomB = makeWorld(second)
world:resolveCircuits()
local rooms, err = world:addWireConnection({x = 3, y = 1}, {x = 3, y = 2}, "circuit_a")
assertTrue(#rooms == 0 and err ~= nil, "second switch terminal was accepted")
assertTrue(roomB.wires[2] == nil or roomB.wires[2][1] == nil,
"rejected second terminal left a connected wire cell")
local colors = world:wireSwitchColorsAt(world.levels.a, {x = 1, y = 1}, "circuit_a")
assertTrue(colors.blue and not colors.red, "run stopped deriving color from its sole terminal switch")
end)
test("matching IDs do not bridge a gap or one-sided room edge", function()
local world, _, roomB, wiredDoor = makeWorld()
world.collidableMatrices.blue[1] = {[4] = {}}
roomB.wires[1][1].circuit_a = Wire.EAST -- no reciprocal WEST edge
world:resolveCircuits()
assertFalse(world.circuitState.doorPowered[wiredDoor])
end)
io.write("PASS: " .. passed .. " wire tests\n")