star-sdk-cli 0.1.14 → 0.1.16

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 (2) hide show
  1. package/dist/cli.mjs +101 -46
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -80,7 +80,39 @@ Star.game(ctx => {
80
80
 
81
81
  ## Common Patterns
82
82
 
83
- ### Game Over -> Submit Score -> Show Leaderboard
83
+ ### Game Over Screen with Leaderboard
84
+
85
+ Use DOM buttons (via \`ui.render()\` + \`on()\`) for all interactive UI \u2014 never draw buttons on canvas:
86
+
87
+ \`\`\`javascript
88
+ // UI button handler (register once \u2014 survives ui.render calls)
89
+ ctx.on('click', '#lb-btn', () => Star.leaderboard.show());
90
+ ctx.on('click', '#restart-btn', () => startGame());
91
+
92
+ // Gameplay tap handler (single handler, state-based)
93
+ canvas.addEventListener('pointerdown', () => {
94
+ if (state === 'playing') { jump(); }
95
+ });
96
+
97
+ function endGame() {
98
+ state = 'gameover';
99
+ Star.leaderboard.submit(score);
100
+ ctx.ui.render(\`
101
+ <div class="h-full flex flex-col items-center justify-center text-white">
102
+ <div class="text-3xl font-bold mb-2">GAME OVER</div>
103
+ <div class="text-6xl font-bold mb-6">\${score}</div>
104
+ <button id="lb-btn" class="px-6 py-3 mb-4 bg-purple-600 rounded-lg font-bold">
105
+ VIEW LEADERBOARD
106
+ </button>
107
+ <button id="restart-btn" class="px-6 py-3 bg-gray-700 rounded-lg">
108
+ PLAY AGAIN
109
+ </button>
110
+ </div>
111
+ \`);
112
+ }
113
+ \`\`\`
114
+
115
+ ### Submit Score Only (No UI)
84
116
 
85
117
  \`\`\`javascript
86
118
  function gameOver(finalScore) {
@@ -127,6 +159,8 @@ canvas.onclick = (e) => {
127
159
  - **Don't** use \`setInterval\` for game loops - use \`ctx.loop()\`
128
160
  - **Don't** destructure Star - use \`Star.audio\`, \`Star.leaderboard\`, etc.
129
161
  - **Don't** invent audio preset names - only 17 exist (see audio.md)
162
+ - **Don't** draw buttons on canvas (fillRect + hit-test) \u2014 use \`ui.render()\` with HTML \`<button>\` elements + \`on()\` for clicks. Canvas-drawn "buttons" conflict with tap handlers and have no hover/focus states.
163
+ - **Don't** register multiple \`canvas.addEventListener('pointerdown', ...)\` handlers \u2014 use ONE handler with state-based logic. Use \`on()\` for UI button clicks.
130
164
 
131
165
  ## Audio Presets (Full List)
132
166
 
@@ -143,9 +177,9 @@ import Star from 'star-sdk';
143
177
  Star.init({ gameId: '<gameId from .starrc>' }); // run: npx star-sdk init
144
178
 
145
179
  Star.game(ctx => {
146
- const { canvas, width, height, ctx: c } = ctx;
180
+ const { canvas, width, height, ctx: c, ui, on } = ctx;
147
181
  let score = 0;
148
- let gameOver = false;
182
+ let state = 'playing';
149
183
  let playerY = height / 2;
150
184
  let obstacles = [];
151
185
 
@@ -157,13 +191,52 @@ Star.game(ctx => {
157
191
 
158
192
  // Spawn obstacles
159
193
  setInterval(() => {
160
- if (!gameOver) {
194
+ if (state === 'playing') {
161
195
  obstacles.push({ x: width, y: Math.random() * height, passed: false });
162
196
  }
163
197
  }, 2000);
164
198
 
199
+ // Gameplay input \u2014 ONE handler, state-based
200
+ canvas.addEventListener('pointerdown', () => {
201
+ if (state === 'playing') {
202
+ playerY -= 50;
203
+ Star.audio.play('jump');
204
+ }
205
+ });
206
+
207
+ // UI button clicks \u2014 use on() for DOM buttons, not canvas hit-testing
208
+ on('click', '#lb-btn', () => Star.leaderboard.show());
209
+ on('click', '#restart-btn', () => startGame());
210
+
211
+ function startGame() {
212
+ state = 'playing';
213
+ score = 0;
214
+ playerY = height / 2;
215
+ obstacles = [];
216
+ ui.render(''); // Clear game-over UI
217
+ }
218
+
219
+ function endGame() {
220
+ state = 'gameover';
221
+ Star.audio.play('hurt');
222
+ Star.leaderboard.submit(score);
223
+ // Game-over UI with DOM buttons (not canvas-drawn)
224
+ ui.render(\`
225
+ <div class="h-full flex flex-col items-center justify-center text-white">
226
+ <div class="text-3xl font-bold mb-2">GAME OVER</div>
227
+ <div class="text-6xl font-bold mb-6">\${score}</div>
228
+ <button id="lb-btn" class="px-6 py-3 mb-4 bg-purple-600 rounded-lg font-bold">
229
+ VIEW LEADERBOARD
230
+ </button>
231
+ <button id="restart-btn" class="px-6 py-3 bg-gray-700 rounded-lg">
232
+ PLAY AGAIN
233
+ </button>
234
+ </div>
235
+ \`);
236
+ }
237
+
165
238
  ctx.loop((dt) => {
166
- if (gameOver) return;
239
+ if (state !== 'playing') return;
167
240
 
168
241
  // Clear
169
242
  c.fillStyle = '#111827';
@@ -172,24 +245,15 @@ Star.game(ctx => {
172
245
  // Update obstacles
173
246
  obstacles.forEach(obs => {
174
247
  obs.x -= 200 * dt;
175
-
176
- // Score when passed
177
248
  if (!obs.passed && obs.x < 50) {
178
249
  obs.passed = true;
179
250
  score += 10;
180
251
  Star.audio.play('coin');
181
252
  }
182
-
183
- // Collision
184
253
  if (Math.abs(obs.x - 50) < 20 && Math.abs(obs.y - playerY) < 30) {
185
- gameOver = true;
186
- Star.audio.play('hurt');
187
- Star.leaderboard.submit(score);
188
- Star.leaderboard.show();
254
+ endGame();
189
255
  }
190
256
  });
191
-
192
- // Remove off-screen
193
257
  obstacles = obstacles.filter(o => o.x > -20);
194
258
 
195
259
  // Draw player
@@ -209,14 +273,6 @@ Star.game(ctx => {
209
273
  c.font = '24px sans-serif';
210
274
  c.fillText(\`Score: \${score}\`, 20, 40);
211
275
  });
212
-
213
- // Jump on click/tap
214
- canvas.onclick = () => {
215
- if (!gameOver) {
216
- playerY -= 50;
217
- Star.audio.play('jump');
218
- }
219
- };
220
276
  });
221
277
  \`\`\`
222
278
 
@@ -436,11 +492,11 @@ game(({ ctx, width, height, on, loop, ui, canvas }) => {
436
492
  console.log('Button clicked!');
437
493
  });
438
494
 
439
- // 4. For canvas games: listen for taps on canvas
440
- // Taps pass through the UI overlay to the canvas layer
441
- // Elements targeted by on() are automatically interactive
495
+ // 4. Gameplay taps \u2014 use canvas.addEventListener for game mechanics only
496
+ // For buttons/menus, use ui.render() + on() above
497
+ // The SDK auto-suppresses this handler when a UI button is clicked
442
498
  canvas.addEventListener('pointerdown', (e) => {
443
- console.log('Canvas/screen tapped!', e);
499
+ console.log('Gameplay tap!', e);
444
500
  });
445
501
  });
446
502
  \`\`\`
@@ -468,7 +524,9 @@ The 2D drawing context. Its transform is already scaled for DPR. You **always dr
468
524
 
469
525
  The \`<canvas>\` element itself.
470
526
 
471
- - **Use this for gameplay input listeners** (e.g., \`pointerdown\`, \`pointermove\`).
527
+ - **Use this for gameplay input** (e.g., \`pointerdown\` for tap-to-jump, \`pointermove\` for aiming).
528
+ - **For buttons and menus, use \`ui.render()\` + \`on()\` instead** \u2014 HTML buttons get hover states, touch targets, accessibility, and never conflict with gameplay handlers.
529
+ - Use ONE \`addEventListener\` handler with state-based logic. Multiple pointerdown handlers cause ordering bugs.
472
530
 
473
531
  ### \`width: number\` (getter)
474
532
 
@@ -710,6 +768,8 @@ game(({ ctx, width, height, loop, toStagePoint, canvas }) => {
710
768
 
711
769
  ### Recipe 5: Complex Game with Canvas + UI + Events (like FLOW)
712
770
 
771
+ **Key pattern:** Canvas for gameplay input, DOM buttons for UI. Never draw buttons on canvas.
772
+
713
773
  \`\`\`ts
714
774
  import { game } from 'star-canvas';
715
775
  import { createLeaderboard } from 'star-leaderboard';
@@ -720,23 +780,19 @@ game(({ ctx, width, height, loop, ui, on, canvas, toStagePoint }) => {
720
780
  let score = 0;
721
781
  let state = 'menu';
722
782
 
723
- function handleTap() {
724
- if (state === 'menu' || state === 'gameover') {
725
- startGame();
726
- } else if (state === 'playing') {
783
+ // 1. ONE gameplay tap handler \u2014 state-based logic, no button hit-testing
784
+ canvas.addEventListener('pointerdown', () => {
785
+ if (state === 'menu') startGame();
786
+ else if (state === 'playing') {
727
787
  // ... (player float logic) ...
728
788
  }
729
- }
730
-
731
- // 1. Listen for screen taps - this makes UI click-through automatically
732
- canvas.addEventListener('pointerdown', handleTap);
733
-
734
- // 2. Listen for button clicks \u2014 on() auto-enables pointer-events for the selector
735
- on('click', '#leaderboard-btn', (e) => {
736
- e.stopPropagation();
737
- leaderboard.show();
738
789
  });
739
790
 
791
+ // 2. DOM button clicks \u2014 on() auto-enables pointer-events
792
+ // The SDK suppresses the canvas handler when a button is clicked
793
+ on('click', '#leaderboard-btn', () => leaderboard.show());
794
+ on('click', '#restart-btn', () => startGame());
795
+
740
796
  // 3. Render UI \u2014 elements targeted by on() are automatically interactive
741
797
  let lastState = null;
742
798
  let lastScore = -1;
@@ -767,7 +823,9 @@ game(({ ctx, width, height, loop, ui, on, canvas, toStagePoint }) => {
767
823
  <button id="leaderboard-btn" class="px-6 py-3 mb-4 bg-gradient-to-r from-blue-600 to-purple-600 rounded-xl font-bold shadow-lg shadow-blue-500/20">
768
824
  VIEW LEADERBOARD
769
825
  </button>
770
- <div class="text-xl animate-pulse">TAP TO RESTART</div>
826
+ <button id="restart-btn" class="px-6 py-3 bg-gray-700 rounded-xl font-bold">
827
+ PLAY AGAIN
828
+ </button>
771
829
  </div>\`);
772
830
  }
773
831
  }
@@ -779,6 +837,7 @@ game(({ ctx, width, height, loop, ui, on, canvas, toStagePoint }) => {
779
837
  function startGame() {
780
838
  state = 'playing';
781
839
  score = 0;
840
+ ui.render(''); // Clear game-over buttons
782
841
  updateUI();
783
842
  }
784
843
 
@@ -1377,10 +1436,6 @@ function injectImportMapIfNeeded(html, deployDir) {
1377
1436
  if (/<script\s[^>]*type\s*=\s*["']importmap["'][^>]*>/i.test(html)) {
1378
1437
  return html;
1379
1438
  }
1380
- const bareImportPattern = /\bfrom\s+['"](?:star-sdk|star-canvas|star-audio|star-leaderboard|star-multiplayer)['"]/;
1381
- if (!bareImportPattern.test(html)) {
1382
- return html;
1383
- }
1384
1439
  const version = resolveStarSdkVersion(deployDir);
1385
1440
  const suffix = version ? `@${version}` : "";
1386
1441
  const imports = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "star-sdk-cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "CLI for Star SDK — register games, install AI docs, and deploy to Star hosting",
5
5
  "type": "module",
6
6
  "bin": {