-- 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(game.width() / 2 - 50, game.height() / 2 - 30, "TIC-TAC-TOE", true, 2) renderer.text(game.width() / 2 - 50, game.height() / 2, "Play vs AI", true, 2) renderer.text(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(game.width() / 2 - 30, 10, "YOU WIN!", true, 2) elseif game.vars.winner == 2 then renderer.text(game.width() / 2 - 30, 10, "AI WINS!", true, 2) else renderer.text(game.width() / 2 - 20, 10, "DRAW!", true, 2) end renderer.text(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