Created
September 10, 2025 16:45
-
-
Save iitalics/9f250946411ae5068185aa36839aeea2 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| <html> | |
| <head> | |
| <title>Tiles</title> | |
| <style> | |
| html { | |
| --alt1: oklch(0.68 0.17 276); | |
| --alt2: oklch(0.68 0.17 40); | |
| } | |
| .tiles { | |
| display: grid; | |
| grid-template: 1fr 1fr 1fr / 1fr 1fr 1fr; | |
| width: calc(48px * 3 + 16px * 2); | |
| height: calc(48px * 3 + 16px * 2); | |
| column-gap: 16px; | |
| row-gap: 16px; | |
| .tile { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| border: 4px solid transparent; | |
| border-radius: 8px; | |
| box-sizing: border-box; | |
| transition: all 0.1s; | |
| cursor: pointer; | |
| &.alt1 { | |
| background: var(--alt1); | |
| &.sel { border-color: var(--alt2); } | |
| } | |
| &.alt2 { | |
| background: var(--alt2); | |
| &.sel { border-color: var(--alt1); } | |
| } | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="tiles" class="tiles"> | |
| </div> | |
| <br/> | |
| <button id="reset">Reset</button> | |
| <button id="randomize">Randomize</button> | |
| <label><input type="checkbox" id="solve"> Show solution</label> | |
| <script> | |
| const solveElem = document.querySelector('#solve'); | |
| const tilesElem = document.querySelector('#tiles'); | |
| const cellElems = []; | |
| for (let y = 0; y < 3; y++) { | |
| cellElems.push([]); | |
| for (let x = 0; x < 3; x++) { | |
| const cellElem = document.createElement('div'); | |
| cellElems[y][x] = cellElem; | |
| tilesElem.appendChild(cellElem); | |
| } | |
| } | |
| let cells = []; | |
| let solution = []; | |
| function coordToPhase(x, y) { | |
| if (y === 0) { | |
| return x; | |
| } else if (y === 1) { | |
| if (x === 0) { | |
| return 7; | |
| } else { // x === 2 | |
| return 3; | |
| } | |
| } else { // y === 2 | |
| return 6 - x; | |
| } | |
| } | |
| function phaseToCoord(p) { | |
| if (p < 3) { | |
| return [p, 0]; | |
| } if (p === 3) { | |
| return [2, 1]; | |
| } else if (p < 7) { | |
| return [6 - p, 2]; | |
| } else { // p === 7 | |
| return [0, 1]; | |
| } | |
| } | |
| function updateSolution() { | |
| const flip = new Uint8Array(8); | |
| for (let i = 0; i < 8; i++) { | |
| const [x, y] = phaseToCoord(i); | |
| if (cells[y][x]) { | |
| for (let d of [0, 2, 3, 5, 6]) { | |
| flip[(i + d) % 8] ^= 1; | |
| } | |
| } | |
| } | |
| for (let i = 0; i < 8; i++) { | |
| const [x, y] = phaseToCoord(i); | |
| solution[y][x] = flip[i] === 0; | |
| } | |
| } | |
| function* affectedCoords(x, y) { | |
| const phase = coordToPhase(x, y); | |
| for (let delta = 7; delta <= 9; delta++) { | |
| const aphase = (phase + delta) % 8; | |
| yield phaseToCoord(aphase); | |
| } | |
| } | |
| function setupCell(x, y) { | |
| const elem = cellElems[y][x]; | |
| elem.classList.add('tile'); | |
| updateCell(x, y); | |
| elem.onmouseover = () => onCellMouseOver(x, y); | |
| elem.onmouseout = () => onCellMouseOut(); | |
| elem.onmousedown = () => onCellMouseDown(x, y); | |
| } | |
| function updateCell(x, y) { | |
| const elem = cellElems[y][x]; | |
| if (cells[y][x]) { | |
| elem.classList.remove('alt1'); | |
| elem.classList.add('alt2'); | |
| } else { | |
| elem.classList.remove('alt2'); | |
| elem.classList.add('alt1'); | |
| } | |
| if (solveElem.checked && solution[y][x]) { | |
| elem.innerText = '!'; | |
| } else { | |
| elem.innerText = ''; | |
| } | |
| } | |
| function onCellMouseOver(x, y) { | |
| for (const [ax, ay] of affectedCoords(x, y)) { | |
| const elem = cellElems[ay][ax]; | |
| elem.classList.add('sel'); | |
| } | |
| } | |
| function onCellMouseOut() { | |
| for (let y = 0; y < 3; y++) { | |
| for (let x = 0; x < 3; x++) { | |
| const elem = cellElems[y][x]; | |
| if (elem) { | |
| elem.classList.remove('sel'); | |
| } | |
| } | |
| } | |
| } | |
| function onCellMouseDown(x, y) { | |
| for (const [ax, ay] of affectedCoords(x, y)) { | |
| cells[ay][ax] = !cells[ay][ax]; | |
| } | |
| updateSolution(); | |
| for (const [ax, ay] of affectedCoords(x, y)) { | |
| updateCell(ax, ay); | |
| } | |
| } | |
| for (let y = 0; y < 3; y++) { | |
| cells.push([]); | |
| solution.push([]); | |
| for (let x = 0; x < 3; x++) { | |
| cells[y].push(x === 0 && y === 0); | |
| solution[y].push(false); | |
| } | |
| } | |
| updateSolution(); | |
| for (let y = 0; y < 3; y++) { | |
| for (let x = 0; x < 3; x++) { | |
| if (x === 1 && y === 1) { | |
| continue; | |
| } | |
| setupCell(x, y); | |
| } | |
| } | |
| document.querySelector('#randomize').onclick = () => { | |
| for (let i = 0; i < 16; i++) { | |
| const phase = Math.floor(Math.random() * 8); | |
| const [x, y] = phaseToCoord(phase); | |
| onCellMouseDown(x, y); | |
| } | |
| }; | |
| document.querySelector('#reset').onclick = () => { | |
| for (let phase = 0; phase < 8; phase++) { | |
| const [x, y] = phaseToCoord(phase); | |
| cells[y][x] = false; | |
| updateCell(x, y); | |
| } | |
| updateSolution(); | |
| onCellMouseOut(); | |
| }; | |
| solveElem.onchange = () => { | |
| for (let y = 0; y < 3; y++) { | |
| for (let x = 0; x < 3; x++) { | |
| if (x === 1 && y === 1) { | |
| continue; | |
| } | |
| updateCell(x, y); | |
| } | |
| } | |
| }; | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment