Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@thomasweitzel
Last active April 30, 2021 09:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomasweitzel/3fd3197616f0299a2935972edd68bd4a to your computer and use it in GitHub Desktop.
Save thomasweitzel/3fd3197616f0299a2935972edd68bd4a to your computer and use it in GitHub Desktop.
<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 id="f0" class=""></div>
<div id="f1" class=""></div>
<div id="f2" class=""></div>
<div id="f3" class=""></div>
<div id="f4" class=""></div>
<div id="f5" class=""></div>
<div id="f6" class=""></div>
<div id="f7" class=""></div>
<div id="f8" class=""></div>
<div id="f9" class=""></div>
<div id="f10" class=""></div>
<div id="f11" class=""></div>
<div id="f12" class=""></div>
<div id="f13" class=""></div>
<div id="f14" class=""></div>
<div id="f15" class=""></div>
</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" onclick="play()" >
new game
</button>
</div>
</body>
<script>
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: 1,
UP: 2,
LEFT: 3,
DOWN: 4,
};
const range0 = (limit) => [...Array(limit).keys()];
const init = () => {
range0(size).forEach((i) => document.getElementById(`f${i}`).addEventListener("click", move(i)));
};
const move = (id) => (_) => {
if (!hasWon()) {
const emptyIndex = getEmptyIndex();
swapIfPossible(id, emptyIndex, Direction.UP);
swapIfPossible(id, emptyIndex, Direction.LEFT);
swapIfPossible(id, emptyIndex, Direction.DOWN);
swapIfPossible(id, emptyIndex, Direction.RIGHT);
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) => {
let result;
switch (direction) {
case Direction.RIGHT:
result = getCellOrNull({ row: coords.row - 1, column: coords.column });
break;
case Direction.UP:
result = getCellOrNull({ row: coords.row + 1, column: coords.column });
break;
case Direction.LEFT:
result = getCellOrNull({ row: coords.row, column: coords.column + 1 });
break;
case Direction.DOWN:
result = getCellOrNull({ row: coords.row, column: coords.column - 1 });
break;
}
return result;
};
const getEmptyIndex = () => range0(size).filter((i) => permutation[i] === size - 1)[0];
const play = () => {
permutation = getEvenPermutation();
draw();
}
// Lets play ...
let permutation;
init();
play();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment