diff --git a/studio/app/core/project_session.py b/studio/app/core/project_session.py index e1ebf9c..4ebe5dd 100644 --- a/studio/app/core/project_session.py +++ b/studio/app/core/project_session.py @@ -18,6 +18,13 @@ *(f"LPT{i}" for i in range(1, 10)), } +PROJECT_TEMPLATES = { + "basic": "basic", + "doodle-quest": "DoodleQuest", + "ninja-runner": "NinjaRunner", + "catbox-mre": "CatBoxMRE", +} + @dataclass(slots=True) class ProjectInfo: root: Path @@ -74,13 +81,25 @@ def validate_project_name(name: str) -> str: def project_path(self, name: str) -> Path: return self.default_projects_root / self.validate_project_name(name) - def create_project(self, name: str, metadata: dict | None = None, sdk_metadata: dict | None = None) -> ProjectInfo: + def create_project( + self, + name: str, + metadata: dict | None = None, + sdk_metadata: dict | None = None, + *, + template_name: str = "basic", + ) -> ProjectInfo: name = self.validate_project_name(name) destination = self.project_path(name) if destination.exists(): raise FileExistsError(destination) - template = self.engine_root / "templates" / "basic" + template_key = str(template_name or "basic").strip() + template_dir = PROJECT_TEMPLATES.get(template_key) + if not template_dir: + raise ValueError(f"Unknown project template: {template_key}") + + template = self.engine_root / "templates" / template_dir if not template.is_dir(): raise FileNotFoundError(f"Project template not found: {template}") @@ -101,6 +120,7 @@ def create_project(self, name: str, metadata: dict | None = None, sdk_metadata: appid = payload.get("appid") payload.update(dict(metadata)) payload["name"] = name + payload["template"] = template_key if appid is not None: payload["appid"] = appid descriptor.write_text( diff --git a/studio/app/ui/mediatek_mre_dialog.py b/studio/app/ui/mediatek_mre_dialog.py index 6282256..9ffe8e1 100644 --- a/studio/app/ui/mediatek_mre_dialog.py +++ b/studio/app/ui/mediatek_mre_dialog.py @@ -17,6 +17,7 @@ @dataclass(frozen=True) class MediaTekProjectConfig: + template_id: str app_name: str app_version: str vendor: str @@ -34,6 +35,7 @@ class MediaTekProjectConfig: def project_metadata(self) -> dict: return { "name": self.app_name, + "template": self.template_id, "app_version": self.app_version, "vendor": self.vendor, "ram_kb": self.ram_kb, @@ -74,6 +76,14 @@ def sdk_metadata(self) -> dict: } +PROJECT_TEMPLATE_OPTIONS = ( + ("basic", "Blank Project", "Khung Lua tối giản để bắt đầu từ đầu"), + ("doodle-quest", "Doodle Quest", "Game mẫu có UI notebook, HUD, combo, 3 màn"), + ("ninja-runner", "Ninja Runner", "Game runner/parkour mẫu 240x320"), + ("catbox-mre", "CatBoxMRE", "Mẫu game nâng cao với nhiều hệ thống"), +) + + RESOLUTIONS = ( ("240x320", "240x320 (QVGA - Chuẩn Nokia)", 240, 320), ("320x240", "320x240 (QVGA - Ngang)", 320, 240), @@ -202,6 +212,12 @@ def __init__( grid.setHorizontalSpacing(14) grid.setVerticalSpacing(8) + self.template = QComboBox() + self.template.setObjectName("MRECombo") + for template_id, label, description in PROJECT_TEMPLATE_OPTIONS: + self.template.addItem(f"{label} — {description}", template_id) + self.template.setCurrentIndex(1) + self.app_name = QLineEdit(app_name) self.app_name.setObjectName("MREField") self.app_name.setPlaceholderText("Ví dụ: MRE Snake Retro") @@ -234,31 +250,35 @@ def __init__( self.path_preview.setObjectName("MREPathPreview") self.path_preview.setWordWrap(True) - self._label(grid, "Tên ứng dụng (APPNAME)", 0, 0) - self._label(grid, "Phiên bản (APPVER)", 0, 1) - grid.addWidget(self.app_name, 1, 0) - grid.addWidget(self.app_version, 1, 1) + self._label(grid, "Mẫu dự án", 0, 0, 1, 2) + grid.addWidget(self.template, 1, 0, 1, 2) + + self._label(grid, "Tên ứng dụng (APPNAME)", 2, 0) + self._label(grid, "Phiên bản (APPVER)", 2, 1) + grid.addWidget(self.app_name, 3, 0) + grid.addWidget(self.app_version, 3, 1) - self._label(grid, "Nhà phát triển (VENDOR)", 2, 0, 1, 2) - grid.addWidget(self.vendor, 3, 0, 1, 2) + self._label(grid, "Nhà phát triển (VENDOR)", 4, 0, 1, 2) + grid.addWidget(self.vendor, 5, 0, 1, 2) - self._label(grid, "Màn hình (Resolution)", 4, 0) - self._label(grid, "Chipset MediaTek", 4, 1) - grid.addWidget(self.resolution, 5, 0) - grid.addWidget(self.chipset, 5, 1) + self._label(grid, "Màn hình (Resolution)", 6, 0) + self._label(grid, "Chipset MediaTek", 6, 1) + grid.addWidget(self.resolution, 7, 0) + grid.addWidget(self.chipset, 7, 1) - self._label(grid, "Dung lượng Heap RAM cấp phát", 6, 0, 1, 2) - grid.addWidget(self.ram, 7, 0, 1, 2) + self._label(grid, "Dung lượng Heap RAM cấp phát", 8, 0, 1, 2) + grid.addWidget(self.ram, 9, 0, 1, 2) hint = QLabel( - "AppID được tạo tự động. MTK6260 mặc định dùng profile Nokia 225 / " - "S30+ native; đường dẫn MRE SDK dùng cấu hình chung trong Settings." + "AppID được tạo tự động. Chọn Doodle Quest để tạo sẵn game có " + "Splash/Menu/HUD/gameplay notebook. MTK6260 mặc định dùng profile " + "Nokia 225 / S30+ native." ) hint.setObjectName("MREDialogHint") hint.setWordWrap(True) - grid.addWidget(hint, 8, 0, 1, 2) + grid.addWidget(hint, 10, 0, 1, 2) - grid.addWidget(self.path_preview, 9, 0, 1, 2) + grid.addWidget(self.path_preview, 11, 0, 1, 2) root.addWidget(body) footer = QFrame() @@ -360,6 +380,7 @@ def configuration(self) -> MediaTekProjectConfig: cid, compat, api = self.chipset.currentData() ram_kb = int(self.ram.currentData()) return MediaTekProjectConfig( + template_id=str(self.template.currentData() or "basic"), app_name=ProjectSession.validate_project_name(self.app_name.text()), app_version=self.app_version.text().strip(), vendor=self.vendor.text().strip(), diff --git a/studio/app/vxpui/main_window.py b/studio/app/vxpui/main_window.py index 6a025fc..4b92944 100644 --- a/studio/app/vxpui/main_window.py +++ b/studio/app/vxpui/main_window.py @@ -958,6 +958,7 @@ def create_project_from_template(self) -> None: name, metadata=config.project_metadata(), sdk_metadata=config.sdk_metadata(), + template_name=config.template_id, ) except FileExistsError as exc: NoticeDialog("Dự án đã tồn tại", f"Đã có dự án trùng tên:\n{exc}", self, error=True).exec() @@ -976,6 +977,7 @@ def create_project_from_template(self) -> None: self.show_status( f"Project created: {info.root}" + (f" | AppID {appid}" if appid else "") + + f" | Template {config.template_id}" + f" | {config.chipset_id} | {config.screen_width}x{config.screen_height}" ) diff --git a/templates/DoodleQuest/.luas30/mre_sdk.json b/templates/DoodleQuest/.luas30/mre_sdk.json new file mode 100644 index 0000000..2c5b7a5 --- /dev/null +++ b/templates/DoodleQuest/.luas30/mre_sdk.json @@ -0,0 +1,21 @@ +{ + "schema": 1, + "platform": "MediaTek MRE", + "appname": "DoodleQuest", + "appver": "1.0.0", + "vendor": "LuaS30", + "resolution": { + "id": "240x320", + "label": "240x320 (QVGA - Chuẩn Nokia)", + "width": 240, + "height": 320 + }, + "chipset": { + "id": "MTK6260", + "label": "MTK6260 (Nokia 220, 225)" + }, + "heap_kb": 1024, + "heap_label": "1024 KB (1MB - Tiêu chuẩn game MRE)", + "compat_profile": "nokia225-rm1011", + "mre_api": "Audio File ProMng" +} diff --git a/templates/DoodleQuest/.luas30/ui_design.json b/templates/DoodleQuest/.luas30/ui_design.json new file mode 100644 index 0000000..708b1cc --- /dev/null +++ b/templates/DoodleQuest/.luas30/ui_design.json @@ -0,0 +1,16 @@ +{ + "format": "LuaS30 UI Design", + "version": 2, + "canvas": { + "width": 240, + "height": 320 + }, + "current": "main", + "screens": [ + { + "id": "main", + "name": "Main", + "items": [] + } + ] +} diff --git a/templates/DoodleQuest/README.md b/templates/DoodleQuest/README.md new file mode 100644 index 0000000..1b9033e --- /dev/null +++ b/templates/DoodleQuest/README.md @@ -0,0 +1,31 @@ +# Doodle Quest — LuaS30 game template + +A lightweight 240×320 game template with procedural notebook/doodle graphics and a complete keypad UI. + +## Included screens + +- Splash +- Main menu +- How To Play +- Settings +- About +- Gameplay + HUD +- Win / Game Over state + +## Controls + +- 2 / 8: up / down +- 4 / 6: left / right +- 5 or #: confirm +- 0 / Back / Soft Left: back + +## Target + +- Nokia S30+ / MediaTek MRE +- MTK6260 +- 240×320 +- 1024 KB baseline heap +- 15 FPS +- profile: `nokia225-rm1011` + +The graphics are drawn with rectangles, frames and text, so the template works without loading large bitmap assets and is suitable as a low-RAM starter project. diff --git a/templates/DoodleQuest/conf.lua b/templates/DoodleQuest/conf.lua new file mode 100644 index 0000000..1008cdb --- /dev/null +++ b/templates/DoodleQuest/conf.lua @@ -0,0 +1,7 @@ +config = { + name = "Doodle Quest", + screen_width = 240, + screen_height = 320, + fps = 15 +} +return config diff --git a/templates/DoodleQuest/main.lua b/templates/DoodleQuest/main.lua new file mode 100644 index 0000000..398b514 --- /dev/null +++ b/templates/DoodleQuest/main.lua @@ -0,0 +1,52 @@ +-- Doodle Quest: lightweight notebook-art game template for LuaS30 / MRE. +-- Screens: splash -> main menu -> play -> pause, plus guide/settings/about. + +local E = engine +local Gfx = require("src.gfx") +local UI = require("src.ui") +local Game = require("src.game") + +local mode = "ui" + +local KEYMAP = { + ["2"] = "up", ["8"] = "down", ["4"] = "left", ["6"] = "right", + ["5"] = "ok", ["#"] = "ok", + ["0"] = "back", ["clear"] = "back", ["back"] = "back", + ["softleft"] = "back", ["softright"] = "ok", +} + +local function route(name) + if name == "play" then + mode = "game" + Game.start() + else + mode = "ui" + UI.enter(name) + end +end + +function E.load() + E.set_font(8) + UI.go = route + Game.onExit = function() route("menu") end + route("splash") +end + +function E.update(dt) + if mode == "game" then Game.update(dt) else UI.update(dt) end +end + +function E.draw() + if mode == "game" then Game.draw() else UI.draw() end + E.flush() +end + +function E.keypressed(key) + local k = KEYMAP[key] or key + if mode == "game" then Game.keypressed(k) else UI.keypressed(k) end +end + +function E.keyreleased(key) + local k = KEYMAP[key] or key + if mode == "game" then Game.keyreleased(k) end +end diff --git a/templates/DoodleQuest/project.json b/templates/DoodleQuest/project.json new file mode 100644 index 0000000..d05f496 --- /dev/null +++ b/templates/DoodleQuest/project.json @@ -0,0 +1,19 @@ +{ + "name": "DoodleQuest", + "vendor": "LuaS30", + "appid": 586534796, + "ram_kb": 1024, + "screen_width": 240, + "screen_height": 320, + "fps": 15, + "runtime_target": "mre-s30plus", + "single_vxp": true, + "compat_profile": "nokia225-rm1011", + "mre_api": "Audio File ProMng", + "app_version": "1.0.0", + "mediatek_chipset": "MTK6260", + "mediatek_chipset_label": "MTK6260 (Nokia 220, 225)", + "resolution": "240x320", + "resolution_label": "240x320 (QVGA - Chuẩn Nokia)", + "project_wizard": "mediatek-mre-sdk-v1" +} diff --git a/templates/DoodleQuest/src/game.lua b/templates/DoodleQuest/src/game.lua new file mode 100644 index 0000000..56bb346 --- /dev/null +++ b/templates/DoodleQuest/src/game.lua @@ -0,0 +1,124 @@ +local E = engine +local Gfx = require("src.gfx") +local C = Gfx.C + +local M = {} +M.onExit = function() end + +local state = {} +local W, H = 240, 320 + +local function clamp(v, lo, hi) + if v < lo then return lo end + if v > hi then return hi end + return v +end + +local function hit(ax, ay, aw, ah, bx, by, bw, bh) + return ax < bx + bw and ax + aw > bx and ay < by + bh and ay + ah > by +end + +local function resetStar() + state.sx = 34 + ((state.seed * 37) % 174) + state.sy = 52 + ((state.seed * 73) % 206) + state.seed = (state.seed * 17 + 11) % 997 +end + +function M.start() + state.x = 112 + state.y = 154 + state.score = 0 + state.time = 40 + state.seed = math.floor(E.tick_ms()) % 997 + state.frame = 0 + state.anim = 0 + state.over = false + state.win = false + state.hazards = { + { x = 42, y = 90, w = 24, h = 16, vx = 20 }, + { x = 156, y = 210, w = 28, h = 16, vx = -24 }, + } + resetStar() +end + +function M.update(dt) + if state.over then return end + state.time = state.time - dt + if state.time <= 0 then + state.time = 0 + state.over = true + end + + state.anim = state.anim + dt + if state.anim > 0.18 then + state.anim = 0 + state.frame = (state.frame + 1) % 2 + end + + for i = 1, #state.hazards do + local h = state.hazards[i] + h.x = h.x + h.vx * dt + if h.x < 28 then h.x = 28; h.vx = -h.vx end + if h.x + h.w > 212 then h.x = 212 - h.w; h.vx = -h.vx end + if hit(state.x, state.y, 14, 18, h.x, h.y, h.w, h.h) then + state.time = math.max(0, state.time - 3) + state.x, state.y = 112, 154 + end + end + + if hit(state.x, state.y, 14, 18, state.sx, state.sy, 9, 9) then + state.score = state.score + 1 + resetStar() + if state.score >= 10 then + state.win = true + state.over = true + end + end +end + +function M.draw() + Gfx.paper() + Gfx.hud(state.score, state.time) + + -- playfield card + Gfx.panel(27, 43, 186, 224) + + -- doodle obstacles + for i = 1, #state.hazards do + local h = state.hazards[i] + Gfx.rect(h.x, h.y, h.w, h.h, C.accent2) + E.frame(math.floor(h.x), math.floor(h.y), h.w, h.h, C.ink) + Gfx.rect(h.x + 4, h.y + 4, h.w - 8, 2, C.white) + end + + Gfx.star(state.sx, state.sy, C.accent) + Gfx.player(state.x, state.y, state.frame) + + if state.over then + Gfx.panel(42, 112, 156, 98) + Gfx.text_center(130, state.win and "YOU WIN!" or "TIME UP", state.win and C.green or C.accent2) + Gfx.text_center(154, "SCORE " .. tostring(state.score), C.ink) + Gfx.text_center(180, "5 RETRY / 0 MENU", C.ink2) + else + Gfx.text_center(280, "MOVE 2 4 6 8 | 0 PAUSE", C.ink2) + end +end + +function M.keypressed(k) + if state.over then + if k == "ok" then M.start() + elseif k == "back" then M.onExit() end + return + end + + local step = 8 + if k == "left" then state.x = clamp(state.x - step, 30, 196) + elseif k == "right" then state.x = clamp(state.x + step, 30, 196) + elseif k == "up" then state.y = clamp(state.y - step, 46, 246) + elseif k == "down" then state.y = clamp(state.y + step, 46, 246) + elseif k == "back" then M.onExit() end +end + +function M.keyreleased(_) end + +return M diff --git a/templates/DoodleQuest/src/gfx.lua b/templates/DoodleQuest/src/gfx.lua new file mode 100644 index 0000000..7bfb1e8 --- /dev/null +++ b/templates/DoodleQuest/src/gfx.lua @@ -0,0 +1,121 @@ +-- Procedural notebook/doodle renderer. No external bitmap assets required. +local E = engine +local M = {} + +local cache = {} +function M.col(r, g, b) + local key = r * 65536 + g * 256 + b + local c = cache[key] + if not c then + c = E.color(r, g, b) + cache[key] = c + end + return c +end + +local C = { + paper = M.col(246, 242, 220), + paper2 = M.col(238, 233, 205), + line = M.col(173, 198, 212), + margin = M.col(218, 92, 92), + ink = M.col(25, 44, 78), + ink2 = M.col(52, 73, 112), + pencil = M.col(96, 91, 82), + white = M.col(255, 255, 247), + accent = M.col(244, 142, 54), + accent2 = M.col(233, 96, 68), + green = M.col(76, 145, 86), + shadow = M.col(206, 199, 174), + dark = M.col(37, 38, 43), +} +M.C = C + +function M.rect(x, y, w, h, color) + if w <= 0 or h <= 0 then return end + local x2, y2 = x + w, y + h + if x < 0 then x = 0 end + if y < 0 then y = 0 end + if x2 > 240 then x2 = 240 end + if y2 > 320 then y2 = 320 end + if x2 > x and y2 > y then + E.rect(math.floor(x), math.floor(y), math.floor(x2 - x), math.floor(y2 - y), color) + end +end + +function M.paper() + E.clear(C.paper) + for y = 24, 319, 16 do + M.rect(0, y, 240, 1, C.line) + end + M.rect(20, 0, 1, 320, C.margin) + M.rect(22, 0, 1, 320, C.margin) + -- notebook holes + for y = 20, 300, 36 do + M.rect(6, y, 6, 6, C.shadow) + M.rect(7, y + 1, 4, 4, C.paper2) + end +end + +function M.text_center(y, text, color) + local w = E.text_width(text) + E.text(math.floor((240 - w) / 2), y, text, color) +end + +function M.title(text) + M.rect(28, 10, 184, 28, C.white) + E.frame(28, 10, 184, 28, C.ink) + M.text_center(18, text, C.ink) + M.rect(34, 37, 172, 2, C.accent) +end + +function M.button(y, label, selected) + local x, w, h = 38, 164, 28 + M.rect(x + 2, y + 2, w, h, C.shadow) + M.rect(x, y, w, h, selected and C.white or C.paper2) + E.frame(x, y, w, h, selected and C.accent2 or C.ink2) + if selected then + M.rect(x + 4, y + 4, 4, h - 8, C.accent) + E.text(x + 13, y + 9, ">", C.accent2) + end + local tx = x + math.floor((w - E.text_width(label)) / 2) + E.text(tx, y + 9, label, selected and C.ink or C.ink2) +end + +function M.panel(x, y, w, h) + M.rect(x + 3, y + 3, w, h, C.shadow) + M.rect(x, y, w, h, C.white) + E.frame(x, y, w, h, C.ink2) +end + +function M.star(x, y, color) + color = color or C.accent + M.rect(x + 3, y, 3, 9, color) + M.rect(x, y + 3, 9, 3, color) + M.rect(x + 1, y + 1, 7, 7, color) + M.rect(x + 3, y + 3, 3, 3, C.white) +end + +function M.player(x, y, frame) + -- simple doodle character, 14x18 + local bob = frame % 2 + M.rect(x + 4, y, 7, 6, C.ink) + M.rect(x + 5, y + 1, 5, 4, C.white) + M.rect(x + 6, y + 2, 1, 1, C.ink) + M.rect(x + 9, y + 2, 1, 1, C.ink) + M.rect(x + 5, y + 7, 5, 7, C.ink2) + M.rect(x + 3, y + 8 + bob, 2, 6, C.ink2) + M.rect(x + 10, y + 8 + (1 - bob), 2, 6, C.ink2) + M.rect(x + 5, y + 14, 2, 4, C.dark) + M.rect(x + 8, y + 14, 2, 4, C.dark) + M.rect(x + 2, y + 17, 5, 1, C.dark) + M.rect(x + 8, y + 17, 5, 1, C.dark) +end + +function M.hud(score, timeLeft) + M.panel(28, 8, 184, 28) + E.text(36, 18, "SCORE " .. tostring(score), C.ink) + local t = "TIME " .. tostring(math.max(0, math.ceil(timeLeft))) + E.text(204 - E.text_width(t), 18, t, C.accent2) +end + +return M diff --git a/templates/DoodleQuest/src/ui.lua b/templates/DoodleQuest/src/ui.lua new file mode 100644 index 0000000..b22065a --- /dev/null +++ b/templates/DoodleQuest/src/ui.lua @@ -0,0 +1,139 @@ +local E = engine +local Gfx = require("src.gfx") +local C = Gfx.C + +local M = {} +M.show = "splash" +M.go = function(_) end + +local elapsed = 0 +local menu = 1 +local settings = 1 +local sound = true + +local MENU = { "PLAY", "HOW TO PLAY", "SETTINGS", "ABOUT", "EXIT" } + +function M.enter(name) + M.show = name + if name == "splash" then elapsed = 0 end + if name == "menu" then menu = 1 end + if name == "settings" then settings = 1 end +end + +function M.update(dt) + elapsed = elapsed + dt + if M.show == "splash" and elapsed > 2.8 then + M.go("menu") + end +end + +local function footer(text) + Gfx.panel(34, 282, 172, 24) + Gfx.text_center(291, text, C.ink2) +end + +local function drawSplash() + Gfx.paper() + Gfx.panel(32, 78, 176, 122) + Gfx.text_center(96, "DOODLE QUEST", C.ink) + Gfx.text_center(116, "NOTEBOOK ADVENTURE", C.accent2) + Gfx.player(110, 142, math.floor(elapsed * 5)) + Gfx.star(70, 150, C.accent) + Gfx.star(160, 132, C.green) + if math.floor(elapsed * 2) % 2 == 0 then + Gfx.text_center(224, "PRESS 5", C.ink2) + end + footer("LUA S30 GAME TEMPLATE") +end + +local function drawMenu() + Gfx.paper() + Gfx.title("DOODLE QUEST") + for i = 1, #MENU do + Gfx.button(58 + (i - 1) * 39, MENU[i], i == menu) + end + footer("2/8 SELECT 5 OK") +end + +local function drawGuide() + Gfx.paper() + Gfx.title("HOW TO PLAY") + Gfx.panel(30, 58, 180, 178) + local lines = { + "MOVE: 2 4 6 8", + "COLLECT ORANGE STARS", + "AVOID RED BLOCKS", + "GET 10 STARS TO WIN", + "0 / BACK: RETURN", + } + for i = 1, #lines do + E.text(42, 78 + (i - 1) * 28, lines[i], i == 1 and C.accent2 or C.ink) + end + footer("5 / BACK TO MENU") +end + +local function drawSettings() + Gfx.paper() + Gfx.title("SETTINGS") + Gfx.panel(30, 68, 180, 118) + Gfx.button(86, "SOUND: " .. (sound and "ON" or "OFF"), settings == 1) + Gfx.button(128, "RESET DEFAULTS", settings == 2) + footer("2/8 SELECT 5 CHANGE") +end + +local function drawAbout() + Gfx.paper() + Gfx.title("ABOUT") + Gfx.panel(28, 62, 184, 150) + Gfx.text_center(82, "DOODLE QUEST", C.ink) + Gfx.text_center(106, "UI + GAME TEMPLATE", C.accent2) + Gfx.text_center(132, "240x320 / MTK6260", C.ink2) + Gfx.text_center(158, "LOW-RAM PROCEDURAL ART", C.ink2) + Gfx.text_center(184, "Qeafivels / LuaS30", C.green) + footer("5 / BACK TO MENU") +end + +function M.draw() + if M.show == "splash" then drawSplash() + elseif M.show == "menu" then drawMenu() + elseif M.show == "guide" then drawGuide() + elseif M.show == "settings" then drawSettings() + else drawAbout() end +end + +function M.keypressed(k) + if M.show == "splash" then + M.go("menu") + return + end + + if M.show == "menu" then + if k == "up" then menu = (menu - 2) % #MENU + 1 + elseif k == "down" then menu = menu % #MENU + 1 + elseif k == "ok" then + if menu == 1 then M.go("play") + elseif menu == 2 then M.go("guide") + elseif menu == 3 then M.go("settings") + elseif menu == 4 then M.go("about") + else E.exit() end + elseif k == "back" then + E.exit() + end + return + end + + if M.show == "settings" then + if k == "up" then settings = (settings - 2) % 2 + 1 + elseif k == "down" then settings = settings % 2 + 1 + elseif k == "ok" then + if settings == 1 then sound = not sound else sound = true end + elseif k == "back" then M.go("menu") end + return + end + + if k == "back" or k == "ok" then + M.go("menu") + end +end + +return M