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.
This commit is contained in:
Adolfo Reyna
2026-02-12 19:40:42 -05:00
parent 53a2fb046b
commit 50793ac535
4 changed files with 1025 additions and 0 deletions

View File

@@ -0,0 +1,285 @@
-- NAME: Tic-Tac-Toe
-- DESC: Play vs AI opponent
-- Game states
local STATE_MENU = 0
local STATE_PLAYING = 1
local STATE_GAME_OVER = 2
-- Game constants
local GRID_SIZE = 3
local CELL_SIZE = 30
local CELL_SPACING = 2
function init()
game.vars.state = STATE_MENU
game.vars.grid = {}
game.vars.player = 1 -- 1 = X, 2 = O
game.vars.ai = 2
game.vars.game_over = false
game.vars.winner = 0 -- 0 = ongoing, 1 = player wins, 2 = ai wins, 3 = draw
-- Enable continuous updates
game.set_frame_updates(true)
print("Tic-Tac-Toe 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
local cell = get_cell_at(event.x, event.y)
if cell and game.vars.grid[cell] == 0 then
-- Player move
game.vars.grid[cell] = game.vars.player
-- Check win
local winner = check_winner()
if winner ~= 0 then
if winner == game.vars.player then
game.vars.winner = 1
else
game.vars.winner = 2
end
game.vars.state = STATE_GAME_OVER
return true
end
-- Check draw
if is_board_full() then
game.vars.winner = 3
game.vars.state = STATE_GAME_OVER
return true
end
-- AI move
local ai_move = find_best_move()
game.vars.grid[ai_move] = game.vars.ai
-- Check win
winner = check_winner()
if winner ~= 0 then
if winner == game.vars.ai then
game.vars.winner = 2
end
game.vars.state = STATE_GAME_OVER
return true
end
-- Check draw
if is_board_full() then
game.vars.winner = 3
game.vars.state = STATE_GAME_OVER
return true
end
return true
end
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 - 50, game.height() / 2 - 30, "TIC-TAC-TOE", true)
renderer.text(game.width() / 2 - 50, game.height() / 2, "Play vs AI", true)
renderer.text(game.width() / 2 - 50, game.height() / 2 + 20, "Tap to Start", true)
elseif state == STATE_PLAYING or state == STATE_GAME_OVER then
-- Draw grid
local start_x = (game.width() - (GRID_SIZE * (CELL_SIZE + CELL_SPACING))) / 2
local start_y = 30
for row = 0, GRID_SIZE - 1 do
for col = 0, GRID_SIZE - 1 do
local cell_idx = row * GRID_SIZE + col + 1
local cell_x = start_x + col * (CELL_SIZE + CELL_SPACING)
local cell_y = start_y + row * (CELL_SIZE + CELL_SPACING)
-- Draw cell
renderer.rect(cell_x, cell_y, CELL_SIZE, CELL_SIZE, true, false)
-- Draw X or O
local value = game.vars.grid[cell_idx]
if value == 1 then
-- Draw X
renderer.line(cell_x + 2, cell_y + 2, cell_x + CELL_SIZE - 2, cell_y + CELL_SIZE - 2, true, 1)
renderer.line(cell_x + CELL_SIZE - 2, cell_y + 2, cell_x + 2, cell_y + CELL_SIZE - 2, true, 1)
elseif value == 2 then
-- Draw O
renderer.circle(cell_x + CELL_SIZE / 2, cell_y + CELL_SIZE / 2, CELL_SIZE / 2 - 2, true, false)
end
end
end
if state == STATE_GAME_OVER then
if game.vars.winner == 1 then
renderer.text(game.width() / 2 - 30, 10, "YOU WIN!", true)
elseif game.vars.winner == 2 then
renderer.text(game.width() / 2 - 30, 10, "AI WINS!", true)
else
renderer.text(game.width() / 2 - 20, 10, "DRAW!", true)
end
renderer.text(game.width() / 2 - 60, game.height() - 15, "Tap to Menu", true)
end
end
end
function get_cell_at(x, y)
local start_x = (game.width() - (GRID_SIZE * (CELL_SIZE + CELL_SPACING))) / 2
local start_y = 30
for row = 0, GRID_SIZE - 1 do
for col = 0, GRID_SIZE - 1 do
local cell_x = start_x + col * (CELL_SIZE + CELL_SPACING)
local cell_y = start_y + row * (CELL_SIZE + CELL_SPACING)
if x >= cell_x and x < cell_x + CELL_SIZE and
y >= cell_y and y < cell_y + CELL_SIZE then
return row * GRID_SIZE + col + 1
end
end
end
return nil
end
function check_winner()
-- Check rows
for row = 0, GRID_SIZE - 1 do
local val = game.vars.grid[row * GRID_SIZE + 1]
if val ~= 0 then
local match = true
for col = 1, GRID_SIZE - 1 do
if game.vars.grid[row * GRID_SIZE + col + 1] ~= val then
match = false
break
end
end
if match then return val end
end
end
-- Check columns
for col = 0, GRID_SIZE - 1 do
local val = game.vars.grid[col + 1]
if val ~= 0 then
local match = true
for row = 1, GRID_SIZE - 1 do
if game.vars.grid[row * GRID_SIZE + col + 1] ~= val then
match = false
break
end
end
if match then return val end
end
end
-- Check diagonals
local val = game.vars.grid[1]
if val ~= 0 then
if game.vars.grid[5] == val and game.vars.grid[9] == val then
return val
end
end
val = game.vars.grid[3]
if val ~= 0 then
if game.vars.grid[5] == val and game.vars.grid[7] == val then
return val
end
end
return 0
end
function is_board_full()
for i = 1, 9 do
if game.vars.grid[i] == 0 then
return false
end
end
return true
end
function find_best_move()
local best_score = -1000
local best_move = nil
for i = 1, 9 do
if game.vars.grid[i] == 0 then
game.vars.grid[i] = game.vars.ai
local score = minimax(0, false)
game.vars.grid[i] = 0
if score > best_score then
best_score = score
best_move = i
end
end
end
return best_move or 5 -- Fallback to center
end
function minimax(depth, is_maximizing)
local winner = check_winner()
if winner == game.vars.ai then return 10 - depth end
if winner == game.vars.player then return depth - 10 end
if is_board_full() then return 0 end
if is_maximizing then
local best_score = -1000
for i = 1, 9 do
if game.vars.grid[i] == 0 then
game.vars.grid[i] = game.vars.ai
local score = minimax(depth + 1, false)
game.vars.grid[i] = 0
best_score = math.max(best_score, score)
end
end
return best_score
else
local best_score = 1000
for i = 1, 9 do
if game.vars.grid[i] == 0 then
game.vars.grid[i] = game.vars.player
local score = minimax(depth + 1, true)
game.vars.grid[i] = 0
best_score = math.min(best_score, score)
end
end
return best_score
end
end
function reset_game()
game.vars.grid = {}
for i = 1, 9 do
game.vars.grid[i] = 0
end
game.vars.winner = 0
end