stella-coder 5.3.1 → 5.3.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.
Files changed (52) hide show
  1. package/COMMANDS.md +47 -0
  2. package/install-av.bat +17 -0
  3. package/install-stella-pkg.bat +21 -0
  4. package/oneline-av.ps1 +7 -0
  5. package/oneline-stella.ps1 +2 -0
  6. package/package.json +1 -1
  7. package/releases/stella-antivirus/database.mjs +871 -0
  8. package/releases/stella-antivirus/index.mjs +8 -0
  9. package/releases/stella-antivirus/install-av.bat +12 -0
  10. package/releases/stella-antivirus/scanner.mjs +591 -0
  11. package/releases/stella-antivirus/ui.mjs +570 -0
  12. package/releases/stella-antivirus.zip +0 -0
  13. package/releases/stella-coder/README.md +67 -0
  14. package/releases/stella-coder/adb.mjs +200 -0
  15. package/releases/stella-coder/autonomous-agent.mjs +550 -0
  16. package/releases/stella-coder/banner.mjs +46 -0
  17. package/releases/stella-coder/browser-control.mjs +274 -0
  18. package/releases/stella-coder/build.mjs +151 -0
  19. package/releases/stella-coder/charts.mjs +411 -0
  20. package/releases/stella-coder/coding-brain.mjs +753 -0
  21. package/releases/stella-coder/game-engine.mjs +708 -0
  22. package/releases/stella-coder/gdrive-backup.mjs +338 -0
  23. package/releases/stella-coder/git-api.mjs +407 -0
  24. package/releases/stella-coder/gmail.mjs +415 -0
  25. package/releases/stella-coder/home-assistant.mjs +168 -0
  26. package/releases/stella-coder/index.mjs +5810 -0
  27. package/releases/stella-coder/install-stella.bat +12 -0
  28. package/releases/stella-coder/markdown.mjs +100 -0
  29. package/releases/stella-coder/mcp.mjs +296 -0
  30. package/releases/stella-coder/package.json +67 -0
  31. package/releases/stella-coder/presentations.mjs +1106 -0
  32. package/releases/stella-coder/protect.mjs +182 -0
  33. package/releases/stella-coder/screen-monitor.mjs +334 -0
  34. package/releases/stella-coder/sea-config.json +5 -0
  35. package/releases/stella-coder/security.mjs +237 -0
  36. package/releases/stella-coder/subagents.mjs +142 -0
  37. package/releases/stella-coder/telegram-bot.mjs +824 -0
  38. package/releases/stella-coder/tg-server.mjs +116 -0
  39. package/releases/stella-coder/theme.mjs +91 -0
  40. package/releases/stella-coder/tools.mjs +3143 -0
  41. package/releases/stella-coder/web-parser.mjs +229 -0
  42. package/releases/stella-coder/yandex-maps.mjs +426 -0
  43. package/releases/stella-coder.zip +0 -0
  44. package/run-antivirus.bat +4 -0
  45. package/setup-antivirus.bat +64 -0
  46. package/setup-full.bat +65 -0
  47. package/setup-full.ps1 +84 -0
  48. package/setup-stella.bat +46 -0
  49. package/stella-cli/index.mjs +12 -2
  50. package/stella-cli/protect.mjs +182 -0
  51. package/stella.bat +3 -0
  52. package/tests/test-modules.mjs +101 -0
@@ -0,0 +1,708 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import os from "node:os"
4
+ import { execSync } from "node:child_process"
5
+
6
+ const GAMES_DIR = path.join(os.homedir(), ".stella", "games")
7
+
8
+ function ensureDir() {
9
+ if (!fs.existsSync(GAMES_DIR)) fs.mkdirSync(GAMES_DIR, { recursive: true })
10
+ }
11
+
12
+ function openFile(filePath) {
13
+ try {
14
+ if (process.platform === "win32") {
15
+ execSync(`start "" "${filePath}"`, { shell: "cmd.exe", stdio: "ignore" })
16
+ } else if (process.platform === "darwin") {
17
+ execSync(`open "${filePath}"`, { stdio: "ignore" })
18
+ } else {
19
+ execSync(`xdg-open "${filePath}"`, { stdio: "ignore" })
20
+ }
21
+ } catch {}
22
+ }
23
+
24
+ function saveAndOpen(html, name) {
25
+ ensureDir()
26
+ const filename = `${name.replace(/[^a-zA-Z0-9_-]/g, "_")}_${Date.now()}.html`
27
+ const filePath = path.join(GAMES_DIR, filename)
28
+ fs.writeFileSync(filePath, html)
29
+ openFile(filePath)
30
+ return { success: true, path: filePath, filename }
31
+ }
32
+
33
+ const SNAKE_GAME = `
34
+ <!DOCTYPE html><html><head><meta charset="UTF-8"><title>Snake</title>
35
+ <style>
36
+ *{margin:0;padding:0;box-sizing:border-box}
37
+ body{background:#1e1e2e;color:#cdd6f4;font-family:'Segoe UI',system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;overflow:hidden}
38
+ h1{font-size:24px;margin-bottom:8px;color:#a6e3a1}
39
+ .info{display:flex;gap:24px;margin-bottom:12px;font-size:14px;color:#a6adc8}
40
+ canvas{border:2px solid #45475a;border-radius:8px;background:#181825}
41
+ .controls{margin-top:12px;display:flex;gap:8px}
42
+ .controls button{padding:8px 16px;border:1px solid #45475a;border-radius:6px;background:#313244;color:#cdd6f4;cursor:pointer;font-size:14px}
43
+ .controls button:hover{background:#45475a}
44
+ .gameover{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(30,30,46,0.95);padding:32px;border-radius:16px;text-align:center;display:none;border:2px solid #f38ba8}
45
+ .gameover h2{color:#f38ba8;margin-bottom:8px}
46
+ .gameover p{color:#a6adc8;margin-bottom:16px}
47
+ </style></head><body>
48
+ <h1>Snake</h1>
49
+ <div class="info"><span>Очки: <b id="score">0</b></span><span>Рекорд: <b id="best">0</b></span></div>
50
+ <canvas id="c" width="400" height="400"></canvas>
51
+ <div class="controls">
52
+ <button onclick="startGame()">Заново</button>
53
+ </div>
54
+ <div class="gameover" id="go">
55
+ <h2>Игра окончена!</h2>
56
+ <p>Очки: <b id="finalScore">0</b></p>
57
+ <button onclick="startGame();document.getElementById('go').style.display='none'" style="padding:10px 24px;border:1px solid #f38ba8;border-radius:6px;background:#f38ba8;color:#1e1e2e;cursor:pointer;font-size:16px">Заново</button>
58
+ </div>
59
+ <script>
60
+ const c=document.getElementById('c'),ctx=c.getContext('2d');
61
+ const S=20,W=c.width/S,H=c.height/S;
62
+ let snake,food,score,best=+localStorage.getItem('snakeBest')||0,dir,loop,speed;
63
+ document.getElementById('best').textContent=best;
64
+
65
+ function startGame(){
66
+ snake=[{x:10,y:10}];dir={x:1,y:0};score=0;speed=120;
67
+ document.getElementById('score').textContent=0;
68
+ document.getElementById('go').style.display='none';
69
+ placeFood();
70
+ clearInterval(loop);
71
+ loop=setInterval(tick,speed);
72
+ }
73
+
74
+ function placeFood(){
75
+ do{food={x:Math.floor(Math.random()*W),y:Math.floor(Math.random()*H)}}
76
+ while(snake.some(s=>s.x===food.x&&s.y===food.y));
77
+ }
78
+
79
+ function tick(){
80
+ const head={x:snake[0].x+dir.x,y:snake[0].y+dir.y};
81
+ if(head.x<0||head.x>=W||head.y<0||head.y>=H||snake.some(s=>s.x===head.x&&s.y===head.y)){
82
+ clearInterval(loop);
83
+ if(score>best){best=score;localStorage.setItem('snakeBest',best);document.getElementById('best').textContent=best}
84
+ document.getElementById('finalScore').textContent=score;
85
+ document.getElementById('go').style.display='block';
86
+ return;
87
+ }
88
+ snake.unshift(head);
89
+ if(head.x===food.x&&head.y===food.y){
90
+ score+=10;
91
+ document.getElementById('score').textContent=score;
92
+ placeFood();
93
+ if(speed>60){speed-=2;clearInterval(loop);loop=setInterval(tick,speed)}
94
+ }else snake.pop();
95
+ draw();
96
+ }
97
+
98
+ function draw(){
99
+ ctx.fillStyle='#181825';ctx.fillRect(0,0,c.width,c.height);
100
+ snake.forEach((s,i)=>{
101
+ ctx.fillStyle=i?'#a6e3a1':'#94e2d5';
102
+ ctx.fillRect(s.x*S+1,s.y*S+1,S-2,S-2);
103
+ });
104
+ ctx.fillStyle='#f38ba8';
105
+ ctx.beginPath();ctx.arc(food.x*S+S/2,food.y*S+S/2,S/2-2,0,Math.PI*2);ctx.fill();
106
+ }
107
+
108
+ document.addEventListener('keydown',e=>{
109
+ const map={ArrowUp:{x:0,y:-1},ArrowDown:{x:0,y:1},ArrowLeft:{x:-1,y:0},ArrowRight:{x:1,y:0},
110
+ w:{x:0,y:-1},s:{x:0,y:1},a:{x:-1,y:0},d:{x:1,y:0}};
111
+ const nd=map[e.key];
112
+ if(nd&&!(nd.x===-dir.x&&nd.y===-dir.y)){dir=nd;e.preventDefault()}
113
+ });
114
+
115
+ let tx=0,ty=0;
116
+ c.addEventListener('touchstart',e=>{tx=e.touches[0].clientX;ty=e.touches[0].clientY;e.preventDefault()},{passive:false});
117
+ c.addEventListener('touchmove',e=>{
118
+ const dx=e.touches[0].clientX-tx,dy=e.touches[0].clientY-ty;
119
+ if(Math.abs(dx)>Math.abs(dy)){dir={x:dx>0?1:-1,y:0}}else{dir={x:0,y:dy>0?1:-1}}
120
+ e.preventDefault();
121
+ },{passive:false});
122
+
123
+ startGame();
124
+ </script></body></html>`
125
+
126
+ const TETRIS_GAME = `
127
+ <!DOCTYPE html><html><head><meta charset="UTF-8"><title>Tetris</title>
128
+ <style>
129
+ *{margin:0;padding:0;box-sizing:border-box}
130
+ body{background:#1e1e2e;color:#cdd6f4;font-family:'Segoe UI',system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;overflow:hidden}
131
+ h1{font-size:24px;margin-bottom:8px;color:#89b4fa}
132
+ .info{display:flex;gap:24px;margin-bottom:12px;font-size:14px;color:#a6adc8}
133
+ canvas{border:2px solid #45475a;border-radius:8px;background:#181825}
134
+ .controls{margin-top:12px;display:flex;gap:8px}
135
+ .controls button{padding:8px 16px;border:1px solid #45475a;border-radius:6px;background:#313244;color:#cdd6f4;cursor:pointer;font-size:14px}
136
+ .controls button:hover{background:#45475a}
137
+ </style></head><body>
138
+ <h1>Tetris</h1>
139
+ <div class="info"><span>Очки: <b id="score">0</b></span><span>Уровень: <b id="level">1</b></span></div>
140
+ <canvas id="c" width="300" height="600"></canvas>
141
+ <div class="controls">
142
+ <button onclick="startGame()">Заново</button>
143
+ </div>
144
+ <script>
145
+ const c=document.getElementById('c'),ctx=c.getContext('2d');
146
+ const COLS=10,ROWS=20,S=30;
147
+ const PIECES=[[[1,1,1,1]],[[1,1],[1,1]],[[0,1,0],[1,1,1]],[[1,0,0],[1,1,1]],[[0,0,1],[1,1,1]],[[0,1,1],[1,1,0]],[[1,1,0],[0,1,1]]];
148
+ const COLORS=['#f38ba8','#fab387','#f9e2af','#a6e3a1','#89b4fa','#cba6f7','#94e2d5'];
149
+ let board,piece,pX,pY,color,score,level,lines,loop,speed;
150
+
151
+ function startGame(){
152
+ board=Array.from({length:ROWS},()=>Array(COLS).fill(0));
153
+ score=0;level=1;lines=0;speed=500;
154
+ document.getElementById('score').textContent=0;
155
+ document.getElementById('level').textContent=1;
156
+ spawn();clearInterval(loop);loop=setInterval(tick,speed);
157
+ }
158
+
159
+ function spawn(){
160
+ const i=Math.floor(Math.random()*PIECES.length);
161
+ piece=PIECES[i];color=COLORS[i];pX=Math.floor((COLS-piece[0].length)/2);pY=0;
162
+ if(collides(piece,pX,pY)){clearInterval(loop);alert('Game Over! Score: '+score)}
163
+ }
164
+
165
+ function collides(p,px,py){
166
+ for(let r=0;r<p.length;r++)for(let c=0;c<p[r].length;c++)
167
+ if(p[r][c]&&(py+r>=ROWS||px+c<0||px+c>=COLS||board[py+r]?.[px+c]))return true;
168
+ return false;
169
+ }
170
+
171
+ function merge(){
172
+ for(let r=0;r<piece.length;r++)for(let c=0;c<piece[r].length;c++)
173
+ if(piece[r][c])board[pY+r][pX+c]=color;
174
+ }
175
+
176
+ function clearLines(){
177
+ let cleared=0;
178
+ for(let r=ROWS-1;r>=0;r--){
179
+ if(board[r].every(c=>c)){board.splice(r,1);board.unshift(Array(COLS).fill(0));cleared++;r++}
180
+ }
181
+ if(cleared){lines+=cleared;score+=[0,100,300,500,800][cleared]*level;level=Math.floor(lines/10)+1;
182
+ document.getElementById('score').textContent=score;document.getElementById('level').textContent=level;
183
+ clearInterval(loop);speed=Math.max(50,500-level*40);loop=setInterval(tick,speed)}
184
+ }
185
+
186
+ function tick(){
187
+ if(!collides(piece,pX,pY+1)){pY++}else{merge();clearLines();spawn()}draw();
188
+ }
189
+
190
+ function draw(){
191
+ ctx.fillStyle='#181825';ctx.fillRect(0,0,c.width,c.height);
192
+ for(let r=0;r<ROWS;r++)for(let cl=0;cl<COLS;cl++)
193
+ if(board[r][cl]){ctx.fillStyle=board[r][cl];ctx.fillRect(cl*S+1,r*S+1,S-2,S-2)}
194
+ ctx.fillStyle=color;
195
+ for(let r=0;r<piece.length;r++)for(let cl=0;cl<piece[r].length;cl++)
196
+ if(piece[r][cl])ctx.fillRect((pX+cl)*S+1,(pY+r)*S+1,S-2,S-2);
197
+ ctx.strokeStyle='#45475a';ctx.lineWidth=0.5;
198
+ for(let r=0;r<=ROWS;r++){ctx.beginPath();ctx.moveTo(0,r*S);ctx.lineTo(c.width,r*S);ctx.stroke()}
199
+ for(let cl=0;cl<=COLS;cl++){ctx.beginPath();ctx.moveTo(cl*S,0);ctx.lineTo(cl*S,c.height);ctx.stroke()}
200
+ }
201
+
202
+ function rotate(){
203
+ const rot=piece[0].map((_,i)=>piece.map(r=>r[i]).reverse());
204
+ if(!collides(rot,pX,pY))piece=rot;
205
+ }
206
+
207
+ document.addEventListener('keydown',e=>{
208
+ const map={ArrowLeft:()=>{if(!collides(piece,pX-1,pY))pX--},
209
+ ArrowRight:()=>{if(!collides(piece,pX+1,pY))pX++},
210
+ ArrowDown:()=>{if(!collides(piece,pX,pY+1)){pY++}else{merge();clearLines();spawn()}draw()},
211
+ ArrowUp:()=>rotate(),' ':()=>{while(!collides(piece,pX,pY+1))pY++;merge();clearLines();spawn();draw()}};
212
+ if(map[e.key]){map[e.key]();e.preventDefault()}
213
+ });
214
+
215
+ let tx=0;
216
+ c.addEventListener('touchstart',e=>{tx=e.touches[0].clientX;e.preventDefault()},{passive:false});
217
+ c.addEventListener('touchend',e=>{
218
+ const dx=e.changedTouches[0].clientX-tx;
219
+ if(Math.abs(dx)>50){dx>0?!collides(piece,pX+1,pY)&&pX++:!collides(piece,pX-1,pY)&&pX--}
220
+ else{!collides(piece,pX,pY+1)?pY++:(merge(),clearLines(),spawn())}
221
+ draw();
222
+ });
223
+
224
+ startGame();
225
+ </script></body></html>`
226
+
227
+ const MINESWEEPER_GAME = `
228
+ <!DOCTYPE html><html><head><meta charset="UTF-8"><title>Minesweeper</title>
229
+ <style>
230
+ *{margin:0;padding:0;box-sizing:border-box}
231
+ body{background:#1e1e2e;color:#cdd6f4;font-family:'Segoe UI',system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;overflow:hidden}
232
+ h1{font-size:24px;margin-bottom:8px;color:#f9e2af}
233
+ .info{display:flex;gap:24px;margin-bottom:12px;font-size:14px;color:#a6adc8}
234
+ .board{display:inline-grid;gap:2px;background:#45475a;padding:4px;border-radius:8px}
235
+ .cell{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:4px;cursor:pointer;font-size:14px;font-weight:bold;user-select:none}
236
+ .cell.hidden{background:#313244}.cell.hidden:hover{background:#45475a}
237
+ .cell.revealed{background:#1e1e2e}
238
+ .cell.mine{background:#f38ba8}
239
+ .cell.flagged{background:#f9e2af;color:#1e1e2e}
240
+ .controls{margin-top:12px;display:flex;gap:8px}
241
+ .controls button{padding:8px 16px;border:1px solid #45475a;border-radius:6px;background:#313244;color:#cdd6f4;cursor:pointer;font-size:14px}
242
+ .controls button:hover{background:#45475a}
243
+ </style></head><body>
244
+ <h1>Minesweeper</h1>
245
+ <div class="info"><span>Мины: <b id="mines">0</b></span><span>Флаги: <b id="flags">0</b></span></div>
246
+ <div class="board" id="board"></div>
247
+ <div class="controls"><button onclick="initGame()">Заново</button></div>
248
+ <script>
249
+ const ROWS=10,COLS=10,MINES=15;
250
+ let board,revealed,flagged,gameOver,firstClick;
251
+ const colors=['','#89b4fa','#a6e3a1','#f38ba8','#cba6f7','#fab387','#94e2d5','#cdd6f4','#6c7086'];
252
+
253
+ function initGame(){
254
+ board=Array.from({length:ROWS},()=>Array(COLS).fill(0));
255
+ revealed=Array.from({length:ROWS},()=>Array(COLS).fill(false));
256
+ flagged=Array.from({length:ROWS},()=>Array(COLS).fill(false));
257
+ gameOver=false;firstClick=true;
258
+ document.getElementById('mines').textContent=MINES;
259
+ document.getElementById('flags').textContent=0;
260
+ render();
261
+ }
262
+
263
+ function placeMines(safeR,safeC){
264
+ let placed=0;
265
+ while(placed<MINES){
266
+ const r=Math.floor(Math.random()*ROWS),c=Math.floor(Math.random()*COLS);
267
+ if(board[r][c]!==-1&&Math.abs(r-safeR)>1&&Math.abs(c-safeC)>1){board[r][c]=-1;placed++}
268
+ }
269
+ for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++){
270
+ if(board[r][c]===-1)continue;
271
+ let count=0;
272
+ for(let dr=-1;dr<=1;dr++)for(let dc=-1;dc<=1;dc++){
273
+ const nr=r+dr,nc=c+dc;
274
+ if(nr>=0&&nr<ROWS&&nc>=0&&nc<COLS&&board[nr][nc]===-1)count++;
275
+ }
276
+ board[r][c]=count;
277
+ }
278
+ }
279
+
280
+ function reveal(r,c){
281
+ if(r<0||r>=ROWS||c<0||c>=COLS||revealed[r][c]||flagged[r][c])return;
282
+ revealed[r][c]=true;
283
+ if(board[r][c]===0){for(let dr=-1;dr<=1;dr++)for(let dc=-1;dc<=1;dc++)reveal(r+dr,c+dc)}
284
+ }
285
+
286
+ function render(){
287
+ const el=document.getElementById('board');
288
+ el.style.gridTemplateColumns='repeat('+COLS+',32px)';
289
+ el.innerHTML='';
290
+ for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++){
291
+ const cell=document.createElement('div');
292
+ cell.className='cell';
293
+ if(revealed[r][c]){
294
+ cell.classList.add('revealed');
295
+ if(board[r][c]===-1){cell.classList.add('mine');cell.textContent='💣'}
296
+ else if(board[r][c]>0){cell.textContent=board[r][c];cell.style.color=colors[board[r][c]]}
297
+ }else{
298
+ cell.classList.add('hidden');
299
+ if(flagged[r][c]){cell.classList.add('flagged');cell.textContent='🚩'}
300
+ }
301
+ cell.addEventListener('click',()=>{
302
+ if(gameOver||flagged[r][c])return;
303
+ if(firstClick){firstClick=false;placeMines(r,c)}
304
+ if(board[r][c]===-1){gameOver=true;for(let rr=0;rr<ROWS;rr++)for(let cc=0;cc<COLS;cc++)if(board[rr][cc]===-1)revealed[rr][cc]=true;render();alert('Game Over!');return}
305
+ reveal(r,c);
306
+ let unrevealed=0;for(let rr=0;rr<ROWS;rr++)for(let cc=0;cc<COLS;cc++)if(!revealed[rr][cc]&&board[rr][cc]!==-1)unrevealed++;
307
+ if(unrevealed===0){gameOver=true;alert('You Win!')}render();
308
+ });
309
+ cell.addEventListener('contextmenu',e=>{
310
+ e.preventDefault();if(gameOver||revealed[r][c])return;
311
+ flagged[r][c]=!flagged[r][c];
312
+ document.getElementById('flags').textContent=document.querySelectorAll('.flagged').length;
313
+ render();
314
+ });
315
+ el.appendChild(cell);
316
+ }
317
+ }
318
+
319
+ initGame();
320
+ </script></body></html>`
321
+
322
+ const GAME_2048 = `
323
+ <!DOCTYPE html><html><head><meta charset="UTF-8"><title>2048</title>
324
+ <style>
325
+ *{margin:0;padding:0;box-sizing:border-box}
326
+ body{background:#1e1e2e;color:#cdd6f4;font-family:'Segoe UI',system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;overflow:hidden}
327
+ h1{font-size:24px;margin-bottom:8px;color:#fab387}
328
+ .info{display:flex;gap:24px;margin-bottom:12px;font-size:14px;color:#a6adc8}
329
+ .board{display:grid;grid-template-columns:repeat(4,80px);gap:8px;background:#45475a;padding:8px;border-radius:12px}
330
+ .cell{width:80px;height:80px;display:flex;align-items:center;justify-content:center;border-radius:8px;font-size:24px;font-weight:bold;background:#313244;transition:all 0.15s}
331
+ .controls{margin-top:12px;display:flex;gap:8px}
332
+ .controls button{padding:8px 16px;border:1px solid #45475a;border-radius:6px;background:#313244;color:#cdd6f4;cursor:pointer;font-size:14px}
333
+ </style></head><body>
334
+ <h1>2048</h1>
335
+ <div class="info"><span>Очки: <b id="score">0</b></span><span>Рекорд: <b id="best">0</b></span></div>
336
+ <div class="board" id="board"></div>
337
+ <div class="controls"><button onclick="startGame()">Заново</button></div>
338
+ <script>
339
+ let grid,score,best=+localStorage.getItem('2048best')||0;
340
+ document.getElementById('best').textContent=best;
341
+
342
+ function startGame(){
343
+ grid=Array.from({length:4},()=>Array(4).fill(0));score=0;
344
+ document.getElementById('score').textContent=0;
345
+ document.getElementById('go')&&(document.getElementById('go').style.display='none');
346
+ addTile();addTile();render();
347
+ }
348
+
349
+ function addTile(){
350
+ const empty=[];for(let r=0;r<4;r++)for(let c=0;c<4;c++)if(!grid[r][c])empty.push([r,c]);
351
+ if(!empty.length)return;
352
+ const[r,c]=empty[Math.floor(Math.random()*empty.length)];
353
+ grid[r][c]=Math.random()<0.9?2:4;
354
+ }
355
+
356
+ function render(){
357
+ const el=document.getElementById('board');el.innerHTML='';
358
+ const tileColors={0:'#313244',2:'#cdd6f4',4:'#a6adc8',8:'#fab387',16:'#f38ba8',32:'#eba0ac',64:'#f38ba8',128:'#94e2d5',256:'#a6e3a1',512:'#f9e2af',1024:'#89b4fa',2048:'#cba6f7'};
359
+ for(let r=0;r<4;r++)for(let c=0;c<4;c++){
360
+ const cell=document.createElement('div');cell.className='cell';
361
+ cell.style.background=tileColors[grid[r][c]]||'#cba6f7';
362
+ cell.style.color=grid[r][c]<=4?'#1e1e2e':'#cdd6f4';
363
+ cell.textContent=grid[r][c]||'';el.appendChild(cell);
364
+ }
365
+ }
366
+
367
+ function slide(row){
368
+ let a=row.filter(x=>x);for(let i=0;i<a.length-1;i++)if(a[i]===a[i+1]){a[i]*=2;a[i+1]=0;score+=a[i]}
369
+ while(a.length<4)a.push(0);return a;
370
+ }
371
+
372
+ function move(dir){
373
+ let moved=false;const prev=JSON.stringify(grid);
374
+ if(dir==='left'){for(let r=0;r<4;r++)grid[r]=slide(grid[r])}
375
+ else if(dir==='right'){for(let r=0;r<4;r++)grid[r]=slide(grid[r].reverse()).reverse()}
376
+ else if(dir==='up'){for(let c=0;c<4;c++){const col=slide([grid[0][c],grid[1][c],grid[2][c],grid[3][c]]);for(let r=0;r<4;r++)grid[r][c]=col[r]}}
377
+ else if(dir==='down'){for(let c=0;c<4;c++){const col=slide([grid[3][c],grid[2][c],grid[1][c],grid[0][c]]);for(let r=0;r<4;r++)grid[r][c]=col[3-r]}}
378
+ if(JSON.stringify(grid)!==prev){moved=true;addTile()}
379
+ document.getElementById('score').textContent=score;
380
+ if(score>best){best=score;localStorage.setItem('2048best',best);document.getElementById('best').textContent=best}
381
+ if(moved)render();
382
+ if(!canMove()){alert('Game Over! Score: '+score)}
383
+ }
384
+
385
+ function canMove(){
386
+ for(let r=0;r<4;r++)for(let c=0;c<4;c++){
387
+ if(!grid[r][c])return true;
388
+ if(c<3&&grid[r][c]===grid[r][c+1])return true;
389
+ if(r<3&&grid[r][c]===grid[r+1][c])return true;
390
+ }
391
+ return false;
392
+ }
393
+
394
+ document.addEventListener('keydown',e=>{
395
+ const map={ArrowLeft:'left',ArrowRight:'right',ArrowUp:'up',ArrowDown:'down',a:'left',d:'right',w:'up',s:'down'};
396
+ if(map[e.key]){move(map[e.key]);e.preventDefault()}
397
+ });
398
+
399
+ let tx=0,ty=0;
400
+ document.addEventListener('touchstart',e=>{tx=e.touches[0].clientX;ty=e.touches[0].clientY;e.preventDefault()},{passive:false});
401
+ document.addEventListener('touchend',e=>{
402
+ const dx=e.changedTouches[0].clientX-tx,dy=e.changedTouches[0].clientY-ty;
403
+ if(Math.abs(dx)>Math.abs(dy)){dx>0?move('right'):move('left')}else{dy>0?move('down'):move('up')}
404
+ });
405
+
406
+ startGame();
407
+ </script></body></html>`
408
+
409
+ const FLAPPY_BIRD_GAME = `
410
+ <!DOCTYPE html><html><head><meta charset="UTF-8"><title>Flappy Bird</title>
411
+ <style>
412
+ *{margin:0;padding:0;box-sizing:border-box}
413
+ body{background:#1e1e2e;color:#cdd6f4;font-family:'Segoe UI',system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;overflow:hidden}
414
+ h1{font-size:24px;margin-bottom:8px;color:#f9e2af}
415
+ .info{display:flex;gap:24px;margin-bottom:12px;font-size:14px;color:#a6adc8}
416
+ canvas{border:2px solid #45475a;border-radius:8px;background:#181825}
417
+ .controls{margin-top:12px;display:flex;gap:8px}
418
+ .controls button{padding:8px 16px;border:1px solid #45475a;border-radius:6px;background:#313244;color:#cdd6f4;cursor:pointer;font-size:14px}
419
+ </style></head><body>
420
+ <h1>Flappy Bird</h1>
421
+ <div class="info"><span>Очки: <b id="score">0</b></span><span>Рекорд: <b id="best">0</b></span></div>
422
+ <canvas id="c" width="400" height="600"></canvas>
423
+ <div class="controls"><button onclick="startGame()">Заново</button></div>
424
+ <script>
425
+ const c=document.getElementById('c'),ctx=c.getContext('2d');
426
+ let bird,pipes,score,best=+localStorage.getItem('flappyBest')||0,frame,gravity=0.5,jump=-8,pipeW=60,pipeGap=150,speed=2,running=false;
427
+ document.getElementById('best').textContent=best;
428
+
429
+ function startGame(){
430
+ bird={x:100,y:300,vy:0,r:15};pipes=[];score=0;frame=0;running=true;
431
+ document.getElementById('score').textContent=0;
432
+ for(let i=0;i<5;i++)pipes.push({x:400+i*200,h:100+Math.random()*300});
433
+ cancelAnimationFrame(loop);loop=requestAnimationFrame(tick);
434
+ }
435
+
436
+ function tick(){
437
+ if(!running)return;
438
+ bird.vy+=gravity;bird.y+=bird.vy;
439
+ if(bird.y>c.height||bird.y<0)endGame();
440
+ if(frame%90===0)pipes.push({x:c.width,h:80+Math.random()*(c.height-300)});
441
+ pipes.forEach(p=>{p.x-=speed;
442
+ if(p.x+pipeW>bird.x-bird.r&&p.x<bird.x+bird.r){
443
+ if(bird.y-bird.r<p.h||bird.y+bird.r>p.h+pipeGap)endGame();
444
+ }
445
+ if(p.x+pipeW<bird.x&&!p.scored){p.scored=true;score++;document.getElementById('score').textContent=score}
446
+ });
447
+ pipes=pipes.filter(p=>p.x>-pipeW);
448
+ draw();frame=requestAnimationFrame(tick);
449
+ }
450
+
451
+ function draw(){
452
+ ctx.fillStyle='#181825';ctx.fillRect(0,0,c.width,c.height);
453
+ pipes.forEach(p=>{
454
+ ctx.fillStyle='#a6e3a1';ctx.fillRect(p.x,0,pipeW,p.h);ctx.fillRect(p.x,p.h+pipeGap,pipeW,c.height);
455
+ ctx.fillStyle='#94e2d5';ctx.fillRect(p.x-2,p.h-10,pipeW+4,10);ctx.fillRect(p.x-2,p.h+pipeGap,pipeW+4,10);
456
+ });
457
+ ctx.fillStyle='#f9e2af';ctx.beginPath();ctx.arc(bird.x,bird.y,bird.r,0,Math.PI*2);ctx.fill();
458
+ ctx.fillStyle='#1e1e2e';ctx.beginPath();ctx.arc(bird.x+5,bird.y-3,3,0,Math.PI*2);ctx.fill();
459
+ }
460
+
461
+ function endGame(){
462
+ running=false;
463
+ if(score>best){best=score;localStorage.setItem('flappyBest',best);document.getElementById('best').textContent=best}
464
+ alert('Game Over! Score: '+score);
465
+ }
466
+
467
+ document.addEventListener('keydown',e=>{if(e.code==='Space'||e.code==='ArrowUp'){if(running)bird.vy=jump;e.preventDefault()}});
468
+ c.addEventListener('click',()=>{if(running)bird.vy=jump});
469
+ c.addEventListener('touchstart',e=>{if(running)bird.vy=jump;e.preventDefault()},{passive:false});
470
+
471
+ startGame();
472
+ </script></body></html>`
473
+
474
+ const TIC_TAC_TOE_GAME = `
475
+ <!DOCTYPE html><html><head><meta charset="UTF-8"><title>Tic Tac Toe</title>
476
+ <style>
477
+ *{margin:0;padding:0;box-sizing:border-box}
478
+ body{background:#1e1e2e;color:#cdd6f4;font-family:'Segoe UI',system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;overflow:hidden}
479
+ h1{font-size:24px;margin-bottom:8px;color:#89b4fa}
480
+ .info{margin-bottom:12px;font-size:16px;color:#a6adc8;min-height:24px}
481
+ .board{display:grid;grid-template-columns:repeat(3,100px);gap:8px}
482
+ .cell{width:100px;height:100px;display:flex;align-items:center;justify-content:center;background:#313244;border-radius:8px;font-size:40px;cursor:pointer;transition:all 0.2s}
483
+ .cell:hover{background:#45475a}
484
+ .cell.x{color:#89b4fa}.cell.o{color:#f38ba8}.cell.win{background:#a6e3a1;color:#1e1e2e}
485
+ .controls{margin-top:16px;display:flex;gap:8px}
486
+ .controls button{padding:8px 16px;border:1px solid #45475a;border-radius:6px;background:#313244;color:#cdd6f4;cursor:pointer;font-size:14px}
487
+ </style></head><body>
488
+ <h1>Tic Tac Toe</h1>
489
+ <div class="info" id="info">Ход X</div>
490
+ <div class="board" id="board"></div>
491
+ <div class="controls">
492
+ <button onclick="startGame()">Заново</button>
493
+ <button onclick="toggleMode()">Режим: <span id="mode">Игрок vs Игрок</span></button>
494
+ </div>
495
+ <script>
496
+ let board,turn,gameOver,pvpMode=true;
497
+ const wins=[[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
498
+
499
+ function startGame(){
500
+ board=Array(9).fill('');turn='X';gameOver=false;
501
+ document.getElementById('info').textContent='Ход X';
502
+ render();
503
+ }
504
+
505
+ function toggleMode(){
506
+ pvpMode=!pvpMode;
507
+ document.getElementById('mode').textContent=pvpMode?'Игрок vs ИИ':'Игрок vs Игрок';
508
+ startGame();
509
+ }
510
+
511
+ function render(){
512
+ const el=document.getElementById('board');el.innerHTML='';
513
+ board.forEach((v,i)=>{
514
+ const cell=document.createElement('div');cell.className='cell'+(v?' '+v.toLowerCase():'');
515
+ cell.textContent=v;
516
+ cell.addEventListener('click',()=>play(i));
517
+ el.appendChild(cell);
518
+ });
519
+ }
520
+
521
+ function play(i){
522
+ if(board[i]||gameOver)return;
523
+ board[i]=turn;render();
524
+ if(checkWin(turn)){document.getElementById('info').textContent=turn+' победил!';gameOver=true;highlightWin();return}
525
+ if(board.every(c=>c)){document.getElementById('info').textContent='Ничья!';gameOver=true;return}
526
+ turn=turn==='X'?'O':'X';
527
+ document.getElementById('info').textContent='Ход '+turn;
528
+ if(!pvpMode&&turn==='O'&&!gameOver)aiMove();
529
+ }
530
+
531
+ function aiMove(){
532
+ for(const w of wins){
533
+ const vals=w.map(i=>board[i]);
534
+ if(vals.filter(v=>v==='O').length===2&&vals.includes('')){play(w[vals.indexOf('')]);return}
535
+ }
536
+ for(const w of wins){
537
+ const vals=w.map(i=>board[i]);
538
+ if(vals.filter(v=>v==='X').length===2&&vals.includes('')){play(w[vals.indexOf('')]);return}
539
+ }
540
+ if(!board[4]){play(4);return}
541
+ const corners=[0,2,6,8].filter(i=>!board[i]);
542
+ if(corners.length){play(corners[Math.floor(Math.random()*corners.length)]);return}
543
+ const empty=board.map((v,i)=>v?'':i).filter(v=>v!=='');
544
+ if(empty.length)play(empty[Math.floor(Math.random()*empty.length)]);
545
+ }
546
+
547
+ function checkWin(p){
548
+ return wins.some(w=>w.every(i=>board[i]===p));
549
+ }
550
+
551
+ function highlightWin(){
552
+ const w=wins.find(w=>w.every(i=>board[i]===turn));
553
+ if(w){const cells=document.querySelectorAll('.cell');w.forEach(i=>cells[i].classList.add('win'))}
554
+ }
555
+
556
+ startGame();
557
+ </script></body></html>`
558
+
559
+ const SUDOKU_GAME = `
560
+ <!DOCTYPE html><html><head><meta charset="UTF-8"><title>Sudoku</title>
561
+ <style>
562
+ *{margin:0;padding:0;box-sizing:border-box}
563
+ body{background:#1e1e2e;color:#cdd6f4;font-family:'Segoe UI',system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;overflow:hidden}
564
+ h1{font-size:24px;margin-bottom:8px;color:#cba6f7}
565
+ .info{display:flex;gap:24px;margin-bottom:12px;font-size:14px;color:#a6adc8}
566
+ .board{display:grid;grid-template-columns:repeat(9,40px);gap:1px;background:#45475a;padding:2px;border-radius:8px}
567
+ .cell{width:40px;height:40px;display:flex;align-items:center;justify-content:center;background:#313244;font-size:18px;cursor:pointer;transition:all 0.15s}
568
+ .cell:hover{background:#45475a}
569
+ .cell.fixed{color:#6c7086;font-weight:bold}
570
+ .cell.selected{background:#45475a;box-shadow:inset 0 0 0 2px #89b4fa}
571
+ .cell.error{color:#f38ba8}
572
+ .cell.correct{color:#a6e3a1}
573
+ .numpad{margin-top:12px;display:flex;gap:4px;flex-wrap:wrap;justify-content:center;max-width:370px}
574
+ .numpad button{width:40px;height:40px;border:1px solid #45475a;border-radius:6px;background:#313244;color:#cdd6f4;cursor:pointer;font-size:16px}
575
+ .numpad button:hover{background:#45475a}
576
+ .controls{margin-top:12px;display:flex;gap:8px}
577
+ .controls button{padding:8px 16px;border:1px solid #45475a;border-radius:6px;background:#313244;color:#cdd6f4;cursor:pointer;font-size:14px}
578
+ </style></head><body>
579
+ <h1>Sudoku</h1>
580
+ <div class="info"><span>Ошибки: <b id="errors">0</b>/3</span></div>
581
+ <div class="board" id="board"></div>
582
+ <div class="numpad" id="numpad"></div>
583
+ <div class="controls"><button onclick="newGame()">Новая игра</button></div>
584
+ <script>
585
+ let puzzle,solution,selected,errors;
586
+ const easy=[[0,0,3,0,2,0,6,0,0],[9,0,0,3,0,5,0,0,1],[0,0,1,8,0,6,4,0,0],[0,0,8,1,0,2,9,0,0],[7,0,0,0,0,0,0,0,8],[0,0,6,7,0,8,2,0,0],[0,0,2,6,0,9,5,0,0],[8,0,0,2,0,3,0,0,9],[0,0,5,0,1,0,3,0,0]];
587
+ const solutionEasy=[[4,8,3,9,2,1,6,5,7],[9,6,7,3,4,5,8,2,1],[2,5,1,8,7,6,4,9,3],[5,4,8,1,3,2,9,7,6],[7,2,9,5,6,4,1,3,8],[1,3,6,7,9,8,2,4,5],[3,7,2,6,8,9,5,1,4],[8,1,4,2,5,3,7,6,9],[6,9,5,4,1,7,3,8,2]];
588
+
589
+ function newGame(){
590
+ puzzle=easy.map(r=>[...r]);solution=solutionEasy;selected=null;errors=0;
591
+ document.getElementById('errors').textContent=0;
592
+ render();
593
+ }
594
+
595
+ function render(){
596
+ const el=document.getElementById('board');el.innerHTML='';
597
+ for(let r=0;r<9;r++)for(let c=0;c<9;c++){
598
+ const cell=document.createElement('div');cell.className='cell';
599
+ const val=puzzle[r][c];
600
+ if(val)cell.textContent=val;
601
+ if(easy[r][c])cell.classList.add('fixed');
602
+ if(selected&&selected.r===r&&selected.c===c)cell.classList.add('selected');
603
+ if(val&&solution[r][c]&&val!==solution[r][c])cell.classList.add('error');
604
+ else if(val&&solution[r][c]&&val===solution[r][c]&&!easy[r][c])cell.classList.add('correct');
605
+ cell.addEventListener('click',()=>{if(!easy[r][c])selected={r,c};render()});
606
+ el.appendChild(cell);
607
+ }
608
+ const np=document.getElementById('numpad');np.innerHTML='';
609
+ for(let n=1;n<=9;n++){
610
+ const btn=document.createElement('button');btn.textContent=n;
611
+ btn.addEventListener('click',()=>placeNumber(n));
612
+ np.appendChild(btn);
613
+ }
614
+ }
615
+
616
+ function placeNumber(n){
617
+ if(!selected)return;
618
+ puzzle[selected.r][selected.c]=n;
619
+ if(n!==solution[selected.r][selected.c]){errors++;document.getElementById('errors').textContent=errors;if(errors>=3){alert('Game Over!');newGame();return}}
620
+ if(puzzle.every((r,ri)=>r.every((c,ci)=>c===solution[ri][ci])))alert('Congratulations! You won!');
621
+ render();
622
+ }
623
+
624
+ document.addEventListener('keydown',e=>{
625
+ if(!selected)return;
626
+ const n=parseInt(e.key);
627
+ if(n>=1&&n<=9)placeNumber(n);
628
+ if(e.key==='Backspace'||e.key==='Delete'){puzzle[selected.r][selected.c]=0;render()}
629
+ const dir={ArrowUp:{r:-1,c:0},ArrowDown:{r:1,c:0},ArrowLeft:{r:0,c:-1},ArrowRight:{r:0,c:1}};
630
+ if(dir[e.key]){selected.r=(selected.r+dir[e.key].r+9)%9;selected.c=(selected.c+dir[e.key].c+9)%9;render();e.preventDefault()}
631
+ });
632
+
633
+ newGame();
634
+ </script></body></html>`
635
+
636
+ export class GameEngine {
637
+ constructor() {
638
+ ensureDir()
639
+ }
640
+
641
+ async playSnake() {
642
+ return saveAndOpen(SNAKE_GAME, "snake")
643
+ }
644
+
645
+ async playTetris() {
646
+ return saveAndOpen(TETRIS_GAME, "tetris")
647
+ }
648
+
649
+ async playMinesweeper() {
650
+ return saveAndOpen(MINESWEEPER_GAME, "minesweeper")
651
+ }
652
+
653
+ async play2048() {
654
+ return saveAndOpen(GAME_2048, "2048")
655
+ }
656
+
657
+ async playFlappyBird() {
658
+ return saveAndOpen(FLAPPY_BIRD_GAME, "flappy")
659
+ }
660
+
661
+ async playTicTacToe() {
662
+ return saveAndOpen(TIC_TAC_TOE_GAME, "tictactoe")
663
+ }
664
+
665
+ async playSudoku() {
666
+ return saveAndOpen(SUDOKU_GAME, "sudoku")
667
+ }
668
+
669
+ async play(gameName) {
670
+ const games = {
671
+ snake: () => this.playSnake(),
672
+ тетрис: () => this.playTetris(),
673
+ tetris: () => this.playTetris(),
674
+ minesweeper: () => this.playMinesweeper(),
675
+ "мины": () => this.playMinesweeper(),
676
+ 2048: () => this.play2048(),
677
+ flappy: () => this.playFlappyBird(),
678
+ "птица": () => this.playFlappyBird(),
679
+ tictactoe: () => this.playTicTacToe(),
680
+ "крестики": () => this.playTicTacToe(),
681
+ sudoku: () => this.playSudoku(),
682
+ "судоку": () => this.playSudoku(),
683
+ }
684
+ const fn = games[gameName?.toLowerCase()]
685
+ if (fn) return fn()
686
+ return { success: false, error: `Unknown game: ${gameName}. Available: ${Object.keys(games).join(", ")}` }
687
+ }
688
+
689
+ async createCustomGame(html, name = "custom") {
690
+ return saveAndOpen(html, name)
691
+ }
692
+
693
+ listGames() {
694
+ return ["snake", "tetris", "2048", "minesweeper", "flappy-bird", "tic-tac-toe", "sudoku"]
695
+ }
696
+
697
+ listSaved() {
698
+ if (!fs.existsSync(GAMES_DIR)) return []
699
+ return fs.readdirSync(GAMES_DIR)
700
+ .filter(f => f.endsWith(".html"))
701
+ .map(f => ({
702
+ name: f,
703
+ path: path.join(GAMES_DIR, f),
704
+ created: fs.statSync(path.join(GAMES_DIR, f)).birthtime,
705
+ }))
706
+ .sort((a, b) => b.created - a.created)
707
+ }
708
+ }