Ten post, pochodzący z naszych archiwów, przeprowadzi Cię przez kroki tworzenia gry Tic Tac Toe w React, ale pamiętaj, że wersje używanych bibliotek nie są już najnowsze. W szczególności, artykuł ten wykorzystuje wersję 1 naszego React SDK, ale wszystkie kroki i zasady wymienione poniżej są nadal aktualne.
Gra w kółko i krzyżyk to kwintesencja dzieciństwa. Wszystko, czego wymaga, to coś do pisania i coś, czym można pisać. Ale co, jeśli chcesz zagrać z kimś, kto jest w innej lokalizacji? W takim przypadku musisz użyć aplikacji, która połączy ciebie i innego gracza z grą.
Aplikacja musi zapewniać wrażenia w czasie rzeczywistym, aby każdy twój ruch był natychmiast widoczny dla drugiego gracza i odwrotnie. Jeśli aplikacja nie zapewnia takich wrażeń, to ty i wiele innych osób prawdopodobnie nie będzie już z niej korzystać.
Jak więc deweloper może zapewnić połączone doświadczenie, w którym gracze mogą grać w kółko i krzyżyk lub w dowolną inną grę, bez względu na to, gdzie się znajdują?
Koncepcje gier wieloosobowych w czasie rzeczywistym
Istnieje kilka sposobów na zapewnienie infrastruktury czasu rzeczywistego dla gier wieloosobowych. Można pójść drogą budowania własnej infrastruktury od podstaw, korzystając z technologii i protokołów open-source, takich jak Socket.IO, SignalR lub WebSockets.
Chociaż może się to wydawać atrakcyjną drogą, napotkasz kilka problemów; jednym z nich jest skalowalność. Obsługa 100 użytkowników nie jest trudna, ale jak poradzić sobie z ponad 100 000 użytkowników? Poza kwestiami związanymi z infrastrukturą, wciąż musisz martwić się o utrzymanie swojej gry.
Koniec końców, jedyną rzeczą, która się liczy, jest zapewnienie graczom wspaniałych wrażeń z gry. Ale jak rozwiązać problem infrastruktury? Tutaj z pomocą przychodzi PubNub.
PubNub zapewnia infrastrukturę czasu rzeczywistego do zasilania dowolnej aplikacji za pośrednictwem globalnej sieci strumieni danych. Dzięki ponad 70 zestawom SDK, w tym najpopularniejszym językom programowania, PubNub upraszcza wysyłanie i odbieranie wiadomości do dowolnego urządzenia w czasie poniżej 100 ms. Jest bezpieczny, skalowalny i niezawodny, dzięki czemu nie musisz martwić się o tworzenie i utrzymywanie własnej infrastruktury.
Aby pokazać, jak łatwo jest stworzyć grę wieloosobową przy użyciu PubNub, zbudujemy prostą grę React tic tac toe przy użyciu PubNub React SDK. W tej grze dwóch graczy połączy się z unikalnym kanałem gry, gdzie będą grać przeciwko sobie. Każdy ruch wykonany przez gracza zostanie opublikowany na kanale, aby zaktualizować planszę drugiego gracza w czasie rzeczywistym.
Przegląd aplikacji
Oto jak będzie wyglądać nasza aplikacja, gdy ją ukończymy.
Gracze najpierw dołączają do lobby, gdzie mogą utworzyć kanał lub dołączyć do kanału. Jeśli gracz utworzy kanał, otrzyma identyfikator pokoju do współdzielenia z innym graczem. Gracz, który utworzył kanał, staje się graczem X i wykona pierwszy ruch po rozpoczęciu gry.
Gracz, który dołączy do kanału z identyfikatorem pokoju, który otrzymał, staje się graczem O. Gracze mogą dołączać do kanałów tylko wtedy, gdy w kanale znajduje się jedna inna osoba. Jeśli na kanale znajduje się więcej niż jedna osoba, gra na tym kanale jest w toku i gracz nie będzie mógł do niej dołączyć. Gra rozpoczyna się, gdy na kanale znajduje się dwóch graczy.
Na koniec gry wynik zwycięzcy jest zwiększany o jeden punkt. Jeśli gra zakończy się remisem, żaden z graczy nie otrzyma punktu. Graczowi X wyświetlany jest komunikat modalny z prośbą o rozpoczęcie nowej rundy lub zakończenie gry. Jeśli gracz X kontynuuje grę, plansza resetuje się do nowej rundy. W przeciwnym razie gra kończy się i obaj gracze wracają do lobby.
Konfiguracja lobby
Zanim skonfigurujemy lobby, zarejestruj darmowe konto PubNub, aby uzyskać bezpłatne klucze Pub/Sub API z panelu administracyjnego PubNub.
Po otrzymaniu kluczy wstaw je do konstruktora 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;
Również w konstruktorze inicjalizowane są obiekty stanu i zmienne. Omówimy te obiekty i zmienne, gdy pojawią się w całym pliku. Wreszcie, zainicjowaliśmy PubNub na końcu konstruktora.
Wewnątrz metody renderowania i w instrukcji return dodajemy znaczniki dla komponentu 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>
);
Komponent Lobby składa się z tytułu, pustej planszy do gry w kółko i krzyżyk (nic się nie dzieje, gdy gracz naciska kwadraty) oraz przycisków "Utwórz_"_ i "Dołącz_". Ten komponent jest wyświetlany tylko wtedy, gdy wartość stanu _isPlaying jest fałszywa. Jeśli jest ustawiona na true, gra się rozpoczęła, a komponent zostanie zmieniony na komponent Game, który omówimy w drugiej części samouczka.
Komponent Board jest również częścią komponentu Lobby. Wewnątrz komponentu Board znajduje się komponent Square. Nie będziemy szczegółowo omawiać tych dwóch komponentów, aby skupić się na komponentach Lobby i Game.
Gdy gracz naciśnie przycisk "Utwórz", przycisk ten jest wyłączony, więc gracz nie może utworzyć wielu kanałów. Przycisk "Dołącz" nie jest wyłączony, na wypadek gdyby gracz zdecydował się dołączyć do kanału. Po naciśnięciu przycisku "Create" wywoływana jest metoda onPressCreate().
Tworzenie kanału
Pierwszą rzeczą, którą robimy w onPressCreate () jest wygenerowanie losowego identyfikatora łańcucha, który jest obcięty do 5 znaków. Robimy to za pomocą funkcji shortid(). Dołączamy ciąg do "_tictactoelobby-",_który będzie unikalnym kanałem lobby subskrybowanym przez graczy.
// 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
});
}
Aby zapobiec dołączeniu więcej niż dwóch graczy do danego kanału, używamy funkcji Presence. Później przyjrzymy się logice sprawdzania zajętości kanału.
Gdy gracz zasubskrybuje kanał lobby, wyświetlany jest modal z identyfikatorem pokoju, aby inny gracz mógł dołączyć do tego kanału.
Ten modal i wszystkie modale używane w tej aplikacji są tworzone przez SweetAlert2 w celu zastąpienia domyślnych wyskakujących okienek JavaScript alert().
// 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'
}
})
Pod koniec onPressCreate() zmieniamy wartości stanu, aby odzwierciedlić nowy stan aplikacji.
this.setState({
piece: 'X',
isRoomCreator: true,
isDisabled: true, // Disable the 'Create' button
myTurn: true, // Player X makes the 1st move
});
Gdy gracz utworzy pokój, musi poczekać, aż inny gracz dołączy do tego pokoju. Przyjrzyjmy się logice dołączania do pokoju.
Dołączanie do kanału
Gdy gracz naciśnie przycisk "Dołącz", wywoływana jest funkcja onPressJoin(). Graczowi wyświetlany jest modal z prośbą o wprowadzenie identyfikatora pokoju w polu wprowadzania.
Jeśli gracz wpisze identyfikator pokoju i naciśnie przycisk "OK", wywoływana jest metoda joinRoom(value ), gdzie value to identyfikator pokoju. Metoda ta nie jest wywoływana, jeśli pole wejściowe jest puste lub jeśli gracz naciśnie przycisk "Anuluj".
// 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);
}
})
}
Pierwszą rzeczą, którą robimy w joinRoom () jest dołączenie wartości do '_tictactoelobby-',_podobnie jak to zrobiliśmy w onPressCreate().
// Join a room channel
joinRoom = (value) => {
this.roomId = value;
this.lobbyChannel = 'tictactoelobby--' + this.roomId;
}
Zanim gracz zasubskrybuje kanał lobby, musimy sprawdzić całkowitą zajętość kanału za pomocą funkcji hereNow(). Jeśli całkowita zajętość jest mniejsza niż 2, gracz może pomyślnie zasubskrybować kanał lobby.
// 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);
});
Gdy gracz zasubskrybuje kanał lobby, wartość stanu elementu zostanie zmieniona na "O", a wiadomość zostanie opublikowana na tym kanale lobby. Wiadomość ta powiadamia gracza X, że do kanału dołączył inny gracz. Słuchacz wiadomości ustawiamy w funkcji componentDidUpdate(), do której przejdziemy wkrótce.
Jeśli całkowita zajętość jest większa niż 2, oznacza to, że gra jest w toku, a gracz próbujący dołączyć do kanału otrzyma odmowę dostępu. Poniższy kod znajduje się poniżej instrukcji if w funkcji 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'
}
})
}
Przyjrzyjmy się teraz funkcji componentDidUpdate().
Rozpoczęcie gry
W componentDidUpdate() sprawdzamy, czy gracz jest podłączony do kanału, czyli sprawdzamy, czy this.lobbyChannel nie ma wartości null. Jeśli nie ma wartości null, konfigurujemy nasłuchiwacza, który nasłuchuje wszystkich wiadomości przychodzących na kanał.
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]
});
}
});
}
}
Sprawdzamy, czy otrzymana wiadomość to msg.message.notRoomCreator, która jest publikowana przez gracza dołączającego do kanału. Jeśli tak, tworzymy nowy kanał, "tictactoegame-",_z _identyfikatorem pokoju dołączonym do ciągu znaków. Kanał gry jest używany do publikowania wszystkich ruchów wykonanych przez graczy, które zaktualizują ich plansze.
Wreszcie, po zasubskrybowaniu kanału gry, wartość stanu isPlaying jest ustawiana na true. Spowoduje to zastąpienie komponentu lobby komponentem gry.
this.setState({
isPlaying: true
});
// Close the modals if they are opened
Swal.close();
}
Po wyświetleniu komponentu gry chcemy zamknąć wszystkie modale, jeśli zostały otwarte, z komponentu Lobby, wykonując Swal.close().
Teraz, gdy mamy dwóch graczy podłączonych do unikalnego kanału gry, mogą zacząć grać w kółko i krzyżyk! W następnej sekcji zaimplementujemy interfejs użytkownika i logikę dla komponentu gry.
Tworzenie funkcji gry
Pierwszą rzeczą, którą robimy w Game.js, jest skonfigurowanie konstruktora bazowego:
// 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;
Dla obiektów stanu inicjalizujemy właściwość array squares, która służy do przechowywania pozycji gracza na planszy. Zostanie to wyjaśnione poniżej. Ustawiamy również wynik graczy na 0 i ustawiamy wartość whosTurn na myTurn, która jest inicjowana na true dla gracza X i false dla gracza O.
Wartości zmiennych turn i counter będą się zmieniać przez cały czas trwania gry. Na koniec gry wartość gameOver jest ustawiana na true.
Dodaj interfejs użytkownika
Następnie skonfigurujmy znaczniki dla komponentu Game wewnątrz metody renderowania.
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>
);
}
Pokazujemy wartość stanu w interfejsie użytkownika, aby poinformować graczy, czy to ich kolej na wykonanie ruchu, czy też kolej drugiego gracza. Wartość logiczna stanu whosTurn jest aktualizowana za każdym razem, gdy wykonywany jest ruch. Reszta interfejsu użytkownika składa się z komponentu planszy i wyniku gracza.
Dodaj logikę
Gdy gracz wykonuje ruch na planszy, wykonywane jest wywołanie onMakeMove(index), gdzie index jest pozycją, w której pion jest umieszczony na planszy. Plansza ma 3 rzędy i 3 kolumny, czyli łącznie 9 pól. Każde pole ma swoją unikalną wartość indeksu, zaczynając od wartości 0, a kończąc na wartości 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)
}
}
Po uzyskaniu stanu pól tablicy, instrukcja warunkowa jest używana do sprawdzenia, czy pole, którego dotknął gracz, jest puste i czy nadeszła jego kolej na wykonanie ruchu. Jeśli jeden lub oba warunki nie są spełnione, pion gracza nie jest umieszczany na polu. W przeciwnym razie pion gracza jest dodawany do pól tablicy w indeksie, na którym został umieszczony.
Na przykład, jeśli gracz X wykona ruch w wierszu 0, kolumnie 2, a instrukcja warunkowa jest prawdziwa, wówczas squares[2] będzie miało wartość "X".Następnie stan jest zmieniany, aby odzwierciedlić nowy stan gry, a tura jest aktualizowana, aby drugi gracz mógł wykonać swój ruch. Aby plansza drugiego gracza została zaktualizowana o bieżące dane, publikujemy je na kanale gry. Wszystko to dzieje się w czasie rzeczywistym, więc obaj gracze natychmiast zobaczą aktualizację swoich plansz, gdy tylko zostanie wykonany prawidłowy ruch. Ostatnią rzeczą do zrobienia w tej metodzie jest wywołanie checkForWinner(squares ), aby sprawdzić, czy jest zwycięzca.
Zanim to zrobimy, przyjrzyjmy się metodzie componentDidMount() , w której konfigurujemy odbiornik nowych wiadomości przychodzących do kanału gry.
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);
}
});
}
Ponieważ obaj gracze są podłączeni do tego samego kanału gry, obaj otrzymają tę wiadomość. Wywoływana jest metoda publishMove(index, piece ), gdzie index to pozycja, na której umieszczony został pion, a piece to pion gracza, który wykonał ruch. Ta metoda aktualizuje planszę o bieżący ruch i sprawdza, czy jest zwycięzca. Aby gracz, który wykonał bieżący ruch, nie musiał ponownie wykonywać tego procesu, instrukcja if sprawdza, czy pion gracza pasuje do wartości turn. Jeśli tak, jego plansza jest aktualizowana.
// 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)
}
Logika aktualizacji planszy jest taka sama jak onMakeMove(). Przejdźmy teraz do 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;
}
}
}
Wszystkie zwycięskie kombinacje znajdują się w podwójnej tablicy possibleCombinations, gdzie każda tablica jest możliwą kombinacją do wygrania gry. Każda tablica w possibleCombinations jest porównywana z tablicą kwadratów. Jeśli istnieje zgodność, to jest zwycięzca. Prześledźmy to na przykładzie, aby uczynić to bardziej przejrzystym.
Powiedzmy, że gracz X wykonuje zwycięski ruch w wierszu 2 kolumnie 0. Indeks tej pozycji wynosi 6. Plansza wygląda teraz następująco:
Zwycięska kombinacja dla gracza X to [2,4,6]. Tablica kwadratów została zaktualizowana do: ["O", "", "X", "O", "X", "", "X", "", ""].
W pętli for, gdy [a,b,c] mają wartości [2,4,6], instrukcja if w pętli for ma wartość true, ponieważ [2,4,6] mają tę samą wartość X. Wynik zwycięzcy musi zostać zaktualizowany, więc funkcja*announceWinner(* ) jest wywoływana w celu przyznania nagrody zwycięskiemu graczowi.
Jeśli gra zakończy się remisem, nie ma zwycięzcy w tej rundzie. Do sprawdzania remisów używamy licznika, który zwiększa się o jeden za każdym razem, gdy na planszy wykonywany jest ruch.
// 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);
}
Jeśli licznik osiągnie wartość 9, gra kończy się remisem, ponieważ gracz nie wykonał zwycięskiego ruchu na ostatnim polu planszy. W takim przypadku metoda newRound( ) jest wywoływana z argumentem null, ponieważ nie ma zwycięzcy.
Zanim przejdziemy do tej metody, wróćmy do funkcji*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);
}
Parametrem tej metody jest zwycięzca, czyli gracz, który wygrał grę. Sprawdzamy, czy zwycięzcą jest "X" lub "O" i zwiększamy wynik zwycięzcy o jeden punkt. Ponieważ gra jest zakończona, zmienna gameOver jest ustawiana na true i wywoływana jest metoda newRound ().
Rozpoczęcie nowej rundy
Gracz X ma możliwość rozegrania kolejnej rundy lub zakończenia gry i powrotu do lobby.
Drugi gracz musi poczekać, aż gracz X zdecyduje, co zrobić.
Gdy gracz X zdecyduje, co zrobić, wiadomość zostanie opublikowana na kanale gry, aby poinformować o tym drugiego gracza. Następnie aktualizowany jest interfejs użytkownika.
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
});
}
})
}
}
Jeśli wiadomość zostanie zresetowana, wszystkie wartości stanu i zmienne, z wyjątkiem wyniku dla graczy, zostaną zresetowane do wartości początkowych. Wszelkie otwarte modale są zamykane i rozpoczyna się nowa runda dla obu graczy.
W przypadku komunikatu endGame wszystkie modale są zamykane i wywoływana jest metoda endGame(). Metoda ta znajduje się w 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]
});
}
Wszystkie wartości stanu i zmienne są resetowane do wartości początkowych. Nazwy kanałów są resetowane do wartości null, ponieważ nowa nazwa jest generowana za każdym razem, gdy gracz tworzy pokój. Ponieważ nazwy kanałów nie będą już przydatne, gracze wypisują się zarówno z lobby, jak i kanału gry. Wartość isPlaying jest resetowana do false, więc komponent gry zostanie zastąpiony komponentem lobby.
Ostatnią metodą do włączenia w App.js jest componentWillUnmount(), która wypisuje graczy z obu kanałów.
componentWillUnmount() {
this.pubnub.unsubscribe({
channels : [this.lobbyChannel, this.gameChannel]
});
}
To wszystko, co musimy zrobić, aby gra działała! Plik CSS dla gry można pobrać z repozytorium. Teraz uruchommy grę.
Uruchom grę
Istnieje kilka małych kroków, które musimy wykonać przed uruchomieniem gry. Po pierwsze, musimy włączyć funkcję Presence, ponieważ używamy jej do uzyskania liczby osób na kanale (użyliśmy withPresence podczas subskrybowania kanału lobby). Przejdź do panelu administratora PubNub i kliknij swoją aplikację. Kliknij na Keyset i przewiń w dół do Application add-ons. Przełącz przełącznik Presence na on. Zachowaj domyślne wartości bez zmian.
Aby zainstalować trzy zależności używane w aplikacji i uruchomić aplikację, możesz uruchomić skrypt dependencies.sh, który znajduje się w katalogu głównym aplikacji.
# dependencies.sh
npm install --save pubnub pubnub-react
npm install --save shortid
npm install --save sweetalert2
npm start
W terminalu przejdź do katalogu głównego aplikacji i wpisz następujące polecenie, aby skrypt był wykonywalny:
chmod +x dependencies.sh
Uruchom skrypt za pomocą tego polecenia:
./dependencies.sh
Aplikacja otworzy się w http://localhost:3000 z wyświetlonym komponentem lobby.Otwórz inną kartę, a najlepiej okno, i skopiuj i wklej http://localhost:3000. W jednym oknie utwórz kanał, klikając przycisk "Utwórz". Pojawi się okno modalne wyświetlające identyfikator pokoju. Skopiuj i wklej ten identyfikator. Przejdź do drugiego okna i kliknij przycisk "Dołącz". Gdy pojawi się okno modalne, wpisz identyfikator pokoju w polu wprowadzania i naciśnij przycisk "OK".
Gdy gracze zostaną połączeni, rozpocznie się gra. Okno użyte do utworzenia kanału wykona pierwszy ruch. Naciśnij dowolne pole na planszy i zobacz, jak pion X jest wyświetlany na planszy w czasie rzeczywistym dla obu okien. Jeśli spróbujesz nacisnąć inne pole na tej samej planszy, nic się nie stanie, ponieważ nie jest to już Twoja kolej na wykonanie ruchu. W drugim oknie naciśnij dowolne pole na planszy, a pion O zostanie umieszczony na tym polu.
Kontynuuj grę do momentu wyłonienia zwycięzcy lub remisu. Następnie wyświetlony zostanie komunikat modalny ogłaszający zwycięzcę rundy lub informujący, że gra zakończyła się remisem. W tym samym oknie gracz X będzie musiał zdecydować, czy kontynuować grę, czy ją zakończyć. Modal dla gracza O powie mu, aby poczekał na nową rundę.
Wszystko, z wyjątkiem wyniku, zostanie zresetowane, jeśli gracz X będzie kontynuował grę. W przeciwnym razie obaj gracze są przenoszeni z powrotem do lobby, gdzie mogą tworzyć lub dołączać do nowych kanałów. Poniżej znajduje się demo gry:
Masz sugestie lub pytania dotyczące treści tego wpisu? Skontaktuj się z nami pod adresem [email protected].
Spis treści
Koncepcje gry wieloosobowej w czasierzeczywistymPrzegląd aplikacjiUstawlobbyUtwórz kanałDołącz dokanałuRozpocznijgręZbudujfunkcje gryDodajUIAdodajlogikęRozpocznijnową rundęUruchomgrę
Jak PubNub może ci pomóc?
Ten artykuł został pierwotnie opublikowany na PubNub.com
Nasza platforma pomaga programistom tworzyć, dostarczać i zarządzać interaktywnością w czasie rzeczywistym dla aplikacji internetowych, aplikacji mobilnych i urządzeń IoT.
Podstawą naszej platformy jest największa w branży i najbardziej skalowalna sieć komunikacyjna w czasie rzeczywistym. Dzięki ponad 15 punktom obecności na całym świecie obsługującym 800 milionów aktywnych użytkowników miesięcznie i niezawodności na poziomie 99,999%, nigdy nie będziesz musiał martwić się o przestoje, limity współbieżności lub jakiekolwiek opóźnienia spowodowane skokami ruchu.
Poznaj PubNub
Sprawdź Live Tour, aby zrozumieć podstawowe koncepcje każdej aplikacji opartej na PubNub w mniej niż 5 minut.
Rozpocznij konfigurację
Załóż konto PubNub, aby uzyskać natychmiastowy i bezpłatny dostęp do kluczy PubNub.
Rozpocznij
Dokumenty PubNub pozwolą Ci rozpocząć pracę, niezależnie od przypadku użycia lub zestawu SDK.