XMoravec
Blog

Building Games in Playground Lab: Wordle, Chess, Tools, Leaderboards, and the Real-Time Roadmap

Published 02/22/2026 · 9 min read

GamesChessWordleLeaderboardsWebSocketsRoadmap
Playground Lab Wordle gameplay screen with account-aware progress

Playground Lab looks like ‘a place with games’ from the outside. Internally it is a growing set of gameplay and utility modules with different identity constraints, persistence needs, and UX synchronization challenges.

1) Wordle: personalized by default, flexible for guests

  • Menu-first entry with active-game resume and history awareness.
  • Start/resume, guess evaluation, and persistence scoped by user identity.
  • One-time hint behavior with explicit score impact handling.
  • Admin-only answer reveal protected by role + admin-mode gates.
  • Limited-mode fallback metadata when primary dictionary sourcing fails.

2) Chess: larger state model and stricter flow control

  • python-chess as legal-rules authority for move validation and game-end decisions.
  • Mongo persistence for invitations, matches, history, and clocks.
  • Mode segmentation (`multiplayer`, `self-play`, `bot`) with account gating only where required.
  • Bot levels (easy/medium/hard) implemented through depth-based search policy.

python

1def order_moves(board: Any, legal_moves: list[Any]) -> list[Any]:
2    ordered: list[tuple[int, Any]] = []
3    for move in legal_moves:
4        priority = 0
5        if board.is_capture(move):
6            priority += 100
7            if board.is_en_passant(move):
8                priority += PIECE_VALUES[1]
9            else:
10                target_piece = board.piece_at(move.to_square)
11                if target_piece is not None:
12                    priority += PIECE_VALUES.get(target_piece.piece_type, 0)
13
14        board.push(move)
15        if board.is_checkmate():
16            priority += 10_000
17        elif board.is_check():
18            priority += 50
19        board.pop()
20
21        ordered.append((priority, move))
22
23    ordered.sort(key=lambda row: row[0], reverse=True)
24    return [row[1] for row in ordered]
25
26
27def best_capture_or_random_move(board: Any) -> Any:
28    legal_moves = list(board.legal_moves)
29    if not legal_moves:
30        raise ValueError("No legal bot moves available")
31
32    best_capture_score = -1
33    best_captures: list[Any] = []
34    for move in legal_moves:
35        capture_score = 0
36        if board.is_capture(move):
37            if board.is_en_passant(move):
38                capture_score = PIECE_VALUES[1]
39            else:
40                target_piece = board.piece_at(move.to_square)
41                if target_piece is not None:
42                    capture_score = PIECE_VALUES.get(target_piece.piece_type, 0)
43
44        if capture_score > best_capture_score:
45            best_capture_score = capture_score
46            best_captures = [move]
47        elif capture_score == best_capture_score:
48            best_captures.append(move)
49
50    return random.choice(best_captures) if best_captures else random.choice(legal_moves)

3) Server-authoritative clocks and fairness

Clock authority lives on the server (`clock_started_at` plus remaining seconds), not in browser-local timers. This keeps match timing fair across refreshes, reconnects, and temporary client lag.

4) Tools and leaderboards as first-class modules

  • Wordle Solver is implemented as a dedicated backend module, not a one-off helper endpoint.
  • Solver supports contradiction validation for clue rows and ranked candidate output.
  • Wordle leaderboard logic is deterministic and transparent, including edge cases (losses and hint usage).
  • A dedicated /tools surface keeps utility modules discoverable and scalable.

5) Why real-time transport is the next step

Current multiplayer behavior works through request/response, but WebSockets are the next meaningful upgrade for move propagation latency and live invitation/game-event updates.

  • Phase 1: invitation and match-state invalidation events.
  • Phase 2: live multiplayer move broadcasts.
  • Phase 3: clock sync events with reconnect replay support.
  • Initial model keeps HTTP as source-of-truth write API while WebSocket channels handle event fanout.

Games and tools in one platform become an identity + persistence + fairness problem as much as a feature problem. That complexity is exactly why the platform was designed for module growth from the start.