Jeu de Tic Tac Toe multijoueurs par navigateur en React
Published: 2024-03-10 22:51:34

**Ce billet, issu de nos archives, vous guidera à travers les étapes pour créer un jeu de Tic Tac Toe en React mais attention, les versions des bibliothèques utilisées ne sont plus les plus récentes.** En particulier, cet article utilise la version 1 de notre [SDK React](https://www.pubnub.com/docs/sdks/react) mais toutes les étapes et les principes énumérés ci-dessous sont encore valables aujourd'hui.

Le morpion est un [jeu d'](https://www.pubnub.com/solutions/gaming/)enfance par excellence. Tout ce dont il a besoin, c'est d'un objet sur lequel écrire et d'un objet avec lequel écrire. Mais que se passe-t-il si vous voulez jouer avec quelqu'un qui se trouve dans un autre endroit ? Dans ce cas, vous devez utiliser une application qui vous connecte au jeu avec un autre joueur.

L'application doit offrir une expérience en temps réel, de sorte que chaque mouvement que vous faites soit vu instantanément par l'autre joueur et vice versa. Si l'application n'offre pas cette expérience, vous et de nombreuses personnes ne l'utiliserez probablement plus.

Alors, comment un développeur peut-il offrir une [expérience connectée](https://www.pubnub.com/blog/connected-shared-experiences-a-developers-guide/) où les joueurs peuvent jouer au morpion, ou à n'importe quel jeu, où qu'ils se trouvent dans le monde ?

Concepts des jeux multijoueurs en temps réel
--------------------------------------------

Il existe plusieurs façons de fournir l'infrastructure en temps réel pour les jeux multijoueurs. Vous pouvez construire votre propre infrastructure en utilisant des technologies et des protocoles open-source tels que [Socket.IO](https://www.pubnub.com/learn/glossary/what-is-socketio/), [SignalR](https://pubnub.com/guides/what-is-signalr/) ou [WebSockets](https://www.pubnub.com/learn/glossary/what-is-websocket/).

Bien que cette voie puisse sembler attrayante, vous rencontrerez plusieurs problèmes, dont celui de l'évolutivité. Il n'est pas difficile de gérer 100 utilisateurs, mais comment gérer 100 000 utilisateurs ou plus ? Outre les problèmes d'infrastructure, vous devez encore vous préoccuper de la maintenance de votre jeu.

En fin de compte, la seule chose qui compte est d'offrir une bonne expérience aux joueurs de votre jeu. Mais comment résoudre le problème de l'infrastructure ? C'est là que PubNub intervient.

PubNub fournit l'infrastructure en temps réel pour alimenter n'importe quelle application grâce à son [réseau](https://pubnub.com/products/pubnub-platform/) mondial [de flux de données](https://pubnub.com/products/pubnub-platform/). Avec plus de [70+ SDK,](https://pubnub.com/docs/) y compris les langages de programmation les plus populaires, PubNub simplifie l'envoi et la réception de messages vers n'importe quel appareil en **moins de 100 ms**. Il est sécurisé, évolutif et fiable, de sorte que vous n'avez pas à vous soucier de la création et de la maintenance de votre propre infrastructure.

Afin de montrer à quel point il est facile de développer un jeu multijoueur avec PubNub, nous allons construire un simple jeu React tic tac toe en utilisant le [SDK React PubNub](https://pubnub.com/docs/sdks/javascript/react). Dans ce jeu, deux joueurs se connecteront à un canal de jeu unique où ils joueront l'un contre l'autre. Chaque mouvement effectué par un joueur sera publié sur le canal afin de mettre à jour le tableau de l'autre joueur en temps réel.

Aperçu de l'application
-----------------------

Voici à quoi ressemblera notre application une fois que nous l'aurons terminée.

![Screen shot of the React Tic Tac Toe Game](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jpy/5d2874d8b7c577a9ce9a5acc6ade755f/React-tic-tac-toe-overview.png "Screen shot of the React Tic Tac Toe Game")Les joueurs rejoignent d'abord le lobby où ils peuvent créer un canal ou rejoindre un canal. Si le joueur crée un canal, il obtient un _identifiant de salle à_ partager avec un autre joueur. Le joueur qui a créé le canal devient le _joueur X_ et fera le premier pas lorsque le jeu commencera.

[![Create a room channel](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jpz/d6a1b08ddc0924624f35e779c0c0059a/create-room.gif "Create a room channel")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jpz/d6a1b08ddc0924624f35e779c0c0059a/create-room.gif)

Le joueur qui rejoint un canal avec l'_identifiant de salle_ qui lui a été attribué devient le _joueur O_. Les joueurs ne peuvent rejoindre un canal que s'il y a une autre personne dans le canal. S'il y a plus d'une personne, un jeu est en cours pour ce canal et le joueur ne pourra pas le rejoindre. Le jeu commence lorsqu'il y a deux joueurs dans le canal.

[![Join the room channel](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq0/96a0ccd6b75f19b211747f353b27bd39/join-channel.gif "Join the room channel")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq0/96a0ccd6b75f19b211747f353b27bd39/join-channel.gif)

À la fin du jeu, le score du gagnant est augmenté d'un point. Si le jeu se termine par une égalité, aucun des deux joueurs ne se voit attribuer un point. Une fenêtre modale s'affiche pour demander au _joueur X_ de commencer un nouveau tour ou de mettre fin au jeu. Si le _joueur X_ continue le jeu, le tableau est réinitialisé pour le nouveau tour. Sinon, le jeu se termine et les deux joueurs retournent dans le hall.

[![Exit to lobby](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq1/f475cc1aeed8a4184348569634ed968d/exit-to-lobby-1.gif " Exit to lobby")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq1/f475cc1aeed8a4184348569634ed968d/exit-to-lobby-1.gif)

Mise en place du lobby
----------------------

Avant de configurer le lobby, créez un compte PubNub gratuit pour obtenir vos clés API Pub/Sub gratuites à partir du [tableau de bord d'administration de PubNub](https://admin.pubnub.com).

Une fois que vous avez obtenu vos clés, insérez-les dans le constructeur d'**App.js**.

```js
// App.js
import React, { Component } from 'react';
import Game from './Game';
import Board from './Board';
import PubNubReact from 'pubnub-react';
import Swal from "sweetalert2";
import shortid  from 'shortid';
import './Game.css';

class App extends Component {
  constructor(props) {
    super(props);
    // REPLACE with your keys
    this.pubnub = new PubNubReact({
      publishKey: "YOUR_PUBLISH_KEY_HERE",
      subscribeKey: "YOUR_SUBSCRIBE_KEY_HERE"
    });

    this.state = {
      piece: '', // X or O
      isPlaying: false, // Set to true when 2 players are in a channel
      isRoomCreator: false,
      isDisabled: false,
      myTurn: false,
    };

    this.lobbyChannel = null; // Lobby channel
    this.gameChannel = null; // Game channel
    this.roomId = null; // Unique id when player creates a room
    this.pubnub.init(this); // Initialize PubNub
  }

  render() {
    return ();
    }
  }

  export default App;
```

C'est également dans le constructeur que les objets et les variables d'état sont initialisés. Nous reviendrons sur les objets et les variables lorsqu'ils apparaîtront dans le fichier. Enfin, nous avons initialisé PubNub à la fin du constructeur.

Dans la méthode de **rendu** et dans la déclaration de **retour**, nous ajoutons le balisage pour le composant Lobby.

```js
return (
    <div>
      <div className="title">
        <p> React Tic Tac Toe </p>
      </div>

      {
        !this.state.isPlaying &&
        <div className="game">
          <div className="board">
            <Board
                squares={0}
                onClick={index => null}
              />

            <div className="button-container">
              <button
                className="create-button "
                disabled={this.state.isDisabled}
                onClick={(e) => this.onPressCreate()}
                > Create
              </button>
              <button
                className="join-button"
                onClick={(e) => this.onPressJoin()}
                > Join
              </button>
            </div>

          </div>
        </div>
      }

      {
        this.state.isPlaying &&
        <Game
          pubnub={this.pubnub}
          gameChannel={this.gameChannel}
          piece={this.state.piece}
          isRoomCreator={this.state.isRoomCreator}
          myTurn={this.state.myTurn}
          xUsername={this.state.xUsername}
          oUsername={this.state.oUsername}
          endGame={this.endGame}
        />
      }
    </div>
);
```

Le composant Lobby se compose d'un titre, d'un tableau tic tac toe vide (rien ne se passe si le joueur appuie sur les carrés) et des boutons "Create_"_ et "Join_"_. Ce composant n'est affiché que si la valeur d'état _isPlaying_ est fausse. Si elle vaut true, le jeu a commencé et le composant est remplacé par le composant Game, que nous verrons dans la deuxième partie du didacticiel.

Le composant Board fait également partie du composant Lobby. Le composant [Square](https://github.com/PubNubDevelopers/react-tictactoe/blob/master/src/Square.js) se trouve à l'intérieur du composant [Board](https://github.com/PubNubDevelopers/react-tictactoe/blob/master/src/Board.js). Nous n'entrerons pas dans les détails de ces deux composants afin de nous concentrer sur les composants Lobby et Game.

Lorsque le joueur appuie sur le bouton "Créer", le bouton est désactivé afin que le joueur ne puisse pas créer plusieurs canaux. Le bouton "Rejoindre" n'est pas désactivé, au cas où le joueur déciderait de rejoindre un canal à la place. Lorsque le joueur appuie sur le bouton "Create", la méthode **onPressCreate()** est appelée.

### Créer un canal

La première chose que nous faisons dans **onPressCreate()** est de générer un identifiant aléatoire tronqué à 5 caractères. Pour ce faire, nous utilisons [**shortid()**](https://www.npmjs.com/package/shortid). Nous ajoutons cette chaîne à '_tictactoelobby-'_, qui sera le canal de lobby unique auquel les joueurs s'abonneront.

```js
// Create a room channel
onPressCreate = (e) => {
  // Create a random name for the channel
  this.roomId = shortid.generate().substring(0,5);
  this.lobbyChannel = 'tictactoelobby--' + this.roomId; // Lobby channel name

  this.pubnub.subscribe({
    channels: [this.lobbyChannel],
    withPresence: true // Checks the number of people in the channel
  });
}
```

Afin d'empêcher plus de deux joueurs de rejoindre un canal donné, nous utilisons [Presence](https://pubnub.com/docs/sdks/javascript/react). Nous verrons plus loin la logique de vérification de l'occupation du canal.

Une fois que le joueur s'est abonné au canal du lobby, une fenêtre modale s'affiche avec l'identifiant de la salle afin qu'un autre joueur puisse rejoindre ce canal.

[![Share the room id](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq2/2e171d056766c2d974cb5b9f3001b5a3/share-roomid-1.png "Share the room id")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq2/2e171d056766c2d974cb5b9f3001b5a3/share-roomid-1.png)

Cette modale, et toutes les modales utilisées dans cette application, sont créées par [**SweetAlert2**](https://sweetalert2.github.io/#download) pour remplacer les fenêtres contextuelles alert() par défaut de JavaScript.

```js
// Inside of onPressCreate()
// Modal
Swal.fire({
  position: 'top',
  allowOutsideClick: false,
  title: 'Share this room ID with your friend',
  text: this.roomId,
  width: 275,
  padding: '0.7em',
  // Custom CSS to change the size of the modal
  customClass: {
      heightAuto: false,
      title: 'title-class',
      popup: 'popup-class',
      confirmButton: 'button-class'
  }
})
```

A la fin de **onPressCreate()**, nous changeons les valeurs d'état pour refléter le nouvel état de l'application.

```js
this.setState({
  piece: 'X',
  isRoomCreator: true,
  isDisabled: true, // Disable the 'Create' button
  myTurn: true, // Player X makes the 1st move
});
```

Une fois que le joueur a créé une salle, il doit attendre qu'un autre joueur la rejoigne. Examinons la logique qui permet de rejoindre une salle.

### Rejoindre un canal

Lorsqu'un joueur appuie sur le bouton " Join ", la **fonction onPressJoin()** est appelée. Une fenêtre modale s'affiche et demande au joueur d'entrer l'_identifiant de la salle_ dans le champ de saisie.

[![Enter the room id](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq3/97e46c029a2583ac018ab14f44ce8572/enter-roomid.png "Enter the room id")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq3/97e46c029a2583ac018ab14f44ce8572/enter-roomid.png)

Si le joueur saisit l'_identifiant de_ la salle et appuie sur le bouton "OK", la méthode **joinRoom(value)** est appelée, la **valeur** étant l'_identifiant de la salle_. Cette méthode n'est pas appelée si le champ de saisie est vide ou si le joueur appuie sur le bouton "Annuler".

```js
// The 'Join' button was pressed
onPressJoin = (e) => {
  Swal.fire({
    position: 'top',
    input: 'text',
    allowOutsideClick: false,
    inputPlaceholder: 'Enter the room id',
    showCancelButton: true,
    confirmButtonColor: 'rgb(208,33,41)',
    confirmButtonText: 'OK',
    width: 275,
    padding: '0.7em',
    customClass: {
      heightAuto: false,
      popup: 'popup-class',
      confirmButton: 'join-button-class',
      cancelButton: 'join-button-class'
    }
  }).then((result) => {
    // Check if the user typed a value in the input field
    if(result.value){
      this.joinRoom(result.value);
    }
  })
}
```

La première chose que nous faisons dans **joinRoom()** est d'ajouter la **valeur** à '_tictactoelobby-',_comme nous l'avons fait dans **onPressCreate()**.

```js
// Join a room channel
joinRoom = (value) => {
  this.roomId = value;
  this.lobbyChannel = 'tictactoelobby--' + this.roomId;
}
```

Avant que le joueur ne s'abonne au canal de lobby, nous devons vérifier l'occupation totale du canal en utilisant [hereNow()](https://pubnub.com/docs/sdks/javascript/react). Si l'occupation totale est inférieure à 2, le joueur peut s'abonner avec succès au canal de lobby.

```js
// Check the number of people in the channel
this.pubnub.hereNow({
  channels: [this.lobbyChannel],
}).then((response) => {
    if(response.totalOccupancy < 2){
      this.pubnub.subscribe({
        channels: [this.lobbyChannel],
        withPresence: true
      });

      this.setState({
        piece: 'O', // Player O
      });

      this.pubnub.publish({
        message: {
          notRoomCreator: true,
        },
        channel: this.lobbyChannel
      });
    }
}).catch((error) => {
  console.log(error);
});
```

Une fois que le joueur s'est abonné au canal de l'antichambre, la valeur d'état de la _pièce_ passe à " O " et un message est publié sur ce canal de l'antichambre. Ce message informe le _joueur X_ qu'un autre joueur a rejoint le canal. Nous avons configuré le récepteur de message dans **componentDidUpdate()**, que nous aborderons prochainement.

Si le taux d'occupation total est supérieur à 2, une partie est en cours et le joueur qui tente de rejoindre le canal se verra refuser l'accès. Le code suivant se trouve sous l'instruction if de **hereNow()**.

```js
// Below the if statement in hereNow()
else{
  // Game in progress
  Swal.fire({
    position: 'top',
    allowOutsideClick: false,
    title: 'Error',
    text: 'Game in progress. Try another room.',
    width: 275,
    padding: '0.7em',
    customClass: {
        heightAuto: false,
        title: 'title-class',
        popup: 'popup-class',
        confirmButton: 'button-class'
    }
  })
}
```

Jetons maintenant un coup d'œil à **componentDidUpdate()**.

### Démarrer le jeu

Dans **componentDidUpdate()**, nous vérifions que le joueur est connecté à un canal, c'est-à-dire que _this.lobbyChannel_ n'est pas **null**. Si ce n'est pas **le** cas, nous mettons en place un listener qui écoute tous les messages qui arrivent sur le canal.

```js
componentDidUpdate() {
  // Check that the player is connected to a channel
  if(this.lobbyChannel != null){
    this.pubnub.getMessage(this.lobbyChannel, (msg) => {
      // Start the game once an opponent joins the channel
      if(msg.message.notRoomCreator){
        // Create a different channel for the game
        this.gameChannel = 'tictactoegame--' + this.roomId;

        this.pubnub.subscribe({
          channels: [this.gameChannel]
        });
      }
    });
  }
}
```

Nous vérifions si le message arrivé est _msg.message.notRoomCreator_, qui est publié par le joueur qui rejoint le canal. Si c'est le cas, nous créons un nouveau canal, "_tictactoegame-",_avec l'_identifiant de la salle_ ajouté à la chaîne. Le canal de jeu est utilisé pour publier tous les mouvements effectués par les joueurs, qui mettront à jour leurs tableaux.

Enfin, après avoir souscrit au canal de jeu, la valeur d'état _isPlaying_ est fixée à true. Ce faisant, le composant lobby est remplacé par le composant jeu.

```js
 this.setState({
   isPlaying: true
 });

 // Close the modals if they are opened
 Swal.close();
}
```

Une fois le composant jeu affiché, nous voulons fermer toutes les fenêtres modales, si elles ont été ouvertes, à partir du composant Lobby en faisant _Swal.close()_.

Maintenant que nous avons deux joueurs connectés à un canal de jeu unique, ils peuvent commencer à jouer au morpion ! Dans la section suivante, nous mettrons en œuvre l'interface utilisateur et la logique du composant de jeu.

Construire les fonctionnalités du jeu
-------------------------------------

La première chose que nous faisons dans **Game.js** est de mettre en place le **constructeur de base**:

```js
// Game.js
import React from 'react';
import Board from './Board';
import Swal from "sweetalert2";

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(''), // 3x3 board
      xScore: 0,
      oScore: 0,
      whosTurn: this.props.myTurn // Player X goes first
    };

    this.turn = 'X';
    this.gameOver = false;
    this.counter = 0; // Game ends in a tie when counter is 9
  }

  render() {
    return ();
  }
 }
export default Game;
```

Pour les objets state, nous initialisons la propriété array _squares_, qui est utilisée pour stocker les positions des joueurs sur le plateau. Cela sera expliqué plus loin. Nous fixons également le score des joueurs à 0 et la valeur de _whosTurn_ à _myTurn_, qui est initialisé à **true** pour le _joueur X_ et à **false** pour le _joueur O_.

La valeur des variables _turn_ et _counter_ changera tout au long de la progression du jeu. À la fin de la partie, la valeur de _gameOver_ est fixée à **true**.

### Ajouter l'interface utilisateur

Ensuite, nous allons mettre en place le balisage du composant Game à l'intérieur de la méthode de **rendu**.

```js
render() {
  let status;
  // Change to current player's turn
  status = `${this.state.whosTurn ? "Your turn" : "Opponent's turn"}`;

  return (
    <div className="game">
      <div className="board">
        <Board
            squares={this.state.squares}
            onClick={index => this.onMakeMove(index)}
          />
          <p className="status-info">{status}</p>
      </div>

      <div className="scores-container">
        <div>
          <p>Player X: {this.state.xScore} </p>
        </div>

        <div>
          <p>Player O: {this.state.oScore} </p>
        </div>
      </div>
    </div>
  );
}
```

Nous affichons la valeur de l'_état_ dans l'interface utilisateur pour que les joueurs sachent si c'est leur tour de jouer ou si c'est le tour de l'autre joueur. La valeur booléenne de l'état _whosTurn_ est mise à jour à chaque fois qu'un mouvement est effectué. Le reste de l'interface utilisateur se compose du composant Board et du score du joueur.

### Ajouter la logique

Lorsque le joueur effectue un déplacement sur le plateau, un appel à **onMakeMove(****index)** est effectué où l'**index** est la position de la pièce sur le plateau. Le plateau comporte 3 lignes et 3 colonnes, soit 9 cases au total. Chaque case a sa propre valeur d'**index**, commençant par la valeur 0 et se terminant par la valeur 8.

```js
onMakeMove = (index) =>{
  const squares = this.state.squares;

  // Check if the square is empty and if it's the player's turn to make a move
  if(!squares[index] && (this.turn === this.props.piece)){
    squares[index] = this.props.piece;

    this.setState({
      squares: squares,
      whosTurn: !this.state.whosTurn
    });

    // Other player's turn to make a move
    this.turn = (this.turn === 'X') ? 'O' : 'X';

    // Publish move to the channel
    this.props.pubnub.publish({
      message: {
        index: index,
        piece: this.props.piece,
        turn: this.turn
      },
      channel: this.props.gameChannel
    });

    // Check if there is a winner
    this.checkForWinner(squares)
  }
}
```

Après avoir obtenu l'état des _cases_ du tableau, une instruction conditionnelle est utilisée pour vérifier si la case que le joueur a touchée est vide et si c'est à son tour d'effectuer un déplacement. Si l'une ou les deux conditions ne sont pas remplies, le pion du joueur n'est pas placé sur la case. Dans le cas contraire, le pion du joueur est ajouté au tableau des _cases_ de l'index sur lequel le pion a été placé.

Par exemple, si le _joueur X_ effectue un déplacement sur la ligne 0, colonne 2 et que la condition est remplie, le tableau _cases\[2\]_ aura la valeur "X".[![Example with the squares array](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq4/a8487d17f078fae4ccfa874baac16013/squares-example.png "Example with the squares array")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq4/a8487d17f078fae4ccfa874baac16013/squares-example.png)Ensuite, l'état est modifié pour refléter le nouvel état du jeu et le _tour_ est mis à jour pour que l'autre joueur puisse jouer son coup. Pour que le plateau de l'autre joueur soit mis à jour avec les données actuelles, nous publions les données sur le canal de jeu. Tout ceci se passe en temps réel, donc les deux joueurs verront immédiatement leur plateau se mettre à jour dès qu'un coup valide est effectué. La dernière chose à faire dans cette méthode est d'appeler **checkForWinner(squares)** pour vérifier s'il y a un gagnant.

Avant cela, jetons un coup d'œil à **componentDidMount****()** où nous configurons l'écouteur pour les nouveaux messages qui arrivent dans le canal du jeu.

```js
componentDidMount(){
  this.props.pubnub.getMessage(this.props.gameChannel, (msg) => {
    // Update other player's board
    if(msg.message.turn === this.props.piece){
      this.publishMove(msg.message.index, msg.message.piece);
    }
  });
}
```

Comme les deux joueurs sont connectés au même canal de jeu, ils recevront tous les deux ce message. La méthode **publishMove(index, piece)** est appelée, où **index** est la position à laquelle la pièce a été placée et **piece** est la pièce du joueur qui a effectué le déplacement. Cette méthode met à jour le plateau avec le coup en cours et vérifie s'il y a un gagnant. Pour éviter que le joueur qui a effectué le déplacement en cours n'ait à recommencer ce processus, l'instruction _if_ vérifie si la pièce du joueur correspond à la valeur de _turn_. Si c'est le cas, son plateau est mis à jour.

```js
// Opponent's move is published to the board
publishMove = (index, piece) => {
  const squares = this.state.squares;

  squares[index] = piece;
  this.turn = (squares[index] === 'X')? 'O' : 'X';

  this.setState({
    squares: squares,
    whosTurn: !this.state.whosTurn
  });

  this.checkForWinner(squares)
}
```

La logique de mise à jour du plateau est la même que celle de **onMakeMove()**. Passons maintenant à **checkForWinner()**.

```js
checkForWinner = (squares) => {
  // Possible winning combinations
  const possibleCombinations = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];

  // Iterate every combination to see if there is a match
  for (let i = 0; i < possibleCombinations.length; i += 1) {
    const [a, b, c] = possibleCombinations[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      this.announceWinner(squares[a]);
      return;
    }
  }
}
```

Toutes les combinaisons gagnantes sont dans le double tableau _possibleCombinations_, où chaque tableau est une combinaison possible pour gagner le jeu. Chaque tableau dans _possibleCombinations_ est comparé au tableau _squares_. S'il y a une correspondance, il y a un gagnant. Prenons un exemple pour mieux comprendre.

Supposons que le joueur X joue un coup gagnant dans la ligne 2 colonne 0. L'_index de_ cette position est 6. Le tableau ressemble maintenant à ceci :

[![Example of a winning move](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq5/77dcc8ab49b7e7b4e41ac6f1365a2df9/winning-move-example-1.png "Example of a winning move")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq5/77dcc8ab49b7e7b4e41ac6f1365a2df9/winning-move-example-1.png)La combinaison gagnante pour le _joueur X_ est \[2,4,6\]. Le tableau des _cases_ est mis à jour : \["O", "", "X", "O", "X", "", "X", "", ""\].

Dans la boucle _for_ , lorsque _\[a,b,c\]_ a les valeurs \[2,4,6\], l'instruction _if_ de la boucle _for_ est vraie puisque \[2,4,6\] ont _tous_ la même valeur de _X_. Le score du gagnant doit être mis à jour, et la**fonction announceWinner()** est donc appelée pour récompenser le joueur gagnant.

Si le jeu se termine par une égalité, il n'y a pas de gagnant pour ce tour. Pour vérifier s'il y a égalité, nous utilisons un compteur qui s'incrémente de un à chaque fois qu'un mouvement est effectué sur le plateau.

```js
// Below the for loop in checkForWinner()
// Check if the game ends in a draw
this.counter++;
// The board is filled up and there is no winner
if(this.counter === 9){
  this.gameOver = true;
  this.newRound(null);
}
```

Si le compteur atteint 9, la partie se termine par un match nul parce que le joueur n'a pas joué un coup gagnant sur la dernière case du plateau. Dans ce cas, la méthode **newRound()** est appelée avec un argument **null** puisqu'il n'y a pas de gagnant.

Avant de passer à cette méthode, revenons à**announceWinner()**.

```js
// Update score for the winner
announceWinner = (winner) => {
  let pieces = {
    'X': this.state.xScore,
    'O': this.state.oScore
  }

  if(winner === 'X'){
    pieces['X'] += 1;
    this.setState({
      xScore: pieces['X']
    });
  }
  else{
    pieces['O'] += 1;
    this.setState({
      oScore: pieces['O']
    });
  }
  // End the game once there is a winner
  this.gameOver = true;
  this.newRound(winner);
}
```

Le paramètre de cette méthode est le _gagnant_, qui est le joueur qui a gagné la partie. Nous vérifions si le gagnant est 'X' ou 'O' et incrémentons son score d'un point. Puisque le jeu est terminé, la variable _gameOver_ est fixée à true et la méthode **newRound()** est appelée.

### Commencer un nouveau tour

Le_joueur X_ a la possibilité de jouer un nouveau tour ou de mettre fin au jeu et de retourner dans le lobby.

[![Endgame modal for Player X](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq6/f7b9623c4b0b628d51d8271f249747ba/playerx-endgame-modal.png "Endgame modal for Player X")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq6/f7b9623c4b0b628d51d8271f249747ba/playerx-endgame-modal.png)

L'autre joueur doit attendre que le _joueur X_ décide de ce qu'il va faire.

[![Endgame modal for Player O](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq7/d96413b49ec1b85838aca013cfba174b/playero-endgame-modal.png "Endgame modal for Player O")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq7/d96413b49ec1b85838aca013cfba174b/playero-endgame-modal.png)

Une fois que le _joueur X_ a pris sa décision, un message est publié sur le canal du jeu pour en informer l'autre joueur. L'interface utilisateur est alors mise à jour.

```js
newRound = (winner) => {
  // Announce the winner or announce a tie game
  let title = (winner === null) ? 'Tie game!' : `Player ${winner} won!`;
  // Show this to Player O
  if((this.props.isRoomCreator === false) && this.gameOver){
    Swal.fire({
      position: 'top',
      allowOutsideClick: false,
      title: title,
      text: 'Waiting for a new round...',
      confirmButtonColor: 'rgb(208,33,41)',
      width: 275,
      customClass: {
          heightAuto: false,
          title: 'title-class',
          popup: 'popup-class',
          confirmButton: 'button-class',
      } ,
    });
    this.turn = 'X'; // Set turn to X so Player O can't make a move
  }

  // Show this to Player X
  else if(this.props.isRoomCreator && this.gameOver){
    Swal.fire({
      position: 'top',
      allowOutsideClick: false,
      title: title,
      text: 'Continue Playing?',
      showCancelButton: true,
      confirmButtonColor: 'rgb(208,33,41)',
      cancelButtonColor: '#aaa',
      cancelButtonText: 'Nope',
      confirmButtonText: 'Yea!',
      width: 275,
      customClass: {
          heightAuto: false,
          title: 'title-class',
          popup: 'popup-class',
          confirmButton: 'button-class',
          cancelButton: 'button-class'
      } ,
    }).then((result) => {
      // Start a new round
      if (result.value) {
        this.props.pubnub.publish({
          message: {
            reset: true
          },
          channel: this.props.gameChannel
        });
      }

      else{
        // End the game
        this.props.pubnub.publish({
          message: {
            endGame: true
          },
          channel: this.props.gameChannel
        });
      }
    })
  }
 }
```

Si le message est _réinitialisé_, toutes les valeurs et variables d'état, à l'exception du score des joueurs, sont ramenées à leur valeur initiale. Toutes les fenêtres modales encore ouvertes sont fermées et un nouveau tour commence pour les deux joueurs.

Pour le message _endGame_, toutes les fenêtres modales sont fermées et la méthode **endGame()** est appelée. Cette méthode se trouve dans **App.js**.

```js
// Reset everything
endGame = () => {
  this.setState({
    piece: '',
    isPlaying: false,
    isRoomCreator: false,
    isDisabled: false,
    myTurn: false,
  });

  this.lobbyChannel = null;
  this.gameChannel = null;
  this.roomId = null;

  this.pubnub.unsubscribe({
    channels : [this.lobbyChannel, this.gameChannel]
  });
}
```

Toutes les valeurs et variables d'état sont réinitialisées à leurs valeurs initiales. Les noms des canaux sont remis à zéro car un nouveau nom est généré chaque fois qu'un joueur crée une salle. Comme les noms des canaux ne seront plus utiles, les joueurs se désinscrivent du lobby et du canal de jeu. La valeur de _isPlaying_ est réinitialisée à false, de sorte que le composant de jeu sera remplacé par le composant de lobby.

La dernière méthode à inclure dans **App.js** est **componentWillUnmount()**, qui désinscrit les joueurs des deux canaux.

```js
componentWillUnmount() {
  this.pubnub.unsubscribe({
    channels : [this.lobbyChannel, this.gameChannel]
  });
}
```

C'est tout ce que nous avons à faire pour que le jeu fonctionne ! Vous pouvez obtenir le fichier CSS pour le jeu dans le [repo](https://github.com/PubNubDevelopers/react-tictactoe/blob/master/src/Game.css). Maintenant, faisons fonctionner le jeu.

Lancer le jeu
-------------

Il y a quelques petites étapes à franchir avant de lancer le jeu. Tout d'abord, nous devons activer la [fonctionnalité Presence](https://pubnub.com/docs/sdks/javascript/react) car nous l'utilisons pour obtenir le nombre de personnes dans le canal (nous avons utilisé _withPresence_ lors de l'abonnement au canal lobby). Allez dans le [PubNub Admin Dashboard](https://admin.pubnub.com/#/login) et cliquez sur votre application. Cliquez sur **Keyset** et descendez jusqu'à **Application add-ons**. **Activez** le commutateur **Presence** . Conservez les valeurs par défaut.

[![Enable presence in PubNub Admin Dashboard](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq8/fb24ea6026b76234f79ee65d2426ac71/enable-presence-1.png "Enable presence in PubNub Admin Dashboard")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq8/fb24ea6026b76234f79ee65d2426ac71/enable-presence-1.png)

Pour installer les trois dépendances utilisées dans l'application et pour lancer l'application, vous pouvez exécuter le script **dependencies.sh** qui se trouve dans le répertoire racine de l'application.

```js
# dependencies.sh
npm install --save pubnub pubnub-react
npm install --save shortid
npm install --save sweetalert2

npm start
```

Dans le terminal, allez dans le répertoire racine de l'application et tapez la commande suivante pour rendre le script exécutable:

```js
chmod +x dependencies.sh
```

Exécutez le script avec cette commande :

```js
./dependencies.sh
```

L'application s'ouvrira à l'adresse http://localhost:3000 avec le composant lobby affiché.![Run the React app locally](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq9/fb4f1a520b9d68e1c283a61d5fa04653/run-the-app.png "Run the React app locally")Ouvrez un autre onglet, ou de préférence une fenêtre, et copiez et collez http://localhost:3000. Dans une fenêtre, créez un canal en cliquant sur le bouton "Créer". Une fenêtre modale s'ouvre et affiche l'_identifiant de la salle_. Copiez et collez cet identifiant. Allez dans l'autre fenêtre et cliquez sur le bouton "Rejoindre". Lorsque la fenêtre s'affiche, saisissez l'_identifiant de la salle_ dans le champ de saisie et cliquez sur le bouton "OK".

[![Create and join the channel](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqa/01343a31a36c16277b86d8a1ed92570b/create-join-lobby.png "Create and join the channel")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqa/01343a31a36c16277b86d8a1ed92570b/create-join-lobby.png)

Une fois que les joueurs sont connectés, le jeu commence. La fenêtre que vous avez utilisée pour créer le canal joue le premier coup. Appuyez sur n'importe quelle case du plateau et voyez la pièce **X s'**afficher sur le plateau en temps réel pour les deux fenêtres. Si vous essayez d'appuyer sur une autre case du même plateau, rien ne se passera car ce n'est plus votre tour de jouer. Dans l'autre fenêtre, appuyez sur n'importe quelle case de l'échiquier et la pièce **O _sera_** placée sur la case.

[![Place the piece on the board](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqb/454174588fc9acf9c2f086c3e9c53a19/place-piece-on-board.png "Place the piece on the board")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqb/454174588fc9acf9c2f086c3e9c53a19/place-piece-on-board.png)

Continuez à jouer jusqu'à ce qu'il y ait un gagnant ou une égalité. Une fenêtre modale s'affiche alors pour annoncer le vainqueur du tour ou pour annoncer que la partie s'est terminée par une égalité. Dans la même fenêtre, le _joueur X_ devra décider s'il continue à jouer ou s'il quitte le jeu. La fenêtre modale du _joueur O_ lui dira d'attendre un nouveau tour.

[![End of game modals](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqc/71b832403fecb058be460282895e3f10/end-of-game.png "End of game modals")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqc/71b832403fecb058be460282895e3f10/end-of-game.png)

Tout, sauf le score, est remis à zéro si _le joueur X_ continue le jeu. Dans le cas contraire, les deux joueurs sont renvoyés dans le lobby où ils peuvent créer ou rejoindre de nouveaux canaux. Découvrez la démo du jeu ci-dessous :

**Vous avez des suggestions ou des questions concernant le contenu de cet article ? N'hésitez pas à nous contacter à l'adresse [devrel@pubnub.com.](mailto:devrel@pubnub.com)**

#### Sommaire

[Concepts du jeu multijoueur en temps](#h-0)[réelVue d'ensemble de](#h-1) l'[applicationConfigurer](#h-0) le[lobbyCréer](#h-2)[un canalJoindre un](#h-3)[canalDémarrer](#h-4)[le jeuConstruire les](#h-5)[fonctionnalités du jeuAjouter](#h-6)[l'UIAAjouter la](#h-7)[logiqueDémarrer](#h-8)[un nouveau roundFaire tourner le](#h-9)[jeu](#h-10)

Comment PubNub peut-il vous aider ?
===================================

Cet article a été publié à l'origine sur [PubNub.com](https://www.pubnub.com/blog/browser-based-multiplayer-tic-tac-toe-game-in-react/)

Notre plateforme aide les développeurs à construire, livrer et gérer l'interactivité en temps réel pour les applications web, les applications mobiles et les appareils IoT.

La base de notre plateforme est le réseau de messagerie en temps réel le plus grand et le plus évolutif de l'industrie. Avec plus de 15 points de présence dans le monde, 800 millions d'utilisateurs actifs mensuels et une fiabilité de 99,999 %, vous n'aurez jamais à vous soucier des pannes, des limites de concurrence ou des problèmes de latence causés par les pics de trafic.

Découvrez PubNub
----------------

Découvrez le [Live Tour](https://www.pubnub.com/tour/introduction/) pour comprendre les concepts essentiels de chaque application alimentée par PubNub en moins de 5 minutes.

S'installer
-----------

Créez un [compte PubNub](https://admin.pubnub.com/signup/) pour un accès immédiat et gratuit aux clés PubNub.

Commencer
---------

La [documentation PubNub](https://www.pubnub.com/docs) vous permettra de démarrer, quel que soit votre cas d'utilisation ou votre [SDK](https://www.pubnub.com/docs).