diff --git a/src/App.svelte b/src/App.svelte index 3b74d2a..ae6261b 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -2,7 +2,7 @@ import svelteLogo from './assets/svelte.svg' import viteLogo from '/vite.svg' import Number from './lib/Number.svelte' - import { cfg, eStates, nStates, randInt, range } from "./utils"; + import { cfg, eStates, JsonTy, nStates, randInt, range } from "./utils"; import Line from "./lib/Line.svelte"; import { cat } from "./examples"; import { solve } from "./solver"; @@ -14,9 +14,21 @@ let numberState = Int8Array.from({ length: rows * cols }, () => 0) let hStates = Int8Array.from({ length: eRows * eCols }, () => 0) let vStates = Int8Array.from({ length: eRows * eCols }, () => 0) + const solutionHStates = hStates.slice() + const solutionVStates = vStates.slice() let grid: HTMLDivElement const editMode = true + let maskMode = false + + interface Checkpoint { + hStates: Int8Array + vStates: Int8Array + numbers: Int8Array + numberMask: Int8Array + } + let checkpoints: Checkpoint[] = JsonTy.parse(localStorage.getItem('slitherlink-checkpoints') ?? "[]") + const saveCheckpoints = () => localStorage.setItem('slitherlink-checkpoints', JsonTy.stringify(checkpoints)) // Run something on the edges of a cell function updateEdges(x: number, y: number, condition: (st: number) => boolean, op: (st: number) => number) { @@ -67,7 +79,7 @@ // Called when the user clicks on the grid function clickDiv(event: MouseEvent) { - if (editMode) return + if (maskMode) return // Compute the x and y coordinates of the clicked cell const rect = grid.getBoundingClientRect() @@ -100,47 +112,58 @@ const [ sx, sy, ex, ey ] = edge if (sx === ex) { // Vertical edge // The commented lines will show the solution (edges) - // vStates[sy * (eCols) + sx] = 1 + solutionVStates[sy * (eCols) + sx] = 1 if (ex != cols) numbers[sy * cols + sx] += 1 if (sx != 0) numbers[sy * cols + sx - 1] += 1 } else { - // hStates[sy * (eCols) + sx] = 1 + solutionHStates[sy * (eCols) + sx] = 1 if (ey != rows) numbers[sy * cols + sx] += 1 if (sy != 0) numbers[(sy - 1) * cols + sx] += 1 } }) -// const data = `....02.... -// 230....223 -// ...3..3... -// 3...22...1 -// .2.2..0.2. -// .2.3..3.3. -// 3...10...2 -// ...2..3... -// 303....331 -// ....02....` -// const lines = data.split('\n') -// numberMask = Int8Array.from({ length: rows * cols }, () => 1) -// lines.forEach((line, y) => { -// line.split('').forEach((ch, x) => { -// if (ch === '.') return -// numbers[y * cols + x] = parseInt(ch) -// numberMask[y * cols + x] = 0 -// }) -// }) - // Check the validity of all cells range(rows).forEach(y => range(cols).forEach(x => checkPos(x, y))) } // Mask the numbers function editModeClickNumber(event: MouseEvent, x: number, y: number) { - if (!editMode) return + if (!maskMode) return numberMask[y * cols + x] = numberMask[y * cols + x] === 0 ? 1 : 0 updateArea.forEach(([dx, dy, outer]) => outer ? 0 : clearAutoMark(x + dx, y + dy)) updateArea.forEach(([dx, dy, _]) => checkPos(x + dx, y + dy)) } + + async function editModeReduce(n = 20, zeroProb = 0.9) { + zeroProb = Math.max(0.5, zeroProb) + if (n === 0) return + console.log(n, zeroProb) + // On each iteration, randomly mask n numbers and try to solve the puzzle + // If the puzzle is solvable, continue until it's not solvable, then reduce n and unmask the last 3 numbers + const masked = [] + while (masked.length < n) { + let idx = randInt(0, rows * cols) + if (numberMask[idx] === 1) continue + // Reduce zeros first + if (numbers[idx] !== 0 && Math.random() < zeroProb) continue + numberMask[idx] = 1 + masked.push(idx) + } + console.log(masked.map(idx => numbers[idx])) + let {horiStates, vertStates, solvable} = await solve(rows, cols, numbers, numberMask) + // Unsolvable or doesn't match the solution + if (!solvable || + horiStates.some((st, idx) => (st === eStates.selected) !== (solutionHStates[idx] === eStates.selected)) || + vertStates.some((st, idx) => (st === eStates.selected) !== (solutionVStates[idx] === eStates.selected))) { + masked.forEach(idx => numberMask[idx] = 0) + setTimeout(() => editModeReduce(n - 1, zeroProb - 0.005), 100) + } else { + horiStates.forEach((st, idx) => hStates[idx] = st) + vertStates.forEach((st, idx) => vStates[idx] = st) + setTimeout(() => editModeReduce(n, zeroProb - 0.005), 100) + } + console.log(numberMask) + }
@@ -175,13 +198,67 @@ {#if editMode}
+ {#if maskMode} + + + + {:else} + + + + + + {/if} + +
+ +
Checkpoints
+ +
+ checkpoints.push({hStates: hStates.slice(), vStates: vStates.slice(), numbers: numbers.slice(), numberMask: numberMask.slice()}) + saveCheckpoints() + }}>Add + + +
+
+ {#each checkpoints as checkpoint, i} + + {/each}
{/if}
@@ -217,4 +294,10 @@ .logo height: 3em padding: 1.5em + + .btn-div + display: flex + gap: 1em + flex-wrap: wrap + justify-content: center