Aller au contenu

Atomic Betting

septembre 2024 → octobre 20245 min de lecture

Code source

L’idée est née d’une blague en cours : on se disait que ce serait drôle de parier sur qui allait arriver en retard. Je me suis dit autant le construire pour de vrai, pour expérimenter et apprendre. C’est mon premier projet perso avec une vraie modélisation de données et une authentification complète.

Atomic Betting, c’est une appli pour parier entre amis ou entre collègues sur à peu près n’importe quoi, comme la durée de la réunion de lundi ou qui apporte les croissants vendredi. On crée une équipe, on y invite les autres, et chacun mise des Atomic Coins, une monnaie fictive. Il n’y a jamais d’argent en jeu. Le site n’est plus en ligne, mais le code est public sur GitHub.

Des cotes qui suivent les mises

Un pari, c’est une question, quelques réponses possibles, des dates d’ouverture et de clôture, et une mise minimale et maximale. Chacun choisit une réponse et mise dessus. On peut remettre des jetons sur la même plus tard, mais pas changer d’avis.

Les cotes, c’est le point technique dont je suis le plus content. Elles ne sont pas fixées à l’avance, elles dépendent de ce que les gens misent. C’est le principe du pari mutuel. La cote d’une réponse, c’est le total misé sur le pari divisé par ce qui a été misé sur elle. Plus une réponse est jouée, plus sa cote baisse. Sur la capture, 385 jetons sont en jeu, dont 250 sur « Plus d’une heure ». Sa cote est de 1,54, alors que « Moins de 30 minutes », bien moins jouée, est à 6,42.

La page d’un pari sur la durée de la réunion de lundi, avec ses trois réponses, et pour chacune sa cote, les jetons misés, le nombre de joueurs et sa part du total.

Une fois le pari terminé, un responsable de l’équipe désigne la bonne réponse. Chaque gagnant reçoit sa mise multipliée par la cote finale, arrondie à l’entier inférieur. Au final, les gagnants se partagent la cagnotte, chacun selon sa mise.

Une réponse que personne n’a encore jouée aurait une cote infinie. Je lui donne le nombre de réponses à la place, comme si elles avaient toutes la même chance.

La même fonction calcule les cotes affichées dans le navigateur et les gains versés par le serveur. C’est aussi la seule partie que j’ai couverte par des tests, avec Vitest.

Un solde et un journal

Les mises et les gains passent par le solde de chacun. Un membre a un solde par équipe, rangé avec son adhésion, et je le lis tel quel pour afficher une page, sans rien recalculer. À côté, les mises, les gains et les récompenses sont écrits dans un journal.

Le risque, c’est que les deux finissent par ne plus être d’accord, par exemple si une opération s’arrête au milieu. Placer un pari écrit donc la ligne du journal et le nouveau solde d’un seul bloc, dans une transaction Prisma. Soit les deux passent, soit aucun.

Ce journal, je ne l’ai pas eu du premier coup. Au départ, la table ne gardait que les mises, puis les gains y sont entrés avec un type. Quand j’ai ajouté la récompense quotidienne, un mouvement de jetons n’avait plus forcément de pari derrière lui. J’ai remplacé la table par un journal plus général, où le pari et la réponse sont facultatifs.

La récompense du jour

Les jetons ne viennent pas que des paris. En entrant dans une équipe, on reçoit un premier solde. Ensuite, on peut réclamer une récompense une fois par jour, et le septième jour d’affilée rapporte un bonus. Tous ces montants se règlent dans chaque équipe. Quand on réclame, des confettis tombent sur l’écran. Voilà le tableau de bord juste après, avec le solde en haut à gauche et les dernières lignes du journal à droite.

Le tableau de bord de l’équipe sous une pluie de confettis, juste après la récompense du jour, avec le solde, la série de jours consécutifs et les dernières transactions.

Les équipes et les rôles

Tout ça se passe dans une équipe. On se connecte avec GitHub, Discord ou Google, par Auth.js, sans mot de passe à gérer, et les sessions sont gardées en base. Ensuite, on rejoint une équipe par un lien d’invitation, et la demande attend qu’un manager ou un administrateur l’accepte.

La liste des invitations en attente dans l’administration de l’équipe, avec les boutons Approuver et Rejeter.

Tout le monde peut parier. Les managers, en plus, créent les paris, désignent la bonne réponse et acceptent les nouveaux. Les administrateurs font tout ça, et eux seuls changent les rôles, depuis ce tableau.

Le tableau des rôles de l’équipe, avec pour chaque membre un sélecteur entre utilisateur, manager et administrateur.

Chaque action vérifie le rôle côté serveur, pas seulement dans l’interface. Dans les réglages de l’équipe, par exemple, un manager ne peut changer que la durée par défaut des paris. Les autres champs sont grisés chez lui, et s’il les envoie quand même, le serveur les ignore.

Trois semaines en septembre

J’ai fait l’appli seul, en trois semaines, en septembre 2024. C’est du Next.js 15 avec React 19, tous les deux encore en release candidate à l’époque. Les formulaires passent par des Server Actions validées avec Zod, les données par Prisma sur PostgreSQL, et l’interface est faite avec shadcn/ui et Tailwind. Le site tournait sur Vercel, avec la base chez Neon.

Dans ces trois semaines, j’ai aussi fait les pages qu’on saute souvent, comme l’accueil, la FAQ, l’écran de bienvenue après la première connexion ou les conditions d’utilisation.

Je me suis arrêté fin septembre, et le dépôt est maintenant archivé.

Technologies utilisées