Skip to content

Commit e5190b4

Browse files
committed
wordsearch prototype
1 parent 33633ac commit e5190b4

7 files changed

Lines changed: 814 additions & 171 deletions

File tree

experiments/wordsearch/.llmignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
tests/
2+
*.md
3+
node_modules/
4+
.idea/

experiments/wordsearch/index.html

Lines changed: 56 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -17,72 +17,68 @@ <h1>Markov Wordsearch</h1>
1717
<button class="mode-tab" data-mode="play">Play</button>
1818
</div>
1919

20-
<fieldset>
21-
<legend>Grid</legend>
22-
<div class="row">
23-
<div>
24-
<label for="cfg-width">Width</label>
25-
<input id="cfg-width" type="number" min="5" max="40" value="15" />
26-
</div>
27-
<div>
28-
<label for="cfg-height">Height</label>
29-
<input id="cfg-height" type="number" min="5" max="40" value="15" />
20+
<div id="config-controls">
21+
<fieldset>
22+
<legend>Grid</legend>
23+
<div class="row">
24+
<div>
25+
<label for="cfg-width">Width</label>
26+
<input id="cfg-width" type="number" min="5" max="40" value="15" />
27+
</div>
28+
<div>
29+
<label for="cfg-height">Height</label>
30+
<input id="cfg-height" type="number" min="5" max="40" value="15" />
31+
</div>
3032
</div>
31-
</div>
32-
</fieldset>
33+
</fieldset>
3334

34-
<fieldset>
35-
<legend>Model</legend>
36-
<label for="cfg-order">Markov order (N)</label>
37-
<input id="cfg-order" type="number" min="0" max="6" value="3" />
38-
<label for="cfg-lattice">Lattice</label>
39-
<select id="cfg-lattice">
40-
<option value="square" selected>square (8-dir)</option>
41-
<option value="hex">hexagonal (6-dir)</option>
42-
<!-- <option value="triangular">triangular (8-dir)</option>-->
43-
</select>
44-
<label>
45-
<input id="cfg-no-backwards" type="checkbox" />
46-
Disable backwards-oriented directions
47-
</label>
35+
<fieldset>
36+
<legend>Model</legend>
37+
<label for="cfg-order">Markov order (N)</label>
38+
<input id="cfg-order" type="number" min="0" max="6" value="3" />
39+
<label for="cfg-lattice">Lattice</label>
40+
<select id="cfg-lattice">
41+
<option value="square" selected>square (8-dir)</option>
42+
<option value="hex">hexagonal (6-dir)</option>
43+
<!-- <option value="triangular">triangular (8-dir)</option>-->
44+
</select>
45+
<label>
46+
<input id="cfg-no-backwards" type="checkbox" />
47+
Disable backwards-oriented directions
48+
</label>
4849

49-
<label for="cfg-combiner">Combiner</label>
50-
<select id="cfg-combiner">
51-
<option value="product" selected>product (AND)</option>
52-
<option value="sum">sum (OR)</option>
53-
<option value="max">max</option>
54-
<option value="vote">vote</option>
55-
</select>
50+
<label for="cfg-combiner">Combiner</label>
51+
<select id="cfg-combiner">
52+
<option value="product" selected>product (AND)</option>
53+
<option value="sum">sum (OR)</option>
54+
<option value="max">max</option>
55+
<option value="vote">vote</option>
56+
</select>
5657

57-
<label for="cfg-sampling">Sampling</label>
58-
<select id="cfg-sampling">
59-
<option value="weighted" selected>weighted</option>
60-
<option value="argmax">argmax</option>
61-
</select>
62-
</fieldset>
58+
<label for="cfg-sampling">Sampling</label>
59+
<select id="cfg-sampling">
60+
<option value="weighted" selected>weighted</option>
61+
<option value="argmax">argmax</option>
62+
</select>
63+
</fieldset>
6364

64-
<fieldset>
65-
<legend>Reference text</legend>
66-
<label for="cfg-reffile">Upload text file</label>
67-
<input id="cfg-reffile" type="file" accept=".txt,text/plain" />
68-
<label for="cfg-reftext">Or paste text</label>
69-
<textarea id="cfg-reftext">
70-
the quick brown fox jumps over the lazy dog while the sun rises slowly over the quiet little village where people gather to tell stories of old times and distant lands beyond the misty mountains that rise against the morning sky</textarea
71-
>
72-
</fieldset>
65+
<fieldset>
66+
<legend>Reference text</legend>
67+
<label for="cfg-preset">Preset</label>
68+
<select id="cfg-preset"></select>
69+
<label for="cfg-reffile">Upload text file</label>
70+
<input id="cfg-reffile" type="file" accept=".txt,text/plain" />
71+
<label for="cfg-reftext">Or paste text</label>
72+
<textarea id="cfg-reftext"></textarea>
73+
</fieldset>
7374

74-
<fieldset>
75-
<legend>Target words</legend>
76-
<label for="cfg-words">One per line or comma-separated</label>
77-
<textarea id="cfg-words">
78-
fox
79-
village
80-
mountain
81-
story
82-
sun</textarea
83-
>
84-
<label><input id="cfg-debug" type="checkbox" /> Highlight placed words</label>
85-
</fieldset>
75+
<fieldset>
76+
<legend>Target words</legend>
77+
<label for="cfg-words">One per line or comma-separated</label>
78+
<textarea id="cfg-words"></textarea>
79+
<label><input id="cfg-debug" type="checkbox" /> Highlight placed words</label>
80+
</fieldset>
81+
</div>
8682

8783
<div id="panel-design">
8884
<div class="actions">

experiments/wordsearch/src/ui/app.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { renderGrid, gridToPNG, gridToText } from './render.js';
55
import { readConfig, wireFileUpload } from './controls.js';
66
import { initWatch, watchStep, watchPlay, watchPause, watchFinish } from './watchMode.js';
77
import { initPlay, stopPlay } from './playMode.js';
8+
import { populatePresetSelect, applyPreset, DEFAULT_PRESET } from './presets.js';
89

910
let lastGrid = null;
1011
let lastPlacement = null;
@@ -55,6 +56,9 @@ function setMode(root, next) {
5556
for (const [name, el] of Object.entries(panels)) {
5657
if (el) el.hidden = name !== mode;
5758
}
59+
// Hide configuration controls in play mode for a streamlined UI.
60+
const configControls = root.querySelector('#config-controls');
61+
if (configControls) configControls.hidden = mode === 'play';
5862
root.querySelectorAll('.mode-tab').forEach((b) => {
5963
b.classList.toggle('active', b.dataset.mode === mode);
6064
});
@@ -72,6 +76,16 @@ function setMode(root, next) {
7276

7377
export function initApp(root = document) {
7478
wireFileUpload(root);
79+
// Presets: populate dropdown, apply default, and re-apply on change.
80+
const presetEl = root.querySelector('#cfg-preset');
81+
if (presetEl) {
82+
populatePresetSelect(presetEl);
83+
applyPreset(root, presetEl.value || DEFAULT_PRESET);
84+
presetEl.addEventListener('change', () => {
85+
applyPreset(root, presetEl.value);
86+
regenerate(root);
87+
});
88+
}
7589

7690
const regenBtn = root.querySelector('#btn-regen');
7791
if (regenBtn) regenBtn.addEventListener('click', () => regenerate(root));

experiments/wordsearch/src/ui/playMode.js

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,33 @@ function clearSelectionClasses() {
6060
td.classList.remove('selecting');
6161
}
6262
}
63+
function flashWrong(cells) {
64+
for (const c of cells) {
65+
const td = cellAt(state.table, c.x, c.y);
66+
if (td) {
67+
td.classList.add('wrong-flash');
68+
setTimeout(() => td.classList.remove('wrong-flash'), 400);
69+
}
70+
}
71+
}
72+
function launchConfetti(root) {
73+
const host = root.querySelector('#grid') || document.body;
74+
const colors = ['#7c5cff', '#36d1ff', '#ff5cae', '#ff9d5c', '#38e8a0', '#ffd166'];
75+
const layer = document.createElement('div');
76+
layer.className = 'confetti-layer';
77+
for (let i = 0; i < 80; i++) {
78+
const bit = document.createElement('span');
79+
bit.className = 'confetti';
80+
bit.style.left = `${Math.random() * 100}%`;
81+
bit.style.background = colors[Math.floor(Math.random() * colors.length)];
82+
bit.style.animationDelay = `${Math.random() * 0.4}s`;
83+
bit.style.animationDuration = `${1.6 + Math.random() * 1.4}s`;
84+
bit.style.transform = `rotate(${Math.random() * 360}deg)`;
85+
layer.appendChild(bit);
86+
}
87+
host.appendChild(layer);
88+
setTimeout(() => layer.remove(), 3200);
89+
}
6390

6491
function markFound(cells) {
6592
for (const c of cells) {
@@ -87,14 +114,22 @@ function finishIfDone(root) {
87114
clearInterval(state.tick);
88115
const status = root.querySelector('#play-status');
89116
if (status) {
90-
status.textContent = `Solved in ${formatTime(Date.now() - state.startedAt)}!`;
117+
status.textContent = `🎉 Solved in ${formatTime(Date.now() - state.startedAt)}! 🎉`;
118+
status.classList.add('win');
91119
}
120+
launchConfetti(root);
92121
}
93122
}
94123

95124
export function initPlay(root, grid, placement) {
96125
if (state && state.tick) clearInterval(state.tick);
97126
const container = root.querySelector('#grid');
127+
for (const c of container.querySelectorAll('.confetti-layer')) c.remove();
128+
const prevStatus = root.querySelector('#play-status');
129+
if (prevStatus) {
130+
prevStatus.classList.remove('win');
131+
prevStatus.textContent = '';
132+
}
98133
const table = renderInteractiveGrid(container, grid, {
99134
lattice: grid.lattice || 'square',
100135
});
@@ -128,7 +163,12 @@ export function initPlay(root, grid, placement) {
128163
if (!cells) return;
129164
const matched = tryMatch(cells);
130165
const status = root.querySelector('#play-status');
131-
if (matched && status) status.textContent = `Found ${matched.toUpperCase()}!`;
166+
if (matched) {
167+
if (status) status.textContent = `✨ Found ${matched.toUpperCase()}!`;
168+
} else {
169+
if (status) status.textContent = `Not quite — keep looking!`;
170+
flashWrong(cells);
171+
}
132172
updateWordList(root);
133173
finishIfDone(root);
134174
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Selectable presets: reference text corpora and target word lists.
2+
// Each preset supplies a reference text (to train the Markov model) and a
3+
// list of target words to hide in the grid.
4+
5+
export const PRESETS = {
6+
science: {
7+
label: 'Science',
8+
referenceText:
9+
'energy flows through every living system as cells divide and molecules react ' +
10+
'under the laws of physics and chemistry while gravity pulls planets around ' +
11+
'distant stars and electrons orbit the nucleus of each atom experiments reveal ' +
12+
'how matter behaves and scientists observe nature to form theories about the ' +
13+
'universe its galaxies and the quantum particles that make up everything we know',
14+
words: ['atom', 'energy', 'planet', 'cell', 'gravity'],
15+
},
16+
fantasy: {
17+
label: 'Fantasy',
18+
referenceText:
19+
'beyond the misty mountains the brave knight rode toward the ancient castle where ' +
20+
'a sleeping dragon guarded a hoard of gold and a wise wizard cast spells of fire ' +
21+
'and frost while elves and dwarves gathered in the enchanted forest to seek the ' +
22+
'lost crown of the fallen kingdom and the magic sword that could break the curse ' +
23+
'binding the realm in eternal shadow',
24+
words: ['dragon', 'wizard', 'castle', 'sword', 'magic'],
25+
},
26+
kids: {
27+
label: 'Kids',
28+
referenceText:
29+
'the happy puppy played with a red ball in the sunny park while a little kitten ' +
30+
'chased butterflies near the flowers a friendly bear shared honey with his pals ' +
31+
'and the children laughed as they ran and jumped and sang silly songs all day ' +
32+
'long before going home to eat tasty cookies and drink warm milk and sleep tight',
33+
words: ['puppy', 'kitten', 'ball', 'bear', 'honey'],
34+
},
35+
computers: {
36+
label: 'Computers',
37+
referenceText:
38+
'the computer processes data using a central processor and stores information in ' +
39+
'memory while software programs run code that the machine executes byte by byte ' +
40+
'across the network packets travel between servers and clients as algorithms sort ' +
41+
'and search through arrays the keyboard sends input to the screen and the internet ' +
42+
'connects millions of devices sharing files through the cloud every single second',
43+
words: ['computer', 'memory', 'code', 'network', 'server'],
44+
},
45+
};
46+
47+
export const DEFAULT_PRESET = 'fantasy';
48+
49+
/** Apply a preset's text + words to the relevant form controls. */
50+
export function applyPreset(root, presetKey) {
51+
const preset = PRESETS[presetKey];
52+
if (!preset) return;
53+
const textEl = root.querySelector('#cfg-reftext');
54+
const wordsEl = root.querySelector('#cfg-words');
55+
if (textEl) textEl.value = preset.referenceText;
56+
if (wordsEl) wordsEl.value = preset.words.join('\n');
57+
}
58+
59+
/** Populate a <select> element with preset options. */
60+
export function populatePresetSelect(selectEl) {
61+
if (!selectEl) return;
62+
selectEl.innerHTML = '';
63+
for (const [key, preset] of Object.entries(PRESETS)) {
64+
const opt = document.createElement('option');
65+
opt.value = key;
66+
opt.textContent = preset.label;
67+
if (key === DEFAULT_PRESET) opt.selected = true;
68+
selectEl.appendChild(opt);
69+
}
70+
}

0 commit comments

Comments
 (0)