- 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.
379 lines
10 KiB
Lua
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
|