L'aléatoire: pièges, effets et conséquences sur un jeu web

Les bugs classiques d'implémentation

L'implémentation d'un élément aléatoire dans un jeu amateur est souvent ratée, à cause d'incompréhensions sur les mathématiques probabilistes qui les sous-tendent. Eliminons déjà ces erreurs.

Le modulo

Quand on veut mettre de l'aléatoire dans un jeu, on a souvent besoin de le borner. Pour cela, la plupart des développeurs courent vers des implémentations trop simplistes, qui s'avèrent alors non-équiprobables, c'est à dire que certains nombres ont plus de chances que d'autres d'être tirés, ce qui peut amener de graves déséquilibres dans le gameplay.

Par exemple, un développeur veut un nombre entier aléatoire dans [0..100[. S'il recourt au modulo, alors il fera un simple RAND()%100. Or, ce calcul n'est pas nécessairement équiprobable. En effet, la fonction RAND() renvoie un nombre aléatoire dans un intervalle qui n'est pas toujours multiple de 10, comme [0..256[. Le tirage sera alors inéquitable: les nombres de 0 à 55 peuvent être tirés de 3 façons (0;100;200..55;155;255) alors que les nombres de 56 à 99 ne peuvent être tirés que de 2 façons (56;156..99;199), ces derniers reviendront moins souvent et un déséquilibre se crée.

Vous devez utiliser une fonction de tirage équiprobable (comme mt_rand($min, $max)).

L'arrondi

De façon similaire, si votre langage ne vous permet de tirer que des nombres "décimaux" (Math.random de JS ou RAND() de MySQL par exemple), alors vous aurez des soucis d'arrondi. Par exemple, pour obtenir un nombre à 1 décimale dans [0..1] (donc 0.0 ou 0.1 ou... ou 0.9 ou 1.0), les développeurs codent ROUND(RAND(), 1). Néanmoins, cet arrondi défavorisera les valeurs extrêmes de l'intervalle: 0.0 et 1.0 sortiront moins souvent puisqu'il faut respectivement un nombre dans [0..0,5[ et dans [0,95..1,0[ (longueur de l'intervalle: 0.05) pour les tirer alors que la plage de tirage des autres nombres est de 0.1. Les nombres extrêmes auront donc 2 fois moins de chances de sortir.

Résultats
Résultat: les bornes sont moins souvent tirées que l'intérieur de l'intervalle

L'implémentation de l'aléatoire doit donc être mathématiquement parfaite pour vous permettre de maîtriser les statistiques (et les mathématiques) associées, et assurer l'équilibrage de votre jeu web.

Les effets spéculatifs

Même en implémentant correctement votre aléatoire, il y a des risques. L'un d'eux est l'apparition d'effets "spéculatifs". Il apparait lorsque de l'aléatoire est introduit dans un schéma d'échange avec le serveur (un PNJ par exemple). Imaginons que le jeu vous permette d'échanger, une fois par jour, une ressource contre une autre à un taux de change partiellement aléatoire. Par exemple, vous pouvez échanger du bois contre de la pierre au taux de 1 bois = 0.5 à 0.8 pierres. Le taux de cet échange est aléatoire, ainsi que le sens de cet échange (un jour, le jeu achètera du bois, et un autre jour, ce sera peut-être de la pierre).

La règle peut vous sembler équilibrée: un même taux de change aléatoire est appliqué pour les ressources, et le sens de l'échange est aussi aléatoire. Mais vous avez ouvert la porte à la spéculation. Imaginons que le joueur dispose de 1000 unités de bois. Il va attendre que le serveur achète du bois (ce qui a 1 chance sur 2 d'arriver chaque jour). Un jour, le serveur achète donc du bois, au taux de 1 bois = 0.7 pierres. Le joueur vend tout, et il a 700 pierres. Maintenant, le joueur attend que le serveur achète des pierres. Cela arrivera fatalement un jour. Mais en plus, le joueur attendra que le serveur achète ces pierres à un taux inférieur à 1 bois = 0.7 pierres (ce qui peut facilement arriver). Le jour viendra donc où le jeu achètera de la pierre au taux de 1 bois = 0.6 pierres. Le joueur échangera ses 700 pierres contre 1167 bois. Il a donc gagné 167 bois en spéculant.

Cette spéculation serait impossible avec un taux fixe de 1 bois = 0.7 pierres, voire avec des taux asymétriques comme 1 bois = 0.5 à 0.7 pierres quand vous vendez du bois au jeu, et 1 bois = 0.7 à 0.8 pierres quand vous achetez du bois au jeu (vous devez donc donner plus de pierres que ce que le jeu vous aurait donné dans l'autre sens).

Cet effet peut être volontaire, mais vous ne l'avez peut-être pas vu venir (comme Battalia). Dans tous les cas, gardez en tête qu'ajouter de l'aléatoire peut rapidement faire surgir des effets inattendus, comme la spéculation.

L'effet cumulatif de l'aléatoire

Supposons maintenant qu'un paramètre aléatoire soit récurrent à chaque tour de jeu (la probabilité de gagner un duel par exemple) et cumulatif par rapport aux tours précédents (plus vous gagnez de duels, plus vous avez de chances de gagner le suivant). Dans cette situation, très courante, vous risquez de voir un effondrement de votre jeu web: sa diversité va dégringoler, et un composant de jeu (ou un joueur) écrasera les autres.

Même une implémentation parfaite de l'aléatoire peut déséquilibrer un jeu si elle est mal utilisée (mauvais gamedesign).

Exemple

Prenons un exemple d'ECLERD. Dans ce jeu, la population se renouvelle régulièrement. Supposons qu'elle soit, au départ, de 100 personnes et qu'à chaque tour de jeu (disons, chaque mois), elle peut varier de -1% à +1% (nombres décimaux inclus). L'effet est cumulatif et on garde la fraction décimale: si la population au mois 0 est de 100 et que le tirage aléatoire donne +0.5%, alors la population au mois 1 sera de 100.5 et si le tirage suivant donne à nouveau +0.5%, alors la population sera de 101.0025 personnes.

Ce qu'on attend

En procédant ainsi, je m'attendais à ce que la population oscille autour de la valeur initiale de 100 habitants (et les actions du joueur créeront des tendances à la hausse ou à la baisse). En effet, comme le tirage est équiprobable (autant de chances de tirer -1% que +1%) et qu'il est centré sur 0 (-1%..+1% donne une moyenne de 0%), alors j'espérais que la population globale tournerait autour de 100. J'estimais également la probabilité qu'elle double à pow(2,70) = 1e21 car il faut environ 70 fois "+1%" pour passer de 100 à 200 habitants.

Les résultats

Diagramme montrant la divergence des essais
Différents essais donnant le même résultat: en 1000 mois, on est loin de la valeur initiale de 100!

Le résultat n'est pas du tout ce à quoi je m'attendais (ce qui explique d'ailleurs que le comportement de jeu d'ECLERD soit aussi anarchique): les effets de l'aléatoire ne se compensent pas, mais se cumulent. Pourquoi? Parce que si vous tirez -1% au premier tour, alors vous devrez tirer +1% au tour suivant et à nouveau +0.01% au tour d'après pour revenir à l'état initial (100 - 1% => 99% +1% => 99.99 +0.01% => 100). Le 1er tirage introduit donc un "biais" dans l'évolution de votre population, qu'il sera difficile de rattraper ensuite. En pratique, il arrivera donc un moment où la courbe de population va complètement décrocher de sa valeur de base, et le jeu apparaitra anarchique aux joueurs s'ils ne peuvent pas compenser cet effet cumulatif. Et ce n'est pas un "manque de bol": les différents courbes sont toutes atteintes de la même "dégénérescence".

On peut rapprocher ces résultats de la "loi de Murphy" (ou des grands nombres): tout évènement, même peu probable, se produira si on retente sa chance assez souvent.

L'effondrement de la médiane

Comme présenté sur JeuWeb, un autre problème intervient: l'effondrement de la médiane. Cela signifie que, au terme de ces 1000 mois, vous aurez plus de pays qui auront une population inférieure à la valeur initiale que de pays ayant une population supérieure! Cela vous semble paradoxal? Pas tant que cela: si vous rajoutez un pourcentage à quelque chose et que vous le retirez ensuite, alors vous aurez une valeur finale inférieure à la valeur initiale. Par exemple, 100 + 5% = 105, 105 - 5% = 99.75 <100. Pour cette raison, la moyenne restera à 100 (car RAND(-5%..5%) est centré sur 0) mais la médiane va dégringoler au fil du temps.

Diagramme de répartition des tirages
Partant de la valeur 100, la médiane des 5000 simulations tombe à 85 au terme de 1000 tours

Vous pouvez télécharger la démo complète.

La moyenne ne suffit pas à décrire la distribution de probabilité d'une formule: votre jeu peut sembler équilibré si sa moyenne l'est, mais en pratique, il sera totalement déséquilibré à cause d'autres paramètres (médiane, 1er décile prépondérant, etc).

Extension

Ce modèle peut s'appliquer à de très nombreux éléments d'un jeu web: la population d'un pays, les ressources d'un joueur, ses points de vie, ceux de ses ennemis, l'équipement ou l'XP du joueur, et même, le plaisir de jouer ou la difficulté du jeu: il suffit de considérer qu'un "mois" dans l'exemple précédent (ou un "tour") correspond à un choix du joueur, et la base 100 correspond à l'équilibre d'un jeu: l'aléatoire va faire dévier cet équilibre, jusqu'à ce que le jeu soit trop simple ou trop difficile.

Cet effet explique parfois qu'un seul joueur (ou un très petit groupe de joueurs) finisse(nt) par accumuler la quasi totalité des richesses d'un jeu.

Les solutions

On peut tenter de résoudre ce problème de différentes façon, la première étant de supprimer l'effet cumulatif (la population ne grandit pas de [-1%..+1%] mais on rajoute [-1..+1] habitant à chaque tour), mais le gameplay ne s'y prête pas toujours (l'exemple de la population ne fait pas très réaliste).

L'aléatoire peut également être rendu très marginal par rapport aux actions du joueur, en réduisant le pourcentage précédent à 0.01% par exemple. Néanmoins, cette réduction risque de rendre l'aléatoire purement et simplement inutile, car sans aucune conséquence.

La troisième solution consiste justement à pousser la solution précédente à l'extrême, en laissant tomber totalement l'aléatoire. Tous ces problèmes s'envolent alors.

De nombreux jeux fonctionnent sans aléatoire (échecs, Go, jeux de stratégie en général, jeux de société sans dé,...) voire avec très peu d'aléatoire (les armes d'Unreal Tournament 2004 n'ont que peu d'aléatoire). Pour autant, ces jeux sont encore joués de nos jours: l'aléatoire ne semble pas nécessaire à la survie d'un jeu

Pourquoi de l'aléatoire?

Cette dernière solution m'amène à une question: pourquoi vouloir mettre de l'aléatoire dans un jeu, et prendre le risque de le voir exploser en vol? Souvent, une seule raison est invoquée par les créateurs de jeu: ajouter de l'imprévisible au jeu. Pourquoi pas, c'est en effet intéressant pour les joueurs de rendre un jeu un peu "imprévisible": cela amène une notion de "risque", et donc d'adrénaline.

Mais le but de cet aléatoire n'est pas de rendre le jeu imprévisible pour son créateur! En cela, l'imprévisibilité (et le risque associé) d'un jeu web devraient être uniquement ressentis par les joueurs, et jamais par les créateurs qui se doivent de garder la main sur l'évolution de leur jeu, pour être capable de lui faire prendre la direction la plus ludique. Ainsi, il faut trouver des sources d'imprévisibilité pour les joueurs, qui n'en soient pas pour les créateurs.

Si certaines données du jeu sont cachées aux joueurs (position des troupes ennemies, technologies débloquées par l'adversaire, formules de calcul, présence ou non derrière l'écran de l'autre joueur, etc) alors celui-ci les considèrera comme des "aléas": il retrouvera en elles le goût du risque recherché par les créateurs, sans que ceux-ci n'aient besoin d'intégrer du "RANDOM" pur.

La pauvreté du gameplay

En fait, un jeu web sera souvent "prédictible", donc en manque de risque ou d'aléatoire, si son gameplay est trop pauvre. En effet, s'il n'y a que très peu de paramètres à prendre en compte pour savoir si telle action va réussir, alors les joueurs sauront prendre ces paramètres en compte de tête, et ne ressentiront aucun risque et aucune montée d'adrénaline. La mauvaise solution est alors d'ajouter de l'aléatoire pour combler ce "vide" du gameplay (ce qui peut être tolérable dans le cadre d'un mini-jeu par exemple, où on ne veut pas trop complexifier les choses pour rester abordable par tout le monde). Mais une bien meilleure solution consiste à étoffer le gameplay, d'une part en rajoutant de nouveaux choix (nouvelles unités, nouveaux chemins pour atteindre une cible, etc) mais aussi de nouveaux types de paramètres à prendre en compte (vitesse et direction du vent dans Worms, position des unités ennemies, etc).

Le "borderline"

Enfin, une dernière façon simple de rajouter du risque sans ajouter de l'aléatoire consister à créer des effets "binaires". Par exemple, imaginez un archer qui a une portée de 100m et qui touche toujours sa cible. Supposons maintenant que plus cet archer est haut (sur un rocher, dans une tour, sur un toit, etc) plus sa portée augmente (s'il est à 10m de hauteur, sa portée est de 110m, on fait simple). Dans ce cas, un duel entre deux archers peut devenir très risqué et nerveux, car vous devez vous approcher de votre ennemi sans entrer dans sa zone de portée: charge à vous d'être "plus haut" que lui, même d'1cm. C'est là que réside l'effet "binaire": si votre archer est 1cm plus haut que votre ennemi, en approchant pas à pas, vous le mettrez à votre portée sans être à sa portée et vous le tuerez "sans risque" (le risque est pris dans le positionnement de votre unité et non dans l'action de tirer).

L'exemple est bateau et un peu tiré par les cheveux, mais l'idée est vraiment là: en faisant en sorte qu'un réglage très précis soit nécessaire, vous rendrez votre jeu web indirectement "imprévisible" pour le joueur (mais pas pour le maitre du jeu que vous êtes).

L'aléatoire dans un jeu web requiert un bagage mathématique important pour être correctement intégré dans votre jeu. Si vous n'avez pas ce bagage, le plus simple est d'éviter toute forme d'aléatoire. Vous verrez: on s'amuse très bien sans, et je dirai même qu'on s'amuse d'avantage sans!

⇇ Retour à l'accueil