MĂȘme graine. MĂȘme obscuritĂ©. Des choix diffĂ©rents.
Et si le mode multijoueur ne nĂ©cessitait pas que les joueurs soient en ligne en mĂȘme temps ? Et si la comparaison de deux parties ne nĂ©cessitait ni serveur ni base de donnĂ©es, mais simplement une graine et une sĂ©quence dâactions ?
Câest ce que jâai explorĂ© avec Darkwaar33 - Seedwake, un petit roguelike tactique au tour par tour dĂ©veloppĂ© avec Godot 4.6.
Deux joueurs peuvent explorer exactement les mĂȘmes sept Ă©tages, prendre des dĂ©cisions diffĂ©rentes, puis comparer leurs parties par la suite. Une partie importĂ©e devient un « fantĂŽme », montrant oĂč lâautre joueur est allĂ©, comment il a survĂ©cu et le moment exact oĂč leurs dĂ©cisions ont divergĂ©.
Ce qui est intĂ©ressant, ce nâest pas seulement le gameplay. Câest lâarchitecture qui rend possible la compĂ©tition asynchrone sans infrastructure en ligne.
Une graine, un monde
La gĂ©nĂ©ration procĂ©durale apporte gĂ©nĂ©ralement de la variĂ©tĂ©. Chaque nouvelle partie produit un monde diffĂ©rent, ce qui favorise lâexploration et la rejouabilitĂ©.
Mais la génération procédurale peut également créer un point de référence commun entre les joueurs.
Dans Seedwake, une graine de 30 bits dĂ©termine lâenvironnement dans son intĂ©gralitĂ© :
- Sept Ă©tages, chacun contenant une grille tactique de 7Ă7.
- Les positions des murs et lâemplacement des escaliers.
- Le placement des ennemis et leur comportement initial.
- Les « glints » Ă collecter et leurs durĂ©es dâexpiration.
- Les offrandes des sanctuaires entre les étages.
Deux joueurs partant de la mĂȘme graine se retrouvent face aux mĂȘmes conditions initiales.
Plus important encore, aucun nombre alĂ©atoire nâest gĂ©nĂ©rĂ© pendant le jeu.
Une fois quâun Ă©tage a Ă©tĂ© gĂ©nĂ©rĂ©, chaque rĂ©sultat obĂ©it Ă des rĂšgles dĂ©terministes. Les ennemis agissent en fonction de leur Ă©tat actuel, les attaques sont prĂ©visibles, et la mĂȘme action appliquĂ©e au mĂȘme Ă©tat produit toujours le mĂȘme rĂ©sultat.
La graine dĂ©termine le monde. Le joueur dĂ©termine ce qui sây passe.
Le déterminisme en tant que contrainte architecturale
Godot permet de gĂ©nĂ©rer des nombres alĂ©atoires via RandomNumberGenerator, mais son algorithme sous-jacent est documentĂ© comme un dĂ©tail dâimplĂ©mentation.
Pour un jeu classique, cette distinction nâa peut-ĂȘtre pas beaucoup dâimportance. Pour un jeu oĂč les joueurs Ă©changent des codes de relecture, elle est en revanche cruciale.
Une rediffusion créée aujourdâhui ne devrait pas produire silencieusement un rĂ©sultat diffĂ©rent aprĂšs une mise Ă jour du moteur.
Seedwake utilise donc un petit gĂ©nĂ©rateur xorshift32 explicitement dĂ©fini, avec des flux indĂ©pendants dĂ©rivĂ©s Ă lâaide de FNV-1a.
Les flux sont séparés par responsabilité :
| Flux | Responsabilité |
|---|---|
map |
Murs, escaliers, reflets et disposition des étages |
enemy |
Positions, orientations et cycles des ennemis |
offer |
Récompenses des sanctuaires |
Chaque flux est également isolé par étage.
Modifier la génération du troisiÚme étage ne devrait pas modifier de maniÚre inattendue celle du quatriÚme.
Cette sĂ©paration Ă©vite un problĂšme courant liĂ© Ă la gĂ©nĂ©ration procĂ©durale : lâutilisation dâun nombre alĂ©atoire supplĂ©mentaire dans un sous-systĂšme peut modifier lâensemble de la sĂ©quence utilisĂ©e par un autre.
Cela signifie également que les décisions de gameplay ne peuvent pas influencer la génération future par une utilisation accidentelle du générateur de nombres aléatoires (RNG).
Une simulation indépendante de son interface
Seedwake sépare ses rÚgles de la couche de présentation Godot.
Lâarchitecture est dĂ©libĂ©rĂ©ment lĂ©gĂšre :
main.gd
Entrées, écrans, HUD, animations
|
v
rules.gd
Génération, actions, combat,
score et simulation
|
v
board_view.gd
Grille, unités, intentions,
fantĂŽme et retour visuel
codec.gd
Graines, codes dâexĂ©cution et sommes de contrĂŽle
Le fonctionnement central est conceptuellement simple :
Rules.apply(state, action)
Elle prend un Ă©tat et une action, valide le coup, puis renvoie lâĂ©tat rĂ©sultant ainsi que les Ă©vĂ©nements dĂ©crivant ce qui sâest passĂ©.
Lâinterface utilise ces Ă©vĂ©nements pour animer les dĂ©placements, les dĂ©gĂąts, la destruction des ennemis et les transitions.
Les animations ne modifient pas la simulation.
Cette distinction est importante. Une interpolation dâune durĂ©e de 120 millisecondes ne doit pas modifier le rĂ©sultat dâun tour, quels que soient la frĂ©quence dâimages ou les performances du navigateur.
La logique du jeu reste indĂ©pendante du rendu, du timing et des pĂ©riphĂ©riques dâentrĂ©e.
Cette mĂȘme architecture rend Ă©galement lâintĂ©gralitĂ© du jeu rejouable.
Une rediffusion nâest quâune liste de dĂ©cisions
Les enregistrements de jeux traditionnels stockent des images ou des images vidéo.
Seedwake nâa besoin ni de lâun ni de lâautre.
La simulation Ă©tant dĂ©terministe, la reproduction dâune partie ne nĂ©cessite que deux Ă©lĂ©ments :
- La graine dâorigine.
- La séquence ordonnée des actions du joueur.
Une rediffusion est reconstituée en appliquant ces actions à un état initial nouvellement généré.
Rules.simulate(seed, actions)
Le rĂ©sultat comprend lâĂ©tat final du jeu et une trace de la progression du joueur.
Il nây a pas de moteur de rediffusion distinct ni de deuxiĂšme implĂ©mentation des rĂšgles de combat.
La mĂȘme simulation est utilisĂ©e pour le gameplay ordinaire, la validation des rediffusions et les fantĂŽmes rivaux.
La rejouabilitĂ© dĂ©coule des rĂšgles elles-mĂȘmes plutĂŽt que dâun sous-systĂšme supplĂ©mentaire ajoutĂ© a posteriori.
Partager une partie sans backend
Ă la fin dâune partie, Seedwake gĂ©nĂšre un code de partie compact.
Par exemple :
SW1-04V9P-NR2B5-âŠ
Ce code contient la version des rĂšgles, la graine, lâidentifiant du joueur, les actions enregistrĂ©es, le rĂ©sultat revendiquĂ© et une somme de contrĂŽle.
Les actions utilisent des identifiants Ă quatre bits, ce qui permet de faire tenir deux actions dans un octet. Les donnĂ©es obtenues sont encodĂ©es Ă lâaide du codage Crockford base32, un alphabet convivial qui Ă©vite plusieurs caractĂšres ambigus.
Un code peut ĂȘtre copiĂ© dans un message et importĂ© par un autre joueur.
Le jeu qui importe le code ne se fie pas aveuglément au résultat.
Il dĂ©code les donnĂ©es, vĂ©rifie leur intĂ©gritĂ©, valide la version des rĂšgles, puis rĂ©exĂ©cute lâintĂ©gralitĂ© de la sĂ©quence dâactions.
Si le score simulé, les niveaux terminés ou le nombre de tours diffÚrent du résultat déclaré, le code est rejeté.
Il ne sâagit ni dâune authentification cryptographique ni dâun systĂšme anti-triche. Une somme de contrĂŽle ne peut empĂȘcher une falsification dĂ©libĂ©rĂ©e, et les joueurs hors ligne peuvent rĂ©essayer une graine autant de fois quâils le souhaitent.
Mais cela fournit un rĂ©sultat reproductible et cohĂ©rent, sans nĂ©cessiter de comptes, de bases de donnĂ©es, dâAPI ou de simulation cĂŽtĂ© serveur.
Pour un petit jeu partagĂ© entre amis, câest un compromis utile.
Le « fantĂŽme » est lâhistorique dâun autre joueur
Une fois quâun code rival a Ă©tĂ© validĂ©, sa trace de simulation devient un « fantĂŽme ».
Le fantĂŽme apparaĂźt sous la forme dâun losange cyan creux se dĂ©plaçant sur les mĂȘmes Ă©tages que le joueur.
Il ne participe pas aux combats, ne bloque pas les dĂ©placements et nâinfluence pas les dĂ©cisions des ennemis.
En revanche, il fournit des informations.
Au troisiĂšme Ă©tage, au cinquiĂšme tour, le jeu peut afficher lâendroit oĂč se trouvait lâadversaire au troisiĂšme Ă©tage, au cinquiĂšme tour.
Si lâadversaire a dĂ©jĂ atteint lâescalier, le jeu indique Ă quel moment il en est sorti. Sâil est mort, sa position finale devient un marqueur de mort.
Une courte traßnée de positions précédentes rend les déplacements du fantÎme lisibles.
Câest le multijoueur asynchrone rĂ©duit Ă son essence informationnelle.
Les deux joueurs nâĂ©changent jamais de messages avec un serveur. Leurs parties nâont jamais besoin de sâexĂ©cuter simultanĂ©ment. Ils partagent simplement un monde dĂ©terministe et comparent leurs historiques.
DĂ©terminer le moment exact oĂč deux parties divergent
La fonctionnalité que je trouve la plus intéressante est le marqueur de divergence.
Imaginez deux joueurs partant dâune mĂȘme graine.
Ils effectuent le mĂȘme premier coup, puis le mĂȘme deuxiĂšme coup, puis le mĂȘme troisiĂšme coup.
La simulation étant déterministe, leurs états restent identiques.
Au quatriĂšme tour, lâun se dĂ©place vers le nord tandis que lâautre repousse un ennemi vers lâest.
Câest lĂ la premiĂšre divergence.
Seedwake enregistre cette dĂ©cision et marque sa position sur le plateau Ă lâaide dâun symbole en forme de fourche.
à partir de ce moment, les deux parties peuvent évoluer vers des résultats complÚtement différents.
Un joueur pourrait rĂ©cupĂ©rer une Ă©tincelle sur le point de sâĂ©teindre. Lâautre pourrait Ă©liminer un ennemi, prĂ©server sa santĂ© ou atteindre lâescalier plus tĂŽt.
Le jeu peut identifier lâaction prĂ©cise Ă partir de laquelle lâhistoire commune sâest scindĂ©e en deux chemins diffĂ©rents.
Câest plus intĂ©ressant que de se contenter de comparer les scores finaux.
Un score vous indique qui a obtenu les meilleurs résultats selon le systÚme de notation.
Un marqueur de divergence vous indique à quel moment leurs décisions ont commencé à diverger.
La comparaison devient alors un moyen de comprendre le jeu lui-mĂȘme.
Rendre les rĂšgles tactiques lisibles
Lâarchitecture dĂ©terministe nâaurait guĂšre de sens si les joueurs ne pouvaient pas comprendre les consĂ©quences de leurs actions.
Seedwake utilise donc un plateau tactique compact et entiĂšrement visible.
Il existe trois types dâennemis :
- Les Brutes poursuivent le joueur et attaquent les cases adjacentes.
- Les Lanciers tirent dans une direction fixe selon un cycle de trois tours.
- Les Sentinelles attaquent périodiquement leurs quatre cases voisines.
Leurs intentions sâaffichent avant que le joueur nâagisse.
Les cases hachurĂ©es en rouge indiquent oĂč les attaques vont frapper.
Le joueur peut se déplacer, attendre, sprinter ou pousser des ennemis. Pousser un ennemi contre un mur, un autre ennemi ou le bord du plateau le détruit.
Les attaques ennemies peuvent Ă©galement toucher dâautres ennemis.
Cela crĂ©e de petits dĂ©fis tactiques oĂč le positionnement importe davantage que la vitesse de rĂ©action.
Les reflets ajoutent une deuxiĂšme dimension dĂ©cisionnelle. Ils disparaissent aprĂšs un nombre limitĂ© de tours et peuvent soit ĂȘtre conservĂ©s pour marquer des points, soit dĂ©pensĂ©s dans des sanctuaires pour obtenir des amĂ©liorations de survie.
Le joueur doit constamment choisir entre collecter des ressources, Ă©viter le danger et progresser vers lâescalier.
Comme les rÚgles ne comportent aucun élément aléatoire en cours de partie, les conséquences de ces décisions sont reproductibles.
Pourquoi pas de serveur ?
Un classement en ligne serait possible.
Un backend pourrait collecter les scores, gérer les défis quotidiens, valider les soumissions et diffuser les parties des adversaires.
Mais cela impliquerait Ă©galement des contraintes liĂ©es Ă lâinfrastructure, Ă lâauthentification, au stockage, Ă la prĂ©vention des abus, Ă la maintenance et au dĂ©ploiement.
Pour Darkwaar33, je voulais tester jusquâoĂč le concept de multijoueur asynchrone pouvait aller sans tout cela.
Le résultat prend en charge trois interactions simples :
- Partager une graine pour relever le mĂȘme dĂ©fi.
- Partager un code de partie pour comparer les performances complĂštes.
- Importer un code de partie pour jouer aux cĂŽtĂ©s du fantĂŽme dâun autre joueur.
Le navigateur exécute tout localement.
La limite est tout aussi claire : il nây a ni recherche automatique dâadversaire, ni classement global, ni application contraignante des tentatives quotidiennes.
Ces fonctionnalitĂ©s nĂ©cessiteraient un modĂšle dâinfrastructure diffĂ©rent.
Pour cette expérience, elles ne sont pas nécessaires.
Un petit jeu, une idée réutilisable
Darkwaar est une expérience en cours visant à créer et à publier réguliÚrement de petites idées jouables.
Seedwake suit cette approche : un plateau limitĂ©, un petit ensemble dâactions, sept niveaux, trois types dâennemis et une mĂ©canique asynchrone distinctive.
Le projet utilise Godot 4.6 et GDScript, avec une exportation HTML5 publiée via le pipeline existant de Darkwaar.
La leçon plus générale dépasse le cadre des roguelikes.
Lorsquâune application est construite autour de transitions dâĂ©tats dĂ©terministes, une sĂ©quence dâentrĂ©es peut devenir une reprĂ©sentation compacte et portable dâune exĂ©cution complĂšte.
Cela ouvre des possibilités pour les systÚmes de relecture, le débogage, les tests reproductibles, les simulations et la collaboration asynchrone.
Dans Seedwake, cela se traduit par un fantÎme qui marche à vos cÎtés dans un donjon.
Mais en rĂ©alitĂ©, il sâagit simplement dâune nouvelle exĂ©cution des mĂȘmes rĂšgles.
MĂȘme graine. MĂȘme obscuritĂ©. Des choix diffĂ©rents.
Jouez Ă Darkwaar33 - Seedwake
Sept étages. Une graine partagée. Aucun serveur requis.
Jouez de maniĂšre indĂ©pendante, partagez votre code dâexĂ©cution et mettez quelquâun dâautre au dĂ©fi de prendre de meilleures dĂ©cisions dans exactement le mĂȘme monde.
Top comments (0)