前回に引き続きReactで足し算ゲームを作成していきたいと思います。
今回は以前作成した状態へ入力欄をクリックしたときどの内部ロジックを組んでいきたいと思います。
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 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 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">
<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);
// 現在のStatusから新しいStatusを入れるクリックイベント
const onNumberClick = (number, currentStatus) => {
// 現在のStatusが使用済なら変更できない
if (currentStatus == "used") {
return;
}
// 新しいStatusの変数
const newCanditateNum =
currentStatus === "available"
// クリックした番号が利用可能なら配列に追加
? candidateNums.concat(number)
// クリックした番号が利用可能でないなら利用可能にする(使用済以外)
: candidateNums.filter((cn) => cn !== number);
// もしクリックした番号の和が星の数と同じじゃない場合
if (utils.sum(newCanditateNum) !== stars) {
// 選択した番号を配列へ入れる
setCanditateNums(newCanditateNum);
}
// クリックした番号の和が星の数と同じ場合
else {
// 該当した番号以外の数字をnewAvailableNumへ入れる
const newAvailableNum = availableNums.filter(
(n) => !newCanditateNum.includes(n)
);
// 星の数をランダムで表示する
setStars(utils.randomSumIn(newAvailableNum, 9));
// 選択可能な数字を代入する
setAvailableNums(newAvailableNum);
// 候補の番号をリセットする
setCanditateNums([]);
}
};
のようになります。 elseの処理内で実行しているnewAvailableNumは
// 配列を定義
const hoge = [1, 2, 3, 4, 5];
// 配列の一つ一つに1が含まれているか確認
const fuga = hoge.filter((n) => ![1].includes(n));
// 1がfalseなので非表示
console.log(fuga); => [2, 3, 4, 5];
のような挙動をしております。
内部のロジックは難しくてなかなか理解するのが大変でした。
JS力が純粋に試されている感じがしますね笑
以上です!!!