-- 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