初心者向き!VueJS で3択クイズゲームをつくってみよう

さてさて、このブログではできるだけプログラミングの楽しさを多くの人に知ってもらいたいという気持ちで公開している記事も多くあるのですが、どうしても扱っているのがプログラミングということで内容が複雑になってしまったり、テーマが業務的な内容になってしまいどうしたもんかな、なんて考えていました。

そして、私の経験上(ピアノや英語)でそうだったんですが、やはり学ぶにはできるだけ好きなものとくっつけると長続きますし、覚えも早かったので、今回は「仕事に直結しないけど、学ぶにはいいだろう」という内容で記事を書いてみることにします。

ということで、開発する内容はズバリ「3択クイズ」です。

3択クイズはテレビ番組なんかでもよく見るクイズ形式なので、題材としてはきっといいのではないかと考えました。

もちろん、初心者にもできるだけ分かりやすくしているので、ぜひ楽しみながらプログラミングに触れてみてくださいね。(最後に教材ソースコード2つをダウンロードできます!)

※ 環境: Google Chrome 70, Vue 2

やりたいこと

  • 1問ずつクイズを表示
  • 解答は3択
  • 最後に結果を表示する

レイアウトをつくる

ではまずは3択クイズのレイアウトを作成します。
今回はCSSにbootstrap、JavaScriptにVue.jsを使いますが、インターネット上から呼び出すのでインストールは必要ありません。

<html>
<head>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
    <style>

        body {
            padding: 50px;
        }

    </style>
</head>
<body>
    <div id="app" class="row">
        <div class="offset-3 col-6">
            <div class="card">
                <div class="card-body">
                    <p class="badge badge-dark">第 1 問</p>
                    <br>
                    <h4 class="card-title">囲碁の石の大きさはどっちが大きい?</h4>
                    <hr>
                    <button type="button" class="btn btn-primary btn-lg btn-block text-left">1. 白い石</button>
                    <button type="button" class="btn btn-primary btn-lg btn-block text-left">2. 黒い石</button>
                    <button type="button" class="btn btn-primary btn-lg btn-block text-left">3. どちらも同じ</button>
                </div>
            </div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script>
    <script>

        new Vue({
            el: '#app'
        })

    </script>
</body>
</html>

これを実行すると、次のようになります。

ではこのレイアウトを使ってJavaScript(Vue.js)部分を作っていきましょう。

JavaScript部分をつくる

クイズを表示する部分をつくる

では、クイズを表示する部分です。
まずは、questionsという名前の変数をdataに登録しましょう。

new Vue({
    el: '#app',
    data: {
        questions: [
            {
                question: '囲碁の石の大きさはどっちが大きい?',
                answers: [
                    '白い石',
                    '黒い石',
                    'どちらも同じ',
                ],
                answer: 1
            },
            {
                question: 'コンビニチェーンのセブンイレブン。アルファベット表記で1文字だけ小文字なのは?',
                answers: [
                    'N',
                    'L',
                    'V',
                ],
                answer: 0
            },
            {
                question: 'カラオケは「空○○」略です。空欄に入る言葉は何?',
                answers: [
                    'OK',
                    'オーケストラ',
                    'おけら',
                ],
                answer: 1
            }
        ]
    }
})

questionsは配列で、その中身はクイズの文章や選択肢、答えがオブジェクトで格納されています。

そして、現在のクイズ番号を保持するquestionIndexも追加します。
初期値はもちろん0です。

data: {
    questionIndex: 0,
    
    // 省略

}

次に現在のクイズ内容を自動的に取得できる疑似変数currentQuestioncomputedにつくります。

computed: {
    currentQuestion: function() {

        return this.questions[this.questionIndex];

    }
}

これで、レイアウトの中でcurrentQuestionとすれば現在のクイズ内容を取得することができます。

では、これらの変数をつかって実際に表示してみましょう。

まずは第 ○ 問の部分です。

<p class="badge badge-dark">第 {{ (questionIndex+1) }} 問</p>

プログラム上では配列の番号は0から始まるのでquestionIndexの初期値は0にしました。
しかし、このままだと “第 0 問” になってしまうので、1を足した結果を表示するようにしています。

そして質問が表示される部分です。
先ほどつくった疑似変数currentQuestionから質問部分を取得しています。

<h4 class="card-title">{{ currentQuestion.question }}</h4>

続いて3択の部分です。
ここは表示すべきデータが3つあるので、ループを使って一気に表示するようにしましょう。

<button
    type="button"
    class="btn btn-primary btn-lg btn-block text-left"
    v-for="(answer,index) in currentQuestion.answers">{{ (index+1) }}. {{ answer }}</button>

まず、v-forの中身ですが、

(answer,index) in currentQuestion.answers

とすることで、currentQuestion.anwersから1つずつデータを取得することができます。そして、answerには解答の選択肢(例えば「白い石」など)が格納され、index0から始まる番号です。

実行するとHTMLはこうなります。

<button type="button" class="btn btn-primary btn-lg btn-block text-left">1. 白い石</button>
<button type="button" class="btn btn-primary btn-lg btn-block text-left">2. 黒い石</button>
<button type="button" class="btn btn-primary btn-lg btn-block text-left">3. どちらも同じ</button>

解答したときの処理をつくる

クイズの表示は完了したので、続いて解答した時(ボタンをクリックした時)のプログラムをつくっていきましょう。

まず全ての解答を保持しておく変数(配列)、answersを登録します。

data: {
    answers: [],

    // 省略

}

先ほどのボタン部分にクリックイベント、@click="answer(index)"を付けます。

<button
    type="button"
    class="btn btn-primary btn-lg btn-block text-left"
    v-for="(answer,index) in currentQuestion.answers"
    @click="addAnswer(index)">{{ (index+1) }}. {{ answer }}</button>

そして、addAnswer()の本体です。

addAnswer: function(index) {

    this.answers.push(index);

    if(this.questions.length == this.answers.length) {

        // クイズが完了したとき

    } else {

        this.questionIndex++;

    }

}

まず答えていく度にanswersの中身がひとつずつ増えていくことになります。

そして、質問の数と解答の数を比較し、もし同じ(つまりすべて解答済み)の場合とそうでない場合の分岐をif分で作ります。

もしまだ次の質問が残っている場合は、questionIndex1追加するので、次のクイズが自動的に表示されることになります。

※ ちなみにmethodscomputedの違いは過去記事、Vueの「methods」と「computed」の違いをご覧ください。

クイズが完了したときの処理をつくる

では最後に全ての質問に答えたときの処理を作っていきましょう。

addAnswer: function(index) {

    this.answers.push(index);

    if(this.questions.length == this.answers.length) {

        var correctCount = 0;

        for(var i in this.answers) {

            var answer = this.answers[i];

            if(answer == this.questions[i].answer) {

                correctCount++;

            }

        }

        alert(correctCount +'問正解です!');

    } else {

        // 省略

    }

}

中身としては、解答データanswersをループさせてひとつずつ解答を取得し、正解データを比較します。そして、この2つが同じ(つまり正解)ならcorrectCount1を追加します。

そして全ての正解チェックが終わったらalert()を使って「○問正解です!」と表示するようにしています。

おまけ

どうしようか悩んだのですが、初心者向けということで今回は結果の表示を正解数だけにしています。

しかし、せっかくここまで作ったので、結果は「どの問題が正解で、正しい答えは何だったのか?」を表示するようにすることにしました。(そのため、今回教材ソースコードは、ショート・バージョンとフル・バージョンの2つがあります)

では、まずcomputedcompletedというクイズの解答がすべて完了しているかどうかが分かる疑似変数をつくります。

computed: {
    
    // 省略

    completed: function() {

        return (this.questions.length == this.answers.length);

    }
}

そして、先ほどつくったaddAnswer()alert()を削除します。

addAnswer: function(index) {

    this.answers.push(index);

    if(!this.completed) {

        this.questionIndex++;

    }

}

さらに、HTMLはこうなります。

<!-- クイズを表示する部分 -->
<div class="offset-3 col-6" v-if="!completed">
    <div class="card">
        <div class="card-body">
            <p class="badge badge-dark">第 {{ (questionIndex+1) }} 問</p>
            <br>
            <h4 class="card-title">{{ currentQuestion.question }}</h4>
            <hr>
            <button
                type="button"
                class="btn btn-primary btn-lg btn-block text-left"
                v-for="(answer,index) in currentQuestion.answers"
                @click="addAnswer(index)">{{ (index+1) }}. {{ answer }}</button>
        </div>
    </div>
</div>

<!-- 結果表示する部分 -->
<div class="offset-3 col-6" v-if="completed">
    <div class="card">
        <div class="card-body">
            <p class="badge badge-dark">結果</p>
            <div v-for="(question,index) in this.questions">
                <h4 class="card-title">{{ question.question }}</h4>
                <ul>
                    <li v-for="answer in question.answers">{{ answer }}</li>
                </ul>
                <span v-if="question.answer == answers[index]">正解 &#x1F44D;</span>
                <span v-else>不正解 &#x1F622;<br>正解は「{{ question.answers[question.answer] }}」でした。</span>
                <hr>
            </div>
        </div>
    </div>
</div>

ここで重要なのが、compltedtruefalseかで表示内容を切り替えていることです。つまり、解答中はクイズのみ表示し、解答が完了したら結果だけを表示するようにしています。

そして、結果ブロックでは各質問と答えを表示し、正解しているかどうかで表示を切り替えています。

これを実行するとこうなります。

今回は以上です。
お疲れ様でした!

デモページを用意しました

3択クイズを試していただけるよう、デモページを用意しました。
ぜひ楽しんでください♪

教材ソースコードをダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードすることができます。

short.htmlが短いバージョンで、full.htmlが結果表示までするフルバージョンになります。

VueJS で3択クイズゲームをつくる