Создайте игру Rock Paper Scissor с помощью ReactJS
Здесь оба игрока будут ходить по очереди. Начиная с первого игрока, за которым следует второй игрок. Есть три вида оружия на выбор: камень, бумага, ножницы. После того, как второй игрок играет, результат своего хода вычисляется, обновляя статус выигрыша / проигрыша обоих игроков.
Используемые технологии / Предпосылки:
- ReactJS
- Библиотека Antd для пользовательского интерфейса
Подход: контейнеры представляют собой компоненты React с отслеживанием состояния (на основе классов). Компоненты - это компоненты React без сохранения состояния (функционально-ориентированные). В этом проекте у меня есть один контейнер - контроллер, который отвечает за управление состоянием и всю игровую логику. Также есть три компонента, а именно:
Player -> Представление сущности Player в игре; GameControls -> Выбор камня, бумаги или ножниц; DecisionBox -> Отображает статус выигрыша / проигрыша для игроков;
При нажатии кнопок оружия состояние в контроллере обновляется для соответствующего игрока. Когда обновляется оружие второго игрока, результат вычисляется функцией обработчика результатов, которая охватывает все девять комбинаций камня, бумаги и ножниц, взятых по две за раз.
Структура проекта:
Шаги:
- Настройте проект React с помощью команды create-response-app: create-response-app << имя проекта >> –scripts-version 1.1.5. С помощью этой команды будет сгенерирована структура проекта, подобная приведенной выше, за исключением компонентов и папок контейнеров, которые должны быть созданы внутри папки src вручную.
- Установите библиотеку antd с помощью команды: npm install antd
- Удалите код из App.css .
- Отредактируйте код внутри App.js : компонент приложения отображает контроллер и отображает заголовок.
App.jsJavascript
import React, { Component } from
"react"
;
import Controller from
"./containers/controller/controller"
;
import { Typography } from
"antd"
;
import
"antd/dist/antd.css"
;
import
"./App.css"
;
const { Title } = Typography;
class App extends Component {
render() {
return
(
<div className=
"App"
>
<Title level={3} style={{ textAlign:
"center"
}}>
Stone Paper Scissor
</Title>
<Controller />
</div>
);
}
}
export
App;
default
Controller.js: Контроллер содержит состояние, которое включает в себя последнее использованное оружие каждым игроком, текущий статус выигрыша / проигрыша и активное состояние, указывающее, у какого игрока сейчас ход.
Javascript
import React, { Component } from
"react"
;
import { Row, Col, Divider } from
"antd"
;
import { Typography } from
"antd"
;
import axios from
"axios"
;
import
"antd/dist/antd.css"
;
import Player from
"../../components/Player/Player"
;
const { Title } = Typography;
class Controller extends Component {
state = {
playerOne: {
active:
true
,
weapon:
""
,
status:
""
},
playerTwo: {
active:
false
,
weapon:
""
,
status:
""
}
};
Основная идея здесь заключается в том, что при нажатии кнопок оружия соответствующее оружие игрока в состоянии должно обновляться, и должен наступить следующий ход игрока, что здесь делается с помощью функции переключения. Кроме того, если обновляется оружие второго игрока, необходимо также обновить выигрыш / проигрыш, что здесь делается с помощью функции resultHandler. Для игровой логики он содержит набор функций:
- WeaponUpdate: для обновления оружия игрока в состоянии. Он принимает два аргумента: первый - playerId, то есть playerOne или playerTwo, так как это игра для двух игроков, а второй - это оружие, которое выбирает игрок.
Javascript
weaponUpdate = (player, weapon) => {
this
.setState({
[player]: {
...
this
.state[player],
weapon: weapon
}
});
if
(player ==
"playerTwo"
) {
this
.resultHandler();
}
else
{
this
.toggleActive();
}
};
- ToggleActive: для изменения активного статуса игроков, когда любой из игроков выбирает оружие. Активное свойство в состоянии - это логическая переменная, поэтому нам просто нужно использовать оператор NOT, чтобы изменить текущее состояние каждого игрока.
Javascript
toggleActive = () => {
this
.setState(prevState => {
return
{
...prevState,
playerOne: {
...prevState.playerOne,
active: !prevState.playerOne.active
},
playerTwo: {
...prevState.playerTwo,
active: !prevState.playerTwo.active
}
};
});
};
- ResultHandler: он содержит основную игровую логику, которая определяет, какой игрок выиграл. Он использует вспомогательную функцию, называемую помощником по принятию решений, которая принимает два аргумента, которые являются оружием каждого игрока, и возвращает массив, содержащий два элемента, в которых первый элемент соответствует статусу выигрыша / проигрыша первого игрока, а второй элемент - статусу второго игрока. Затем обработчик результатов обновляет состояние значениями, возвращаемыми функцией decicerhelper.
Javascript
resultHandler = () => {
this
.setState(prevState => {
let [s1, s2] =
this
.deciderHelper(
prevState.playerOne.weapon,
prevState.playerTwo.weapon);
return
{
...prevState,
playerOne: {
...prevState.playerOne,
status: s1
},
playerTwo: {
...prevState.playerTwo,
status: s2
}
};
});
this
.toggleActive();
};
- DeciderHelper : это вспомогательная функция, которая принимает два входа, которые являются оружием для каждого игрока, и возвращает массив, состоящий из двух элементов, каждый из которых обозначает статус выигрыша / проигрыша игрока 1 и игрока 2 соответственно. Здесь «r» означает камень, «s» означает ножницы, «p» означает бумагу, «w» - выигрыш, а «l» - проигрыш. Учитываются все возможные комбинации камень, камень, ножницы, в случае ничьей возвращаются оба игрока со статусом победы.
Javascript
deciderHelper = (p1, p2) => {
if
(p1 ==
"r"
&& p2 ==
"s"
) {
return
[
"w"
,
"l"
];
}
if
(p1 ==
"r"
&& p2 ==
"p"
) {
return
[
"l"
,
"w"
];
}
if
(p1 ==
"r"
&& p2 ==
"r"
) {
return
[
"w"
,
"w"
];
}
if
(p1 ==
"p"
&& p2 ==
"r"
) {
return
[
"w"
,
"l"
];
}
if
(p1 ==
"p"
&& p2 ==
"s"
) {
return
[
"l"
,
"w"
];
}
if
(p1 ==
"p"
&& p2 ==
"p"
) {
return
[
"w"
,
"w"
];
}
if
(p1 ==
"s"
&& p2 ==
"r"
) {
return
[
"l"
,
"w"
];
}
if
(p1 ==
"s"
&& p2 ==
"p"
) {
return
[
"w"
,
"l"
];
}
if
(p1 ==
"s"
&& p2 ==
"s"
) {
return
[
"w"
,
"w"
];
}
};
- Метод рендеринга:
Javascript
render() {
return
(
<Row justify=
"space-around"
align=
"middle"
>
<Col className=
"gutter-row"
xs={15} sm={15} md={5} lg={5}>
<Title level={3}>Player One</Title>
<Player
active={
this
.state.playerOne.active}
weaponUpdate={weapon =>
this
.weaponUpdate(
"playerOne"
, weapon)}
weapon={
this
.state.playerOne.weapon}
status={
this
.state.playerOne.status}
/>
</Col>
<Col className=
"gutter-row"
xs={15} sm={15} md={5} lg={5}>
<Title level={3}>Player Two</Title>
<Player
active={
this
.state.playerTwo.active}
weaponUpdate={weapon =>
this
.weaponUpdate(
"playerTwo"
, weapon)}
weapon={
this
.state.playerTwo.weapon}
status={
this
.state.playerTwo.status}
/>
</Col>
</Row>
);
}
}
export
Controller;
default
- WeaponUpdate: для обновления оружия игрока в состоянии. Он принимает два аргумента: первый - playerId, то есть playerOne или playerTwo, так как это игра для двух игроков, а второй - это оружие, которое выбирает игрок.
Кодирование компонентов: Код в них очень прост. Здесь находится просто репрезентативная логика.
- Player.js : как упоминалось ранее, это компонент без сохранения состояния, представляющий объект игрока. Он получает четыре реквизита: активный статус игрока, текущее оружие, статус выигрыша / проигрыша и functionHandler для обновления оружия. Кроме того, он использует два компонента GameControls и блок принятия решений, которые обсуждаются ниже. Кроме того, если полученные активные реквизиты истинны, то к div применяется объект CSS с именем glowEffect.
Javascript
import React from
"react"
;
import GameControls from
"../GameControls/GameControls"
;
import DecisionBox from
"../DecisionBox/DecisionBox"
;
const Player = props => {
let glowEffect = {};
if
(props.active) {
glowEffect = {
"-webkit-box-shadow"
:
"0 0 20px blue"
,
"-moz-box-shadow"
:
"0 0 20px blue"
,
"box-shadow"
:
"0 0 20px blue"
};
}
return
(
<div style={glowEffect}>
<GameControls wUpdate={props.weaponUpdate} isActive={props.active} />
<DecisionBox weapon={props.weapon} status={props.status} />
</div>
);
};
export
Player;
default
- GameControls.js: содержит три кнопки: камень, камень, ножницы. Если плеер не активен, кнопки отключены. При нажатии кнопки вызывается функция weaponupdate, которая принимает один аргумент «p» для бумаги, «r» для камня, «s» для ножниц, и состояние соответствующего обновляется.
Javascript
import React from
"react"
;
import { Card } from
"antd"
;
import { Button } from
"antd"
;
import
"antd/dist/antd.css"
;
const GameControls = props => {
return
(
<Card
title=
"Controls"
style={{ width:
"300px"
, height:
"250px"
, alignItems:
"center"
}}
>
<p style={{ alignItems:
"center"
}}>
<Button
type=
"dashed"
size=
"large"
shape=
"round"
block
onClick={() => props.wUpdate(
"r"
)}
disabled={!props.isActive}
>
Rock
</Button>
</p>
<p style={{ alignItems:
"center"
}}>
{
" "
}
<Button
type=
"dashed"
size=
"large"
shape=
"round"
block
onClick={() => props.wUpdate(
"p"
)}
disabled={!props.isActive}
>
Paper
</Button>
</p>
<p style={{ alignItems:
"center"
}}>
<Button
type=
"dashed"
size=
"large"
shape=
"round"
block
onClick={() => props.wUpdate(
"s"
)}
disabled={!props.isActive}
>
Scissors
</Button>
</p>
</Card>
);
};
export
GameControls;
default
- DecisionBox.js : он отображает две вещи: оружие игрока и его выигрышный / проигрышный статус. Он получает два реквизита: оружие ('r' или 'p' или 's') и статус ('w' или 'l'). Два объекта: карта оружия и карта состояния используются для сопоставления сокращений с соответствующими словами, которые могут отображаться на экране.
Javascript
import React from
"react"
;
import { Card } from
"antd"
;
import { Typography } from
"antd"
;
const { Title } = Typography;
const weaponMap = {
s:
"Scissors"
,
p:
"Paper"
,
r:
"Rock"
};
const statusMap = {
w:
"Win"
,
l:
"Loose"
};
const DecisionBox = props => {
return
(
<Card
title=
"Decision Box"
style={{
width:
"300px"
,
height:
"250px"
,
alignItems:
"center"
,
marginTop:
"15px"
}}
bodyStyle={{ textAlign:
"center"
}}
>
<Title level={1} type=
"warning"
>
{weaponMap[props.weapon]}
</Title>
<Title level={2} mark type=
"secondary"
>
{statusMap[props.status]}
</Title>
</Card>
);
};
export
DecisionBox;
default
- Player.js : как упоминалось ранее, это компонент без сохранения состояния, представляющий объект игрока. Он получает четыре реквизита: активный статус игрока, текущее оружие, статус выигрыша / проигрыша и functionHandler для обновления оружия. Кроме того, он использует два компонента GameControls и блок принятия решений, которые обсуждаются ниже. Кроме того, если полученные активные реквизиты истинны, то к div применяется объект CSS с именем glowEffect.
Выход: