-- 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