Last active
October 15, 2024 15:49
-
-
Save thomasweitzel/3fd3197616f0299a2935972edd68bd4a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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