Files
2023-11-16 13:14:44 -08:00

153 lines
3.2 KiB
HTML

<title>Tic Tac Toe</title>
<meta charset="UTF-8" />
<style>
body,
td,
input {
font-size: 24px;
line-height: 1.5;
text-align: center;
font-family: system-ui, sans-serif;
}
td {
height: 3rem;
width: 3rem;
text-align: center;
}
table {
margin: 5rem auto;
}
@media (prefers-color-scheme: dark) {
body,
input {
background: #222;
color: #ccc;
}
}
</style>
<h1>Tic Tac Toe</h1>
<table>
<tr>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
</tr>
<tr>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
</tr>
<tr>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
</tr>
</table>
<h2 hidden>Game over</h2>
<script>
// Initialization
const boxes = Array.from(document.querySelectorAll("td"));
const inputs = Array.from(document.querySelectorAll("[type=checkbox]"));
const rows = [1, 2, 3].map((n) =>
Array.from(document.querySelectorAll(`tr:nth-child(${n}) td`))
);
const columns = [1, 2, 3].map((n) =>
Array.from(document.querySelectorAll(`td:nth-child(${n})`))
);
const diagonals = [
[
[1, 1],
[2, 2],
[3, 3],
],
[
[3, 1],
[2, 2],
[1, 3],
],
].map((set) =>
set.map(([x, y]) =>
document.querySelector(`tr:nth-child(${x}) td:nth-child(${y})`)
)
);
const gameOver = document.querySelector("h2");
const HUMAN = "🧑‍💻";
const ROBOT = "🤖";
// Decide who goes first
if (Math.random() > 0.5) {
runRobotTurn();
}
// Handle when a user clicks an input
for (const input of inputs) {
input.addEventListener("click", handleClickInput);
}
// The user chose a box, check for win, robot turn, check for win
function handleClickInput(evt) {
evt.target.closest("td").innerHTML = HUMAN;
if (hasWin()) {
return endGame();
}
runRobotTurn();
if (hasWin()) {
return endGame();
}
}
// Choose a random available box
function runRobotTurn() {
const robotBoxes = shuffle(boxes);
while (robotBoxes.length) {
const box = robotBoxes.shift();
if (box.querySelector("input")) {
box.innerHTML = ROBOT;
break;
}
}
}
// Determine win
function hasWin() {
return (
rows.some(hasAllSame) ||
columns.some(hasAllSame) ||
diagonals.some(hasAllSame)
);
}
// Determine if every cell matches
function hasAllSame(arr) {
return arr.every(
(td) => !td.querySelector("input") && td.innerHTML === arr[0].innerHTML
);
}
// Display game over, cancel events
function endGame() {
gameOver.hidden = false;
for (const input of inputs) {
input.removeEventListener("click", handleClickInput);
input.remove();
}
}
// Shuffle a copy of the input array
function shuffle(array) {
array = array.slice(0);
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * i);
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
</script>