naider 1.24.2 → 1.24.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/naide.js +1 -1
- package/mcp/server.js +1 -1
- package/package.json +1 -1
- package/src/gen.js +388 -355
package/bin/naide.js
CHANGED
|
@@ -8,7 +8,7 @@ import { spawn } from 'child_process';
|
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
9
|
const _require = createRequire(import.meta.url);
|
|
10
10
|
|
|
11
|
-
const NAIDE_VERSION = '1.24.
|
|
11
|
+
const NAIDE_VERSION = '1.24.3';
|
|
12
12
|
|
|
13
13
|
function crashReport(err, context = {}) {
|
|
14
14
|
const info = [
|
package/mcp/server.js
CHANGED
|
@@ -192,7 +192,7 @@ async function handleMessage(msg) {
|
|
|
192
192
|
respond(msg.id, {
|
|
193
193
|
protocolVersion: '2024-11-05',
|
|
194
194
|
capabilities: { tools: {} },
|
|
195
|
-
serverInfo: { name: 'naide-mcp', version: '1.24.
|
|
195
|
+
serverInfo: { name: 'naide-mcp', version: '1.24.3' }
|
|
196
196
|
});
|
|
197
197
|
break;
|
|
198
198
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naider",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.3",
|
|
4
4
|
"description": "NAIDE - Simpler than Python, compiles to 15 targets. AI-specialized language with 35+ built-in functions, syntax sugar (unless/until/repeat/swap/is/isnt), and 47 features. Targets: Node.js, Python, TypeScript, C, C++, Java, Go, Rust, PHP, Ruby, Kotlin, Swift, Dart, C#, Bun.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
package/src/gen.js
CHANGED
|
@@ -1781,370 +1781,403 @@ function migrationFile(entities, relationships, m2mRels) {
|
|
|
1781
1781
|
|
|
1782
1782
|
// ── Code Composer ───────────────────────────────────────────
|
|
1783
1783
|
|
|
1784
|
-
// ── Game
|
|
1785
|
-
|
|
1786
|
-
const
|
|
1787
|
-
:
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
.
|
|
1799
|
-
.
|
|
1784
|
+
// ── Game Engine (compositional) ──────────────────────────
|
|
1785
|
+
|
|
1786
|
+
const GAME_PALETTES = {
|
|
1787
|
+
tapping: { bg:'#1a0a2e', sf:'#2d1a42', pr:'#FF6B35', sc:'#FF3864', ac:'#FFD700', tx:'#fff', mt:'#a89cc8' },
|
|
1788
|
+
quiz: { bg:'#0d1b2a', sf:'#1b2838', pr:'#3A86FF', sc:'#8338EC', ac:'#06D6A0', tx:'#e8ecf1', mt:'#7b8ca3' },
|
|
1789
|
+
memory: { bg:'#1a0f25', sf:'#2d1f3d', pr:'#C77DFF', sc:'#E05297', ac:'#FF6B9D', tx:'#f5f0ff', mt:'#9b8bb4' },
|
|
1790
|
+
snake: { bg:'#071a0e', sf:'#0f2918', pr:'#4ADE80', sc:'#22D3EE', ac:'#FCD34D', tx:'#e8f5e9', mt:'#6b8a7a' },
|
|
1791
|
+
typing: { bg:'#0a192f', sf:'#112240', pr:'#64FFDA', sc:'#00B4D8', ac:'#CCD6F6', tx:'#CCD6F6', mt:'#5c7a8a' },
|
|
1792
|
+
reaction: { bg:'#18181b', sf:'#27272a', pr:'#F87171', sc:'#EF4444', ac:'#4ADE80', tx:'#fafafa', mt:'#71717a' },
|
|
1793
|
+
breakout: { bg:'#0c0a1d', sf:'#1c1833', pr:'#A78BFA', sc:'#8B5CF6', ac:'#F59E0B', tx:'#f0f0ff', mt:'#8f85a3' },
|
|
1794
|
+
};
|
|
1795
|
+
|
|
1796
|
+
function extractGameOpts(raw) {
|
|
1797
|
+
const o = {};
|
|
1798
|
+
const d = raw.match(/(\d+)\s*(?:sec|seconds|秒|s\b)/i);
|
|
1799
|
+
if (d) o.duration = Math.min(120, Math.max(3, parseInt(d[1])));
|
|
1800
|
+
const sz = raw.match(/(\d+)\s*x\s*(\d+)/i);
|
|
1801
|
+
if (sz) { o.cols = parseInt(sz[1]); o.rows = parseInt(sz[2]); }
|
|
1802
|
+
if (/fast|高速|はやい/i.test(raw)) o.speed = 'fast';
|
|
1803
|
+
if (/hard|難しい|ハード/i.test(raw)) o.difficulty = 'hard';
|
|
1804
|
+
if (/easy|簡単|イージー/i.test(raw)) o.difficulty = 'easy';
|
|
1805
|
+
return o;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
function cssVars(p) {
|
|
1809
|
+
return `:root{--bg:${p.bg};--sf:${p.sf};--pr:${p.pr};--sc:${p.sc};--ac:${p.ac};--tx:${p.tx};--mt:${p.mt}}`;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
function cssBase(p) {
|
|
1813
|
+
return `*{margin:0;padding:0;box-sizing:border-box}
|
|
1814
|
+
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;background:${p.bg};color:${p.tx};min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent}
|
|
1815
|
+
.ct{width:min(420px,92vw);text-align:center}
|
|
1816
|
+
h1{font-size:1.8rem;margin-bottom:.5rem;background:linear-gradient(135deg,${p.pr},${p.sc});-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
|
1817
|
+
.sc{font-size:3rem;font-weight:800;margin:.5rem 0;font-variant-numeric:tabular-nums}
|
|
1818
|
+
.btn{display:inline-block;padding:.75rem 2rem;border:none;border-radius:12px;font-size:1.1rem;font-weight:600;cursor:pointer;transition:transform .1s,box-shadow .2s;color:#fff;background:linear-gradient(135deg,${p.pr},${p.sc})}
|
|
1819
|
+
.btn:active{transform:scale(.95)}.btn:hover{box-shadow:0 0 24px ${p.pr}66}
|
|
1820
|
+
.st{display:flex;gap:1.5rem;justify-content:center;margin:.75rem 0;font-size:.9rem;color:${p.mt}}
|
|
1821
|
+
.st span{display:flex;flex-direction:column;align-items:center;gap:.2rem}.st b{color:${p.tx};font-size:1.1rem}
|
|
1822
|
+
.hd{display:none!important}.hi{color:${p.ac}}
|
|
1800
1823
|
@keyframes pop{0%{transform:scale(1)}50%{transform:scale(1.15)}100%{transform:scale(1)}}
|
|
1801
1824
|
@keyframes fadeUp{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-60px)}}`;
|
|
1825
|
+
}
|
|
1802
1826
|
|
|
1803
|
-
function
|
|
1804
|
-
return
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
.
|
|
1811
|
-
|
|
1812
|
-
.
|
|
1813
|
-
.
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
<
|
|
1819
|
-
<
|
|
1820
|
-
<
|
|
1821
|
-
<
|
|
1822
|
-
<
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
<
|
|
1828
|
-
<div class="
|
|
1829
|
-
</
|
|
1830
|
-
|
|
1831
|
-
<div class="score" id="final">0</div>
|
|
1832
|
-
<div class="result" id="summary"></div>
|
|
1833
|
-
<button class="btn" style="margin-top:1rem" onclick="startGame()">Play Again</button>
|
|
1834
|
-
</div>
|
|
1827
|
+
function cssTimerBar(p) {
|
|
1828
|
+
return `.bar{width:100%;height:6px;background:${p.sf};border-radius:3px;margin:.5rem 0;overflow:hidden}
|
|
1829
|
+
.bar .fl{height:100%;background:linear-gradient(90deg,${p.ac},${p.pr});transition:width .1s linear;border-radius:3px}`;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
function jsCore(storageKey) {
|
|
1833
|
+
return `const $=id=>document.getElementById(id);
|
|
1834
|
+
let best=+(localStorage.getItem('${storageKey}')||0);
|
|
1835
|
+
if($('best'))$('best').textContent=best;
|
|
1836
|
+
function show(s){document.querySelectorAll('[id^=s-]').forEach(e=>e.classList.add('hd'));$('s-'+s).classList.remove('hd')}
|
|
1837
|
+
function saveBest(v){if(v>best){best=v;localStorage.setItem('${storageKey}',best);if($('best'))$('best').textContent=best;return true}return false}`;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
function htmlWrap(title, css, sections, js) {
|
|
1841
|
+
return `<!DOCTYPE html>
|
|
1842
|
+
<html lang="en">
|
|
1843
|
+
<head>
|
|
1844
|
+
<meta charset="UTF-8">
|
|
1845
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
|
|
1846
|
+
<title>${title}</title>
|
|
1847
|
+
<style>
|
|
1848
|
+
${css}
|
|
1849
|
+
</style>
|
|
1850
|
+
</head>
|
|
1851
|
+
<body>
|
|
1852
|
+
<div class="ct">
|
|
1853
|
+
<h1>${title}</h1>
|
|
1854
|
+
${sections}
|
|
1835
1855
|
</div>
|
|
1836
1856
|
<script>
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
.
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
.
|
|
1857
|
-
.
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
.
|
|
1861
|
-
</
|
|
1862
|
-
<
|
|
1863
|
-
<
|
|
1864
|
-
<div id="
|
|
1865
|
-
<
|
|
1866
|
-
<div class="
|
|
1867
|
-
<
|
|
1868
|
-
|
|
1869
|
-
<div
|
|
1870
|
-
<div
|
|
1871
|
-
<
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
<
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
{
|
|
1892
|
-
{
|
|
1893
|
-
{
|
|
1894
|
-
|
|
1895
|
-
{
|
|
1896
|
-
{
|
|
1897
|
-
{
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
<
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
.
|
|
1922
|
-
.
|
|
1923
|
-
.
|
|
1924
|
-
|
|
1925
|
-
<
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1857
|
+
${js}
|
|
1858
|
+
</script>
|
|
1859
|
+
</body>
|
|
1860
|
+
</html>`;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
// ── Tapping Game Builder ──
|
|
1864
|
+
|
|
1865
|
+
function buildTapping(title, opts) {
|
|
1866
|
+
const p = GAME_PALETTES.tapping;
|
|
1867
|
+
const dur = opts.duration || 10;
|
|
1868
|
+
|
|
1869
|
+
const css = [cssVars(p), cssBase(p), cssTimerBar(p),
|
|
1870
|
+
`.tap{width:min(300px,75vw);height:min(300px,75vw);border-radius:50%;background:radial-gradient(circle at 35% 35%,${p.pr},${p.sc});display:flex;align-items:center;justify-content:center;margin:1.5rem auto;cursor:pointer;position:relative;font-size:3.5rem;font-weight:900;transition:box-shadow .15s;letter-spacing:-.05em}`,
|
|
1871
|
+
`.tap:active{box-shadow:0 0 60px ${p.pr}88}`,
|
|
1872
|
+
`.tap.on{animation:pulse 1s infinite}`,
|
|
1873
|
+
`@keyframes pulse{0%,100%{box-shadow:0 0 0 0 ${p.pr}66}50%{box-shadow:0 0 0 20px ${p.pr}00}}`,
|
|
1874
|
+
`.rp{position:absolute;border-radius:50%;background:rgba(255,255,255,.25);animation:rpAn .5s ease-out forwards;pointer-events:none}`,
|
|
1875
|
+
`@keyframes rpAn{0%{width:0;height:0;opacity:.6}100%{width:200px;height:200px;opacity:0}}`,
|
|
1876
|
+
`.cb{position:fixed;font-weight:800;font-size:1.5rem;color:${p.ac};pointer-events:none;animation:fadeUp .6s ease-out forwards}`,
|
|
1877
|
+
].join('\n');
|
|
1878
|
+
|
|
1879
|
+
const html = `<div id="s-menu">
|
|
1880
|
+
<p style="color:${p.mt};margin-bottom:1.5rem">Tap as fast as you can — ${dur} seconds!</p>
|
|
1881
|
+
<div class="st"><span>Best<b id="best">0</b></span><span>Last<b id="last">-</b></span></div>
|
|
1882
|
+
<button class="btn" onclick="go()">Start</button></div>
|
|
1883
|
+
<div id="s-play" class="hd">
|
|
1884
|
+
<div class="bar"><div class="fl" id="bar" style="width:100%"></div></div>
|
|
1885
|
+
<div class="sc" id="cnt">0</div>
|
|
1886
|
+
<div class="tap on" id="tap" ontouchstart="hit(event)" onmousedown="hit(event)">TAP</div>
|
|
1887
|
+
<div class="st"><span>Combo<b id="cmb">x1</b></span><span>TPS<b id="tps">0</b></span></div></div>
|
|
1888
|
+
<div id="s-end" class="hd">
|
|
1889
|
+
<div class="sc" id="fin">0</div>
|
|
1890
|
+
<div id="msg" style="font-size:1.2rem;line-height:2"></div>
|
|
1891
|
+
<button class="btn" style="margin-top:1rem" onclick="go()">Again</button></div>`;
|
|
1892
|
+
|
|
1893
|
+
const js = [jsCore('tap_best'),
|
|
1894
|
+
`const DUR=${dur*1000};let taps,t0,raf,combo,lh;`,
|
|
1895
|
+
`function go(){taps=0;combo=1;lh=0;t0=Date.now();$('cnt').textContent='0';$('cmb').textContent='x1';$('bar').style.width='100%';show('play');raf=requestAnimationFrame(tick)}`,
|
|
1896
|
+
`function tick(){const e=Date.now()-t0,pc=Math.max(0,1-e/DUR)*100;$('bar').style.width=pc+'%';$('tps').textContent=(taps/Math.max(.1,e/1000)).toFixed(1);if(e>=DUR)return end();raf=requestAnimationFrame(tick)}`,
|
|
1897
|
+
`function hit(ev){ev.preventDefault();if(Date.now()-t0>DUR)return;taps++;const now=Date.now(),gap=now-lh;combo=gap<200?Math.min(combo+1,99):gap>500?1:combo;lh=now;$('cnt').textContent=taps;$('cmb').textContent='x'+combo;$('cnt').style.animation='none';void $('cnt').offsetWidth;$('cnt').style.animation='pop .15s';const el=$('tap'),rc=el.getBoundingClientRect(),cx=(ev.touches?ev.touches[0].clientX:ev.clientX)-rc.left,cy=(ev.touches?ev.touches[0].clientY:ev.clientY)-rc.top;const r=document.createElement('div');r.className='rp';r.style.cssText='left:'+(cx-100)+'px;top:'+(cy-100)+'px';el.appendChild(r);setTimeout(()=>r.remove(),500);if(combo>=3){const c=document.createElement('div');c.className='cb';c.textContent='x'+combo;c.style.cssText='left:'+Math.random()*60+20+'%;top:'+Math.random()*30+20+'%';document.body.appendChild(c);setTimeout(()=>c.remove(),600)}}`,
|
|
1898
|
+
`function end(){cancelAnimationFrame(raf);const tps=(taps/(DUR/1000)).toFixed(1);const nb=saveBest(taps);$('last').textContent=taps;$('fin').textContent=taps;const r=taps>=100?'Insane!':taps>=70?'Amazing!':taps>=50?'Great!':taps>=30?'Nice!':'Keep trying!';$('msg').innerHTML=r+'<br>'+tps+' taps/sec'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
1899
|
+
].join('\n');
|
|
1900
|
+
|
|
1901
|
+
return htmlWrap(title, css, html, js);
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// ── Quiz Game Builder ──
|
|
1905
|
+
|
|
1906
|
+
function buildQuiz(title, opts) {
|
|
1907
|
+
const p = GAME_PALETTES.quiz;
|
|
1908
|
+
const perQ = opts.duration || 15;
|
|
1909
|
+
|
|
1910
|
+
const css = [cssVars(p), cssBase(p),
|
|
1911
|
+
`.q{font-size:1.3rem;margin:1.5rem 0;min-height:3em;line-height:1.5}`,
|
|
1912
|
+
`.ch{display:flex;flex-direction:column;gap:.75rem;margin:1rem 0}`,
|
|
1913
|
+
`.ch div{padding:1rem;border:2px solid ${p.sf};border-radius:12px;background:${p.sf};cursor:pointer;font-size:1rem;text-align:left;transition:border-color .2s,background .2s}`,
|
|
1914
|
+
`.ch div:hover{border-color:${p.pr}}`,
|
|
1915
|
+
`.ch .ok{border-color:${p.ac};background:${p.ac}22}`,
|
|
1916
|
+
`.ch .no{border-color:#F87171;background:#F8717122}`,
|
|
1917
|
+
`.dots{display:flex;gap:4px;justify-content:center;margin-bottom:1rem}`,
|
|
1918
|
+
`.dot{width:10px;height:10px;border-radius:50%;background:${p.sf}}`,
|
|
1919
|
+
`.dot.pass{background:${p.ac}}.dot.fail{background:#F87171}.dot.now{background:${p.pr};box-shadow:0 0 8px ${p.pr}}`,
|
|
1920
|
+
`.tm{font-size:2.5rem;font-weight:800;font-variant-numeric:tabular-nums}`,
|
|
1921
|
+
].join('\n');
|
|
1922
|
+
|
|
1923
|
+
const html = `<div id="s-menu">
|
|
1924
|
+
<p style="color:${p.mt};margin:1rem 0">10 questions, ${perQ} seconds each</p>
|
|
1925
|
+
<div class="st"><span>Best<b id="best">0</b></span><span>Last<b id="last">-</b></span></div>
|
|
1926
|
+
<button class="btn" onclick="go()">Start Quiz</button></div>
|
|
1927
|
+
<div id="s-play" class="hd">
|
|
1928
|
+
<div class="dots" id="dots"></div>
|
|
1929
|
+
<div class="tm" id="timer">${perQ}</div>
|
|
1930
|
+
<div class="q" id="qtext"></div>
|
|
1931
|
+
<div class="ch" id="choices"></div></div>
|
|
1932
|
+
<div id="s-end" class="hd">
|
|
1933
|
+
<div class="sc" id="fin">0/10</div>
|
|
1934
|
+
<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>
|
|
1935
|
+
<button class="btn" onclick="go()">Play Again</button></div>`;
|
|
1936
|
+
|
|
1937
|
+
const js = [jsCore('quiz_best'),
|
|
1938
|
+
`const QS=[{q:"What does HTML stand for?",a:["HyperText Markup Language","High Tech Modern Language","Home Tool Markup Language","Hyper Transfer Markup Language"],c:0},{q:"Which planet is closest to the Sun?",a:["Mercury","Venus","Mars","Earth"],c:0},{q:"What is the largest ocean on Earth?",a:["Pacific","Atlantic","Indian","Arctic"],c:0},{q:"In JavaScript, which keyword declares a constant?",a:["const","let","var","static"],c:0},{q:"What year did the World Wide Web go public?",a:["1991","1989","1995","2000"],c:0},{q:"What is the chemical symbol for gold?",a:["Au","Ag","Go","Gd"],c:0},{q:"How many bits are in a byte?",a:["8","4","16","32"],c:0},{q:"What does CSS stand for?",a:["Cascading Style Sheets","Computer Style Sheets","Creative Style System","Coded Style Sheets"],c:0},{q:"What is the speed of light (approx km/s)?",a:["300,000","150,000","500,000","1,000,000"],c:0},{q:"Which data structure uses FIFO?",a:["Queue","Stack","Tree","Graph"],c:0},{q:"What is the square root of 144?",a:["12","14","10","16"],c:0},{q:"Who painted the Mona Lisa?",a:["Da Vinci","Picasso","Van Gogh","Monet"],c:0},{q:"What gas do plants absorb?",a:["CO2","O2","N2","H2"],c:0},{q:"Which protocol is used for secure browsing?",a:["HTTPS","FTP","SMTP","SSH"],c:0},{q:"What is 2^10?",a:["1024","512","2048","4096"],c:0}];`,
|
|
1939
|
+
`const PER_Q=${perQ};let cur,score,pool,tmr,tl;`,
|
|
1940
|
+
`function shuf(a){for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]]}return a}`,
|
|
1941
|
+
`function go(){pool=shuf([...QS]).slice(0,10);cur=0;score=0;$('dots').innerHTML=pool.map((_,i)=>'<div class="dot" id="d'+i+'"></div>').join('');show('play');next()}`,
|
|
1942
|
+
`function next(){if(cur>=pool.length)return end();$('d'+cur).classList.add('now');const q=pool[cur],mx=q.a.map((t,i)=>({t,ok:i===q.c}));shuf(mx);$('qtext').textContent=q.q;const ch=$('choices');ch.innerHTML='';mx.forEach(m=>{const d=document.createElement('div');d.textContent=m.t;d.onclick=()=>pick(d,m.ok,ch);ch.appendChild(d)});tl=PER_Q;$('timer').textContent=tl;clearInterval(tmr);tmr=setInterval(()=>{tl--;$('timer').textContent=Math.max(0,tl);if(tl<=0){clearInterval(tmr);timeout()}},1000)}`,
|
|
1943
|
+
`function pick(el,ok,ch){clearInterval(tmr);ch.querySelectorAll('div').forEach(c=>{c.onclick=null;if(c===el)c.classList.add(ok?'ok':'no');else if(!ok){const t=pool[cur].a[pool[cur].c];if(c.textContent===t)c.classList.add('ok')}});$('d'+cur).classList.remove('now');$('d'+cur).classList.add(ok?'pass':'fail');if(ok)score++;cur++;setTimeout(next,800)}`,
|
|
1944
|
+
`function timeout(){const ch=$('choices');ch.querySelectorAll('div').forEach(c=>{c.onclick=null;if(c.textContent===pool[cur].a[pool[cur].c])c.classList.add('ok')});$('d'+cur).classList.remove('now');$('d'+cur).classList.add('fail');cur++;setTimeout(next,800)}`,
|
|
1945
|
+
`function end(){clearInterval(tmr);const nb=saveBest(score);$('last').textContent=score;$('fin').textContent=score+'/'+pool.length;const pc=Math.round(score/pool.length*100);const r=pc===100?'Perfect!':pc>=80?'Excellent!':pc>=60?'Good!':pc>=40?'Not bad!':'Study more!';$('msg').innerHTML=r+'<br>'+pc+'% correct'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
1946
|
+
].join('\n');
|
|
1947
|
+
|
|
1948
|
+
return htmlWrap(title, css, html, js);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
// ── Memory Game Builder ──
|
|
1952
|
+
|
|
1953
|
+
function buildMemory(title, opts) {
|
|
1954
|
+
const p = GAME_PALETTES.memory;
|
|
1955
|
+
const cols = opts.cols || 4;
|
|
1956
|
+
const pairs = Math.floor((cols * (opts.rows || cols)) / 2);
|
|
1957
|
+
|
|
1958
|
+
const css = [cssVars(p), cssBase(p),
|
|
1959
|
+
`.grid{display:grid;grid-template-columns:repeat(${cols},1fr);gap:8px;margin:1rem auto;width:min(${cols*90}px,88vw)}`,
|
|
1960
|
+
`.card{aspect-ratio:1;border-radius:12px;cursor:pointer;perspective:600px;-webkit-perspective:600px}`,
|
|
1961
|
+
`.card .inn{position:relative;width:100%;height:100%;transition:transform .4s;transform-style:preserve-3d;-webkit-transform-style:preserve-3d}`,
|
|
1962
|
+
`.card.flip .inn{transform:rotateY(180deg)}`,
|
|
1963
|
+
`.card .fr,.card .bk{position:absolute;width:100%;height:100%;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:${cols<=4?'2rem':'1.4rem'};backface-visibility:hidden;-webkit-backface-visibility:hidden}`,
|
|
1964
|
+
`.card .fr{background:linear-gradient(135deg,${p.pr},${p.sc})}`,
|
|
1965
|
+
`.card .bk{background:${p.sf};transform:rotateY(180deg);border:2px solid ${p.pr}}`,
|
|
1966
|
+
`.card.done .fr{background:linear-gradient(135deg,${p.ac},#059669)}`,
|
|
1967
|
+
`.card.done{pointer-events:none}`,
|
|
1968
|
+
].join('\n');
|
|
1969
|
+
|
|
1970
|
+
const html = `<div id="s-menu">
|
|
1971
|
+
<p style="color:${p.mt};margin:1rem 0">Match all ${pairs} pairs with fewest moves</p>
|
|
1972
|
+
<div class="st"><span>Best<b id="best">-</b></span></div>
|
|
1973
|
+
<button class="btn" onclick="go()">Start</button></div>
|
|
1974
|
+
<div id="s-play" class="hd">
|
|
1975
|
+
<div class="st"><span>Moves<b id="mv">0</b></span><span>Pairs<b id="pr">0/${pairs}</b></span><span>Time<b id="tm">0s</b></span></div>
|
|
1976
|
+
<div class="grid" id="grid"></div></div>
|
|
1977
|
+
<div id="s-end" class="hd">
|
|
1978
|
+
<div class="sc" id="fin">0</div>
|
|
1938
1979
|
<p style="font-size:1.2rem;margin:.5rem 0">moves</p>
|
|
1939
|
-
<div id="
|
|
1940
|
-
<button class="btn" onclick="
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
const
|
|
1945
|
-
let
|
|
1946
|
-
document.
|
|
1947
|
-
function
|
|
1948
|
-
function
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
</
|
|
1970
|
-
<div
|
|
1971
|
-
<
|
|
1972
|
-
<
|
|
1973
|
-
<div class="
|
|
1974
|
-
<
|
|
1975
|
-
<
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
<div id="
|
|
1979
|
-
<div class="
|
|
1980
|
-
<div id="
|
|
1981
|
-
<button class="btn" onclick="
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
let
|
|
1987
|
-
|
|
1988
|
-
function
|
|
1989
|
-
function
|
|
1990
|
-
function
|
|
1991
|
-
function
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
function
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
.
|
|
2010
|
-
.
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
<
|
|
2018
|
-
<
|
|
2019
|
-
</div>
|
|
2020
|
-
<div id="
|
|
2021
|
-
<div class="
|
|
2022
|
-
<div class="zone wait" id="zone" onmousedown="tap()" ontouchstart="tap()">
|
|
2023
|
-
<
|
|
2024
|
-
|
|
2025
|
-
<div class="
|
|
2026
|
-
</div>
|
|
2027
|
-
<div id="state-result" class="hidden">
|
|
2028
|
-
<div class="ms" id="finalMs">0</div>
|
|
1980
|
+
<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>
|
|
1981
|
+
<button class="btn" onclick="go()">Play Again</button></div>`;
|
|
1982
|
+
|
|
1983
|
+
const js = [jsCore('mem_best'),
|
|
1984
|
+
`const EM=['🎮','🎯','🚀','💎','🔥','⚡','🌟','🎵','🎨','🏆','🍕','🌈','🦊','🐙','🎃','🍀','🎲','🌸'];`,
|
|
1985
|
+
`const PAIRS=${pairs};let cards,flp,done,moves,tmr,t0;`,
|
|
1986
|
+
`function shuf(a){for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]]}return a}`,
|
|
1987
|
+
`function go(){const pick=shuf([...EM]).slice(0,PAIRS);cards=shuf([...pick,...pick]);flp=[];done=0;moves=0;$('mv').textContent=0;$('pr').textContent='0/'+PAIRS;const g=$('grid');g.innerHTML='';cards.forEach((em,i)=>{const d=document.createElement('div');d.className='card';d.innerHTML='<div class="inn"><div class="fr">?</div><div class="bk">'+em+'</div></div>';d.onclick=()=>flip(d,i);g.appendChild(d)});t0=Date.now();tmr=setInterval(()=>{$('tm').textContent=Math.floor((Date.now()-t0)/1000)+'s'},200);show('play')}`,
|
|
1988
|
+
`function flip(el,i){if(flp.length>=2||el.classList.contains('flip')||el.classList.contains('done'))return;el.classList.add('flip');flp.push({el,i,v:cards[i]});if(flp.length===2){moves++;$('mv').textContent=moves;const[a,b]=flp;if(a.v===b.v){a.el.classList.add('done');b.el.classList.add('done');done++;$('pr').textContent=done+'/'+PAIRS;flp=[];if(done===PAIRS)end()}else{setTimeout(()=>{a.el.classList.remove('flip');b.el.classList.remove('flip');flp=[]},700)}}}`,
|
|
1989
|
+
`function end(){clearInterval(tmr);const sec=Math.floor((Date.now()-t0)/1000);const nb=saveBest(moves);$('fin').textContent=moves;const r=moves<=PAIRS+2?'Perfect memory!':moves<=PAIRS*2?'Great!':moves<=PAIRS*3?'Good!':'Keep practicing!';$('msg').innerHTML=r+'<br>Completed in '+sec+'s'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
1990
|
+
].join('\n');
|
|
1991
|
+
|
|
1992
|
+
return htmlWrap(title, css, html, js);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
// ── Snake Game Builder ──
|
|
1996
|
+
|
|
1997
|
+
function buildSnake(title, opts) {
|
|
1998
|
+
const p = GAME_PALETTES.snake;
|
|
1999
|
+
const spd = opts.speed === 'fast' ? 80 : opts.difficulty === 'hard' ? 90 : 120;
|
|
2000
|
+
const W = 16, S = 20;
|
|
2001
|
+
|
|
2002
|
+
const css = [cssVars(p), cssBase(p),
|
|
2003
|
+
`canvas{border-radius:12px;background:${p.sf};display:block;margin:1rem auto;image-rendering:pixelated;touch-action:none}`,
|
|
2004
|
+
`.ctl{display:grid;grid-template-columns:repeat(3,56px);grid-template-rows:repeat(2,56px);gap:6px;justify-content:center;margin:1rem auto}`,
|
|
2005
|
+
`.ctl button{border:none;border-radius:10px;background:${p.sf};color:${p.tx};font-size:1.4rem;cursor:pointer;display:flex;align-items:center;justify-content:center}`,
|
|
2006
|
+
`.ctl button:active{background:${p.pr}}`,
|
|
2007
|
+
].join('\n');
|
|
2008
|
+
|
|
2009
|
+
const html = `<div id="s-menu">
|
|
2010
|
+
<p style="color:${p.mt};margin:1rem 0">Arrow keys or swipe to control</p>
|
|
2011
|
+
<div class="st"><span>Best<b id="best">0</b></span></div>
|
|
2012
|
+
<button class="btn" onclick="go()">Play</button></div>
|
|
2013
|
+
<div id="s-play" class="hd">
|
|
2014
|
+
<div class="st"><span>Score<b id="score">0</b></span><span>Best<b id="hi">0</b></span></div>
|
|
2015
|
+
<canvas id="cv" width="${W*S}" height="${W*S}"></canvas>
|
|
2016
|
+
<div class="ctl">
|
|
2017
|
+
<div></div><button ontouchstart="sd(0,-1)" onclick="sd(0,-1)">▲</button><div></div>
|
|
2018
|
+
<button ontouchstart="sd(-1,0)" onclick="sd(-1,0)">◀</button><button ontouchstart="sd(0,1)" onclick="sd(0,1)">▼</button><button ontouchstart="sd(1,0)" onclick="sd(1,0)">▶</button></div></div>
|
|
2019
|
+
<div id="s-end" class="hd">
|
|
2020
|
+
<div class="sc" id="fin">0</div>
|
|
2021
|
+
<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>
|
|
2022
|
+
<button class="btn" onclick="go()">Play Again</button></div>`;
|
|
2023
|
+
|
|
2024
|
+
const js = [jsCore('snake_best'),
|
|
2025
|
+
`const GS=${S},GW=${W},cv=$('cv'),ctx=cv.getContext('2d');`,
|
|
2026
|
+
`let sn,dir,nd,food,sc,lp;$('hi').textContent=best;`,
|
|
2027
|
+
`function rnd(){let p;do{p={x:Math.floor(Math.random()*GW),y:Math.floor(Math.random()*GW)}}while(sn.some(s=>s.x===p.x&&s.y===p.y));return p}`,
|
|
2028
|
+
`function go(){sn=[{x:8,y:8},{x:7,y:8},{x:6,y:8}];dir={x:1,y:0};nd={x:1,y:0};food=rnd();sc=0;$('score').textContent=0;show('play');clearInterval(lp);lp=setInterval(tick,${spd})}`,
|
|
2029
|
+
`function sd(x,y){if(x===-dir.x&&y===-dir.y)return;nd={x,y}}`,
|
|
2030
|
+
`function tick(){dir=nd;const h={x:sn[0].x+dir.x,y:sn[0].y+dir.y};if(h.x<0||h.x>=GW||h.y<0||h.y>=GW||sn.some(s=>s.x===h.x&&s.y===h.y))return end();sn.unshift(h);if(h.x===food.x&&h.y===food.y){sc++;$('score').textContent=sc;food=rnd()}else sn.pop();draw()}`,
|
|
2031
|
+
`function draw(){ctx.fillStyle='${p.sf}';ctx.fillRect(0,0,GW*GS,GW*GS);ctx.fillStyle='${p.ac}';ctx.beginPath();ctx.arc(food.x*GS+GS/2,food.y*GS+GS/2,GS/2-2,0,Math.PI*2);ctx.fill();sn.forEach((s,i)=>{const t=i/sn.length;ctx.fillStyle=i===0?'${p.pr}':'hsl('+(140+t*40)+',70%,'+(55-t*20)+'%)';ctx.beginPath();ctx.roundRect(s.x*GS+1,s.y*GS+1,GS-2,GS-2,4);ctx.fill()})}`,
|
|
2032
|
+
`function end(){clearInterval(lp);const nb=saveBest(sc);$('hi').textContent=best;$('fin').textContent=sc;const r=sc>=30?'Legend!':sc>=20?'Amazing!':sc>=10?'Great!':sc>=5?'Good!':'Try again!';$('msg').innerHTML=r+(nb&&sc>0?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2033
|
+
`document.addEventListener('keydown',e=>{if(e.key==='ArrowUp')sd(0,-1);if(e.key==='ArrowDown')sd(0,1);if(e.key==='ArrowLeft')sd(-1,0);if(e.key==='ArrowRight')sd(1,0)});`,
|
|
2034
|
+
`let tx,ty;cv.addEventListener('touchstart',e=>{tx=e.touches[0].clientX;ty=e.touches[0].clientY},{passive:true});cv.addEventListener('touchend',e=>{const dx=e.changedTouches[0].clientX-tx,dy=e.changedTouches[0].clientY-ty;if(Math.abs(dx)>Math.abs(dy))sd(dx>0?1:-1,0);else sd(0,dy>0?1:-1)},{passive:true});`,
|
|
2035
|
+
].join('\n');
|
|
2036
|
+
|
|
2037
|
+
return htmlWrap(title, css, html, js);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
// ── Reaction Game Builder ──
|
|
2041
|
+
|
|
2042
|
+
function buildReaction(title, opts) {
|
|
2043
|
+
const p = GAME_PALETTES.reaction;
|
|
2044
|
+
const rounds = opts.difficulty === 'hard' ? 10 : 5;
|
|
2045
|
+
|
|
2046
|
+
const css = [cssVars(p), cssBase(p),
|
|
2047
|
+
`.zone{width:min(340px,85vw);height:min(340px,85vw);border-radius:24px;display:flex;align-items:center;justify-content:center;margin:1.5rem auto;cursor:pointer;font-size:1.2rem;font-weight:600;transition:background .2s}`,
|
|
2048
|
+
`.zone.wait{background:${p.sf}}`,
|
|
2049
|
+
`.zone.ready{background:${p.pr};color:#fff}`,
|
|
2050
|
+
`.zone.go{background:${p.ac};color:${p.bg}}`,
|
|
2051
|
+
`.zone.early{background:#DC2626}`,
|
|
2052
|
+
`.times{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin:1rem 0}`,
|
|
2053
|
+
`.times span{background:${p.sf};padding:.3rem .6rem;border-radius:6px;font-size:.85rem;font-variant-numeric:tabular-nums}`,
|
|
2054
|
+
`.ms{font-size:3.5rem;font-weight:900;font-variant-numeric:tabular-nums}`,
|
|
2055
|
+
].join('\n');
|
|
2056
|
+
|
|
2057
|
+
const html = `<div id="s-menu">
|
|
2058
|
+
<p style="color:${p.mt};margin:1rem 0">Tap when it turns green — ${rounds} rounds</p>
|
|
2059
|
+
<div class="st"><span>Best Avg<b id="best">-</b></span></div>
|
|
2060
|
+
<button class="btn" onclick="go()">Start</button></div>
|
|
2061
|
+
<div id="s-play" class="hd">
|
|
2062
|
+
<div class="st"><span>Round<b id="rnd">1/${rounds}</b></span><span>Avg<b id="avg">-</b></span></div>
|
|
2063
|
+
<div class="zone wait" id="zone" onmousedown="tap()" ontouchstart="tap()"><span id="ztxt">Wait...</span></div>
|
|
2064
|
+
<div class="times" id="times"></div></div>
|
|
2065
|
+
<div id="s-end" class="hd">
|
|
2066
|
+
<div class="ms" id="fin">0</div>
|
|
2029
2067
|
<p style="font-size:1.2rem;margin:.5rem 0">ms average</p>
|
|
2030
|
-
<div id="
|
|
2031
|
-
<div class="times" id="
|
|
2032
|
-
<button class="btn" style="margin-top:1rem" onclick="
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
const
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
function
|
|
2040
|
-
function
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
.
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
.
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
.
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
<p style="color
|
|
2063
|
-
<div class="
|
|
2064
|
-
<button class="btn" onclick="
|
|
2065
|
-
|
|
2066
|
-
<div id="
|
|
2067
|
-
<div class="
|
|
2068
|
-
<div class="
|
|
2069
|
-
<div class="
|
|
2070
|
-
<
|
|
2071
|
-
<
|
|
2072
|
-
</div>
|
|
2073
|
-
<div id="state-result" class="hidden">
|
|
2074
|
-
<div class="score" id="finalWpm">0</div>
|
|
2068
|
+
<div id="msg" style="margin:1rem 0"></div>
|
|
2069
|
+
<div class="times" id="ftimes"></div>
|
|
2070
|
+
<button class="btn" style="margin-top:1rem" onclick="go()">Try Again</button></div>`;
|
|
2071
|
+
|
|
2072
|
+
const js = [jsCore('react_best'),
|
|
2073
|
+
`if(best)$('best').textContent=best+'ms';`,
|
|
2074
|
+
`const RDS=${rounds};let rd,res,phase,gAt,tmr;`,
|
|
2075
|
+
`function go(){rd=0;res=[];$('times').innerHTML='';show('play');nr()}`,
|
|
2076
|
+
`function nr(){if(rd>=RDS)return end();$('rnd').textContent=(rd+1)+'/'+RDS;const z=$('zone');z.className='zone ready';$('ztxt').textContent='Wait for green...';phase='wait';clearTimeout(tmr);tmr=setTimeout(()=>{z.className='zone go';$('ztxt').textContent='TAP NOW!';gAt=Date.now();phase='go'},1500+Math.random()*3000)}`,
|
|
2077
|
+
`function tap(){if(phase==='wait'){clearTimeout(tmr);$('zone').className='zone early';$('ztxt').textContent='Too early! Tap to retry';phase='early'}else if(phase==='early'){nr()}else if(phase==='go'){const ms=Date.now()-gAt;res.push(ms);const t=document.createElement('span');t.textContent=ms+'ms';$('times').appendChild(t);$('avg').textContent=Math.round(res.reduce((a,b)=>a+b,0)/res.length)+'ms';rd++;phase='done';setTimeout(nr,600)}}`,
|
|
2078
|
+
`function end(){const avg=Math.round(res.reduce((a,b)=>a+b,0)/res.length);const nb=saveBest(avg);if(nb)$('best').textContent=avg+'ms';$('fin').textContent=avg;$('ftimes').innerHTML=res.map((r,i)=>'<span>R'+(i+1)+': '+r+'ms</span>').join('');const r=avg<=200?'Superhuman!':avg<=250?'Lightning!':avg<=300?'Fast!':avg<=400?'Average':'Keep practicing!';$('msg').innerHTML='<span style="font-size:1.2rem">'+r+'</span>'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2079
|
+
].join('\n');
|
|
2080
|
+
|
|
2081
|
+
return htmlWrap(title, css, html, js);
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
// ── Typing Game Builder ──
|
|
2085
|
+
|
|
2086
|
+
function buildTyping(title, opts) {
|
|
2087
|
+
const p = GAME_PALETTES.typing;
|
|
2088
|
+
const dur = opts.duration || 30;
|
|
2089
|
+
|
|
2090
|
+
const css = [cssVars(p), cssBase(p), cssTimerBar(p),
|
|
2091
|
+
`.wd{font-size:2rem;font-weight:700;letter-spacing:.1em;margin:1.5rem 0;min-height:3rem;display:flex;justify-content:center;gap:2px;flex-wrap:wrap}`,
|
|
2092
|
+
`.wd .c{transition:color .1s}.wd .y{color:${p.ac}}.wd .n{color:#F87171}`,
|
|
2093
|
+
`.wd .cur{border-bottom:3px solid ${p.pr};animation:blink 1s step-end infinite}`,
|
|
2094
|
+
`@keyframes blink{50%{border-color:transparent}}`,
|
|
2095
|
+
`.ti{background:${p.sf};border:2px solid ${p.pr};border-radius:12px;padding:.75rem 1rem;font-size:1.2rem;color:${p.tx};width:100%;text-align:center;outline:none;caret-color:${p.pr};font-family:inherit}`,
|
|
2096
|
+
`.wq{color:${p.mt};font-size:1rem;margin:.5rem 0;height:2em;overflow:hidden;display:flex;gap:1rem;justify-content:center;flex-wrap:wrap}`,
|
|
2097
|
+
].join('\n');
|
|
2098
|
+
|
|
2099
|
+
const html = `<div id="s-menu">
|
|
2100
|
+
<p style="color:${p.mt};margin:1rem 0">Type as many words as you can in ${dur} seconds</p>
|
|
2101
|
+
<div class="st"><span>Best WPM<b id="best">0</b></span></div>
|
|
2102
|
+
<button class="btn" onclick="go()">Start</button></div>
|
|
2103
|
+
<div id="s-play" class="hd">
|
|
2104
|
+
<div class="st"><span>Time<b id="timer">${dur}</b></span><span>Words<b id="wc">0</b></span><span>WPM<b id="wpm">0</b></span></div>
|
|
2105
|
+
<div class="bar"><div class="fl" id="bar" style="width:100%"></div></div>
|
|
2106
|
+
<div class="wd" id="disp"></div>
|
|
2107
|
+
<div class="wq" id="queue"></div>
|
|
2108
|
+
<input class="ti" id="inp" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" placeholder="Type here..."></div>
|
|
2109
|
+
<div id="s-end" class="hd">
|
|
2110
|
+
<div class="sc" id="fin">0</div>
|
|
2075
2111
|
<p style="font-size:1.2rem;margin:.5rem 0">WPM</p>
|
|
2076
|
-
<div id="
|
|
2077
|
-
<button class="btn" onclick="
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
const
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
function
|
|
2085
|
-
function
|
|
2086
|
-
function
|
|
2087
|
-
function
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
<
|
|
2105
|
-
</div>
|
|
2106
|
-
<
|
|
2107
|
-
<div
|
|
2108
|
-
<
|
|
2109
|
-
|
|
2110
|
-
<div id="
|
|
2111
|
-
<div class="
|
|
2112
|
-
<div id="
|
|
2113
|
-
<button class="btn" onclick="
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
const cv
|
|
2118
|
-
const
|
|
2119
|
-
let px,ball,dx,dy,bricks,sc,lives,
|
|
2120
|
-
|
|
2121
|
-
function
|
|
2122
|
-
|
|
2123
|
-
function
|
|
2124
|
-
function
|
|
2125
|
-
function
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
quiz:
|
|
2138
|
-
|
|
2139
|
-
snake: snakeGameHTML,
|
|
2140
|
-
reaction: reactionGameHTML,
|
|
2141
|
-
typing: typingGameHTML,
|
|
2142
|
-
breakout: breakoutGameHTML,
|
|
2112
|
+
<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>
|
|
2113
|
+
<button class="btn" onclick="go()">Play Again</button></div>`;
|
|
2114
|
+
|
|
2115
|
+
const js = [jsCore('type_best'),
|
|
2116
|
+
`const WDS=["the","be","to","and","in","that","have","for","not","with","you","this","but","from","they","say","she","or","will","my","one","all","would","there","what","so","up","out","if","about","who","get","which","go","when","make","can","like","time","just","know","take","people","into","year","good","some","could","them","see","other","than","then","now","look","only","come","its","over","think","also","back","after","use","two","how","work","first","well","even","new","want","give","most","find","here","thing","code","fast","type","game","play","next","open","read","test","data","file","line","word","text","long","name","part","best","help","made","move","hand","high","keep","last","same","tell","set","each","much","turn","real","show","full","form","left","start","might","run","need","home","life","old","big","end","point","still","call","live","away","right","hard","plan","team","late","mind","wait","stop","must","land","close","draw","press","mark","step","pick","rock","blue","deep","dark","hold","four","week","room","free","fall","base","rest","less","note","hear"];`,
|
|
2117
|
+
`const DUR=${dur*1000};let pool,ci,pos,done,t0,tmr;`,
|
|
2118
|
+
`function shuf(a){for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]]}return a}`,
|
|
2119
|
+
`function go(){pool=shuf([...WDS]);ci=0;pos=0;done=0;t0=Date.now();$('wc').textContent=0;$('wpm').textContent=0;$('inp').value='';show('play');$('inp').focus();render();tmr=setInterval(tick,100)}`,
|
|
2120
|
+
`function render(){const w=pool[ci%pool.length];const v=$('inp').value;$('disp').innerHTML=w.split('').map((c,i)=>{let cl=i<pos?(v[i]===c?'y':'n'):'';if(i===pos)cl+=' cur';return'<span class="c '+cl+'">'+c+'</span>'}).join('');$('queue').innerHTML='';for(let i=1;i<=5;i++)$('queue').innerHTML+='<span>'+pool[(ci+i)%pool.length]+'</span>'}`,
|
|
2121
|
+
`function tick(){const e=Date.now()-t0,pc=Math.max(0,1-e/DUR)*100;$('bar').style.width=pc+'%';$('timer').textContent=Math.max(0,Math.ceil((DUR-e)/1000));if(e>=DUR)end()}`,
|
|
2122
|
+
`$('inp').addEventListener('input',function(){if(Date.now()-t0>DUR)return;const w=pool[ci%pool.length],v=this.value;pos=v.length;if(v.endsWith(' ')||v.length>=w.length){if(v.trim()===w){done++;$('wc').textContent=done;$('wpm').textContent=Math.round(done/((Date.now()-t0)/60000))}ci++;pos=0;this.value=''}render()});`,
|
|
2123
|
+
`function end(){clearInterval(tmr);$('inp').blur();const wpm=Math.round(done/(DUR/60000));const nb=saveBest(wpm);$('fin').textContent=wpm;const r=wpm>=80?'Blazing!':wpm>=60?'Excellent!':wpm>=40?'Good!':wpm>=20?'Decent!':'Keep practicing!';$('msg').innerHTML=r+'<br>'+done+' words in ${dur}s'+(nb&&wpm>0?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2124
|
+
].join('\n');
|
|
2125
|
+
|
|
2126
|
+
return htmlWrap(title, css, html, js);
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// ── Breakout Game Builder ──
|
|
2130
|
+
|
|
2131
|
+
function buildBreakout(title, opts) {
|
|
2132
|
+
const p = GAME_PALETTES.breakout;
|
|
2133
|
+
const COLS = 8, ROWS = opts.difficulty === 'hard' ? 7 : 5;
|
|
2134
|
+
|
|
2135
|
+
const css = [cssVars(p), cssBase(p),
|
|
2136
|
+
`canvas{border-radius:12px;background:${p.sf};display:block;margin:1rem auto;touch-action:none}`,
|
|
2137
|
+
].join('\n');
|
|
2138
|
+
|
|
2139
|
+
const html = `<div id="s-menu">
|
|
2140
|
+
<p style="color:${p.mt};margin:1rem 0">Move the paddle to break all ${COLS * ROWS} blocks</p>
|
|
2141
|
+
<div class="st"><span>Best<b id="best">0</b></span></div>
|
|
2142
|
+
<button class="btn" onclick="go()">Play</button></div>
|
|
2143
|
+
<div id="s-play" class="hd">
|
|
2144
|
+
<div class="st"><span>Score<b id="score">0</b></span><span>Lives<b id="lives">3</b></span></div>
|
|
2145
|
+
<canvas id="cv" width="360" height="480"></canvas></div>
|
|
2146
|
+
<div id="s-end" class="hd">
|
|
2147
|
+
<div class="sc" id="fin">0</div>
|
|
2148
|
+
<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>
|
|
2149
|
+
<button class="btn" onclick="go()">Play Again</button></div>`;
|
|
2150
|
+
|
|
2151
|
+
const brickColors = `['${p.pr}','${p.sc}','#A78BFA','${p.ac}','#EC4899']`;
|
|
2152
|
+
const js = [jsCore('brk_best'),
|
|
2153
|
+
`const cv=$('cv'),ctx=cv.getContext('2d'),CW=360,CH=480,COLS=${COLS},ROWS=${ROWS},BW=CW/COLS-4,BH=18,PW=70,PH=12,BR=6;`,
|
|
2154
|
+
`const BC=${brickColors};`,
|
|
2155
|
+
`let px,ball,dx,dy,bricks,sc,lives,raf;`,
|
|
2156
|
+
`function mkBricks(){bricks=[];for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++)bricks.push({x:c*(BW+4)+2,y:r*(BH+4)+40,w:BW,h:BH,color:BC[r%BC.length],alive:true})}`,
|
|
2157
|
+
`function go(){px=CW/2;ball={x:CW/2,y:CH-60};dx=3*(Math.random()>.5?1:-1);dy=-3.5;sc=0;lives=3;mkBricks();$('score').textContent=0;$('lives').textContent=3;show('play');cancelAnimationFrame(raf);tick()}`,
|
|
2158
|
+
`function tick(){update();draw();if(lives>0&&bricks.some(b=>b.alive))raf=requestAnimationFrame(tick);else end()}`,
|
|
2159
|
+
`function update(){ball.x+=dx;ball.y+=dy;if(ball.x<=BR||ball.x>=CW-BR)dx=-dx;if(ball.y<=BR)dy=-dy;if(ball.y>=CH-PH-BR-8&&ball.y<=CH-8&&ball.x>=px-PW/2&&ball.x<=px+PW/2){dy=-Math.abs(dy);dx+=((ball.x-px)/(PW/2))*2}if(ball.y>CH){lives--;$('lives').textContent=lives;if(lives>0){ball={x:CW/2,y:CH-60};dx=3*(Math.random()>.5?1:-1);dy=-3.5}}for(const b of bricks){if(!b.alive)continue;if(ball.x+BR>b.x&&ball.x-BR<b.x+b.w&&ball.y+BR>b.y&&ball.y-BR<b.y+b.h){b.alive=false;dy=-dy;sc+=10;$('score').textContent=sc}}}`,
|
|
2160
|
+
`function draw(){ctx.clearRect(0,0,CW,CH);for(const b of bricks){if(!b.alive)continue;ctx.fillStyle=b.color;ctx.beginPath();ctx.roundRect(b.x,b.y,b.w,b.h,4);ctx.fill()}ctx.fillStyle='${p.pr}';ctx.beginPath();ctx.roundRect(px-PW/2,CH-PH-8,PW,PH,6);ctx.fill();ctx.fillStyle='${p.ac}';ctx.beginPath();ctx.arc(ball.x,ball.y,BR,0,Math.PI*2);ctx.fill()}`,
|
|
2161
|
+
`function end(){cancelAnimationFrame(raf);const won=!bricks.some(b=>b.alive);const nb=saveBest(sc);$('fin').textContent=sc;$('msg').innerHTML=(won?'You cleared it!':'Game Over')+(nb&&sc>0?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2162
|
+
`cv.addEventListener('mousemove',e=>{const r=cv.getBoundingClientRect();px=Math.max(PW/2,Math.min(CW-PW/2,(e.clientX-r.left)*(CW/r.width)))});`,
|
|
2163
|
+
`cv.addEventListener('touchmove',e=>{e.preventDefault();const r=cv.getBoundingClientRect();px=Math.max(PW/2,Math.min(CW-PW/2,(e.touches[0].clientX-r.left)*(CW/r.width)))},{passive:false});`,
|
|
2164
|
+
`document.addEventListener('keydown',e=>{if(e.key==='ArrowLeft')px=Math.max(PW/2,px-20);if(e.key==='ArrowRight')px=Math.min(CW-PW/2,px+20)});`,
|
|
2165
|
+
].join('\n');
|
|
2166
|
+
|
|
2167
|
+
return htmlWrap(title, css, html, js);
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// ── Game Router ──
|
|
2171
|
+
|
|
2172
|
+
const GAME_BUILDERS = {
|
|
2173
|
+
tapping: buildTapping, quiz: buildQuiz, memory: buildMemory,
|
|
2174
|
+
snake: buildSnake, reaction: buildReaction, typing: buildTyping, breakout: buildBreakout,
|
|
2143
2175
|
};
|
|
2144
2176
|
|
|
2145
|
-
function gameHTML(type, title) {
|
|
2146
|
-
const
|
|
2147
|
-
|
|
2177
|
+
function gameHTML(type, title, rawInstruction) {
|
|
2178
|
+
const opts = extractGameOpts(rawInstruction || '');
|
|
2179
|
+
const builder = GAME_BUILDERS[type] || buildTapping;
|
|
2180
|
+
return builder(title || type, opts);
|
|
2148
2181
|
}
|
|
2149
2182
|
|
|
2150
2183
|
function gameNaideCode(title, port) {
|
|
@@ -2522,7 +2555,7 @@ export function generateProject(instruction, options = {}) {
|
|
|
2522
2555
|
const gt = result.analysis.gameType;
|
|
2523
2556
|
const title = result.analysis.gameTitle || gt;
|
|
2524
2557
|
const projectName = safeName(gt) + '-game';
|
|
2525
|
-
const html = gameHTML(gt, title);
|
|
2558
|
+
const html = gameHTML(gt, title, instruction);
|
|
2526
2559
|
|
|
2527
2560
|
files.push({ path: 'index.html', content: html });
|
|
2528
2561
|
files.push({ path: 'app.naide', content: result.code });
|