Connect 4

article publié le 19 février 2021 (modifié le 22 février 2021)

Idée

Un jour je jouais au puissance 4 avec un ami sur Discord (logiciel de discussion en ligne similaire à Skype).
On utilisait les caractères Unicode suivant en guise de plateau :

⚫⚫⚫⚫⚫⚫⚫
⚫⚫⚫⚫⚫⚫⚫
⚫⚫⚫⚫⚫⚫⚫
⚫⚫⚫⚫⚫⚫⚫
⚫⚫⚫⚫🟡⚫⚫
⚫⚫⚫🔴🔴🟡⚫

C'était assez fun mais très laborieux. Mon ami m'a alors donné l'idée de créer un bot pour automatiser le procédé. Nous avons aussi eu l'idée d'ajouter un système de classement type Échecs avec un tableau des scores, car l'idée de rendre compétitif ce jeu simple nous faisait marrer.

Principe d'un bot Discord

Discord fonctionne de manière différente à Skype : Il y a bien des canaux de discussion privés et des groupes de discussion, cependant la majorité des utilisateurs préfère rejoindre des serveurs. Un serveur Discord contient plusieurs canaux de discussion vocaux ou textuels. Les serveurs Discord regroupent généralement un grand nombre d'utilisateurs, pouvant aller jusqu'à plusieurs milliers.

Les serveurs ont aussi la possibilité d'inviter des bots. Ce sont des comptes qui se présentent comme un compte utilisateur classique, mais qui est géré par un programme à travers une API. Les utilisateurs interagissent avec les bots par le biais de commandes : Par exemple quelqu'un écrit dans le tchat &help et le bot lui répond avec un message d'aide.

Réalisation

Généralités

J'ai développé le bot en TypeScript et avec la stack Node.js.

Le principe de fonctionnement de bot est le suivant :

  1. Un utilisateur qui veut jouer tape la commande &play et le bot répond avec un message contenant un plateau de jeu.
  2. Le bot ajoute ensuite des réactions à son propre message. Chaque reaction est un numéro de colonne jouable.
  3. Pour jouer dans une colonne, le joueur n'a plus qu'à cliquer sur une des réactions comme s'il s'agissait d'un bouton.
  4. Le bot met à jour son message et le second joueur peut à son tour jouer.

partie

À la fin de chaque partie, les participants gagnent ou perdent une certaine quantité de points. Cette quantité de point est déterminé par une implémentation du classement Elo qui est utilisé au Échecs. Les meilleurs joueurs apparaissent dans le classement global qui est visible quand on tape la commande &lead :

classement

Le bot possède plein d'autre fonctionnalités / commandes :

Base de données

Une base de données est nécessaire pour stocker toutes les données relatives aux parties, aux joueurs et aux serveurs Discord. J'ai choisi d'utiliser MySQL car la partie du code qui gère les données est orientée objet et il est facile de mapper une classe à une table via un ORM.

En ce qui concerne l'ORM, j'ai choisi d'utiliser TypeORM, car il s'intègre très bien avec TypeScript. Il est également beaucoup plus simple d'utilisation et est moins lourd que le précédent que j'avais testé : Sequelize.

Pour ce qui est des données elles-mêmes, elles sont toutes stockées en base de données. Les seules données stockées en RAM consistent uniquement de données misent en cache et de données concernant les interactions immédiates avec les utilisateurs. Cela permet une grande résilience : Si le bot se coupe pour un problème quelconque : coupure réseau, coupure électrique, crash du bot, downtime de l'API, mais aussi mise-à-jour, au redémarrage il peut reprendre comme si de rien n'était, sans perdre d'information. Cela a aussi l'avantage qu'il sera plus facile de mettre en place le sharding qui consiste à diviser la charge de travail sur différents processus. Le sharding est obligatoire pour les bots présents sur beaucoup de serveurs et il sera peut-être nécessaire que je le mette en place.

CI / CD

Le bot est déployé sur un Raspberry Pi 4. Vu le nombre de serveurs déjà présent sur la machine, tout est géré avec des conteneurs pour éviter les conflits et aussi tout simplement, car c'est beaucoup plus simple à gérer en général.

Il faut donc construire une image Docker. La forge que j'utilise (GitLab) permet de mettre en place un pipeline qui est lancé à chaque commit et qui peut être utilisé pour construire l'image. Coup de chance : GitLab permet aussi d'héberger des images Docker.

Malheureusement les Raspberry utilisent une architecture ARM et GitLab est sous x86-64. Afin de construire une image pour une architecture différente j'utilise Buildx qui de son côté utilise Qemu.

Pour résumer, à chaque fois que je fais un commit, une image Docker est construite et je n'ai plus qu'à la télécharger sur le Raspberry et la lancer. Tout ça à l'air très compliqué, mais dans les fait, tout tient dans le petit fichier suivant :

variables:
  CI_BUILD_ARCHS: linux/arm64,linux/amd64
  DOCKER_HOST: tcp://docker:2375/

stages:
- build_files
- build_image

build_files_job:
  only:
  - master
  image: cl00e9ment/node.js-builder:light
  stage: build_files
  script:
  - pnpm i
  - pnpm run build
  - pnpm prune --production
  - mkdir app
  - mv build lang node_modules res package.json app
  artifacts:
    paths:
    - app
    expire_in: 1 hour

build_image_job:
  only:
  - master
  image: jonoh/docker-buildx-qemu
  stage: build_image
  services:
  - docker:dind
  before_script:
  - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
  # Use docker-container driver to allow useful features (push/multi-platform)
  - docker buildx create --driver docker-container --use
  - docker buildx inspect --bootstrap
  script:
  - update-binfmts --enable # Important: Ensures execution of other binary formats is enabled in the kernel
  - docker buildx build --platform $CI_BUILD_ARCHS --progress plain --pull -t "$CI_REGISTRY_IMAGE" --push .

Un succès inattendu

Contre toute attente le bot a eu un succès largement supérieur à ce que j'avais imaginé. Visiblement le Puissance 4 compétitif a de l'avenir...

J'ai publié le bot sur un site non officiel qui recense les bots et serveurs Discord (top.gg). À partir de ce moment là j'ai gagné beaucoup d'utilisateurs. Au moment où j'écris cet article plusieurs milliers d'utilisateurs ont joué au bot et ce dernier est présent sur plus de 500 serveurs.

Le bot à ensuite été vérifié officiellement par Discord, ce qui est nécessaire pour croître au-delà de 100 serveurs.

Ce succès vient avec des causes intéressantes, comme des joueurs qui tentent d'exploiter le système d'Elo pour atteindre la première place du classement en utilisant plusieurs comptes secondaires qu'ils font perdre à leur avantage. J'ai donc dû mettre en place un système d'anti-triche qui attribue, à chaque joueur, un coefficient indiquant la probabilité de triche. Quand ce coefficient est trop haut, une vérification manuelle est faite et si triche il y a, le joueur est banni.

Conclusion

J'ai vraiment apprécié ce petit projet car il m'a permis de toucher à plein de choses différentes :

C'est également un projet que j'ai terminé en un lapse de temps raisonnable : quelques semaines. Enfin, il y a non seulement la satisfaction d'un projet terminé, mais également la satisfaction que je ne l'ai pas fait que pour moi : d'autres personnes semblent apprécier le bot.