tic-tac-toe / index.html
sudo-saidso's picture
Update index.html
03684bd verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tic-Tac-Toe AI</title>
<style>
:root {
--primary: #0f172a;
--secondary: #1e293b;
--accent: #0ea5e9;
--win: #10b981;
--lose: #ef4444;
--draw: #f59e0b;
--text: #e2e8f0;
--subtle: #64748b;
--cell-size: min(25vw, 120px);
--transition: all .3s cubic-bezier(.4,0,.2,1);
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', system-ui, sans-serif; }
body {
display: grid;
place-content: center;
min-height: 100vh;
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: var(--text);
padding: 2rem;
position: relative;
}
h1 {
font-size: clamp(1.5rem, 4vw, 2.5rem);
font-weight: 700;
text-align: center;
margin-bottom: 1rem;
letter-spacing: .05em;
}
#status {
text-align: center;
font-size: 1.125rem;
min-height: 1.5em;
margin-bottom: 1.5rem;
transition: var(--transition);
}
#board {
display: grid;
grid-template-columns: repeat(3, var(--cell-size));
grid-template-rows: repeat(3, var(--cell-size));
gap: 8px;
margin: 0 auto 2rem;
position: relative;
}
.cell {
background: var(--secondary);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: calc(var(--cell-size) * .45);
cursor: pointer;
transition: var(--transition);
user-select: none;
}
.cell:hover { background: #334155; }
.cell.x { color: var(--accent); }
.cell.o { color: var(--win); }
.cell.disabled { pointer-events: none; }
#score {
display: flex;
justify-content: space-evenly;
gap: 1rem;
margin-bottom: 1.5rem;
font-size: 1rem;
}
.score-item {
display: flex;
flex-direction: column;
align-items: center;
gap: .25rem;
color: var(--subtle);
}
.score-item span:last-child {
font-size: 1.5rem;
font-weight: 700;
color: var(--text);
}
button {
display: block;
margin: 0 auto;
padding: .75rem 2rem;
border: none;
border-radius: 999px;
background: var(--accent);
color: #fff;
font-size: 1rem;
cursor: pointer;
transition: var(--transition);
}
button:hover { background: #0284c7; transform: translateY(-2px); }
button:active { transform: translateY(0); }
.win-line {
position: absolute;
background: var(--win);
border-radius: 4px;
transition: var(--transition);
opacity: 0;
}
.win-line.show { opacity: 1; }
.hf-link {
position: fixed;
bottom: 15px;
left: 15px;
background: rgba(14, 165, 233, 0.15);
padding: 6px 12px;
border-radius: 8px;
font-size: 0.9rem;
color: var(--accent);
text-decoration: none;
transition: var(--transition);
}
.hf-link:hover {
background: var(--accent);
color: #fff;
}
@media (max-width: 400px) {
#board {
grid-template-columns: repeat(3, 80px);
grid-template-rows: repeat(3, 80px);
}
}
</style>
</head>
<body>
<h1>Tic-Tac-Toe vs AI</h1>
<div id="status">Your turn (X)</div>
<div id="board">
<div class="cell" data-index="0"></div>
<div class="cell" data-index="1"></div>
<div class="cell" data-index="2"></div>
<div class="cell" data-index="3"></div>
<div class="cell" data-index="4"></div>
<div class="cell" data-index="5"></div>
<div class="cell" data-index="6"></div>
<div class="cell" data-index="7"></div>
<div class="cell" data-index="8"></div>
<div class="win-line"></div>
</div>
<div id="score">
<div class="score-item">
<span>You</span>
<span id="playerScore">0</span>
</div>
<div class="score-item">
<span>Draws</span>
<span id="drawScore">0</span>
</div>
<div class="score-item">
<span>Bot</span>
<span id="botScore">0</span>
</div>
</div>
<button onclick="restartGame()">Restart Game</button>
<!-- Hugging Face link -->
<a href="https://huggingface.co/spaces/sudo-saidso/tic-tac-toe" target="_blank" class="hf-link">
🤖 on HuggingFace
</a>
<script>
const cells = document.querySelectorAll('.cell');
const statusEl = document.getElementById('status');
const winLine = document.querySelector('.win-line');
let board = Array(9).fill(null);
let gameOver = false;
let scores = { player: 0, draw: 0, bot: 0 };
const winPatterns = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]
];
cells.forEach(c => c.addEventListener('click', handlePlayerMove));
function handlePlayerMove(e) {
const idx = e.target.dataset.index;
if (board[idx] || gameOver) return;
makeMove(idx, 'x');
if (!gameOver) {
statusEl.textContent = 'Bot is thinking...';
setTimeout(() => makeMove(bestMove(), 'o'), 400);
}
}
function makeMove(idx, player) {
board[idx] = player;
cells[idx].textContent = player === 'x' ? 'X' : 'O';
cells[idx].classList.add(player, 'disabled');
const winner = checkWinner();
if (winner) endGame(winner);
else if (board.every(Boolean)) endGame('draw');
else statusEl.textContent = 'Your turn (X)';
}
function checkWinner(b = board) {
for (const [a, bIdx, c] of winPatterns) {
if (b[a] && b[a] === b[bIdx] && b[a] === b[c]) return b[a];
}
return null;
}
function endGame(result) {
gameOver = true;
cells.forEach(c => c.classList.add('disabled'));
if (result === 'draw') {
statusEl.textContent = 'It\'s a draw!';
scores.draw++;
document.getElementById('drawScore').textContent = scores.draw;
} else {
if (result === 'x') {
statusEl.textContent = 'You win!';
scores.player++;
document.getElementById('playerScore').textContent = scores.player;
} else {
statusEl.textContent = 'Bot wins!';
scores.bot++;
document.getElementById('botScore').textContent = scores.bot;
}
}
}
function bestMove() {
return minimax(board, 'o').index;
}
function minimax(newBoard, player) {
const availSpots = newBoard.map((v, i) => v === null ? i : null).filter(v => v !== null);
if (checkWinner(newBoard) === 'x') return { score: -10 };
if (checkWinner(newBoard) === 'o') return { score: 10 };
if (availSpots.length === 0) return { score: 0 };
const moves = [];
for (let i = 0; i < availSpots.length; i++) {
const move = {};
move.index = availSpots[i];
newBoard[availSpots[i]] = player;
move.score = (player === 'o')
? minimax(newBoard, 'x').score
: minimax(newBoard, 'o').score;
newBoard[availSpots[i]] = null;
moves.push(move);
}
let bestMove;
if (player === 'o') {
let bestScore = -Infinity;
for (let i = 0; i < moves.length; i++) {
if (moves[i].score > bestScore) {
bestScore = moves[i].score;
bestMove = i;
}
}
} else {
let bestScore = Infinity;
for (let i = 0; i < moves.length; i++) {
if (moves[i].score < bestScore) {
bestScore = moves[i].score;
bestMove = i;
}
}
}
return moves[bestMove];
}
function restartGame() {
board = Array(9).fill(null);
gameOver = false;
cells.forEach(c => {
c.textContent = '';
c.className = 'cell';
});
winLine.className = 'win-line';
statusEl.textContent = 'Your turn (X)';
}
</script>
</body>
</html>