diff --git a/Apps/LuaGame/main/Source/main.c b/Apps/LuaGame/main/Source/main.c index d1f9624..3d961a5 100644 --- a/Apps/LuaGame/main/Source/main.c +++ b/Apps/LuaGame/main/Source/main.c @@ -17,6 +17,7 @@ #include "lualib.h" #include "audio.h" #include "font8x8.h" +#include "pico_games.h" #undef ESP_LOGI #undef ESP_LOGW @@ -53,11 +54,13 @@ typedef struct { lv_timer_t* timer; TaskHandle_t lua_task; bool touch_pressed; + bool prev_touch_pressed; int touch_x; int touch_y; lua_State* L; bool lua_ok; bool initialized; + bool is_pico; uint64_t last_tick_us; bool should_exit; // fps @@ -203,7 +206,161 @@ static int l_sys_sfx(lua_State* L){ return 0; } +/* ---------- Pico API compatibility ---------- */ +static int l_renderer_clear(lua_State* L){ + AppCtx* ctx=g_ctx; if(!ctx) return 0; + bool white = false; + if(lua_gettop(L)>=1) white = lua_toboolean(L,1); + uint16_t col = white ? 0xFFFF : 0x0000; + fill_rect(ctx,0,0,GAME_W,GAME_H,col,true); + clear_labels(ctx); + return 0; +} +static int l_renderer_pixel(lua_State* L){ + AppCtx* ctx=g_ctx; if(!ctx) return 0; + int x=(int)luaL_checknumber(L,1); + int y=(int)luaL_checknumber(L,2); + bool on = true; if(lua_gettop(L)>=3) on = lua_toboolean(L,3); + uint16_t col = on ? 0xFFFF : 0x0000; + set_px(ctx,x,y,col); + return 0; +} +static int l_renderer_line(lua_State* L){ + AppCtx* ctx=g_ctx; if(!ctx) return 0; + int x0=(int)luaL_checknumber(L,1); + int y0=(int)luaL_checknumber(L,2); + int x1=(int)luaL_checknumber(L,3); + int y1=(int)luaL_checknumber(L,4); + bool on = true; if(lua_gettop(L)>=5) on = lua_toboolean(L,5); + // width ignored + uint16_t col = on ? 0xFFFF : 0x0000; + draw_line(ctx,x0,y0,x1,y1,col); + return 0; +} +static int l_renderer_rect(lua_State* L){ + AppCtx* ctx=g_ctx; if(!ctx) return 0; + int x=(int)luaL_checknumber(L,1); + int y=(int)luaL_checknumber(L,2); + int w=(int)luaL_checknumber(L,3); + int h=(int)luaL_checknumber(L,4); + bool on = true; if(lua_gettop(L)>=5) on = lua_toboolean(L,5); + bool filled = false; if(lua_gettop(L)>=6) filled = lua_toboolean(L,6); + uint16_t col = on ? 0xFFFF : 0x0000; + fill_rect(ctx,x,y,w,h,col,filled); + // if not filled and on=true, we already draw border via fill_rect false, but our fill_rect does border + // To match pico: if filled false, draw outline with on color + if(!filled){ + // ensure border drawn (our fill_rect already draws border when filled=false) + } + return 0; +} +static int l_renderer_circle(lua_State* L){ + AppCtx* ctx=g_ctx; if(!ctx) return 0; + int x=(int)luaL_checknumber(L,1); + int y=(int)luaL_checknumber(L,2); + int r=(int)luaL_checknumber(L,3); + bool on = true; if(lua_gettop(L)>=4) on = lua_toboolean(L,4); + bool filled = false; if(lua_gettop(L)>=5) filled = lua_toboolean(L,5); + uint16_t col = on ? 0xFFFF : 0x0000; + draw_circle(ctx,x,y,r,col,filled); + return 0; +} +// Simple triangle rasterizer: draw outline and optionally filled via scanline +static void draw_triangle_filled(AppCtx* ctx, int x0,int y0,int x1,int y1,int x2,int y2,uint16_t col){ + // Sort vertices by y + if(y0>y1){ int tx=x0; x0=x1; x1=tx; int ty=y0; y0=y1; y1=ty; } + if(y1>y2){ int tx=x1; x1=x2; x2=tx; int ty=y1; y1=y2; y2=ty; } + if(y0>y1){ int tx=x0; x0=x1; x1=tx; int ty=y0; y0=y1; y1=ty; } + // Now y0 <= y1 <= y2 + int total_height = y2 - y0; + if(total_height==0) return; + for(int i=0;i y1 - y0 || y1==y0; + int segment_height = second_half ? y2 - y1 : y1 - y0; + if(segment_height==0) continue; + float alpha = (float)i / total_height; + float beta = (float)(i - (second_half ? y1 - y0 : 0)) / segment_height; + int ax = x0 + (int)((x2 - x0) * alpha); + int bx; + if(second_half) bx = x1 + (int)((x2 - x1) * beta); + else bx = x0 + (int)((x1 - x0) * beta); + if(ax>bx){ int t=ax; ax=bx; bx=t; } + for(int j=ax;j<=bx;j++) set_px(ctx,j,y0+i,col); + } +} +static int l_renderer_triangle(lua_State* L){ + AppCtx* ctx=g_ctx; if(!ctx) return 0; + int x0=(int)luaL_checknumber(L,1); + int y0=(int)luaL_checknumber(L,2); + int x1=(int)luaL_checknumber(L,3); + int y1=(int)luaL_checknumber(L,4); + int x2=(int)luaL_checknumber(L,5); + int y2=(int)luaL_checknumber(L,6); + bool on = true; if(lua_gettop(L)>=7) on = lua_toboolean(L,7); + bool filled = false; if(lua_gettop(L)>=8) filled = lua_toboolean(L,8); + uint16_t col = on ? 0xFFFF : 0x0000; + if(filled){ + draw_triangle_filled(ctx,x0,y0,x1,y1,x2,y2,col); + }else{ + draw_line(ctx,x0,y0,x1,y1,col); + draw_line(ctx,x1,y1,x2,y2,col); + draw_line(ctx,x2,y2,x0,y0,col); + } + return 0; +} +static int l_renderer_text(lua_State* L){ + AppCtx* ctx=g_ctx; if(!ctx) return 0; + int x=(int)luaL_checknumber(L,1); + int y=(int)luaL_checknumber(L,2); + const char* txt=luaL_checkstring(L,3); + bool on = true; if(lua_gettop(L)>=4) on = lua_toboolean(L,4); + uint16_t col = on ? 0xFFFF : 0x0000; + draw_text8(ctx,x,y,txt,col); + return 0; +} +static int l_renderer_text_scaled(lua_State* L){ + AppCtx* ctx=g_ctx; if(!ctx) return 0; + int x=(int)luaL_checknumber(L,1); + int y=(int)luaL_checknumber(L,2); + const char* txt=luaL_checkstring(L,3); + bool on = true; if(lua_gettop(L)>=4) on = lua_toboolean(L,4); + int scale = 1; if(lua_gettop(L)>=5) scale = (int)luaL_checknumber(L,5); + if(scale<1) scale=1; + if(scale>4) scale=4; + uint16_t col = on ? 0xFFFF : 0x0000; + // Simple scaled: draw each char scaled by duplicating pixels + int cx=x; + int cy=y; + for(int i=0; txt[i]; i++){ + char ch=txt[i]; + if(ch=='\n'){ cx=x; cy+=8*scale+2; continue; } + unsigned char uch=(unsigned char)ch; + if(uch>=128) continue; + for(int row=0; row<8; row++){ + uint8_t bits = font8x8_basic[uch][row]; + for(int colb=0; colb<8; colb++){ + if(bits & (1<GAME_W){ cx=x; cy+=8*scale+2; } + } + return 0; +} +static int l_game_width(lua_State* L){ lua_pushinteger(L, GAME_W); return 1; } +static int l_game_height(lua_State* L){ lua_pushinteger(L, GAME_H); return 1; } +static int l_game_set_frame_updates(lua_State* L){ (void)L; return 0; } +static int l_game_exit(lua_State* L){ (void)L; printf("[LuaGame] game.exit() called\n"); return 0; } + static void register_sys(lua_State* L){ + // sys table (Tactility original) lua_newtable(L); lua_pushcfunction(L,l_sys_log); lua_setfield(L,-2,"log"); lua_pushcfunction(L,l_sys_clear); lua_setfield(L,-2,"clear"); @@ -215,10 +372,45 @@ static void register_sys(lua_State* L){ lua_pushcfunction(L,l_sys_tone); lua_setfield(L,-2,"tone"); lua_pushcfunction(L,l_sys_sfx); lua_setfield(L,-2,"sfx"); lua_setglobal(L,"sys"); + + // renderer table (Pico) + lua_newtable(L); + lua_pushcfunction(L,l_renderer_clear); lua_setfield(L,-2,"clear"); + lua_pushcfunction(L,l_renderer_pixel); lua_setfield(L,-2,"pixel"); + lua_pushcfunction(L,l_renderer_line); lua_setfield(L,-2,"line"); + lua_pushcfunction(L,l_renderer_rect); lua_setfield(L,-2,"rect"); + lua_pushcfunction(L,l_renderer_circle); lua_setfield(L,-2,"circle"); + lua_pushcfunction(L,l_renderer_triangle); lua_setfield(L,-2,"triangle"); + lua_pushcfunction(L,l_renderer_text); lua_setfield(L,-2,"text"); + lua_pushcfunction(L,l_renderer_text_scaled); lua_setfield(L,-2,"text_scaled"); + lua_setglobal(L,"renderer"); + + // game table + lua_newtable(L); + lua_pushcfunction(L,l_game_width); lua_setfield(L,-2,"width"); + lua_pushcfunction(L,l_game_height); lua_setfield(L,-2,"height"); + lua_pushcfunction(L,l_game_set_frame_updates); lua_setfield(L,-2,"set_frame_updates"); + lua_pushcfunction(L,l_game_exit); lua_setfield(L,-2,"exit"); + // game.vars subtable + lua_newtable(L); + lua_setfield(L,-2,"vars"); + lua_setglobal(L,"game"); + + // INPUT constants + lua_newtable(L); + lua_pushinteger(L,0); lua_setfield(L,-2,"NONE"); + lua_pushinteger(L,1); lua_setfield(L,-2,"TOUCH_DOWN"); + lua_pushinteger(L,2); lua_setfield(L,-2,"TOUCH_MOVE"); + lua_pushinteger(L,3); lua_setfield(L,-2,"TOUCH_UP"); + lua_pushinteger(L,4); lua_setfield(L,-2,"BUTTON_0"); + lua_pushinteger(L,5); lua_setfield(L,-2,"BUTTON_1"); + lua_pushinteger(L,6); lua_setfield(L,-2,"FRAME_TICK"); + lua_pushinteger(L,7); lua_setfield(L,-2,"GESTURE"); + lua_setglobal(L,"INPUT"); } /* Embedded breakout - faster ball: 3.5 instead of 2 */ -static const char* breakout_lua_src = +static const char* embedded_breakout_lua_src = "local PADDLE_W=40 local PADDLE_H=8 local BALL_SIZE=6 local BRICK_ROWS=4 local BRICK_COLS=6 local BRICK_W=34 local BRICK_H=8 local BRICK_GAP=4\n" "local score=0 local lives=3 local game_state='serve' local px,py=100,210 local bx,by=120,150 local bdx,bdy=4.5,-4.5 local bricks={}\n" "function init() sys.log('Init Breakout fast v2!') reset_bricks() reset_ball() end\n" @@ -267,10 +459,20 @@ static void lua_init_task(void* arg){ char* src_buf=NULL; size_t src_len=0; const char* loaded_from="embedded"; - // For this build, force embedded to demonstrate faster ball + audio. - // To test external file, uncomment loop below. - bool use_external = false; // set true to load SD file - if(use_external){ + bool use_external = false; + bool use_pico_test = false; // set true to test pico games + const char* pico_test_src = NULL; + if(use_pico_test){ + pico_test_src = pong_lua_src; // choose pong for test + printf("[LuaGame] using Pico test game: Pong (%d bytes)\n", (int)strlen(pico_test_src)); + src_len = strlen(pico_test_src); + src_buf = (char*)alloc_psram(src_len+1); + if(src_buf){ + memcpy(src_buf, pico_test_src, src_len+1); + loaded_from = "pico_pong"; + } + } + if(!src_buf && use_external){ for(int i=0; external_paths[i]; i++){ src_buf=load_file_to_mem(external_paths[i], &src_len); if(src_buf){ @@ -280,13 +482,26 @@ static void lua_init_task(void* arg){ } } } + // Detect Pico API by content before loading + bool detected_pico = false; + if(src_buf){ + if(strstr(src_buf,"renderer.")!=NULL || strstr(src_buf,"INPUT.")!=NULL){ + detected_pico = true; + } + } else { + if(strstr(embedded_breakout_lua_src,"renderer.")!=NULL){ + detected_pico = true; + } + } + int st; if(src_buf){ st=luaL_loadbuffer(ctx->L,src_buf,src_len,loaded_from); heap_caps_free(src_buf); + src_buf=NULL; }else{ - printf("[LuaGame] loading embedded lua %d bytes\n",(int)strlen(breakout_lua_src)); - st=luaL_loadstring(ctx->L,breakout_lua_src); + printf("[LuaGame] loading embedded lua %d bytes\n",(int)strlen(embedded_breakout_lua_src)); + st=luaL_loadstring(ctx->L,embedded_breakout_lua_src); loaded_from="embedded"; } if(st!=LUA_OK){const char* e=lua_tostring(ctx->L,-1); printf("[LuaGame] load err (%s) %s\n",loaded_from,e); lua_pop(ctx->L,1); vTaskDelete(NULL); return;} @@ -297,12 +512,13 @@ static void lua_init_task(void* arg){ }else lua_pop(ctx->L,1); ctx->lua_ok=true; ctx->initialized=true; + ctx->is_pico = detected_pico; ctx->last_tick_us=esp_timer_get_time(); ctx->fps_last_us=esp_timer_get_time(); ctx->fps_frames=0; - printf("[LuaGame] init done OK from %s\n",loaded_from); - // Instead of creating new task (which may fail due to memory), reuse this init task as game loop + printf("[LuaGame] init done OK from %s (pico=%d)\n",loaded_from, ctx->is_pico?1:0); printf("[LuaGame] entering game loop in same task\n"); + // Enter game loop directly while(!ctx->should_exit){ if(ctx->lua_ok && ctx->initialized && ctx->L){ @@ -311,11 +527,82 @@ static void lua_init_task(void* arg){ if(dt>0.1f) dt=0.1f; ctx->last_tick_us=now; lua_State* L=ctx->L; - lua_getglobal(L,"update"); - if(lua_isfunction(L,-1)){ - lua_pushnumber(L,dt); - if(lua_pcall(L,1,0,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR update] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} - }else lua_pop(L,1); + + if(ctx->is_pico){ + // Generate event based on touch + int ev_type = 6; // FRAME_TICK default + bool has_touch_event = false; + if(!ctx->prev_touch_pressed && ctx->touch_pressed){ + ev_type = 1; // TOUCH_DOWN + has_touch_event = true; + } else if(ctx->prev_touch_pressed && !ctx->touch_pressed){ + ev_type = 3; // TOUCH_UP + has_touch_event = true; + } else if(ctx->touch_pressed){ + ev_type = 2; // TOUCH_MOVE + has_touch_event = true; + } else { + ev_type = 6; // FRAME_TICK + } + + // Helper to push event table and call update + // First call with touch event if any + if(has_touch_event){ + lua_getglobal(L,"update"); + if(lua_isfunction(L,-1)){ + lua_newtable(L); + lua_pushinteger(L, ev_type); lua_setfield(L,-2,"type"); + lua_pushinteger(L, ctx->touch_x); lua_setfield(L,-2,"x"); + lua_pushinteger(L, ctx->touch_y); lua_setfield(L,-2,"y"); + lua_pushboolean(L,1); lua_setfield(L,-2,"valid"); + lua_pushinteger(L,0); lua_setfield(L,-2,"button_id"); + if(lua_pcall(L,1,1,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR update] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} + else { + bool redraw = lua_toboolean(L,-1); + lua_pop(L,1); + // we will draw regardless, but could check redraw + (void)redraw; + } + } else lua_pop(L,1); + } + // Always also call with FRAME_TICK if not already frame tick + if(ev_type != 6){ + lua_getglobal(L,"update"); + if(lua_isfunction(L,-1)){ + lua_newtable(L); + lua_pushinteger(L, 6); lua_setfield(L,-2,"type"); + lua_pushinteger(L, ctx->touch_x); lua_setfield(L,-2,"x"); + lua_pushinteger(L, ctx->touch_y); lua_setfield(L,-2,"y"); + lua_pushboolean(L,1); lua_setfield(L,-2,"valid"); + if(lua_pcall(L,1,1,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR update FRAME_TICK] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} + else { lua_pop(L,1); } + } else lua_pop(L,1); + } else if(!has_touch_event){ + // No touch, just frame tick (single call) + lua_getglobal(L,"update"); + if(lua_isfunction(L,-1)){ + lua_newtable(L); + lua_pushinteger(L, 6); lua_setfield(L,-2,"type"); + lua_pushinteger(L, ctx->touch_x); lua_setfield(L,-2,"x"); + lua_pushinteger(L, ctx->touch_y); lua_setfield(L,-2,"y"); + lua_pushboolean(L,1); lua_setfield(L,-2,"valid"); + if(lua_pcall(L,1,1,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR update] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} + else { + bool redraw = lua_toboolean(L,-1); + lua_pop(L,1); + (void)redraw; + } + } else lua_pop(L,1); + } + ctx->prev_touch_pressed = ctx->touch_pressed; + } else { + // Original sys API + lua_getglobal(L,"update"); + if(lua_isfunction(L,-1)){ + lua_pushnumber(L,dt); + if(lua_pcall(L,1,0,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR update] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} + }else lua_pop(L,1); + } lua_getglobal(L,"draw"); if(lua_isfunction(L,-1)){ if(lua_pcall(L,0,0,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR draw] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} diff --git a/Apps/LuaGame/main/Source/pico_games.h b/Apps/LuaGame/main/Source/pico_games.h new file mode 100644 index 0000000..7855069 --- /dev/null +++ b/Apps/LuaGame/main/Source/pico_games.h @@ -0,0 +1,4627 @@ +#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]);