-- Forge (FORGE.md): opened on a real description, driven, saved, and the result compiled and -- played. -- -- Driven by calling the editor's own functions rather than by a pointer, the way the menu's -- sanitizer scene drives the menu: what is being tested is that moving something in the editor -- reaches the description, survives a save, and changes the game that comes out. The pointer path -- itself was settled by the spike in FORGE.md section 1 and needs a real X server to exercise. -- As a library: the tail of Forge.singe that launches it is skipped, because this scene -- drives it rather than being it. FORGE_LIBRARY = true dofile("Forge/Forge.singe") local font = fontLoad("Singe/FreeSansBold.ttf", 16) local out = singeGetDataPath() local work = out .. "edited.game" fontSelect(font) fontQuality(FONT_QUALITY_BLENDED) overlaySetResolution(720, 480) -- Work on a copy; an editor that writes back to the file it was handed is right, but a test that -- edits the repository's own sample is not. local source = assert(io.open("testScripts/author/platformer.game", "r")) local copy = assert(io.open(work, "w")) copy:write(source:read("a")) source:close() copy:close() if not forgeBegin(work) then debugPrint("FORGE FAIL could not open " .. work) singeQuit() end local frames = 0 local stage = 0 local pointer = { x = 430, y = 300 } local before = nil -- Each stage is one thing the editor must be able to do, checked as it happens. local function step() if stage == 1 then -- Pick the prize up off the ledge, the way a press on the canvas would. local index = forgePick(520, 300) if index == nil then debugPrint("FORGE FAIL nothing under the prize's own position") else before = forgeRoom().entities[index].x debugPrint("FORGE picked " .. forgeRoom().entities[index].id .. " at x=" .. before) forgePress(520, 300) end elseif stage == 2 then -- Drag it left and down, off the ledge and onto the ground. forgeDrag(300, 300) pointer.x = 300 elseif stage == 3 then forgeRelease() local moved = forgeRoom().entities[forgePick(300, 300)] debugPrint("FORGE moved to x=" .. moved.x .. " dirty=" .. tostring(FORGE.dirty)) elseif stage == 4 then forgeSave() -- Reload from disc and check the move is in the file, not just in memory. local reloaded = authorLoad(work) local found = nil for _, e in ipairs(reloaded.rooms[1].entities) do if e.id == "prize" then found = e.x end end debugPrint("FORGE saved and reloaded, prize x=" .. tostring(found) .. " (was " .. tostring(before) .. ")") if found == before then debugPrint("FORGE FAIL the move did not survive the save") end elseif stage == 5 then -- And the edit has to reach the game, not just the file. local built = forgeBuild(out .. "edited.singe") local text = assert(io.open(built, "r")):read("a") if text:find('id = "prize", type = "prize", x = 300') then debugPrint("FORGE the compiled game carries the new position") else debugPrint("FORGE FAIL the compiled game does not carry the new position") end elseif stage == 6 then -- The invariant the whole editor rests on: a description that has been through load and -- save has to compile to the same game as the one that has not. Without it, opening a -- game in the editor and saving it unchanged could quietly alter it. local original = authorLoad("testScripts/author/platformer.game") local trip = out .. "trip.game" authorSave(original, trip) local again = authorLoad(trip) if authorCompile(original) == authorCompile(again) then debugPrint("FORGE round trip identical") else debugPrint("FORGE FAIL round trip changed the game") end elseif stage == 7 then -- The hero lives at x=120, underneath the chrome. A press there must not reach it. forgeSelect(nil) forgePress(120, 380) if FORGE.selected == nil then debugPrint("FORGE the panel correctly swallows a press beneath it") else debugPrint("FORGE FAIL a press under the panel reached the canvas") end elseif stage == 8 then -- Slide the chrome out of the way by pressing its tab, through forgePress, which is -- the path a real pointer takes. Grabbing the panel through a GUI handler would not be: -- the spike found a document can go without pointer input entirely, so the tab is drawn on -- the canvas and this test goes through the same door a player's mouse does. local tx, ty, tw, th = forgeTab() forgePress(tx + tw / 2, ty + th / 2) if FORGE.panelDrag == nil then debugPrint("FORGE FAIL pressing the tab did not start a panel drag") end forgeDrag(440 + tw / 2, 200) forgeRelease() pointer.x = 440 debugPrint("FORGE panel moved to x=" .. FORGE.panelX) elseif stage == 9 then -- And now the same press reaches the hero, which is the whole point of a movable panel. forgePress(120, 380) if FORGE.selected ~= nil and forgeRoom().entities[FORGE.selected].id == "hero" then debugPrint("FORGE the hero is reachable once the panel has moved") else debugPrint("FORGE FAIL the hero is still unreachable") end forgeRelease() debugPrint("FORGE RESULT done") end end function onOverlayUpdate() frames = frames + 1 if frames % 12 == 0 and stage < 10 then stage = stage + 1 step() end forgeDraw(pointer.x, pointer.y) colorForeground(255, 255, 255, 255) fontPrint(200, 455, "Forge, stage " .. stage) if frames == 10 or frames == 54 or frames == 88 or frames == 118 then singeScreenshot() end if frames > 130 then singeQuit() end return OVERLAY_UPDATED end