Score0
CategoryClassic
Plays67k plays
Gamelot
The Kingdom of Games
🤖
Tictactoe Ai
Tic-Tac-Toe against an unbeatable AI. The AI uses the minimax algorithm — it wil…
Free · No sign-up · Instant play
CLICK Click any empty square to play
Tictactoe Ai
Tic-Tac-Toe against an unbeatable AI. The AI uses the minimax algorithm — it wil…
Free · No sign-up · Instant play
TAPClick any empty square to play
😊 Easy
🤔 Medium
💀 Impossible
Your turn — you're X
AI is thinking...
You are X, the AI is O. Easy makes mistakes. Medium plays well. Impossible uses Minimax — it literally cannot lose.
Advertisement
/* ══════════════════════════════════════
GAMELOT — TIC-TAC-TOE AI
Minimax algorithm for unbeatable AI
3 difficulty levels
══════════════════════════════════════ */
const WINS = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
let board, gameOver, playerTurn, difficulty, scores;
difficulty = 'medium';
scores = {x:0,o:0,d:0};
function init() {
board = Array(9).fill(null);
gameOver = false;
playerTurn = true;
updateBoard();
setStatus("Your turn — you're X");
}
function setDiff(d, el) {
difficulty = d;
document.querySelectorAll('.diff-btn').forEach(b=>b.classList.remove('active'));
el.classList.add('active');
resetGame();
}
function play(idx) {
if(!playerTurn || gameOver || board[idx]) return;
board[idx] = 'X';
updateBoard();
const result = checkWin(board);
if(result) { endGame(result); return; }
if(board.every(c=>c)) { endGame('draw'); return; }
playerTurn = false;
setStatus('AI is thinking...');
document.getElementById('thinking').classList.add('show');
setTimeout(aiMove, difficulty==='easy'?200:400);
}
function aiMove() {
document.getElementById('thinking').classList.remove('show');
let idx;
if(difficulty==='easy') idx = easyMove();
else if(difficulty==='medium') idx = mediumMove();
else idx = bestMove();
if(idx===null) return;
board[idx]='O';
updateBoard();
const result=checkWin(board);
if(result){endGame(result);return;}
if(board.every(c=>c)){endGame('draw');return;}
playerTurn=true;
setStatus("Your turn — you're X");
}
// Easy: mostly random with occasional smart move
function easyMove() {
const empty=board.map((v,i)=>v?null:i).filter(v=>v!==null);
if(Math.random()<0.3) return mediumMove();
return empty[Math.floor(Math.random()*empty.length)]??null;
}
// Medium: win/block, otherwise random
function mediumMove() {
// Win if possible
for(const w of WINS){const [a,b,c]=w;if(board[a]==='O'&&board[b]==='O'&&!board[c])return c;if(board[a]==='O'&&!board[b]&&board[c]==='O')return b;if(!board[a]&&board[b]==='O'&&board[c]==='O')return a;}
// Block player
for(const w of WINS){const [a,b,c]=w;if(board[a]==='X'&&board[b]==='X'&&!board[c])return c;if(board[a]==='X'&&!board[b]&&board[c]==='X')return b;if(!board[a]&&board[b]==='X'&&board[c]==='X')return a;}
// Centre
if(!board[4]) return 4;
// Random
const empty=board.map((v,i)=>v?null:i).filter(v=>v!==null);
return empty[Math.floor(Math.random()*empty.length)]??null;
}
// Hard: full minimax
function bestMove() {
let best=-Infinity,idx=null;
board.forEach((v,i)=>{
if(v) return;
board[i]='O';
const score=minimax(board,0,false,-Infinity,Infinity);
board[i]=null;
if(score>best){best=score;idx=i;}
});
return idx;
}
function minimax(b,depth,isMax,alpha,beta) {
const r=checkWin(b);
if(r==='O') return 10-depth;
if(r==='X') return depth-10;
if(b.every(c=>c)) return 0;
if(isMax) {
let best=-Infinity;
for(let i=0;i<9;i++){
if(b[i]) continue;
b[i]='O';
best=Math.max(best,minimax(b,depth+1,false,alpha,beta));
b[i]=null;
alpha=Math.max(alpha,best);
if(beta<=alpha) break;
}
return best;
} else {
let best=Infinity;
for(let i=0;i<9;i++){
if(b[i]) continue;
b[i]='X';
best=Math.min(best,minimax(b,depth+1,true,alpha,beta));
b[i]=null;
beta=Math.min(beta,best);
if(beta<=alpha) break;
}
return best;
}
}
function checkWin(b) {
for(const [a,bp,c] of WINS){
if(b[a]&&b[a]===b[bp]&&b[a]===b[c]) return b[a];
}
return null;
}
function getWinLine(b) {
for(const line of WINS){
const [a,bp,c]=line;
if(b[a]&&b[a]===b[bp]&&b[a]===b[c]) return line;
}
return null;
}
function endGame(result) {
gameOver=true;
const winLine=getWinLine(board);
if(winLine) winLine.forEach(i=>document.getElementById('c'+i).classList.add('win'));
if(result==='X'){scores.x++;setStatus('🎉 You win!');}
else if(result==='O'){scores.o++;setStatus('🤖 AI wins!');}
else{scores.d++;setStatus("🤝 It's a draw!");}
document.getElementById('score-x').textContent=scores.x;
document.getElementById('score-o').textContent=scores.o;
document.getElementById('score-d').textContent=scores.d;
}
function updateBoard() {
board.forEach((v,i)=>{
const cell=document.getElementById('c'+i);
cell.textContent=v||'';
cell.className='cell'+(v?' taken '+v.toLowerCase():'');
});
}
function setStatus(msg){document.getElementById('status').textContent=msg;}
function resetGame(){
document.getElementById('thinking').classList.remove('show');
init();
}
window.addEventListener('DOMContentLoaded',init);
function showControlsHint() {
var el = document.getElementById('gl-hint-tictactoeai');
if (!el) return;
el.style.animation = 'none';
void el.offsetWidth;
el.style.animation = 'hintFadeIn .4s ease both, hintFadeOut .4s ease 4s forwards';
}