Files
basic1/games/lua_examples/2048.lua
Adolfo Reyna 50793ac535 feat: add final 4 games for basic1 console
- 2048: Grid merging, directional movement, score tracking, win/draw detection
- Tic-Tac-Toe: Minimax AI opponent, perfect play, win detection
- Lunar Lander: Gravity + thrust physics, fuel management, landing validation
- Air Hockey: Refined paddle physics, puck acceleration, goal detection

All games tested for state transitions, collision logic, and win conditions.
Suite now complete with 10 classic games ready for SD card deployment.
2026-02-12 19:40:42 -05:00

379 lines
10 KiB
Lua

-- NAME: 2048
-- DESC: Merge tiles to reach 2048
-- Game states
local STATE_MENU = 0
local STATE_PLAYING = 1
local STATE_GAME_OVER = 2
local STATE_WIN = 3
-- Game constants
local GRID_SIZE = 4
local TILE_SIZE = 20
local TILE_SPACING = 2
function init()
game.vars.state = STATE_MENU
game.vars.score = 0
-- Grid (4x4, 0 = empty)
game.vars.grid = {}
for y = 1, GRID_SIZE do
game.vars.grid[y] = {}
for x = 1, GRID_SIZE do
game.vars.grid[y][x] = 0
end
end
game.vars.moved = false
game.vars.won = false
-- Enable continuous updates
game.set_frame_updates(true)
print("2048 initialized")
end
function update(event)
local state = game.vars.state
if state == STATE_MENU then
if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then
reset_game()
game.vars.state = STATE_PLAYING
return true
end
elseif state == STATE_PLAYING then
if event.type == INPUT.TOUCH_DOWN then
-- Determine swipe direction
local direction = get_swipe_direction(event.x, event.y)
if direction then
game.vars.moved = false
move_tiles(direction)
if game.vars.moved then
spawn_tile()
-- Check win/lose
if has_tile(2048) then
if not game.vars.won then
game.vars.state = STATE_WIN
game.vars.won = true
end
end
if not can_move() then
game.vars.state = STATE_GAME_OVER
end
return true
end
end
end
elseif state == STATE_WIN then
if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then
-- Can continue playing or go to menu
if event.y < game.height() / 2 then
game.vars.state = STATE_PLAYING
else
game.vars.state = STATE_MENU
end
return true
end
elseif state == STATE_GAME_OVER then
if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then
game.vars.state = STATE_MENU
return true
end
end
return false
end
function draw()
renderer.clear(false)
local state = game.vars.state
if state == STATE_MENU then
renderer.text(game.width() / 2 - 15, game.height() / 2 - 30, "2048", true)
renderer.text(game.width() / 2 - 50, game.height() / 2, "Tap to Start", true)
elseif state == STATE_PLAYING or state == STATE_WIN or state == STATE_GAME_OVER then
-- Draw grid
local start_x = (game.width() - (GRID_SIZE * (TILE_SIZE + TILE_SPACING))) / 2
local start_y = 20
for y = 1, GRID_SIZE do
for x = 1, GRID_SIZE do
local tile_x = start_x + (x - 1) * (TILE_SIZE + TILE_SPACING)
local tile_y = start_y + (y - 1) * (TILE_SIZE + TILE_SPACING)
local value = game.vars.grid[y][x]
if value == 0 then
-- Empty tile
renderer.rect(tile_x, tile_y, TILE_SIZE, TILE_SIZE, true, false)
else
-- Filled tile
renderer.rect(tile_x, tile_y, TILE_SIZE, TILE_SIZE, true, true)
-- Draw value (simplified)
local text = tostring(value)
if string.len(text) <= 2 then
renderer.text(tile_x + 2, tile_y + 2, text, false)
else
renderer.text(tile_x + 1, tile_y + 2, text, false)
end
end
end
end
-- Draw score
renderer.text(10, 10, "Score: " .. tostring(game.vars.score), true)
if state == STATE_WIN then
renderer.text(game.width() / 2 - 30, game.height() / 2 - 20, "YOU WIN!", true)
renderer.text(game.width() / 2 - 60, game.height() / 2, "Tap up: Continue", true)
renderer.text(game.width() / 2 - 50, game.height() / 2 + 15, "Tap down: Menu", true)
end
if state == STATE_GAME_OVER then
renderer.text(game.width() / 2 - 40, game.height() / 2, "GAME OVER", true)
renderer.text(game.width() / 2 - 30, game.height() / 2 + 15, "Score: " .. tostring(game.vars.score), true)
renderer.text(game.width() / 2 - 60, game.height() / 2 + 30, "Tap to Menu", true)
end
end
end
function get_swipe_direction(x, y)
-- Simple tap-based direction (could be enhanced with swipe tracking)
local mid_x = game.width() / 2
local mid_y = game.height() / 2
local dx = x - mid_x
local dy = y - mid_y
if math.abs(dx) > math.abs(dy) then
if dx > 0 then return "right" end
return "left"
else
if dy > 0 then return "down" end
return "up"
end
end
function move_tiles(direction)
local old_grid = {}
for y = 1, GRID_SIZE do
old_grid[y] = {}
for x = 1, GRID_SIZE do
old_grid[y][x] = game.vars.grid[y][x]
end
end
if direction == "left" then
move_left()
elseif direction == "right" then
move_right()
elseif direction == "up" then
move_up()
elseif direction == "down" then
move_down()
end
-- Check if grid changed
for y = 1, GRID_SIZE do
for x = 1, GRID_SIZE do
if old_grid[y][x] ~= game.vars.grid[y][x] then
game.vars.moved = true
return
end
end
end
end
function move_left()
for y = 1, GRID_SIZE do
-- Compact
local row = {}
for x = 1, GRID_SIZE do
if game.vars.grid[y][x] ~= 0 then
table.insert(row, game.vars.grid[y][x])
end
end
-- Merge
local i = 1
while i < #row do
if row[i] == row[i + 1] then
row[i] = row[i] * 2
game.vars.score = game.vars.score + row[i]
table.remove(row, i + 1)
end
i = i + 1
end
-- Fill back
for x = 1, GRID_SIZE do
if x <= #row then
game.vars.grid[y][x] = row[x]
else
game.vars.grid[y][x] = 0
end
end
end
end
function move_right()
for y = 1, GRID_SIZE do
local row = {}
for x = GRID_SIZE, 1, -1 do
if game.vars.grid[y][x] ~= 0 then
table.insert(row, game.vars.grid[y][x])
end
end
local i = 1
while i < #row do
if row[i] == row[i + 1] then
row[i] = row[i] * 2
game.vars.score = game.vars.score + row[i]
table.remove(row, i + 1)
end
i = i + 1
end
for x = GRID_SIZE, 1, -1 do
if GRID_SIZE - x + 1 <= #row then
game.vars.grid[y][x] = row[GRID_SIZE - x + 1]
else
game.vars.grid[y][x] = 0
end
end
end
end
function move_up()
for x = 1, GRID_SIZE do
local col = {}
for y = 1, GRID_SIZE do
if game.vars.grid[y][x] ~= 0 then
table.insert(col, game.vars.grid[y][x])
end
end
local i = 1
while i < #col do
if col[i] == col[i + 1] then
col[i] = col[i] * 2
game.vars.score = game.vars.score + col[i]
table.remove(col, i + 1)
end
i = i + 1
end
for y = 1, GRID_SIZE do
if y <= #col then
game.vars.grid[y][x] = col[y]
else
game.vars.grid[y][x] = 0
end
end
end
end
function move_down()
for x = 1, GRID_SIZE do
local col = {}
for y = GRID_SIZE, 1, -1 do
if game.vars.grid[y][x] ~= 0 then
table.insert(col, game.vars.grid[y][x])
end
end
local i = 1
while i < #col do
if col[i] == col[i + 1] then
col[i] = col[i] * 2
game.vars.score = game.vars.score + col[i]
table.remove(col, i + 1)
end
i = i + 1
end
for y = GRID_SIZE, 1, -1 do
if GRID_SIZE - y + 1 <= #col then
game.vars.grid[y][x] = col[GRID_SIZE - y + 1]
else
game.vars.grid[y][x] = 0
end
end
end
end
function spawn_tile()
local empty = {}
for y = 1, GRID_SIZE do
for x = 1, GRID_SIZE do
if game.vars.grid[y][x] == 0 then
table.insert(empty, {x = x, y = y})
end
end
end
if #empty > 0 then
local pos = empty[math.random(1, #empty)]
game.vars.grid[pos.y][pos.x] = math.random() > 0.9 and 4 or 2
end
end
function has_tile(value)
for y = 1, GRID_SIZE do
for x = 1, GRID_SIZE do
if game.vars.grid[y][x] == value then
return true
end
end
end
return false
end
function can_move()
-- Check if any empty tiles
for y = 1, GRID_SIZE do
for x = 1, GRID_SIZE do
if game.vars.grid[y][x] == 0 then
return true
end
end
end
-- Check if any merges possible
for y = 1, GRID_SIZE do
for x = 1, GRID_SIZE do
local val = game.vars.grid[y][x]
if x < GRID_SIZE and game.vars.grid[y][x + 1] == val then return true end
if y < GRID_SIZE and game.vars.grid[y + 1][x] == val then return true end
end
end
return false
end
function reset_game()
game.vars.score = 0
game.vars.won = false
for y = 1, GRID_SIZE do
for x = 1, GRID_SIZE do
game.vars.grid[y][x] = 0
end
end
spawn_tile()
spawn_tile()
end