singe/testScripts/scene55.singe
2026-09-13 22:06:39 -05:00

176 lines
5.4 KiB
Text

-- The event sheet (PLAN section 58): rules edited, not just entities moved.
--
-- Driven by keys, which is how the editor is meant to be used and how the bundled menu's document
-- has always been driven. What is asserted is that an edit made through the rule editor reaches
-- the compiled game -- adding a condition has to change what the game actually does.
-- 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", 15)
local out = singeGetDataPath()
local work = out .. "rules.game"
fontSelect(font)
fontQuality(FONT_QUALITY_BLENDED)
overlaySetResolution(720, 480)
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("RULES FAIL could not open " .. work)
singeQuit()
end
local frames = 0
local stage = 0
local before = ""
-- The engine hands onKeyPressed two integers, so the test does too. Passing the SCANCODE table
-- instead made every comparison in forgeKey false and the test still passed, which is worse than
-- failing: the keyboard did nothing for a real user and nothing said so.
local function key(code, character)
forgeKey(character or 0, code.value)
end
local function compiled()
FORGE.game.source = work
return authorCompile(FORGE.game)
end
local function step()
if stage == 1 then
key(SCANCODE.TAB)
debugPrint("RULES mode is now " .. FORGE.mode .. ", rules " .. #FORGE.game.rules)
elseif stage == 2 then
-- Walk to the second rule the way the arrow keys do.
key(SCANCODE.DOWN)
key(SCANCODE.DOWN)
key(SCANCODE.DOWN)
debugPrint("RULES at rule " .. FORGE.rule .. " part " .. FORGE.part)
elseif stage == 3 then
before = compiled()
-- A new rule, then a condition and an action on it, all from the manifest.
forgeRuleNew("jump with the up arrow too")
forgeRuleAdd("when", "keyHeld")
forgePartSet("key", "UP")
forgeRuleAdd("when", "onGround")
forgePartSet("entity", "hero")
forgeRuleAdd("act", "jump")
forgePartSet("entity", "hero")
debugPrint("RULES built a rule with " .. #forgeParts(FORGE.game.rules[FORGE.rule]) .. " parts")
elseif stage == 4 then
local after = compiled()
if after == before then
debugPrint("RULES FAIL the new rule did not reach the compiled game")
elseif after:find('authorKeyHeld%(SCANCODE%.UP%)') and after:find('authorOnGround%("hero"%)') and after:find('authorJump%("hero"%)') then
debugPrint("RULES the compiled game carries the new rule")
else
debugPrint("RULES FAIL the compiled game is missing part of the new rule")
end
elseif stage == 5 then
-- And deleting a part has to take it back out again.
FORGE.part = 1
forgePartDelete()
local after = compiled()
if after:find('authorKeyHeld%(SCANCODE%.UP%)') then
debugPrint("RULES FAIL the deleted condition is still compiled")
else
debugPrint("RULES the deleted condition is gone from the compiled game")
end
elseif stage == 6 then
-- Type a value the way a person does: ENTER opens the first parameter, characters go in,
-- ENTER commits. Rule 1 is "Run left", whose only condition is keyHeld with key = LEFT.
FORGE.rule = 1
FORGE.part = 1
key(SCANCODE.RETURN)
for c in string.gmatch("RIGHT", ".") do
key(SCANCODE.A, string.byte(c))
end
key(SCANCODE.RETURN)
local typed = FORGE.game.rules[1].when[1]
if typed.key == "RIGHT" then
debugPrint("RULES typed a value in: key = " .. tostring(typed.key))
else
debugPrint("RULES FAIL typing did not reach the rule: key = " .. tostring(typed.key))
end
elseif stage == 7 then
-- A number has to come back a number: "100" and 100 compile to different source, so a
-- retyped value that turned into a string would stop the description round-tripping.
-- Rule 4 is "Take the prize once"; its third part is addScore, whose value is amount.
FORGE.rule = 4
FORGE.part = 3
key(SCANCODE.RETURN)
for c in string.gmatch("250", ".") do
key(SCANCODE.A, string.byte(c))
end
key(SCANCODE.RETURN)
local amount = FORGE.game.rules[4].act[1].amount
if amount == 250 and type(amount) == "number" then
debugPrint("RULES a typed number is a number: " .. tostring(amount))
else
debugPrint("RULES FAIL amount is " .. type(amount) .. " " .. tostring(amount))
end
-- And ESC has to put back what was there.
FORGE.part = 1
key(SCANCODE.RETURN)
key(SCANCODE.A, string.byte("Z"))
key(SCANCODE.ESCAPE)
if FORGE.game.rules[4].when[1].entity == "hero" then
debugPrint("RULES escape put the value back")
else
debugPrint("RULES FAIL escape left " .. tostring(FORGE.game.rules[4].when[1].entity))
end
elseif stage == 8 then
key(SCANCODE.S)
local reloaded = authorLoad(work)
local last = reloaded.rules[#reloaded.rules]
debugPrint("RULES saved; last rule is now '" .. tostring(last.note) .. "' with " .. #(last.when or {}) .. " conditions")
debugPrint("RULES RESULT done")
end
end
function onOverlayUpdate()
frames = frames + 1
if frames % 14 == 0 and stage < 9 then
stage = stage + 1
step()
end
forgeDraw(nil, nil)
colorForeground(255, 255, 255, 255)
fontPrint(210, 455, "event sheet, stage " .. stage)
if frames == 16 or frames == 44 or frames == 62 or frames == 96 then
singeScreenshot()
end
if frames > 150 then
singeQuit()
end
return OVERLAY_UPDATED
end