singe/testScripts/scene92.singe
2026-09-14 22:32:53 -05:00

175 lines
5.6 KiB
Text

-- Forge tutorial 10, "Releasing your game": the platformer with the arcade's plumbing -- lives,
-- a coin slot, a continue, the bezel, the score sent to the board -- released to a folder of its
-- own with everything it needs. Continues from tutorial 3.
TUTORIAL = { number = 10, name = "10-release", tag = "TUTORIAL10" }
dofile("testScripts/tutorialDrive.singe")
local frames = 0
local stage = 0
local game = nil
local folder = nil
tutorialBegin("03-sprites")
local function ruleGo(index, part)
local guard = 0
while ((FORGE.rule ~= index) or (FORGE.part ~= (part or 0))) and (guard < 60) do
local past = (FORGE.rule > index) or ((FORGE.rule == index) and (FORGE.part > (part or 0)))
key(past and SCANCODE.UP or SCANCODE.DOWN)
guard = guard + 1
end
end
local function rule(note, on, filters)
key(SCANCODE.N)
form("note", note)
key(SCANCODE.E)
pick(on)
for _, pair in ipairs(filters or {}) do
form(pair[1], pair[2])
end
end
local function part(partKey, name, fields)
key(partKey)
pick(name)
for _, pair in ipairs(fields or {}) do
form(pair[1], pair[2])
end
end
local function step()
if stage == 1 then
-- The game's form: a title of its own, lives and credits, and the bezel around the picture.
key(SCANCODE.MAIN_1)
forgeSelect(nil)
form("title", "Coin Run")
form("vars", "score=0, lives=3, credits=0")
form("layers", "world2d, bezel")
tutorialShot("bezel")
elseif stage == 2 then
-- Spikes cost a life now, and a coin buys a continue.
key(SCANCODE.MAIN_3)
ruleGo(6, 0)
key(SCANCODE.DOWN)
key(SCANCODE.DOWN)
key(SCANCODE.DOWN)
part(SCANCODE.T, "addVar", { { "name", "lives" }, { "amount", -1 } })
clear("entity")
rule("Out of lives", "frame", {})
part(SCANCODE.C, "test", { { "expr", "lives <= 0" } })
part(SCANCODE.C, "once", { { "tag", "over" }, { "scope", "game" } })
part(SCANCODE.T, "submitScore", { { "board", "default" } })
part(SCANCODE.T, "say", { { "text", '"GAME OVER"' }, { "seconds", 2 } })
part(SCANCODE.T, "gameOver", {})
rule("A coin", "pressed", { { "switch", "SWITCH_COIN1" } })
part(SCANCODE.T, "credit", {})
rule("Continue", "pressed", { { "switch", "SWITCH_START1" } })
part(SCANCODE.C, "test", { { "expr", "credits > 0 and lives <= 0" } })
part(SCANCODE.T, "addVar", { { "name", "credits" }, { "amount", -1 } })
clear("entity")
part(SCANCODE.T, "setVar", { { "name", "lives" }, { "value", "3" } })
clear("entity")
part(SCANCODE.T, "restart", {})
ruleGo(4, 1)
form("text", '"score " .. score .. " lives " .. lives')
tutorialShot("arcadeRules")
elseif stage == 3 then
key(SCANCODE.S)
-- X releases: the compiled game, the runtime, the description, a games.dat, and every
-- file the description names, in a folder of the game's own.
key(SCANCODE.X)
folder = forgeReleaseFolder()
tutorialShot("released")
local files = {}
for name in lfs.dir(folder) do
if name:sub(1, 1) ~= "." then
files[#files + 1] = name
end
end
table.sort(files)
debugPrint(TUTORIAL.tag .. " released to " .. folder .. ": " .. table.concat(files, ", "))
for _, want in ipairs({ "CoinRun.singe", "CoinRun.game", "Author.singe", "games.dat", "Forge" }) do
if lfs.attributes(folder .. "/" .. want) == nil then
debugPrint(TUTORIAL.tag .. " FAIL the release lacks " .. want)
end
end
if lfs.attributes(folder .. "/Forge/kit/hero.png", "mode") ~= "file" then
debugPrint(TUTORIAL.tag .. " FAIL the release should carry the kit files it names")
end
elseif stage == 4 then
-- Packed, as the tutorial's last step says, when the harness says where the engine is: a
-- scene cannot know its own executable, so SINGE_BIN carries it, and without it the step
-- is reported as not tried rather than failed.
local bin = os.getenv("SINGE_BIN")
if bin then
local packed = TUTORIAL_OUT .. "CoinRun.game"
os.remove(packed)
os.execute(string.format('"%s" --pack "%s" "%s" > "%s" 2>&1', bin, folder, packed, TUTORIAL_OUT .. "pack.log"))
-- What the packer said is the measure: a .game is a database, and the engine's own
-- file lookups treat one as a game rather than as bytes, so its size is not readable
-- from inside a running script.
local log = io.open(TUTORIAL_OUT .. "pack.log", "r")
local size = 0
if log then
for line in log:lines() do
debugPrint(TUTORIAL.tag .. " pack: " .. line)
size = tonumber(line:match("Packed %d+ files %((%d+) bytes%)")) or size
end
log:close()
end
debugPrint(string.format("%s packed the release into %s: %d bytes", TUTORIAL.tag, packed, size))
if size < 1000 then
debugPrint(TUTORIAL.tag .. " FAIL --pack should have written a .game")
end
else
debugPrint(TUTORIAL.tag .. " --pack not tried: SINGE_BIN is not set")
end
tutorialEnd()
-- The release runs from its own folder, finding its runtime and its files beside itself.
local mine = onOverlayUpdate
dofile(folder .. "/CoinRun.singe")
game = onOverlayUpdate
onOverlayUpdate = mine
authorKeyDown(0, SCANCODE.RIGHT.value)
elseif stage == 8 then
tutorialShot("playing")
debugPrint(string.format("%s the release plays from %s: score %s, lives %s, runtime at %s", TUTORIAL.tag, folder, tostring(AUTHOR_VARS.score), tostring(AUTHOR_VARS.lives), tostring(AUTHOR_DIR)))
if AUTHOR_DIR ~= folder .. "/" then
debugPrint(TUTORIAL.tag .. " FAIL the release should know its own folder")
end
elseif stage == 9 then
debugPrint(TUTORIAL.tag .. " RESULT done")
singeQuit()
end
end
function onOverlayUpdate()
frames = frames + 1
if frames % 12 == 0 and stage < 9 then
stage = stage + 1
step()
end
if game ~= nil then
return game()
end
forgeDraw(nil, nil)
return OVERLAY_UPDATED
end