create-smore-game 2.3.0 → 2.4.0

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/package.json +1 -1
  2. package/templates.js +87 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-smore-game",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "create-smore-game": "./index.js"
package/templates.js CHANGED
@@ -1262,6 +1262,16 @@ async function main() {
1262
1262
  app.get('/', servePage(join(__dirname, 'index.html')));
1263
1263
  app.get('/controller', servePage(join(__dirname, 'controller.html')));
1264
1264
 
1265
+ // Serve game.json for dev harness (minPlayers, maxPlayers, title, etc.)
1266
+ app.get('/game.json', (_req, res) => {
1267
+ try {
1268
+ const gameJson = JSON.parse(readFileSync(join(__dirname, '..', 'game.json'), 'utf-8'));
1269
+ res.json(gameJson);
1270
+ } catch (e) {
1271
+ res.json({ id: 'dev-game', title: 'Dev Game', minPlayers: 1, maxPlayers: 8 });
1272
+ }
1273
+ });
1274
+
1265
1275
  // ── Socket handling ──
1266
1276
 
1267
1277
  io.on('connection', (socket) => {
@@ -1535,6 +1545,10 @@ export function devHarness(gameId) {
1535
1545
  .btn-primary:disabled:hover { background: #4f46e5; }
1536
1546
  .btn-danger { background: #dc2626; border-color: #dc2626; color: #fff; }
1537
1547
  .btn-danger:hover { background: #b91c1c; }
1548
+ .btn-start { background: #16a34a; border-color: #16a34a; color: #fff; font-size: 13px; padding: 5px 16px; }
1549
+ .btn-start:hover { background: #15803d; }
1550
+ .btn-start:disabled { opacity: 0.35; cursor: not-allowed; }
1551
+ .btn-start:disabled:hover { background: #16a34a; }
1538
1552
 
1539
1553
  /* Main layout */
1540
1554
  .main { display: flex; height: calc(100% - 48px); }
@@ -1610,6 +1624,7 @@ export function devHarness(gameId) {
1610
1624
  <div class="badge-muted" id="playerCount">0 players</div>
1611
1625
  <div class="phase-badge phase-lobby" id="phaseBadge">Lobby</div>
1612
1626
  <div class="topbar-spacer"></div>
1627
+ <button class="btn btn-start" id="startGameBtn" disabled>▶ Start</button>
1613
1628
  <button class="btn btn-danger" id="endGameBtn" style="display:none;">End Game</button>
1614
1629
  <button class="btn" id="resetBtn">Reset</button>
1615
1630
  <button class="btn btn-primary" id="addPlayerBtn">+ Player</button>
@@ -1648,6 +1663,10 @@ export function devHarness(gameId) {
1648
1663
  var screenIframe = null;
1649
1664
  var screenReady = false;
1650
1665
  var gamePhase = 'lobby';
1666
+ var gameStarted = false;
1667
+ var pendingGameReady = []; // { socket, data }
1668
+ var minPlayers = 1;
1669
+ var gameConfig = {};
1651
1670
 
1652
1671
  // ── DOM refs ──
1653
1672
  var roomCodeEl = document.getElementById('roomCode');
@@ -1659,6 +1678,7 @@ export function devHarness(gameId) {
1659
1678
  var controllerGrid = document.getElementById('controllerGrid');
1660
1679
  var emptyState = document.getElementById('emptyState');
1661
1680
  var addPlayerBtn = document.getElementById('addPlayerBtn');
1681
+ var startGameBtn = document.getElementById('startGameBtn');
1662
1682
  var endGameBtn = document.getElementById('endGameBtn');
1663
1683
  var resetBtn = document.getElementById('resetBtn');
1664
1684
 
@@ -1670,12 +1690,19 @@ export function devHarness(gameId) {
1670
1690
  phaseBadgeEl.className = 'phase-badge phase-' + phase;
1671
1691
  addPlayerBtn.disabled = (phase === 'playing');
1672
1692
  endGameBtn.style.display = (phase === 'playing') ? 'inline-block' : 'none';
1693
+ startGameBtn.style.display = (phase === 'lobby') ? 'inline-block' : 'none';
1694
+ updateStartButton();
1695
+ }
1696
+
1697
+ function updateStartButton() {
1698
+ startGameBtn.disabled = gameStarted || serverPlayers.length < minPlayers;
1673
1699
  }
1674
1700
 
1675
1701
  // ── Helpers ──
1676
1702
  function updatePlayerCount() {
1677
1703
  var count = serverPlayers.length;
1678
1704
  playerCountEl.textContent = count + ' player' + (count !== 1 ? 's' : '');
1705
+ updateStartButton();
1679
1706
  }
1680
1707
 
1681
1708
  function updateEmptyState() {
@@ -1897,9 +1924,13 @@ export function devHarness(gameId) {
1897
1924
  return;
1898
1925
  }
1899
1926
 
1900
- // Relay game-ready to server for ready/start sync
1927
+ // Buffer game-ready until game is started (prevents auto-start)
1901
1928
  if (event === 'smore:game-ready') {
1902
- screenSocket.emit('smore:game-ready', data || {});
1929
+ if (gameStarted) {
1930
+ screenSocket.emit('smore:game-ready', data || {});
1931
+ } else {
1932
+ pendingGameReady.push({ socket: screenSocket, data: data });
1933
+ }
1903
1934
  return;
1904
1935
  }
1905
1936
 
@@ -1945,9 +1976,13 @@ export function devHarness(gameId) {
1945
1976
  setPhase('lobby');
1946
1977
  }
1947
1978
 
1948
- // Relay game-ready to server for ready/start sync
1979
+ // Buffer game-ready until game is started (prevents auto-start)
1949
1980
  if (event === 'smore:game-ready') {
1950
- player.socket.emit('smore:game-ready', data || {});
1981
+ if (gameStarted) {
1982
+ player.socket.emit('smore:game-ready', data || {});
1983
+ } else {
1984
+ pendingGameReady.push({ socket: player.socket, data: data });
1985
+ }
1951
1986
  return;
1952
1987
  }
1953
1988
 
@@ -1966,6 +2001,9 @@ export function devHarness(gameId) {
1966
2001
 
1967
2002
  // ── End Game ──
1968
2003
  function endGame() {
2004
+ gameStarted = false;
2005
+ pendingGameReady = [];
2006
+
1969
2007
  // Broadcast phase-change to ALL controllers (including phone) via server relay
1970
2008
  screenSocket.emit('phase-change', { phase: 'lobby' });
1971
2009
 
@@ -1986,6 +2024,9 @@ export function devHarness(gameId) {
1986
2024
 
1987
2025
  // ── Reset ──
1988
2026
  function resetAll() {
2027
+ gameStarted = false;
2028
+ pendingGameReady = [];
2029
+
1989
2030
  // 1. Disconnect all local iframe player sockets
1990
2031
  players.forEach(function(p) {
1991
2032
  if (p.socket) p.socket.disconnect();
@@ -2034,6 +2075,24 @@ export function devHarness(gameId) {
2034
2075
  if (!addPlayerBtn.disabled) addController();
2035
2076
  });
2036
2077
 
2078
+ startGameBtn.addEventListener('click', function() {
2079
+ if (gameStarted || serverPlayers.length < minPlayers) return;
2080
+ gameStarted = true;
2081
+
2082
+ // Tell server to start the game (triggers smore:game-selected + smore:game-started)
2083
+ screenSocket.emit('smore:select-game', { gameId: gameConfig.id || 'dev-game' });
2084
+
2085
+ // Flush all buffered game-ready messages after server processes select-game
2086
+ setTimeout(function() {
2087
+ pendingGameReady.forEach(function(entry) {
2088
+ entry.socket.emit('smore:game-ready', entry.data || {});
2089
+ });
2090
+ pendingGameReady = [];
2091
+ }, 50);
2092
+
2093
+ setPhase('playing');
2094
+ });
2095
+
2037
2096
  endGameBtn.addEventListener('click', endGame);
2038
2097
  resetBtn.addEventListener('click', resetAll);
2039
2098
 
@@ -2053,6 +2112,13 @@ export function devHarness(gameId) {
2053
2112
  });
2054
2113
 
2055
2114
  // ── Boot ──
2115
+ // Fetch game.json for minPlayers config
2116
+ fetch('/game.json').then(function(r) { return r.json(); }).then(function(cfg) {
2117
+ gameConfig = cfg;
2118
+ minPlayers = cfg.minPlayers || 1;
2119
+ updateStartButton();
2120
+ }).catch(function() {});
2121
+
2056
2122
  initScreen();
2057
2123
  setTimeout(function() { addController(); }, 500);
2058
2124
  })();
@@ -2127,6 +2193,8 @@ export function devControllerPage(gameId) {
2127
2193
  var roomCode = '';
2128
2194
  var players = [];
2129
2195
  var iframeReady = false;
2196
+ var gameStarted = false;
2197
+ var pendingGameReady = null;
2130
2198
 
2131
2199
  // Connect and join room
2132
2200
  socket = io();
@@ -2159,6 +2227,15 @@ export function devControllerPage(gameId) {
2159
2227
  }
2160
2228
  });
2161
2229
 
2230
+ // Listen for game-started to flush pending game-ready
2231
+ socket.on('smore:game-started', function() {
2232
+ gameStarted = true;
2233
+ if (pendingGameReady !== null) {
2234
+ socket.emit('smore:game-ready', pendingGameReady);
2235
+ pendingGameReady = null;
2236
+ }
2237
+ });
2238
+
2162
2239
  // Forward system events to iframe
2163
2240
  var sysEvents = [
2164
2241
  'smore:game-over',
@@ -2220,9 +2297,13 @@ export function devControllerPage(gameId) {
2220
2297
  var data = msg.payload.data;
2221
2298
  var ackId = msg.payload.ackId;
2222
2299
 
2223
- // Relay game-ready to server for ready/start sync
2300
+ // Buffer game-ready until game is started (prevents auto-start)
2224
2301
  if (event === 'smore:game-ready') {
2225
- socket.emit('smore:game-ready', data || {});
2302
+ if (gameStarted) {
2303
+ socket.emit('smore:game-ready', data || {});
2304
+ } else {
2305
+ pendingGameReady = data || {};
2306
+ }
2226
2307
  return;
2227
2308
  }
2228
2309