-- Lesson 29: Keeping Data. The finished script, exactly as the lesson ends. dofile("Singe/Framework.singe") local sqlite3 = require("sqlite3") local PERFECT = 1000 -- What ships with the game: the stages, read once and closed. local stages = {} local shipped = sqlite3.open(DIR .. "art/stages.db", sqlite3.OPEN_READONLY) for row in shipped:nrows("SELECT name, waitMin, waitMax FROM stages ORDER BY id") do stages[#stages + 1] = row end shipped:close() -- What the game writes: every run anybody plays, kept in the data directory. local runs = sqlite3.open(singeGetDataPath() .. "runs.db") runs:exec([[ CREATE TABLE IF NOT EXISTS runs ( id INTEGER PRIMARY KEY, stage TEXT NOT NULL, score INTEGER NOT NULL, at INTEGER NOT NULL ) ]]) local addRun = runs:prepare("INSERT INTO runs (stage, score, at) VALUES (?, ?, ?)") local stageAt = 1 local state = "attract" local goAt = 0 local score = 0 local best = {} local perStage = {} local played = 0 -- The questions, asked when a run is added rather than every frame. local function refresh() best = {} for row in runs:nrows("SELECT stage, score FROM runs ORDER BY score DESC, id LIMIT 5") do best[#best + 1] = row end perStage = {} for row in runs:nrows("SELECT stage, MAX(score) AS top, COUNT(*) AS tries FROM runs GROUP BY stage ORDER BY stage") do perStage[#perStage + 1] = row end for count in runs:urows("SELECT COUNT(*) FROM runs") do played = count end end local function record(stage, points) addRun:bind_values(stage, points, os.time()) addRun:step() addRun:reset() refresh() end local function play() local stage = stages[stageAt] state = "waiting" goAt = singeGetTicks() + math.random(stage.waitMin, stage.waitMax) score = 0 end function onInputPressed(what) if what == SWITCH_BUTTON2 and state ~= "waiting" and state ~= "go" then stageAt = stageAt % #stages + 1 return end if what ~= SWITCH_BUTTON1 then return end if state == "waiting" then state = "result" score = 0 record(stages[stageAt].name, score) elseif state == "go" then score = math.max(0, PERFECT - (singeGetTicks() - goAt)) state = "result" record(stages[stageAt].name, score) else play() end end function onOverlayUpdate() if state == "waiting" and singeGetTicks() >= goAt then state = "go" end overlayClear() overlayPrint(2, 0, "REACTION TEST: " .. stages[stageAt].name) if state == "attract" then overlayPrint(2, 2, "Button 1 to play, button 2 for another stage.") elseif state == "waiting" then overlayPrint(2, 2, "Wait for it...") elseif state == "go" then overlayPrint(2, 2, "NOW!") else overlayPrint(2, 2, "You scored " .. score .. ". Button 1 plays again.") end overlayPrint(2, 4, "Runs kept: " .. played) overlayPrint(2, 6, "BEST RUNS") for place, row in ipairs(best) do overlayPrint(2, 6 + place, string.format("%d. %-8s %4d", place, row.stage, row.score)) end overlayPrint(24, 6, "BY STAGE") for line, row in ipairs(perStage) do overlayPrint(24, 6 + line, string.format("%-8s best %4d of %d", row.stage, row.top, row.tries)) end return OVERLAY_UPDATED end function onShutdown() addRun:finalize() runs:close() end refresh()