Intelligence artificielle – jeux vidéo – obstacle

 

Suite de l’article Intelligence artificiellejeux vidéo javascript – mouvement, : le passage ou contournement d’obstacles, dans le cas présent un mur.

Je vous conseille de lire les articles précédents dans l’ordre :
Intelligence artificiellejeux vidéo javascript – crash and turn
Initialiser le développement d’un jeu vidéo pour en reprendre le code.

// début du code isolé
(function () {
 var requestAnimId;
 
 var initialisation = function() {
  // le code de l'initialisation 
  requestAnimId = window.requestAnimationFrame(main); // premier appel de main au rafraichissement de la page
 }
 
 var main= function() {
  // le code du jeu
  requestAnimId = window.requestAnimationFrame(main); // rappel de main au prochain rafraichissement de la page
 }
 
 window.onload = initialisation; // appel de la fonction initialisation au chargement de la page
})();
// fin du code isolé

On intègre dans le code ci-dessus, le code développé jusqu’à présent :

// début du code isolé
(function () {
 var requestAnimId;
 
 var gameDiv;
 var gameCanvasContext;
  
 var enemy = {
  x : 0,
  y : 0,
  
  currentDirection : null,
  xStep : 0,
  yStep : 0,
  
  searchDirectionToTarget : function(enemy, player) {
   var xDirection;
   var yDirection;   
   var xDist;
   var yDist;
   
   if ( enemy.x > player.x ) {
    xDist = enemy.x - player.x;
    xDirection  = "W";
   } else {
    xDist = player.x - enemy.x;
    xDirection  = "E";
   }
   
   if ( enemy.y > player.y ) {
    yDist = enemy.y - player.y;
    yDirection  = "N";
   } else {
    yDist = player.y - enemy.y;
    yDirection  = "S";
   }   
   
   if ( xDist > yDist ) {
    this.currentDirection = xDirection;
   } else {
    this.currentDirection = yDirection;
   }
  },
  
  setDSPStepFromCurrentDirection : function() {
   switch ( this.currentDirection ) {
    case "E":
     this.xStep = 1;
     this.yStep = 0;
     break;
	  
    case "W":
     this.xStep = -1;
     this.yStep = 0;
     break;
	  
    case "N":
     this.xStep = 0;
     this.yStep = -1;
     break;
	  
    case "S":
     this.xStep = 0;
     this.yStep = 1;
     break;
	  
    default:
     this.xStep = 0;
     this.yStep = 0;	 
   }	
  },
  
  moveDSPToTarget : function() {
   this.x += this.xStep;
   this.y += this.yStep;
  }
 };
 
 var player = {
  x : 0,
  y : 0
 };
 
 var createCanvasContext = function(name, width, height, zindex, htmlElement, color) {
  var canvas = window.document.createElement("canvas");
  canvas.id = name;
  canvas.style.position = "absolute";
  if ( color != undefined )
   canvas.style.background = color;
  canvas.style.zIndex = zindex;
  canvas.width = width;
  canvas.height = height;
  if ( htmlElement != undefined )
   htmlElement.appendChild(canvas.getContext('2d').canvas);
  return canvas.getContext('2d');
 }
 
 var showSquare = function(canvasContext,color,x,y) {
  canvasContext.beginPath();
  canvasContext.rect(x*10,y*10,10,10);
  canvasContext.fillStyle = color;
  canvasContext.fill();
  canvasContext.stroke();
 }
 
 var showWall = function(canvasContext,x,y) {
  showSquare(canvasContext,"grey",x,y);
 }
 
 var showPlayer = function(canvasContext,x,y) {
  showSquare(canvasContext,"yellow",x,y);
 }
 
 var showEnemy = function(canvasContext,x,y) {
  showSquare(canvasContext,"red",x,y);
 }
 
 var initGameGrid = function() {
  for(var i in walls)  {
   var line = walls[i];
   var element = line.split("");
   for(var j in element)  {
    if ( element[ j ] == "#" ) {
     showWall(gameCanvasContext,j,i);
    } else if ( element[ j ] == "E" ) {
     showEnemy(gameCanvasContext,j,i);
     enemy.x = parseInt(j);
     enemy.y = parseInt(i);
     element[ j ] = " ";
    } else if ( element[ j ] == "P" ) {
     showPlayer(gameCanvasContext,j,i);
     player.x = parseInt(j);
     player.y = parseInt(i);
     element[ j ] = " ";
    }
   }
  }
 }
 
 var walls = {
  0:  "                    ",
  1:  "              #     ",
  2:  "              #     ",
  3:  "                    ",
  4:  "                    ",
  5:  "                    ",
  6:  "              #     ",
  7:  "         #    #     ",
  8:  "         #    #     ",
  9:  "  E           #  P  ",
  10: "              #     ",
  11: "         #    #     ",
  12: "         #    #     ",
  13: "         #    #     ",
  14: "                    ",
  15: "                    ",
  16: "                    ",
  17: "              #     ",
  18: "              #     ",
  19: "                    "
 } 
 
 var initialisation = function() {
  // code init
  gameDiv = document.getElementById("gameDiv");
  gameCanvasContext = createCanvasContext("game",200,200,1,gameDiv,"#000000");

  // read and display game grid
  initGameGrid();
  enemy.searchDirectionToTarget(enemy,player);
  enemy.setDSPStepFromCurrentDirection();
  enemy.moveDSPToTarget();
  showEnemy(gameCanvasContext,enemy.x,enemy.y);
  showPlayer(gameCanvasContext,player.x,player.y);

  requestAnimId = window.requestAnimationFrame(main);
 }
 
 var main = function() {
  // main code
  requestAnimId = window.requestAnimationFrame(main);
 }
 
 window.onload = initialisation; // appel de la fonction initialisation au chargement de la page
})();

 

Mouvement animé

Vous allez modifier le code existant de façon à donner l’impression de mouvement de l’enemy dans le labyrinthe.

Vous devez donc :
– effacer le canvas;
– afficher le labyrinthe;
– rechercher la meilleure direction à prendre pour l’enemy (N, S, E, W);
– déplacer l’enemy;
– afficher l’enemy;
– afficher le player.

Tout ça dans une boucle infinie.

Première chose, la fonction d’effacement du canvas html5 :

var clearCanvas = function(name, canvasContext) {
 var canvas = document.getElementById(name);
 canvasContext.clearRect(0, 0, canvas.width, canvas.height);
}

Ensuite une fonction displayWall qui affiche uniquement les murs, très inspirée de la fonction initGameGrid :

var displayWall = function() {
 for(var i in walls)  {
  var line = walls[i];
  var element = line.split("");
  for(var j in element)  {
   if ( element[ j ] == "#" ) {
    showWall(gameCanvasContext,j,i);
   } 
  }
 }
}

Puis par le biais de la fonction main, on appel en boucle les 2 fonctions précédentes ajoutées aux fonctions de recherche de direction, de choix de direction, de mouvement et d’affichage :

 var main = function() {
  // main code
  clearCanvas("game", gameCanvasContext);
  displayWall();
  enemy.searchDirectionToTarget(enemy,player);
  enemy.setDSPStepFromCurrentDirection();
  enemy.moveDSPToTarget();
  showEnemy(gameCanvasContext,enemy.x,enemy.y);
  showPlayer(gameCanvasContext,player.x,player.y);
  requestAnimId = window.requestAnimationFrame(main);
 }

Je vous invite à alléger la fonction initialisation pour éviter un double appel inutile à un même code, le double appel concernant :

 enemy.searchDirectionToTarget(enemy,player);
 enemy.setDSPStepFromCurrentDirection();
 enemy.moveDSPToTarget();
 showEnemy(gameCanvasContext,enemy.x,enemy.y);
 showPlayer(gameCanvasContext,player.x,player.y);

La fonction initialisation devient :

var initialisation = function() {
 // code init
 gameDiv = document.getElementById("gameDiv");
 gameCanvasContext = createCanvasContext("game",200,200,1,gameDiv,"#000000");
 // read and display game grid
 initGameGrid();
 requestAnimId = window.requestAnimationFrame(main);
}

Si tout se passe bien, vous devriez voir un carré rouge (enemy) traversant l’écran de la gauche vers la droite.

Remarquez, tel un fantôme, enemy traverse les murs.

 

Franchissement des obstacles

Voilà le gros du travail qui consiste à franchir un obstacle en s’approchant le plus possible du joueur : plus simple à dire qu’à faire.

Jusqu’à présent, l’enemy se déplace verticalement et horizontalement dans la direction du joueur.

Pour éviter qu’il ne traverse les murs, l’enemy doit vérifier avant d’avancer s’il y a un mur devant lui. Puis si c’est le cas, selon la position du joueur, se tourner à droite ou à gauche et aller droit devant lui.

Il faut donc créer une fonction qui permet à l’enemy de vérifier s’il y a un mur ou pas devant lui.

En considérant que les bords du labyrinthe sont infranchissables, ils peuvent être apparentés à des murs.

La fonction doit donc aussi prendre en compte les bords du labyrinthe.

Cette méthode utilise la direction qu’a pris l’enemy : on verifie seulement la présence d’un mur devant lui.

Cette méthode renvoie la valeur :
– vrai (true) s’il y a un mur ou le bord du labyrinthe devant;
– faux (false) dans le cas contraire.

Le code de la nouvelle méthode rattachée à l’objet enemy :

wallDSPInFrontOf : function() {
 var returnValue = false;
 switch ( this.currentDirection ) {
  case "E":
   if (this.x+1 >=20 || walls[this.y][this.x+1] == "#")
    returnValue = true;
   break;
	  
  case "W":
   if (this.x-1 <0 || walls[this.y][this.x-1] == "#")
    returnValue = true;
   break;
	  
  case "N":
   if (this.y-1 <0 || walls[this.y-1][this.x] == "#")
    returnValue = true;
   break;
	  
  case "S":
   if (this.y+1 >=20 || walls[this.y+1][this.x] == "#")
    returnValue = true;
   break;
 }
 return returnValue;
}

Maintenant que l’on sait s’il y a un mur devant, l’enemy doit se tourner vers la droite ou la gauche en fonction de la position du joueur.

Pour simplifier, l’enemy doit rechercher une nouvelle direction en excluant la direction courante. Ceci nécessite donc de modifier la méthode searchDirectionToTarget afin qu’elle ne renvoie pas la direction du mur.

Il suffit de conditionner le changement de direction en fonction de la direction courante :
– si la direction courante est différente W et E, le choix de la direction est N ou S;
– si la direction courante est différente N et S, le choix de la direction est W ou E.

Le choix de la direction garde la nécessité de se rapprocher de la cible.

Le code :

searchDirectionToTarget : function(enemy, player) {
 var xDirection;
 var yDirection;   
 var xDist;
 var yDist;
   
 if ( this.currentDirection != "E" && this.currentDirection != "W" ) {
  if ( enemy.x > player.x ) {
   xDist = enemy.x - player.x;
   xDirection  = "W";
  } else {
   xDist = player.x - enemy.x;
   xDirection  = "E";
  }
 }
   
 if ( this.currentDirection != "N" && this.currentDirection != "S" ) {
  if ( enemy.y > player.y ) {
   yDist = enemy.y - player.y;
   yDirection  = "N";
  } else {
   yDist = player.y - enemy.y;
   yDirection  = "S";
  }   
 }
 
 if ( xDist > yDist ) {
  this.currentDirection = xDirection != undefined ? xDirection : yDirection;
 } else {
  this.currentDirection = yDirection != undefined ? yDirection : xDirection;
 }
}

Les obstacles sont maintenant pris en compte, mais l’enemy tourne un peu en rond. Il doit maintenant atteindre sa cible : ce sera l’objet du 4ème et dernier article sur l’algorithme crash and turn.

Pour la suite, c’est ici Intelligence artificielle jeux vidéo – atteindre la cible
 

Des remarques, des améliorations, des idées, des coquilles : dites le. Faites savoir si cet article vous a été utile par un commentaire ou les réseaux sociaux.

Le code complet :

<html>
 <body>
  <div id="gameDiv" style="margin-left:auto;margin-right:auto;width:1168px;height:740px;">
  </div>
 </body>
 <script>
(function() {
 var requestAnimId;
 
 var gameDiv;
 var gameCanvasContext;
 
 var enemy = {
  x : 0,
  y : 0,
  
  currentDirection : undefined,
  xStep : 0,
  yStep : 0,
 
  searchDirectionToTarget : function(enemy, player) {
   var xDirection;
   var yDirection;   
   var xDist;
   var yDist;
   
   if ( this.currentDirection != "E" && this.currentDirection != "W" ) {
    if ( enemy.x > player.x ) {
     xDist = enemy.x - player.x;
     xDirection  = "W";
    } else {
     xDist = player.x - enemy.x;
     xDirection  = "E";
    }
   }
   
   if ( this.currentDirection != "N" && this.currentDirection != "S" ) {
    if ( enemy.y > player.y ) {
     yDist = enemy.y - player.y;
     yDirection  = "N";
    } else {
     yDist = player.y - enemy.y;
     yDirection  = "S";
    }   
   }
   
   if ( xDist > yDist ) {
    this.currentDirection = xDirection != undefined ? xDirection : yDirection;
   } else {
    this.currentDirection = yDirection != undefined ? yDirection : xDirection;
   }
 },
 
  setDSPStepFromCurrentDirection : function() {
   switch ( this.currentDirection ) {
    case "E":
     this.xStep = 1;
     this.yStep = 0;
     break;
 
    case "W":
     this.xStep = -1;
     this.yStep = 0;
     break;
 
    case "N":
     this.xStep = 0;
     this.yStep = -1;
     break;
 
    case "S":
     this.xStep = 0;
     this.yStep = 1;
     break;
 
    default:
     this.xStep = 0;
     this.yStep = 0;	 
   }	
  },
 
  moveDSPToTarget : function() {
    this.x += this.xStep;
    this.y += this.yStep;
  },
  
  wallDSPInFrontOf : function() {
    var returnValue = false;
    switch ( this.currentDirection ) {
	 case "E":
	  if (this.x+1 >=20 || walls[this.y][this.x+1] == "#")
		returnValue = true;
	  break;
	  
	 case "W":
	  if (this.x-1 <0 || walls[this.y][this.x-1] == "#")
		returnValue = true;
      break;
	  
	 case "N":
	  if (this.y-1 <0 || walls[this.y-1][this.x] == "#")
		returnValue = true;
      break;
	  
	 case "S":
	  if (this.y+1 >=20 || walls[this.y+1][this.x] == "#")
		returnValue = true;
      break;
	}
	return returnValue;
  }
 
 };
 
 var player = {
   x : 0,
   y : 0
 };
 
 var createCanvasContext = function(name, width, height, zindex, htmlElement, color) {
  var canvas = window.document.createElement("canvas");
  canvas.id = name;
  canvas.style.position = "absolute";
  if ( color != undefined )
   canvas.style.background = color;
  canvas.style.zIndex = zindex;
  canvas.width = width;
  canvas.height = height;
  if ( htmlElement != undefined )
   htmlElement.appendChild(canvas.getContext('2d').canvas);
  return canvas.getContext('2d');
 }
 
 var clearCanvas = function(name, canvasContext) {
  var canvas = document.getElementById(name);
  canvasContext.clearRect(0, 0, canvas.width, canvas.height);
 }
 
 var showSquare = function(canvasContext,color,x,y) {
  canvasContext.beginPath();
  canvasContext.rect(x*10,y*10,10,10);
  canvasContext.fillStyle = color;
  canvasContext.fill();
  canvasContext.stroke();
 }
 
 var showWall = function(canvasContext,x,y) {
  showSquare(canvasContext,"grey",x,y);
 }
 
 var showPlayer = function(canvasContext,x,y) {
  showSquare(canvasContext,"yellow",x,y);
 }
 
 var showEnemy = function(canvasContext,x,y) {
  showSquare(canvasContext,"red",x,y);
 }
 
 var initGameGrid = function() {
  for(var i in walls)  {
   var line = walls[i];
   var element = line.split("");
   for(var j in element)  {
    if ( element[ j ] == "#" ) {
     showWall(gameCanvasContext,j,i);
    } else if ( element[ j ] == "E" ) {
     showEnemy(gameCanvasContext,j,i);
	 enemy.x = parseInt(j);
	 enemy.y = parseInt(i);
     element[ j ] = " ";
    } else if ( element[ j ] == "P" ) {
     showPlayer(gameCanvasContext,j,i);
	 player.x = parseInt(j);
	 player.y = parseInt(i);
     element[ j ] = " ";
    }
   }
  }
 }

 var displayWall = function() {
  for(var i in walls)  {
   var line = walls[i];
   var element = line.split("");
   for(var j in element)  {
    if ( element[ j ] == "#" ) {
     showWall(gameCanvasContext,j,i);
    } 
   }
  }
 }
 
 var walls = {
  0:  "                    ",
  1:  "              #     ",
  2:  "              #     ",
  3:  "                    ",
  4:  "                    ",
  5:  "                    ",
  6:  "              #     ",
  7:  "         #    #     ",
  8:  "         #    #     ",
  9:  "  E           #  P  ",
  10: "              #     ",
  11: "         #    #     ",
  12: "         #    #     ",
  13: "         #    #     ",
  14: "                    ",
  15: "                    ",
  16: "                    ",
  17: "              #     ",
  18: "              #     ",
  19: "                    "
 }
 
  var initialisation = function() {
  // code init
  gameDiv = document.getElementById("gameDiv");
  gameCanvasContext = createCanvasContext("game",200,200,1,gameDiv,"#000000");
  
  // read and display game grid
  initGameGrid();
  
  enemy.searchDirectionToTarget(enemy,player);
  
  requestAnimId = window.requestAnimationFrame(main);
 }
 
 var main = function() {

  // main code
  clearCanvas("game", gameCanvasContext);
  displayWall();
  
  if ( enemy.wallDSPInFrontOf() ) {
   enemy.searchDirectionToTarget(enemy,player);
  }
  
  enemy.setDSPStepFromCurrentDirection();
  enemy.moveDSPToTarget();

  showEnemy(gameCanvasContext,enemy.x,enemy.y);
  showPlayer(gameCanvasContext,player.x,player.y);
  
  requestAnimId = window.requestAnimationFrame(main);
 }
 
 window.onload = initialisation; // appel de la fonction initialisation au chargement de la page
})();
 </script>
</html>

 

Posté dans html5Taggé crash and turn, intelligence artificielle, intelligence artificielle jeu vidéo, jeu vidéo javascript  |  2 commentaires

2 réponses à "Intelligence artificielle – jeux vidéo – obstacle"

Répondre