Word Search: extract pure selection/match logic + pin with vitest
Per Codex's Phase 2 audit notes. Moved the drag-snap (lineFrom) and find-match (matchWord) logic into $lib/wordsearch.js and added vitest coverage: - lineFrom always yields a straight, in-bounds path — a non-straight drag snaps, never returns bent; single cell and edge-clamping covered. - matchWord matches forward + reversed selections, is a harmless no-op on an already-found word (so completion/best-time can't double-record), and returns null for non-words / too-short selections. Restore behaviour audited: finish() (which records best-time) only runs when the final word is found mid-play; on refresh, restore() repopulates found cells + time and the derived status flips to done WITHOUT calling finish(), so best-time never re-records. First JS test runner for the frontend (npm test → vitest run). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import { getJSON } from '$lib/api.js';
|
||||
import { lineFrom, matchWord } from '$lib/wordsearch.js';
|
||||
|
||||
let { onstatus } = $props();
|
||||
|
||||
@@ -69,25 +70,6 @@
|
||||
return [r, c];
|
||||
}
|
||||
|
||||
// Snap a drag to the nearest of the 8 straight directions.
|
||||
function lineFrom(start, end) {
|
||||
const [r0, c0] = start, [r1, c1] = end;
|
||||
let dr = r1 - r0, dc = c1 - c0;
|
||||
if (dr === 0 && dc === 0) return [start];
|
||||
const adr = Math.abs(dr), adc = Math.abs(dc);
|
||||
if (adr > adc * 2) dc = 0;
|
||||
else if (adc > adr * 2) dr = 0;
|
||||
else { const m = Math.max(adr, adc); dr = Math.sign(dr) * m; dc = Math.sign(dc) * m; }
|
||||
const steps = Math.max(Math.abs(dr), Math.abs(dc));
|
||||
const sr = Math.sign(dr), sc = Math.sign(dc), cells = [];
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const r = r0 + sr * i, c = c0 + sc * i;
|
||||
if (r < 0 || r >= size || c < 0 || c >= size) break;
|
||||
cells.push([r, c]);
|
||||
}
|
||||
return cells.length ? cells : [start];
|
||||
}
|
||||
|
||||
function down(e) {
|
||||
if (status === 'done') return;
|
||||
selecting = true;
|
||||
@@ -98,7 +80,7 @@
|
||||
}
|
||||
function move(e) {
|
||||
if (!selecting) return;
|
||||
sel = lineFrom(sel[0], cellAt(e));
|
||||
sel = lineFrom(sel[0], cellAt(e), size);
|
||||
}
|
||||
function up() {
|
||||
if (!selecting) return;
|
||||
@@ -108,11 +90,7 @@
|
||||
}
|
||||
|
||||
function evaluate(cells) {
|
||||
if (cells.length < 2) return;
|
||||
const word = cells.map(([r, c]) => grid[r][c]).join('');
|
||||
const rev = [...word].reverse().join('');
|
||||
const hit = words.includes(word) && !found.includes(word) ? word
|
||||
: words.includes(rev) && !found.includes(rev) ? rev : null;
|
||||
const hit = matchWord(cells, grid, words, found);
|
||||
if (!hit) return;
|
||||
found = [...found, hit];
|
||||
const fc = new Set(foundCells);
|
||||
|
||||
Reference in New Issue
Block a user