nx_games_js.nx source
↩ module page · 123 lines · 10193 B
1// nx_games_js.nx -- LIB (no main): the /games portal's shared emit helpers plus the whole client
2// script block. Split out of nx_games_page.nx 2026-08-10.
3//
4// WHY THE SPLIT. Two reasons, in order of merit:
5// 1. SINGLE RESPONSIBILITY (rule 9). The page organ authors document structure, tokens and copy;
6// this organ authors game behaviour. One file was doing both and had grown past 31 KB.
7// 2. It also happens to fix a transport ceiling: the estate's nx_fs_write lane could not carry a
8// 31 KB payload (eight verified attempts, artifact unchanged every time), while both halves of
9// the split carry first try. That is a side benefit, NOT the justification -- a split made only
10// to dodge a transport limit would be a hack; this one is the decomposition the file was owed.
11//
12// SUDOKU IS NO LONGER HERE (2026-08-10). It became a sovereign NishiLang module compiled to
13// WebAssembly (nx_sudoku_wasm) and served at /games/sudoku, where the page holds no game logic at
14// all. Its JS, its bank parser and the GJS_ROW record constant went with it: keeping a second
15// implementation of the same ruleset in JavaScript would have been the duplicate-ruler defect.
16// Connect Four and Tic-Tac-Toe are still JS here and are owed the same treatment.
17// license_tier: ORIGINAL
18//
19// genealogy_id: nishi_games_portal
20// lineage_id: nx_games_js_v2
21import "nx_syscalls.nx"
22
23// append a NUL-terminated string into dst at off; returns new off.
24func gm_cat(dst: *u8, off: i64, s: *u8) -> i64 {
25 var o: i64 = off
26 var i: i64 = 0
27 while s[i] != 0 as u8 { dst[o] = s[i]; o = o + 1; i = i + 1 }
28 return o
29}
30
31// append a signed decimal into dst at off; returns new off.
32func gm_catn(dst: *u8, off: i64, v: i64) -> i64 {
33 var o: i64 = off
34 var n: i64 = v
35 if n < 0 { dst[o] = 45 as u8; o = o + 1; n = 0 - n }
36 let tmp: *u8 = sys_mmap(32)
37 var k: i64 = 0
38 if n == 0 { tmp[0] = 48 as u8; k = 1 }
39 while n > 0 { tmp[k] = (48 + (n % 10)) as u8; n = n / 10; k = k + 1 }
40 var j: i64 = k - 1
41 while j >= 0 { dst[o] = tmp[j]; o = o + 1; j = j - 1 }
42 return o
43}
44
45// gm_slurp was removed with the bank: nothing on this page reads a data file any more, and a defined
46// function nobody calls is what nx_unwired exists to catch.
47
48// one inventory card. state: 0 playable-here 1 engine-demo 2 not-started
49func gm_card(dst: *u8, off: i64, name: *u8, note: *u8, href: *u8, state: i64) -> i64 {
50 var w: i64 = off
51 if state == 1 { w = gm_cat(dst, w, "<a class='card' href='" as *u8); w = gm_cat(dst, w, href); w = gm_cat(dst, w, "'>" as *u8) }
52 if state != 1 { w = gm_cat(dst, w, "<div class='card'>" as *u8) }
53 w = gm_cat(dst, w, "<h3>" as *u8); w = gm_cat(dst, w, name); w = gm_cat(dst, w, "</h3>" as *u8)
54 w = gm_cat(dst, w, "<div class='s'>" as *u8); w = gm_cat(dst, w, note); w = gm_cat(dst, w, "</div>" as *u8)
55 if state == 0 { w = gm_cat(dst, w, "<span class='tag live'>Playable here</span>" as *u8) }
56 if state == 1 { w = gm_cat(dst, w, "<span class='tag eng'>Engine demo →</span>" as *u8) }
57 if state == 2 { w = gm_cat(dst, w, "<span class='tag soon'>Not started</span>" as *u8) }
58 if state == 1 { w = gm_cat(dst, w, "</a>" as *u8) }
59 if state != 1 { w = gm_cat(dst, w, "</div>" as *u8) }
60 w = gm_cat(dst, w, "\n" as *u8)
61 return w
62}
63
64// the remaining client script block: Connect Four and Tic-Tac-Toe only.
65// single-quoted JS only, no backslashes (the nx lexer is happiest that way).
66func gjs_scripts(h: *u8, w0: i64) -> i64 {
67 var w: i64 = w0
68
69 // ---------------- connect four ----------------
70 w = gm_cat(h, w, "var CB=[],CO=false;\n" as *u8)
71 w = gm_cat(h, w, "function cidx(r,c){return r*7+c;}\n" as *u8)
72 w = gm_cat(h, w, "function cdrop(b,c){for(var r=5;r>=0;r--){if(b[cidx(r,c)]=='')return r;}return -1;}\n" as *u8)
73 w = gm_cat(h, w, "function cwin(b,p){var r,c;\n" as *u8)
74 w = gm_cat(h, w, "for(r=0;r<6;r++)for(c=0;c<4;c++)if(b[cidx(r,c)]==p&&b[cidx(r,c+1)]==p&&b[cidx(r,c+2)]==p&&b[cidx(r,c+3)]==p)return true;\n" as *u8)
75 w = gm_cat(h, w, "for(c=0;c<7;c++)for(r=0;r<3;r++)if(b[cidx(r,c)]==p&&b[cidx(r+1,c)]==p&&b[cidx(r+2,c)]==p&&b[cidx(r+3,c)]==p)return true;\n" as *u8)
76 w = gm_cat(h, w, "for(r=0;r<3;r++)for(c=0;c<4;c++)if(b[cidx(r,c)]==p&&b[cidx(r+1,c+1)]==p&&b[cidx(r+2,c+2)]==p&&b[cidx(r+3,c+3)]==p)return true;\n" as *u8)
77 w = gm_cat(h, w, "for(r=3;r<6;r++)for(c=0;c<4;c++)if(b[cidx(r,c)]==p&&b[cidx(r-1,c+1)]==p&&b[cidx(r-2,c+2)]==p&&b[cidx(r-3,c+3)]==p)return true;\n" as *u8)
78 w = gm_cat(h, w, "return false;}\n" as *u8)
79 w = gm_cat(h, w, "function cfull(b){for(var c=0;c<7;c++)if(cdrop(b,c)>=0)return false;return true;}\n" as *u8)
80 w = gm_cat(h, w, "function cwscore(a,p){var o=p=='y'?'r':'y',cp=0,co=0,ce=0,i;for(i=0;i<4;i++){if(a[i]==p)cp++;else if(a[i]==o)co++;else ce++;}\n" as *u8)
81 w = gm_cat(h, w, "if(cp>0&&co>0)return 0;if(cp==4)return 1000;if(co==4)return -1000;if(cp==3&&ce==1)return 50;if(cp==2&&ce==2)return 8;if(co==3&&ce==1)return -75;if(co==2&&ce==2)return -6;return 0;}\n" as *u8)
82 w = gm_cat(h, w, "function cheur(b,p){var s=0,r,c;for(r=0;r<6;r++)if(b[cidx(r,3)]==p)s+=6;\n" as *u8)
83 w = gm_cat(h, w, "for(r=0;r<6;r++)for(c=0;c<4;c++)s+=cwscore([b[cidx(r,c)],b[cidx(r,c+1)],b[cidx(r,c+2)],b[cidx(r,c+3)]],p);\n" as *u8)
84 w = gm_cat(h, w, "for(c=0;c<7;c++)for(r=0;r<3;r++)s+=cwscore([b[cidx(r,c)],b[cidx(r+1,c)],b[cidx(r+2,c)],b[cidx(r+3,c)]],p);\n" as *u8)
85 w = gm_cat(h, w, "for(r=0;r<3;r++)for(c=0;c<4;c++)s+=cwscore([b[cidx(r,c)],b[cidx(r+1,c+1)],b[cidx(r+2,c+2)],b[cidx(r+3,c+3)]],p);\n" as *u8)
86 w = gm_cat(h, w, "for(r=3;r<6;r++)for(c=0;c<4;c++)s+=cwscore([b[cidx(r,c)],b[cidx(r-1,c+1)],b[cidx(r-2,c+2)],b[cidx(r-3,c+3)]],p);return s;}\n" as *u8)
87 w = gm_cat(h, w, "var CORD=[3,2,4,1,5,0,6];\n" as *u8)
88 w = gm_cat(h, w, "function cmm(b,d,al,be,mx){if(cwin(b,'y'))return{s:100000-(5-d)};if(cwin(b,'r'))return{s:-100000+(5-d)};if(cfull(b)||d==0)return{s:cheur(b,'y')};\n" as *u8)
89 w = gm_cat(h, w, "var k,c,r,v;if(mx){var best={s:-1e9,m:-1};for(k=0;k<7;k++){c=CORD[k];r=cdrop(b,c);if(r<0)continue;b[cidx(r,c)]='y';v=cmm(b,d-1,al,be,false).s;b[cidx(r,c)]='';\n" as *u8)
90 w = gm_cat(h, w, "if(v>best.s)best={s:v,m:c};if(v>al)al=v;if(al>=be)break;}return best;}\n" as *u8)
91 w = gm_cat(h, w, "var bes={s:1e9,m:-1};for(k=0;k<7;k++){c=CORD[k];r=cdrop(b,c);if(r<0)continue;b[cidx(r,c)]='r';v=cmm(b,d-1,al,be,true).s;b[cidx(r,c)]='';\n" as *u8)
92 w = gm_cat(h, w, "if(v<bes.s)bes={s:v,m:c};if(v<be)be=v;if(al>=be)break;}return bes;}\n" as *u8)
93 w = gm_cat(h, w, "function crender(){var e=document.getElementById('c4');e.innerHTML='';for(var i=0;i<42;i++){var d=document.createElement('button');\n" as *u8)
94 w = gm_cat(h, w, "d.className='disc'+(CB[i]=='r'?' r':'')+(CB[i]=='y'?' y':'');d.setAttribute('aria-label','column '+((i%7)+1));\n" as *u8)
95 w = gm_cat(h, w, "(function(col){d.onclick=function(){cplay(col);};})(i%7);e.appendChild(d);}}\n" as *u8)
96 w = gm_cat(h, w, "function cstat(t){document.getElementById('c4-status').textContent=t;}\n" as *u8)
97 w = gm_cat(h, w, "function ccheck(){crender();if(cwin(CB,'r')){cstat('Red (you) win. Nicely done.');CO=true;return true;}\n" as *u8)
98 w = gm_cat(h, w, "if(cwin(CB,'y')){cstat('Yellow (AI) wins.');CO=true;return true;}if(cfull(CB)){cstat('Board full - a draw.');CO=true;return true;}return false;}\n" as *u8)
99 w = gm_cat(h, w, "function cplay(c){if(CO)return;var r=cdrop(CB,c);if(r<0)return;CB[cidx(r,c)]='r';if(ccheck())return;cstat('AI thinking...');\n" as *u8)
100 w = gm_cat(h, w, "setTimeout(function(){var m=cmm(CB.slice(),5,-1e9,1e9,true).m;if(m<0){for(var c2=0;c2<7;c2++)if(cdrop(CB,c2)>=0){m=c2;break;}}\n" as *u8)
101 w = gm_cat(h, w, "var rr=cdrop(CB,m);if(rr>=0)CB[cidx(rr,m)]='y';if(!ccheck())cstat('Your move - tap a column to drop a red disc.');},30);}\n" as *u8)
102 w = gm_cat(h, w, "function creset(){CB=[];for(var i=0;i<42;i++)CB.push('');CO=false;crender();cstat('Your move - tap a column to drop a red disc (you are red).');}\n" as *u8)
103
104 // ---------------- tic tac toe ----------------
105 w = gm_cat(h, w, "var TB=[],TO=false;\n" as *u8)
106 w = gm_cat(h, w, "var TW=[[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];\n" as *u8)
107 w = gm_cat(h, w, "function twin(b,p){for(var i=0;i<8;i++){var l=TW[i];if(b[l[0]]==p&&b[l[1]]==p&&b[l[2]]==p)return true;}return false;}\n" as *u8)
108 w = gm_cat(h, w, "function tfull(b){for(var i=0;i<9;i++)if(b[i]=='')return false;return true;}\n" as *u8)
109 w = gm_cat(h, w, "function tmm(b,pl){if(twin(b,'O'))return{s:10};if(twin(b,'X'))return{s:-10};if(tfull(b))return{s:0};\n" as *u8)
110 w = gm_cat(h, w, "var best=pl=='O'?{s:-999}:{s:999};for(var i=0;i<9;i++){if(b[i]==''){b[i]=pl;var r=tmm(b,pl=='O'?'X':'O');b[i]='';\n" as *u8)
111 w = gm_cat(h, w, "if(pl=='O'){if(r.s>best.s)best={s:r.s,m:i};}else{if(r.s<best.s)best={s:r.s,m:i};}}}return best;}\n" as *u8)
112 w = gm_cat(h, w, "function trender(){var e=document.getElementById('ttt');e.innerHTML='';for(var i=0;i<9;i++){var c=document.createElement('button');\n" as *u8)
113 w = gm_cat(h, w, "c.className='cell';c.textContent=TB[i];c.setAttribute('aria-label','square '+(i+1));(function(k){c.onclick=function(){tplay(k);};})(i);e.appendChild(c);}}\n" as *u8)
114 w = gm_cat(h, w, "function tstat(t){document.getElementById('ttt-status').textContent=t;}\n" as *u8)
115 w = gm_cat(h, w, "function tcheck(){trender();if(twin(TB,'X')){tstat('You win. The AI blundered.');TO=true;return true;}\n" as *u8)
116 w = gm_cat(h, w, "if(twin(TB,'O')){tstat('AI wins.');TO=true;return true;}if(tfull(TB)){tstat('Draw - perfect play.');TO=true;return true;}return false;}\n" as *u8)
117 w = gm_cat(h, w, "function tplay(i){if(TO||TB[i]!='')return;TB[i]='X';if(tcheck())return;var m=tmm(TB.slice(),'O').m;if(m!=undefined)TB[m]='O';\n" as *u8)
118 w = gm_cat(h, w, "if(!tcheck())tstat('Your move (X). The AI is unbeatable - aim for a draw.');}\n" as *u8)
119 w = gm_cat(h, w, "function treset(){TB=['','','','','','','','',''];TO=false;trender();tstat('Your move (X). The AI is unbeatable - aim for a draw.');}\n" as *u8)
120
121 w = gm_cat(h, w, "treset();creset();\n" as *u8)
122 return w
123}