Browser-basiertes Multiplayer Tic Tac Toe Spiel in React

PubNub Developer Relations - Mar 10 - - Dev Community

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, aber alle unten aufgeführten Schritte und Prinzipien sind immer noch gültig

Tic Tac Toe ist ein typisches Kinderspiel. 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 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, SignalR oder WebSockets 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 zu betreiben. Mit mehr als 70 SDKs, 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 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 GameDie 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

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

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

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 zu erhalten.

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

// 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;
Enter fullscreen mode Exit fullscreen mode

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.

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>
);
Enter fullscreen mode Exit fullscreen mode

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 befindet sich die Square-Komponente. 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(). Die Zeichenkette wird an 'tictactoelobby-'_angehängt,_was der eindeutige Lobby-Kanal sein wird, den die Spieler abonnieren.

// 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
  });
}
Enter fullscreen mode Exit fullscreen mode

Um zu verhindern, dass mehr als zwei Spieler einem bestimmten Kanal beitreten, verwenden wir Presence. 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

Dieses Modal und alle in dieser Anwendung verwendeten Modals werden von SweetAlert2 erstellt, um die standardmäßigen alert()-Popup-Boxen von JavaScript zu ersetzen.

// 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'
  }
})
Enter fullscreen mode Exit fullscreen mode

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

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

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

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.

// 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);
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

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

// Join a room channel
joinRoom = (value) => {
  this.roomId = value;
  this.lobbyChannel = 'tictactoelobby--' + this.roomId;
}
Enter fullscreen mode Exit fullscreen mode

Bevor der Spieler den Lobby-Kanal abonniert, müssen wir die Gesamtbelegung des Kanals mit hereNow() überprüfen. Wenn die Gesamtbelegung weniger als 2 beträgt, kann der Spieler den Lobby-Kanal erfolgreich abonnieren.

// 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);
});
Enter fullscreen mode Exit fullscreen mode

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().

// 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'
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

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.

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]
        });
      }
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

 this.setState({
   isPlaying: true
 });

 // Close the modals if they are opened
 Swal.close();
}
Enter fullscreen mode Exit fullscreen mode

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:

// 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;
Enter fullscreen mode Exit fullscreen mode

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.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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 arrayAls 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.

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);
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

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.

// 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)
}
Enter fullscreen mode Exit fullscreen mode

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

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;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 moveDie 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.

// 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);
}
Enter fullscreen mode Exit fullscreen mode

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.

// 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);
}
Enter fullscreen mode Exit fullscreen mode

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

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

Endgame modal for Player O

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.

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
        });
      }
    })
  }
 }
Enter fullscreen mode Exit fullscreen mode

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.

// 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]
  });
}
Enter fullscreen mode Exit fullscreen mode

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.

componentWillUnmount() {
  this.pubnub.unsubscribe({
    channels : [this.lobbyChannel, this.gameChannel]
  });
}
Enter fullscreen mode Exit fullscreen mode

Das ist alles, was wir tun müssen, damit das Spiel funktioniert! Die CSS-Datei für das Spiel ist im Repo 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 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 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

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.

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

npm start
Enter fullscreen mode Exit fullscreen mode

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

chmod +x dependencies.sh
Enter fullscreen mode Exit fullscreen mode

Führen Sie das Skript mit diesem Befehl aus:

./dependencies.sh
Enter fullscreen mode Exit fullscreen mode

Die Anwendung wird in http://localhost:3000 geöffnet und die Lobbykomponente wird angezeigt.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

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

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

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.

Inhalt

Konzepte fürEchtzeit-Multiplayer-SpieleApp-ÜbersichtLobbyeinrichtenKanalerstellenKanalbeitretenSpielstartenSpielfunktionenerstellenUmgebunghinzufügenLogikhinzufügenNeue RundestartenSpiel starten

Wie kann PubNub dir helfen?

Dieser Artikel wurde ursprünglich auf PubNub.com 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 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 an und erhalten Sie sofort kostenlosen Zugang zu den PubNub-Schlüsseln

Beginnen Sie

Mit den PubNub-Dokumenten können Sie sofort loslegen, unabhängig von Ihrem Anwendungsfall oder SDK

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .