singe/testScripts/scene57.singe
2026-09-13 23:48:15 -05:00

236 lines
7.2 KiB
Text

-- Forge as a game (PLAN section 58): 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 " .. #FORGE.game.entities .. " entities")
end
elseif stage == 2 then
-- Add an entity, name it, and give it a look by typing the fields ENTER walks.
local count = #FORGE.game.entities
key(SCANCODE.A)
if #FORGE.game.entities ~= count + 1 then
debugPrint("GAME FAIL A did not add an entity")
end
key(SCANCODE.RETURN) -- id
typeIn("coin")
key(SCANCODE.RETURN) -- x
typeIn("300")
key(SCANCODE.RETURN) -- y
typeIn("200")
key(SCANCODE.RETURN) -- kind
key(SCANCODE.ESCAPE) -- untouched
local coin = FORGE.game.entities[#FORGE.game.entities]
if coin.id == "coin" and coin.x == 300 and coin.y == 200 and coin.look.kind == "box" then
debugPrint("GAME typed an entity in: " .. coin.id .. " at " .. coin.x .. "," .. coin.y)
else
debugPrint("GAME FAIL the typed entity is " .. tostring(coin.id) .. " at " .. tostring(coin.x) .. "," .. tostring(coin.y) .. " " .. tostring(coin.look.kind))
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 FORGE.game.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 FORGE.game.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(FORGE.game.entities[FORGE.selected].id) .. ", " .. named .. " actions renamed")
end
elseif stage == 4 then
-- A rule from the pickers: N, then C picks the first condition, T the first action.
local rules = #FORGE.game.rules
key(SCANCODE.TAB)
key(SCANCODE.N)
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 then
debugPrint("GAME built a rule from the pickers: 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, 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)
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)
local count = #FORGE.game.entities
key(SCANCODE.D)
key(SCANCODE.DELETE)
if #FORGE.game.entities == count then
debugPrint("GAME duplicate then delete leaves " .. count .. " entities")
else
debugPrint("GAME FAIL duplicate then delete leaves " .. #FORGE.game.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(FORGE.game.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