aboutsummaryrefslogtreecommitdiffstats
path: root/src/App.svelte
diff options
context:
space:
mode:
Diffstat (limited to 'src/App.svelte')
-rw-r--r--src/App.svelte268
1 files changed, 240 insertions, 28 deletions
diff --git a/src/App.svelte b/src/App.svelte
index 3a1a685..104a981 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -1,41 +1,63 @@
<script lang="ts">
+ import { getNewCoord } from "./lib/helpers";
+
import { readGridFromFile, save } from "./lib/Saving";
- import { validate, type Coord } from "./lib/Sudoku";
+ import { solve, validate, type Coord } from "./lib/Sudoku";
let selTile: HTMLDivElement | EventTarget;
+ let isImmu = false;
+ let selTileCoords: Coord = { x: -1, y: -1 };
let fileInput: HTMLInputElement;
const getSelTile = () => selTile as HTMLDivElement;
- const getCoord = (): Coord => ({
- x: parseInt(getSelTile().dataset.i),
- y: parseInt(getSelTile().dataset.j),
- });
let grid = [] as string[][]; //new Array(9).fill(new Array(9).fill("."));
+ let solvedGrid = [] as string[][]; //new Array(9).fill(new Array(9).fill("."));
let immutableTiles = [] as Coord[]; //new Array(9).fill(new Array(9).fill("."));
const tiles = [] as HTMLDivElement[][]; //new Array(9).fill(new Array(9).fill("."));
+ const doneNums = [] as HTMLDivElement[]; //new Array(9).fill(new Array(9).fill("."));
+ const doneNumsCount = [] as number[]; //new Array(9).fill(new Array(9).fill("."));
+ let hintTime = false;
+ let eraseTime = false;
+
for (let i = 0; i < 9; i++) {
grid.push([]);
tiles.push([]);
+ doneNumsCount.push(0);
for (let j = 0; j < 9; j++) {
grid[i].push(".");
- tiles[i].push(null);
+ // tiles[i].push(null);
}
}
function keyDownHandler(e: KeyboardEvent) {
const foundImmu = immutableTiles.find(
- (el) => el.x === getCoord().x && el.y === getCoord().y
+ (el) => el.x === selTileCoords.x && el.y === selTileCoords.y
);
- console.log(immutableTiles);
- console.log(foundImmu);
- if (foundImmu) return;
+ if (foundImmu) isImmu = true;
+
+ let key = e.key;
+ key = key === "j" ? "ArrowDown" : key;
+ key = key === "k" ? "ArrowUp" : key;
+ key = key === "h" ? "ArrowLeft" : key;
+ key = key === "l" ? "ArrowRight" : key;
+ const t = key.split("Arrow")[1]?.toLowerCase();
+ if (["right", "left", "up", "down"].includes(t) && selTile) {
+ setSelTile(null, getNewCoord(t, selTileCoords));
+ }
+ if (isImmu) return;
- if (e.key === "Backspace") {
- setSelTile(".");
+ let num: string | number = parseInt(e.key);
+ if (e.key === "Backspace" || e.key === "d" || eraseTime) {
+ setGrid(".", selTileCoords);
+ num = ".";
}
- const num = parseInt(e.key);
- if (Number.isNaN(num) || selTile == null || num === 0) return;
- setSelTile(num);
+ if (Number.isNaN(num) || selTile == null || num === 0 || eraseTime) return;
+ if (hintTime) {
+ setHint(num);
+ return;
+ }
+
+ setGrid(num.toString(), selTileCoords);
for (const tile of tiles.flat()) {
tile.classList.remove("err");
tile.classList.remove("good");
@@ -45,16 +67,29 @@
tiles[bad.x][bad.y].classList.add("err");
}
}
- // function showTile(val: string) {
- // return val === "." ? "" : val;
- // }
- function setSelTile(val: string | number) {
- grid[getCoord().x][getCoord().y] = val.toString();
- // getSelTile().innerText = val === "." ? "" : val.toString();
+ function setSelTile(
+ nst: HTMLDivElement | EventTarget | null,
+ x: number | Coord,
+ y?: number
+ ) {
+ if (selTile) getSelTile().classList.remove("selected");
+ if (typeof x === "object") {
+ y = x.y;
+ x = x.x;
+ }
+ if (y == undefined) throw new Error("y is undefinedly!");
+
+ if (nst) selTile = nst;
+ else selTile = tiles[x][y];
+ selTileCoords = { x, y };
+ if (eraseTime) setGrid(".", selTileCoords);
+ getSelTile().classList.add("selected");
}
async function load() {
- const maybeGrid = await readGridFromFile(fileInput.files[0]);
+ const fs = fileInput.files;
+ const maybeGrid = await readGridFromFile(fs ? fs[0] : undefined);
+ fileInput.value = "";
if (!maybeGrid) {
alert("Error while loading file");
return;
@@ -66,14 +101,63 @@
tiles[i][j].classList.add("const");
}
}
+ if (validate(maybeGrid).length > 0) {
+ alert("Can not load grid, not valid");
+ return;
+ }
grid = maybeGrid;
- fileInput.value = null;
+ solvedGrid = solve(grid);
+ }
+
+ function setGrid(val: string, ax: number | Coord, ay?: number) {
+ let x = -1,
+ y = -1;
+ if (typeof ax === "object") [x, y] = [ax.x, ax.y];
+ else if (ay != null) [x, y] = [ax, ay];
+ else throw new Error("setGrid() didnt get any good arguments");
+
+ const oldNum = grid[x][y];
+ grid[x][y] = val;
+
+ if (oldNum !== ".") doneNumsCount[parseInt(oldNum) - 1]--;
+ if (val !== ".") doneNumsCount[parseInt(val) - 1]++;
+ // else getSelTile().classList.remove("wrong", "good", "err");
+
+ if (solvedGrid.length > 0) {
+ if (grid[x][y] !== solvedGrid[x][y]) getSelTile().classList.add("wrong");
+ else getSelTile().classList.add("good");
+ }
+ // if (typeof x === "object") grid[x.x][x.y] = val;
+ // else if (y != null) grid[x][y] = val;
+ // else throw new Error("setGrid() didnt get any good arguments");
+ }
+ function setHint(num: number | string) {
+ const t = getSelTile();
+ // debugger;
+ const c = t.querySelector(`.hint-${num}`);
+ if (!c) return;
+ const cih = c.innerHTML;
+ if (cih === "") c.innerHTML = num.toString();
+ else c.innerHTML = "";
+ }
+
+ function solveGrid() {
+ if (validate(grid).length > 0) {
+ alert("Grid is not valid, can not solve");
+ return;
+ }
+ grid = solve(grid);
+ for (let i = 0; i < doneNumsCount.length; i++) doneNumsCount[i] = 9;
}
</script>
<!-- <svelte:body on:keydown|preventDefault|stopPropagation={keyDownHandler} /> -->
<svelte:body on:keydown={keyDownHandler} />
<main>
+ <div class="left">
+ <input type="file" on:change={load} bind:this={fileInput} />
+ <button on:click={() => save(grid)}>Save</button>
+ </div>
<div class="grid">
{#each grid as row, i}
{#each row as tile, j}
@@ -81,22 +165,74 @@
class="tile"
class:br={j % 3 === 2 && j !== 8}
class:bb={i % 3 === 2 && i !== 8}
- on:click|self={(e) => (selTile = e.target)}
+ on:click|self={(e) => setSelTile(e.target, i, j)}
data-i={i}
data-j={j}
bind:this={tiles[i][j]}
on:keypress={null}
>
{tile === "." ? "" : tile}
+ <span class="hint hint-1" />
+ <span class="hint hint-2" />
+ <span class="hint hint-3" />
+ <span class="hint hint-4" />
+ <span class="hint hint-5" />
+ <span class="hint hint-6" />
+ <span class="hint hint-7" />
+ <span class="hint hint-8" />
+ <span class="hint hint-9" />
</div>
{/each}
{/each}
</div>
- <button on:click={() => save(grid)}>Save</button>
- <input type="file" on:change={load} bind:this={fileInput} />
+ <div class="right" class:err={false}>
+ <button class="btn-solve" on:click={solveGrid}>Solve</button>
+ <div>
+ <label>
+ Hint:
+ <input type="checkbox" bind:checked={hintTime} />
+ </label>
+ <label>
+ Erase:
+ <input type="checkbox" bind:checked={eraseTime} />
+ </label>
+ <div class="small-grid">
+ {#each { length: 9 } as _, i}
+ <div bind:this={doneNums[i]} class:done={doneNumsCount[i] === 9}>
+ {i + 1}
+ </div>
+ {/each}
+ </div>
+ </div>
+ </div>
</main>
<style>
+ main {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ }
+ @media screen and (max-width: 1144px) {
+ main {
+ flex-direction: column;
+ }
+ .left {
+ min-height: 10rem;
+ align-items: center !important;
+ }
+ .right {
+ padding-top: 2rem;
+ }
+ }
+ .left {
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ gap: 2rem;
+ flex-direction: column;
+ padding-right: 3rem;
+ }
.grid {
display: grid;
grid-template-columns: repeat(9, 1fr);
@@ -111,6 +247,48 @@
gap: 0;
box-sizing: border-box;
user-select: none;
+ position: relative;
+ }
+ .hint {
+ font-size: 0.6rem;
+ position: absolute;
+ }
+ .right {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding-left: 3rem;
+ gap: 2rem;
+ }
+ .right > div {
+ display: flex;
+ flex-direction: column;
+ }
+ .btn-solve {
+ background-color: #ff000000;
+ border: 3px solid white;
+ color: white;
+ font-size: 1.5rem;
+ }
+ .small-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1rem;
+ margin-top: 2rem;
+ }
+ .small-grid > div {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 2rem;
+ width: 2rem;
+ line-height: 2rem;
+ padding: 0.8rem;
+ background-color: #aaaaaa;
+ }
+ .small-grid > div.done {
+ background-color: #00aaaa;
}
.tile:nth-child(odd) {
background-color: lightgrey;
@@ -124,10 +302,44 @@
.br {
border-right: 3px solid black;
}
- :global(.err) {
- background-color: rgba(150, 0, 0, 0.6) !important;
+ .err:nth-child(n) {
+ background-color: rgba(150, 0, 0, 0.6);
}
:global(.const) {
color: rebeccapurple;
}
+ :global(.wrong) {
+ color: #dd0000;
+ }
+ :global(.good) {
+ color: #00dd00;
+ }
+ @keyframes blink-animation {
+ 0% {
+ background-color: revert;
+ }
+ 50% {
+ background-color: pink;
+ }
+ 100% {
+ background-color: revert;
+ }
+ }
+ :global(.selected) {
+ animation: blink-animation 5s ease-in-out infinite;
+ }
+ @media print {
+ .left,
+ .right {
+ display: none;
+ }
+ .tile {
+ width: 5rem;
+ /* border: 1px solid black; */
+ print-color-adjust: exact;
+ }
+ :global(.selected) {
+ animation: none;
+ }
+ }
</style>