f398d62af2
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.
311 lines
9.4 KiB
Lua
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
|