Browser-basiertes Multiplayer Tic Tac Toe Spiel in React
Published: 2024-03-10 22:46:31

**Dieser Beitrag aus unserem Archiv führt Sie durch die Schritte zur Erstellung eines Tic Tac Toe-Spiels in React. Beachten Sie jedoch, dass die verwendeten Bibliotheksversionen nicht mehr die neuesten sind.** Insbesondere verwendet dieser Artikel die Version 1 unseres [React SDKs](https://www.pubnub.com/docs/sdks/react), aber alle unten aufgeführten Schritte und Prinzipien sind immer noch gültig

Tic Tac Toe ist ein typisches [Kinderspiel](https://www.pubnub.com/solutions/gaming/). Alles, was es braucht, ist etwas zum Schreiben und etwas, mit dem man schreiben kann. Was aber, wenn Sie mit jemandem spielen möchten, der sich an einem anderen Ort befindet? In diesem Fall müssen Sie eine Anwendung verwenden, die Sie und einen anderen Spieler mit dem Spiel verbindet.

Die Anwendung muss ein Echtzeit-Erlebnis bieten, so dass jeder Zug, den Sie machen, sofort von dem anderen Spieler gesehen wird und andersherum. Wenn die Anwendung dieses Erlebnis nicht bietet, werden Sie und viele andere sie wahrscheinlich nicht mehr nutzen.

Wie kann ein Entwickler also eine [vernetzte Erfahrung](https://www.pubnub.com/blog/connected-shared-experiences-a-developers-guide/) bieten, bei der die Spieler Tic Tac Toe oder ein anderes Spiel spielen können, egal wo sie sich auf der Welt befinden?

Konzepte für Echtzeit-Multiplayer-Spiele
----------------------------------------

Es gibt mehrere Möglichkeiten, die Echtzeit-Infrastruktur für Multiplayer-Spiele bereitzustellen. Sie können Ihre eigene Infrastruktur von Grund auf aufbauen, indem Sie Technologien und Open-Source-Protokolle wie [Socket.IO](https://www.pubnub.com/learn/glossary/what-is-socketio/), [SignalR](https://pubnub.com/guides/what-is-signalr/) oder [WebSockets](https://www.pubnub.com/learn/glossary/what-is-websocket/) verwenden.

Dieser Weg mag zwar verlockend erscheinen, aber Sie werden dabei auf mehrere Probleme stoßen; ein solches Problem ist die Skalierbarkeit. Es ist nicht schwer, mit 100 Benutzern umzugehen, aber wie können Sie 100.000+ Benutzer verwalten? Neben den Problemen mit der Infrastruktur müssen Sie sich auch noch um die Wartung Ihres Spiels kümmern.

Letzten Endes ist das Einzige, was zählt, ein großartiges Spielerlebnis für die Spieler. Aber wie löst man das Infrastrukturproblem? An dieser Stelle kommt PubNub ins Spiel.

PubNub bietet die Echtzeit-Infrastruktur, um jede Anwendung über sein globales [Data Stream Network](https://pubnub.com/products/pubnub-platform/) zu betreiben. Mit [mehr als 70 SDKs,](https://pubnub.com/docs/) einschließlich der beliebtesten Programmiersprachen, vereinfacht PubNub das Senden und Empfangen von Nachrichten an jedes Gerät in **weniger als 100 ms**. PubNub ist sicher, skalierbar und zuverlässig, so dass Sie sich nicht um die Erstellung und Wartung Ihrer eigenen Infrastruktur kümmern müssen.

Um zu zeigen, wie einfach es ist, ein Multiplayer-Spiel mit PubNub zu entwickeln, werden wir ein einfaches React Tic Tac Toe-Spiel mit dem [PubNub React SDK](https://pubnub.com/docs/sdks/javascript/react) erstellen. In diesem Spiel verbinden sich zwei Spieler mit einem eigenen Spielkanal, in dem sie gegeneinander antreten. Jeder Zug, den ein Spieler macht, wird auf dem Kanal veröffentlicht, um das Spielbrett des anderen Spielers in Echtzeit zu aktualisieren.

App-Übersicht
-------------

So wird unsere App aussehen, wenn wir fertig sind.

![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")Die Spieler betreten zunächst die Lobby, wo sie einen Kanal erstellen oder einem Kanal beitreten können. Wenn der Spieler einen Kanal erstellt, erhält er eine _Raumnummer_, die er mit einem anderen Spieler teilen kann. Der Spieler, der den Kanal erstellt hat, wird _Spieler X_ und macht den ersten Zug, wenn das Spiel beginnt.

[![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)

Der Spieler, der einem Channel mit der ihm zugewiesenen _Raumnummer_ beitritt, wird _Spieler O_. Spieler können nur Channels beitreten, wenn sich eine weitere Person in dem Channel befindet. Wenn mehr als eine Person anwesend ist, läuft ein Spiel in diesem Channel und der Spieler kann nicht beitreten. Das Spiel beginnt, sobald sich zwei Spieler im Channel befinden.

[![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)

Am Ende des Spiels wird der Punktestand des Gewinners um einen Punkt erhöht. Wenn das Spiel unentschieden endet, erhält keiner der beiden Spieler einen Punkt. _Spieler X_ wird in einem Modal gefragt, ob er eine neue Runde beginnen oder das Spiel beenden möchte. Wenn _Spieler X_ das Spiel fortsetzt, wird das Spielbrett für die neue Runde zurückgesetzt. Andernfalls endet das Spiel und beide Spieler kehren in die Lobby zurück.

[![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)

Einrichten der Lobby
--------------------

Bevor wir die Lobby einrichten, melden Sie sich für ein kostenloses PubNub-Konto an, um Ihre kostenlosen Pub/Sub-API-Schlüssel über das [PubNub-Administrations-Dashboard](https://admin.pubnub.com) zu erhalten.

Sobald Sie Ihre Schlüssel erhalten haben, fügen Sie sie in den Konstruktor von **App.js** ein.

```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;
```

Ebenfalls im Konstruktor werden die Statusobjekte und Variablen initialisiert. Wir werden auf die Objekte und Variablen eingehen, wenn sie in der Datei auftauchen. Schließlich haben wir PubNub am Ende des Konstruktors initialisiert.

Innerhalb der **Render-Methode** und der **Rückgabeanweisung** fügen wir das Markup für die Lobby-Komponente hinzu.

```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>
);
```

Die Lobby-Komponente besteht aus einem Titel, einem leeren Tic-Tac-Toe-Brett (es passiert nichts, wenn der Spieler auf die Quadrate drückt) und den Schaltflächen "Erstellen_"_ und "Beitreten_"_. Diese Komponente wird nur angezeigt, wenn der Statuswert _isPlaying_ falsch ist. Wenn er auf true gesetzt ist, hat das Spiel begonnen und die Komponente wird in die Komponente Game umgewandelt, die wir im zweiten Teil des Tutorials besprechen werden.

Die Board-Komponente ist ebenfalls Teil der Lobby-Komponente. Innerhalb der [Board-Komponente](https://github.com/PubNubDevelopers/react-tictactoe/blob/master/src/Board.js) befindet sich die [Square-Komponente](https://github.com/PubNubDevelopers/react-tictactoe/blob/master/src/Square.js). Wir werden nicht näher auf diese beiden Komponenten eingehen, um uns auf die Komponenten Lobby und Spiel zu konzentrieren.

Wenn der Spieler auf die Schaltfläche "Erstellen" drückt, wird die Schaltfläche deaktiviert, damit der Spieler nicht mehrere Kanäle erstellen kann. Die Schaltfläche "Beitreten" ist nicht deaktiviert, für den Fall, dass der Spieler stattdessen einem Channel beitreten möchte. Sobald die Schaltfläche "Erstellen" gedrückt wird, wird die Methode **onPressCreate()** aufgerufen.

### Einen Kanal erstellen

Das erste, was wir in **onPressCreate()** tun, ist, eine zufällige, auf 5 Zeichen gekürzte String-ID zu generieren. Dazu verwenden wir [**shortid()**](https://www.npmjs.com/package/shortid). Die Zeichenkette wird an '_tictactoelobby-'_angehängt_,_was der eindeutige Lobby-Kanal sein wird, den die Spieler abonnieren.

```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
  });
}
```

Um zu verhindern, dass mehr als zwei Spieler einem bestimmten Kanal beitreten, verwenden wir [Presence](https://pubnub.com/docs/sdks/javascript/react). Später werden wir uns die Logik zur Überprüfung der Belegung des Kanals ansehen.

Sobald der Spieler den Lobbykanal abonniert hat, wird ein Modal mit der Raum-ID angezeigt, damit ein anderer Spieler diesem Kanal beitreten kann.

[![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)

Dieses Modal und alle in dieser Anwendung verwendeten Modals werden von [**SweetAlert2**](https://sweetalert2.github.io/#download) erstellt, um die standardmäßigen alert()-Popup-Boxen von JavaScript zu ersetzen.

```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'
  }
})
```

Am Ende von **onPressCreate()** ändern wir die Statuswerte, um den neuen Status der App wiederzugeben.

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

Sobald der Spieler einen Raum erstellt hat, muss er warten, bis ein anderer Spieler diesem Raum beitritt. Schauen wir uns die Logik für den Beitritt zu einem Raum an.

### Einem Kanal beitreten

Wenn ein Spieler auf die Schaltfläche 'Beitreten' drückt, wird ein Aufruf von **onPressJoin()** ausgeführt. Dem Spieler wird ein Modal angezeigt, in dem er aufgefordert wird, die _Raum-ID_ in das Eingabefeld einzugeben.

[![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)

Wenn der Spieler die _Raumkennung_ eingibt und die Schaltfläche 'OK' drückt, wird **joinRoom(value)** aufgerufen, wobei **value** die _Raumkennung_ ist. Diese Methode wird nicht aufgerufen, wenn das Eingabefeld leer ist oder wenn der Spieler die Schaltfläche "Abbrechen" drückt.

```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);
    }
  })
}
```

Das erste, was wir in **joinRoom()** tun, ist, den **Wert** an '_tictactoelobby-'_anzuhängen_,_ähnlich wie wir es in **onPressCreate()** getan haben.

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

Bevor der Spieler den Lobby-Kanal abonniert, müssen wir die Gesamtbelegung des Kanals mit [hereNow()](https://pubnub.com/docs/sdks/javascript/react) überprüfen. Wenn die Gesamtbelegung weniger als 2 beträgt, kann der Spieler den Lobby-Kanal erfolgreich abonnieren.

```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);
});
```

Nachdem der Spieler den Lobby-Kanal abonniert hat, wird der Statuswert von _piece_ auf 'O' geändert und eine Nachricht an diesen Lobby-Kanal veröffentlicht. Diese Nachricht benachrichtigt den _Spieler X_, dass ein anderer Spieler dem Kanal beigetreten ist. Wir richten den Nachrichten-Listener in **componentDidUpdate()** ein, zu dem wir gleich kommen werden.

Wenn die Gesamtbelegung größer als 2 ist, ist ein Spiel im Gange und der Spieler, der versucht, dem Kanal beizutreten, wird abgewiesen. Der folgende Code befindet sich unterhalb der if-Anweisung in **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'
    }
  })
}
```

Werfen wir nun einen Blick auf **componentDidUpdate()**.

### Starten Sie das Spiel

In **componentDidUpdate()** prüfen wir, ob der Spieler mit einem Kanal verbunden ist, d. h. wir prüfen, ob _this.lobbyChannel_ nicht **null** ist. Wenn dies nicht **der** Fall ist, richten wir einen Listener ein, der auf alle Nachrichten hört, die auf dem Kanal ankommen.

```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]
        });
      }
    });
  }
}
```

Wir prüfen, ob die Nachricht _msg.message.notRoomCreator_ ist, die von dem Spieler veröffentlicht wird, der dem Kanal beitritt. Wenn ja, erstellen wir einen neuen Kanal, '_tictactoegame-',_mit der _Raum-ID_ an die Zeichenkette angehängt. Der Spielkanal wird verwendet, um alle Züge der Spieler zu veröffentlichen, die ihre Spielbretter aktualisieren.

Schließlich wird nach dem Abonnieren des Spielkanals der Statuswert von _isPlaying_ auf true gesetzt. Dadurch wird die Lobbykomponente durch die Spielkomponente ersetzt.

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

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

Sobald die Spielkomponente angezeigt wird, wollen wir alle Modals, sofern sie geöffnet sind, von der Lobby-Komponente aus schließen, indem wir _Swal.close()_ ausführen.

Jetzt haben wir zwei Spieler, die mit einem einzigen Spielkanal verbunden sind, und sie können anfangen, Tic Tac Toe zu spielen! Im nächsten Abschnitt werden wir die Benutzeroberfläche und die Logik für die Spielkomponente implementieren.

Spielfunktionen erstellen
-------------------------

Das erste, was wir in **Game.js** tun, ist, den **Basiskonstruktor** einzurichten:

```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;
```

Für die Zustandsobjekte initialisieren wir die Eigenschaft array _squares_, die dazu dient, die Positionen der Spieler auf dem Spielbrett zu speichern. Dies wird weiter unten erklärt. Außerdem setzen wir den Punktestand der Spieler auf 0 und den Wert von _whosTurn_ auf _myTurn_, der für _Spieler X_ auf **true** und für _Spieler O_ auf **false** initialisiert wird.

Der Wert der Variablen _turn_ und _counter_ ändert sich im Laufe des Spiels. Am Ende des Spiels wird _gameOver_ auf **true** gesetzt.

### Hinzufügen der Benutzeroberfläche

Als Nächstes richten wir das Markup für die Game-Komponente innerhalb der **Render-Methode** ein.

```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>
  );
}
```

Wir zeigen den Wert des _Status_ in der UI an, um den Spielern mitzuteilen, ob sie an der Reihe sind, einen Zug zu machen, oder ob der andere Spieler am Zug ist. Der boolesche Wert des Status _whosTurn_ wird jedes Mal aktualisiert, wenn ein Zug gemacht wird. Der Rest der Benutzeroberfläche besteht aus der Brettkomponente und dem Punktestand des Spielers.

### Hinzufügen der Logik

Wenn der Spieler einen Zug auf dem Brett macht, erfolgt ein Aufruf von **onMakeMove(****index)**, wobei **index** die Position ist, an der die Figur auf dem Brett steht. Das Spielbrett hat 3 Reihen und 3 Spalten, also insgesamt 9 Felder. Jedes Feld hat seinen eigenen **Indexwert**, beginnend mit dem Wert 0 und endend mit dem Wert 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)
  }
}
```

Nachdem der Zustand der _Felder_ ermittelt wurde, wird eine bedingte Anweisung verwendet, um zu prüfen, ob das Feld, das der Spieler berührt hat, leer ist und ob er an der Reihe ist, einen Zug zu machen. Wenn eine oder beide Bedingungen nicht erfüllt sind, wird die Figur des Spielers nicht auf das Feld gesetzt. Andernfalls wird die Figur des Spielers zu den _Feldern_ im Array hinzugefügt, die dem Index entsprechen, auf dem die Figur platziert wurde.

Wenn _Spieler X_ zum Beispiel einen Zug in Zeile 0, Spalte 2 macht und die Bedingung erfüllt ist, dann haben die _Felder\[2\]_ den Wert "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)Als nächstes wird der Status geändert, um den neuen Zustand des Spiels widerzuspiegeln, und der _Zug_ wird aktualisiert, damit der andere Spieler seinen Zug machen kann. Damit das Brett des anderen Spielers mit den aktuellen Daten aktualisiert wird, veröffentlichen wir die Daten im Spielkanal. All dies geschieht in Echtzeit, so dass beide Spieler sofort sehen, dass ihre Spielbretter aktualisiert werden, sobald ein gültiger Zug gemacht wird. Der letzte Schritt in dieser Methode ist der Aufruf **checkForWinner(squares)**, um zu prüfen, ob es einen Gewinner gibt.

Bevor wir das tun, werfen wir einen Blick auf **componentDidMount****()** , wo wir den Listener für neue Nachrichten einrichten, die im Spielkanal ankommen.

```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);
    }
  });
}
```

Da beide Spieler mit demselben Spielkanal verbunden sind, werden beide diese Nachricht erhalten. Die Methode **publishMove(index, piece)** wird aufgerufen, wobei **index** die Position ist, an der die Figur platziert wurde, und **piece** die Figur des Spielers ist, der den Zug gemacht hat. Diese Methode aktualisiert das Brett mit dem aktuellen Zug und prüft, ob es einen Gewinner gibt. Um zu verhindern, dass der Spieler, der den aktuellen Zug ausgeführt hat, diesen Vorgang erneut durchführen muss, prüft die _if-Anweisung_, ob die Figur des Spielers dem Wert von _turn_ entspricht. Ist dies der Fall, wird das Brett aktualisiert.

```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)
}
```

Die Logik der Aktualisierung des Spielbretts ist die gleiche wie bei **onMakeMove()**. Lassen Sie uns nun **checkForWinner()** durchgehen.

```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;
    }
  }
}
```

Alle Gewinnkombinationen befinden sich in dem doppelten Array _possibleCombinations_, wobei jedes Array eine mögliche Kombination ist, um das Spiel zu gewinnen. Jedes Array in _possibleCombinations_ wird mit dem Array _squares_ abgeglichen. Wenn es eine Übereinstimmung gibt, dann gibt es einen Gewinner. Um dies zu verdeutlichen, wollen wir ein Beispiel verwenden.

Angenommen, Spieler X macht einen Gewinnzug in Reihe 2 Spalte 0. Der _Index_ dieser Position ist 6. Das Brett sieht nun wie folgt aus:

[![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)Die Gewinnkombination für _Spieler X_ ist \[2,4,6\]. Das Feld _Quadrate_ wird aktualisiert zu: \["O", "", "X", "O", "X", "", "X", "", ""\].

Wenn _\[a,b,c\]_ in der _for-Schleife_ die Werte \[2,4,6\] hat, ist die _if-Anweisung_ in der _for-Schleife_ wahr, da \[2,4,6\] _alle_ den gleichen Wert von _X_ haben. Der Punktestand des Gewinners muss aktualisiert werden, also wird**announceWinner()** aufgerufen, um den Gewinner zu ermitteln.

Wenn das Spiel mit einem Unentschieden endet, gibt es für diese Runde keinen Gewinner. Um zu prüfen, ob ein Spiel unentschieden endet, verwenden wir einen Zähler, der bei jedem Zug auf dem Brett um eins erhöht wird.

```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);
}
```

Erreicht der Zähler den Wert 9, endet das Spiel unentschieden, weil der Spieler auf dem letzten Feld des Brettes keinen Gewinnzug gemacht hat. In diesem Fall wird die Methode **newRound()** mit einem **Null-Argument** aufgerufen, da es keinen Gewinner gibt.

Bevor wir zu dieser Methode gehen, lassen Sie uns zu**announceWinner()** zurückkehren.

```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);
}
```

Der Parameter dieser Methode ist _winner_, d.h. der Spieler, der das Spiel gewonnen hat. Wir prüfen, ob der Gewinner 'X' oder 'O' ist und erhöhen den Punktestand des Gewinners um einen Punkt. Da das Spiel beendet ist, wird die Variable _gameOver_ auf true gesetzt und die Methode **newRound()** aufgerufen.

### Eine neue Runde beginnen

_Spieler X_ hat die Möglichkeit, eine weitere Runde zu spielen oder das Spiel zu beenden und zurück in die Lobby zu gehen.

[![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)

Der andere Spieler hat gesagt, dass er warten soll, bis _Spieler X_ entscheidet, was er tun will.

[![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)

Sobald _Spieler X_ entschieden hat, was er tun will, wird eine Nachricht im Spielkanal veröffentlicht, um den anderen Spieler zu informieren. Die Benutzeroberfläche wird dann aktualisiert.

```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
        });
      }
    })
  }
 }
```

Wenn die Nachricht _zurückgesetzt_ wird, werden alle Statuswerte und Variablen, mit Ausnahme des Punktestands der Spieler, auf ihre ursprünglichen Werte zurückgesetzt. Alle noch offenen Modals werden geschlossen und eine neue Runde beginnt für beide Spieler.

Bei der Nachricht _endGame_ werden alle Modals geschlossen und die Methode **endGame()** wird aufgerufen. Diese Methode befindet sich in **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]
  });
}
```

Alle Statuswerte und Variablen werden auf ihre Anfangswerte zurückgesetzt. Die Kanalnamen werden auf Null zurückgesetzt, da jedes Mal, wenn ein Spieler einen Raum erstellt, ein neuer Name generiert wird. Da die Kanalnamen nicht mehr nützlich sein werden, melden sich die Spieler sowohl von der Lobby als auch vom Spielkanal ab. Der Wert von _isPlaying_ wird auf false zurückgesetzt, so dass die Spielkomponente durch die Lobbykomponente ersetzt wird.

Die letzte Methode, die in **App.js** aufgenommen werden muss, ist **componentWillUnmount()**, die die Spieler von beiden Kanälen abmeldet.

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

Das ist alles, was wir tun müssen, damit das Spiel funktioniert! Die CSS-Datei für das Spiel ist im [Repo](https://github.com/PubNubDevelopers/react-tictactoe/blob/master/src/Game.css) zu finden. Nun wollen wir das Spiel zum Laufen bringen.

Das Spiel starten
-----------------

Bevor wir das Spiel starten können, müssen wir noch ein paar kleine Schritte durchführen. Als erstes müssen wir das [Presence-Feature](https://pubnub.com/docs/sdks/javascript/react) aktivieren, da wir es benutzen, um die Anzahl der Personen im Channel zu ermitteln (wir haben _withPresence_ benutzt, als wir den Lobby-Channel abonniert haben). Gehe zum [PubNub Admin Dashboard](https://admin.pubnub.com/#/login) und klicke auf deine Anwendung. Klicken Sie auf **Keyset** und scrollen Sie nach unten zu **Application add-ons**. Schalten Sie den Schalter **Presence** auf **on**. Behalten Sie die Standardwerte bei.

[![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)

Um die drei in der Anwendung verwendeten Abhängigkeiten zu installieren und die Anwendung auszuführen, können Sie das Skript **dependencies.sh** ausführen, das sich im Stammverzeichnis der Anwendung befindet.

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

npm start
```

Wechseln Sie im Terminal in das Stammverzeichnis der App und geben Sie den folgenden Befehl ein, um das Skript ausführbar zu machen:

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

Führen Sie das Skript mit diesem Befehl aus:

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

Die Anwendung wird in http://localhost:3000 geöffnet und die Lobbykomponente wird angezeigt.![Run the React app locally](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq9/fb4f1a520b9d68e1c283a61d5fa04653/run-the-app.png "Run the React app locally")Öffnen Sie eine weitere Registerkarte oder vorzugsweise ein Fenster und kopieren Sie http://localhost:3000 und fügen Sie es ein. Erstellen Sie in einem Fenster einen Kanal, indem Sie auf die Schaltfläche "Erstellen" klicken. Es öffnet sich ein Modal, das die _Raum-ID_ anzeigt. Kopieren Sie diese Kennung und fügen Sie sie ein. Gehen Sie zum anderen Fenster und klicken Sie auf die Schaltfläche "Beitreten". Wenn das Modal erscheint, geben Sie die _Raumnummer_ in das Eingabefeld ein und klicken Sie auf die Schaltfläche "Okay".

[![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)

Sobald die Spieler verbunden sind, wird das Spiel gestartet. Das Fenster, in dem du den Kanal erstellt hast, macht den ersten Zug. Drücken Sie ein beliebiges Feld auf dem Spielbrett und sehen Sie, wie die Figur **X** in Echtzeit für beide Fenster auf dem Spielbrett angezeigt wird. Wenn Sie versuchen, ein anderes Feld auf demselben Brett zu drücken, passiert nichts, weil Sie nicht mehr am Zug sind. Drücken Sie im anderen Fenster auf ein beliebiges Feld auf dem Brett und die Figur **O _wird_** auf das Feld gesetzt.

[![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)

Spielen Sie weiter, bis es einen Sieger oder ein Unentschieden gibt. Dann wird ein Modal angezeigt, in dem der Gewinner der Runde bekannt gegeben wird oder das Spiel mit einem Unentschieden endet. In demselben Modal muss _Spieler X_ entscheiden, ob er weiterspielen oder das Spiel beenden möchte. Das Modal für _Spieler O_ wird ihm sagen, dass er auf eine neue Runde warten soll.

[![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)

Wenn _Spieler X_ das Spiel fortsetzt, wird alles zurückgesetzt, außer dem Spielstand. Andernfalls kehren beide Spieler in die Lobby zurück, wo sie neue Kanäle erstellen oder ihnen beitreten können. Sehen Sie sich die Spieldemo unten an:

**Haben Sie Vorschläge oder Fragen zum Inhalt dieses Beitrags? Melden Sie sich unter [devrel@pubnub.com.](mailto:devrel@pubnub.com)**

#### Inhalt

[Konzepte für](#h-0)[Echtzeit-Multiplayer-SpieleApp-ÜbersichtLobby](#h-2)[einrichtenKanal](#h-3)[erstellenKanal](#h-4)[beitretenSpiel](#h-5)[startenSpielfunktionen](#h-6)[erstellenUmgebung](#h-7)[hinzufügenLogik](#h-8)[hinzufügenNeue Runde](#h-9)[startenSpiel](#h-10) starten

Wie kann PubNub dir helfen?
===========================

Dieser Artikel wurde ursprünglich auf [PubNub.com](https://www.pubnub.com/blog/browser-based-multiplayer-tic-tac-toe-game-in-react/) veröffentlicht.

Unsere Plattform unterstützt Entwickler bei der Erstellung, Bereitstellung und Verwaltung von Echtzeit-Interaktivität für Webanwendungen, mobile Anwendungen und IoT-Geräte.

Die Grundlage unserer Plattform ist das größte und am besten skalierbare Echtzeit-Edge-Messaging-Netzwerk der Branche. Mit über 15 Points-of-Presence weltweit, die 800 Millionen monatlich aktive Nutzer unterstützen, und einer Zuverlässigkeit von 99,999 % müssen Sie sich keine Sorgen über Ausfälle, Gleichzeitigkeitsgrenzen oder Latenzprobleme aufgrund von Verkehrsspitzen machen.

PubNub erleben
--------------

Sehen Sie sich die [Live Tour](https://www.pubnub.com/tour/introduction/) an, um in weniger als 5 Minuten die grundlegenden Konzepte hinter jeder PubNub-gestützten App zu verstehen

Einrichten
----------

Melden Sie sich für einen [PubNub-Account](https://admin.pubnub.com/signup/) an und erhalten Sie sofort kostenlosen Zugang zu den PubNub-Schlüsseln

Beginnen Sie
------------

Mit den [PubNub-Dokumenten](https://www.pubnub.com/docs) können Sie sofort loslegen, unabhängig von Ihrem Anwendungsfall oder [SDK](https://www.pubnub.com/docs)