引き続きReactで足し算ゲームを作成していきたいと思います。
前回内部ロジックを作成し、足し算ゲームが正しく動くことを確認できました。
足し算ゲームをクリアした際に再度ゲームが実行できるようRestartボタンを作成していきたいと思います。
import React, { useState } from "react";
import { render } from "react-dom";
import "./styles.css";
const StarsDisplay = (props) => (
<>
{utils.range(1, props.count).map((starId) => (
<div key={starId} className="star" />
))}
</>
);
const PlayNumber = (props) => (
<button
className="number"
style={{ backgroundColor: colors[props.status] }}
onClick={() => props.onClick(props.number, props.status)}
>
{props.number}
</button>
);
const PlayAgain = (props) => (
<>
<button onClick={props.onClick}>PlayAgain</button>
</>
);
const StarMatch = () => {
const [stars, setStars] = useState(utils.random(1, 9));
const [availableNums, setAvailableNums] = useState(utils.range(1, 9));
const [candidateNums, setCanditateNums] = useState([]);
const candidatAreWrong = utils.sum(candidateNums) > stars;
const gameIsDone = availableNums.length === 0;
const resetGame = () => {
setStars(utils.random(1, 9));
setAvailableNums(utils.range(1, 9));
setCanditateNums([]);
};
const numberStatus = (number) => {
if (!availableNums.includes(number)) {
return "used";
}
if (candidateNums.includes(number)) {
return candidatAreWrong ? "wrong" : "candidate";
}
return "available";
};
const onNumberClick = (number, currentStatus) => {
// currentStatus = newStatus
if (currentStatus == "used") {
return;
}
const newCanditateNum =
currentStatus === "available"
? candidateNums.concat(number)
: candidateNums.filter((cn) => cn !== number);
if (utils.sum(newCanditateNum) !== stars) {
setCanditateNums(newCanditateNum);
} else {
const newAvailableNum = availableNums.filter(
(n) => !newCanditateNum.includes(n)
);
setStars(utils.randomSumIn(newAvailableNum, 9));
setAvailableNums(newAvailableNum);
setCanditateNums([]);
}
};
return (
<div className="game">
<div className="body">
<div className="left">
{gameIsDone ? (
<PlayAgain onClick={resetGame} />
) : (
<StarsDisplay count={stars} />
)}
</div>
<div className="right">
{utils.range(1, 9).map((number) => (
<PlayNumber
key={number}
number={number}
status={numberStatus(number)}
onClick={onNumberClick}
/>
))}
</div>
</div>
</div>
);
};
// Color Theme
const colors = {
available: "lightgray",
used: "lightgreen",
wrong: "lightcoral",
candidate: "deepskyblue"
};
// Math science
const utils = {
// Sum an array
sum: (arr) => arr.reduce((acc, curr) => acc + curr, 0),
// create an array of numbers between min and max (edges included)
range: (min, max) => Array.from({ length: max - min + 1 }, (_, i) => min + i),
// pick a random number between min and max (edges included)
random: (min, max) => min + Math.floor(Math.random() * (max - min + 1)),
// Given an array of numbers and a max...
// Pick a random sum (< max) from the set of all available sums in arr
randomSumIn: (arr, max) => {
const sets = [[]];
const sums = [];
for (let i = 0; i < arr.length; i++) {
for (let j = 0, len = sets.length; j < len; j++) {
const candidateSet = sets[j].concat(arr[i]);
const candidateSum = utils.sum(candidateSet);
if (candidateSum <= max) {
sets.push(candidateSet);
sums.push(candidateSum);
}
}
}
return sums[utils.random(0, sums.length - 1)];
}
};
const rootElement = document.getElementById("root");
render(<StarMatch />, rootElement);
const gameIsDone = availableNums.length === 0;
const resetGame = () => {
setStars(utils.random(1, 9));
setAvailableNums(utils.range(1, 9));
setCanditateNums([]);
};
…
…
…
<div className="left">
{gameIsDone ? (
<PlayAgain onClick={resetGame} />
) : (
<StarsDisplay count={stars} />
)}
</div>
まず、ゲームが終了したかの判定を書いていく必要があります。
利用可能な配列(availableNums)の中身が何もなくなった場合
三項演算子でPlayAgainとStarsDisplayを切り替えていきます。
resetGame関数のなかには、各状態を初期化する処理が書かれていて
PlayAgainコンポーネントにその関数を渡しています。
const PlayAgain = (props) => (
<>
<button onClick={props.onClick}>PlayAgain</button>
</>
);
上記で指定したresetGameのトリガーはボタンをクリックした時としたいので
PropsからonClickをとってきます。
そうすることで、ゲームが終了した時にPlayAgainのボタンを表示し再度足し算ゲームを起動することができます!