Files
basic1/games/lua_examples/tic_tac_toe.lua
T
Adolfo Reyna f398d62af2 Add renderer.text_scaled() Lua binding
lua_bindings.cpp:
- Added new lua_renderer_text_scaled() function
- Wraps renderer->draw_string_scaled() with proper scale support
- Registered as renderer.text_scaled(x, y, text, on, scale)
- Scale parameter defaults to 1 if omitted

Updated all 4 games to use text_scaled():
- simon_says.lua: All text now uses text_scaled with scale=2
- tic_tac_toe.lua: All text now uses text_scaled with scale=2
- memory_match.lua: All text now uses text_scaled with scale=2
- 2048.lua: All text now uses text_scaled with scale=2

This properly uses the C++ renderer's scaled text rendering instead of
workarounds in Lua, providing better performance and consistency.
2026-02-12 21:31:15 -05:00

311 lines
9.4 KiB
Lua

-- 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
-- Calculate cell size based on screen size
local function get_cell_size()
-- Use smallest dimension to maintain square grid
local min_dim = math.min(game.width(), game.height())
local padding = math.floor(min_dim / 8)
local available = min_dim - (padding * 2)
local cell_size = math.floor(available / GRID_SIZE)
return cell_size
end
local function get_grid_start_y()
-- Center grid vertically
local cell_size = get_cell_size()
local grid_height = cell_size * GRID_SIZE
return math.floor((game.height() - grid_height) / 2)
end
local function get_grid_start_x()
-- Center grid horizontally
local cell_size = get_cell_size()
local grid_width = cell_size * GRID_SIZE
return math.floor((game.width() - grid_width) / 2)
end
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
local cell_size = get_cell_size()
local start_x = get_grid_start_x()
local start_y = get_grid_start_y()
local cell_spacing = 2
if state == STATE_MENU then
renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 - 30, "TIC-TAC-TOE", true, 2)
renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, "Play vs AI", true, 2)
renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 + 20, "Tap to Start", true, 2)
elseif state == STATE_PLAYING or state == STATE_GAME_OVER then
-- Draw grid
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 (convert center to integers)
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)
end
end
end
if state == STATE_GAME_OVER then
if game.vars.winner == 1 then
renderer.text_scaled(game.width() / 2 - 30, 10, "YOU WIN!", true, 2)
elseif game.vars.winner == 2 then
renderer.text_scaled(game.width() / 2 - 30, 10, "AI WINS!", true, 2)
else
renderer.text_scaled(game.width() / 2 - 20, 10, "DRAW!", true, 2)
end
renderer.text_scaled(game.width() / 2 - 60, game.height() - 15, "Tap to Menu", true, 2)
end
end
end
function get_cell_at(x, y)
local cell_size = get_cell_size()
local start_x = get_grid_start_x()
local start_y = get_grid_start_y()
local cell_spacing = 2
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