175 lines
5.6 KiB
Text
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
|