gabraken commited on
Commit
fdb51e7
·
1 Parent(s): 1fd47ae
.gitignore CHANGED
@@ -8,3 +8,4 @@ __pycache__/
8
  frontend/.svelte-kit/generated/
9
  frontend/.svelte-kit/output/
10
  real/backend/data/stats.db
 
 
8
  frontend/.svelte-kit/generated/
9
  frontend/.svelte-kit/output/
10
  real/backend/data/stats.db
11
+ real/
backend/data/stats.db ADDED
Binary file (20.5 kB). View file
 
backend/lobby/manager.py CHANGED
@@ -217,13 +217,26 @@ class LobbyManager:
217
  now = time.time()
218
  result = []
219
  for room in self._rooms.values():
220
- if room.status == "playing" and len(room.players) == 2:
221
- elapsed = int(now - room.started_at) if room.started_at else 0
 
 
 
 
 
 
 
 
 
 
 
 
222
  result.append({
223
  "room_id": room.room_id,
224
- "player1": room.players[0].name,
225
- "player2": room.players[1].name,
226
  "elapsed": elapsed,
 
227
  })
228
  return result
229
 
 
217
  now = time.time()
218
  result = []
219
  for room in self._rooms.values():
220
+ if room.status != "playing":
221
+ continue
222
+ elapsed = int(now - room.started_at) if room.started_at else 0
223
+ real_players = [p for p in room.players if p.sid != BOT_PLAYER_ID]
224
+ if len(real_players) == 2:
225
+ result.append({
226
+ "room_id": room.room_id,
227
+ "player1": real_players[0].name,
228
+ "player2": real_players[1].name,
229
+ "elapsed": elapsed,
230
+ "is_tutorial": False,
231
+ })
232
+ elif len(real_players) == 1 and len(room.players) == 1:
233
+ # Tutorial room: only one real player, no bot in room
234
  result.append({
235
  "room_id": room.room_id,
236
+ "player1": real_players[0].name,
237
+ "player2": None,
238
  "elapsed": elapsed,
239
+ "is_tutorial": True,
240
  })
241
  return result
242
 
backend/main.py CHANGED
@@ -856,12 +856,13 @@ async def observe(sid: str, data: dict) -> None:
856
  async def create_room(sid: str, data: dict) -> None:
857
  name = str(data.get("name", "Player")).strip() or "Player"
858
  name = await sanitize_player_name(name)
 
 
859
  room = lobby.create_room(sid, name)
860
  await sio.enter_room(sid, room.room_id)
861
  await sio.emit("room_created", {"room_id": room.room_id, "room": room.to_dict()}, to=sid)
862
  log.info("Room %s created by %s", room.room_id, name)
863
  # Start bot-offer countdown
864
- _cancel_bot_timer(sid)
865
  _bot_timers[sid] = asyncio.create_task(_schedule_bot_offer(sid, room.room_id))
866
 
867
 
@@ -891,6 +892,8 @@ async def join_room(sid: str, data: dict) -> None:
891
  async def quick_match(sid: str, data: dict) -> None:
892
  name = str(data.get("name", "Player")).strip() or "Player"
893
  name = await sanitize_player_name(name)
 
 
894
  room, is_new = lobby.quick_match(sid, name)
895
 
896
  if not is_new:
@@ -977,6 +980,18 @@ async def cancel_construction(sid: str, data: dict) -> None:
977
  await _emit_error(sid, f"Impossible d'annuler: {result.data.get('error', '?')}")
978
 
979
 
 
 
 
 
 
 
 
 
 
 
 
 
980
  @sio.event
981
  async def start_tutorial(sid: str, data: dict) -> None:
982
  """Start a solo tutorial game for the player."""
@@ -988,14 +1003,7 @@ async def start_tutorial(sid: str, data: dict) -> None:
988
 
989
  # Leave & stop any previous game/tutorial so stale game_update events
990
  # don't bleed into the new tutorial session.
991
- old_room = lobby.get_room_for_sid(sid)
992
- if old_room:
993
- old_room_id = old_room.room_id
994
- await sio.leave_room(sid, old_room_id)
995
- if old_room_id in engines:
996
- await engines[old_room_id].stop()
997
- del engines[old_room_id]
998
- log.info("Partie précédente stoppée (nouveau tutoriel) room %s | parties actives: %d/%d", old_room_id, len(engines), MAX_SIMULTANEOUS_GAMES)
999
 
1000
  if len(engines) >= MAX_SIMULTANEOUS_GAMES:
1001
  log.warning("Limite de %d parties simultanées atteinte, impossible de démarrer le tutoriel pour %s", MAX_SIMULTANEOUS_GAMES, sid)
 
856
  async def create_room(sid: str, data: dict) -> None:
857
  name = str(data.get("name", "Player")).strip() or "Player"
858
  name = await sanitize_player_name(name)
859
+ _cancel_bot_timer(sid)
860
+ await _cleanup_old_game(sid)
861
  room = lobby.create_room(sid, name)
862
  await sio.enter_room(sid, room.room_id)
863
  await sio.emit("room_created", {"room_id": room.room_id, "room": room.to_dict()}, to=sid)
864
  log.info("Room %s created by %s", room.room_id, name)
865
  # Start bot-offer countdown
 
866
  _bot_timers[sid] = asyncio.create_task(_schedule_bot_offer(sid, room.room_id))
867
 
868
 
 
892
  async def quick_match(sid: str, data: dict) -> None:
893
  name = str(data.get("name", "Player")).strip() or "Player"
894
  name = await sanitize_player_name(name)
895
+ _cancel_bot_timer(sid)
896
+ await _cleanup_old_game(sid)
897
  room, is_new = lobby.quick_match(sid, name)
898
 
899
  if not is_new:
 
980
  await _emit_error(sid, f"Impossible d'annuler: {result.data.get('error', '?')}")
981
 
982
 
983
+ async def _cleanup_old_game(sid: str) -> None:
984
+ """Leave socket room and stop engine for any game the player is currently in."""
985
+ old_room = lobby.get_room_for_sid(sid)
986
+ if old_room:
987
+ old_room_id = old_room.room_id
988
+ await sio.leave_room(sid, old_room_id)
989
+ if old_room_id in engines:
990
+ await engines[old_room_id].stop()
991
+ del engines[old_room_id]
992
+ log.info("Partie précédente stoppée room %s | parties actives: %d/%d", old_room_id, len(engines), MAX_SIMULTANEOUS_GAMES)
993
+
994
+
995
  @sio.event
996
  async def start_tutorial(sid: str, data: dict) -> None:
997
  """Start a solo tutorial game for the player."""
 
1003
 
1004
  # Leave & stop any previous game/tutorial so stale game_update events
1005
  # don't bleed into the new tutorial session.
1006
+ await _cleanup_old_game(sid)
 
 
 
 
 
 
 
1007
 
1008
  if len(engines) >= MAX_SIMULTANEOUS_GAMES:
1009
  log.warning("Limite de %d parties simultanées atteinte, impossible de démarrer le tutoriel pour %s", MAX_SIMULTANEOUS_GAMES, sid)
frontend/src/lib/components/Map.svelte CHANGED
@@ -872,7 +872,7 @@
872
  per-cell rects, works in all environments (no SVG mask url(#id) needed).
873
  Computed reactively via $: fogRects so viewport panning is always in sync. -->
874
  {#each fogRects as r}
875
- <rect x={r.x} y={r.y} width={r.w} height={1} fill="rgba(0,0,0,{r.opacity})" pointer-events="none" shape-rendering="crispEdges" />
876
  {/each}
877
 
878
  <!-- Tutorial target beacon (rendered above fog so it's always visible) -->
 
872
  per-cell rects, works in all environments (no SVG mask url(#id) needed).
873
  Computed reactively via $: fogRects so viewport panning is always in sync. -->
874
  {#each fogRects as r}
875
+ <rect x={r.x} y={r.y} width={r.w + 0.02} height={1.02} fill="rgba(0,0,0,{r.opacity})" pointer-events="none" shape-rendering="crispEdges" />
876
  {/each}
877
 
878
  <!-- Tutorial target beacon (rendered above fog so it's always visible) -->
frontend/src/routes/+page.svelte CHANGED
@@ -166,6 +166,7 @@
166
  if (game_state) {
167
  gameState.set(game_state);
168
  myPlayerId.set(null);
 
169
  }
170
  goto("/game");
171
  },
@@ -404,8 +405,9 @@
404
  type PlayingRoom = {
405
  room_id: string;
406
  player1: string;
407
- player2: string;
408
  elapsed: number;
 
409
  };
410
  let observeModalOpen = false;
411
  let playingRooms: PlayingRoom[] = [];
@@ -702,9 +704,14 @@
702
  {#each playingRooms as room (room.room_id)}
703
  <li class="obs-item">
704
  <div class="obs-players">
705
- <span class="obs-player">{room.player1}</span>
706
- <span class="obs-vs">vs</span>
707
- <span class="obs-player">{room.player2}</span>
 
 
 
 
 
708
  </div>
709
  <div class="obs-meta">
710
  <span class="obs-timer">{formatElapsed(room.elapsed)}</span>
@@ -1489,6 +1496,18 @@
1489
  flex-shrink: 0;
1490
  }
1491
 
 
 
 
 
 
 
 
 
 
 
 
 
1492
  .obs-meta {
1493
  display: flex;
1494
  align-items: center;
 
166
  if (game_state) {
167
  gameState.set(game_state);
168
  myPlayerId.set(null);
169
+ isTutorial.set(!!game_state.is_tutorial);
170
  }
171
  goto("/game");
172
  },
 
405
  type PlayingRoom = {
406
  room_id: string;
407
  player1: string;
408
+ player2: string | null;
409
  elapsed: number;
410
+ is_tutorial: boolean;
411
  };
412
  let observeModalOpen = false;
413
  let playingRooms: PlayingRoom[] = [];
 
704
  {#each playingRooms as room (room.room_id)}
705
  <li class="obs-item">
706
  <div class="obs-players">
707
+ {#if room.is_tutorial}
708
+ <span class="obs-tutorial-badge">Tutorial</span>
709
+ <span class="obs-player">{room.player1}</span>
710
+ {:else}
711
+ <span class="obs-player">{room.player1}</span>
712
+ <span class="obs-vs">vs</span>
713
+ <span class="obs-player">{room.player2}</span>
714
+ {/if}
715
  </div>
716
  <div class="obs-meta">
717
  <span class="obs-timer">{formatElapsed(room.elapsed)}</span>
 
1496
  flex-shrink: 0;
1497
  }
1498
 
1499
+ .obs-tutorial-badge {
1500
+ font-size: 0.65rem;
1501
+ font-weight: 700;
1502
+ text-transform: uppercase;
1503
+ letter-spacing: 0.06em;
1504
+ color: #0f1420;
1505
+ background: var(--lobby-accent-cyan);
1506
+ border-radius: 4px;
1507
+ padding: 2px 6px;
1508
+ flex-shrink: 0;
1509
+ }
1510
+
1511
  .obs-meta {
1512
  display: flex;
1513
  align-items: center;