88870d7ac5
- Added renderer, game, INPUT tables mapping Pico monochrome API to RGB565 framebuffer - renderer.clear, pixel, line, rect, circle, triangle (filled via scanline), text, text_scaled (8x8 font scaled) - game.width/height, vars, set_frame_updates, exit - INPUT constants NONE, TOUCH_DOWN/MOVE/UP, BUTTON_0/1, FRAME_TICK, GESTURE - Event generation: touch down/move/up detection + FRAME_TICK each frame - is_pico detection via source content (renderer./INPUT.) and game.vars heuristic - Unified game loop handling both sys API (update(dt)) and Pico API (update(event)) - Generated pico_games.h with all 17 examples from pico-bare-metal: 2048, air_hockey, asteroids, ball, breakout, counter, flappy_bird, lunar_lander, memory_match, pacman, pong, simon_says, snake, solitaire, tetris, tic_tac_toe - Tested: Pong runs at 17 FPS, 'Pong initialized' log, no Lua errors - Fixes: renamed breakout_lua_src to embedded_breakout_lua_src to avoid conflict with pico breakout - Ready for game selector UI (menu wrapper) - currently embedded breakout fast v2 remains default, pico test flag use_pico_test available for dev
4628 lines
162 KiB
C
4628 lines
162 KiB
C
#pragma once
|
|
// Auto-generated pico games
|
|
static const char game_2048_lua_src[] = "-- NAME: 2048\n"
|
|
"-- DESC: Merge tiles to reach 2048\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_GAME_OVER = 2\n"
|
|
"local STATE_WIN = 3\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local GRID_SIZE = 4\n"
|
|
"\n"
|
|
"-- Calculate tile size based on screen size\n"
|
|
"local function get_tile_size()\n"
|
|
" -- Use smallest dimension for square grid\n"
|
|
" local min_dim = math.min(game.width(), game.height())\n"
|
|
" local padding = math.floor(min_dim / 8)\n"
|
|
" local available = min_dim - (padding * 2)\n"
|
|
" local tile_size = math.floor(available / GRID_SIZE)\n"
|
|
" return tile_size\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function get_grid_start_x()\n"
|
|
" local tile_size = get_tile_size()\n"
|
|
" local grid_width = tile_size * GRID_SIZE\n"
|
|
" return math.floor((game.width() - grid_width) / 2)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function get_grid_start_y()\n"
|
|
" local tile_size = get_tile_size()\n"
|
|
" local grid_height = tile_size * GRID_SIZE\n"
|
|
" return math.floor((game.height() - grid_height) / 2)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" game.vars.score = 0\n"
|
|
" \n"
|
|
" -- Grid (4x4, 0 = empty)\n"
|
|
" game.vars.grid = {}\n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" game.vars.grid[y] = {}\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" game.vars.grid[y][x] = 0\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" game.vars.moved = false\n"
|
|
" game.vars.won = false\n"
|
|
" \n"
|
|
" -- Enable continuous updates\n"
|
|
" -- game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"2048 initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" reset_game()\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN then\n"
|
|
" -- Determine swipe direction\n"
|
|
" local direction = get_swipe_direction(event.x, event.y)\n"
|
|
" if direction then\n"
|
|
" game.vars.moved = false\n"
|
|
" move_tiles(direction)\n"
|
|
" \n"
|
|
" if game.vars.moved then\n"
|
|
" spawn_tile()\n"
|
|
" \n"
|
|
" -- Check win/lose\n"
|
|
" if has_tile(2048) then\n"
|
|
" if not game.vars.won then\n"
|
|
" game.vars.state = STATE_WIN\n"
|
|
" game.vars.won = true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" if not can_move() then\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_WIN then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" -- Can continue playing or go to menu\n"
|
|
" if event.y < game.height() / 2 then\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" else\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" end\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false)\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" local tile_size = get_tile_size()\n"
|
|
" local start_x = get_grid_start_x()\n"
|
|
" local start_y = get_grid_start_y()\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 15, game.height() / 2 - 30, \"2048\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, \"Tap to Start\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 + 30, \"Welcome Adolfo\", true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING or state == STATE_WIN or state == STATE_GAME_OVER then\n"
|
|
" -- Draw grid\n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" local tile_x = start_x + (x - 1) * tile_size\n"
|
|
" local tile_y = start_y + (y - 1) * tile_size\n"
|
|
" local value = game.vars.grid[y][x]\n"
|
|
" \n"
|
|
" if value == 0 then\n"
|
|
" -- Empty tile\n"
|
|
" renderer.rect(tile_x, tile_y, tile_size, tile_size, true, false)\n"
|
|
" else\n"
|
|
" local reduce_size\n"
|
|
" if value == 2 then\n"
|
|
" reduce_size = 10\n"
|
|
" elseif value == 4 then\n"
|
|
" reduce_size = 8\n"
|
|
" elseif value == 8 then\n"
|
|
" reduce_size = 6\n"
|
|
" elseif value == 16 then\n"
|
|
" reduce_size = 4\n"
|
|
" elseif value == 32 then\n"
|
|
" reduce_size = 2\n"
|
|
" else\n"
|
|
" reduce_size = 0\n"
|
|
" end\n"
|
|
" -- Empty tile\n"
|
|
" renderer.rect(tile_x, tile_y, tile_size, tile_size, true, false)\n"
|
|
" -- Filled tile\n"
|
|
" renderer.rect(tile_x+reduce_size, tile_y+reduce_size, tile_size-reduce_size*2, tile_size-reduce_size*2, true, true)\n"
|
|
" \n"
|
|
" -- Draw value (simplified)\n"
|
|
" local text = tostring(value)\n"
|
|
" if string.len(text) < 2 then\n"
|
|
" renderer.text_scaled(tile_x + tile_size / 2 - 4, tile_y + tile_size / 2 - 8, text, false, 2)\n"
|
|
" else\n"
|
|
" renderer.text_scaled(tile_x + tile_size / 2 - 12, tile_y + tile_size / 2 - 8, text, false, 2)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw score\n"
|
|
" renderer.text_scaled(10, 10, \"Score: \" .. tostring(game.vars.score), true, 2)\n"
|
|
" \n"
|
|
" if state == STATE_WIN then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, game.height() / 2 - 20, \"YOU WIN!\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() / 2, \"Tap up: Continue\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 + 15, \"Tap down: Menu\", true, 2)\n"
|
|
" end\n"
|
|
" \n"
|
|
" if state == STATE_GAME_OVER then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2, \"GAME OVER\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, game.height() / 2 + 15, \"Score: \" .. tostring(game.vars.score), true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() / 2 + 30, \"Tap to Menu\", true, 2)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function get_swipe_direction(x, y)\n"
|
|
" -- Simple tap-based direction (could be enhanced with swipe tracking)\n"
|
|
" local mid_x = game.width() / 2\n"
|
|
" local mid_y = game.height() / 2\n"
|
|
" \n"
|
|
" local dx = x - mid_x\n"
|
|
" local dy = y - mid_y\n"
|
|
" \n"
|
|
" if math.abs(dx) > math.abs(dy) then\n"
|
|
" if dx > 0 then return \"right\" end\n"
|
|
" return \"left\"\n"
|
|
" else\n"
|
|
" if dy > 0 then return \"down\" end\n"
|
|
" return \"up\"\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function move_tiles(direction)\n"
|
|
" local old_grid = {}\n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" old_grid[y] = {}\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" old_grid[y][x] = game.vars.grid[y][x]\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" if direction == \"left\" then\n"
|
|
" move_left()\n"
|
|
" elseif direction == \"right\" then\n"
|
|
" move_right()\n"
|
|
" elseif direction == \"up\" then\n"
|
|
" move_up()\n"
|
|
" elseif direction == \"down\" then\n"
|
|
" move_down()\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Check if grid changed\n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" if old_grid[y][x] ~= game.vars.grid[y][x] then\n"
|
|
" game.vars.moved = true\n"
|
|
" return\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function move_left()\n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" -- Compact\n"
|
|
" local row = {}\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" if game.vars.grid[y][x] ~= 0 then\n"
|
|
" table.insert(row, game.vars.grid[y][x])\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Merge\n"
|
|
" local i = 1\n"
|
|
" while i < #row do\n"
|
|
" if row[i] == row[i + 1] then\n"
|
|
" row[i] = row[i] * 2\n"
|
|
" game.vars.score = game.vars.score + row[i]\n"
|
|
" table.remove(row, i + 1)\n"
|
|
" end\n"
|
|
" i = i + 1\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Fill back\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" if x <= #row then\n"
|
|
" game.vars.grid[y][x] = row[x]\n"
|
|
" else\n"
|
|
" game.vars.grid[y][x] = 0\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function move_right()\n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" local row = {}\n"
|
|
" for x = GRID_SIZE, 1, -1 do\n"
|
|
" if game.vars.grid[y][x] ~= 0 then\n"
|
|
" table.insert(row, game.vars.grid[y][x])\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" local i = 1\n"
|
|
" while i < #row do\n"
|
|
" if row[i] == row[i + 1] then\n"
|
|
" row[i] = row[i] * 2\n"
|
|
" game.vars.score = game.vars.score + row[i]\n"
|
|
" table.remove(row, i + 1)\n"
|
|
" end\n"
|
|
" i = i + 1\n"
|
|
" end\n"
|
|
" \n"
|
|
" for x = GRID_SIZE, 1, -1 do\n"
|
|
" if GRID_SIZE - x + 1 <= #row then\n"
|
|
" game.vars.grid[y][x] = row[GRID_SIZE - x + 1]\n"
|
|
" else\n"
|
|
" game.vars.grid[y][x] = 0\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function move_up()\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" local col = {}\n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" if game.vars.grid[y][x] ~= 0 then\n"
|
|
" table.insert(col, game.vars.grid[y][x])\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" local i = 1\n"
|
|
" while i < #col do\n"
|
|
" if col[i] == col[i + 1] then\n"
|
|
" col[i] = col[i] * 2\n"
|
|
" game.vars.score = game.vars.score + col[i]\n"
|
|
" table.remove(col, i + 1)\n"
|
|
" end\n"
|
|
" i = i + 1\n"
|
|
" end\n"
|
|
" \n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" if y <= #col then\n"
|
|
" game.vars.grid[y][x] = col[y]\n"
|
|
" else\n"
|
|
" game.vars.grid[y][x] = 0\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function move_down()\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" local col = {}\n"
|
|
" for y = GRID_SIZE, 1, -1 do\n"
|
|
" if game.vars.grid[y][x] ~= 0 then\n"
|
|
" table.insert(col, game.vars.grid[y][x])\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" local i = 1\n"
|
|
" while i < #col do\n"
|
|
" if col[i] == col[i + 1] then\n"
|
|
" col[i] = col[i] * 2\n"
|
|
" game.vars.score = game.vars.score + col[i]\n"
|
|
" table.remove(col, i + 1)\n"
|
|
" end\n"
|
|
" i = i + 1\n"
|
|
" end\n"
|
|
" \n"
|
|
" for y = GRID_SIZE, 1, -1 do\n"
|
|
" if GRID_SIZE - y + 1 <= #col then\n"
|
|
" game.vars.grid[y][x] = col[GRID_SIZE - y + 1]\n"
|
|
" else\n"
|
|
" game.vars.grid[y][x] = 0\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function spawn_tile()\n"
|
|
" local empty = {}\n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" if game.vars.grid[y][x] == 0 then\n"
|
|
" table.insert(empty, {x = x, y = y})\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" if #empty > 0 then\n"
|
|
" local pos = empty[math.random(1, #empty)]\n"
|
|
" game.vars.grid[pos.y][pos.x] = math.random() > 0.9 and 4 or 2\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function has_tile(value)\n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" if game.vars.grid[y][x] == value then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function can_move()\n"
|
|
" -- Check if any empty tiles\n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" if game.vars.grid[y][x] == 0 then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Check if any merges possible\n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" local val = game.vars.grid[y][x]\n"
|
|
" if x < GRID_SIZE and game.vars.grid[y][x + 1] == val then return true end\n"
|
|
" if y < GRID_SIZE and game.vars.grid[y + 1][x] == val then return true end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_game()\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.won = false\n"
|
|
" \n"
|
|
" for y = 1, GRID_SIZE do\n"
|
|
" for x = 1, GRID_SIZE do\n"
|
|
" game.vars.grid[y][x] = 0\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" spawn_tile()\n"
|
|
" spawn_tile()\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char air_hockey_lua_src[] = "-- NAME: Air Hockey\n"
|
|
"-- DESC: Fast-paced 2-player hockey\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_GAME_OVER = 2\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local PADDLE_WIDTH = 6\n"
|
|
"local PADDLE_HEIGHT = 35\n"
|
|
"local PUCK_RADIUS = 3\n"
|
|
"local MAX_SCORE = 7\n"
|
|
"local PUCK_SPEED = 4\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" \n"
|
|
" -- Left paddle (player 1)\n"
|
|
" game.vars.paddle_left_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)\n"
|
|
" game.vars.paddle_left_score = 0\n"
|
|
" \n"
|
|
" -- Right paddle (player 2)\n"
|
|
" game.vars.paddle_right_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)\n"
|
|
" game.vars.paddle_right_score = 0\n"
|
|
" \n"
|
|
" -- Puck\n"
|
|
" game.vars.puck_x = game.width() / 2\n"
|
|
" game.vars.puck_y = game.height() / 2\n"
|
|
" game.vars.puck_vel_x = PUCK_SPEED\n"
|
|
" game.vars.puck_vel_y = 1\n"
|
|
" \n"
|
|
" -- Enable continuous updates\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Air Hockey initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" reset_game()\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Handle paddle input via touch\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.TOUCH_MOVE then\n"
|
|
" if event.x < game.width() / 2 then\n"
|
|
" -- Left paddle\n"
|
|
" game.vars.paddle_left_y = math.max(0, math.min(game.height() - PADDLE_HEIGHT, event.y - PADDLE_HEIGHT / 2))\n"
|
|
" else\n"
|
|
" -- Right paddle\n"
|
|
" game.vars.paddle_right_y = math.max(0, math.min(game.height() - PADDLE_HEIGHT, event.y - PADDLE_HEIGHT / 2))\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Update physics on frame tick\n"
|
|
" if event.type == INPUT.FRAME_TICK then\n"
|
|
" update_puck()\n"
|
|
" check_collisions()\n"
|
|
" \n"
|
|
" -- Check win\n"
|
|
" if game.vars.paddle_left_score >= MAX_SCORE or game.vars.paddle_right_score >= MAX_SCORE then\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false) -- Black background\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 35, game.height() / 2 - 30, \"AIR HOCKEY\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, \"Tap to Start\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() / 2 + 20, \"First to \" .. tostring(MAX_SCORE), true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING or state == STATE_GAME_OVER then\n"
|
|
" -- Draw center line\n"
|
|
" for y = 0, game.height(), 4 do\n"
|
|
" renderer.pixel(game.width() / 2, y, true)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw goal areas (top/bottom highlights)\n"
|
|
" renderer.line(0, 5, game.width(), 5, true, 1)\n"
|
|
" renderer.line(0, game.height() - 5, game.width(), game.height() - 5, true, 1)\n"
|
|
" \n"
|
|
" -- Draw paddles\n"
|
|
" renderer.rect(5, game.vars.paddle_left_y, PADDLE_WIDTH, PADDLE_HEIGHT, true, true)\n"
|
|
" renderer.rect(game.width() - 5 - PADDLE_WIDTH, game.vars.paddle_right_y, PADDLE_WIDTH, PADDLE_HEIGHT, true, true)\n"
|
|
" \n"
|
|
" -- Draw puck (convert to integers)\n"
|
|
" renderer.circle(math.floor(game.vars.puck_x + 0.5), math.floor(game.vars.puck_y + 0.5), PUCK_RADIUS, true, true)\n"
|
|
" \n"
|
|
" -- Draw scores\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, 5, tostring(game.vars.paddle_left_score), true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 + 20, 5, tostring(game.vars.paddle_right_score), true, 2)\n"
|
|
" \n"
|
|
" if state == STATE_GAME_OVER then\n"
|
|
" local winner = game.vars.paddle_left_score > game.vars.paddle_right_score and \"Player 1\" or \"Player 2\"\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 - 20, \"GAME OVER\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2, winner .. \" Wins!\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() / 2 + 20, \"Tap to Menu\", true, 2)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update_puck()\n"
|
|
" -- Move puck\n"
|
|
" game.vars.puck_x = game.vars.puck_x + game.vars.puck_vel_x\n"
|
|
" game.vars.puck_y = game.vars.puck_y + game.vars.puck_vel_y\n"
|
|
" \n"
|
|
" -- Bounce off top/bottom\n"
|
|
" if game.vars.puck_y - PUCK_RADIUS < 0 or game.vars.puck_y + PUCK_RADIUS > game.height() then\n"
|
|
" game.vars.puck_vel_y = -game.vars.puck_vel_y\n"
|
|
" game.vars.puck_y = math.max(PUCK_RADIUS, math.min(game.height() - PUCK_RADIUS, game.vars.puck_y))\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Goal: left side\n"
|
|
" if game.vars.puck_x < 0 then\n"
|
|
" game.vars.paddle_right_score = game.vars.paddle_right_score + 1\n"
|
|
" reset_puck()\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Goal: right side\n"
|
|
" if game.vars.puck_x > game.width() then\n"
|
|
" game.vars.paddle_left_score = game.vars.paddle_left_score + 1\n"
|
|
" reset_puck()\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function check_collisions()\n"
|
|
" -- Left paddle collision\n"
|
|
" if game.vars.puck_x - PUCK_RADIUS < 5 + PADDLE_WIDTH then\n"
|
|
" if game.vars.puck_y > game.vars.paddle_left_y and game.vars.puck_y < game.vars.paddle_left_y + PADDLE_HEIGHT then\n"
|
|
" if game.vars.puck_vel_x < 0 then\n"
|
|
" game.vars.puck_vel_x = -game.vars.puck_vel_x + 0.5 -- Speed up slightly\n"
|
|
" \n"
|
|
" -- Add spin\n"
|
|
" local hit_pos = (game.vars.puck_y - game.vars.paddle_left_y) / PADDLE_HEIGHT\n"
|
|
" game.vars.puck_vel_y = (hit_pos - 0.5) * 6\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Right paddle collision\n"
|
|
" if game.vars.puck_x + PUCK_RADIUS > game.width() - 5 - PADDLE_WIDTH then\n"
|
|
" if game.vars.puck_y > game.vars.paddle_right_y and game.vars.puck_y < game.vars.paddle_right_y + PADDLE_HEIGHT then\n"
|
|
" if game.vars.puck_vel_x > 0 then\n"
|
|
" game.vars.puck_vel_x = -game.vars.puck_vel_x - 0.5 -- Speed up slightly\n"
|
|
" \n"
|
|
" -- Add spin\n"
|
|
" local hit_pos = (game.vars.puck_y - game.vars.paddle_right_y) / PADDLE_HEIGHT\n"
|
|
" game.vars.puck_vel_y = (hit_pos - 0.5) * 6\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_puck()\n"
|
|
" game.vars.puck_x = game.width() / 2\n"
|
|
" game.vars.puck_y = game.height() / 2\n"
|
|
" game.vars.puck_vel_x = PUCK_SPEED * (math.random() > 0.5 and 1 or -1)\n"
|
|
" game.vars.puck_vel_y = (math.random() - 0.5) * 2\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_game()\n"
|
|
" game.vars.paddle_left_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)\n"
|
|
" game.vars.paddle_left_score = 0\n"
|
|
" game.vars.paddle_right_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)\n"
|
|
" game.vars.paddle_right_score = 0\n"
|
|
" reset_puck()\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char asteroids_lua_src[] = "-- NAME: Asteroids\n"
|
|
"-- DESC: Destroy asteroids, avoid collisions\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_GAME_OVER = 2\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local SHIP_SPEED = 2\n"
|
|
"local SHIP_ROTATION_SPEED = 8\n"
|
|
"local BULLET_SPEED = 5\n"
|
|
"local ASTEROID_SPAWN_RATE = 120\n"
|
|
"local ASTEROID_SPEEDS = {1.5, 2, 2.5, 3}\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.level = 1\n"
|
|
" \n"
|
|
" -- Ship\n"
|
|
" game.vars.ship_x = game.width() / 2\n"
|
|
" game.vars.ship_y = game.height() / 2\n"
|
|
" game.vars.ship_angle = 0 -- Radians\n"
|
|
" game.vars.ship_vel_x = 0\n"
|
|
" game.vars.ship_vel_y = 0\n"
|
|
" game.vars.thrusting = false\n"
|
|
" \n"
|
|
" -- Bullets\n"
|
|
" game.vars.bullets = {}\n"
|
|
" game.vars.bullet_cooldown = 0\n"
|
|
" \n"
|
|
" -- Asteroids\n"
|
|
" game.vars.asteroids = {}\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" \n"
|
|
" -- Enable continuous updates\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Asteroids initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" reset_game()\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Handle input\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.TOUCH_MOVE then\n"
|
|
" if event.x < game.width() / 2 then\n"
|
|
" -- Left side: rotate counter-clockwise\n"
|
|
" game.vars.ship_angle = game.vars.ship_angle - SHIP_ROTATION_SPEED * 0.017\n"
|
|
" else\n"
|
|
" -- Right side: rotate clockwise\n"
|
|
" game.vars.ship_angle = game.vars.ship_angle + SHIP_ROTATION_SPEED * 0.017\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Thrust\n"
|
|
" game.vars.thrusting = true\n"
|
|
" else\n"
|
|
" game.vars.thrusting = false\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Update physics on frame tick\n"
|
|
" if event.type == INPUT.FRAME_TICK then\n"
|
|
" update_ship()\n"
|
|
" update_bullets()\n"
|
|
" update_asteroids()\n"
|
|
" check_collisions()\n"
|
|
" \n"
|
|
" -- Spawn asteroids\n"
|
|
" game.vars.frame_count = game.vars.frame_count + 1\n"
|
|
" if game.vars.frame_count >= ASTEROID_SPAWN_RATE then\n"
|
|
" spawn_asteroid(game.width() / 2, game.height() / 2, 3)\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false) -- Black background\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 35, game.height() / 2 - 30, \"ASTEROIDS\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, \"Tap to Start\", true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING or state == STATE_GAME_OVER then\n"
|
|
" -- Draw asteroids (convert to integers)\n"
|
|
" for i = 1, #game.vars.asteroids do\n"
|
|
" local ast = game.vars.asteroids[i]\n"
|
|
" renderer.circle(math.floor(ast.x + 0.5), math.floor(ast.y + 0.5), ast.size, true, false)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw bullets\n"
|
|
" for i = 1, #game.vars.bullets do\n"
|
|
" local bullet = game.vars.bullets[i]\n"
|
|
" renderer.pixel(bullet.x, bullet.y, true)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw ship\n"
|
|
" local ship_size = 6\n"
|
|
" local nose_x = game.vars.ship_x + math.cos(game.vars.ship_angle) * ship_size\n"
|
|
" local nose_y = game.vars.ship_y + math.sin(game.vars.ship_angle) * ship_size\n"
|
|
" local back_x = game.vars.ship_x - math.cos(game.vars.ship_angle) * (ship_size / 2)\n"
|
|
" local back_y = game.vars.ship_y - math.sin(game.vars.ship_angle) * (ship_size / 2)\n"
|
|
" \n"
|
|
" renderer.line(game.vars.ship_x, game.vars.ship_y, nose_x, nose_y, true, 1)\n"
|
|
" renderer.line(back_x, back_y, nose_x, nose_y, true, 1)\n"
|
|
" \n"
|
|
" -- Draw thrust indicator\n"
|
|
" if game.vars.thrusting then\n"
|
|
" local flame_x = game.vars.ship_x - math.cos(game.vars.ship_angle) * ship_size\n"
|
|
" local flame_y = game.vars.ship_y - math.sin(game.vars.ship_angle) * ship_size\n"
|
|
" renderer.line(game.vars.ship_x, game.vars.ship_y, flame_x, flame_y, true, 1)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw score\n"
|
|
" renderer.text_scaled(10, 10, \"Score: \" .. tostring(game.vars.score), true, 2)\n"
|
|
" renderer.text_scaled(10, 20, \"Level: \" .. tostring(game.vars.level), true, 2)\n"
|
|
" \n"
|
|
" if state == STATE_GAME_OVER then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2, \"GAME OVER\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 + 20, \"Tap to Menu\", true, 2)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update_ship()\n"
|
|
" if game.vars.thrusting then\n"
|
|
" game.vars.ship_vel_x = game.vars.ship_vel_x + math.cos(game.vars.ship_angle) * SHIP_SPEED\n"
|
|
" game.vars.ship_vel_y = game.vars.ship_vel_y + math.sin(game.vars.ship_angle) * SHIP_SPEED\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Friction\n"
|
|
" game.vars.ship_vel_x = game.vars.ship_vel_x * 0.98\n"
|
|
" game.vars.ship_vel_y = game.vars.ship_vel_y * 0.98\n"
|
|
" \n"
|
|
" -- Move ship\n"
|
|
" game.vars.ship_x = game.vars.ship_x + game.vars.ship_vel_x\n"
|
|
" game.vars.ship_y = game.vars.ship_y + game.vars.ship_vel_y\n"
|
|
" \n"
|
|
" -- Wrap around screen\n"
|
|
" if game.vars.ship_x < 0 then game.vars.ship_x = game.width() end\n"
|
|
" if game.vars.ship_x > game.width() then game.vars.ship_x = 0 end\n"
|
|
" if game.vars.ship_y < 0 then game.vars.ship_y = game.height() end\n"
|
|
" if game.vars.ship_y > game.height() then game.vars.ship_y = 0 end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update_bullets()\n"
|
|
" -- Update existing bullets\n"
|
|
" for i = #game.vars.bullets, 1, -1 do\n"
|
|
" local bullet = game.vars.bullets[i]\n"
|
|
" bullet.x = bullet.x + bullet.vel_x\n"
|
|
" bullet.y = bullet.y + bullet.vel_y\n"
|
|
" \n"
|
|
" -- Remove if off-screen\n"
|
|
" if bullet.x < 0 or bullet.x > game.width() or bullet.y < 0 or bullet.y > game.height() then\n"
|
|
" table.remove(game.vars.bullets, i)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Fire bullet\n"
|
|
" game.vars.bullet_cooldown = math.max(0, game.vars.bullet_cooldown - 1)\n"
|
|
" if game.vars.thrusting and game.vars.bullet_cooldown == 0 then\n"
|
|
" local bullet_x = game.vars.ship_x + math.cos(game.vars.ship_angle) * 8\n"
|
|
" local bullet_y = game.vars.ship_y + math.sin(game.vars.ship_angle) * 8\n"
|
|
" \n"
|
|
" table.insert(game.vars.bullets, {\n"
|
|
" x = bullet_x,\n"
|
|
" y = bullet_y,\n"
|
|
" vel_x = math.cos(game.vars.ship_angle) * BULLET_SPEED + game.vars.ship_vel_x,\n"
|
|
" vel_y = math.sin(game.vars.ship_angle) * BULLET_SPEED + game.vars.ship_vel_y\n"
|
|
" })\n"
|
|
" \n"
|
|
" game.vars.bullet_cooldown = 5\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update_asteroids()\n"
|
|
" for i = 1, #game.vars.asteroids do\n"
|
|
" local ast = game.vars.asteroids[i]\n"
|
|
" ast.x = ast.x + ast.vel_x\n"
|
|
" ast.y = ast.y + ast.vel_y\n"
|
|
" \n"
|
|
" -- Wrap around screen\n"
|
|
" if ast.x < -ast.size then ast.x = game.width() + ast.size end\n"
|
|
" if ast.x > game.width() + ast.size then ast.x = -ast.size end\n"
|
|
" if ast.y < -ast.size then ast.y = game.height() + ast.size end\n"
|
|
" if ast.y > game.height() + ast.size then ast.y = -ast.size end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function check_collisions()\n"
|
|
" -- Bullet-asteroid collisions\n"
|
|
" for b = #game.vars.bullets, 1, -1 do\n"
|
|
" local bullet = game.vars.bullets[b]\n"
|
|
" for a = #game.vars.asteroids, 1, -1 do\n"
|
|
" local ast = game.vars.asteroids[a]\n"
|
|
" \n"
|
|
" local dx = bullet.x - ast.x\n"
|
|
" local dy = bullet.y - ast.y\n"
|
|
" local dist = math.sqrt(dx * dx + dy * dy)\n"
|
|
" \n"
|
|
" if dist < ast.size then\n"
|
|
" -- Hit!\n"
|
|
" table.remove(game.vars.bullets, b)\n"
|
|
" table.remove(game.vars.asteroids, a)\n"
|
|
" game.vars.score = game.vars.score + (4 - ast.size) * 50\n"
|
|
" \n"
|
|
" -- Spawn smaller asteroids\n"
|
|
" if ast.size > 1 then\n"
|
|
" for _ = 1, 2 do\n"
|
|
" spawn_asteroid(ast.x, ast.y, ast.size - 1)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" break\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Ship-asteroid collisions\n"
|
|
" for i = 1, #game.vars.asteroids do\n"
|
|
" local ast = game.vars.asteroids[i]\n"
|
|
" \n"
|
|
" local dx = game.vars.ship_x - ast.x\n"
|
|
" local dy = game.vars.ship_y - ast.y\n"
|
|
" local dist = math.sqrt(dx * dx + dy * dy)\n"
|
|
" \n"
|
|
" if dist < ast.size + 6 then\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function spawn_asteroid(x, y, size)\n"
|
|
" if size < 1 then return end\n"
|
|
" \n"
|
|
" local speed = ASTEROID_SPEEDS[size]\n"
|
|
" local angle = math.random() * math.pi * 2\n"
|
|
" \n"
|
|
" table.insert(game.vars.asteroids, {\n"
|
|
" x = x,\n"
|
|
" y = y,\n"
|
|
" size = size,\n"
|
|
" vel_x = math.cos(angle) * speed,\n"
|
|
" vel_y = math.sin(angle) * speed\n"
|
|
" })\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_game()\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.level = 1\n"
|
|
" game.vars.ship_x = game.width() / 2\n"
|
|
" game.vars.ship_y = game.height() / 2\n"
|
|
" game.vars.ship_angle = 0\n"
|
|
" game.vars.ship_vel_x = 0\n"
|
|
" game.vars.ship_vel_y = 0\n"
|
|
" game.vars.bullets = {}\n"
|
|
" game.vars.asteroids = {}\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" \n"
|
|
" spawn_asteroid(game.width() / 2 - 40, game.height() / 2 - 40, 3)\n"
|
|
" spawn_asteroid(game.width() / 2 + 40, game.height() / 2 + 40, 3)\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char ball_lua_src[] = "-- NAME: Bouncing Ball\n"
|
|
"-- DESC: Physics demo with state management\n"
|
|
"\n"
|
|
"-- States\n"
|
|
"local STATE_PAUSED = 0\n"
|
|
"local STATE_RUNNING = 1\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_PAUSED\n"
|
|
" game.vars.ball_x = game.width() / 2\n"
|
|
" game.vars.ball_y = game.height() / 2\n"
|
|
" game.vars.vel_x = 3\n"
|
|
" game.vars.vel_y = 2\n"
|
|
" game.vars.radius = 10\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" \n"
|
|
" -- Enable continuous frame updates for smooth animation\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Bouncing Ball initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" -- Toggle pause on tap\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" if game.vars.state == STATE_PAUSED then\n"
|
|
" game.vars.state = STATE_RUNNING\n"
|
|
" else\n"
|
|
" game.vars.state = STATE_PAUSED\n"
|
|
" end\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Update physics if running (on any frame tick)\n"
|
|
" if event.type == INPUT.FRAME_TICK and game.vars.state == STATE_RUNNING then\n"
|
|
" -- Move ball\n"
|
|
" game.vars.ball_x = game.vars.ball_x + game.vars.vel_x\n"
|
|
" game.vars.ball_y = game.vars.ball_y + game.vars.vel_y\n"
|
|
" \n"
|
|
" -- Bounce off walls\n"
|
|
" if game.vars.ball_x - game.vars.radius < 0 or game.vars.ball_x + game.vars.radius > game.width() then\n"
|
|
" game.vars.vel_x = -game.vars.vel_x\n"
|
|
" game.vars.ball_x = math.max(game.vars.radius, math.min(game.width() - game.vars.radius, game.vars.ball_x))\n"
|
|
" end\n"
|
|
" \n"
|
|
" if game.vars.ball_y - game.vars.radius < 0 or game.vars.ball_y + game.vars.radius > game.height() then\n"
|
|
" game.vars.vel_y = -game.vars.vel_y\n"
|
|
" game.vars.ball_y = math.max(game.vars.radius, math.min(game.height() - game.vars.radius, game.vars.ball_y))\n"
|
|
" end\n"
|
|
" \n"
|
|
" game.vars.frame_count = game.vars.frame_count + 1\n"
|
|
" return true -- Always redraw when running\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false)\n"
|
|
" \n"
|
|
" -- Draw ball (convert to integers)\n"
|
|
" renderer.circle(math.floor(game.vars.ball_x + 0.5), math.floor(game.vars.ball_y + 0.5), game.vars.radius, true, true)\n"
|
|
" \n"
|
|
" -- Draw trail (previous positions)\n"
|
|
" local trail_radius = game.vars.radius - 2\n"
|
|
" if trail_radius > 2 then\n"
|
|
" renderer.circle(math.floor(game.vars.ball_x - game.vars.vel_x + 0.5), \n"
|
|
" math.floor(game.vars.ball_y - game.vars.vel_y + 0.5), \n"
|
|
" trail_radius, true, false)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw status\n"
|
|
" if game.vars.state == STATE_PAUSED then\n"
|
|
" renderer.text_scaled(10, 10, \"PAUSED - Tap to start\", true, 2)\n"
|
|
" else\n"
|
|
" renderer.text_scaled(10, 10, \"Frames: \" .. tostring(game.vars.frame_count), true, 2)\n"
|
|
" renderer.text_scaled(10, 25, \"Tap to pause\", true, 2)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw velocity vector\n"
|
|
" local arrow_x = game.vars.ball_x + game.vars.vel_x * 3\n"
|
|
" local arrow_y = game.vars.ball_y + game.vars.vel_y * 3\n"
|
|
" renderer.line(math.floor(game.vars.ball_x + 0.5), math.floor(game.vars.ball_y + 0.5), math.floor(arrow_x + 0.5), math.floor(arrow_y + 0.5), true, 2)\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char breakout_lua_src[] = "-- NAME: Breakout\n"
|
|
"-- DESC: Break bricks with bouncing ball and paddle\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_GAME_OVER = 2\n"
|
|
"local STATE_LEVEL_COMPLETE = 3\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local PADDLE_WIDTH = 40\n"
|
|
"local PADDLE_HEIGHT = 6\n"
|
|
"local BALL_RADIUS = 4\n"
|
|
"local BRICK_WIDTH = 16\n"
|
|
"local BRICK_HEIGHT = 6\n"
|
|
"local BRICK_COLS = 16\n"
|
|
"local BRICK_ROWS = 5\n"
|
|
"local BRICK_START_Y = 20\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.lives = 3\n"
|
|
" \n"
|
|
" -- Paddle\n"
|
|
" game.vars.paddle_x = (game.width() / 2) - (PADDLE_WIDTH / 2)\n"
|
|
" \n"
|
|
" -- Ball\n"
|
|
" game.vars.ball_x = game.width() / 2\n"
|
|
" game.vars.ball_y = game.height() - 30\n"
|
|
" game.vars.ball_vel_x = 2\n"
|
|
" game.vars.ball_vel_y = -3\n"
|
|
" \n"
|
|
" -- Bricks (true = exists, false = broken)\n"
|
|
" game.vars.bricks = {}\n"
|
|
" game.vars.bricks_remaining = 0\n"
|
|
" \n"
|
|
" -- Enable continuous updates\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Breakout initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" reset_game()\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Handle paddle input\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.TOUCH_MOVE then\n"
|
|
" game.vars.paddle_x = math.max(0, math.min(game.width() - PADDLE_WIDTH, event.x - PADDLE_WIDTH / 2))\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Update physics on frame tick\n"
|
|
" if event.type == INPUT.FRAME_TICK then\n"
|
|
" update_ball()\n"
|
|
" check_brick_collisions()\n"
|
|
" \n"
|
|
" -- Check win\n"
|
|
" if game.vars.bricks_remaining <= 0 then\n"
|
|
" game.vars.state = STATE_LEVEL_COMPLETE\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_LEVEL_COMPLETE then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false) -- Black background\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, game.height() / 2 - 30, \"BREAKOUT\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, \"Tap to Start\", true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Draw bricks\n"
|
|
" for row = 1, BRICK_ROWS do\n"
|
|
" for col = 1, BRICK_COLS do\n"
|
|
" local idx = (row - 1) * BRICK_COLS + col\n"
|
|
" if game.vars.bricks[idx] then\n"
|
|
" local x = (col - 1) * BRICK_WIDTH\n"
|
|
" local y = BRICK_START_Y + (row - 1) * BRICK_HEIGHT\n"
|
|
" renderer.rect(x, y, BRICK_WIDTH, BRICK_HEIGHT, true, true)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw paddle\n"
|
|
" renderer.rect(game.vars.paddle_x, game.height() - PADDLE_HEIGHT - 2, PADDLE_WIDTH, PADDLE_HEIGHT, true, true)\n"
|
|
" \n"
|
|
" -- Draw ball (convert to integers)\n"
|
|
" renderer.circle(math.floor(game.vars.ball_x + 0.5), math.floor(game.vars.ball_y + 0.5), BALL_RADIUS, true, true)\n"
|
|
" \n"
|
|
" -- Draw score and lives\n"
|
|
" renderer.text_scaled(5, 5, \"Score: \" .. tostring(game.vars.score), true, 2)\n"
|
|
" renderer.text_scaled(game.width() - 50, 5, \"Lives: \" .. tostring(game.vars.lives), true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2 - 20, \"GAME OVER\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, game.height() / 2, \"Score: \" .. tostring(game.vars.score), true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() / 2 + 20, \"Tap to Menu\", true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_LEVEL_COMPLETE then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 - 20, \"LEVEL COMPLETE!\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, game.height() / 2, \"Score: \" .. tostring(game.vars.score), true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() / 2 + 20, \"Tap to Menu\", true, 2)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update_ball()\n"
|
|
" -- Move ball\n"
|
|
" game.vars.ball_x = game.vars.ball_x + game.vars.ball_vel_x\n"
|
|
" game.vars.ball_y = game.vars.ball_y + game.vars.ball_vel_y\n"
|
|
" \n"
|
|
" -- Bounce off walls\n"
|
|
" if game.vars.ball_x - BALL_RADIUS < 0 or game.vars.ball_x + BALL_RADIUS > game.width() then\n"
|
|
" game.vars.ball_vel_x = -game.vars.ball_vel_x\n"
|
|
" game.vars.ball_x = math.max(BALL_RADIUS, math.min(game.width() - BALL_RADIUS, game.vars.ball_x))\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Bounce off top\n"
|
|
" if game.vars.ball_y - BALL_RADIUS < 0 then\n"
|
|
" game.vars.ball_vel_y = -game.vars.ball_vel_y\n"
|
|
" game.vars.ball_y = BALL_RADIUS\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Check paddle collision\n"
|
|
" if game.vars.ball_y + BALL_RADIUS > game.height() - PADDLE_HEIGHT - 2 then\n"
|
|
" if game.vars.ball_x > game.vars.paddle_x and game.vars.ball_x < game.vars.paddle_x + PADDLE_WIDTH then\n"
|
|
" if game.vars.ball_vel_y > 0 then\n"
|
|
" game.vars.ball_vel_y = -game.vars.ball_vel_y\n"
|
|
" \n"
|
|
" -- Add spin based on hit position\n"
|
|
" local hit_pos = (game.vars.ball_x - game.vars.paddle_x) / PADDLE_WIDTH\n"
|
|
" game.vars.ball_vel_x = (hit_pos - 0.5) * 4\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Fall off bottom = lose life\n"
|
|
" if game.vars.ball_y > game.height() then\n"
|
|
" game.vars.lives = game.vars.lives - 1\n"
|
|
" if game.vars.lives <= 0 then\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" else\n"
|
|
" reset_ball()\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function check_brick_collisions()\n"
|
|
" for row = 1, BRICK_ROWS do\n"
|
|
" for col = 1, BRICK_COLS do\n"
|
|
" local idx = (row - 1) * BRICK_COLS + col\n"
|
|
" if game.vars.bricks[idx] then\n"
|
|
" local brick_x = (col - 1) * BRICK_WIDTH\n"
|
|
" local brick_y = BRICK_START_Y + (row - 1) * BRICK_HEIGHT\n"
|
|
" \n"
|
|
" -- Simple AABB collision with ball\n"
|
|
" if game.vars.ball_x + BALL_RADIUS > brick_x and\n"
|
|
" game.vars.ball_x - BALL_RADIUS < brick_x + BRICK_WIDTH and\n"
|
|
" game.vars.ball_y + BALL_RADIUS > brick_y and\n"
|
|
" game.vars.ball_y - BALL_RADIUS < brick_y + BRICK_HEIGHT then\n"
|
|
" \n"
|
|
" -- Destroy brick\n"
|
|
" game.vars.bricks[idx] = false\n"
|
|
" game.vars.bricks_remaining = game.vars.bricks_remaining - 1\n"
|
|
" game.vars.score = game.vars.score + 10\n"
|
|
" \n"
|
|
" -- Bounce ball (simple: vertical bounce)\n"
|
|
" game.vars.ball_vel_y = -game.vars.ball_vel_y\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_ball()\n"
|
|
" game.vars.ball_x = game.vars.paddle_x + PADDLE_WIDTH / 2\n"
|
|
" game.vars.ball_y = game.height() - 30\n"
|
|
" game.vars.ball_vel_x = 2\n"
|
|
" game.vars.ball_vel_y = -3\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_game()\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.lives = 3\n"
|
|
" game.vars.paddle_x = (game.width() / 2) - (PADDLE_WIDTH / 2)\n"
|
|
" \n"
|
|
" -- Create brick grid\n"
|
|
" game.vars.bricks = {}\n"
|
|
" game.vars.bricks_remaining = BRICK_ROWS * BRICK_COLS\n"
|
|
" for i = 1, BRICK_ROWS * BRICK_COLS do\n"
|
|
" game.vars.bricks[i] = true\n"
|
|
" end\n"
|
|
" \n"
|
|
" reset_ball()\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char counter_lua_src[] = "-- NAME: Touch Counter\n"
|
|
"-- DESC: Simple tap counter demo\n"
|
|
"\n"
|
|
"-- Initialize game state\n"
|
|
"function init()\n"
|
|
" game.vars.count = 0\n"
|
|
" game.vars.last_x = 0\n"
|
|
" game.vars.last_y = 0\n"
|
|
" print(\"Counter initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Update game logic based on input\n"
|
|
"function update(event)\n"
|
|
" -- Check if touch/button pressed\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.count = game.vars.count + 1\n"
|
|
" game.vars.last_x = event.x\n"
|
|
" game.vars.last_y = event.y\n"
|
|
" print(\"Count: \" .. game.vars.count)\n"
|
|
" return true -- Request redraw\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false -- No redraw needed\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Draw the game\n"
|
|
"function draw()\n"
|
|
" -- Clear screen\n"
|
|
" renderer.clear(true)\n"
|
|
" \n"
|
|
" -- Draw title\n"
|
|
" renderer.text_scaled(20, 20, \"Touch Counter\", true, 2)\n"
|
|
" \n"
|
|
" -- Draw count (centered)\n"
|
|
" local count_text = \"Count: \" .. tostring(game.vars.count)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2 - 10, count_text, true, 2)\n"
|
|
" \n"
|
|
" -- Draw last touch position\n"
|
|
" if game.vars.count > 0 then\n"
|
|
" local pos_text = \"Last: (\" .. tostring(game.vars.last_x) .. \", \" .. tostring(game.vars.last_y) .. \")\"\n"
|
|
" renderer.text_scaled(20, game.height() - 30, pos_text, true, 2)\n"
|
|
" \n"
|
|
" -- Draw marker at last touch (convert to integers)\n"
|
|
" renderer.circle(math.floor(game.vars.last_x + 0.5), math.floor(game.vars.last_y + 0.5), 5, true, false)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw instructions\n"
|
|
" renderer.text_scaled(20, 50, \"Tap screen to increment\", true, 2)\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char flappy_bird_lua_src[] = "-- NAME: Flappy Bird\n"
|
|
"-- DESC: Tap to flap, avoid pipes\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_GAME_OVER = 2\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local BIRD_SIZE = 8\n"
|
|
"local BIRD_GRAVITY = 0.3\n"
|
|
"local BIRD_FLAP_POWER = -7\n"
|
|
"local PIPE_WIDTH = 20\n"
|
|
"local PIPE_GAP = 50\n"
|
|
"local PIPE_SPEED = 3\n"
|
|
"local SPAWN_RATE = 80 -- Frames between pipe spawns\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" \n"
|
|
" -- Bird\n"
|
|
" game.vars.bird_y = game.height() / 2\n"
|
|
" game.vars.bird_vel = 0\n"
|
|
" \n"
|
|
" -- Pipes (array of {x, gap_y})\n"
|
|
" game.vars.pipes = {}\n"
|
|
" game.vars.last_pipe_frame = 0\n"
|
|
" game.vars.score_submit_pending = false\n"
|
|
" game.vars.score_submitted = 0\n"
|
|
" \n"
|
|
" -- Enable continuous updates\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Flappy Bird initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function set_game_over()\n"
|
|
" if game.vars.state ~= STATE_GAME_OVER then\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" game.vars.score_submitted = game.vars.score\n"
|
|
" game.vars.score_submit_pending = true\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" reset_game()\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Handle flap input\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.bird_vel = BIRD_FLAP_POWER\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Update physics on frame tick\n"
|
|
" if event.type == INPUT.FRAME_TICK then\n"
|
|
" update_bird()\n"
|
|
" update_pipes()\n"
|
|
" check_collisions()\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false) -- Black background\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2 - 30, \"FLAPPY BIRD\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, \"Tap to Start\", true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Draw bird (convert to integer)\n"
|
|
" renderer.circle(20, math.floor(game.vars.bird_y + 0.5), BIRD_SIZE, true, true)\n"
|
|
" \n"
|
|
" -- Draw pipes\n"
|
|
" for i = 1, #game.vars.pipes do\n"
|
|
" local pipe = game.vars.pipes[i]\n"
|
|
" \n"
|
|
" -- Top pipe\n"
|
|
" renderer.rect(pipe.x, 0, PIPE_WIDTH, pipe.gap_y, true, true)\n"
|
|
" \n"
|
|
" -- Bottom pipe\n"
|
|
" local bottom_start = pipe.gap_y + PIPE_GAP\n"
|
|
" renderer.rect(pipe.x, bottom_start, PIPE_WIDTH, game.height() - bottom_start, true, true)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw score\n"
|
|
" renderer.text_scaled(10, 10, \"Score: \" .. tostring(game.vars.score), true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2 - 30, \"GAME OVER\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, game.height() / 2, \"Score: \" .. tostring(game.vars.score), true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() / 2 + 20, \"Tap to Restart\", true, 2)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update_bird()\n"
|
|
" -- Apply gravity\n"
|
|
" game.vars.bird_vel = game.vars.bird_vel + BIRD_GRAVITY\n"
|
|
" game.vars.bird_y = game.vars.bird_y + game.vars.bird_vel\n"
|
|
" \n"
|
|
" -- Clamp to screen (game over if hit top/bottom)\n"
|
|
" if game.vars.bird_y - BIRD_SIZE < 0 or game.vars.bird_y + BIRD_SIZE > game.height() then\n"
|
|
" set_game_over()\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update_pipes()\n"
|
|
" game.vars.frame_count = game.vars.frame_count + 1\n"
|
|
" \n"
|
|
" -- Spawn new pipe\n"
|
|
" if game.vars.frame_count - game.vars.last_pipe_frame >= SPAWN_RATE then\n"
|
|
" local gap_y = math.random(30, game.height() - PIPE_GAP - 30)\n"
|
|
" table.insert(game.vars.pipes, {x = game.width(), gap_y = gap_y})\n"
|
|
" game.vars.last_pipe_frame = game.vars.frame_count\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Move and remove off-screen pipes\n"
|
|
" for i = #game.vars.pipes, 1, -1 do\n"
|
|
" local pipe = game.vars.pipes[i]\n"
|
|
" pipe.x = pipe.x - PIPE_SPEED\n"
|
|
" \n"
|
|
" -- Score when pipe passes bird\n"
|
|
" if pipe.x == 20 then\n"
|
|
" game.vars.score = game.vars.score + 1\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Remove if off-screen\n"
|
|
" if pipe.x < -PIPE_WIDTH then\n"
|
|
" table.remove(game.vars.pipes, i)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function check_collisions()\n"
|
|
" -- Check collision with pipes\n"
|
|
" for i = 1, #game.vars.pipes do\n"
|
|
" local pipe = game.vars.pipes[i]\n"
|
|
" \n"
|
|
" -- Bird hitbox: circle at (20, bird_y) with radius BIRD_SIZE\n"
|
|
" -- Check if within pipe's X range\n"
|
|
" if 20 + BIRD_SIZE > pipe.x and 20 - BIRD_SIZE < pipe.x + PIPE_WIDTH then\n"
|
|
" -- Check Y collision\n"
|
|
" if game.vars.bird_y - BIRD_SIZE < pipe.gap_y or \n"
|
|
" game.vars.bird_y + BIRD_SIZE > pipe.gap_y + PIPE_GAP then\n"
|
|
" set_game_over()\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_game()\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.bird_y = game.height() / 2\n"
|
|
" game.vars.bird_vel = 0\n"
|
|
" game.vars.pipes = {}\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" game.vars.last_pipe_frame = 0\n"
|
|
" game.vars.score_submit_pending = false\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char lunar_lander_lua_src[] = "-- NAME: Lunar Lander\n"
|
|
"-- DESC: Land safely with limited fuel\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_LANDED = 2\n"
|
|
"local STATE_CRASHED = 3\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local GRAVITY = 0.15\n"
|
|
"local THRUST_POWER = 0.3\n"
|
|
"local MAX_FUEL = 100\n"
|
|
"local SAFE_LANDING_SPEED = 1.5\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" game.vars.score = 0\n"
|
|
" \n"
|
|
" -- Lander\n"
|
|
" game.vars.lander_x = game.width() / 2\n"
|
|
" game.vars.lander_y = 10\n"
|
|
" game.vars.lander_vel_y = 0\n"
|
|
" game.vars.fuel = MAX_FUEL\n"
|
|
" game.vars.thrusting = false\n"
|
|
" \n"
|
|
" -- Terrain\n"
|
|
" game.vars.landing_zone_x = game.width() / 2 - 20\n"
|
|
" game.vars.landing_zone_w = 40\n"
|
|
" game.vars.terrain_y = game.height() - 15\n"
|
|
" \n"
|
|
" -- Enable continuous updates\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Lunar Lander initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" reset_game()\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Handle thrust input\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.TOUCH_MOVE then\n"
|
|
" if game.vars.fuel > 0 then\n"
|
|
" game.vars.thrusting = true\n"
|
|
" game.vars.fuel = game.vars.fuel - 1\n"
|
|
" else\n"
|
|
" game.vars.thrusting = false\n"
|
|
" end\n"
|
|
" else\n"
|
|
" game.vars.thrusting = false\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Update physics on frame tick\n"
|
|
" if event.type == INPUT.FRAME_TICK then\n"
|
|
" update_lander()\n"
|
|
" check_landing()\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_LANDED or state == STATE_CRASHED then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false)\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 - 30, \"LUNAR LANDER\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 70, game.height() / 2 - 5, \"Land in the zone safely\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 + 20, \"Tap to Start\", true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING or state == STATE_LANDED or state == STATE_CRASHED then\n"
|
|
" -- Draw terrain\n"
|
|
" renderer.rect(0, game.vars.terrain_y, game.width(), game.height() - game.vars.terrain_y, true, true)\n"
|
|
" \n"
|
|
" -- Draw landing zone (outline)\n"
|
|
" renderer.rect(game.vars.landing_zone_x, game.vars.terrain_y - 2, game.vars.landing_zone_w, 2, true, true)\n"
|
|
" \n"
|
|
" -- Draw lander (triangle)\n"
|
|
" local lander_w = 8\n"
|
|
" local lander_h = 6\n"
|
|
" renderer.line(game.vars.lander_x - lander_w / 2, game.vars.lander_y + lander_h, \n"
|
|
" game.vars.lander_x, game.vars.lander_y, true, 1)\n"
|
|
" renderer.line(game.vars.lander_x, game.vars.lander_y, \n"
|
|
" game.vars.lander_x + lander_w / 2, game.vars.lander_y + lander_h, true, 1)\n"
|
|
" renderer.line(game.vars.lander_x - lander_w / 2, game.vars.lander_y + lander_h,\n"
|
|
" game.vars.lander_x + lander_w / 2, game.vars.lander_y + lander_h, true, 1)\n"
|
|
" \n"
|
|
" -- Draw thrust flame\n"
|
|
" if game.vars.thrusting then\n"
|
|
" renderer.line(game.vars.lander_x - 2, game.vars.lander_y + lander_h,\n"
|
|
" game.vars.lander_x - 1, game.vars.lander_y + lander_h + 3, true, 1)\n"
|
|
" renderer.line(game.vars.lander_x + 2, game.vars.lander_y + lander_h,\n"
|
|
" game.vars.lander_x + 1, game.vars.lander_y + lander_h + 3, true, 1)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw stats\n"
|
|
" renderer.text_scaled(5, 5, \"Fuel: \" .. tostring(math.floor(game.vars.fuel)), true, 2)\n"
|
|
" renderer.text_scaled(5, 15, \"Speed: \" .. tostring(math.floor(game.vars.lander_vel_y * 10)), true, 2)\n"
|
|
" \n"
|
|
" if state == STATE_LANDED then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2 - 20, \"LANDED!\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, game.height() / 2, \"Score: \" .. tostring(game.vars.score), true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() / 2 + 20, \"Tap to Menu\", true, 2)\n"
|
|
" end\n"
|
|
" \n"
|
|
" if state == STATE_CRASHED then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2 - 20, \"CRASHED!\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() / 2 + 20, \"Tap to Menu\", true, 2)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update_lander()\n"
|
|
" -- Apply gravity\n"
|
|
" game.vars.lander_vel_y = game.vars.lander_vel_y + GRAVITY\n"
|
|
" \n"
|
|
" -- Apply thrust\n"
|
|
" if game.vars.thrusting then\n"
|
|
" game.vars.lander_vel_y = game.vars.lander_vel_y - THRUST_POWER\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Update position\n"
|
|
" game.vars.lander_y = game.vars.lander_y + game.vars.lander_vel_y\n"
|
|
"end\n"
|
|
"\n"
|
|
"function check_landing()\n"
|
|
" -- Check if lander reached terrain\n"
|
|
" if game.vars.lander_y + 6 >= game.vars.terrain_y then\n"
|
|
" local lander_left = game.vars.lander_x - 4\n"
|
|
" local lander_right = game.vars.lander_x + 4\n"
|
|
" local zone_left = game.vars.landing_zone_x\n"
|
|
" local zone_right = game.vars.landing_zone_x + game.vars.landing_zone_w\n"
|
|
" \n"
|
|
" -- Check if in landing zone\n"
|
|
" if lander_left >= zone_left and lander_right <= zone_right then\n"
|
|
" -- Check landing speed\n"
|
|
" if game.vars.lander_vel_y <= SAFE_LANDING_SPEED then\n"
|
|
" game.vars.state = STATE_LANDED\n"
|
|
" game.vars.score = 100 + math.floor(game.vars.fuel)\n"
|
|
" else\n"
|
|
" game.vars.state = STATE_CRASHED\n"
|
|
" end\n"
|
|
" else\n"
|
|
" game.vars.state = STATE_CRASHED\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_game()\n"
|
|
" game.vars.lander_x = game.width() / 2\n"
|
|
" game.vars.lander_y = 10\n"
|
|
" game.vars.lander_vel_y = 0\n"
|
|
" game.vars.fuel = MAX_FUEL\n"
|
|
" game.vars.thrusting = false\n"
|
|
" game.vars.score = 0\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char memory_match_lua_src[] = "-- NAME: Memory Match\n"
|
|
"-- DESC: Find matching pairs\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_GAME_OVER = 2\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local GRID_COLS = 4\n"
|
|
"local GRID_ROWS = 4\n"
|
|
"local FLIP_DURATION = 15 -- Frames to show flip animation\n"
|
|
"\n"
|
|
"-- Calculate card size based on screen size\n"
|
|
"local function get_card_size()\n"
|
|
" -- Use available space with padding, based on smallest dimension\n"
|
|
" local min_dim = math.min(game.width(), game.height())\n"
|
|
" local padding = math.floor(min_dim / 8)\n"
|
|
" local available_w = game.width() - (padding * 2)\n"
|
|
" local available_h = game.height() - 80 -- Leave room for text\n"
|
|
" \n"
|
|
" -- Calculate based on grid\n"
|
|
" local card_width = math.floor(available_w / GRID_COLS)\n"
|
|
" local card_height = math.floor(available_h / GRID_ROWS)\n"
|
|
" \n"
|
|
" -- Use the smaller dimension for square cards\n"
|
|
" return math.min(card_width, card_height)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function get_grid_start_x()\n"
|
|
" local card_size = get_card_size()\n"
|
|
" local grid_width = card_size * GRID_COLS\n"
|
|
" return math.floor((game.width() - grid_width) / 2)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function get_grid_start_y()\n"
|
|
" return 60\n"
|
|
"end\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.moves = 0\n"
|
|
" \n"
|
|
" -- Card grid\n"
|
|
" game.vars.cards = {} -- {id, face_up, matched}\n"
|
|
" game.vars.selected = {} -- Indices of selected cards\n"
|
|
" game.vars.flip_frame = 0\n"
|
|
" game.vars.waiting = false -- Waiting to flip back incorrect pair\n"
|
|
" \n"
|
|
" -- Enable continuous updates\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Memory Match initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" reset_game()\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Handle card selection\n"
|
|
" if event.type == INPUT.TOUCH_DOWN and not game.vars.waiting then\n"
|
|
" local card_idx = get_card_at(event.x, event.y)\n"
|
|
" if card_idx and not game.vars.cards[card_idx].matched and not game.vars.cards[card_idx].face_up then\n"
|
|
" -- Select card\n"
|
|
" game.vars.cards[card_idx].face_up = true\n"
|
|
" table.insert(game.vars.selected, card_idx)\n"
|
|
" \n"
|
|
" if #game.vars.selected == 2 then\n"
|
|
" game.vars.moves = game.vars.moves + 1\n"
|
|
" \n"
|
|
" -- Check for match\n"
|
|
" if game.vars.cards[game.vars.selected[1]].id == game.vars.cards[game.vars.selected[2]].id then\n"
|
|
" -- Match!\n"
|
|
" game.vars.cards[game.vars.selected[1]].matched = true\n"
|
|
" game.vars.cards[game.vars.selected[2]].matched = true\n"
|
|
" game.vars.score = game.vars.score + 1\n"
|
|
" game.vars.selected = {}\n"
|
|
" \n"
|
|
" -- Check win\n"
|
|
" if game.vars.score == (GRID_COLS * GRID_ROWS) / 2 then\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" end\n"
|
|
" else\n"
|
|
" -- No match, wait then flip back\n"
|
|
" game.vars.waiting = true\n"
|
|
" game.vars.flip_frame = 0\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Handle flip-back animation\n"
|
|
" if game.vars.waiting and event.type == INPUT.FRAME_TICK then\n"
|
|
" game.vars.flip_frame = game.vars.flip_frame + 1\n"
|
|
" \n"
|
|
" if game.vars.flip_frame >= FLIP_DURATION then\n"
|
|
" -- Flip cards back\n"
|
|
" game.vars.cards[game.vars.selected[1]].face_up = false\n"
|
|
" game.vars.cards[game.vars.selected[2]].face_up = false\n"
|
|
" game.vars.selected = {}\n"
|
|
" game.vars.waiting = false\n"
|
|
" game.vars.flip_frame = 0\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false) -- Black background\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" local card_size = get_card_size()\n"
|
|
" local start_x = get_grid_start_x()\n"
|
|
" local start_y = get_grid_start_y()\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2 - 40, \"MEMORY MATCH\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 - 10, \"Find all pairs\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 + 20, \"Tap to Start\", true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING or state == STATE_GAME_OVER then\n"
|
|
" -- Draw grid\n"
|
|
" for row = 0, GRID_ROWS - 1 do\n"
|
|
" for col = 0, GRID_COLS - 1 do\n"
|
|
" local idx = row * GRID_COLS + col + 1\n"
|
|
" local card = game.vars.cards[idx]\n"
|
|
" local x = start_x + col * card_size\n"
|
|
" local y = start_y + row * card_size\n"
|
|
" \n"
|
|
" if card.face_up or card.matched then\n"
|
|
" -- Show card value\n"
|
|
" renderer.rect(x, y, card_size, card_size, true, true)\n"
|
|
" local text = tostring(card.id)\n"
|
|
" renderer.text_scaled(x + math.floor(card_size / 2 - 2), y + math.floor(card_size / 2 - 2), text, false, 2)\n"
|
|
" else\n"
|
|
" -- Face down (outline)\n"
|
|
" renderer.rect(x, y, card_size, card_size, true, false)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw stats\n"
|
|
" renderer.text_scaled(10, 10, \"Pairs: \" .. tostring(game.vars.score) .. \"/\" .. tostring((GRID_COLS * GRID_ROWS) / 2), true, 2)\n"
|
|
" renderer.text_scaled(10, 25, \"Moves: \" .. tostring(game.vars.moves), true, 2)\n"
|
|
" \n"
|
|
" if state == STATE_GAME_OVER then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, 5, \"YOU WIN!\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() - 20, \"Tap to Menu\", true, 2)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function get_card_at(x, y)\n"
|
|
" local card_size = get_card_size()\n"
|
|
" local start_x = get_grid_start_x()\n"
|
|
" local start_y = get_grid_start_y()\n"
|
|
" \n"
|
|
" for row = 0, GRID_ROWS - 1 do\n"
|
|
" for col = 0, GRID_COLS - 1 do\n"
|
|
" local card_x = start_x + col * card_size\n"
|
|
" local card_y = start_y + row * card_size\n"
|
|
" \n"
|
|
" if x >= card_x and x < card_x + card_size and\n"
|
|
" y >= card_y and y < card_y + card_size then\n"
|
|
" return row * GRID_COLS + col + 1\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return nil\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_game()\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.moves = 0\n"
|
|
" game.vars.selected = {}\n"
|
|
" game.vars.flip_frame = 0\n"
|
|
" game.vars.waiting = false\n"
|
|
" \n"
|
|
" -- Create shuffled card pairs\n"
|
|
" local card_ids = {}\n"
|
|
" local num_pairs = (GRID_COLS * GRID_ROWS) / 2\n"
|
|
" for i = 1, num_pairs do\n"
|
|
" table.insert(card_ids, i)\n"
|
|
" table.insert(card_ids, i)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Shuffle\n"
|
|
" for i = #card_ids, 2, -1 do\n"
|
|
" local j = math.random(1, i)\n"
|
|
" card_ids[i], card_ids[j] = card_ids[j], card_ids[i]\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Create card objects\n"
|
|
" game.vars.cards = {}\n"
|
|
" for i = 1, #card_ids do\n"
|
|
" game.vars.cards[i] = {\n"
|
|
" id = card_ids[i],\n"
|
|
" face_up = false,\n"
|
|
" matched = false\n"
|
|
" }\n"
|
|
" end\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char pacman_lua_src[] = "-- NAME: Pacman\n"
|
|
"-- DESC: Simplified pacman with digital tile movement\n"
|
|
"\n"
|
|
"local TILE = 14\n"
|
|
"\n"
|
|
"local LEVEL = {\n"
|
|
" \"####################\",\n"
|
|
" \"#o...#........#....#\",\n"
|
|
" \"#.##.#.######.#.##.#\",\n"
|
|
" \"#.#..............#.#\",\n"
|
|
" \"#.#.##.## ##.##.#.#\",\n"
|
|
" \"#......# #......#\",\n"
|
|
" \"#.#.##.######.##.#.#\",\n"
|
|
" \"#.#..............#.#\",\n"
|
|
" \"#.##.#.######.#.##.#\",\n"
|
|
" \"#o...#........#....#\",\n"
|
|
" \"####################\",\n"
|
|
"}\n"
|
|
"\n"
|
|
"local WALL = 1\n"
|
|
"local DOT = 2\n"
|
|
"local POWER = 3\n"
|
|
"local EMPTY = 0\n"
|
|
"\n"
|
|
"local DIRS = {\n"
|
|
" U = {0, -1},\n"
|
|
" D = {0, 1},\n"
|
|
" L = {-1, 0},\n"
|
|
" R = {1, 0},\n"
|
|
"}\n"
|
|
"\n"
|
|
"local OPP = {U = \"D\", D = \"U\", L = \"R\", R = \"L\"}\n"
|
|
"local BTN = 22\n"
|
|
"local POWER_DURATION_TICKS = 240\n"
|
|
"\n"
|
|
"local function level_dims()\n"
|
|
" return #LEVEL[1], #LEVEL\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function level_origin()\n"
|
|
" local w, h = level_dims()\n"
|
|
" local px_w = w * TILE\n"
|
|
" local px_h = h * TILE\n"
|
|
" local ox = math.floor((game.width() - px_w) / 2)\n"
|
|
" local oy = 64\n"
|
|
" return ox, oy, px_w, px_h\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function parse_level()\n"
|
|
" local w, h = level_dims()\n"
|
|
" game.vars.map_w = w\n"
|
|
" game.vars.map_h = h\n"
|
|
" game.vars.map = {}\n"
|
|
" game.vars.pellet_count = 0\n"
|
|
"\n"
|
|
" for y = 1, h do\n"
|
|
" game.vars.map[y] = {}\n"
|
|
" local row = LEVEL[y]\n"
|
|
" for x = 1, w do\n"
|
|
" local ch = string.sub(row, x, x)\n"
|
|
" local v = EMPTY\n"
|
|
" if ch == \"#\" then\n"
|
|
" v = WALL\n"
|
|
" elseif ch == \".\" then\n"
|
|
" v = DOT\n"
|
|
" game.vars.pellet_count = game.vars.pellet_count + 1\n"
|
|
" elseif ch == \"o\" then\n"
|
|
" v = POWER\n"
|
|
" game.vars.pellet_count = game.vars.pellet_count + 1\n"
|
|
" end\n"
|
|
" game.vars.map[y][x] = v\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function in_bounds(tx, ty)\n"
|
|
" return tx >= 1 and tx <= game.vars.map_w and ty >= 1 and ty <= game.vars.map_h\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function is_wall(tx, ty)\n"
|
|
" if not in_bounds(tx, ty) then return true end\n"
|
|
" return game.vars.map[ty][tx] == WALL\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function tile_to_px(tx, ty)\n"
|
|
" local ox, oy = level_origin()\n"
|
|
" return ox + (tx - 1) * TILE, oy + (ty - 1) * TILE\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function tile_center(tx, ty)\n"
|
|
" local px, py = tile_to_px(tx, ty)\n"
|
|
" return px + math.floor(TILE / 2), py + math.floor(TILE / 2)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function nearest_open_tile(tx, ty)\n"
|
|
" if in_bounds(tx, ty) and not is_wall(tx, ty) then\n"
|
|
" return tx, ty\n"
|
|
" end\n"
|
|
"\n"
|
|
" local best_tx, best_ty = 1, 1\n"
|
|
" local best_d = 999999\n"
|
|
" for y = 1, game.vars.map_h do\n"
|
|
" for x = 1, game.vars.map_w do\n"
|
|
" if not is_wall(x, y) then\n"
|
|
" local d = math.abs(x - tx) + math.abs(y - ty)\n"
|
|
" if d < best_d then\n"
|
|
" best_d = d\n"
|
|
" best_tx = x\n"
|
|
" best_ty = y\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return best_tx, best_ty\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function try_step_tile(tx, ty, dir)\n"
|
|
" local dv = DIRS[dir]\n"
|
|
" local nx = tx + dv[1]\n"
|
|
" local ny = ty + dv[2]\n"
|
|
" if not is_wall(nx, ny) then\n"
|
|
" return nx, ny, true\n"
|
|
" end\n"
|
|
" return tx, ty, false\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function consume_pellet_at(tx, ty)\n"
|
|
" if not in_bounds(tx, ty) then return end\n"
|
|
" local v = game.vars.map[ty][tx]\n"
|
|
" if v == DOT then\n"
|
|
" game.vars.map[ty][tx] = EMPTY\n"
|
|
" game.vars.pellet_count = game.vars.pellet_count - 1\n"
|
|
" game.vars.score = game.vars.score + 1\n"
|
|
" elseif v == POWER then\n"
|
|
" game.vars.map[ty][tx] = EMPTY\n"
|
|
" game.vars.pellet_count = game.vars.pellet_count - 1\n"
|
|
" game.vars.score = game.vars.score + 5\n"
|
|
" game.vars.power_ticks = POWER_DURATION_TICKS\n"
|
|
" end\n"
|
|
"\n"
|
|
" if game.vars.pellet_count <= 0 then\n"
|
|
" game.vars.state = \"won\"\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function respawn_ghost(g)\n"
|
|
" local sx, sy = nearest_open_tile(math.floor(game.vars.map_w / 2), math.floor(game.vars.map_h / 2))\n"
|
|
" g.x = sx\n"
|
|
" g.y = sy\n"
|
|
" g.dir = (math.random(1, 2) == 1) and \"L\" or \"R\"\n"
|
|
" g.release = game.vars.tick + 30\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function resolve_ghost_collision(g)\n"
|
|
" if game.vars.tick <= game.vars.grace then return end\n"
|
|
" if g.x ~= game.vars.px or g.y ~= game.vars.py then return end\n"
|
|
"\n"
|
|
" if game.vars.power_ticks > 0 then\n"
|
|
" game.vars.score = game.vars.score + 20\n"
|
|
" respawn_ghost(g)\n"
|
|
" else\n"
|
|
" game.vars.state = \"lost\"\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_wall_tile(tx, ty)\n"
|
|
" local x, y = tile_to_px(tx, ty)\n"
|
|
" local ix = x + 1\n"
|
|
" local iy = y + 1\n"
|
|
" local ex = x + TILE - 2\n"
|
|
" local ey = y + TILE - 2\n"
|
|
"\n"
|
|
" if not is_wall(tx, ty - 1) then renderer.line(ix, iy, ex, iy, true, 1) end\n"
|
|
" if not is_wall(tx, ty + 1) then renderer.line(ix, ey, ex, ey, true, 1) end\n"
|
|
" if not is_wall(tx - 1, ty) then renderer.line(ix, iy, ix, ey, true, 1) end\n"
|
|
" if not is_wall(tx + 1, ty) then renderer.line(ex, iy, ex, ey, true, 1) end\n"
|
|
"\n"
|
|
" if (not is_wall(tx, ty - 1)) and (not is_wall(tx - 1, ty)) then renderer.pixel(ix, iy, true) end\n"
|
|
" if (not is_wall(tx, ty - 1)) and (not is_wall(tx + 1, ty)) then renderer.pixel(ex, iy, true) end\n"
|
|
" if (not is_wall(tx, ty + 1)) and (not is_wall(tx - 1, ty)) then renderer.pixel(ix, ey, true) end\n"
|
|
" if (not is_wall(tx, ty + 1)) and (not is_wall(tx + 1, ty)) then renderer.pixel(ex, ey, true) end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_level()\n"
|
|
" for y = 1, game.vars.map_h do\n"
|
|
" for x = 1, game.vars.map_w do\n"
|
|
" local v = game.vars.map[y][x]\n"
|
|
" if v == WALL then\n"
|
|
" draw_wall_tile(x, y)\n"
|
|
" elseif v == DOT then\n"
|
|
" local px, py = tile_center(x, y)\n"
|
|
" renderer.circle(px, py, 1, true, true)\n"
|
|
" elseif v == POWER then\n"
|
|
" local px, py = tile_center(x, y)\n"
|
|
" renderer.circle(px, py, 3, true, true)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_ghost(cx, cy, vulnerable, blink)\n"
|
|
" if vulnerable then\n"
|
|
" renderer.rect(cx - 5, cy - 2, 10, 7, true, false)\n"
|
|
" renderer.circle(cx - 2, cy - 2, 3, true, false)\n"
|
|
" renderer.circle(cx + 2, cy - 2, 3, true, false)\n"
|
|
" renderer.rect(cx - 4, cy + 4, 2, 2, false, true)\n"
|
|
" renderer.rect(cx - 1, cy + 4, 2, 2, false, true)\n"
|
|
" renderer.rect(cx + 2, cy + 4, 2, 2, false, true)\n"
|
|
" if blink then\n"
|
|
" renderer.circle(cx, cy, 7, true, false)\n"
|
|
" end\n"
|
|
" else\n"
|
|
" renderer.rect(cx - 5, cy - 2, 10, 7, true, true)\n"
|
|
" renderer.circle(cx - 2, cy - 2, 3, true, true)\n"
|
|
" renderer.circle(cx + 2, cy - 2, 3, true, true)\n"
|
|
" renderer.rect(cx - 4, cy + 4, 2, 2, false, true)\n"
|
|
" renderer.rect(cx - 1, cy + 4, 2, 2, false, true)\n"
|
|
" renderer.rect(cx + 2, cy + 4, 2, 2, false, true)\n"
|
|
" end\n"
|
|
" renderer.rect(cx - 3, cy - 1, 2, 2, false, true)\n"
|
|
" renderer.rect(cx + 1, cy - 1, 2, 2, false, true)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_pacman(cx, cy, mouth_open, dir)\n"
|
|
" renderer.circle(cx, cy, 5, true, true)\n"
|
|
" if not mouth_open then return end\n"
|
|
"\n"
|
|
" if dir == \"R\" then\n"
|
|
" renderer.triangle(cx, cy, cx + 6, cy - 3, cx + 6, cy + 3, false, true)\n"
|
|
" elseif dir == \"L\" then\n"
|
|
" renderer.triangle(cx, cy, cx - 6, cy - 3, cx - 6, cy + 3, false, true)\n"
|
|
" elseif dir == \"U\" then\n"
|
|
" renderer.triangle(cx, cy, cx - 3, cy - 6, cx + 3, cy - 6, false, true)\n"
|
|
" else\n"
|
|
" renderer.triangle(cx, cy, cx - 3, cy + 6, cx + 3, cy + 6, false, true)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function control_positions()\n"
|
|
" local cx = game.width() - 48\n"
|
|
" local cy = game.height() - 50\n"
|
|
" return {\n"
|
|
" U = {x = cx, y = cy - BTN, w = BTN, h = BTN},\n"
|
|
" D = {x = cx, y = cy + BTN, w = BTN, h = BTN},\n"
|
|
" L = {x = cx - BTN, y = cy, w = BTN, h = BTN},\n"
|
|
" R = {x = cx + BTN, y = cy, w = BTN, h = BTN},\n"
|
|
" }\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function point_in(px, py, r)\n"
|
|
" return px >= r.x and px < (r.x + r.w) and py >= r.y and py < (r.y + r.h)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function touch_to_dir(tx, ty)\n"
|
|
" local ctl = control_positions()\n"
|
|
" if point_in(tx, ty, ctl.U) then return \"U\" end\n"
|
|
" if point_in(tx, ty, ctl.D) then return \"D\" end\n"
|
|
" if point_in(tx, ty, ctl.L) then return \"L\" end\n"
|
|
" if point_in(tx, ty, ctl.R) then return \"R\" end\n"
|
|
" return nil\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function handle_touch(tx, ty)\n"
|
|
" local pcx, pcy = tile_center(game.vars.px, game.vars.py)\n"
|
|
" local dx = tx - pcx\n"
|
|
" local dy = ty - pcy\n"
|
|
" if math.abs(dx) > math.abs(dy) then\n"
|
|
" game.vars.want_dir = (dx >= 0) and \"R\" or \"L\"\n"
|
|
" else\n"
|
|
" game.vars.want_dir = (dy >= 0) and \"D\" or \"U\"\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function available_dirs_tile(tx, ty, current)\n"
|
|
" local out = {}\n"
|
|
" for dir, _ in pairs(DIRS) do\n"
|
|
" if dir ~= OPP[current] then\n"
|
|
" local _, _, ok = try_step_tile(tx, ty, dir)\n"
|
|
" if ok then out[#out + 1] = dir end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" if #out == 0 then\n"
|
|
" out[1] = OPP[current]\n"
|
|
" end\n"
|
|
" return out\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function move_player_step()\n"
|
|
" local _, _, ok_turn = try_step_tile(game.vars.px, game.vars.py, game.vars.want_dir)\n"
|
|
" if ok_turn then\n"
|
|
" game.vars.dir = game.vars.want_dir\n"
|
|
" end\n"
|
|
"\n"
|
|
" local nx, ny, ok = try_step_tile(game.vars.px, game.vars.py, game.vars.dir)\n"
|
|
" if ok then\n"
|
|
" game.vars.px = nx\n"
|
|
" game.vars.py = ny\n"
|
|
" consume_pellet_at(nx, ny)\n"
|
|
" end\n"
|
|
"\n"
|
|
" for i = 1, #game.vars.ghosts do\n"
|
|
" resolve_ghost_collision(game.vars.ghosts[i])\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function move_ghosts_step()\n"
|
|
" for i = 1, #game.vars.ghosts do\n"
|
|
" local g = game.vars.ghosts[i]\n"
|
|
" if game.vars.tick >= g.release then\n"
|
|
" local dirs = available_dirs_tile(g.x, g.y, g.dir)\n"
|
|
" local pick = dirs[1]\n"
|
|
"\n"
|
|
" local vulnerable = game.vars.power_ticks > 0\n"
|
|
" if math.random(1, 100) <= 70 then\n"
|
|
" local best = pick\n"
|
|
" local bestd = vulnerable and -1 or 99999\n"
|
|
" for k = 1, #dirs do\n"
|
|
" local dv = DIRS[dirs[k]]\n"
|
|
" local tx = g.x + dv[1]\n"
|
|
" local ty = g.y + dv[2]\n"
|
|
" local md = math.abs(tx - game.vars.px) + math.abs(ty - game.vars.py)\n"
|
|
" if (not vulnerable and md < bestd) or (vulnerable and md > bestd) then\n"
|
|
" bestd = md\n"
|
|
" best = dirs[k]\n"
|
|
" end\n"
|
|
" end\n"
|
|
" pick = best\n"
|
|
" else\n"
|
|
" pick = dirs[math.random(1, #dirs)]\n"
|
|
" end\n"
|
|
"\n"
|
|
" g.dir = pick\n"
|
|
" local nx, ny, ok = try_step_tile(g.x, g.y, g.dir)\n"
|
|
" if ok then\n"
|
|
" g.x = nx\n"
|
|
" g.y = ny\n"
|
|
" end\n"
|
|
"\n"
|
|
" resolve_ghost_collision(g)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function spawn_entities()\n"
|
|
" local ptx, pty = nearest_open_tile(math.floor(game.vars.map_w / 2), game.vars.map_h - 3)\n"
|
|
" game.vars.px = ptx\n"
|
|
" game.vars.py = pty\n"
|
|
" game.vars.dir = \"R\"\n"
|
|
" game.vars.want_dir = \"R\"\n"
|
|
"\n"
|
|
" local g1tx, g1ty = nearest_open_tile(math.floor(game.vars.map_w / 2), math.floor(game.vars.map_h / 2))\n"
|
|
" local g2tx, g2ty = nearest_open_tile(math.floor(game.vars.map_w / 2) + 2, math.floor(game.vars.map_h / 2))\n"
|
|
"\n"
|
|
" game.vars.ghosts = {\n"
|
|
" {x = g1tx, y = g1ty, dir = \"L\", release = 18},\n"
|
|
" {x = g2tx, y = g2ty, dir = \"R\", release = 42},\n"
|
|
" }\n"
|
|
"\n"
|
|
" game.vars.grace = 80\n"
|
|
" game.vars.mouth_open = true\n"
|
|
" game.vars.power_ticks = 0\n"
|
|
"\n"
|
|
" -- movement cadence (digital): half the previous speed\n"
|
|
" game.vars.player_step_mod = 6\n"
|
|
" game.vars.ghost_step_mod = 12\n"
|
|
" -- Animate mouth twice per movement cycle\n"
|
|
" game.vars.anim_mod = 3\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function reset_game()\n"
|
|
" parse_level()\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.tick = 0\n"
|
|
" game.vars.state = \"playing\"\n"
|
|
" spawn_entities()\n"
|
|
" consume_pellet_at(game.vars.px, game.vars.py)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_control_button(r, dir, active)\n"
|
|
" renderer.rect(r.x, r.y, r.w, r.h, true, active)\n"
|
|
" local cx = r.x + math.floor(r.w / 2)\n"
|
|
" local cy = r.y + math.floor(r.h / 2)\n"
|
|
" if dir == \"U\" then\n"
|
|
" renderer.triangle(cx, cy - 6, cx - 5, cy + 3, cx + 5, cy + 3, not active, true)\n"
|
|
" elseif dir == \"D\" then\n"
|
|
" renderer.triangle(cx, cy + 6, cx - 5, cy - 3, cx + 5, cy - 3, not active, true)\n"
|
|
" elseif dir == \"L\" then\n"
|
|
" renderer.triangle(cx - 6, cy, cx + 3, cy - 5, cx + 3, cy + 5, not active, true)\n"
|
|
" else\n"
|
|
" renderer.triangle(cx + 6, cy, cx - 3, cy - 5, cx - 3, cy + 5, not active, true)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_touch_controls()\n"
|
|
" local ctl = control_positions()\n"
|
|
" draw_control_button(ctl.U, \"U\", game.vars.want_dir == \"U\")\n"
|
|
" draw_control_button(ctl.D, \"D\", game.vars.want_dir == \"D\")\n"
|
|
" draw_control_button(ctl.L, \"L\", game.vars.want_dir == \"L\")\n"
|
|
" draw_control_button(ctl.R, \"R\", game.vars.want_dir == \"R\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" math.randomseed(12345)\n"
|
|
" reset_game()\n"
|
|
" game.set_frame_updates(true)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" if event.type == INPUT.BUTTON_1 then\n"
|
|
" game.exit()\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" if event.type == INPUT.BUTTON_0 then\n"
|
|
" reset_game()\n"
|
|
" return true\n"
|
|
" end\n"
|
|
"\n"
|
|
" if game.vars.state ~= \"playing\" then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN then\n"
|
|
" reset_game()\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" if event.type == INPUT.TOUCH_DOWN then\n"
|
|
" local d = touch_to_dir(event.x, event.y)\n"
|
|
" if d then\n"
|
|
" game.vars.want_dir = d\n"
|
|
" else\n"
|
|
" handle_touch(event.x, event.y)\n"
|
|
" end\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" if event.type == INPUT.FRAME_TICK then\n"
|
|
" game.vars.tick = game.vars.tick + 1\n"
|
|
" if game.vars.power_ticks > 0 then\n"
|
|
" game.vars.power_ticks = game.vars.power_ticks - 1\n"
|
|
" end\n"
|
|
"\n"
|
|
" if game.vars.tick % game.vars.anim_mod == 0 then\n"
|
|
" game.vars.mouth_open = not game.vars.mouth_open\n"
|
|
" end\n"
|
|
"\n"
|
|
" if game.vars.tick % game.vars.player_step_mod == 0 then\n"
|
|
" move_player_step()\n"
|
|
" end\n"
|
|
"\n"
|
|
" if game.vars.tick % game.vars.ghost_step_mod == 0 then\n"
|
|
" move_ghosts_step()\n"
|
|
" end\n"
|
|
"\n"
|
|
" return true\n"
|
|
" end\n"
|
|
"\n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false)\n"
|
|
"\n"
|
|
" renderer.text(math.floor(game.width() / 2) - 18, 22, \"PACMAN\", true)\n"
|
|
" draw_level()\n"
|
|
"\n"
|
|
" local pcx, pcy = tile_center(game.vars.px, game.vars.py)\n"
|
|
" draw_pacman(pcx, pcy, game.vars.mouth_open, game.vars.dir)\n"
|
|
"\n"
|
|
" for i = 1, #game.vars.ghosts do\n"
|
|
" local g = game.vars.ghosts[i]\n"
|
|
" local gx, gy = tile_center(g.x, g.y)\n"
|
|
" local vulnerable = game.vars.power_ticks > 0\n"
|
|
" local blink = vulnerable and ((game.vars.tick % 10) < 5)\n"
|
|
" draw_ghost(gx, gy, vulnerable, blink)\n"
|
|
" end\n"
|
|
"\n"
|
|
" local ox, oy, _, ph = level_origin()\n"
|
|
" renderer.text_scaled(ox, oy + ph + 14, \"SCORE:\" .. tostring(game.vars.score), true, 2)\n"
|
|
"\n"
|
|
" if game.vars.power_ticks > 0 then\n"
|
|
" local secs = math.ceil(game.vars.power_ticks / 24)\n"
|
|
" renderer.rect(ox + 124, oy + ph + 18, 130, 20, true, false)\n"
|
|
" renderer.text(ox + 132, oy + ph + 24, \"POWER \" .. tostring(secs) .. \"s\", true)\n"
|
|
" end\n"
|
|
"\n"
|
|
" draw_touch_controls()\n"
|
|
"\n"
|
|
" if game.vars.state == \"won\" then\n"
|
|
" renderer.rect(128, 20, 144, 18, true, false)\n"
|
|
" renderer.text(178, 26, \"YOU WIN\", true)\n"
|
|
" elseif game.vars.state == \"lost\" then\n"
|
|
" renderer.rect(128, 20, 144, 18, true, false)\n"
|
|
" renderer.text(172, 26, \"GAME OVER\", true)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char pong_lua_src[] = "-- NAME: Pong\n"
|
|
"-- DESC: Classic two-player Pong game\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_GAME_OVER = 2\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local PADDLE_WIDTH = 8\n"
|
|
"local PADDLE_HEIGHT = 40\n"
|
|
"local BALL_RADIUS = 5\n"
|
|
"local MAX_SCORE = 5\n"
|
|
"\n"
|
|
"-- Initialize game\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" \n"
|
|
" -- Left paddle (player 1)\n"
|
|
" game.vars.paddle_left_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)\n"
|
|
" game.vars.paddle_left_score = 0\n"
|
|
" \n"
|
|
" -- Right paddle (player 2)\n"
|
|
" game.vars.paddle_right_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)\n"
|
|
" game.vars.paddle_right_score = 0\n"
|
|
" \n"
|
|
" -- Ball\n"
|
|
" game.vars.ball_x = game.width() / 2\n"
|
|
" game.vars.ball_y = game.height() / 2\n"
|
|
" game.vars.ball_vel_x = 3\n"
|
|
" game.vars.ball_vel_y = 2\n"
|
|
" \n"
|
|
" -- Enable continuous frame updates for smooth animation\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Pong initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Update game logic\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" -- State: MENU\n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" reset_game()\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- State: PLAYING\n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Handle paddle input via touch\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.TOUCH_MOVE then\n"
|
|
" -- Left side touch moves left paddle, right side touch moves right paddle\n"
|
|
" if event.x < game.width() / 2 then\n"
|
|
" -- Move left paddle (constrain within bounds)\n"
|
|
" game.vars.paddle_left_y = math.max(0, math.min(game.height() - PADDLE_HEIGHT, event.y - PADDLE_HEIGHT / 2))\n"
|
|
" else\n"
|
|
" -- Move right paddle (constrain within bounds)\n"
|
|
" game.vars.paddle_right_y = math.max(0, math.min(game.height() - PADDLE_HEIGHT, event.y - PADDLE_HEIGHT / 2))\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Update physics on frame tick\n"
|
|
" if event.type == INPUT.FRAME_TICK then\n"
|
|
" update_ball()\n"
|
|
" \n"
|
|
" -- Check win condition\n"
|
|
" if game.vars.paddle_left_score >= MAX_SCORE or game.vars.paddle_right_score >= MAX_SCORE then\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true -- Always redraw when playing\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- State: GAME_OVER\n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Draw game\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false) -- Black background\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" -- Draw: MENU\n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 15, game.height() / 2 - 30, \"PONG\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, \"Tap to Start\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 70, game.height() / 2 + 20, \"First to \" .. tostring(MAX_SCORE), true, 2)\n"
|
|
" \n"
|
|
" -- Draw: PLAYING\n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Draw center line\n"
|
|
" for y = 0, game.height(), 5 do\n"
|
|
" renderer.pixel(game.width() / 2, y, true)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw paddles\n"
|
|
" renderer.rect(10, game.vars.paddle_left_y, PADDLE_WIDTH, PADDLE_HEIGHT, true, true)\n"
|
|
" renderer.rect(game.width() - 10 - PADDLE_WIDTH, game.vars.paddle_right_y, PADDLE_WIDTH, PADDLE_HEIGHT, true, true)\n"
|
|
" \n"
|
|
" -- Draw ball (convert to integers)\n"
|
|
" renderer.circle(math.floor(game.vars.ball_x + 0.5), math.floor(game.vars.ball_y + 0.5), BALL_RADIUS, true, true)\n"
|
|
" \n"
|
|
" -- Draw scores\n"
|
|
" local left_score_text = tostring(game.vars.paddle_left_score)\n"
|
|
" local right_score_text = tostring(game.vars.paddle_right_score)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, 5, left_score_text, true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 + 20, 5, right_score_text, true, 2)\n"
|
|
" \n"
|
|
" -- Draw: GAME_OVER\n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2 - 30, \"GAME OVER\", true, 2)\n"
|
|
" \n"
|
|
" local winner = \"Player 1 Wins!\"\n"
|
|
" if game.vars.paddle_right_score > game.vars.paddle_left_score then\n"
|
|
" winner = \"Player 2 Wins!\"\n"
|
|
" end\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, winner, true, 2)\n"
|
|
" \n"
|
|
" local final_text = game.vars.paddle_left_score .. \" - \" .. game.vars.paddle_right_score\n"
|
|
" renderer.text_scaled(game.width() / 2 - 25, game.height() / 2 + 20, final_text, true, 2)\n"
|
|
" \n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() / 2 + 40, \"Tap to Menu\", true, 2)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Helper: Update ball physics\n"
|
|
"function update_ball()\n"
|
|
" -- Move ball\n"
|
|
" game.vars.ball_x = game.vars.ball_x + game.vars.ball_vel_x\n"
|
|
" game.vars.ball_y = game.vars.ball_y + game.vars.ball_vel_y\n"
|
|
" \n"
|
|
" -- Bounce off top/bottom\n"
|
|
" if game.vars.ball_y - BALL_RADIUS < 0 or game.vars.ball_y + BALL_RADIUS > game.height() then\n"
|
|
" game.vars.ball_vel_y = -game.vars.ball_vel_y\n"
|
|
" game.vars.ball_y = math.max(BALL_RADIUS, math.min(game.height() - BALL_RADIUS, game.vars.ball_y))\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Check left paddle collision\n"
|
|
" if game.vars.ball_x - BALL_RADIUS < 10 + PADDLE_WIDTH then\n"
|
|
" if game.vars.ball_y > game.vars.paddle_left_y and game.vars.ball_y < game.vars.paddle_left_y + PADDLE_HEIGHT then\n"
|
|
" if game.vars.ball_vel_x < 0 then\n"
|
|
" game.vars.ball_vel_x = -game.vars.ball_vel_x\n"
|
|
" \n"
|
|
" -- Add spin based on where ball hits paddle\n"
|
|
" local hit_pos = (game.vars.ball_y - game.vars.paddle_left_y) / PADDLE_HEIGHT\n"
|
|
" game.vars.ball_vel_y = (hit_pos - 0.5) * 6\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Check right paddle collision\n"
|
|
" if game.vars.ball_x + BALL_RADIUS > game.width() - 10 - PADDLE_WIDTH then\n"
|
|
" if game.vars.ball_y > game.vars.paddle_right_y and game.vars.ball_y < game.vars.paddle_right_y + PADDLE_HEIGHT then\n"
|
|
" if game.vars.ball_vel_x > 0 then\n"
|
|
" game.vars.ball_vel_x = -game.vars.ball_vel_x\n"
|
|
" \n"
|
|
" -- Add spin based on where ball hits paddle\n"
|
|
" local hit_pos = (game.vars.ball_y - game.vars.paddle_right_y) / PADDLE_HEIGHT\n"
|
|
" game.vars.ball_vel_y = (hit_pos - 0.5) * 6\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Score on left side miss\n"
|
|
" if game.vars.ball_x < 0 then\n"
|
|
" game.vars.paddle_right_score = game.vars.paddle_right_score + 1\n"
|
|
" reset_ball()\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Score on right side miss\n"
|
|
" if game.vars.ball_x > game.width() then\n"
|
|
" game.vars.paddle_left_score = game.vars.paddle_left_score + 1\n"
|
|
" reset_ball()\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Helper: Reset ball to center\n"
|
|
"function reset_ball()\n"
|
|
" game.vars.ball_x = game.width() / 2\n"
|
|
" game.vars.ball_y = game.height() / 2\n"
|
|
" game.vars.ball_vel_x = 3 * (math.random() > 0.5 and 1 or -1)\n"
|
|
" game.vars.ball_vel_y = 2 * (math.random() > 0.5 and 1 or -1)\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Helper: Reset game\n"
|
|
"function reset_game()\n"
|
|
" game.vars.paddle_left_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)\n"
|
|
" game.vars.paddle_left_score = 0\n"
|
|
" game.vars.paddle_right_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)\n"
|
|
" game.vars.paddle_right_score = 0\n"
|
|
" reset_ball()\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char simon_says_lua_src[] = "-- NAME: Simon Says\n"
|
|
"-- DESC: Repeat the color sequence\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_SHOWING = 2\n"
|
|
"local STATE_GAME_OVER = 3\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local SHOW_DURATION = 30 -- Frames to show each button\n"
|
|
"local WAIT_DURATION = 20 -- Frames between shows\n"
|
|
"\n"
|
|
"-- Button positions (calculated dynamically based on screen size)\n"
|
|
"local function get_buttons()\n"
|
|
" local padding = math.floor(math.min(game.width(), game.height()) / 10)\n"
|
|
" local available = math.min(game.width(), game.height()) - (padding * 3)\n"
|
|
" local button_size = math.floor(available / 2)\n"
|
|
" local spacing = padding\n"
|
|
" \n"
|
|
" return {\n"
|
|
" {x = padding, y = padding, size = button_size, color = 1}, -- Top-left\n"
|
|
" {x = padding + button_size + spacing, y = padding, size = button_size, color = 2}, -- Top-right\n"
|
|
" {x = padding, y = padding + button_size + spacing, size = button_size, color = 3}, -- Bottom-left\n"
|
|
" {x = padding + button_size + spacing, y = padding + button_size + spacing, size = button_size, color = 4} -- Bottom-right\n"
|
|
" }\n"
|
|
"end\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" game.vars.score = 0\n"
|
|
" \n"
|
|
" -- Sequence of button presses\n"
|
|
" game.vars.sequence = {}\n"
|
|
" game.vars.player_seq = {}\n"
|
|
" \n"
|
|
" -- Animation state\n"
|
|
" game.vars.showing_idx = 0\n"
|
|
" game.vars.show_frame = 0\n"
|
|
" game.vars.show_button = nil\n"
|
|
" \n"
|
|
" -- Input state\n"
|
|
" game.vars.waiting_for_input = false\n"
|
|
" game.vars.input_idx = 0\n"
|
|
" \n"
|
|
" -- Enable continuous updates\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Simon Says initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" reset_game()\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Start showing sequence\n"
|
|
" if event.type == INPUT.FRAME_TICK then\n"
|
|
" game.vars.show_frame = game.vars.show_frame + 1\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
" \n"
|
|
" elseif state == STATE_SHOWING then\n"
|
|
" -- Animate sequence\n"
|
|
" if event.type == INPUT.FRAME_TICK then\n"
|
|
" game.vars.show_frame = game.vars.show_frame + 1\n"
|
|
" \n"
|
|
" -- Move to next button in sequence\n"
|
|
" if game.vars.show_frame > SHOW_DURATION + WAIT_DURATION then\n"
|
|
" game.vars.showing_idx = game.vars.showing_idx + 1\n"
|
|
" game.vars.show_frame = 0\n"
|
|
" game.vars.show_button = nil\n"
|
|
" \n"
|
|
" -- Done showing sequence, wait for player input\n"
|
|
" if game.vars.showing_idx > #game.vars.sequence then\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" game.vars.waiting_for_input = true\n"
|
|
" game.vars.input_idx = 0\n"
|
|
" end\n"
|
|
" else\n"
|
|
" -- Highlight button during show duration\n"
|
|
" if game.vars.show_frame <= SHOW_DURATION then\n"
|
|
" game.vars.show_button = game.vars.sequence[game.vars.showing_idx]\n"
|
|
" else\n"
|
|
" game.vars.show_button = nil\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
" \n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Handle player input\n"
|
|
" if game.vars.waiting_for_input and event.type == INPUT.TOUCH_DOWN then\n"
|
|
" local button = get_button_at(event.x, event.y)\n"
|
|
" if button then\n"
|
|
" game.vars.player_seq[#game.vars.player_seq + 1] = button\n"
|
|
" game.vars.input_idx = game.vars.input_idx + 1\n"
|
|
" \n"
|
|
" -- Check if correct\n"
|
|
" if game.vars.sequence[game.vars.input_idx] ~= button then\n"
|
|
" -- Wrong! Game over\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Check if completed sequence\n"
|
|
" if game.vars.input_idx == #game.vars.sequence then\n"
|
|
" -- Advance to next round\n"
|
|
" game.vars.sequence[#game.vars.sequence + 1] = math.random(1, 4)\n"
|
|
" game.vars.player_seq = {}\n"
|
|
" game.vars.waiting_for_input = false\n"
|
|
" game.vars.showing_idx = 0\n"
|
|
" game.vars.show_frame = 0\n"
|
|
" game.vars.show_button = nil\n"
|
|
" game.vars.state = STATE_SHOWING\n"
|
|
" game.vars.score = game.vars.score + 1\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false) -- Black background\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" local buttons = get_buttons()\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2 - 40, \"SIMON SAYS\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 - 10, \"Repeat the sequence\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 + 20, \"Tap to Start\", true, 2)\n"
|
|
" \n"
|
|
" else\n"
|
|
" -- Draw buttons with highlight\n"
|
|
" for i = 1, 4 do\n"
|
|
" local btn = buttons[i]\n"
|
|
" local filled = (game.vars.show_button == i)\n"
|
|
" renderer.rect(btn.x, btn.y, btn.size, btn.size, true, filled)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw score\n"
|
|
" renderer.text_scaled(10, 10, \"Level: \" .. tostring(game.vars.score + 1), true, 2)\n"
|
|
" \n"
|
|
" if state == STATE_PLAYING and game.vars.waiting_for_input then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() - 20, \"Your turn!\", true, 2)\n"
|
|
" end\n"
|
|
" \n"
|
|
" if state == STATE_GAME_OVER then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2 - 20, \"GAME OVER\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, game.height() / 2, \"Level: \" .. tostring(game.vars.score + 1), true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() / 2 + 20, \"Tap to Restart\", true, 2)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function get_button_at(x, y)\n"
|
|
" local buttons = get_buttons()\n"
|
|
" for i = 1, 4 do\n"
|
|
" local btn = buttons[i]\n"
|
|
" if x >= btn.x and x < btn.x + btn.size and\n"
|
|
" y >= btn.y and y < btn.y + btn.size then\n"
|
|
" return i\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return nil\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_game()\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.sequence = {math.random(1, 4)}\n"
|
|
" game.vars.player_seq = {}\n"
|
|
" game.vars.showing_idx = 0\n"
|
|
" game.vars.show_frame = 0\n"
|
|
" game.vars.show_button = nil\n"
|
|
" game.vars.waiting_for_input = false\n"
|
|
" game.vars.input_idx = 0\n"
|
|
" game.vars.state = STATE_SHOWING\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char snake_lua_src[] = "-- NAME: Snake Game\n"
|
|
"-- DESC: Classic snake with state machine\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_GAME_OVER = 2\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local CELL_SIZE = 10\n"
|
|
"local BTN = 30\n"
|
|
"local ARENA_WIDTH_RATIO = 0.8\n"
|
|
"local BASE_MOVE_SPEED = 16\n"
|
|
"local MAX_SPEED_MULTIPLIER = 5\n"
|
|
"local DOTS_PER_SPEED_STEP = 10\n"
|
|
"\n"
|
|
"local function setup_arena()\n"
|
|
" game.vars.grid_h = math.floor(game.height() / CELL_SIZE)\n"
|
|
" game.vars.grid_w = math.floor((game.width() * ARENA_WIDTH_RATIO) / CELL_SIZE)\n"
|
|
" game.vars.arena_x = 0\n"
|
|
" game.vars.arena_y = 0\n"
|
|
" game.vars.arena_w = game.vars.grid_w * CELL_SIZE\n"
|
|
" game.vars.arena_h = game.vars.grid_h * CELL_SIZE\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function control_positions()\n"
|
|
" local cx = game.width() - 48\n"
|
|
" local cy = game.height() - 50\n"
|
|
" return {\n"
|
|
" U = {x = cx, y = cy - BTN, w = BTN, h = BTN},\n"
|
|
" D = {x = cx, y = cy + BTN, w = BTN, h = BTN},\n"
|
|
" L = {x = cx - BTN, y = cy, w = BTN, h = BTN},\n"
|
|
" R = {x = cx + BTN, y = cy, w = BTN, h = BTN},\n"
|
|
" }\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function point_in(px, py, r)\n"
|
|
" return px >= r.x and px < (r.x + r.w) and py >= r.y and py < (r.y + r.h)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function touch_to_dir(tx, ty)\n"
|
|
" local ctl = control_positions()\n"
|
|
" if point_in(tx, ty, ctl.U) then return \"U\" end\n"
|
|
" if point_in(tx, ty, ctl.D) then return \"D\" end\n"
|
|
" if point_in(tx, ty, ctl.L) then return \"L\" end\n"
|
|
" if point_in(tx, ty, ctl.R) then return \"R\" end\n"
|
|
" return nil\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function set_dir(dir)\n"
|
|
" if dir == \"R\" and game.vars.dir_x ~= -1 then\n"
|
|
" game.vars.dir_x = 1\n"
|
|
" game.vars.dir_y = 0\n"
|
|
" elseif dir == \"L\" and game.vars.dir_x ~= 1 then\n"
|
|
" game.vars.dir_x = -1\n"
|
|
" game.vars.dir_y = 0\n"
|
|
" elseif dir == \"D\" and game.vars.dir_y ~= -1 then\n"
|
|
" game.vars.dir_x = 0\n"
|
|
" game.vars.dir_y = 1\n"
|
|
" elseif dir == \"U\" and game.vars.dir_y ~= 1 then\n"
|
|
" game.vars.dir_x = 0\n"
|
|
" game.vars.dir_y = -1\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function global_touch_to_dir(tx, ty)\n"
|
|
" local cx = math.floor(game.width() / 2)\n"
|
|
" local cy = math.floor(game.height() / 2)\n"
|
|
" local dx = tx - cx\n"
|
|
" local dy = ty - cy\n"
|
|
"\n"
|
|
" if math.abs(dx) > math.abs(dy) then\n"
|
|
" if dx >= 0 then\n"
|
|
" return \"R\"\n"
|
|
" end\n"
|
|
" return \"L\"\n"
|
|
" end\n"
|
|
" if dy >= 0 then\n"
|
|
" return \"D\"\n"
|
|
" end\n"
|
|
" return \"U\"\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_control_button(r, dir, active)\n"
|
|
" renderer.rect(r.x, r.y, r.w, r.h, true, active)\n"
|
|
" local cx = r.x + math.floor(r.w / 2)\n"
|
|
" local cy = r.y + math.floor(r.h / 2)\n"
|
|
" if dir == \"U\" then\n"
|
|
" renderer.triangle(cx, cy - 6, cx - 5, cy + 3, cx + 5, cy + 3, not active, true)\n"
|
|
" elseif dir == \"D\" then\n"
|
|
" renderer.triangle(cx, cy + 6, cx - 5, cy - 3, cx + 5, cy - 3, not active, true)\n"
|
|
" elseif dir == \"L\" then\n"
|
|
" renderer.triangle(cx - 6, cy, cx + 3, cy - 5, cx + 3, cy + 5, not active, true)\n"
|
|
" else\n"
|
|
" renderer.triangle(cx + 6, cy, cx - 3, cy - 5, cx - 3, cy + 5, not active, true)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_touch_controls()\n"
|
|
" local ctl = control_positions()\n"
|
|
" draw_control_button(ctl.U, \"U\", game.vars.dir_y == -1)\n"
|
|
" draw_control_button(ctl.D, \"D\", game.vars.dir_y == 1)\n"
|
|
" draw_control_button(ctl.L, \"L\", game.vars.dir_x == -1)\n"
|
|
" draw_control_button(ctl.R, \"R\", game.vars.dir_x == 1)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_dotted_boundary()\n"
|
|
" local x0 = game.vars.arena_x\n"
|
|
" local y0 = game.vars.arena_y\n"
|
|
" local x1 = game.vars.arena_x + game.vars.arena_w - 1\n"
|
|
" local y1 = game.vars.arena_y + game.vars.arena_h - 1\n"
|
|
" local step = 4\n"
|
|
"\n"
|
|
" local x = x0\n"
|
|
" while x <= x1 do\n"
|
|
" renderer.pixel(x, y0, true)\n"
|
|
" renderer.pixel(x, y1, true)\n"
|
|
" x = x + step\n"
|
|
" end\n"
|
|
"\n"
|
|
" local y = y0\n"
|
|
" while y <= y1 do\n"
|
|
" renderer.pixel(x0, y, true)\n"
|
|
" renderer.pixel(x1, y, true)\n"
|
|
" y = y + step\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function update_move_speed()\n"
|
|
" local mult = 1 + math.floor(game.vars.food_eaten / DOTS_PER_SPEED_STEP)\n"
|
|
" if mult > MAX_SPEED_MULTIPLIER then\n"
|
|
" mult = MAX_SPEED_MULTIPLIER\n"
|
|
" end\n"
|
|
" game.vars.speed_multiplier = mult\n"
|
|
" game.vars.move_speed = math.max(1, math.floor(BASE_MOVE_SPEED / mult))\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Initialize game\n"
|
|
"function init()\n"
|
|
" setup_arena()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.high_score = 0\n"
|
|
" \n"
|
|
" -- Snake as array of {x, y} positions\n"
|
|
" local cx = math.floor(game.vars.grid_w / 2)\n"
|
|
" local cy = math.floor(game.vars.grid_h / 2)\n"
|
|
" game.vars.snake = {}\n"
|
|
" game.vars.snake[1] = {x = cx, y = cy}\n"
|
|
" game.vars.snake[2] = {x = cx - 1, y = cy}\n"
|
|
" game.vars.snake[3] = {x = cx - 2, y = cy}\n"
|
|
" \n"
|
|
" game.vars.dir_x = 1\n"
|
|
" game.vars.dir_y = 0\n"
|
|
" \n"
|
|
" game.vars.food_x = math.min(cx + 10, game.vars.grid_w - 1)\n"
|
|
" game.vars.food_y = cy\n"
|
|
" \n"
|
|
" game.vars.frame_count = 0\n"
|
|
" game.vars.food_eaten = 0\n"
|
|
" game.vars.speed_multiplier = 1\n"
|
|
" game.vars.move_speed = BASE_MOVE_SPEED\n"
|
|
" game.vars.score_submit_pending = false\n"
|
|
" game.vars.score_submitted = 0\n"
|
|
" \n"
|
|
" -- Enable continuous frame updates\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Snake Game initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Update game logic\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" -- State: MENU\n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" game.vars.score = 0\n"
|
|
" init_snake()\n"
|
|
" spawn_food()\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- State: PLAYING\n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Handle input for direction change\n"
|
|
" if event.type == INPUT.TOUCH_DOWN then\n"
|
|
" local d = touch_to_dir(event.x, event.y)\n"
|
|
" if d then\n"
|
|
" set_dir(d)\n"
|
|
" else\n"
|
|
" set_dir(global_touch_to_dir(event.x, event.y))\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Move snake every N frames\n"
|
|
" game.vars.frame_count = game.vars.frame_count + 1\n"
|
|
" if game.vars.frame_count >= game.vars.move_speed then\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" move_snake()\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- State: GAME_OVER\n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Draw game\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false)\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" -- Draw: MENU\n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text(game.width() / 2 - 30, game.height() / 2 - 20, \"SNAKE\", true)\n"
|
|
" renderer.text(game.width() / 2 - 50, game.height() / 2, \"Tap to Start\", true)\n"
|
|
" \n"
|
|
" if game.vars.high_score > 0 then\n"
|
|
" local hs_text = \"High: \" .. tostring(game.vars.high_score)\n"
|
|
" renderer.text(game.width() / 2 - 30, game.height() / 2 + 20, hs_text, true)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw: PLAYING\n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" draw_dotted_boundary()\n"
|
|
"\n"
|
|
" -- Draw snake\n"
|
|
" for i = 1, #game.vars.snake do\n"
|
|
" local seg = game.vars.snake[i]\n"
|
|
" local filled = (i == 1) -- Head filled, body outline\n"
|
|
" renderer.rect(game.vars.arena_x + seg.x * CELL_SIZE, game.vars.arena_y + seg.y * CELL_SIZE, CELL_SIZE, CELL_SIZE, true, filled)\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw food (convert center to integers)\n"
|
|
" renderer.circle(math.floor(game.vars.arena_x + game.vars.food_x * CELL_SIZE + CELL_SIZE / 2 + 0.5), \n"
|
|
" math.floor(game.vars.arena_y + game.vars.food_y * CELL_SIZE + CELL_SIZE / 2 + 0.5), \n"
|
|
" CELL_SIZE / 2, true, true)\n"
|
|
" \n"
|
|
" -- Draw score\n"
|
|
" local score_text = \"Score: \" .. tostring(game.vars.score)\n"
|
|
" renderer.text(5, 5, score_text, true)\n"
|
|
" draw_touch_controls()\n"
|
|
" \n"
|
|
" -- Draw: GAME_OVER\n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" renderer.text(game.width() / 2 - 40, game.height() / 2 - 20, \"GAME OVER\", true)\n"
|
|
" \n"
|
|
" local score_text = \"Score: \" .. tostring(game.vars.score)\n"
|
|
" renderer.text(game.width() / 2 - 40, game.height() / 2, score_text, true)\n"
|
|
" \n"
|
|
" renderer.text(game.width() / 2 - 60, game.height() / 2 + 20, \"Tap to Continue\", true)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Helper: Initialize snake\n"
|
|
"function init_snake()\n"
|
|
" local cx = math.floor(game.vars.grid_w / 2)\n"
|
|
" local cy = math.floor(game.vars.grid_h / 2)\n"
|
|
" game.vars.snake = {}\n"
|
|
" game.vars.snake[1] = {x = cx, y = cy}\n"
|
|
" game.vars.snake[2] = {x = cx - 1, y = cy}\n"
|
|
" game.vars.snake[3] = {x = cx - 2, y = cy}\n"
|
|
" game.vars.dir_x = 1\n"
|
|
" game.vars.dir_y = 0\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" game.vars.food_eaten = 0\n"
|
|
" update_move_speed()\n"
|
|
" game.vars.score_submit_pending = false\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Helper: Spawn food at random position\n"
|
|
"function spawn_food()\n"
|
|
" game.vars.food_x = math.random(0, game.vars.grid_w - 1)\n"
|
|
" game.vars.food_y = math.random(0, game.vars.grid_h - 1)\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Helper: Move snake\n"
|
|
"function move_snake()\n"
|
|
" local head = game.vars.snake[1]\n"
|
|
" local new_head = {\n"
|
|
" x = (head.x + game.vars.dir_x) % game.vars.grid_w,\n"
|
|
" y = (head.y + game.vars.dir_y) % game.vars.grid_h\n"
|
|
" }\n"
|
|
" \n"
|
|
" -- Wrap around negative values (Lua modulo behavior)\n"
|
|
" if new_head.x < 0 then new_head.x = new_head.x + game.vars.grid_w end\n"
|
|
" if new_head.y < 0 then new_head.y = new_head.y + game.vars.grid_h end\n"
|
|
" \n"
|
|
" -- Check self collision\n"
|
|
" for i = 1, #game.vars.snake do\n"
|
|
" local seg = game.vars.snake[i]\n"
|
|
" if new_head.x == seg.x and new_head.y == seg.y then\n"
|
|
" game_over()\n"
|
|
" return\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Check food collision\n"
|
|
" local ate_food = false\n"
|
|
" if new_head.x == game.vars.food_x and new_head.y == game.vars.food_y then\n"
|
|
" ate_food = true\n"
|
|
" game.vars.score = game.vars.score + 10\n"
|
|
" game.vars.food_eaten = game.vars.food_eaten + 1\n"
|
|
" update_move_speed()\n"
|
|
" spawn_food()\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Move snake\n"
|
|
" table.insert(game.vars.snake, 1, new_head)\n"
|
|
" \n"
|
|
" if not ate_food then\n"
|
|
" table.remove(game.vars.snake) -- Remove tail\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Helper: Game over\n"
|
|
"function game_over()\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" \n"
|
|
" if game.vars.score > game.vars.high_score then\n"
|
|
" game.vars.high_score = game.vars.score\n"
|
|
" end\n"
|
|
"\n"
|
|
" if not game.vars.score_submit_pending then\n"
|
|
" game.vars.score_submitted = game.vars.score\n"
|
|
" game.vars.score_submit_pending = true\n"
|
|
" end\n"
|
|
" \n"
|
|
" print(\"Game Over! Score: \" .. game.vars.score)\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char solitaire_lua_src[] = "-- NAME: Solitaire\n"
|
|
"-- DESC: Klondike-style solitaire\n"
|
|
"\n"
|
|
"local CARD_W = 38\n"
|
|
"local CARD_H = 48\n"
|
|
"local TOP_Y = 30\n"
|
|
"local TAB_Y = 98\n"
|
|
"local LEFT = 37\n"
|
|
"local GAP_X = 10\n"
|
|
"local FAN_DOWN = 9\n"
|
|
"\n"
|
|
"local SUITS = {\"S\", \"H\", \"D\", \"C\"}\n"
|
|
"\n"
|
|
"local function C(r, s, up)\n"
|
|
" return {r = r, s = s, up = up and true or false}\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function card_color_red(c)\n"
|
|
" return c.s == \"H\" or c.s == \"D\"\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function rank_text(r)\n"
|
|
" if r == 1 then return \"A\" end\n"
|
|
" if r == 11 then return \"J\" end\n"
|
|
" if r == 12 then return \"Q\" end\n"
|
|
" if r == 13 then return \"K\" end\n"
|
|
" return tostring(r)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local SUIT_BITMAPS = {\n"
|
|
" D = {\n"
|
|
" \"0001000\",\n"
|
|
" \"0011100\",\n"
|
|
" \"0111110\",\n"
|
|
" \"1111111\",\n"
|
|
" \"0111110\",\n"
|
|
" \"0011100\",\n"
|
|
" \"0001000\",\n"
|
|
" },\n"
|
|
" H = {\n"
|
|
" \"0110110\",\n"
|
|
" \"1111111\",\n"
|
|
" \"1111111\",\n"
|
|
" \"1111111\",\n"
|
|
" \"0111110\",\n"
|
|
" \"0011100\",\n"
|
|
" \"0001000\",\n"
|
|
" },\n"
|
|
" S = {\n"
|
|
" \"0001000\",\n"
|
|
" \"0011100\",\n"
|
|
" \"0111110\",\n"
|
|
" \"1111111\",\n"
|
|
" \"0011100\",\n"
|
|
" \"0011100\",\n"
|
|
" \"0001000\",\n"
|
|
" },\n"
|
|
" C = {\n"
|
|
" \"0011100\",\n"
|
|
" \"0111110\",\n"
|
|
" \"0011100\",\n"
|
|
" \"1111111\",\n"
|
|
" \"0011100\",\n"
|
|
" \"0011100\",\n"
|
|
" \"0001000\",\n"
|
|
" }\n"
|
|
"}\n"
|
|
"\n"
|
|
"local function label_metrics(label, scale)\n"
|
|
" local w = (#label * 6 * scale)\n"
|
|
" local h = (7 * scale)\n"
|
|
" return w, h\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_suit_icon(x, y, suit, scale)\n"
|
|
" local bmp = SUIT_BITMAPS[suit]\n"
|
|
" if not bmp then return end\n"
|
|
" for row = 1, #bmp do\n"
|
|
" local line = bmp[row]\n"
|
|
" for col = 1, #line do\n"
|
|
" if string.sub(line, col, col) == \"1\" then\n"
|
|
" renderer.rect(\n"
|
|
" x + (col - 1) * scale,\n"
|
|
" y + (row - 1) * scale,\n"
|
|
" scale,\n"
|
|
" scale,\n"
|
|
" true,\n"
|
|
" true\n"
|
|
" )\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function make_deck()\n"
|
|
" local d = {}\n"
|
|
" for _, s in ipairs(SUITS) do\n"
|
|
" for r = 1, 13 do\n"
|
|
" d[#d + 1] = {r = r, s = s, up = false}\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return d\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function shuffle(deck)\n"
|
|
" for i = #deck, 2, -1 do\n"
|
|
" local j = math.random(1, i)\n"
|
|
" deck[i], deck[j] = deck[j], deck[i]\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function setup_game()\n"
|
|
" game.vars.stock = {}\n"
|
|
" game.vars.waste = {}\n"
|
|
" game.vars.foundations = {{}, {}, {}, {}}\n"
|
|
" game.vars.tableau = {{}, {}, {}, {}, {}, {}, {}}\n"
|
|
" game.vars.selected = nil\n"
|
|
"\n"
|
|
" local deck = make_deck()\n"
|
|
" shuffle(deck)\n"
|
|
"\n"
|
|
" -- Klondike deal: 1..7 cards in each tableau column, top card face up\n"
|
|
" local idx = 1\n"
|
|
" for col = 1, 7 do\n"
|
|
" for row = 1, col do\n"
|
|
" local c = deck[idx]\n"
|
|
" idx = idx + 1\n"
|
|
" c.up = (row == col)\n"
|
|
" game.vars.tableau[col][#game.vars.tableau[col] + 1] = c\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" while idx <= #deck do\n"
|
|
" local c = deck[idx]\n"
|
|
" c.up = false\n"
|
|
" game.vars.stock[#game.vars.stock + 1] = c\n"
|
|
" idx = idx + 1\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Deterministic mid-game tableau to visually match the provided reference.\n"
|
|
"local function setup_reference_layout()\n"
|
|
" game.vars.stock = {}\n"
|
|
" game.vars.waste = {}\n"
|
|
" game.vars.foundations = {{}, {}, {}, {}}\n"
|
|
" game.vars.tableau = {{}, {}, {}, {}, {}, {}, {}}\n"
|
|
" game.vars.selected = nil\n"
|
|
"\n"
|
|
" -- Stock / waste\n"
|
|
" for i = 1, 24 do\n"
|
|
" game.vars.stock[#game.vars.stock + 1] = C(((i - 1) % 13) + 1, SUITS[((i - 1) % 4) + 1], false)\n"
|
|
" end\n"
|
|
" game.vars.waste[#game.vars.waste + 1] = C(7, \"D\", true)\n"
|
|
"\n"
|
|
" -- Foundations: A♣ and 2♠ shown\n"
|
|
" game.vars.foundations[1] = {C(1, \"C\", true)}\n"
|
|
" game.vars.foundations[2] = {C(1, \"S\", true), C(2, \"S\", true)}\n"
|
|
"\n"
|
|
" -- Tableau (approximate composition from reference image)\n"
|
|
" game.vars.tableau[1] = {C(13, \"D\", true)}\n"
|
|
" game.vars.tableau[2] = {C(8, \"C\", false), C(9, \"S\", true)}\n"
|
|
" game.vars.tableau[3] = {C(4, \"D\", false), C(6, \"C\", false), C(10, \"S\", true)}\n"
|
|
" game.vars.tableau[4] = {C(5, \"D\", false), C(2, \"C\", false), C(8, \"H\", false), C(3, \"H\", true)}\n"
|
|
" game.vars.tableau[5] = {C(10, \"D\", false), C(7, \"C\", false), C(4, \"S\", false), C(13, \"S\", true), C(12, \"D\", true), C(11, \"S\", true)}\n"
|
|
" game.vars.tableau[6] = {C(1, \"D\", false), C(12, \"C\", false), C(6, \"H\", false), C(5, \"S\", false), C(3, \"D\", true)}\n"
|
|
" game.vars.tableau[7] = {C(9, \"H\", false), C(11, \"C\", false), C(2, \"H\", false), C(6, \"D\", false), C(4, \"H\", false), C(13, \"H\", true)}\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function stock_rect()\n"
|
|
" return LEFT, TOP_Y, CARD_W, CARD_H\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function waste_rect()\n"
|
|
" return LEFT + CARD_W + GAP_X, TOP_Y, CARD_W, CARD_H\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function foundation_rect(i)\n"
|
|
" local x = LEFT + (CARD_W + GAP_X) * (2 + i)\n"
|
|
" return x, TOP_Y, CARD_W, CARD_H\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function tableau_x(col)\n"
|
|
" return LEFT + (col - 1) * (CARD_W + GAP_X)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function point_in(px, py, x, y, w, h)\n"
|
|
" return px >= x and px < x + w and py >= y and py < y + h\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function can_place_on_tableau(card, target)\n"
|
|
" if not target then\n"
|
|
" return card.r == 13 -- only King on empty column\n"
|
|
" end\n"
|
|
" if not target.up then return false end\n"
|
|
" local red1 = card_color_red(card)\n"
|
|
" local red2 = card_color_red(target)\n"
|
|
" return red1 ~= red2 and card.r + 1 == target.r\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function can_place_on_foundation(card, pile)\n"
|
|
" if #pile == 0 then\n"
|
|
" return card.r == 1\n"
|
|
" end\n"
|
|
" local top = pile[#pile]\n"
|
|
" return top.s == card.s and card.r == top.r + 1\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function reveal_tableau_top(col)\n"
|
|
" local t = game.vars.tableau[col]\n"
|
|
" if #t > 0 then\n"
|
|
" t[#t].up = true\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function take_from_tableau(col, start_idx)\n"
|
|
" local t = game.vars.tableau[col]\n"
|
|
" local moved = {}\n"
|
|
" for i = start_idx, #t do\n"
|
|
" moved[#moved + 1] = t[i]\n"
|
|
" end\n"
|
|
" for _ = #t, start_idx, -1 do\n"
|
|
" t[#t] = nil\n"
|
|
" end\n"
|
|
" return moved\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function append_cards(dst, cards)\n"
|
|
" for i = 1, #cards do\n"
|
|
" dst[#dst + 1] = cards[i]\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function clear_selection()\n"
|
|
" game.vars.selected = nil\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function select_from_waste()\n"
|
|
" if #game.vars.waste == 0 then return false end\n"
|
|
" game.vars.selected = {src = \"waste\"}\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function select_from_foundation(fi)\n"
|
|
" local pile = game.vars.foundations[fi]\n"
|
|
" if not pile or #pile == 0 then return false end\n"
|
|
" game.vars.selected = {src = \"foundation\", idx = fi}\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function select_from_tableau(col, idx)\n"
|
|
" local t = game.vars.tableau[col]\n"
|
|
" local c = t[idx]\n"
|
|
" if not c or not c.up then return false end\n"
|
|
"\n"
|
|
" -- Ensure selected run is valid descending alternating colors\n"
|
|
" for i = idx, #t - 1 do\n"
|
|
" local a = t[i]\n"
|
|
" local b = t[i + 1]\n"
|
|
" if card_color_red(a) == card_color_red(b) or a.r ~= b.r + 1 then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" game.vars.selected = {src = \"tableau\", col = col, idx = idx}\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function is_valid_tableau_start(col, idx)\n"
|
|
" local t = game.vars.tableau[col]\n"
|
|
" local c = t[idx]\n"
|
|
" if not c or not c.up then return false end\n"
|
|
"\n"
|
|
" for i = idx, #t - 1 do\n"
|
|
" local a = t[i]\n"
|
|
" local b = t[i + 1]\n"
|
|
" if card_color_red(a) == card_color_red(b) or a.r ~= b.r + 1 then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function next_tableau_selection(col, current_idx, clicked_idx)\n"
|
|
" local t = game.vars.tableau[col]\n"
|
|
" if #t == 0 then return nil end\n"
|
|
"\n"
|
|
" local candidates = {}\n"
|
|
" for i = 1, #t do\n"
|
|
" if is_valid_tableau_start(col, i) then\n"
|
|
" candidates[#candidates + 1] = i\n"
|
|
" end\n"
|
|
" end\n"
|
|
" if #candidates == 0 then return nil end\n"
|
|
"\n"
|
|
" -- Repeated taps on same column cycle through valid starts.\n"
|
|
" if current_idx then\n"
|
|
" for k = 1, #candidates do\n"
|
|
" if candidates[k] == current_idx then\n"
|
|
" local nk = (k % #candidates) + 1\n"
|
|
" return candidates[nk]\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- First tap: choose nearest valid start at/after click; otherwise wrap.\n"
|
|
" if clicked_idx then\n"
|
|
" for k = 1, #candidates do\n"
|
|
" if candidates[k] >= clicked_idx then\n"
|
|
" return candidates[k]\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" return candidates[1]\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function selected_first_card()\n"
|
|
" local s = game.vars.selected\n"
|
|
" if not s then return nil end\n"
|
|
" if s.src == \"waste\" then\n"
|
|
" return game.vars.waste[#game.vars.waste]\n"
|
|
" end\n"
|
|
" if s.src == \"tableau\" then\n"
|
|
" return game.vars.tableau[s.col][s.idx]\n"
|
|
" end\n"
|
|
" if s.src == \"foundation\" then\n"
|
|
" local pile = game.vars.foundations[s.idx]\n"
|
|
" if pile and #pile > 0 then return pile[#pile] end\n"
|
|
" end\n"
|
|
" return nil\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function try_move_to_foundation(fi)\n"
|
|
" local s = game.vars.selected\n"
|
|
" if not s then return false end\n"
|
|
" local c = selected_first_card()\n"
|
|
" if not c then return false end\n"
|
|
"\n"
|
|
" -- Only single-card moves to foundation\n"
|
|
" if s.src == \"tableau\" and s.idx ~= #game.vars.tableau[s.col] then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
" if s.src == \"foundation\" then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" local pile = game.vars.foundations[fi]\n"
|
|
" if not can_place_on_foundation(c, pile) then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" if s.src == \"waste\" then\n"
|
|
" game.vars.waste[#game.vars.waste] = nil\n"
|
|
" else\n"
|
|
" local t = game.vars.tableau[s.col]\n"
|
|
" t[#t] = nil\n"
|
|
" reveal_tableau_top(s.col)\n"
|
|
" end\n"
|
|
"\n"
|
|
" pile[#pile + 1] = c\n"
|
|
" clear_selection()\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function try_move_to_tableau(col)\n"
|
|
" local s = game.vars.selected\n"
|
|
" if not s then return false end\n"
|
|
" local c = selected_first_card()\n"
|
|
" if not c then return false end\n"
|
|
"\n"
|
|
" local target = nil\n"
|
|
" local tdst = game.vars.tableau[col]\n"
|
|
" if #tdst > 0 then target = tdst[#tdst] end\n"
|
|
" if not can_place_on_tableau(c, target) then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" if s.src == \"waste\" then\n"
|
|
" game.vars.waste[#game.vars.waste] = nil\n"
|
|
" tdst[#tdst + 1] = c\n"
|
|
" elseif s.src == \"foundation\" then\n"
|
|
" local p = game.vars.foundations[s.idx]\n"
|
|
" p[#p] = nil\n"
|
|
" tdst[#tdst + 1] = c\n"
|
|
" else\n"
|
|
" if s.col == col then return false end\n"
|
|
" local moved = take_from_tableau(s.col, s.idx)\n"
|
|
" append_cards(tdst, moved)\n"
|
|
" reveal_tableau_top(s.col)\n"
|
|
" end\n"
|
|
"\n"
|
|
" clear_selection()\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_card_outline(x, y)\n"
|
|
" renderer.line(x + 2, y, x + CARD_W - 3, y, true, 1)\n"
|
|
" renderer.line(x + 2, y + CARD_H - 1, x + CARD_W - 3, y + CARD_H - 1, true, 1)\n"
|
|
" renderer.line(x, y + 2, x, y + CARD_H - 3, true, 1)\n"
|
|
" renderer.line(x + CARD_W - 1, y + 2, x + CARD_W - 1, y + CARD_H - 3, true, 1)\n"
|
|
" renderer.pixel(x + 1, y + 1, true)\n"
|
|
" renderer.pixel(x + CARD_W - 2, y + 1, true)\n"
|
|
" renderer.pixel(x + 1, y + CARD_H - 2, true)\n"
|
|
" renderer.pixel(x + CARD_W - 2, y + CARD_H - 2, true)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_card_back(x, y)\n"
|
|
" -- Face-down cards are outline-only for the desired aesthetic.\n"
|
|
" draw_card_outline(x, y)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_card_face(c, x, y)\n"
|
|
" draw_card_outline(x, y)\n"
|
|
" local rank = rank_text(c.r)\n"
|
|
" local scale = 1\n"
|
|
" local rw, rh = label_metrics(rank, scale)\n"
|
|
" local icon_scale = 1\n"
|
|
" local icon_w = 7 * icon_scale\n"
|
|
" local icon_h = 7 * icon_scale\n"
|
|
" local gap = 1\n"
|
|
" local lw = rw + gap + icon_w\n"
|
|
" local lh = math.max(rh, icon_h)\n"
|
|
" local pad_x = 3\n"
|
|
" local pad_y = 1\n"
|
|
"\n"
|
|
" local top_text_y = y - math.floor(lh / 2)\n"
|
|
" local left_inset = 4\n"
|
|
" renderer.rect(x + left_inset, top_text_y - pad_y, lw + pad_x, lh + pad_y + 1, false, true)\n"
|
|
" renderer.text_scaled(x + left_inset + 1, top_text_y + math.floor((lh - rh) / 2), rank, true, scale)\n"
|
|
" draw_suit_icon(x + left_inset + 1 + rw + gap, top_text_y + math.floor((lh - icon_h) / 2), c.s, icon_scale)\n"
|
|
"\n"
|
|
" local bot_text_y = (y + CARD_H - 1) - math.floor(lh / 2)\n"
|
|
" local right_inset = 4\n"
|
|
" local bx = x + CARD_W - lw - pad_x - right_inset\n"
|
|
" renderer.rect(bx, bot_text_y - pad_y, lw + pad_x, lh + pad_y + 1, false, true)\n"
|
|
" renderer.text_scaled(bx + 1, bot_text_y + math.floor((lh - rh) / 2), rank, true, scale)\n"
|
|
" draw_suit_icon(bx + 1 + rw + gap, bot_text_y + math.floor((lh - icon_h) / 2), c.s, icon_scale)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_card(c, x, y)\n"
|
|
" if c.up then\n"
|
|
" draw_card_face(c, x, y)\n"
|
|
" else\n"
|
|
" draw_card_back(x, y)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_selected_highlight(x, y)\n"
|
|
" renderer.rect(x - 1, y - 1, CARD_W + 2, CARD_H + 2, true, false)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_terminal_chrome()\n"
|
|
" -- Outer frame\n"
|
|
" renderer.rect(1, 1, game.width() - 2, game.height() - 2, true, false)\n"
|
|
" -- Title bar\n"
|
|
" renderer.rect(2, 2, game.width() - 4, 16, true, false)\n"
|
|
" renderer.text(math.floor(game.width() / 2) - 27, 6, \"Solitaire\", true)\n"
|
|
"\n"
|
|
" -- Window controls (monochrome circles)\n"
|
|
" renderer.circle(10, 10, 3, true, false)\n"
|
|
" renderer.circle(20, 10, 3, true, false)\n"
|
|
" renderer.circle(30, 10, 3, true, false)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_top_row()\n"
|
|
" -- Stock\n"
|
|
" local sx, sy = stock_rect()\n"
|
|
" if #game.vars.stock > 0 then\n"
|
|
" draw_card_back(sx, sy)\n"
|
|
" else\n"
|
|
" draw_card_outline(sx, sy)\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- Waste\n"
|
|
" local wx, wy = waste_rect()\n"
|
|
" local wc = game.vars.waste[#game.vars.waste]\n"
|
|
" if wc then draw_card_face(wc, wx, wy) else draw_card_outline(wx, wy) end\n"
|
|
"\n"
|
|
" -- Foundations\n"
|
|
" for i = 1, 4 do\n"
|
|
" local fx, fy = foundation_rect(i)\n"
|
|
" local pile = game.vars.foundations[i]\n"
|
|
" local c = pile[#pile]\n"
|
|
" if c then\n"
|
|
" draw_card_face(c, fx, fy)\n"
|
|
" else\n"
|
|
" draw_card_outline(fx, fy)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_tableau()\n"
|
|
" for col = 1, 7 do\n"
|
|
" local x = tableau_x(col)\n"
|
|
" local t = game.vars.tableau[col]\n"
|
|
"\n"
|
|
" if #t == 0 then\n"
|
|
" draw_card_outline(x, TAB_Y)\n"
|
|
" else\n"
|
|
" for i = 1, #t do\n"
|
|
" local y = TAB_Y + (i - 1) * FAN_DOWN\n"
|
|
" local c = t[i]\n"
|
|
" draw_card(c, x, y)\n"
|
|
"\n"
|
|
" -- Make stacked cards opaque; only top card is fully visible.\n"
|
|
" if i < #t then\n"
|
|
" local next_y = TAB_Y + i * FAN_DOWN\n"
|
|
" local cut_h = (y + CARD_H) - next_y\n"
|
|
" if cut_h > 0 then\n"
|
|
" renderer.rect(x - 2, next_y - 5, CARD_W + 4, cut_h + 10, false, true)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_selection()\n"
|
|
" local s = game.vars.selected\n"
|
|
" if not s then return end\n"
|
|
"\n"
|
|
" if s.src == \"waste\" then\n"
|
|
" local x, y = waste_rect()\n"
|
|
" draw_selected_highlight(x, y)\n"
|
|
" elseif s.src == \"foundation\" then\n"
|
|
" local x, y = foundation_rect(s.idx)\n"
|
|
" draw_selected_highlight(x, y)\n"
|
|
" elseif s.src == \"tableau\" then\n"
|
|
" local x = tableau_x(s.col)\n"
|
|
" local y = TAB_Y + (s.idx - 1) * FAN_DOWN\n"
|
|
" draw_selected_highlight(x, y)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function can_selected_move_to_foundation(fi)\n"
|
|
" local s = game.vars.selected\n"
|
|
" if not s then return false end\n"
|
|
" if s.src == \"foundation\" then return false end\n"
|
|
"\n"
|
|
" local c = selected_first_card()\n"
|
|
" if not c then return false end\n"
|
|
"\n"
|
|
" -- Only a single top tableau card or waste card can go to foundation.\n"
|
|
" if s.src == \"tableau\" and s.idx ~= #game.vars.tableau[s.col] then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" return can_place_on_foundation(c, game.vars.foundations[fi])\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function can_selected_move_to_tableau(col)\n"
|
|
" local s = game.vars.selected\n"
|
|
" if not s then return false end\n"
|
|
"\n"
|
|
" local c = selected_first_card()\n"
|
|
" if not c then return false end\n"
|
|
"\n"
|
|
" if s.src == \"tableau\" and s.col == col then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" local tdst = game.vars.tableau[col]\n"
|
|
" local target = nil\n"
|
|
" if #tdst > 0 then target = tdst[#tdst] end\n"
|
|
" return can_place_on_tableau(c, target)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_down_arrow(cx, y)\n"
|
|
" renderer.line(cx - 3, y, cx, y + 4, true, 1)\n"
|
|
" renderer.line(cx + 3, y, cx, y + 4, true, 1)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_up_arrow(cx, y)\n"
|
|
" renderer.line(cx - 3, y + 4, cx, y, true, 1)\n"
|
|
" renderer.line(cx + 3, y + 4, cx, y, true, 1)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function draw_move_hints()\n"
|
|
" if not game.vars.selected then return end\n"
|
|
"\n"
|
|
" -- Tableau destinations: down arrows above each target stack.\n"
|
|
" for col = 1, 7 do\n"
|
|
" if can_selected_move_to_tableau(col) then\n"
|
|
" local x = tableau_x(col)\n"
|
|
" draw_down_arrow(x + math.floor(CARD_W / 2), TAB_Y - 7)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- Foundation destinations: up arrows under each target foundation.\n"
|
|
" for i = 1, 4 do\n"
|
|
" if can_selected_move_to_foundation(i) then\n"
|
|
" local fx, fy = foundation_rect(i)\n"
|
|
" draw_up_arrow(fx + math.floor(CARD_W / 2), fy + CARD_H + 2)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function handle_touch(x, y)\n"
|
|
" -- Stock click: draw from stock or recycle waste\n"
|
|
" do\n"
|
|
" local sx, sy, sw, sh = stock_rect()\n"
|
|
" if point_in(x, y, sx, sy, sw, sh) then\n"
|
|
" clear_selection()\n"
|
|
" if #game.vars.stock > 0 then\n"
|
|
" local c = game.vars.stock[#game.vars.stock]\n"
|
|
" game.vars.stock[#game.vars.stock] = nil\n"
|
|
" c.up = true\n"
|
|
" game.vars.waste[#game.vars.waste + 1] = c\n"
|
|
" else\n"
|
|
" while #game.vars.waste > 0 do\n"
|
|
" local c = game.vars.waste[#game.vars.waste]\n"
|
|
" game.vars.waste[#game.vars.waste] = nil\n"
|
|
" c.up = false\n"
|
|
" game.vars.stock[#game.vars.stock + 1] = c\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- Foundation click: try move selected there, else select nothing\n"
|
|
" for i = 1, 4 do\n"
|
|
" local fx, fy, fw, fh = foundation_rect(i)\n"
|
|
" if point_in(x, y, fx, fy, fw, fh) then\n"
|
|
" if game.vars.selected then\n"
|
|
" if try_move_to_foundation(i) then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" if game.vars.selected.src == \"foundation\" and game.vars.selected.idx == i then\n"
|
|
" clear_selection()\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" clear_selection()\n"
|
|
" return true\n"
|
|
" end\n"
|
|
"\n"
|
|
" if select_from_foundation(i) then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
"\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- Waste click: select waste top\n"
|
|
" do\n"
|
|
" local wx, wy, ww, wh = waste_rect()\n"
|
|
" if point_in(x, y, wx, wy, ww, wh) then\n"
|
|
" if game.vars.selected and game.vars.selected.src == \"waste\" then\n"
|
|
" clear_selection()\n"
|
|
" else\n"
|
|
" select_from_waste()\n"
|
|
" end\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- Tableau click: move selected onto column or select card/run\n"
|
|
" for col = 1, 7 do\n"
|
|
" local x0 = tableau_x(col)\n"
|
|
" local t = game.vars.tableau[col]\n"
|
|
"\n"
|
|
" local hit = false\n"
|
|
" local idx = nil\n"
|
|
"\n"
|
|
" if #t == 0 then\n"
|
|
" if point_in(x, y, x0, TAB_Y, CARD_W, CARD_H) then\n"
|
|
" hit = true\n"
|
|
" end\n"
|
|
" else\n"
|
|
" -- Expanded hit area by fan depth, with index based on Y\n"
|
|
" local y0 = TAB_Y\n"
|
|
" local y1 = TAB_Y + (#t - 1) * FAN_DOWN + CARD_H\n"
|
|
" if point_in(x, y, x0, y0, CARD_W, y1 - y0) then\n"
|
|
" hit = true\n"
|
|
" idx = math.floor((y - TAB_Y) / FAN_DOWN) + 1\n"
|
|
" if idx < 1 then idx = 1 end\n"
|
|
" if idx > #t then idx = #t end\n"
|
|
" -- If clicked lower covered part, snap to nearest visible face-up boundary\n"
|
|
" while idx < #t and (TAB_Y + idx * FAN_DOWN) > y do\n"
|
|
" idx = idx - 1\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" if hit then\n"
|
|
" if game.vars.selected then\n"
|
|
" if try_move_to_tableau(col) then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" if #t > 0 then\n"
|
|
" if game.vars.selected and game.vars.selected.src == \"tableau\" and game.vars.selected.col == col then\n"
|
|
" local next_idx = next_tableau_selection(col, game.vars.selected.idx, idx)\n"
|
|
" if next_idx then\n"
|
|
" game.vars.selected = {src = \"tableau\", col = col, idx = next_idx}\n"
|
|
" else\n"
|
|
" clear_selection()\n"
|
|
" end\n"
|
|
" elseif t[idx] and t[idx].up then\n"
|
|
" if not select_from_tableau(col, idx) then\n"
|
|
" local next_idx = next_tableau_selection(col, nil, idx)\n"
|
|
" if next_idx then\n"
|
|
" game.vars.selected = {src = \"tableau\", col = col, idx = next_idx}\n"
|
|
" else\n"
|
|
" clear_selection()\n"
|
|
" end\n"
|
|
" end\n"
|
|
" else\n"
|
|
" clear_selection()\n"
|
|
" end\n"
|
|
" else\n"
|
|
" clear_selection()\n"
|
|
" end\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" clear_selection()\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function check_win()\n"
|
|
" for i = 1, 4 do\n"
|
|
" if #game.vars.foundations[i] ~= 13 then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function card_can_move_to_any_foundation(card)\n"
|
|
" for i = 1, 4 do\n"
|
|
" if can_place_on_foundation(card, game.vars.foundations[i]) then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function card_can_move_to_any_tableau(card, src_col)\n"
|
|
" for col = 1, 7 do\n"
|
|
" if not src_col or col ~= src_col then\n"
|
|
" local t = game.vars.tableau[col]\n"
|
|
" local target = nil\n"
|
|
" if #t > 0 then target = t[#t] end\n"
|
|
" if can_place_on_tableau(card, target) then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function has_progress_move()\n"
|
|
" -- Any legal move from tableau runs/tops?\n"
|
|
" for col = 1, 7 do\n"
|
|
" local t = game.vars.tableau[col]\n"
|
|
" for idx = 1, #t do\n"
|
|
" if is_valid_tableau_start(col, idx) then\n"
|
|
" local c = t[idx]\n"
|
|
" if card_can_move_to_any_tableau(c, col) then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" if idx == #t and card_can_move_to_any_foundation(c) then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- Any foundation top can move to tableau?\n"
|
|
" for i = 1, 4 do\n"
|
|
" local p = game.vars.foundations[i]\n"
|
|
" if #p > 0 and card_can_move_to_any_tableau(p[#p], nil) then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- Any stock or waste card can eventually be played (draw-1 with recycle)?\n"
|
|
" for i = 1, #game.vars.waste do\n"
|
|
" local c = game.vars.waste[i]\n"
|
|
" if card_can_move_to_any_foundation(c) or card_can_move_to_any_tableau(c, nil) then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" for i = 1, #game.vars.stock do\n"
|
|
" local c = game.vars.stock[i]\n"
|
|
" if card_can_move_to_any_foundation(c) or card_can_move_to_any_tableau(c, nil) then\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function recompute_end_state()\n"
|
|
" game.vars.won = check_win()\n"
|
|
" game.vars.lost = (not game.vars.won) and (not has_progress_move())\n"
|
|
"end\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" setup_game()\n"
|
|
" game.vars.won = false\n"
|
|
" game.vars.lost = false\n"
|
|
" recompute_end_state()\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" if game.vars.won then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 then\n"
|
|
" setup_game()\n"
|
|
" game.vars.won = false\n"
|
|
" game.vars.lost = false\n"
|
|
" recompute_end_state()\n"
|
|
" return true\n"
|
|
" elseif event.type == INPUT.BUTTON_1 then\n"
|
|
" game.exit()\n"
|
|
" return false\n"
|
|
" end\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" if game.vars.lost then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 then\n"
|
|
" setup_game()\n"
|
|
" game.vars.won = false\n"
|
|
" game.vars.lost = false\n"
|
|
" recompute_end_state()\n"
|
|
" return true\n"
|
|
" elseif event.type == INPUT.BUTTON_1 then\n"
|
|
" game.exit()\n"
|
|
" return false\n"
|
|
" end\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" if event.type == INPUT.TOUCH_DOWN then\n"
|
|
" local changed = handle_touch(event.x, event.y)\n"
|
|
" recompute_end_state()\n"
|
|
" return changed\n"
|
|
" end\n"
|
|
"\n"
|
|
" if event.type == INPUT.BUTTON_0 then\n"
|
|
" setup_game()\n"
|
|
" game.vars.won = false\n"
|
|
" game.vars.lost = false\n"
|
|
" recompute_end_state()\n"
|
|
" return true\n"
|
|
" end\n"
|
|
"\n"
|
|
" if event.type == INPUT.BUTTON_1 then\n"
|
|
" game.exit()\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false)\n"
|
|
"\n"
|
|
" draw_terminal_chrome()\n"
|
|
" draw_top_row()\n"
|
|
" draw_tableau()\n"
|
|
" draw_move_hints()\n"
|
|
" draw_selection()\n"
|
|
"\n"
|
|
" if game.vars.won then\n"
|
|
" renderer.rect(75, 120, 250, 52, true, false)\n"
|
|
" renderer.text(160, 136, \"YOU WIN\", true)\n"
|
|
" renderer.text(112, 152, \"Tap: new B1:exit\", true)\n"
|
|
" elseif game.vars.lost then\n"
|
|
" renderer.rect(60, 120, 280, 52, true, false)\n"
|
|
" renderer.text(150, 136, \"NO MOVES\", true)\n"
|
|
" renderer.text(102, 152, \"Tap/B0: new B1:exit\", true)\n"
|
|
" else\n"
|
|
" renderer.text(8, game.height() - 12, \"B0:new B1:exit\", true)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char tetris_lua_src[] = "-- NAME: Tetris\n"
|
|
"-- DESC: Stack falling blocks, clear lines\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_GAME_OVER = 2\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local GRID_WIDTH = 10\n"
|
|
"local GRID_HEIGHT = 20\n"
|
|
"local CELL_SIZE = 8\n"
|
|
"local SPAWN_RATE = 30 -- Frames before piece drops\n"
|
|
"\n"
|
|
"-- Tetromino shapes (4 orientations each)\n"
|
|
"local TETROMINOS = {\n"
|
|
" -- I piece\n"
|
|
" {\n"
|
|
" {{0, 0}, {1, 0}, {2, 0}, {3, 0}},\n"
|
|
" {{0, 0}, {0, 1}, {0, 2}, {0, 3}},\n"
|
|
" {{0, 0}, {1, 0}, {2, 0}, {3, 0}},\n"
|
|
" {{0, 0}, {0, 1}, {0, 2}, {0, 3}}\n"
|
|
" },\n"
|
|
" -- O piece\n"
|
|
" {\n"
|
|
" {{0, 0}, {1, 0}, {0, 1}, {1, 1}},\n"
|
|
" {{0, 0}, {1, 0}, {0, 1}, {1, 1}},\n"
|
|
" {{0, 0}, {1, 0}, {0, 1}, {1, 1}},\n"
|
|
" {{0, 0}, {1, 0}, {0, 1}, {1, 1}}\n"
|
|
" },\n"
|
|
" -- T piece\n"
|
|
" {\n"
|
|
" {{1, 0}, {0, 1}, {1, 1}, {2, 1}},\n"
|
|
" {{1, 0}, {0, 1}, {1, 1}, {1, 2}},\n"
|
|
" {{0, 1}, {1, 1}, {2, 1}, {1, 2}},\n"
|
|
" {{1, 0}, {1, 1}, {2, 1}, {1, 2}}\n"
|
|
" },\n"
|
|
" -- S piece\n"
|
|
" {\n"
|
|
" {{1, 0}, {2, 0}, {0, 1}, {1, 1}},\n"
|
|
" {{0, 0}, {0, 1}, {1, 1}, {1, 2}},\n"
|
|
" {{1, 0}, {2, 0}, {0, 1}, {1, 1}},\n"
|
|
" {{0, 0}, {0, 1}, {1, 1}, {1, 2}}\n"
|
|
" },\n"
|
|
" -- Z piece\n"
|
|
" {\n"
|
|
" {{0, 0}, {1, 0}, {1, 1}, {2, 1}},\n"
|
|
" {{1, 0}, {0, 1}, {1, 1}, {0, 2}},\n"
|
|
" {{0, 0}, {1, 0}, {1, 1}, {2, 1}},\n"
|
|
" {{1, 0}, {0, 1}, {1, 1}, {0, 2}}\n"
|
|
" },\n"
|
|
" -- J piece\n"
|
|
" {\n"
|
|
" {{0, 0}, {0, 1}, {1, 1}, {2, 1}},\n"
|
|
" {{1, 0}, {2, 0}, {1, 1}, {1, 2}},\n"
|
|
" {{0, 1}, {1, 1}, {2, 1}, {2, 0}},\n"
|
|
" {{1, 0}, {1, 1}, {0, 2}, {1, 2}}\n"
|
|
" },\n"
|
|
" -- L piece\n"
|
|
" {\n"
|
|
" {{2, 0}, {0, 1}, {1, 1}, {2, 1}},\n"
|
|
" {{1, 0}, {1, 1}, {1, 2}, {2, 2}},\n"
|
|
" {{0, 1}, {1, 1}, {2, 1}, {0, 0}},\n"
|
|
" {{0, 0}, {1, 0}, {1, 1}, {1, 2}}\n"
|
|
" }\n"
|
|
"}\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.level = 1\n"
|
|
" game.vars.lines = 0\n"
|
|
" \n"
|
|
" -- Grid (0 = empty, 1 = filled)\n"
|
|
" game.vars.grid = {}\n"
|
|
" for y = 1, GRID_HEIGHT do\n"
|
|
" game.vars.grid[y] = {}\n"
|
|
" for x = 1, GRID_WIDTH do\n"
|
|
" game.vars.grid[y][x] = 0\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Current piece\n"
|
|
" game.vars.piece = nil\n"
|
|
" game.vars.piece_x = 0\n"
|
|
" game.vars.piece_y = 0\n"
|
|
" game.vars.piece_type = 0\n"
|
|
" game.vars.piece_rotation = 0\n"
|
|
" \n"
|
|
" -- Animation\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" game.vars.clear_rows = {}\n"
|
|
" game.vars.clearing = false\n"
|
|
" game.vars.score_submit_pending = false\n"
|
|
" game.vars.score_submitted = 0\n"
|
|
" \n"
|
|
" -- Enable continuous updates\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Tetris initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function set_game_over()\n"
|
|
" if game.vars.state ~= STATE_GAME_OVER then\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" game.vars.score_submitted = game.vars.score\n"
|
|
" game.vars.score_submit_pending = true\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" reset_game()\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" -- Handle input\n"
|
|
" if event.type == INPUT.TOUCH_DOWN then\n"
|
|
" if event.x < game.width() / 2 then\n"
|
|
" -- Left side: move left\n"
|
|
" if can_move(game.vars.piece, game.vars.piece_x - 1, game.vars.piece_y, game.vars.piece_rotation) then\n"
|
|
" game.vars.piece_x = game.vars.piece_x - 1\n"
|
|
" end\n"
|
|
" else\n"
|
|
" -- Right side: move right\n"
|
|
" if can_move(game.vars.piece, game.vars.piece_x + 1, game.vars.piece_y, game.vars.piece_rotation) then\n"
|
|
" game.vars.piece_x = game.vars.piece_x + 1\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Update physics on frame tick\n"
|
|
" if event.type == INPUT.FRAME_TICK then\n"
|
|
" game.vars.frame_count = game.vars.frame_count + 1\n"
|
|
" \n"
|
|
" if game.vars.clearing then\n"
|
|
" -- Clear animation\n"
|
|
" if game.vars.frame_count % 10 == 0 then\n"
|
|
" clear_lines()\n"
|
|
" game.vars.clearing = false\n"
|
|
" end\n"
|
|
" else\n"
|
|
" -- Drop piece\n"
|
|
" if game.vars.frame_count >= SPAWN_RATE then\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" \n"
|
|
" if can_move(game.vars.piece, game.vars.piece_x, game.vars.piece_y + 1, game.vars.piece_rotation) then\n"
|
|
" game.vars.piece_y = game.vars.piece_y + 1\n"
|
|
" else\n"
|
|
" -- Lock piece\n"
|
|
" lock_piece()\n"
|
|
" \n"
|
|
" -- Check for complete lines\n"
|
|
" local complete = check_complete_lines()\n"
|
|
" if #complete > 0 then\n"
|
|
" game.vars.clear_rows = complete\n"
|
|
" game.vars.clearing = true\n"
|
|
" else\n"
|
|
" spawn_piece()\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false) -- Black background\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 20, game.height() / 2 - 30, \"TETRIS\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, \"Tap to Start\", true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING or state == STATE_GAME_OVER then\n"
|
|
" -- Draw grid\n"
|
|
" local start_x = 20\n"
|
|
" local start_y = 15\n"
|
|
" \n"
|
|
" for y = 1, GRID_HEIGHT do\n"
|
|
" for x = 1, GRID_WIDTH do\n"
|
|
" if game.vars.grid[y][x] == 1 then\n"
|
|
" renderer.rect(start_x + (x - 1) * CELL_SIZE, start_y + (y - 1) * CELL_SIZE, CELL_SIZE, CELL_SIZE, true, true)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw current piece\n"
|
|
" if game.vars.piece then\n"
|
|
" for _, block in ipairs(game.vars.piece) do\n"
|
|
" local x = start_x + (game.vars.piece_x + block[1]) * CELL_SIZE\n"
|
|
" local y = start_y + (game.vars.piece_y + block[2]) * CELL_SIZE\n"
|
|
" renderer.rect(x, y, CELL_SIZE, CELL_SIZE, true, true)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Draw score\n"
|
|
" renderer.text_scaled(game.width() - 50, 10, \"Score: \" .. tostring(game.vars.score), true, 2)\n"
|
|
" renderer.text_scaled(game.width() - 50, 20, \"Lines: \" .. tostring(game.vars.lines), true, 2)\n"
|
|
" \n"
|
|
" if state == STATE_GAME_OVER then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 40, game.height() / 2, \"GAME OVER\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 + 20, \"Tap to Menu\", true, 2)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function spawn_piece()\n"
|
|
" game.vars.piece_type = math.random(1, #TETROMINOS)\n"
|
|
" game.vars.piece_rotation = 1\n"
|
|
" game.vars.piece = TETROMINOS[game.vars.piece_type][game.vars.piece_rotation]\n"
|
|
" game.vars.piece_x = 3\n"
|
|
" game.vars.piece_y = 0\n"
|
|
" \n"
|
|
" -- Check if game over\n"
|
|
" if not can_move(game.vars.piece, game.vars.piece_x, game.vars.piece_y, game.vars.piece_rotation) then\n"
|
|
" set_game_over()\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function lock_piece()\n"
|
|
" if not game.vars.piece then return end\n"
|
|
" \n"
|
|
" for _, block in ipairs(game.vars.piece) do\n"
|
|
" local x = game.vars.piece_x + block[1] + 1\n"
|
|
" local y = game.vars.piece_y + block[2] + 1\n"
|
|
" \n"
|
|
" if y >= 1 and y <= GRID_HEIGHT and x >= 1 and x <= GRID_WIDTH then\n"
|
|
" game.vars.grid[y][x] = 1\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function can_move(piece, x, y, rotation)\n"
|
|
" if not piece then return false end\n"
|
|
" \n"
|
|
" for _, block in ipairs(piece) do\n"
|
|
" local grid_x = x + block[1] + 1\n"
|
|
" local grid_y = y + block[2] + 1\n"
|
|
" \n"
|
|
" if grid_x < 1 or grid_x > GRID_WIDTH or grid_y < 1 or grid_y > GRID_HEIGHT then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
" \n"
|
|
" if game.vars.grid[grid_y][grid_x] == 1 then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"function check_complete_lines()\n"
|
|
" local complete = {}\n"
|
|
" \n"
|
|
" for y = 1, GRID_HEIGHT do\n"
|
|
" local full = true\n"
|
|
" for x = 1, GRID_WIDTH do\n"
|
|
" if game.vars.grid[y][x] == 0 then\n"
|
|
" full = false\n"
|
|
" break\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" if full then\n"
|
|
" table.insert(complete, y)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return complete\n"
|
|
"end\n"
|
|
"\n"
|
|
"function clear_lines()\n"
|
|
" for _, y in ipairs(game.vars.clear_rows) do\n"
|
|
" -- Remove line\n"
|
|
" table.remove(game.vars.grid, y)\n"
|
|
" -- Add empty line at top\n"
|
|
" table.insert(game.vars.grid, 1, {})\n"
|
|
" for x = 1, GRID_WIDTH do\n"
|
|
" game.vars.grid[1][x] = 0\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" game.vars.score = game.vars.score + (#game.vars.clear_rows * 100)\n"
|
|
" game.vars.lines = game.vars.lines + #game.vars.clear_rows\n"
|
|
" game.vars.clear_rows = {}\n"
|
|
" \n"
|
|
" spawn_piece()\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_game()\n"
|
|
" game.vars.score = 0\n"
|
|
" game.vars.level = 1\n"
|
|
" game.vars.lines = 0\n"
|
|
" game.vars.frame_count = 0\n"
|
|
" game.vars.clearing = false\n"
|
|
" game.vars.score_submit_pending = false\n"
|
|
" \n"
|
|
" -- Clear grid\n"
|
|
" for y = 1, GRID_HEIGHT do\n"
|
|
" for x = 1, GRID_WIDTH do\n"
|
|
" game.vars.grid[y][x] = 0\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" spawn_piece()\n"
|
|
"end\n"
|
|
"";
|
|
|
|
static const char tic_tac_toe_lua_src[] = "-- NAME: Tic-Tac-Toe\n"
|
|
"-- DESC: Play vs AI opponent\n"
|
|
"\n"
|
|
"-- Game states\n"
|
|
"local STATE_MENU = 0\n"
|
|
"local STATE_PLAYING = 1\n"
|
|
"local STATE_GAME_OVER = 2\n"
|
|
"\n"
|
|
"-- Game constants\n"
|
|
"local GRID_SIZE = 3\n"
|
|
"\n"
|
|
"-- Calculate cell size based on screen size\n"
|
|
"local function get_cell_size()\n"
|
|
" -- Use smallest dimension to maintain square grid\n"
|
|
" local min_dim = math.min(game.width(), game.height())\n"
|
|
" local padding = math.floor(min_dim / 8)\n"
|
|
" local available = min_dim - (padding * 2)\n"
|
|
" local cell_size = math.floor(available / GRID_SIZE)\n"
|
|
" return cell_size\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function get_grid_start_y()\n"
|
|
" -- Center grid vertically\n"
|
|
" local cell_size = get_cell_size()\n"
|
|
" local grid_height = cell_size * GRID_SIZE\n"
|
|
" return math.floor((game.height() - grid_height) / 2)\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function get_grid_start_x()\n"
|
|
" -- Center grid horizontally\n"
|
|
" local cell_size = get_cell_size()\n"
|
|
" local grid_width = cell_size * GRID_SIZE\n"
|
|
" return math.floor((game.width() - grid_width) / 2)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function init()\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" game.vars.grid = {}\n"
|
|
" game.vars.player = 1 -- 1 = X, 2 = O\n"
|
|
" game.vars.ai = 2\n"
|
|
" game.vars.game_over = false\n"
|
|
" game.vars.winner = 0 -- 0 = ongoing, 1 = player wins, 2 = ai wins, 3 = draw\n"
|
|
" \n"
|
|
" -- Enable continuous updates\n"
|
|
" game.set_frame_updates(true)\n"
|
|
" \n"
|
|
" print(\"Tic-Tac-Toe initialized\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function update(event)\n"
|
|
" local state = game.vars.state\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" reset_game()\n"
|
|
" game.vars.state = STATE_PLAYING\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN then\n"
|
|
" local cell = get_cell_at(event.x, event.y)\n"
|
|
" if cell and game.vars.grid[cell] == 0 then\n"
|
|
" -- Player move\n"
|
|
" game.vars.grid[cell] = game.vars.player\n"
|
|
" \n"
|
|
" -- Check win\n"
|
|
" local winner = check_winner()\n"
|
|
" if winner ~= 0 then\n"
|
|
" if winner == game.vars.player then\n"
|
|
" game.vars.winner = 1\n"
|
|
" else\n"
|
|
" game.vars.winner = 2\n"
|
|
" end\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Check draw\n"
|
|
" if is_board_full() then\n"
|
|
" game.vars.winner = 3\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- AI move\n"
|
|
" local ai_move = find_best_move()\n"
|
|
" game.vars.grid[ai_move] = game.vars.ai\n"
|
|
" \n"
|
|
" -- Check win\n"
|
|
" winner = check_winner()\n"
|
|
" if winner ~= 0 then\n"
|
|
" if winner == game.vars.ai then\n"
|
|
" game.vars.winner = 2\n"
|
|
" end\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Check draw\n"
|
|
" if is_board_full() then\n"
|
|
" game.vars.winner = 3\n"
|
|
" game.vars.state = STATE_GAME_OVER\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" \n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" elseif state == STATE_GAME_OVER then\n"
|
|
" if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then\n"
|
|
" game.vars.state = STATE_MENU\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function draw()\n"
|
|
" renderer.clear(false)\n"
|
|
" \n"
|
|
" local state = game.vars.state\n"
|
|
" local cell_size = get_cell_size()\n"
|
|
" local start_x = get_grid_start_x()\n"
|
|
" local start_y = get_grid_start_y()\n"
|
|
" local cell_spacing = 2\n"
|
|
" \n"
|
|
" if state == STATE_MENU then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 - 30, \"TIC-TAC-TOE\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, \"Play vs AI\", true, 2)\n"
|
|
" renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 + 20, \"Tap to Start\", true, 2)\n"
|
|
" \n"
|
|
" elseif state == STATE_PLAYING or state == STATE_GAME_OVER then\n"
|
|
" -- Draw grid\n"
|
|
" for row = 0, GRID_SIZE - 1 do\n"
|
|
" for col = 0, GRID_SIZE - 1 do\n"
|
|
" local cell_idx = row * GRID_SIZE + col + 1\n"
|
|
" local cell_x = start_x + col * (cell_size + cell_spacing)\n"
|
|
" local cell_y = start_y + row * (cell_size + cell_spacing)\n"
|
|
" \n"
|
|
" -- Draw cell\n"
|
|
" renderer.rect(cell_x, cell_y, cell_size, cell_size, true, false)\n"
|
|
" \n"
|
|
" -- Draw X or O\n"
|
|
" local value = game.vars.grid[cell_idx]\n"
|
|
" if value == 1 then\n"
|
|
" -- Draw X\n"
|
|
" renderer.line(cell_x + 2, cell_y + 2, cell_x + cell_size - 2, cell_y + cell_size - 2, true, 1)\n"
|
|
" renderer.line(cell_x + cell_size - 2, cell_y + 2, cell_x + 2, cell_y + cell_size - 2, true, 1)\n"
|
|
" elseif value == 2 then\n"
|
|
" -- Draw O (convert center to integers)\n"
|
|
" renderer.circle(math.floor(cell_x + cell_size / 2 + 0.5), math.floor(cell_y + cell_size / 2 + 0.5), math.floor(cell_size / 2 - 2 + 0.5), true, false)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" if state == STATE_GAME_OVER then\n"
|
|
" if game.vars.winner == 1 then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, 10, \"YOU WIN!\", true, 2)\n"
|
|
" elseif game.vars.winner == 2 then\n"
|
|
" renderer.text_scaled(game.width() / 2 - 30, 10, \"AI WINS!\", true, 2)\n"
|
|
" else\n"
|
|
" renderer.text_scaled(game.width() / 2 - 20, 10, \"DRAW!\", true, 2)\n"
|
|
" end\n"
|
|
" \n"
|
|
" renderer.text_scaled(game.width() / 2 - 60, game.height() - 15, \"Tap to Menu\", true, 2)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function get_cell_at(x, y)\n"
|
|
" local cell_size = get_cell_size()\n"
|
|
" local start_x = get_grid_start_x()\n"
|
|
" local start_y = get_grid_start_y()\n"
|
|
" local cell_spacing = 2\n"
|
|
" \n"
|
|
" for row = 0, GRID_SIZE - 1 do\n"
|
|
" for col = 0, GRID_SIZE - 1 do\n"
|
|
" local cell_x = start_x + col * (cell_size + cell_spacing)\n"
|
|
" local cell_y = start_y + row * (cell_size + cell_spacing)\n"
|
|
" \n"
|
|
" if x >= cell_x and x < cell_x + cell_size and\n"
|
|
" y >= cell_y and y < cell_y + cell_size then\n"
|
|
" return row * GRID_SIZE + col + 1\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return nil\n"
|
|
"end\n"
|
|
"\n"
|
|
"function check_winner()\n"
|
|
" -- Check rows\n"
|
|
" for row = 0, GRID_SIZE - 1 do\n"
|
|
" local val = game.vars.grid[row * GRID_SIZE + 1]\n"
|
|
" if val ~= 0 then\n"
|
|
" local match = true\n"
|
|
" for col = 1, GRID_SIZE - 1 do\n"
|
|
" if game.vars.grid[row * GRID_SIZE + col + 1] ~= val then\n"
|
|
" match = false\n"
|
|
" break\n"
|
|
" end\n"
|
|
" end\n"
|
|
" if match then return val end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Check columns\n"
|
|
" for col = 0, GRID_SIZE - 1 do\n"
|
|
" local val = game.vars.grid[col + 1]\n"
|
|
" if val ~= 0 then\n"
|
|
" local match = true\n"
|
|
" for row = 1, GRID_SIZE - 1 do\n"
|
|
" if game.vars.grid[row * GRID_SIZE + col + 1] ~= val then\n"
|
|
" match = false\n"
|
|
" break\n"
|
|
" end\n"
|
|
" end\n"
|
|
" if match then return val end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" -- Check diagonals\n"
|
|
" local val = game.vars.grid[1]\n"
|
|
" if val ~= 0 then\n"
|
|
" if game.vars.grid[5] == val and game.vars.grid[9] == val then\n"
|
|
" return val\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" val = game.vars.grid[3]\n"
|
|
" if val ~= 0 then\n"
|
|
" if game.vars.grid[5] == val and game.vars.grid[7] == val then\n"
|
|
" return val\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return 0\n"
|
|
"end\n"
|
|
"\n"
|
|
"function is_board_full()\n"
|
|
" for i = 1, 9 do\n"
|
|
" if game.vars.grid[i] == 0 then\n"
|
|
" return false\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"function find_best_move()\n"
|
|
" local best_score = -1000\n"
|
|
" local best_move = nil\n"
|
|
" \n"
|
|
" for i = 1, 9 do\n"
|
|
" if game.vars.grid[i] == 0 then\n"
|
|
" game.vars.grid[i] = game.vars.ai\n"
|
|
" local score = minimax(0, false)\n"
|
|
" game.vars.grid[i] = 0\n"
|
|
" \n"
|
|
" if score > best_score then\n"
|
|
" best_score = score\n"
|
|
" best_move = i\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" \n"
|
|
" return best_move or 5 -- Fallback to center\n"
|
|
"end\n"
|
|
"\n"
|
|
"function minimax(depth, is_maximizing)\n"
|
|
" local winner = check_winner()\n"
|
|
" \n"
|
|
" if winner == game.vars.ai then return 10 - depth end\n"
|
|
" if winner == game.vars.player then return depth - 10 end\n"
|
|
" if is_board_full() then return 0 end\n"
|
|
" \n"
|
|
" if is_maximizing then\n"
|
|
" local best_score = -1000\n"
|
|
" for i = 1, 9 do\n"
|
|
" if game.vars.grid[i] == 0 then\n"
|
|
" game.vars.grid[i] = game.vars.ai\n"
|
|
" local score = minimax(depth + 1, false)\n"
|
|
" game.vars.grid[i] = 0\n"
|
|
" best_score = math.max(best_score, score)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return best_score\n"
|
|
" else\n"
|
|
" local best_score = 1000\n"
|
|
" for i = 1, 9 do\n"
|
|
" if game.vars.grid[i] == 0 then\n"
|
|
" game.vars.grid[i] = game.vars.player\n"
|
|
" local score = minimax(depth + 1, true)\n"
|
|
" game.vars.grid[i] = 0\n"
|
|
" best_score = math.min(best_score, score)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return best_score\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reset_game()\n"
|
|
" game.vars.grid = {}\n"
|
|
" for i = 1, 9 do\n"
|
|
" game.vars.grid[i] = 0\n"
|
|
" end\n"
|
|
" game.vars.winner = 0\n"
|
|
"end\n"
|
|
"";
|
|
|
|
typedef struct { const char* name; const char* desc; const char* src; } PicoGame;
|
|
static const PicoGame pico_games[] = {
|
|
{"2048", "Merge tiles to reach 2048", game_2048_lua_src},
|
|
{"Air Hockey", "Fast-paced 2-player hockey", air_hockey_lua_src},
|
|
{"Asteroids", "Destroy asteroids, avoid collisions", asteroids_lua_src},
|
|
{"Bouncing Ball", "Physics demo with state management", ball_lua_src},
|
|
{"Breakout", "Break bricks with bouncing ball and paddle", breakout_lua_src},
|
|
{"Touch Counter", "Simple tap counter demo", counter_lua_src},
|
|
{"Flappy Bird", "Tap to flap, avoid pipes", flappy_bird_lua_src},
|
|
{"Lunar Lander", "Land safely with limited fuel", lunar_lander_lua_src},
|
|
{"Memory Match", "Find matching pairs", memory_match_lua_src},
|
|
{"Pacman", "Simplified pacman with digital tile movement", pacman_lua_src},
|
|
{"Pong", "Classic two-player Pong game", pong_lua_src},
|
|
{"Simon Says", "Repeat the color sequence", simon_says_lua_src},
|
|
{"Snake Game", "Classic snake with state machine", snake_lua_src},
|
|
{"Solitaire", "Klondike-style solitaire", solitaire_lua_src},
|
|
{"Tetris", "Stack falling blocks, clear lines", tetris_lua_src},
|
|
{"Tic-Tac-Toe", "Play vs AI opponent", tic_tac_toe_lua_src},
|
|
};
|
|
static const int pico_games_count = sizeof(pico_games)/sizeof(pico_games[0]);
|