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("room switch resolution keeps a regular door open around an occupant", function() local oldLove, oldLoadSprite = love, loadSprite love = {audio = {newSource = function() return {stop = function() end, play = function() end} end}} loadSprite = function() return {getWidth = function() return 16 end, getHeight = function() return 16 end} end require "room" local door = Door:new(2, 2, "red") local player, block = {x = 2, y = 2}, {x = 1, y = 1} function player:occupiesCell(cell) return self.x == cell.x and self.y == cell.y end function block:occupiesCell(cell) return self.x == cell.x and self.y == cell.y end function block:canPassOver() return false end local room = setmetatable({ player = {blue = player}, doors = {door}, collideables = {red = {door}, green = {block}, blue = {}}, }, {__index = Room}) room.switchCheck = function() return {red = true} end room:resolveSwitchesAndDoors() assertFalse(door.locked, "switch did not open regular door") room.switchCheck = function() return {red = false} end room:resolveSwitchesAndDoors() assertFalse(door.locked, "regular door shut through player during room resolution") room.player.blue.x, room.player.blue.y = 1, 1 room.collideables.green[1].x, room.collideables.green[1].y = 2, 2 room:resolveSwitchesAndDoors() assertFalse(door.locked, "regular door shut through collidable during room resolution") room.collideables.green[1].x, room.collideables.green[1].y = 1, 1 room:resolveSwitchesAndDoors() assertTrue(door.locked, "regular door did not close once clear") love, loadSprite = oldLove, oldLoadSprite 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("WASD room browsing changes view without moving players", function() local player = {x = 3, y = 4} local roomA = {name = "a", player = {red = player}} local roomB = {name = "b", player = {green = {x = 7, y = 8}}} local world = setmetatable({ levels = {a = roomA, b = roomB}, roomByName = {a = {name = "a", x = 1, y = 1}, b = {name = "b", x = 2, y = 1}}, rooms = {{name = "a", x = 1, y = 1}, {name = "b", x = 2, y = 1}}, }, {__index = World}) local oldCurrentRoom, oldSetEditorRoomName = currentRoom, setEditorRoomName currentRoom = roomA setEditorRoomName = function() end assertTrue(world:browseRoom(roomA, "d")) assertTrue(currentRoom == roomB) assertTrue(world.roomBrowseMode) assertTrue(player.x == 3 and player.y == 4, "browsing moved the player") currentRoom, setEditorRoomName = oldCurrentRoom, oldSetEditorRoomName end) test("screen-to-cell mapping uses the neighbourhood render transform", function() local oldWidth, oldHeight, oldGridWidth, oldGridHeight = width, height, gridWidth, gridHeight width, height, gridWidth, gridHeight = 110, 110, 11, 11 local room = {name = "a"} local world = setmetatable({}, {__index = World}) world.roomNeighborhoodTransform = function() return {zoom = 0.78, originX = 22, originY = 22, cameraX = 0, cameraY = 0} end local cell = world:screenCellInRoom(room, 22 + width * 0.78 * (4.5 / 11), 22 + height * 0.78 * (7.5 / 11)) assertTrue(cell.x == 5 and cell.y == 8, "rendered cell did not map back to itself") assertTrue(world:screenCellInRoom(room, 1, 1) == nil, "preview margin mapped into active room") width, height, gridWidth, gridHeight = oldWidth, oldHeight, oldGridWidth, oldGridHeight 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")