Skip to content

Instantly share code, notes, and snippets.

@thomasweitzel
Last active October 15, 2024 15:49
Show Gist options
  • Save thomasweitzel/3fd3197616f0299a2935972edd68bd4a to your computer and use it in GitHub Desktop.
Save thomasweitzel/3fd3197616f0299a2935972edd68bd4a to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Game of Fifteen</title>
<link rel="stylesheet" href="https://unpkg.com/tailwindcss@^2.0/dist/tailwind.min.css">
</head>
<body class="m-4 bg-gray-100 antialiased">
<div id="board">
<div class="m-4 grid grid-flow-row grid-cols-4 grid-rows-4 h-64 w-64 gap-1"></div>
</div>
<div class="mt-4">
<button id="playButton" class="py-2 px-4 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-blue-600 shadow-sm hover:bg-blue-500 focus:outline-none focus:shadow-outline active:bg-blue-600 transition duration-150 ease-in-out" >
new game
</button>
</div>
<script defer>
window.addEventListener("load", () => {
const size = 16;
const boardSize = 4;
const tileClasses = 'p-2 h-full w-full text-center align-middle text-3xl text-red-900 font-bold bg-white border rounded shadow cursor-pointer select-none';
const winClasses = 'mt-4 bg-yellow-300 inline-block border border-gray-500 rounded-lg shadow-lg';
const playClasses = 'mt-4 bg-blue-300 inline-block border border-gray-500 rounded-lg shadow-lg';
const boardDiv = document.getElementById('board');
const emptyClasses = '';
const Direction = {
RIGHT: { row: -1, column: 0 },
UP: { row: 1, column: 0 },
LEFT: { row: 0, column: 1 },
DOWN: { row: 0, column: -1 },
};
const range0 = (limit) => [...Array(limit).keys()];
const init = () => {
document.querySelector("#playButton").addEventListener("click", play);
const parent = document.querySelector(".grid");
range0(size).forEach((i) => {
const div = document.createElement("div");
div.id = `f${i}`;
div.addEventListener("click", move(i));
parent.appendChild(div);
});
};
const move = (id) => () => {
if (!hasWon()) {
const emptyIndex = getEmptyIndex();
Object.values(Direction).forEach((direction) => swapIfPossible(id, emptyIndex, direction));
drawElement(id);
drawElement(emptyIndex);
drawBoard();
}
};
const swapIfPossible = (id, emptyId, direction) => {
const emptyCoords = indexToCoords(emptyId);
const neighborCoords = getNeighbor(direction, emptyCoords);
if (neighborCoords !== null && coordsToIndex(neighborCoords) === id) {
const temp = permutation[emptyId];
permutation[emptyId] = permutation[id];
permutation[id] = temp;
}
};
const drawElement = (id) => {
const element = document.getElementById(`f${id}`);
if (permutation[id] === size - 1) {
element.innerHTML = '';
element.className = emptyClasses;
} else {
element.innerHTML = `${permutation[id] + 1}`;
element.className = tileClasses;
}
};
const hasWon = () => range0(size).every((i) => permutation[i] === i);
const drawBoard = () => {
if (hasWon()) {
boardDiv.className = winClasses;
} else {
boardDiv.className = playClasses;
}
};
const draw = () => {
drawBoard();
range0(size).forEach((i) => drawElement(i));
};
const getRandomPermutation = () => {
const permutation = [];
const set = new Set(range0(size - 1));
while (set.size !== 0) {
const randomIndex = Math.floor(Math.random() * Math.floor(set.size));
const element = [...set][randomIndex];
permutation.push(element);
set.delete(element);
}
permutation.push(size - 1);
return permutation;
};
const parity = (p) => range0(size)
.map((i) =>
range0(size)
.filter((j) => i < j && p[i] > p[j])
.length
)
.reduce((agg, v) => agg + v, 0);
const isEvenPermutation = (p) => parity(p) % 2 === 0;
const getEvenPermutation = () => {
let p;
do {
p = getRandomPermutation();
} while (!isEvenPermutation(p))
return p;
};
const indexToCoords = (i) => ({ row: Math.trunc(i / boardSize), column: i % boardSize });
const coordsToIndex = (coords) => coords.row * boardSize + coords.column;
const getCellOrNull = (coords) => {
if (coords.row < 0 || coords.row >= boardSize || coords.column < 0 || coords.column >= boardSize) {
return null;
}
return coords;
};
const getNeighbor = (direction, coords) => getCellOrNull({ row: coords.row + direction.row, column: coords.column + direction.column });
const getEmptyIndex = () => range0(size).filter((i) => permutation[i] === size - 1)[0];
const play = () => {
permutation = getEvenPermutation();
draw();
}
// Lets play ...
let permutation;
init();
play();
});
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment