Games: in-progress hub status + distribution-aware word-search placement (Codex)

- Play hub: word cards now surface IN-PROGRESS games too (not just won/lost) so
  "continue on another device" shows at a glance — card reads "5:3…" and the
  selection option says "Continue · 3/6".
- Word Search generator: replace "prefer any crossing" with a SCORED placement —
  score = overlap*4 - local crowding (filled neighbours that aren't crossings) —
  then pick among the best ~20%. Keeps the organic interlocking but spreads words
  across the board instead of clumping around the first-placed (longest) words.
  Every word still placed (tests green). NOTE: changes today's grid layouts, so
  an in-progress word search resets once.

237 pytest + 11 vitest green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-12 15:18:04 -04:00
parent de59cf49d8
commit 64339aafb0
2 changed files with 35 additions and 14 deletions
+9 -4
View File
@@ -24,8 +24,11 @@
function readWord(v) {
try {
const s = JSON.parse(localStorage.getItem(`goodnews:word:${v}:${date}`) || 'null');
if (s && (s.status === 'won' || s.status === 'lost')) {
return { status: s.status, tries: (s.guesses || []).length, max: v === '6' ? 7 : 6 };
const tries = (s?.guesses || []).length;
// Surface in-progress too (so "continue on another device" shows on the card),
// not just finished games.
if (s && (s.status === 'won' || s.status === 'lost' || (s.status === 'playing' && tries > 0))) {
return { status: s.status, tries, max: v === '6' ? 7 : 6 };
}
} catch { /* ignore */ }
return null;
@@ -79,7 +82,7 @@
function wordLabel() {
const a = wordStatus['5'], b = wordStatus['6'];
if (!a && !b) return 'Guess the days word';
const part = (s, mx) => s ? (s.status === 'won' ? `${s.tries}/${mx}` : 'X') : '';
const part = (s, mx) => !s ? '' : s.status === 'won' ? `${s.tries}/${mx}` : s.status === 'lost' ? 'X' : `${s.tries}…`;
return `Today · 5:${part(a, 6)} 6:${part(b, 7)}`;
}
function wsHubLabel() {
@@ -93,7 +96,9 @@
function wordOpt(v) {
const s = wordStatus[v];
if (!s) return 'Play';
return s.status === 'won' ? `Solved ${s.tries}/${s.max}` : 'Out of guesses';
if (s.status === 'won') return `Solved ${s.tries}/${s.max}`;
if (s.status === 'lost') return 'Out of guesses';
return `Continue · ${s.tries}/${s.max}`;
}
function wsOpt(sz) {
const s = readWsSize(sz);