Building Games in Playground Lab: Wordle, Chess, Tools, Leaderboards, and the Real-Time Roadmap
Published 02/22/2026 · 9 min read

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.