ブログの投稿ログ

2021年
9月
8月
7月
6月

初めてのREACT-v15

ゲームクリアした時にリセットする

引き続きReactで足し算ゲームを作成していきたいと思います。


DEMO


前回内部ロジックを作成し、足し算ゲームが正しく動くことを確認できました。
足し算ゲームをクリアした際に再度ゲームが実行できるようRestartボタンを作成していきたいと思います。

今回のコード

codesandbox

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);

追加部分

// StarMatch

  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コンポーネントにその関数を渡しています。

// PlayAgain

const PlayAgain = (props) => (
  <>
    <button onClick={props.onClick}>PlayAgain</button>
  </>
);

上記で指定したresetGameのトリガーはボタンをクリックした時としたいので
PropsからonClickをとってきます。
そうすることで、ゲームが終了した時にPlayAgainのボタンを表示し再度足し算ゲームを起動することができます!