サイトロゴ

Enjoy Creating
Web & Mobile Apps

MENU BOX
WEB
MOBILE
OPEN

ホーム

 > 

 > 

【React Native】じゃんけんゲームアプリを作ってみよう!

【React Native】じゃんけんゲームアプリを作ってみよう!

この記事にはプロモーションが含まれています。

いきなりですが、初心者でも挫折せずに作れるモバイルアプリって何でしょうか?

たとえば、メモ帳アプリToDo管理アプリなんかは、プログラミングの参考書のサンプルとしてよく出てきますし、初心者でも作りやすそうというイメージがあるかもしれません。

しかし、こういったアプリはデータを永続的に保存できるようにしたり、保存されたデータを後から編集できるようにしたり…といった仕組みが必要となるので、設計がやや難しく、コードも複雑になりがちです。

正直なところ、メモ帳やToDoアプリは初心者でも簡単に作れるアプリとは言い難いと、筆者は思います。

では、初心者でも作りやすいアプリって何なのでしょうか?

タイトルでバレバレですが、それはずばり、じゃんけんゲームアプリです。

じゃんけんは勝敗条件がシンプルで分かりやすく、複雑な状態管理や計算を行う必要がない上に、当然ながらデータの永続保存も必要ありません。

初心者がつまずくポイントはほとんどないと言えるので、初心者がプログラミングを理解しながら作成するアプリとしては最適だと思います。

ということで、今回はReact Nativeで下の動画のような、シンプルなじゃんけんゲームを作ってみたいと思います!

じゃんけんゲームデモ(動画)

これぐらいなら、なんだか本当に簡単に作れそうな気がしませんか?

基本的な状態変数管理や条件分岐などは使いますが、逆に言えば、そこさえ押さえれば他は何も難しいことはありません。

また、この記事で紹介するコードが絶対正解というわけではない…どころか、ツッコミどころや改善点万歳だと思うので、オリジナルのアレンジを加えたりしながら、ぜひ実際にアプリを作って動かしてみてくださいね!

この記事を読むことで分かること
  • ・useState()を使った基本的な状態管理
  • ・三項演算子でViewの描画を切り替える方法
  • ・ランダムな数値を利用する方法
  • ・switch文で条件別に処理を行う方法

– 目次 –

まずは完成イメージを確認

まずは、今回作るじゃんけんゲームアプリの完成形を確認してみましょう。

下はじゃんけんゲームアプリのコード(左)と実行結果(右)です。Webブラウザでも実行可能なので、ぜひ試してみてください。

今回のアプリの機能(処理の流れ)を大まかにまとめると、次のようになります。

  • (1)STARTボタンを押すと画像が切り替わり、じゃんけんが始まる
  • (2)『グー』・『チョキ』・『パー』のボタンのいずれかを押すと、勝敗判定が行われる
  • (3)『もう一度』ボタンを押すと、再びじゃんけんが始まる

細かいところは後で詳しく見ていくとして、まずは大まかな処理の流れをリストや図に表したりして整理しておくと、この後の解説が分かりやすくなると思います。

なお、完成形のコードでは全てのパーツを一つのコンポーネントにまとめていますが、本来であればパーツごとにコンポーネントを細かく分けた方が良いです。(コードが見やすくなり、管理しやすくなるため)

ですが、今回は解説をシンプルにするため、あえてコンポーネントを分けていません。

というのも、コンポーネントを切り分けるとpropsを渡さないといけなくなり、どこでどのpropsを渡しているかの説明が加わることで、返って分かりづらくなる場合があるためです。

この点、あらかじめご理解・ご了承の上、この後の解説に進んで頂ければと思います!

次の章から、じゃんけんゲームアプリのポイントとなるロジックやコードに解説を加えていきます。

    POINT!
  1. ・まずは完成形を確認して、アプリに必要となる機能を大まかに把握しておこう!
  2. ・今回作成するじゃんけんゲームアプリの流れは、スタート→選択→結果表示(勝敗判定)→もう一度プレイ!
  3. ・本来、コンポーネントを切り分けて管理することが重要だが、今回は解説のために全てまとめている!

ゲーム開始前の画面構成

じゃんけんゲーム開始前の状態は、『グー』の画像STARTボタンが表示されている状態です。

その他の画像やボタンは、最初は見えない(画面に描画されない)状態にしておく必要があります。

ということはすなわち、ゲームが開始されたかどうか=STARTボタンが押されたかどうかの状態を管理する必要がある…と言えますね。

STARTボタンは、押されたか、押されていないかの2つの状態しか持たないので、次のように真偽値(true/false)で管理可能です。

  • ・まだボタンが押されていない(false)
  • ・ボタンが押された(true)

このように、2択しか状態を持たない場合の状態管理は真偽値(Boolean)で行うようにすると、アプリ全体の管理が楽になります。

数値を使って『押されていない状態が0、押された状態が1』というふうに管理することもできるのですが、数値だと2とか3とか、別の数値を代入することもできてしまうため、処理のどこかで想定外の数値が代入されるといったエラーが起こる確率が上がってしまいます。

それに、数値だと『ボタンがおされていない状態が0だっけ?1だっけ?』といった混乱も招きやすいと言えます。

一方、真偽値なら『押されていない=false』というのが人間の感覚に合うので、ミスが起こりにくいです。

以上を踏まえて、STARTボタンが押されたかどうかの状態を管理している部分と、それによって表示パーツを分けている部分を確認してみましょう。

下は完成形のコードから、ゲーム開始前の画面を描画するのに必要な部分だけを抜き出したものです。(※コンポーネントの読み込み、StyleSheetによるスタイルの調整は省略しています)

App.js

import React,{useState} from 'react';

function App() {

  //画像のパスを配列で管理
  const imageFiles = [
    require('./assets/gu.png'),
    require('./assets/tyoki.png'),
    require('./assets/pa.png')
  ];

  //配列imageFilesのインデックスを管理(初期値は0)
  let [imageNum, setImageNum] = useState(0);

  //STARTボタンが押されたかどうかの状態管理(初期値はfalse)
  const [isStart, setIsStart] = useState(false);
  
  return (
    <View style={styles.container}>
        <Image 
            source={imageFiles[imageNum]}
            style={{width: 200,height: 200}}
        />
        <View style={{height: 40}}/>

        {/* STARTボタンが押されていなければボタンを表示 */}
        {isStart ? null : 
            <Button title="START"
                onPress={() => {
                setIsStart(true); //STARTボタンが押されたら状態をtrueにする
                }}
            />
        }
    </View>
  );
}

export default App;

このコードの大きなポイントは、STARTボタンが押されたらボタンを非表示にする必要があるという点です。

そうでなければ、じゃんけんゲームが始まった後でもSTARTボタンを押せることになるため、バグやユーザーが混乱する原因になってしまいます。

そこで、STARTボタンが押されたかどうかの状態を、Reactの基本的なフックであるuseStateで管理し、ボタンが押されているかどうかでボタンの描画の有無を切り替えます。

returnの中で文は使えないので、if文を使ってコンポーネントの描画を切り替えることはできませんが、三項演算子は式でありreturnの中でも使えるので、こちらを利用すると良いです。

三項演算子の構文
条件 ? trueの時の処理 : falseの時の処理

同様に、STARTボタンが押された(状態がtrueになったら)という条件を三項演算子で判定し、『グー』『チョキ』『パー』のボタンを表示させるViewを描画すればOKです。(完成形のコードのSTARTボタン以下の、{isStart ? ~の部分がそれに当たります)

プログラミングの学び始めたばかりの頃は、条件分岐=if文というイメージが強いかと思いますが、三項演算子もよく使うので慣れておくと良いでしょう。

    POINT!
  1. ・ボタンが押されたかどうかの状態管理は、真偽値で行うと便利で分かりやすい!
  2. ・バグや混乱を防ぐため、STARTボタンが押されたらボタンを非表示にしておこう!
  3. ・returnの中でも三項演算子なら使えることを利用して、描画コンポーネントを切り替えよう!

画像表示を切り替える

STARTボタンを押した後、グーの画像しか表示されていないままだと、なんだかちょっと味気ないですし、ゲームっぽくないですよね。

というわけで、次はグー・チョキ・パーの画像を細かく切り替えることで、相手がどんな手を出してくるのか分からない、じゃんけんならではのドキドキ感を演出してみましょう!

今回、画像のファイルパスを配列で管理しており、状態変数ImageNumの数値によってImageコンポーネントで読み込まれる画像が変化するように設計しています。

あとは、STARTボタンが押されたら一定時間おきにImageNum変数に格納する値を変えれば良いということになります。

ただし、画像はグー・チョキ・パーの3枚だけであり、配列番号は0〜2までしか存在しないため、この範囲内で数値を循環させる必要があります。

今回は、2(最大値)未満までは1ずつ数値を増やし、2以上になると0に戻してリセットするようにしてみましょう。

ImageNumの数値を変化させる関数は次のようになります。

App.js(一部)

function imageNumChange(){
    // 画像番号が2より小さい場合
    if(imageNum < 2){ 
        setImageNum(imageNum += 1);
    } else {
        setImageNum(imageNum = 0);
    }
}

これで、imageNumChange関数を実行するたびに、imageNumの数値が変化するようになりました。

あとは、imageNumChange関数を一定時間おきに実行する関数を用意して、STARTボタンが押されると同時にその関数を実行すればOKです。

一定時間おきに処理を行いたい場合は、setInterval()を使います。

なお、任意のタイミングで処理を止められるように、intervalIDを管理する変数を用意しておくのを忘れないようにしましょう。

下は、一定時間おきに処理を行うために必要な変数、関数を抜き出したものです。この例では、200m秒間隔で処理が行われることになります。

App.js(一部)

const [intervalID, setIntervalID] = useState(null);

function changeImage() {
  if (intervalID !== null) {
    clearInterval(intervalID);
  }
  const id = setInterval(imageNumChange,200);
  setIntervalID(id);
}

これで、changeImage関数が呼ばれると200m秒ごとにimageNumChange関数が実行され、Imageコンポーネントで指定している画像の配列番号が順番に切り替わることになります。

最後に、STARTボタンのonPressの処理内にchangeImage関数の実行を追加すれば、ボタンが押される(=じゃんけんゲームが始まる)と画像が切り替わるようになります。

App.js(一部)

import React,{useState} from 'react';

function App() {

  //画像のパスを配列で管理
  const imageFiles = [
    require('./assets/gu.png'),
    require('./assets/tyoki.png'),
    require('./assets/pa.png')
  ];

  //配列imageFilesのインデックスを管理(初期値は0)
  let [imageNum, setImageNum] = useState(0);

  //STARTボタンが押されたかどうかの状態管理(初期値はfalse)
  const [isStart, setIsStart] = useState(false);

  // setIntervalのIDを管理(初期値はnull)
  const [intervalID, setIntervalID] = useState(null);

  // 画像の番号を変更する関数
  function imageNumChange() {
    // 画像番号が2より小さい場合
    if(imageNum < 2){ 
        setImageNum(imageNum += 1);
    } else {
        setImageNum(imageNum = 0);
    }
  }

  // 200msおきに画像番号を変更する関数(imageNumChange)を実行する
  function changeImage() {
    if (intervalID !== null) {
      clearInterval(intervalID);
    }
    const id = setInterval(imageNumChange,200);
    setIntervalID(id);
  }
  
  return (
    <View style={styles.container}>
        <Image 
            source={imageFiles[imageNum]}
            style={{width: 200,height: 200}}
        />
        <View style={{height: 40}}/>

        {/* STARTボタンが押されていなければボタンを表示 */}
        {isStart ? null : 
            <Button title="START"
                onPress={() => {
                    setIsStart(true); //STARTボタンが押されたら状態をtrueにする
                    changeImage();
                }}
            />
        }
    </View>
  );
}

export default App;

以上で、じゃんけんゲームの開始前の準備がほぼ完了しました。

ですがあくまでまだ準備段階であり、このままではゲームになっていませんね。汗

ということで次は、グー・チョキ・パーの3つの手から、ランダムで1つが選ばれるような仕組みを作っていきましょう!

    POINT!
  1. ・画像のファイルパスを配列で管理しておくことで、簡単に画像の表示を切り替えられる!
  2. ・読み込まれる画像の配列のインデックスを変数で管理し、それを変化させる関数を用意しよう!
  3. ・一定時間おきに処理を行いたい場合は、setInterval()メソッドを利用しよう!

ランダムで選ばれるようにする

じゃんけんというのは、相手が出す手が読めてしまったらゲームとして成立しなくなります。

たとえば、グー→チョキ→パー→グー…というふうに、出す手の順番が決められていたら、絶対に勝ててしまうので面白くありませんよね。

そこで、相手(アプリ側)が出す手が読めないように、ランダムで選ばれる仕組みを構築していきましょう。

じゃんけんは、グー・チョキ・パーの3通りであり、それぞれ画像の配列番号は0〜2までの数値であるため、結論としては0〜2までの数値がランダムで得られる仕組みを作れば良い…ということになります。

ランダムな数値を得るには、Math.random()を使います。

ただし、Math.random()は0以上1未満の浮動小数点数(0.56…とか、0.972…とか)を得るものなので、そのままでは使いづらいです。

今回は0以上2以下の範囲内の整数が得たいわけですから、Math.random()で得られた数値に3を掛けて、いらない小数点以下の部分はMath.floor()で丸めてしまいましょう。

そして、ランダムで選ばれた0〜2までの数値を画像の配列番号としてセットし、アプリ側が出す手として表示させればOKです。

なお、この後の過程(勝敗判定)において、ランダムで選ばれた数値が必要となるので、returnで戻り値として返しておくのも大切なポイントとなります。

下は、0〜2の範囲でランダムな数値(整数)を取得してそれを戻り値として返すと同時に、画像の配列番号に当てはめるための関数のコードです。

App.js(一部)

// 0〜2までの数字をランダムに得て、画像の配列番号として指定する
function randomChoice() {
  clearInterval(intervalID);
  let randomChoiceNum = Math.floor(Math.random()*3);
  setImageNum(randomChoiceNum);

  // ランダムで得られた数値を戻り値として返す
  return randomChoiceNum;
}

なお、この関数はユーザーがグー・チョキ・パーのいずれかのボタンを押したのと同時に実行されなければなりません。(でなければ、先出し・後出しじゃんけんになってしまいますよね)

その際、一定時間おきに画像の配列番号を増減させて表示を切り替えていた処理を止めなければならないため、最初にclearInterval()で、一定時間おきの処理を解除しています。

clearInterval()で必要となるintervalIDは、useState()で管理しているので、Appコンポーネント内であればどこからでも参照可能です。

今回、画像の配列番号が0であればグーの画像、1であればチョキの画像、2であればパーの画像が表示されるようになっているので、ランダムで選ばれた数値さえ把握できれば、勝敗判定も行える仕組みになっています。

たとえば、ユーザーがグーを選んだ場合、ランダムで選ばれた数値が0なら引き分け、1ならユーザーの勝ち、2ならユーザーの負けとなります。

このようにランダムで選ばれた数値を勝敗判定として利用するために、returnで戻り値として返しています。

今回のじゃんけんゲームアプリのように、その他のゲームやくじ引き、占いアプリなどでも、Math.random()でランダムな値を得たいケースが多いので、基本的な扱いに慣れておくと何かと便利です。

    POINT!
  1. ・ランダムな値を得たい場合は、Math.random()を利用しよう!
  2. ・0〜任意の整数の範囲内でランダムな数値を得たい場合は、(整数+1)を掛けてMath.floor()で小数点以下を丸めよう!
  3. ・ランダムで選ばれた数値を他で利用したい場合は、returnで戻り値として返すのを忘れずに!

ユーザーが選ぶボタンと勝敗判定

これまでの過程で、じゃんけんゲームの開始前の状態と、じゃんけんが始まった時に手がランダムで選ばれるようにする仕組みが整いました。

あとは、ユーザーがグー・チョキ・パーの中から任意で一つ選ぶことができるようにして、勝敗判定を行えばゲームの完成です。

ランダムで選ばれた数値は、randomChoice()関数で得ることができるようになっているので、今度はユーザーが選んだ手を把握するための仕組みを作っていきましょう。

今回、画像の配列番号の関係で、0=グー、1=チョキ、2=パー…となっているので、ユーザーが選ぶ手も同じように、0ならばグー、1ならばチョキ…というふうにします。

具体的には、useState()で変数を定義しておき、グーボタンが押されたら0を変数に格納、チョキボタンが押された1を格納…という感じですね。

また、ユーザーがどのボタンを押したかが分かりやすいように、押されたボタン以外は非表示にしておきます。

そのためには、グー・チョキ・パーボタンが押されたかどうかを監視する必要があるので、こちらもuseState()を使って真偽値で管理しておきます。

さらに、勝敗判定によって『勝ち!』とか『負け!』とかのメッセージを表示させたいので、それを格納しておく状態変数もuseState()で定義しておきます。

このステップにおいて、追加で必要となる状態変数は次の通りです。

App.js(一部)

// ユーザーが選んだ手を管理する(0~2の数値が入る)
const [userChoice,setUserChoice] = useState(null);

// グー・チョキ・パーのいずれかのボタンが押されたかどうかを管理する
const [isButtonPress,setIsButtonPress] = useState(false);

// 勝敗判定のメッセージを格納する
const [message,setMessage] = useState("");

あとは、グー・チョキ・パーボタンと、勝敗判定メッセージを表示するテキストコンポーネントを用意して、ボタンが押されたら必要な処理が行われるように組み立てていくだけです。

勝敗判定はif文を使っても良いのですが、じゃんけんのように複数の条件の中から判定を行いたい場合は、switch文が分かりやすくて便利です。

ランダムで選ばれた数値とユーザーが選択した手の数値(グーなら0)を比較して、勝敗判定メッセージをsetMessage()で更新します。

なお、『まだボタンが押されていないか、ユーザーが選んだボタンならばボタンを表示』という条件を三項演算子で判定することで、ユーザーが選ばなかったボタンは非表示にすることができます。

以上を踏まえて、グー・チョキ・パーのButtonコンポーネントと、Textコンポーネントの部分を抜き出して確認してみましょう。

App.js(一部)

{isStart ? 
<View style={styles.userChoiceBtn}>
    {!isButtonPress || userChoice === 0 ? 
    <Button title="グー" 
        onPress={ () => {
            if(!isButtonPress) {
                const choicedNum = randomChoice();
                setUserChoice(0);
                setIsButtonPress(true);
                switch(choicedNum) {
                    case 0:
                        setMessage("引き分け");
                        break;
                    case 1:
                        setMessage("勝ち!");
                        break;
                    case 2:
                        setMessage("負け!");
                        break;
                    }
                }
            }
        }
    /> : null}

    {/* チョキ・パーボタンは省略 */}

</View> : null }

{/*勝敗結果表示*/}
<Text style={{margin: 10}}>{message}</Text>

まず、STARTボタンが押されるまではグー・チョキ・パーボタンは隠しておきたいので、isStartの真偽値と三項演算子で条件分岐させています。

さらに、グー・チョキ・パーボタンがいずれもまだ押されていないか、もしくは自身が押された場合のみボタンを表示させるため、OR(論理和:||)で条件を指定し、条件がtrueである場合にボタンが表示されるようにしています。

また、ユーザーがグーボタンを押した場合は、userChoice変数に0(グー)を格納しておいて画像の配列番号と比較したいので、setUserChoice()で0を代入しておきます。

そして、randomChoice()で得られたランダムな値(=画像の配列番号)を定数に格納した上で、switch文でuserChoice変数の値と比較し、それぞれのcaseにおいてsetMessage()で判定メッセージを設定すれば、勝敗を判定することができます。

なお、グー・チョキ・パーボタンが押されたかどうかを管理する変数(isButtonPress)を更新するのも忘れないようにしておきましょう。

上のコードではチョキボタンとパーボタンは省略していますが、userChoicに代入する数値やswitch文の条件判定の部分などが少し変わるだけです。

注意点としては、グー(またはチョキorパー)ボタンを連打しても問題ないようにしておく必要があるという点です。

そのままだとボタンが押される度に勝敗判定が行われてしまうことになりますが、これを防止するために、if文を使って『グー・チョキ・パーボタンがいずれもまだ押されていない状態(isButtonPressがfalse)でボタンが押された場合に処理が行われる』…という仕組みにしています。

このようにしておくことで、ユーザーがグー・チョキ・パーボタンのいずれかを押した後でもう一度ボタンを押したとしても何も起こらなくなり、処理が重複しないことになります。

一般的に、ボタンが連打されてしまうと開発者にとって不都合となる場合が多いです。(予期しない動作を引き起こしたり、処理が重複してアプリが重たくなったりなど)

なので、ボタンを設置する際は、『ユーザーが連続で何度も押しても問題が起こらないかどうか』を常に気にしておくのが大切です。

    POINT!
  1. ・ユーザーが選ぶじゃんけんの手を数値で管理し、ランダムで選ばれる画像の配列番号と比較することで勝敗の判定ができる!
  2. ・switch文を利用して、場合別に勝敗判定メッセージをセットしよう!
  3. ・ボタンを設置する際は、ボタンが連打されても大丈夫なように対策しておこう!

状況をリセットする

一度きりのじゃんけん勝負では、なんだか物足りないですよね。

人によっては、勝てるまで何度でも勝負したいと考える人もいると思います。

ということで、最後の仕上げとして、勝敗判定をリセットして、繰り返しプレイできる仕組みを整えていきまいと思います。

Webページであれば、ブラウザを更新させるだけで状況をリセットすることができますが、React Nativeで開発するモバイルアプリはブラウザで動作するわけではないので、ブラウザ更新というお手軽な手は使えません。

そもそも、React自体が『余計な読み込みや再レンダリングを減らして、高速に動作するアプリを構築する』という思想で設計されています。

そのため、画面全体を手軽に更新できる手段があったとしても、できるだけ使うべきではないと言えるでしょう。

Reactで状態を更新するには、状態変数を更新すればOKです。

ユーザーの選択を管理するuserChoice変数や、勝敗判定メッセージの状態を管理するmessage変数などを初期状態に戻すことで、それに関係する部分を更新(再レンダリング)させることができます。

状況をリセットするための関数を用意し、リセット(もう一度プレイする)ボタンでその関数を実行するようにすれば、続けてプレイすることが可能となります。

下のコードは、リセットに関係する部分を抜き出したものです。

App.js(一部)

// Viewをリセットするための関数
function resetView() {
  setMessage("");
  changeImage();
  setIsButtonPress(false);
  setUserChoice(null);
}

App.js(一部)

{/*リセット(もう一度プレイ)ボタン*/}
{isButtonPress !== false ? 
<Button title="もう一度" 
  onPress={resetView}
/> : null}

リセットボタンは、じゃんけんゲームが一度でもプレイされた…すなわちグー・チョキ・パーのいずれかのボタンが押された際に表示させたいので、isButtonPress(状態変数)と三項演算子で表示をコントロールしています。

この条件指定がなければ、STARTボタンが押された直後(勝敗判定がまだ行われていない時)に、『もう一度』ボタンが表示されてしまうため、おかしなことになってしまいます。

以上で、目標であったじゃんけんゲームアプリ制作の完了です!

とてもシンプルなアプリですが、最初から複雑なアプリを作れる人なんていません。

それに、複雑で高機能=良いアプリ…というわけではないですからね。(むしろ複雑過ぎて使いづらいアプリってたくさんありますよね)

その日の天気をお知らせするだけとか、タイミングに合わせて画面をタップするだけのゲームとかでも、それが誰かにとって役に立つものであれば、それは存在価値のある立派なアプリだと思います。

まずは今の自分のレベルでも作れそうなアプリを作ってみることから始めて、少しずつでもレベルアップしていければ良いのではないかと思います!

    POINT!
  1. ・React Nativeで開発されたアプリはブラウザで動作するわけではない!
  2. ・なので、画面を更新したいからといって、ブラウザを読み込み直せば良い…というわけにはいかない!
  3. ・Reactでは、状態変数の値を変えることで関連するコンポーネントを更新することができる!

« »

カテゴリーリンク

著者について- author profile -

ROYDOプロフィール写真
Michihiro

モバイルアプリ(iOS・Android)ディベロッパー&デザイナー

これまでに、可読性の高いカラーパターンを自動で生成するアプリや、『第3火曜日』といった形式で通知をスケジュールできるアプリなどを制作。

サブでWebデザイン・フロントエンドエンジニアとしても活動しています。

📝ツール・言語:JavaScript/React Native/Kotlin/Android Studio/Swift/SwiftUI

🎓資格:応用情報技術者/基本情報技術者/ウェブデザイン技能検定3級

Twitterアイコン Instagramアイコン