This content is not fully available in your language 😕 You want to contribute? Send us an e-mail at hello@gamebuino.com.

Intelligence artificielle

Step 5
Step completed?

Jouer contre les humains c'est cool, mais créer une IA c'est mieux.

Dans cette étape, nous allons:

  • Ajouter une intelligence artificielle simple

  • Ajouter de l'aléatoire dans le jeu

Vous avez fini votre premier jeu, améliorons-le avec de l'intelligence artificielle.

Notre première IA

Durée 30 minutes (et plus si affinité)

Niveau débutant

Dans l'étape précédente: Pong, vous avez créé un jeu à deux joueurs fonctionnel. On y utilisait les flèches pour bouger la raquette de gauche, et les boutons A et B pour la raquette de droite. Vous avez aussi implémenté un système de points et vous avez utilisé des if avec plusieurs conditions pour faire rebondir la balle.

Pong à deux c'est sympa, mais comment faire si on veut jouer tout seul? Figurez-vous qu'une petite intelligence artificielle vous permettra de jouer seul contre votre Gamebuino ;)

Mais, qu'est-ce qu'une intelligence artificielle au juste? L'intelligence artificielle, aussi appelée ** IA** consiste à donner le pouvoir à l'ordinateur de prendre des décisions par lui-même: C'est le rendre vivant!

Une IA peut être très puissante. C'est un domaine très populaire aujourd'hui, et il peut prendre des formes assez complexes. Il y a même des diplômes universitaires spécialisés dans l'IA. Dans cet atelier, nous allons revenir aux bases de l'IA, aux origines. On va créer une intelligence pour une des raquettes de notre jeu Pong.

La manière la plus simple de programmer une IA est d'utiliser des déclarations conditionnelles if plus poussées. Avec de l'aléatoire, nous rendrons l'ordinateur moins prévisible et plus amusant dans la partie 2 de cette étape.

Notre premier ennemi virtuel

Commençons par examiner comment l'ordinateur pourra atteindre ses objectifs:

  • L'ordinateur contrôlera la raquette de droite, appelée raquette2 dans le pong de l'atelier précédent. L'objectif de cette IA est de taper la balle quand elle arrive de son côté. Autrement dit, il faut que l'ordinateur puisse suivre la balle avec la raquette.

La logique derrière les coulisses

L'algorithme devrait donc avoir cette forme:

Si la balle est au-dessus du centre de la raquette
    Alors, déplacer la raquette vers le haut
Sinon, si la raquette est en dessous du centre de la raquette
    Alors, déplacer la raquette vers le bas

Avec cet algorithme, on veut toujours faire une de ces deux choses:

  • aller vers le haut,
  • ou aller vers le bas.

De manière généralle, on veut mettre la raquette face à la balle. Pour faire ça, on regarde si la balle est au-dessus ou en dessous du centre de la raquette. Vous avez vu que la variable raquette2_posY contient la position du haut de la raquette. Si on veut le centre, il nous faut y ajouter la moitié de la hauteur de la raquette. On a donc raquette2_centreY = raquette2_posY + raquette_hauteur / 2.

L'algorithme introduit aussi quelque chose de nouveau pour nous: le sinon. Il arrive que vous vouliez exprimer l'idée de "Si A est vrai, alors on fait une chose, sinon on fait une autre chose." Ce qui se traduit par:

if (A) {
  // Faire une chose
}
else {
  // Faire une autre chose
}

Ceux d'entre vous qui ne parlent pas anglais auront peut-être deviné que "else" signifie "sinon" en français ;) Une autre particularité très pratique est le else if. Quand notre sinon est directement suivi par un if, alors on peut écrire else if () {...} à la place de else { if () {...} }. C'est plus lisible et surtout on peut placer autant de else if à la suite que l'on souhaite (alors qu'on peut ne mettre qu'un seul else). Par exemple, ceci est permis:

if (A) {
  // Faire une chose
}
else if (B) {
  // Faire une autre chose
}
else if (C) {
  // Faite encore autre chose
}
else {  // Si A, B, et C sont faux
  // Faire autre chose
} 

Pour en revenir à notre algorithme, on a donc un if et un else if. En appliquant ce qu'on vient de voir, il suffit donc de trouver le code correspondant à l'algorithme. Maintenant c'est...

Notre IA en code

Vous avez tous les outils à votre disposition pour pouvoir programmer votre première intelligence artificielle. Commencez par relire l'algorithme, puis modifiez le code de l'étape précédente à l'aide de ce qu'on vient de voir. Si vous avez une idée mais que vous n'êtes pas sûr, essayez-la quand même! Vous avez le code, vous avez la console, quelle meilleure façon de savoir si ça marche que de l'essayer?

Petit coup de main: pour trouver le centre de la raquette, relisez le début du tutoriel.

J'ai mis la solution juste en dessous, donc si vous n'avez pas cherché par vous-même, c'est votre dernière chance :) Encore une fois, essayer par soi-même est une des meilleurs techniques pour apprendre rapidement.

Et voilà, c'est fait! Vous pouvez jouer contre votre Gamebuino à Pong! Mais... en jouant vous avez peut-être remarqué que la raquette ennemie suit le mouvement exact de la balle et fait peu de fautes. Nous allons corriger ça en améliorant notre IA.

Exemple de Solution

Ok, donc regardons si vous avez quelque chose de similaire:

void loop() {
  //// MAJ de la raquette1 (raquette du joueur)

  // MAJ de la raquette2 (raquette de l'ordinateur)
  if (balle_posY > raquette2_posY + raquette_hauteur / 2) {  // Si la balle est plus basse que le centre
    raquette2_posY = raquette2_posY + 1;                     // Se déplacer vers le bas
  } 
  else if (balle_posY < raquette2_posY + raquette_hauteur / 2) {  // Si la balle est plus haute que le centre
    raquette2_posY = raquette2_posY - 1;                          // Se déplacer vers le haut
  }

  //// MAJ de la balle et collisions
  //// Afficher la balle, les raquettes et le score 
}

Une IA moins prévisible

Vous avez conçu une intelligence artificielle. L'ordinateur est capable de suivre la balle pour ne pas perdre. Il joue contre vous mais son comportement est très prévisible. 'Si la balle est en dessous de la raquette, je descends. Sinon je monte'. Ce comportement n'est pas très naturel. Ça serait mieux si notre adversaire se déplaçait plus chaotiquement et ratait la balle de temps en temps.

rand(): la clé de l'aléatoire

Mais comment concevoir un comportement imprévisible? Et bien il existe une fonction qui nous permet de générer un nombre aléatoire:

rand(); Avec cette syntaxe, l'instruction retourne un nombre décimal compris entre 0 (inclusif) et 1 (exclusif). Comme 0 est inclusif, l'instruction peut retourner 0 mais comme 1 est exclusif, le nombre retourné pourra en être proche mais ne vaudra jamais 1. rand(max:nombre): L'instruction retourne un nombre décimal compris entre 0 (inclusif) et le nombre maximal (max) (exclusif). L'instruction retournera donc un nombre entre 0 et max (mais comme max est exclusif, le nombre retourné ne vaudra jamais max). rand(min:number, max:number):number; Cette fois, la valeur retournée sera comprise entre min (inclusif) et max (exclusif).

Dans tous les cas, l'instruction retourne un nombre aléatoire. Avant d'attaquer notre IA avancée, utilisons rand() pour améliorer notre jeu.

Dans votre jeu, actuellement, la balle est replacée sur l'écran à chaque fois qu'un joueur marque un point ou en début de partie. Elle part toujours du même endroit et a toujours la même trajectoire. On pourrait donc quasiment faire la même partie à chaque lancement. C'est un peu répétitif non? Alors rand() va vous sauver et pimenter tout ça ! Pour cela nous allons juste modifier notre fonction relance_balle()

function relance_balle() {
  // Relancer la balle
  balle_posX = 50;
  balle_posY = rand(30, hauteur_ecran - 30);  // Position aléatoire au centre de l'écran
  balle_speedX = 1;

  if (rand(0, 2) > 1) {  // 50% du temps
    balle_speedY = 2;
  } 
  else {  // 50% du temps
    balle_speedY = -2;
  }
}

Maintenant, la balle réapparait avec position en Y aléatoire comprise entre 30 et 98 (128 - 30). De plus, comme leif (rand(0, 2) > 1) est vrai une fois sur deux, la balle part dans une direction verticale aléatoire: soit vers le haut, soit vers le bas. Ce sont des petites modifications comme ça qui différencient les jeux moyens des bons jeux ;)

Remarque: Si on veut tester une égalité dans une condition, il faut utiliser un double égal ==. Donc if (a == b) est vrai quand a vaut b. C'est TRÈS important car si vous mettez un seul signe égal, votre jeu fera des choses inattendues car au lieu de comparer a et b, il assignera la valeur de b à a... ce qui n'est pas du tout le comportement attendu.

Moins prévisible == plus amusant

Améliorons notre IA avec rand()! Pour que l'ordinateur semble plus "humain", il lui faut des réflexes plus longs. Pour l'instant, la raquette est toujours en face de la balle, mais avec random, on peut faire en sorte que la raquette ait plus de mal à suivre la balle. De même, on peut ajouter différente conditions pour avoir des omportements spécifiques dans des conditions spécifiques. Le but étant d'obtenir un adversaire moins parfait, moins prévisible mais surtout plus amusant.

Tout d'abord, nous décidons de ce que l'on veut faire: moi, j'ai envi que la raquette de l'ordinateur ne bouge que lorsque la balle franchie la moitié de l'écran en direction de sa raquette (vers la droite). De plus, dans ce cas, je veux que l'odinateur ne se déplace qu'une fois sur 3. C'est ma décision, mon postula de base pour mon IA. Les valeurs avec lesquelles je vais commencer pour les essayer et les ajuster après en fonction du résultat que j'obtiendrais et qu'il faudra que j'ajuste pour rendre le jeu plus ou moins diffcile (On pourrait imaginer par exemple des valeurs qui changent en fonction de la sélection d'un niveau de difficulté.) Pour cela, c'est simple, dans la boucle dans laquelle on gère les modifications de la position de la raquette, je vais inclure mes ìf` dans un autre if qui testera si l'ordinateur doit se déplacer ou non à chaque fois qu'il en arrivera à cette section du code.

function update(time) {

  // Contrôles de la raquette1 (raquette du joueur)
  if (UP && (raquette1_posY > raquette_hauteur / 2)) {
    raquette1_posY = raquette1_posY - vitesse_raquettes;
  }

  if (DOWN && raquette1_posY < (hauteur_ecran - raquette_hauteur / 2)) {
    raquette1_posY = raquette1_posY + vitesse_raquettes;
  }

  if ((balle_posX > largeur_ecran / 2 ) && (balle_speedY > 0) && (rand(0, 3) <1.9)) {
    // MAJ de la raquette2 (raquette de l'ordinateur)
    if (balle_posY > (raquette2_posY + raquette_hauteur / 2) 
        && raquette2_posY < (hauteur_ecran - raquette_hauteur / 2)) {  // Si la balle est plus basse que le centre
          raquette2_posY = raquette2_posY + 1;                     // Se déplacer vers le bas
    } 
    else if (balle_posY < (raquette2_posY + raquette_hauteur / 2) 
        && raquette2_posY > raquette_hauteur / 2) {  // Si la balle est plus haute que le centre
          raquette2_posY = raquette2_posY - 1;                          // Se déplacer vers le haut
    }
  }

Ici, nous avons ajouté la condition rand(0, 3) < 1.9. Donc dans le premier if selon la valeur du tirage effectué via le rand et que la balle aura franchi la moitié de l'écran alors le programme testera si la balle est plus basse que la raquette (de même quand la balle est au-dessus de la raquette). Cette méthode est un peu différente de celle utilisée dans le tuto du Pong en C++ et je vous invite à combiner les 2 en jouant sur les valeurs pour obtenir le comportement du bot (c'est aussi comme ça que l'on appelle l'IA du jeu) que vous affrontez dans ce jeu.

Mon but n'est pas de faire le jeu pour vous mais de vous guidez afin que vous fassiez celui que vous souhaitez... Modifiez, ajustez et modifiez encore jusqu'à ce que vous obteniez vraiment le comportement que vous dsiriez.

A vous de jouer !

Dans cet atelier, nous avons créé notre IA puis nous l'avons améliorée avec rand. Comme exercice de fin d'atelier, je vous propose d'implémenter une nouvelle fonctionnalité: changer de difficulté. En appuyant sur le bouton MENU, le joueur peut changer entre "facile" et "difficile".

A vous de continuer de perfectionner votre IA, ou de vous en inspirer pour faire une IA dans un autre jeu ;)

Steps