見出し画像

#6 React.js でプログラミング・プラモデル Vol.2 「Border7」:【出題編】山札からランダムに一枚引く

これまで

前回までで、画面にトランプカードを表示するところまで進めました。
初めての方は、#3からご覧ください。

さて、PlayArea コンポーネントと Card コンポーネントをセットするときに、 props.card には仮の値をセットしていたと思います。
今回は、山札となる state deck を定義し、その中からランダムに一枚引いた値を state card にセット。そして、それを PlayArea コンポーネントに渡すことでトランプカードがランダムに表示されるところまで進めていきたいと思います。

関連ファイル

今回、編集する必要があるファイルは Border7.js のみです。
では早速始めましょう!

ここでのタスク

山札からトランプをランダムに一枚引いて画面に表示するために必要なタスクは以下の三つです。

state deck を宣言して、定数 initialDeck の値で初期化する
state card を宣言して、関数 getCard() の戻り値で初期化する
関数 getCard() を実装する

スタイル設定と state の定義

Border7.js で、Border7 コンポーネントの宣言をしているところの jsDoc を見てみましょう。

/**
* Border7 コンポーネント
*
* 処理概要
*  Border7 ゲームの画面を作成する
*  カードを並べる PlayArea コンポーネントと、その下にゲーム進行のためのメッセージ出力とボタンを配置する
*
* 処理詳細
*  - style 設定のため、定数 classes を宣言して、useStyles() hook を使用して初期化する
*
*  - useState() で以下の state を定義する
*     - deck 初期値: getDeck() で作成した配列 deck
*     - card 初期値: getCard() で取得した card オブジェクト
*     - isWin 初期値: null
*     - answered 初期値: false
*     - isGameFinished 初期値: false
*     - winCount 初期値:0
*     - loseCount 初期値:0
*
* 省略
*/
export default function Border7() {

処理詳細には、「スタイル設定のための定数宣言と useState() を使用して state の定義を行う」ということが書かれています。
今回は、以下に示したように state card を定義するところまで行ってください。

* 処理詳細
*  - style 設定のため、定数 classes を宣言して、useStyles() hook を使用して初期化する
*
*  - useState() で以下の state を定義する
*     - deck 初期値: getDeck() で作成した配列 deck
*     - card 初期値: getCard() で取得した card オブジェクト

フックにまだ慣れていない方は、公式ドキュメントを参照してください。
これまで React.js で state を使用する場合はクラスコンポーネントを使用しなくてはなりませんでした。
しかし、このフックの登場で、関数コンポーネントでも state が使用できるようになりました。
そして、その便利さや記述量の少なさなどの理由から、今はコンポーネントの定義は関数コンポーネントで行うことが主流になっています。
これを機にぜひフックの使い方を覚えていきましょう!

フック useStyles() について

少し横道に逸れますが、これまでに何度か useStyles() というフックが登場していました。
フック useStyles() は、React.js で元から実装されている組み込みのフックとは違い、独自で定義したフックです。
今回はすでに実装済みですが、簡単に内容を説明しておきます。
Border7.js フック useSyles() をインポートしているところを見てください。

import { useStyles } from "./hooks/useStyles";

フック useStyles() は、src/hooks/useStyles で定義されているということがわかります。
では早速そのファイルを見てみましょう。

import { makeStyles } from "@material-ui/styles";

export const useStyles = makeStyles({
 playArea: {
   margin: "50px 25px"
 },
 messageArea: {
   margin: "10px"
 },
 message: {
   fontSize: "22px",
   color: "white"
 }
});

@material-ui/styles から関数 makeStyles() がインポートされています。
そして、エクスポートされている定数 useStyles は、関数 makeSyles() の戻り値で初期化されています。

関数 makeStyles() のドキュメントはこちらです。
英語のドキュメント(メニューから日本語表示もできますが、あまり精度がよくありません)ですが、重要な点は以下の二つです。

1.第1引数に styles object(または それを生成する関数)を渡す
2.戻り値は hook

syles object は上記のコードでいうと以下の部分のことを指します。

{
 playArea: {
   margin: "50px 25px"
 },
 messageArea: {
   margin: "10px"
 },
 message: {
   fontSize: "22px",
   color: "white"
 }
}

見ての通り、これは Javascript のオブジェクトです。
これをよく見てみると、ネストしたオブジェクトには CSS のような記述がありますね。
この styles object を関数 makeStyles() に渡すと、オブジェクト内で定義したスタイル設定にアクセスするためのフックが返却されます。
このようにしてフック useStyles() は定義されています。
Material-UI のドキュメントのサンプルコードを参照して実装してみください!

関数 getCard() を実装する

少し横道に逸れましたが、そろそろ実装に戻りましょう。
無事に state deckstate card の定義はできましたか?
次は関数 getCard() の実装です。例のごとく、 jsDoc の実装内容を見ていきます。Border7.js の中から、関数 getCard() を見つけてください。
関数 getCard() の jsDoc は以下のようになっています。

  /**
  * card オブジェクト取得関数
  *
  * 処理概要
  *  state deck から card オブジェクト要素を取得する
  *
  * 処理詳細
  *  - 定数 index を宣言し、0 から デッキ枚数 までのランダムな整数を代入する
  *  - state deck から定数 index の値を使用して要素(card オブジェクト)を取得する
  *  - 取得した要素は削除し、state deck を更新する
  *
  * ランダムな数値を取得する際には以下を用いる
  * Math.floor(Math.random() * ランダムに取得したい数値の最大値)
  * 例: 0 から 10 までのランダムな数値を取得したい
  *   Math.floor(Math.random() * 10);
  *
  * state deck を更新する際は、イミュータブルに更新する
  *
  * 参考:
  *  React.js チュートリアル: 「イミュータビリティは何故重要なのか」
  *  https://ja.reactjs.org/tutorial/tutorial.html#why-immutability-is-important
  *
  * @return {object} cardObj
  */
 function getCard() {
   
 }

処理詳細を参考に、関数 getCard() を実装してみましょう!
実装にあたり、以下の二つのがポイントです。

・ランダムな整数の取得
state をイミュータブルに更新する

ランダムな整数の取得

ランダムな整数の取得の仕方については、jsDoc にて以下のように記載しています。

  * ランダムな数値を取得する際には以下を用いる
  * Math.floor(Math.random() * ランダムに取得したい数値の最大値)
  * 例: 0 から 10 までのランダムな数値を取得したい
  *   Math.floor(Math.random() * 10);

この方法は、ここではお馴染みの MDN Web Docs で紹介されています。
なぜ、このようにすることでランダムな整数が取得できるのか、イメージがわかない方は一つ一つ噛み砕いていきましょう。

Math.floor()

Math.floor() について、MDN Web Docs では以下のように説明されています。

Math.floor() 関数は与えられた数値以下の最大の整数を返します。

これに関しては百聞は一見にしかずだと思いますので、MDN Web Docs のサンプルコードを引用します。

console.log(Math.floor(5.95));
// expected output: 5

console.log(Math.floor(5.05));
// expected output: 5

console.log(Math.floor(5));
// expected output: 5

console.log(Math.floor(-5.05));
// expected output: -6​

Math.random()

Math.random() について、MDN Web Docs では以下のように説明されています。

Math.random()関数は、 0 以上 1 未満 (0 は含むが、 1 は含まない) の範囲で浮動小数点の擬似乱数を返します。

要するに、0 から 0.9999... の範囲で値が返却されるということですね。
この値に「ランダムに取得したい値の最大値」を渡すことで、ある一定の範囲のランダムな値を取得することができます。

例えば、Math.random() を呼び出すと以下のような値が得られます。

console.log(Math.random())
//0.9760108941563288

この値に「ランダムに取得したい値の最大値 52(トランプの枚数)」をかけると、50.752566... という値が得られます。

ここで注意しておきたいのは、返却される値は 1未満だということです。
Math.random() の戻り値に 52 をかけると、最大で 51.9999... という値になり、決して 52 は得られません。
今回は配列のインデックス指定のために使用するので、特に問題は起こりません。ですが、例えば「0 から 10までのランダムな数値がほしい」のに、「Math.random() * 10」としてしまうと、決して 10 は得られないので気をつけましょう。

二つを組み合わせる

Math.random()Math.floor() のそれぞれの使い方について学びましたので、この二つを組み合わせていきましょう。

「Math.random() * 52」 で得られる数は 0 から 0.9999... という値でした。
ですが、これはインデックスを指定するために使用する値なので、整数でなくてはなりません。
なので、「Math.random() * 52」で得られる値を Math.floor() に渡してあげると完成です!

Math.floor(Math.random() * 52)

もちろんこのままでも構いませんが、今後機能アップをして使用するトランプのデッキが2組、3組... と増えたときのために、state deck の長さで指定してあげましょう。

Math.floor(Math.random() * deck.length)

state のイミュータブルな更新

これに関しては、React.js の公式チュートリアルでバッチリ説明されているので、その記事の紹介のみにしておきます。

Border7 ゲームにおいても、タイムトラベル機能を実装できるかもしれませんね!

カードの返却

state deck の更新が完了したら、最後にランダムに取得した card オブジェクトを呼び出し元に返却しましょう!
以上で関数 getCard() の実装は完了です!

PlayArea コンポーネントに渡す

関数 getCard() の実装、お疲れさまでした!
ここまで来たら、後少しですのでがんばりましょう!

state card の初期値に関数 getCard() の戻り値が設定できていれば、state card には今、何かしらの card オブジェクトがセットされているはずです。
これを PlayArea コンポーネントの props.card にセットしてください。
前回props.card には仮のデータをセットしましたので、それを state card で置き換えてください。

お疲れさまでした

実装、お疲れさまでした!
ここまでの手順を踏むことで、画面にはランダムなカードが表示されるようになっていると思います。
プレビュー画面の上にあるリロードボタンをポチポチしてみて、カードがランダムに表示されることを確認してみてください。

次回

トランプのカードがランダムに表示されるようになりましたので、今後は「トランプの数字が 7 より大きいか、小さいか、または等しいか」などを選択するためのボタンを作成していくことになります。
ただその前に、区切りがよいので一度解答編を挟みたいと思います。
次回は、#5 と #6(今回)の実装例をご紹介します。

感想を教えて下さい

プログラミング・プラモデルをご覧いただき、ありがとうございます!
今後の励みになりますので、スキやフォローお願いします!
また、もし「わかりやすい」「わかりにくい」「こういうのも知りたい」などがあればコメントにてお知らせください。

この記事があなたのお役に立ちましたら、よろしければサポートをお願いいたします! より良い記事をお届けできるよう、活動費に充てさせていただきます。