Автор оригинала: Wern Ancheta.
В этом уроке мы будем реализовывать классическую игру Tic-Tac-Tac-носок с реактивным родным и толкателем. В этом руководстве предполагается, что у вас уже есть базовые знания оригинального родного.
Предпосылки
Учетная учетная запись – Нужна учетная запись PUSHER для создания экземпляра топирования, который мы будем использовать в этом руководстве. Если у вас еще нет аккаунта, вы можете Зарегистрируйтесь здесь Отказ После создания учетной записи вы можете пойти дальше и создать новое приложение Pusher.
Android SDK – Мы будем конкретно развертывать приложение в качестве приложения для Android, поэтому вам нужен Android SDK для запуска приложения на устройстве или эмуляторе Android.
Машина, которая готова к реагированию нативного развития – Если у вас еще нет ваша машина для реагирования на родных, вы можете следовать за Начало работы Руководство на официальных документах. Обязательно следуйте инструкциям в вкладке «Строительные проекты с нативным кодом».
Генимот или эмулятор Android – Это необязательно, так как вы всегда можете использовать настоящее устройство для тестирования.
Что мы собираемся построить
Вот что приложение будет выглядеть по умолчанию:
Когда пользователь решил создать комнату, идентификатор комнаты будет сгенерирован приложением. Идентификатор этого номера должен быть введен другим пользователем, поэтому игра может начать. На протяжении всего этого учебника я буду ссылаться на пользователя, кроме текущего пользователя, как «соперник».
Как только кто-то присоединился к комнате, будет показан доска Tic-Tac-Toe. На данный момент любой из игроков может начать первый ход.
После того, как последний шаг используется для заполнения доски, будет отображаться предупреждение в Comment Creator, спрашивая, хотят ли они перезапустить игру (опустошить доску и запускать) или завершить игру. Если создатель комнаты решил закончить игру, состояние приложения будет сброшено, а экран по умолчанию будет показан.
Вы можете найти полный исходный код приложения в его Github Repo Отказ
Кодирование компонента сервера
Компонент сервера аутентифицирует запросы, которые будут приходить из приложения. Это необходимо, потому что мы будем использовать События клиента отправлять данные от клиента клиенту. Компонент сервера аутентифицирует запрос всякий раз, когда приложение пытается подключиться с использованием ключа API приложенного вами созданного ранее. Таким образом, вы можете проверить, был ли запрос действительно пришел из вашего приложения.
Начните с помощью инициализации Package.json
файл:
npm init
Установите зависимости:
npm install --save express body-parser pusher dotenv
Создать .env
Файл в той же папке, что и Package.json
Файл и добавьте детали приложения PUSHER:
APP_ID="YOUR PUSHER APP ID" APP_KEY="YOUR PUSHER APP KEY" APP_SECRET="YOUR PUSHER APP SECRET" APP_CLUSTER="YOUR PUSHER APP CLUSTER"
Создать server.js
Файл и добавьте следующий код:
var express = require('express'); // for running a server var bodyParser = require('body-parser'); // for processing JSON submitted in the request body var Pusher = require('pusher'); // for connecting to Pusher require('dotenv').config(); var app = express(); app.use(bodyParser.json()); // for parsing JSON strings passed in the request body app.use(bodyParser.urlencoded({ extended: false })); // for parsing URL encoded request body var pusher = new Pusher({ // connect to pusher appId: process.env.APP_ID, // load the Pusher app settings from the .env file key: process.env.APP_KEY, secret: process.env.APP_SECRET, cluster: process.env.APP_CLUSTER, }); app.get('/', function(req, res){ // for testing if the server is running res.send('everything is good...'); }); app.post('/pusher/auth', function(req, res) { // authenticate user's who's trying to connect var socketId = req.body.socket_id; var channel = req.body.channel_name; var auth = pusher.authenticate(socketId, channel); res.send(auth); }); var port = process.env.PORT || 5000; app.listen(port);
Вот что делает код выше:
Линия 1 Импорт
[Express] (https://expressjs.com/)
, веб-каркас для Node.js, которая позволяет нам создавать сервер и отвечать на определенные маршруты.Линия 2 Импорт
Тело-парсер
, промежуточное программное обеспечение для анализа тела запроса, так что данные, передаваемые в тело запроса, могут быть доступны как объект. Например, в/pusher/auth
Маршрут, это позволяет сделать следующее для доступа к идентификатору сокета из тела запроса:req.body.socket_id
ОтказЛиния 3 Импортирует
толкатель
упаковка. Это позволяет нам взаимодействовать с приложением Pusher, которое вы создали ранее, чтобы аутентифицировать пользователя (строку 25).Линия 5 Импортирует
Доценв
Пакет, который загружает конфигурацию в.env
файл, который вы создали ранее. Вы можете видеть их доступ к в качестве переменных среды на линиях от 12 до 15.Строки 7 до 9 Рассказывает экспресс использовать
Тело-парсер
Чтобы создать два разных завода промежуточного программного обеспечения, один для разборки строк JSON, а другой для разборки URL-кодированных строк.Расширенные
Опция установлена наложь
Поскольку мы не очень ожидаем, что насыщенные объекты и массивы будут включены в тело запроса. Вместо этого мы только ожидаем пропущенные строки JSON в тело запроса.Линии от 18 до 20 Для тестирования, если сервер работает, вы можете получить доступ к
http://localhost: 5000
из вашего браузера. Если вы видите, что вывод строки «Все хорошо …», то он работает.Строки 22 до 27 Для обработки запросов аутентификации, исходящие из приложения. Запрос аутентификации отправляется каждый раз, когда клиент подключается к Pusher от приложения, которую мы будем создавать. Обратите внимание, что код для аутентификации пользователей на самом деле не имеет никаких мер безопасности. Это означает, что кто-либо может просто использовать свое приложение Pusher, если они получают удержание ваших учетных данных приложений Pusher.
Кодирование приложения
Теперь мы готовы добавить код для приложения. Первый загрузчик новое нативное приложение raction:
react-native init RNPusherTicTacToe
Как только это будет сделано, вы теперь можете установить зависимости:
npm install --save lodash.range pusher-js react-native-prompt shortid react-native-spinkit@latest
Из этих зависимостей React Nature Spinkit имеет некоторые активы, которые должны быть связаны, поэтому выполните следующую команду, чтобы связать те:
react-native link
Вот как установлены пакеты, которые вы только что установили, используются в приложении:
- PUSHER-JS – для использования толкателя. Это позволяет нам отправлять сообщения на каналы и получать сообщения из каналов в режиме реального времени.
- Реагистративно-подсказка – Для отображения подсказки используется для получения пользовательского ввода.
- React-Native-Spinkit – Чтобы показать спиннер, ожидая другого игрока, чтобы присоединиться к комнате.
- lodash.range – для генерации массивов, которые имеют определенное количество предметов.
- Подробнее – Для создания уникальных идентификаторов при создании комнаты.
Теперь мы готовы добавить код для приложения. Во-первых, откройте index.android.js
Файл и замените код по умолчанию следующим образом:
import React, { Component } from 'react'; import { AppRegistry } from 'react-native'; import Main from './components/Main'; export default class RNPusherTicTacToe extends Component { render() { return ( ); } } AppRegistry.registerComponent('RNPusherTicTacToe', () => RNPusherTicTacToe);
Убедитесь, что Rnpushertictacteke
Соответствует названию, которое вы дали приложению, когда вы создали его с помощью React - родной init
Отказ
Далее создайте Компоненты/Main.js
Файл и добавьте следующее:
import React, { Component } from 'react'; import { StyleSheet, Text, View, Button, Alert } from 'react-native'; // include the dependencies import Pusher from 'pusher-js/react-native'; import shortid from 'shortid'; import Spinner from 'react-native-spinkit'; // include the components import Header from './Header'; import Home from './Home'; // the default screen import Board from './Board'; // the tic-tac-toe board and score UI
Внутри конструктора инициализируйте состояние и функции, которые будут использоваться по всему компоненту:
export default class Main extends Component { constructor() { super(); this.state = { username: '', // the name of the user piece: '', // the piece assigned to the user rival_username: '', // the name of the rival player is_playing: false, // whether the user is currently playing or not show_prompt: false, // whether the prompt box for entering the room name is visible is_waiting: false, // whether the user is currently waiting for another player (rival) or not is_room_creator: false // whether the user is the room's creator } this.game_channel = null; // the Pusher channel where data regarding the game will be sent this.is_channel_binded = false; // whether a channel has already been binded or not this.onChangeUsername = this.onChangeUsername.bind(this); // executes when the value of the username text field changes this.onPressCreateRoom = this.onPressCreateRoom.bind(this); // executes when user creates a room this.onPressJoinRoom = this.onPressJoinRoom.bind(this); // executes when user taps on the join room button this.joinRoom = this.joinRoom.bind(this); // the function for joining a room this.onCancelJoinRoom = this.onCancelJoinRoom.bind(this); // executes when user cancels joining a room this.endGame = this.endGame.bind(this); // the function for ending the game } }
Перед установленным компонентом подключитесь к тошеру с помощью учетных данных, которые вам дали, когда вы создали приложение PUSHER:
componentWillMount() { this.pusher = new Pusher('YOUR PUSHER API KEY', { authEndpoint: 'YOUR AUTH ENDPOINT', cluster: 'YOUR PUSHER APP CLUSTER', encrypted: true }); }
Когда компонент обновляется, нам нужно проверить, уже ожидает, что пользователь уже ждет соперника и что канал толкателя еще не связан ни на какие события. Если это так, мы слушаем Клиент, присоединенный к
мероприятие. Когда это произойдет, обновите состояние, чтобы пользовательский интерфейс показывает игровую доску. Если пользователь является создателем комнаты, срабатывайте одинаковое событие, чтобы соперник (тот, кто присоединился к комнате), сообщается, что игра уже может начать.
componentDidUpdate() { if(this.state.is_waiting && !this.is_channel_binded){ this.game_channel.bind('client-joined', (data) => { this.setState({ is_waiting: false, is_playing: true, rival_username: data.username }); if(this.state.is_room_creator){ // inform the one who joined the room that the game can begin this.game_channel.trigger('client-joined', { username: this.state.username // send the name of the room creator to the one who joined }); } }); this.is_channel_binded = true; } }
В оказывать
Метод, Главная
Компонент отображается по умолчанию. Он отображает пользовательский интерфейс, чтобы позволить пользователю ввести их имя и присоединиться или создать новую комнату. Как только соперник присоединяется к комнате, игровая доска будет показана. Спиннер
Компонент используется в качестве состояния перехода между двумя в ожидании того, чтобы соперник присоединиться к комнате.
render() { return (); } { !this.state.is_playing && !this.state.is_waiting && } { this.state.is_playing && }
Вот функция, которая выполняется, когда текстовое поле для ввода имени пользователя меняется:
onChangeUsername(username) { this.setState({username}); }
Когда пользователь нажал на Создать комнату Кнопка, генерируйте уникальный идентификатор для комнаты и подпишитесь на новый канал толкателя, используя этот идентификатор. Здесь мы используем Частный канал Так что мы можем отправлять сообщения прямо из приложения:
onPressCreateRoom() { let room_id = shortid.generate(); // generate a unique ID for the room this.game_channel = this.pusher.subscribe('private-' + room_id); // subscribe to a channel // alert the user of the ID that the friend needs to enter Alert.alert( 'Share this room ID to your friend', room_id, [ {text: 'Done'}, ], { cancelable: false } ); // show loading state while waiting for someone to join the room this.setState({ piece: 'X', // room creator is always X is_waiting: true, is_room_creator: true }); }
Когда конкурентные краны на Присоединяйтесь к комнате Кнопка, окно приглашения отображается:
onPressJoinRoom() { this.setState({ show_prompt: true }); }
После того, как соперник присоединяется к комнате, выполняется следующая функция. Room_id
Предоставляется окном приглашения, поэтому мы просто используем его, чтобы подписаться на тот же канал, что и для создателя комнаты. Это позволяет двум пользователям взаимодействовать напрямую использовать этот канал. Обратите внимание, что код ниже не обрабатывается, если присоединиться к комнате третьего человека. Вы можете добавить функциональность для проверки количества пользователей в комнате, если хотите. Таким образом, приложение отклонит его, если в комнате уже есть два пользователя.
joinRoom(room_id) { this.game_channel = this.pusher.subscribe('private-' + room_id); // inform the room creator that a rival has joined this.game_channel.trigger('client-joined', { username: this.state.username }); this.setState({ piece: 'O', // the one who joins the room is always O show_prompt: false, is_waiting: true // wait for the room creator to confirm }); }
Когда пользователь отменяет присоединение к комнате, просто скрыть окно приглашения:
onCancelJoinRoom() { this.setState({ show_prompt: false }); }
Когда создатель комнаты решает завершить игру, приложение обратно обратно в состояние по умолчанию:
endGame() { // reset to the default state this.setState({ username: '', piece: '', rival_username: '', is_playing: false, show_prompt: false, is_waiting: false, is_room_creator: false }); // reset the game channel this.game_channel = null; this.is_channel_binded = false; }
Наконец, добавьте стили:
const styles = StyleSheet.create({ container: { flex: 1, padding: 20, backgroundColor: '#F5FCFF', }, spinner: { flex: 1, alignSelf: 'center', marginTop: 20, marginBottom: 50 } });
Далее – Заголовок
составная часть. Создать Компоненты/header.js
Файл и добавьте следующее:
import React, { Component } from 'react'; import { StyleSheet, Text, View } from 'react-native'; export default class Header extends Component { render() { return (); } } const styles = StyleSheet.create({ title_container: { flex: 1, }, title: { alignSelf: 'center', fontWeight: 'bold', fontSize: 30 } }); {this.props.title}
Все этот компонент делает это отображение заголовка приложения в заголовке.
Далее создайте Компоненты/home.js
файл. Как упоминалось ранее, это компонент по умолчанию, который отображается в первый раз, когда пользователь открывает приложение или когда создатель комнаты заканчивается игрой.
import React, { Component } from 'react'; import { StyleSheet, Text, View, TextInput, Button } from 'react-native'; import Prompt from 'react-native-prompt'; export default class Home extends Component { render() { return (); } } const styles = StyleSheet.create({ content_container: { flex: 1 }, input_container: { marginBottom: 20 }, button_container: { flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center' }, text_input: { backgroundColor: '#FFF', height: 40, borderColor: '#CCC', borderWidth: 1 }, button: { flex: 1 } });
Далее создайте Компоненты/Board.js
файл. Этот компонент служит главным мясом приложения, потому что это там, где происходит игра.
Во-первых, включите компоненты и пакеты, которые нам понадобится:
import React, { Component } from 'react'; import { StyleSheet, Text, View, TextInput, Button, TouchableHighlight, Alert } from 'react-native'; import range from 'lodash.range';
В конструкторе свяжите методы для создания контента для доски (доска 3×3). Возможные комбинации для получения оценки также объявлены. IDS
используются в качестве идентификаторов для ссылки на отдельные блоки. Как видите, это массив, который имеет в ней три массива. Каждая из этих массивов относится к рядам в доске, и его предметы относятся к отдельным блокам. Поэтому при обращении к второму столбцу в первой строке доски вы можете получить идентификатор для этого, используя Это. [0] [1]
Отказ Это тогда вернется 1
Отказ Идентификатор будет использоваться позже, чтобы определить оценки на основе Возможные_combinations
множество.
export default class Board extends Component { constructor() { super(); this.generateRows = this.generateRows.bind(this); // bind the method for generating the rows for the board this.generateBlocks = this.generateBlocks.bind(this); // bind the method for generating individual blocks for each row // the possible combinations for getting a score in a 3x3 tic-tac-toe board this.possible_combinations = [ [0, 3, 6], [1, 4, 7], [0, 1, 2], [3, 4, 5], [2, 5, 8], [6, 7, 8], [0, 4, 8], [2, 4, 6] ]; // the IDs of the individual blocks this.ids = [ [0, 1, 2], [3, 4, 5], [6, 7, 8] ]; // the individual rows this.rows = [ range(3).fill(''), // make an array with 3 elements and set each item to an empty string range(3).fill(''), range(3).fill('') ]; this.state = { moves: range(9).fill(''), // the pieces (X or O) used on each block x_score: 0, // score of the room creator o_score: 0 // score of the rival } }
Прямо под объявлением для Это Является ли массив, который будет использоваться для генерации строк в доске.
Как только компонент установлен, мы тогда хотим слушать Client-Make-Move
событие произошло. Это событие срабатывает каждый раз, когда пользователь помещает их кусок (либо «X», либо «O») на плате. Обратите внимание, что это будет запущено только на соперниках, а не пользователю, который отправил мероприятие.
componentDidMount() { this.props.channel.bind('client-make-move', (data) => { let moves = this.state.moves; let id = this.ids[data.row_index][data.index]; // get the ID based on the row index and block index moves[id] = data.piece; // set the piece // update the UI this.setState({ moves }); this.updateScores.call(this, moves); // update the user scores }); }
Каждый раз сделан движение, UpdateScores
Функция выполняется. Это петли через все возможные комбинации. Он использует каждый () Метод проверки, использовался ли удельный кусок на каждом из предметов для возможной комбинации. Например, если «X» используется для блоков 0, 1 и 2, то 1 точка награждается пользователю, у которого есть «X» как их кусок.
updateScores(moves) { var pieces = { 'X': 0, 'O': 0 } function isInArray(moves, piece, element, index, array){ return moves[element] && moves[element] == piece; // check if there's a piece assigned to a specific block and that piece is the piece we're looking for (either "X" or "O") } this.possible_combinations.forEach((p_row) => { if(p_row.every(isInArray.bind(null, moves, 'X'))){ pieces['X'] += 1; }else if(p_row.every(isInArray.bind(null, moves, 'O'))){ pieces['O'] += 1; } }); this.setState({ x_score: pieces['X'], o_score: pieces['O'] }); }
Вот …| Render () метод. Он использует
nechanterwows () Способ генерирования контента для доски. Ниже приведен дисплей баллов для двух пользователей.
render() { return (); } {this.generateRows()} {this.state.x_score} {this.props.username} (x) {this.state.o_score} {this.props.rival_username} (o)
Вот …| nechanterwows () Метод:
generateRows() { return this.rows.map((row, index) => { return ({this.generateBlocks(row, index)} ); }); }
GenerateBlocks ()
Метод используется для генерации отдельных блоков на каждой строке. Он использует Touchablehighlight
Компонент для создания представления, который может быть подключен пользователем. Каждый блок отображает часть пользователя, который сначала постучал на него. Нажатие на блок выполняет OnMakemove ()
Метод, который помещает кусок пользователя на этот блок.
generateBlocks(row, row_index) { return row.map((block, index) => { let id = this.ids[row_index][index]; return (); }); } {this.state.moves[id]}
OnMakemove ()
Метод получает ROW_INDEX
и блок индекс
Отказ Они позволяют нам получить блок ID
который используется для установки части на определенный блок. После этого UpdateScores ()
Также называется обновление результатов пользователей. Чтобы обновить UI соперника, детали движения отправляются с помощью Client-Make-Move
мероприятие.
onMakeMove(row_index, index) { let moves = this.state.moves; let id = this.ids[row_index][index]; if(!moves[id]){ // nobody has occupied the space yet moves[id] = this.props.piece; this.setState({ moves }); this.updateScores.call(this, moves); // inform the rival that a move is made this.props.channel.trigger('client-make-move', { row_index: row_index, index: index, piece: this.props.piece }); } }
После того, как доска была заполнена кусочками, спросите комнату создателя, если они хотят перезапустить или завершить игру. Если создатель комнаты решает перезапустить игру, что доска просто сбрасывается в состояние по умолчанию, в противном случае приложение сбрасывается в состояние по умолчанию (так же, как когда приложение впервые открывается).
if(this.props.is_room_creator && moves.indexOf('') == -1){ Alert.alert( "Restart Game", "Do you want to restart the game?", [ { text: "Nope. Let's call it quits.", onPress: () => { this.setState({ moves: range(9).fill(''), x_score: 0, o_score: 0 }); this.props.endGame(); }, style: 'cancel' }, { text: 'Heck yeah!', onPress: () => { this.setState({ moves: range(9).fill(''), x_score: 0, o_score: 0 }); } }, ], { cancelable: false } ); }
Наконец, добавьте стили:
const styles = StyleSheet.create({ board_container: { flex: 9 }, board: { flex: 7, flexDirection: 'column' }, row: { flex: 1, flexDirection: 'row', borderBottomWidth: 1, }, block: { flex: 1, borderRightWidth: 1, borderColor: '#000', alignItems: 'center', justifyContent: 'center' }, block_text: { fontSize: 30, fontWeight: 'bold' }, scores_container: { flex: 2, flexDirection: 'row', alignItems: 'center' }, score: { flex: 1, alignItems: 'center' }, user_score: { fontSize: 25, fontWeight: 'bold' }, username: { fontSize: 20 } });
Тестирование приложения
Теперь, когда вы построили приложение, сейчас время попробовать его. Первое, что вам нужно сделать, это запустить сервер:
node server.js
Вы можете запустить приложение со следующей командой:
react-native run-android
Убедитесь, что у вас уже есть подключенное устройство или эмулятор, открытый при выполнении этого.
Если вы используете Wenymotion или эмулятор Android, и вы не хотите тестировать на реальном устройстве, вы можете использовать браузер для имитации соперника.
Как только это сделано, запустите приложение и создайте новую комнату. Затем скопируйте идентификатор комнаты, показанного в поле оповещения.
Далее перейдите на приборную панель приложения Pusher и нажмите на Отладка консоли вкладка Нажмите на Показать Создатель событий и введите Частный номер_Ид
для Канал Отказ Обязательно замените Room_id
с фактическим идентификатором комнаты, затем установить Клиент, присоединенный к
как ценность Мероприятие. Значение для Данные является:
{ "username": "doraemon" }
Используйте скриншот ниже в качестве ссылки:
Как только это сделано, нажмите на Отправить событие кнопка. Это должно вызвать приложение для изменения своего UI на фактическую игровую доску. Чтобы вызвать некоторые ходы, установите Событие Имя на Client-Make-Move
Затем добавьте детали перемещения на Данные поле:
{ "row_index": 0, "index": 0, "piece": "O" }
Это поместит штуку «O» на первом поле в игровой доске.
Оттуда вы можете разместить другое значение для индекс
и ROW_INDEX
Эмулировать игру игры.
Развертывание сервера
Метод, который я показал вам выше, отлично, если вы хотите только тестировать в свою местную сеть. Но что, если вы хотите проверить его с друзьями за пределами сети? Для этого вы могли бы использовать Сейчас Отказ Я не собираюсь вспомнить детали о том, как развернуть сервер, но вы можете проверить их Документы Отказ Сейчас Свободно использовать, единственный недостаток в том, что ваш код будет доступен публично.
Заключение
Это оно! В этом руководстве вы узнали, как повторно создать TIC-TAC-TAC, используя толкатель. Как вы видели, Pusher действительно позволяет легко реализовать функции в реальном времени в играх. В то время как Tic-Tac-Toe – очень простая игра, это не означает, что толкатель может использоваться только в простых играх. Вы можете в значительной степени использовать Pusher в любой игре в режиме реального времени, о которой вы можете подумать.
Первоначально опубликовано на Душерный блог .