<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@^1.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