Aller au contenu principal

Les fantômes

Objectif:

Découvrir les bases de l'intelligence artificielle (IA) en introduisant un comportement simple pour les fantômes dans notre jeu Pacman.

Notions théoriques

L'intelligence artificielle dans les jeux peut sembler complexe, mais elle débute souvent avec des règles simples.

Dans notre cas, un fantôme se déplacera aléatoirement dans le labyrinthe.

Pour cela, nous utiliserons la fonction Math.random() pour choisir une direction parmi les possibilités autorisées (haut, bas, gauche, droite) en évitant les murs.

remarque

Ce comportement représente une IA très basique, mais il constitue un bon point de départ.

Exemple pratique

Dans notre jeu de Pacman, en utilisant la fonction Math.random(), nous pourrons déplacer un fantôme de façon aléatoire.

Voici un exemple d'une fonction choisirDirectionAleatoire qui :

  • contient un tableau directionsPossibles avec les 4 directions possibles ;
  • tire au sort une des 4 directions ;
  • et retourne la direction tirée au sort.
function choisirDirectionAleatoire(positionFantome) {
let directionsPossibles = [];
// Vérifier chaque direction pour s'assurer qu'elle ne mène pas à un mur
if (labyrinthe[positionFantome.y - 1][positionFantome.x] === 0) {
directionsPossibles.push('ArrowUp');
}
if (labyrinthe[positionFantome.y + 1][positionFantome.x] === 0) {
directionsPossibles.push('ArrowDown');
}
if (labyrinthe[positionFantome.y][positionFantome.x - 1] === 0) {
directionsPossibles.push('ArrowLeft');
}
if (labyrinthe[positionFantome.y][positionFantome.x + 1] === 0) {
directionsPossibles.push('ArrowRight');
}
// Choisir une direction aléatoire parmi les possibilités
return directionsPossibles[Math.floor(Math.random() * directionsPossibles.length)];
}

Ainsi :

  • directionsPossibles.length est égale à 4
  • directionsPossibles[0] est égale à ArrowUp c'est à dire la touche de flèche de direction Haut
  • directionsPossibles[1] est égale à ArrowDown c'est à dire la touche de flèche de direction Bas
  • directionsPossibles[2] est égale à ArrowLeft c'est à dire la touche de flèche de direction Gauche
  • directionsPossibles[3] est égale à ArrowRight c'est à dire la touche de flèche de direction Droite

Quelques méthodes à connaître

  • Math.random(): Renvoie un nombre flottant pseudo-aléatoire compris dans l'intervalle [0, 1].
  • Math.floor(): Arrondit à l'entier inférieur.

Test de mémorisation/compréhension


Quelle fonction est utilisée pour générer des nombres aléatoires en JavaScript ?


Quelle méthode est utilisée pour arrondir un nombre à l'entier le plus proche en dessous ?


Quel est le rôle de l'IA dans notre jeu Pacman pour le moment ?


Quelle structure de données utiliserions-nous pour stocker les directions possibles pour le fantôme ?


Que doit vérifier l'IA avant de choisir une direction pour le fantôme ?



TP pour réfléchir et résoudre des problèmes

Mission 1 - Afficher un fantôme

Votre première mission consiste à ajouter un fantôme dans le jeu (qui doit se déplacer de manière aléatoire).

Le fantôme :

  • ne doit pas pouvoir traverser les murs ;
  • et doit changer de direction lorsqu'il atteint une intersection dans le labyrinthe.

Voici un exemple de code qui permet à un fantôme de se déplacer dans le labyrinthe de façon aléatoire, sans traverser les murs :

  1. Créez un nouveau fichier Fantome.js à la racine de votre projet (à côté de App.js).

    // Fantome.js
  2. Ajoutez l'import de React et du composant View dans votre fichier Fantome.js :

    import React from 'react';
    import { View } from 'react-native';
  3. Créez une fonction Fantome dans le fichier Fantome.js :


    ...

    function Fantome(props) {

    }
  4. Complétez le code de votre fonction Fantome, afin de définir les attributs et le style d'affichage de votre fantôme :


    ...

    function Fantome(props) {
    const position = props.position;
    const styleFantome = {
    width: tailleCase,
    height: tailleCase,
    borderRadius: tailleCase / 2,
    backgroundColor: 'red', // Les fantômes sont souvent représentés en rouge
    position: 'absolute',
    left: position.x * tailleCase,
    top: position.y * tailleCase,
    transition: 'left 0.5s, top 0.5s', // Transition douce pour le mouvement
    };

    return <View style={styleFantome} />;
    }
  5. Ajoutez l'export de la fonction Fantome à la fin du fichier Fantome.js :


    ...

    export default Fantome;

  1. Créez un nouveau fichier DeplacementFantome.js à la racine de votre projet (à côté de App.js).

    // DeplacementFantome.js
  2. Créez une fonction choisirDirectionAleatoire dans le fichier DeplacementFantome.js :


    ...

    function choisirDirectionAleatoire(position, labyrinthe) {

    }
  3. Complétez le code de votre fonction choisirDirectionAleatoire, afin de choisir une direction aléatoire parmi les 4 possibilités :


    ...

    function choisirDirectionAleatoire(position, labyrinthe) {
    var directionsPossibles = [];
    // Vérifier chaque direction pour s'assurer qu'elle ne mène pas à un mur
    if (labyrinthe[position.y - 1][position.x] === 0) {
    directionsPossibles.push({ x: 0, y: -1 });
    }
    if (labyrinthe[position.y + 1][position.x] === 0) {
    directionsPossibles.push({ x: 0, y: 1 });
    }
    if (labyrinthe[position.y][position.x - 1] === 0) {
    directionsPossibles.push({ x: -1, y: 0 });
    }
    if (labyrinthe[position.y][position.x + 1] === 0) {
    directionsPossibles.push({ x: 1, y: 0 });
    }
    // Choisir une direction aléatoire parmi les possibilités
    return directionsPossibles[Math.floor(Math.random() * directionsPossibles.length)];
    }
  4. Créez une fonction deplacerFantome dans le fichier DeplacementFantome.js :


    ...

    function deplacerFantome(fantome1Position, labyrinthe, setFantome1Position) {

    }
  5. Complétez le code de votre fonction deplacerFantome, afin de déplacer votre fantôme, si c'est possible :


    ...

    function deplacerFantome(fantome1Position, labyrinthe, setFantome1Position) {
    setFantome1Position(function (prevPosition) {
    var direction = choisirDirectionAleatoire(prevPosition, labyrinthe);
    if (!direction) {
    return prevPosition; // Si aucune direction n'est possible, on ne bouge pas
    }
    var nouvellePosition = { x: prevPosition.x + direction.x, y: prevPosition.y + direction.y };
    return nouvellePosition;
    });
    }
  6. Ajoutez l'export de la fonction deplacerFantome à la fin du fichier DeplacementFantome.js :


    ...

    export default deplacerFantome;

  1. Modifiez le code du fichier App.js pour qu'il ressemble à ceci :

    // App.js

    import React, { useState, useEffect } from "react";
    import { StyleSheet, View } from "react-native";
    import genererLabyrinthe from "./GenererationLabyrinthe";
    import Labyrinthe from "./Labyrinthe";
    import Pacman from "./Pacman";
    import deplacerPacman from "./DeplacementPacman";
    import Fantome from "./Fantome";
    import deplacerFantome from "./DeplacementFantome";

    const labyrinthe = genererLabyrinthe(11, 11);

    export default function App() {
    const [pacmanPosition, setPacmanPosition] = useState({ x: 1, y: 1 }); // Démarre en position x=1 et y=1
    const [fantome1Position, setFantome1Position] = useState({ x: 3, y: 5 }); // Démarre en position x=5 et y=5

    useEffect(() => {
    function handleKeyDown(event) {
    deplacerPacman(event.key, pacmanPosition, labyrinthe, setPacmanPosition);
    }

    document.addEventListener("keydown", handleKeyDown);

    return () => document.removeEventListener("keydown", handleKeyDown);
    }, [pacmanPosition]);

    useEffect(() => {
    const intervalId = setInterval(() => {
    deplacerFantome(fantome1Position, labyrinthe, setFantome1Position);
    }, 1000); // Se déplace toutes les secondes (1000 millisecondes)

    return () => clearInterval(intervalId);
    // Ajoutez fantome1Position à la liste des dépendances pour réagir aux changements de position
    }, [fantome1Position]);

    return (
    <View style={styles.container}>
    <View>
    <Labyrinthe labyrinthe={labyrinthe} />
    <Pacman position={pacmanPosition} />
    <Fantome position={fantome1Position} />
    </View>
    </View>
    );
    }

    const styles = StyleSheet.create({
    container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
    },
    });
    A quoi sert l'appel de la fonction clearInterval ?

    La fonction clearInterval est utilisé pour nettoyer et arrêter l'intervalle créé par setInterval lorsque le composant est démonté ou lorsque les dépendances de l'effet changent, afin de s'assurer que les fonctions ne continuent pas à s'exécuter inutilement, pour ne pas bloquer l'exécution de l'application.

Vous devriez obtenir quelque chose qui ressemble à cela :

Une solution

Mission 2 - Afficher un 2ème fantôme

Votre nouvelle mission consiste à ajouter un 2ème fantôme.

Maintenant que vous avez réussi à afficher un premier fantôme, vous êtes capable d'ajouter un deuxième fantôme, en ajoutant quelques lignes de code.

Une solution