-- Forge as a game (FORGE.md): the chooser, the engine callbacks, and the editing that -- needs no pointer -- entities added, renamed and deleted, a rule built from the pickers, undo. -- -- Unlike scene54 to 56 this does not set FORGE_LIBRARY: Forge installs its own callbacks, and -- the scene drives them the way the engine would, two integers to onKeyPressed. What is -- asserted is that every key reaches the description and that a rename reaches the rules. dofile("Forge/Forge.singe") local forgeUpdate = onOverlayUpdate local frames = 0 local stage = 0 local opened = nil local function key(code, character) onKeyPressed(character or 0, code.value) end local function typeIn(text) for c in string.gmatch(text, ".") do key(SCANCODE.A, string.byte(c)) end end local function step() if stage == 1 then -- The chooser is up and offers a new game at least. if FORGE.game ~= nil then debugPrint("GAME FAIL the editor is open before anything was chosen") end debugPrint("GAME the chooser offers " .. #forgeFiles() .. " entries") -- The last entry is always a new game. for _ = 1, 20 do key(SCANCODE.DOWN) end key(SCANCODE.RETURN) if FORGE.game == nil then debugPrint("GAME FAIL ENTER on the chooser opened nothing") else opened = FORGE.path debugPrint("GAME opened " .. opened .. " with " .. #forgeRoom().entities .. " entities") end elseif stage == 2 then -- Add an entity, name it, and make it a hero by typing the fields ENTER walks. local count = #forgeRoom().entities key(SCANCODE.A) if #forgeRoom().entities ~= count + 1 then debugPrint("GAME FAIL A did not add an entity") end key(SCANCODE.RETURN) -- id typeIn("coin") key(SCANCODE.RETURN) -- type typeIn("hero") key(SCANCODE.RETURN) -- x typeIn("300") key(SCANCODE.RETURN) -- y typeIn("200") key(SCANCODE.RETURN) -- z key(SCANCODE.ESCAPE) -- untouched local coin = forgeRoom().entities[#forgeRoom().entities] if coin.id == "coin" and coin.type == "hero" and coin.x == 300 and coin.y == 200 then debugPrint("GAME typed an entity in: " .. coin.id .. " the " .. coin.type .. " at " .. coin.x .. "," .. coin.y) else debugPrint("GAME FAIL the typed entity is " .. tostring(coin.id) .. " the " .. tostring(coin.type) .. " at " .. tostring(coin.x) .. "," .. tostring(coin.y)) end elseif stage == 3 then -- Renaming the hero has to reach every rule that names it. key(SCANCODE.UP) key(SCANCODE.UP) key(SCANCODE.UP) key(SCANCODE.UP) key(SCANCODE.DOWN) -- ground, then hero key(SCANCODE.RETURN) typeIn("player") key(SCANCODE.ESCAPE) -- ESC puts it back: the hero keeps its name if forgeRoom().entities[FORGE.selected].id ~= "hero" then debugPrint("GAME FAIL escape did not put the name back") end key(SCANCODE.RETURN) typeIn("player") key(SCANCODE.RETURN) -- commits, moves on to x key(SCANCODE.ESCAPE) local named = 0 for _, rule in ipairs(FORGE.game.rules) do for _, item in ipairs(rule.act) do if item.entity == "player" then named = named + 1 end end end if forgeRoom().entities[FORGE.selected].id == "player" and named == 3 then debugPrint("GAME renamed the hero and " .. named .. " actions followed") else debugPrint("GAME FAIL rename: id " .. tostring(forgeRoom().entities[FORGE.selected].id) .. ", " .. named .. " actions renamed") end elseif stage == 4 then -- A rule from the pickers: N, E picks an event, C a condition, T an action. local rules = #FORGE.game.rules key(SCANCODE.TAB) -- types, key(SCANCODE.TAB) -- rules. key(SCANCODE.N) key(SCANCODE.E) key(SCANCODE.DOWN) key(SCANCODE.RETURN) key(SCANCODE.C) if FORGE.picker == nil then debugPrint("GAME FAIL C opened no picker") end key(SCANCODE.DOWN) key(SCANCODE.RETURN) key(SCANCODE.T) key(SCANCODE.RETURN) local rule = FORGE.game.rules[#FORGE.game.rules] if #FORGE.game.rules == rules + 1 and #rule.when == 1 and #rule.act == 1 and rule.on ~= "frame" then debugPrint("GAME built a rule from the pickers: on " .. rule.on .. " when " .. rule.when[1][1] .. " then " .. rule.act[1][1]) else debugPrint("GAME FAIL the picked rule has " .. #(rule.when or {}) .. " conditions and " .. #(rule.act or {}) .. " actions") end elseif stage == 5 then -- Undo takes the action back, then the condition, the event, then the rule; redo brings -- the rule back, and a change by hand ends the redo history. local rules = #FORGE.game.rules key(SCANCODE.U) key(SCANCODE.U) key(SCANCODE.U) key(SCANCODE.U) if #FORGE.game.rules == rules - 1 then debugPrint("GAME three undos took the rule back out") else debugPrint("GAME FAIL after three undos there are " .. #FORGE.game.rules .. " rules, not " .. (rules - 1)) end key(SCANCODE.R) if (#FORGE.game.rules == rules) and (#FORGE.game.rules[rules].when == 0) then debugPrint("GAME redo brought the empty rule back") else debugPrint("GAME FAIL redo left " .. #FORGE.game.rules .. " rules") end key(SCANCODE.U) key(SCANCODE.N) key(SCANCODE.R) if #FORGE.redo == 0 and #FORGE.game.rules == rules then debugPrint("GAME a new change emptied the redo history") else debugPrint("GAME FAIL redo history survived a change: " .. #FORGE.redo .. " kept") end key(SCANCODE.U) elseif stage == 6 then -- Duplicate and delete in the entity list. key(SCANCODE.TAB) -- tracks, key(SCANCODE.TAB) -- entities. local count = #forgeRoom().entities key(SCANCODE.D) key(SCANCODE.DELETE) if #forgeRoom().entities == count then debugPrint("GAME duplicate then delete leaves " .. count .. " entities") else debugPrint("GAME FAIL duplicate then delete leaves " .. #forgeRoom().entities) end elseif stage == 7 then -- ESC on unsaved work warns, S saves, ESC then leaves; the chooser lists the file. key(SCANCODE.ESCAPE) if FORGE.game == nil then debugPrint("GAME FAIL one ESC closed unsaved work") end key(SCANCODE.S) key(SCANCODE.ESCAPE) if FORGE.game ~= nil then debugPrint("GAME FAIL ESC did not close a saved description") end local listed = false for _, entry in ipairs(forgeFiles()) do if entry.path == opened then listed = true end end debugPrint("GAME closed; the chooser lists what was saved: " .. tostring(listed)) elseif stage == 8 then -- Open it again from the chooser -- it is listed first -- and the rename is in the file. for _ = 1, 20 do key(SCANCODE.UP) end key(SCANCODE.RETURN) if FORGE.game == nil then debugPrint("GAME FAIL could not reopen the saved description") else local found = false for _, entity in ipairs(forgeRoom().entities) do if entity.id == "player" then found = true end end debugPrint("GAME reopened " .. FORGE.path .. "; the rename was saved: " .. tostring(found)) end elseif stage == 9 then -- B builds beside the runtime, which is what makes the result playable. key(SCANCODE.B) local runtime = io.open(singeGetDataPath() .. "Author.singe", "r") if runtime == nil then debugPrint("GAME FAIL build left no runtime beside the preview") else runtime:close() debugPrint("GAME the build put the runtime beside the preview") end debugPrint("GAME RESULT done") end end function onOverlayUpdate() frames = frames + 1 if frames % 12 == 0 and stage < 10 then stage = stage + 1 step() end forgeUpdate() if frames == 6 or frames == 40 or frames == 60 or frames == 100 then singeScreenshot() end if frames > 130 then singeQuit() end return OVERLAY_UPDATED end