Le pitch
Nexus est un jeu de stratégie pour deux joueurs sur une grille 7x9. On pioche des tuiles dans un sac commun (81 tuiles, distribution Fibonacci), on les place sur le plateau en respectant les connexions de ports, et on essaie de relier son bord de départ au bord opposé. Trois modes : local, contre une IA, ou en ligne en peer-to-peer.
Jouer : presque.cool/nexus/
Pourquoi ce projet
Nexus est basé sur un concept et un design original de 360Genius, qui partage publiquement les étapes de conception de son jeu de plateau physique sur les réseaux sociaux. L’idée : essayer de prototyper une version web jouable à partir des informations disponibles (publications, images, déductions).
C’est un exercice de rétro-ingénierie et de game design. Les règles complètes ne sont pas publiques, donc une partie du travail consiste à déduire les mécaniques à partir de ce qui est visible, puis à les tester pour voir si elles tiennent. Certaines décisions de game design dans cette version web sont des interprétations personnelles là où l’information manquait.
360Genius est crédité dans le projet et au courant de l’existence du prototype.
Deux motivations techniques en parallèle :
- La rétro-ingénierie de game design : comprendre une mécanique de jeu à partir de fragments d’information, combler les trous, playtester.
- PeerJS : ça faisait longtemps que je voulais tester le P2P dans le navigateur. Un jeu tour par tour pour deux joueurs était le candidat idéal.
Le jeu : tuiles, ports et réseaux
Les règles
Chaque joueur démarre d’un bord opposé de la grille (sud pour le bleu, nord pour l’orange). À chaque tour, on pioche une tuile dans sa main de 5, on la place sur le plateau, et on essaie de construire un chemin continu vers le bord adverse.
Les tuiles ont des ports directionnels (nord, est, sud, ouest) qui doivent s’aligner avec les tuiles adjacentes. On peut les tourner avant de les poser. 5 formes de tuiles existent : ligne, courbe, T, croix, terminaison.
Deux types de tuiles : les noeud et les flux. Un noeud peut écraser un flux adverse, un flux ne peut se poser que sur une case vide. Cette asymétrie crée de la tension tactique : poser un noeud coûte plus cher (il y en a moins dans le sac) mais il protège le territoire.
La distribution Fibonacci
Le sac contient 81 tuiles réparties selon une distribution inspirée de Fibonacci : 23 noeuds, 58 flux. Une première version (v0) utilisait une distribution différente, mais le rééquilibrage vers Fibonacci a donné des parties plus tendues et moins prévisibles.
Les conditions de victoire
Trois cas, dans l’ordre :
- Connexion : relier son bord de départ au bord opposé.
- Blocage : si l’adversaire n’a plus de coup légal après un reroll, on gagne.
- Égalité : si les tuiles sont épuisées, le SPR (Smallest Path Radius) départage les joueurs.
Rétro-ingénierie : reconstruire un jeu à partir de fragments
L’exercice est différent de Grid4 où l’inspiration était un jeu digital jouable. Ici, le jeu de référence est un jeu de plateau en cours de conception, partagé par bribes sur les réseaux. Pas de règles complètes, pas de version jouable.
Ce que j’avais :
- Des images du plateau et des tuiles
- Des descriptions partielles de mécaniques
- Le thème général (réseaux, connexion bord à bord)
Ce que j’ai dû déduire ou inventer :
- Le système de ports et les règles de connexion exactes
- L’équilibrage du sac de tuiles
- Les conditions de victoire secondaires (blocage, SPR)
- Les règles d’ouverture (les 2 premiers tours contraints au bord de départ)
Le playtest a révélé des problèmes que la théorie ne montrait pas. La v0 du sac produisait des parties où un joueur pouvait dominer trop vite. Le passage à la distribution Fibonacci a corrigé ça : plus de flux que de noeuds force à construire des chemins fragiles, donc attaquables.
P2P avec PeerJS : le multi sans serveur
Ça faisait longtemps que je voulais tester le peer-to-peer dans le navigateur. Un jeu tour par tour à deux joueurs était le cas d’usage parfait : peu de données échangées, pas de synchronisation temps réel, et la latence est tolérable.
L’architecture host/guest
Le host crée une room (code à 6 caractères), le guest rejoint via le code ou un lien URL (?code=XXXXXX). Le host est l’autorité du jeu : il valide les coups, met à jour l’état, et le broadcast au guest. Le guest envoie des requêtes d’action (sélection, rotation, placement) que le host accepte ou refuse.
Ce modèle simplifie beaucoup les choses : pas de conflit d’état possible, pas de consensus à gérer. Si le host dit non, c’est non.
Les messages typés
Un système de discriminated unions TypeScript pour les messages P2P. Chaque message a un type (move, sync, ready, disconnect…) et un payload typé. Ça rend les handlers exhaustifs et le compilateur attrape les oublis.
La reconnexion
Le guest tente automatiquement de se reconnecter 3 fois avec backoff exponentiel. Si la connexion est perdue, un overlay apparaît côté guest. Côté host, le jeu reste intact et attend.
PeerJS abstrait la complexité de WebRTC (signaling, STUN/TURN), ce qui permet de se concentrer sur la logique de jeu plutôt que sur la plomberie réseau. Le compromis : on dépend du serveur PeerJS pour le signaling initial. Pour un prototype, c’est acceptable.
L’IA : trois niveaux, une heuristique
L’IA énumère tous les coups légaux, les évalue, et en choisit un selon le niveau de difficulté.
L’évaluation combine plusieurs critères :
- Progression : est-ce que le coup rapproche de la victoire (SPR) ?
- Connectivité : est-ce que le coup étend le réseau ?
- Blocage : est-ce que le coup gêne l’adversaire ?
Les trois niveaux jouent sur le bruit ajouté au score :
- Facile : bruit élevé (30), seuils bas. L’IA fait souvent des coups médiocres.
- Normal : bruit modéré (10). L’IA joue correctement mais rate les meilleures opportunités.
- Difficile : bruit minimal (2). L’IA prend quasi-systématiquement le meilleur coup disponible.
C’est un système simple, mais suffisant pour un jeu de cette complexité. L’IA en mode difficile est un adversaire crédible.
Stack technique
- React 19 + TypeScript (strict, zero any)
- Vite 8 comme bundler (avec SWC)
- Tailwind CSS v4 en mode CSS-first (OKLCH)
- PeerJS pour le multijoueur en ligne (P2P via WebRTC)
- Zustand pour le state : game state (localStorage), settings (localStorage v6 avec migrations), stats (IndexedDB)
- Web Audio API pour les SFX
- PWA via vite-plugin-pwa (Workbox, installable, offline)
- i18n maison (francais/anglais)
- CSP avec hashes auto-generes
- Compression Brotli + Gzip
Architecture en couches : logique de jeu pure dans utils/gameLogic.ts, IA isolée dans ai/, services réseau dans services/peerService.ts, hooks d’orchestration, composants UI. Le lazy-loading est appliqué aux modules PeerJS et IA pour réduire le bundle initial.
Évolutions
| Date | Version | Description |
|---|---|---|
| Mars 2026 | 1.0 | Version complète : IA, multi local, multi online (P2P) |
Bilan
Durée : environ 3 semaines.
Ce que j’ai appris :
- La rétro-ingénierie de game design à partir de sources fragmentaires force à prendre des décisions de design explicites là où un jeu complet les prend pour vous. Chaque règle non-documentée est un choix à faire et à playtester.
- PeerJS rend le P2P dans le navigateur accessible : la couche WebRTC est abstraite, et pour du tour par tour, la latence n’est pas un problème. Le modèle host/guest avec autorité unilatérale simplifie tout.
- Les discriminated unions TypeScript pour les messages réseau sont un filet de sécurité : le compilateur vérifie que chaque type de message est géré.
Ce que je referais pareil :
- Le modèle host-autorité pour le multi. Pas de consensus, pas de conflit, c’est le bon compromis pour un jeu à deux.
- Créditer et contacter le créateur original en amont. C’est son design, même si l’implémentation web est la mienne.
Ce que je changerais :
- Tester le jeu avec plus de gens pour valider l’équilibrage. Deux semaines de dev solo, c’est peu de recul sur le game design.
The pitch
Nexus is a two-player strategy game on a 7x9 grid. Players draw tiles from a shared bag (81 tiles, Fibonacci distribution), place them on the board respecting port connections, and try to link their starting edge to the opposite one. Three modes: local, against an AI, or online via peer-to-peer.
Play: presque.cool/nexus/
Why this project
Nexus is based on an original concept and design by 360Genius, who publicly shares the design process of their physical board game on social media. The idea: try to prototype a playable web version from the limited publicly available information (posts, images, deductions).
It’s an exercise in reverse engineering and game design. The full rules aren’t public, so part of the work is deducing mechanics from what’s visible, then testing them to see if they hold up. Some game design decisions in this web version are personal interpretations where information was missing.
360Genius is credited in the project and aware of this prototype.
Two technical motivations ran in parallel:
- Game design reverse engineering: understanding a game mechanic from fragments, filling in the gaps, playtesting.
- PeerJS: I’d been wanting to try browser-based P2P for a while. A turn-based two-player game was the ideal candidate.
The game: tiles, ports and networks
The rules
Each player starts from an opposite edge of the grid (south for blue, north for orange). Each turn, you draw a tile from your hand of 5, place it on the board, and try to build a continuous path to the opposite edge.
Tiles have directional ports (north, east, south, west) that must align with adjacent tiles. You can rotate them before placing. 5 tile shapes exist: line, curve, T-junction, cross, terminal.
Two tile types: nodes and flux. A node can overwrite an opponent’s flux tile; a flux can only be placed on an empty cell. This asymmetry creates tactical tension: placing a node costs more (fewer in the bag) but it protects territory.
Fibonacci distribution
The bag contains 81 tiles distributed following a Fibonacci-inspired ratio: 23 nodes, 58 flux. An earlier version (v0) used a different distribution, but rebalancing toward Fibonacci produced tighter, less predictable games.
Victory conditions
Three cases, in order:
- Connection: link your starting edge to the opposite edge.
- Blockage: if the opponent has no legal move after a reroll, you win.
- Tie-break: if all tiles are exhausted, SPR (Smallest Path Radius) decides.
Reverse engineering: rebuilding a game from fragments
The exercise differs from Grid4, where the inspiration was a playable digital game. Here, the reference is a physical board game being designed, shared in bits on social media. No complete rules, no playable version.
What I had:
- Images of the board and tiles
- Partial descriptions of mechanics
- The general theme (networks, edge-to-edge connection)
What I had to deduce or invent:
- The port system and exact connection rules
- Tile bag balancing
- Secondary victory conditions (blockage, SPR)
- Opening rules (first 2 turns constrained to starting edge)
Playtesting revealed problems theory didn’t show. The v0 bag produced games where one player could dominate too quickly. Switching to Fibonacci distribution fixed that: more flux than nodes forces building fragile paths, which makes them attackable.
P2P with PeerJS: multiplayer without a server
I’d been wanting to try peer-to-peer in the browser for a while. A turn-based two-player game was the perfect use case: little data exchanged, no real-time synchronization, and latency is tolerable.
Host/guest architecture
The host creates a room (6-character code), the guest joins via the code or a URL link (?code=XXXXXX). The host is the game authority: it validates moves, updates state, and broadcasts to the guest. The guest sends action requests (select, rotate, place) that the host accepts or rejects.
This model simplifies things considerably: no possible state conflicts, no consensus to manage. If the host says no, it’s no.
Typed messages
A discriminated union TypeScript system for P2P messages. Each message has a type (move, sync, ready, disconnect…) and a typed payload. This makes handlers exhaustive and the compiler catches oversights.
Reconnection
The guest automatically retries connection 3 times with exponential backoff. If the connection drops, an overlay appears on the guest side. On the host side, the game stays intact and waits.
PeerJS abstracts away WebRTC complexity (signaling, STUN/TURN), letting you focus on game logic rather than network plumbing. The trade-off: depending on the PeerJS server for initial signaling. For a prototype, that’s acceptable.
The AI: three levels, one heuristic
The AI enumerates all legal moves, evaluates them, and picks one based on difficulty level.
Evaluation combines several criteria:
- Progression: does the move get closer to victory (SPR)?
- Connectivity: does the move extend the network?
- Blocking: does the move disrupt the opponent?
The three levels adjust the noise added to scores:
- Easy: high noise (30), low thresholds. The AI often plays mediocre moves.
- Normal: moderate noise (10). The AI plays decently but misses the best opportunities.
- Hard: minimal noise (2). The AI almost always picks the best available move.
Simple system, but it holds up. On hard, the AI is hard to beat.
Tech stack
- React 19 + TypeScript (strict, zero any)
- Vite 8 as bundler (with SWC)
- Tailwind CSS v4 in CSS-first mode (OKLCH)
- PeerJS for online multiplayer (P2P via WebRTC)
- Zustand for state: game state (localStorage), settings (localStorage v6 with migrations), stats (IndexedDB)
- Web Audio API for SFX
- PWA via vite-plugin-pwa (Workbox, installable, offline)
- i18n custom (French/English)
- CSP with auto-generated hashes
- Compression Brotli + Gzip
Layered architecture: pure game logic in utils/gameLogic.ts, AI isolated in ai/, network services in services/peerService.ts, orchestration hooks, UI components. Lazy-loading applied to PeerJS and AI modules to reduce the initial bundle.
Timeline
| Date | Version | Description |
|---|---|---|
| Mar 2026 | 1.0 | Full release: AI, local multiplayer, online P2P |
Looking back
Duration: about 3 weeks.
What I learned:
- Reverse engineering game design from fragmented sources forces explicit design decisions where a complete game makes them for you. Every undocumented rule is a choice to make and playtest.
- PeerJS makes browser P2P accessible: the WebRTC layer is abstracted, and for turn-based play, latency isn’t an issue. The host/guest model with unilateral authority keeps state trivially consistent.
- TypeScript discriminated unions for network messages are a safety net: the compiler verifies that every message type is handled.
What I’d do the same:
- The host-authority model for multiplayer. No consensus, no conflicts, right call for a two-player game.
- Crediting and contacting the original creator upfront. It’s their design, even if the web implementation is mine.
What I’d change:
- Testing the game with more people to validate balancing. Two weeks of solo dev is little perspective on game design.