Reactによるブラウザベースのマルチプレイヤー三目並べゲーム

PubNub Developer Relations - Mar 10 - - Dev Community

この記事では、ReactでTic Tac Toeゲームを作成する手順について説明します。 特に、この記事ではReact SDKのバージョン1を使用していますが、以下の手順と原則はすべて現在でも有効です。

三目並べは、子供の頃のゲームの典型です。必要なのは、書くものと書くものだけです。しかし、別の場所にいる人と遊びたい場合はどうすればいいのだろう?この場合、あなたと他のプレイヤーをゲームにつなぐアプリケーションを使う必要があります。

そのアプリケーションは、あなたの一挙手一投足が瞬時に相手に伝わり、またその逆もしかりとなるようなリアルタイム体験を提供する必要がある。もしアプリケーションがこのような体験を提供しないのであれば、あなただけでなく多くの人はもうそのアプリケーションを使うことはないでしょう。

では、どうすれば開発者は、プレイヤーが世界中どこにいても、チックタック・トゥやどんなゲームでもプレイできるようなコネクテッド体験を提供できるのでしょうか?

リアルタイム・マルチプレイヤー・ゲームのコンセプト

マルチプレイヤーゲームのリアルタイムインフラを提供するには、いくつかの方法がある。Socket.IOSignalRWebSocketなどの技術やオープンソースのプロトコルを使用して、独自のインフラを一から構築するルートがあります。

これは一見魅力的なルートに見えるかもしれないが、いくつかの問題が発生する。100人のユーザーを扱うのは難しくないが、10万人以上のユーザーをどう扱うのか?インフラの問題に加えて、ゲームのメンテナンスも心配しなければならない。

結局のところ、重要なのはゲームのプレイヤーに素晴らしい体験を提供することだけです。しかし、インフラの問題はどのように解決するのでしょうか?そこでPubNubの登場です。

PubNubは、グローバルなデータストリームネットワークを通じて、あらゆるアプリケーションに電力を供給するリアルタイムインフラを提供します。 最も一般的なプログラミング言語を含む70以上のSDKを備えたPubNubは、100ミリ秒未満であらゆるデバイスへのメッセージの送受信を簡素化します。セキュアでスケーラブル、かつ信頼性が高いため、独自のインフラストラクチャの作成や保守を心配する必要はありません。

PubNubを使用してマルチプレイヤーゲームを開発することがいかに簡単であるかを示すために、PubNub React SDKを使用して簡単なReactチックタックトゥゲームを構築します。このゲームでは、2人のプレイヤーがユニークなゲームチャンネルに接続して対戦します。プレイヤーの一挙手一投足がチャンネルに公開され、相手のボードをリアルタイムで更新します。

アプリの概要

完成後のアプリはこんな感じになります。

Screen shot of the React Tic Tac Toe Gameプレイヤーはまずロビーに参加し、そこでチャンネルを作成するか、チャンネルに参加します。チャンネルを作成したプレイヤーはルームIDを取得し、他のプレイヤーと共有します。チャンネルを作成したプレイヤーはプレイヤーXとなり、ゲーム開始時に先手を取ります。

Create a room channel

与えられたルームIDでチャンネルに参加したプレイヤーはプレイヤーOとなります。複数人がいる場合、そのチャンネルではゲームが進行中であり、プレイヤーは参加できません.チャンネルにプレーヤーが2人いるとゲームが開始されます。

Join the room channel

ゲーム終了時に勝者の得点が1点加算されます。ゲームが引き分けに終わった場合、どちらのプレイヤーにも得点は与えられません。プレイヤーXに新しいラウンドを始めるかゲームを終了するかのモーダルが表示される。プレイヤーXがゲームを続行する場合、ボードは新しいラウンドのためにリセットされます。そうでない場合、ゲームは終了し、両プレイヤーはロビーに戻ります。

Exit to lobby

ロビーのセットアップ

ロビーをセットアップする前に、無料のPubNubアカウントにサインアップしてPubNubの管理ダッシュボードから無料のPub/Sub APIキーを取得してください。

キーを取得したらApp.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;
Enter fullscreen mode Exit fullscreen mode

また、コンストラクタの中で状態オブジェクトと変数を初期化する。オブジェクトと変数についてはこのファイルを通して説明する。最後にコンストラクタの最後でPubNubを初期化する。

renderメソッドの中とreturn文の中にLobbyコンポーネントのマークアップを追加する。

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

Lobbyコンポーネントはタイトル、空のチックタック・トゥ・ボード(プレイヤーがマスを押しても何も起こらない)、「Create_」と「Join」_ボタンで構成される。このコンポーネントは、ステート値_isPlayingが_falseの場合のみ表示されます。もしisPlayingがtrueに設定されていれば、ゲームは開始され、コンポーネントはGameコンポーネントに変更されます。

BoardコンポーネントはLobbyコンポーネントの一部でもある。Boardコンポーネントの中にSquareコンポーネントがあります。ロビーコンポーネントとゲームコンポーネントに集中するために、この2つのコンポーネントの詳細には触れません。

プレイヤーが'Create'ボタンを押すと、ボタンは無効化され、複数のチャンネルを作ることはできません。Join'ボタンは無効化されませんが、これはプレイヤーがチャンネルに参加する場合に備えています。Create」ボタンが押されると、onPressCreate()メソッドが呼び出されます。

チャンネルの作成

onPressCreate() で最初にすることは、5文字に切り詰めたランダムな文字列IDを生成することです。これはshortid()を使って行う。この文字列を'tictactoelobby-'に追加することで、プレイヤーが購読するユニークなロビーチャンネルとなります。

// 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

つのチャンネルに2人以上のプレイヤーが参加できないようにするために、Presenceを使用します。後ほど、チャンネルの占有率をチェックするロジックについて説明します。

プレイヤーがロビーチャンネルに登録すると、ルーム ID がモーダルで表示され、他のプレイヤーがそのチャンネルに参加できるようになります。

Share the room id

このモーダルと、このアプリで使用されているすべてのモーダルは、JavaScript のデフォルトの alert() ポップアップ ボックスを置き換えるために、SweetAlert2によって作成されています。

// 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

onPressCreate()の最後で、アプリの新しい状態を反映するために状態の値を変更します。

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

プレイヤーがルームを作成したら、他のプレイヤーがそのルームに参加するのを待つ必要があります。ルームに参加するためのロジックを見てみましょう。

チャンネルに参加する

プレイヤーが'Join'ボタンを押すと、onPressJoin()が呼び出されます。入力フィールドにルームIDを入力するよう、プレイヤーにモーダルが表示されます。

Enter the room id

プレイヤーがルーム IDを入力して「OK」ボタンを押すと、joinRoom(value) 呼び出されます。このメソッドは入力フィールドが空の場合や、プレイヤーが「キャンセル」ボタンを押した場合には呼び出されません。

// 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

joinRoom()で最初に行うことは、onPressCreate()で行ったことと同様に、valueを'tictactoelobby-'に追加することです。

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

プレイヤーがロビーチャンネルに登録する前に、hereNow()を使用してチャンネルの総占有率をチェックする必要があります。 総占有率が2未満であれば、プレイヤーはロビーチャンネルに登録することができます。

// 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

プレイヤーがロビーチャンネルに登録すると、pieceの状態が'O'に変更され、そのロビーチャンネルにメッセージが発行される。このメッセージは、他のプレイヤーがチャンネルに参加したことをプレイヤーXに通知します。メッセージリスナーはcomponentDidUpdate()で設定します。

総占有率が2より大きい場合、ゲームが進行中であり、チャンネルに参加しようとしたプレイヤーはアクセスを拒否されます。以下のコードはhereNow()のif文の下にあります。

// 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

次に、componentDidUpdate()を見てみましょう。

ゲームの開始

componentDidUpdate()では、プレイヤーがチャンネルに接続しているかどうか、つまりthis.lobbyChannelが nullでないかどうかをチェックします。もしnullでなければ、チャンネルに届くすべてのメッセージをリスニングするリスナーをセットアップします。

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

到着したメッセージがmsg.message.notRoomCreatorであるかどうかをチェックします。もしそうなら、ルームIDを文字列に追加した新しいチャンネル「tictactoegame-」を作成します。ゲームチャンネルは、プレーヤーが行ったすべての手を公開するために使用されます。

最後に、ゲームチャンネルを購読した後、isPlayingの状態をtrueに設定します。そうすることで、ロビーコンポーネントがゲームコンポーネントに置き換わります。

 this.setState({
   isPlaying: true
 });

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

ゲーム・コンポーネントが表示されたら、Swal.close()を実行して、ロビー・コンポーネントからすべてのモーダルを閉じます。

これで、2人のプレイヤーがユニークなゲームチャンネルに接続され、チックタックトーを始めることができます!次のセクションでは、ゲームコンポーネントのUIとロジックを実装します。

ゲーム機能のビルド

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

ステート・オブジェクトのために、配列のsquaresプロパティを初期化する。これについては後ほど説明する。また、プレイヤーのスコアを0に設定し、whosTurnの値をmyTurnに設定する。これは、プレイヤーXの場合はtrueプレイヤーOの場合はfalseに初期化される。

変数turnと counterの値は、ゲームの進行を通じて変化する。ゲームが終了すると、gameOverが trueに設定される。

UIの追加

次に、Gameコンポーネントのマークアップをrenderメソッドの中で設定する。

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

プレイヤーに自分の番なのか、相手の番なのかを知らせるために、ステータスの値をUIに表示します。whosTurnステートのブール値は、移動が行われるたびに更新されます。残りのUIは、ボードコンポーネントとプレイヤーのスコアで構成されています。

ロジックの追加

プレイヤーがボード上で手を打つとき、onMakeMove( index)の呼び出しが行われます。ボードは3行3列なので、全部で9マスあります。各マスには固有のインデックス値があり、0から始まり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

配列されたマスの状態を取得した後、条件文を使って、プレイヤーがタッチしたマスが空かどうか、そして自分の手番かどうかをチェックする。どちらかの条件が満たされなければ、そのマスに駒は置かれない。そうでない場合、そのプレイヤーの駒は、その駒が置かれたインデックスの配列マスに追加されます。

例えば、プレーヤーXが_0行2列目に着手し、条件文が真であれば、_squares[2] は "X "となります。Example with the squares array次に、ゲームの新しい状態を反映するように状態が変更され、手番が更新され、他のプレーヤーが手を打つことができるようになります。相手の碁盤が現在のデータで更新されるように、ゲームチャンネルにデータを公開します。この処理はすべてリアルタイムで行われるため、有効な手が打たれるとすぐに両プレイヤーは自分の碁盤が更新されるのを見ることができる。このメソッドで最後に行うことは、checkForWinner(squares)を呼び出して勝者がいるかどうかをチェックすることです。

その前に、componentDidMount() を見てみよう。ここでは、ゲームチャンネルに届いた新しいメッセージのリスナーをセットアップする。

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

両プレイヤーは同じゲームチャンネルに接続しているので、どちらもこのメッセージを受信します。メソッドpublishMove(index, piece)が呼ばれます。indexは駒が置かれた位置、pieceは移動を行ったプレイヤーの駒です。このメソッドは、現在の手でボードを更新し、勝者がいるかどうかをチェックします。現在の手を打ったプレーヤーがこの処理をもう一度やり直す必要がないように、if文はプレーヤーの駒がturnの値と一致するかどうかをチェックする。もし一致すれば、そのプレーヤーのボードが更新される。

// 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

盤面を更新するロジックはonMakeMove()と同じです。それでは、checkForWinner()について説明します。

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

すべての勝ちの組み合わせは二重配列のpossibleCombinationsにあり、すべての配列がゲームに勝つ可能性のある組み合わせです。possibleCombinationsのすべての配列は、配列squaresと照合されます。もし一致すれば勝ちとなる。これをより明確にするために、例に従って説明しよう。

プレイヤーXが2行目0列目に勝ち筋を作ったとする。そのポジションの_インデックスは_6である:

Example of a winning moveプレイヤーXの勝ちの組み合わせは[2,4,6]である。配列の正方形は次のように更新される:配列は次のように更新される: ["o", "", "x", "o", "x", "", "x", "", ""]。

for ループの中で、[a,b,c]が[2,4,6]の値を持つとき、[2,4,6]は すべて 同じXの値を持つので、forループの中のif文は真になります。勝者のスコアを更新する必要があるので、勝者に賞を与えるためにannounceWinner()が呼ばれます。

ゲームが引き分けで終わった場合、そのラウンドの勝者はいない。引き分けかどうかをチェックするために、碁盤上で手が打たれるたびに1ずつ増加するカウンタを使用します。

// 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

もしカウンタが9に達したら、そのプレイヤーは碁盤の最後のマスで勝利の手を打たなかったので、ゲームは引き分けに終わります。この場合、勝者はいないので、newRound()メソッドはnull引数で呼ばれます。

このメソッドに行く前に、announceWinner()に戻りましょう。

// 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

このメソッドのパラメータは勝者です。勝者が'X'か'O'かをチェックし、勝者の得点を1点増やします。ゲームが終了したので、変数_gameOverに_trueがセットされ、メソッドnewRound()が呼び出されます。

新しいラウンドの開始

プレイヤーXは別のラウンドをプレイするか、ゲームを終了してロビーに戻るかを選択できます。

Endgame modal for Player X

もう一方のプレイヤーは、プレイヤーXがどうするか決めるまで待つように言われています。

Endgame modal for Player O

プレイヤーXがどうするか決めると、ゲームチャンネルにメッセージが公開され、相手プレイヤーに知らせる。その後、UIが更新される。

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

メッセージがリセットされた場合、プレイヤーのスコア以外のすべてのステート値と変数は初期値にリセットされます。まだ開いているモダルはすべて閉じられ、両プレイヤーにとって新しいラウンドが始まります。

endGameというメッセージでは、すべてのモダルが閉じられ、endGame()というメソッドが呼ばれます。このメソッドは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

ステートの値と変数はすべて初期値にリセットされます。チャンネル名はnullにリセットされます。プレイヤーが部屋を作成するたびに新しい名前が生成されるからです。チャンネル名はもう役に立たないので、プレイヤーはロビーとゲームチャンネルの両方から退会します。isPlayingの値はfalseにリセットされるので、ゲームコンポーネントはロビーコンポーネントに置き換えられます。

App.jsに含める最後のメソッドはcomponentWillUnmount()で、両方のチャンネルからプレイヤーの登録を解除します。

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

ゲームが動作するために必要なことはこれだけです!ゲームのCSSファイルはレポで入手できます。では、ゲームを起動して動かしてみよう。

ゲームを動かす

ゲームを実行する前に、いくつか小さなステップを踏む必要がある。まず、Presence機能を有効にする必要があります。Presence機能はチャンネルに参加している人数を取得するために使います(ロビーチャンネルを購読するときにwithPresenceを使いました)。PubNub管理者ダッシュボードに行き、自分のアプリケーションをクリックします。Keysetをクリックし、Application add-onsまでスクロールダウンします。Presence スイッチをオンに切り替えます。デフォルト値はそのままにしてください。

Enable presence in PubNub Admin Dashboard

アプリで使用される3つの依存関係をインストールし、アプリを実行するには、アプリのルート・ディレクトリにあるdependencies.shスクリプトを実行します。

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

npm start
Enter fullscreen mode Exit fullscreen mode

ターミナルでアプリのルート・ディレクトリに移動し、以下のコマンドを入力してスクリプトを実行可能にしてください:

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

このコマンドでスクリプトを実行する:

./dependencies.sh
Enter fullscreen mode Exit fullscreen mode

Run the React app locally別のタブ、またはできればウィンドウを開き、http://localhost:3000 をコピー&ペーストしてください。1つのウィンドウで'Create'ボタンをクリックしてチャンネルを作成します。ルームIDを表示するモーダルがポップアップします。そのIDをコピー&ペーストしてください。もう一つのウィンドウで「Join」ボタンをクリックします。モーダルがポップアップしたら、入力フィールドにルームIDを 入力し、'OK'ボタンを押してください。

Create and join the channel

プレイヤーが接続されると、ゲームが始まります。チャンネル作成に使用したウインドウが先手を取ります.盤上の任意のマスを押すと、両方のウィンドウの駒Xが リアルタイムで盤上に表示されます。同じ盤面の別のマスを押しても、もうあなたの手番ではないので何も起こりません。もう一方のウインドウでは、盤面のどのマスを押しても、駒Oがそのマスに置か れます

Place the piece on the board

勝敗が決するか引き分けになるまで続けます。モーダルが表示され、そのラウンドの勝者、または引き分けに終わったことが告げられます。同じモーダルで、プレイヤーXは ゲームを続けるか終了するかを決めなければならない。プレイヤー O のモーダルは、新しいラウンドを待つように指示する。

End of game modals

プレイヤーXが ゲームを続行した場合、スコア以外のすべてがリセットされる。そうでない場合、両プレイヤーはロビーに戻り、そこで新しいチャンネルを作成したり、参加したりすることができます。ゲームデモは以下からご覧いただけます:

この記事の内容に関して提案や質問がありますか?devrel@pubnub.com までご連絡ください。

コンテンツ

リアルタイム・マルチプレイ・ゲームのコンセプトアプリの概要ロビーのセットアップチャンネルの作成チャンネルへの参加ゲームの開始ゲーム機能の構築UIAの追加ロジックの追加新しいラウンドの開始ゲームの実行

PubNubはどのようにお役に立ちますか?

この記事はPubNub.comに掲載されたものです。

PubNubのプラットフォームは、開発者がウェブアプリ、モバイルアプリ、IoTデバイス向けにリアルタイムのインタラクティブ機能を構築、提供、管理できるよう支援します。

私たちのプラットフォームの基盤は、業界最大かつ最もスケーラブルなリアルタイムエッジメッセージングネットワークです。世界15か所以上で8億人の月間アクティブユーザーをサポートし、99.999%の信頼性を誇るため、停電や同時実行数の制限、トラフィックの急増による遅延の問題を心配する必要はありません。

PubNubを体験

ライブツアーをチェックして、5分以内にすべてのPubNub搭載アプリの背後にある本質的な概念を理解する

セットアップ

PubNubアカウントにサインアップすると、PubNubキーに無料ですぐにアクセスできます。

始める

PubNubのドキュメントは、ユースケースやSDKに関係なく、あなたを立ち上げ、実行することができます。

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