JavaScriptでランダム出題の２択クイズ制作

# はじめに
HTML/CSSくらいしか触ったことがなく、他にも挑戦したい！せっかく勉強するなら何か制作したい...と考え、JavaScriptを用いて２択クイズを制作しました。

# すべてのコード
今回は、scriptタグでHTMLファイルに直接JavaScriptのコードを入力する方法で制作しました。
<details><summary>HTML</summary>

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="css/reset.css">
    <link rel="stylesheet" href="css/style.css">
    <title>石川クイズ</title>
    <script>
        let Q = [//クイズ
            ['Q.金沢市にある日本三名園は「兼六園」である', ' onclick="Q_(1);" >〇', ' onclick="Q_(2);" >×',
             '正解は「〇」！</br>金沢市の中心部に位置する兼六園は、季節を彩る花々を眺めながら散策を楽しむのがおすすめです。'],
            ['Q.石川県の伝統的工芸品の中に「輪島塗」がある', ' onclick="Q_(1);" >〇', ' onclick="Q_(2);" >×',
             '正解は「〇」<br/>輪島でとれる「地の粉」を下地に塗り、何度も漆を塗っては研くことで丈夫な仕上がりになります。'],
            ['Q.石川県で最も高い山は「別山」である。', ' onclick="Q_(2);" >〇', ' onclick="Q_(1);" >×',
             '正解は「×」</br>最も高い山は「白山」です。富士山、立山と並ぶ日本三名山の一つで、2,702mあります。日帰りでも登ることが可能です。'],
            ['Q.石川県の代表的な祭りの金沢百万石まつりは、加賀藩主前田利家の入城を祝う祭りである', ' onclick="Q_(1);" >〇', ' onclick="Q_(2);" >×',
             '正解は「〇」</br>毎年６月に開催される金沢最大のイベントです。メインイベントの「百万石行列」は、海外からも多くの人が見物に訪れます。'],
            ['Q.石川県の粟津温泉・片山津温泉・山代温泉・山中温泉の総称を「金沢温泉郷」という', ' onclick="Q_(2);" >〇', ' onclick="Q_(1);" >×',
             '正解は「×」</br>正解は「加賀温泉郷」です。金沢から南に約40mの南加賀地域にある温泉地で、それぞれ異なる泉質や雰囲気を楽しむことができます。'],
            ['Q.石川県の日本海に面した半島は能登半島である。', ' onclick="Q_(1);" >〇', ' onclick="Q_(2);" >×',
             '正解は「〇」</br>能登半島は日本海に大きく突き出しており、変化に富んだ美しい景観が楽しめる景勝地です。'],
            ['Q.石川県ではお正月に買う鏡餅は、紅×白である', ' onclick="Q_(1);" >〇', ' onclick="Q_(2);" >×',
             '正解は「〇」</br>石川県の和菓子店やスーパーには紅白が並びます。上段が紅、下段が白だった前田家の鏡餅に対し、町人が真似をしたとされています。'],
            ['Q.石川県の特産品である「加賀棒茶」は、１度雪を被った茶葉を焙じる。', ' onclick="Q_(2);" >〇', ' onclick="Q_(1);" >×',
             '正解は「×」</br>加賀棒茶は新茶の茎を浅く焙じて作るのが特徴です。すっきりとした飲み心地で、季節を問わず楽しむことができます。'],
            ['Q.金沢城の別名に「白鳥城」がある', ' onclick="Q_(2);" >〇', ' onclick="Q_(1);" >×',
             '正解は「×」</br>別名は尾山城、尾上城、金城があります。金沢城は季節によって開園時間が異なるので、訪れる際は要チェックです。'],
            ['Q.能登半島は車で２時間程度で１周することができる', ' onclick="Q_(2);" >〇', ' onclick="Q_(1);" >×',
             '正解は「×」</br>能登半島は1周約300kmあり、車で4時間半以上かかります。しかし、公共交通機関よりもレンタカーを利用する方が便利です。'],
        ];

        let quizCount = 0; // 回答回数
        let correctCount = 0; // 正解数
        let incorrectQuestions = []; // 誤答した問題を保持する配列
        let selectedQuestion; // 現在の問題

        //初期表示
        window.onload = function() {
            showQuestion();
        }
        
        //次の問題を表示するためのプログラム
        function Q_(oxq) {
            if (oxq === 0) {
                if (Q.length === 0) {
                    // 問題がなくなった場合
                    showResult();
                } else {
                    // ランダムで問題を選択
                    let randomIndex = Math.floor(Math.random() * Q.length);
                    selectedQuestion = Q[randomIndex];

                    document.getElementById("ox").innerHTML = '';
                    document.getElementById("quiz").innerHTML = '';

                    let quiz = document.getElementById('quiz');
                    quiz.insertAdjacentHTML('afterbegin', '<p>' + selectedQuestion[0] + '</p>');
                    quiz.insertAdjacentHTML('beforeend', '<button' + selectedQuestion[1] + '</button>');
                    quiz.insertAdjacentHTML('beforeend', '<button' + selectedQuestion[2] + '</button><br/>');

                    // 選択された問題を削除
                    Q.splice(randomIndex, 1);
                }
            } else if (oxq === 1) {
                correctCount++;
                document.getElementById("ox").innerHTML = '<p class="true">正解！</p>';
                document.getElementById("quiz").innerHTML = '<p>' + selectedQuestion[3] + '</p>';
                quizCount++;
                if (quizCount < 10) {
                    document.getElementById("next").innerHTML = '<p><button onClick="Q_(0);">次の問題へ</button></p>';
                } else {
                    showResult();
                }
            } else if (oxq === 2) {
                incorrectQuestions.push(selectedQuestion);
                document.getElementById("ox").innerHTML = '<p class="false">不正解！</p>';
                document.getElementById("quiz").innerHTML = '<p>' + selectedQuestion[3] + '</p>';
                quizCount++;
                if (quizCount < 10) {
                    document.getElementById("next").innerHTML = '<p><button onClick="Q_(0);">次の問題へ</button></p>';
                } else {
                    showResult();
                }
            }
        }

        function showResult() {
            document.getElementById("ox").innerHTML = '<p class="end">終了！</br>おつかれさまでした！</p>';
            document.getElementById("quiz").innerHTML = '<p>正解数: ' + correctCount + '/10</p>';

            if (incorrectQuestions.length > 0) {
                document.getElementById("quiz").innerHTML += '<p>誤答した問題:</p>';
                document.getElementById("quiz").innerHTML += '<ul>';
                incorrectQuestions.forEach(function (question) {
                    document.getElementById("quiz").innerHTML += '<li>' + question[0] + '</li>';
                });
                document.getElementById("quiz").innerHTML += '</ul>';
            }

            document.getElementById("next").innerHTML = '';
        }

        // 最初の問題を表示する関数
        function showQuestion() {
            if (Q.length === 0) {
                // 問題がなくなった場合
                showResult();
            } else {
                // ランダムで問題を選択
                let randomIndex = Math.floor(Math.random() * Q.length);
                selectedQuestion = Q[randomIndex];

                document.getElementById("ox").innerHTML = '';
                document.getElementById("quiz").innerHTML = '';

                let quiz = document.getElementById('quiz');
                quiz.insertAdjacentHTML('afterbegin', '<p>' + selectedQuestion[0] + '</p>');
                quiz.insertAdjacentHTML('beforeend', '<button' + selectedQuestion[1] + '</button>');
                quiz.insertAdjacentHTML('beforeend', '<button' + selectedQuestion[2] + '</button><br/>');

                // 選択された問題を削除
                Q.splice(randomIndex, 1);
            }
        }
    </script>
</head>
<body>
<div class="inner-block">
    <h1>石川クイズ</h1>

    <script>
        document.write('<p class="seifu" id="ox"></p>');

        document.write('<p class="qu" id="quiz">');
        document.write('<button class="bott1"' + Q[0][1] + '</button>');
        document.write('<button class="bott2"' + Q[0][2] + '</button><br/>');
        document.write('</p>');

        document.write('<p class="next" id="next"><button onClick="Q_(0);">次の問題へ</button></p>');
    </script>
</div>
</body>
</html>

```
</details>

<details><summary>CSS</summary>

```css
@charset "UTF-8";
.inner-block {
    text-align: center;
    position: relative;
    border: 2px solid #000;
    padding: 20px 0;
    width: 1200px;
    margin: 120px auto;
    background-color: #fff;
}

h1 {
    font-size: 40px;
}

.qu {
    font-size: 50px;
}

button {
    background-color: #EAA770;
    width: 250px;
    margin: 20px;
    padding: 10px 0;
    border-radius: 15px;
}

.true {
    font-size: 50px;
    color: #f00;
}

.false {
    font-size: 50px;
    color: #00f;
}

.end {
    font-size: 50px;
    text-decoration: underline; 
}

li {
    font-size: 30px;
    text-align: left;
    padding: 20px;
}
```
</details>

# 目次
- 多次元配列でクイズを作る
- 乱数でランダムに問題を選ぶ
- クイズと選択肢を表示する
- 引数で正解と不正解を判定する
- 回答を終了したら回答数と正解数、誤答した問題を表示する

## 多次元配列でクイズを作る
**let 配列名 = [];**　で配列を作ります。
今回はランダムで問題を表示するために配列の中にさらに配列が入っている多次元配列を使いました。

```html:main.html
<script>
    let Q = [
        ['Q.金沢市にある日本三名園は「兼六園」である', ' onclick="Q_(1);" >〇', ' onclick="Q_(2);" >×',
        '正解は「〇」！</br>金沢市の中心部に位置する兼六園は、季節を彩る花々を眺めながら散策を楽しむのがおすすめです。'],
    ]
</script>
```

`'Q.金沢市にある日本三名園は「兼六園」である'`
これはクイズの問題文です。

`'onclick="Q_(1);">〇'`
これはクイズの正解を選択するボタンを表しています。onclick属性によってこのボタンがクリックされたときにQ_関数が呼び出され、引数として正解である１が渡されます。このボタンの表示は〇です。

`'onclick="Q_(2);">×'`
これはクイズの不正解を選択するボタンを表しています。上と同様に。onclick属性によってこのボタンがクリックされたときにQ_関数が呼び出され、引数として不正解である２が渡されます。このボタンの表示は×です。

`'正解は「〇」！</br>金沢市の中心部に位置する兼六園は、季節を彩る花々を眺めながら散策を楽しむのがおすすめです。'`
これはクイズの正解とその解説を表しています。クイズに回答した後に使います。
他のクイズも同様の構造を持っています。

## 乱数でランダムに問題を選ぶ

```html:main.html
<script>
    let randomIndex = Math.floor(Math.random() * Q.length);
    selectedQuestion = Q[randomIndex];

    document.getElementById("ox").innerHTML = '';
    document.getElementById("quiz").innerHTML = '';

    let quiz = document.getElementById('quiz');
    quiz.insertAdjacentHTML('afterbegin', '<p>' + selectedQuestion[0] + '</p>');
    quiz.insertAdjacentHTML('beforeend', '<button' + selectedQuestion[1] + '</button>');
    quiz.insertAdjacentHTML('beforeend', '<button' + selectedQuestion[2] + '</button><br/>');

    // 選択された問題を削除
    Q.splice(randomIndex, 1);
</script>
```

`let randomIndex = Math.floor(Math.random() * Q.length);`
Math.random()で０以上１未満のランダムな浮動小数点数を生成しています。
Math.floor()は小数点以下を切り捨て、０から`Q.length-1`の整数を得ます。

`selectedQuestion = Q[randomIndex];`
ランダムに選ばれた問題がselectedQuestionに格納されます。これによってselectedQuestion関数が呼ばれるたびにランダムな問題が表示されます。

`Q.splice(randomIndex, 1);`
選択された問題をQ配列から削除します。
これによって、同じ問題が選ばれることを防ぎます。

## クイズと選択肢を表示する

```html:main.html
<script>
    document.write('<p class="seifu" id="ox"></p>');
    document.write('<p class="qu" id="quiz">');
</script>
```

`document.write('<p class="seifu" id="ox"></p>');`
クイズの正誤を表示しています。

`document.write('<p class="qu" id="quiz">');`
クイズの問題文と選択肢を表示しています。

## 引数で正解と不正解を判定する

```html:main.html
<script>
function Q_(oxq) {
            if (oxq === 0) {
                if (Q.length === 0) {
                    // 問題がなくなった場合
                    showResult();
                } else {
                    // ランダムで問題を選択
                    let randomIndex = Math.floor(Math.random() * Q.length);
                    selectedQuestion = Q[randomIndex];

                    document.getElementById("ox").innerHTML = '';
                    document.getElementById("quiz").innerHTML = '';

                    let quiz = document.getElementById('quiz');
                    quiz.insertAdjacentHTML('afterbegin', '<p>' + selectedQuestion[0] + '</p>');
                    quiz.insertAdjacentHTML('beforeend', '<button' + selectedQuestion[1] + '</button>');
                    quiz.insertAdjacentHTML('beforeend', '<button' + selectedQuestion[2] + '</button><br/>');

                    // 選択された問題を削除
                    Q.splice(randomIndex, 1);
                }
            } else if (oxq === 1) {
                correctCount++;
                document.getElementById("ox").innerHTML = '<p class="true">正解！</p>';
                document.getElementById("quiz").innerHTML = '<p>' + selectedQuestion[3] + '</p>';
                quizCount++;
                if (quizCount < 10) {
                    document.getElementById("next").innerHTML = '<p><button onClick="Q_(0);">次の問題へ</button></p>';
                } else {
                    showResult();
                }
            } else if (oxq === 2) {
                incorrectQuestions.push(selectedQuestion);
                document.getElementById("ox").innerHTML = '<p class="false">不正解！</p>';
                document.getElementById("quiz").innerHTML = '<p>' + selectedQuestion[3] + '</p>';
                quizCount++;
                if (quizCount < 10) {
                    document.getElementById("next").innerHTML = '<p><button onClick="Q_(0);">次の問題へ</button></p>';
                } else {
                    showResult();
                }
            }
        }
</script>
```

`if(oxq === 0){}`
もし引数が０なら（次の問題へボタンを押したら）、次の問題を表示するプログラムを実行します。
`else if(oxq === 1){}`
引数が１なら（正解を選択したら）、「正解」のプログラムを実行します。

## 引数で正解と不正解を判定する

```html:main.html
<script>
    function Q_(oxq) {
        if (oxq === 0) {
            if (Q.length === 0) {
                // 問題がなくなった場合
                showResult();
            } else {
                // ランダムで問題を選択
                let randomIndex = Math.floor(Math.random() * Q.length);
                selectedQuestion = Q[randomIndex];

                document.getElementById("ox").innerHTML = '';
                document.getElementById("quiz").innerHTML = '';

                let quiz = document.getElementById('quiz');
                quiz.insertAdjacentHTML('afterbegin', '<p>' + selectedQuestion[0] + '</p>');
                quiz.insertAdjacentHTML('beforeend', '<button' + selectedQuestion[1] + '</button>');
                quiz.insertAdjacentHTML('beforeend', '<button' + selectedQuestion[2] + '</button><br/>');

                // 選択された問題を削除
                Q.splice(randomIndex, 1);
            }
        } else if (oxq === 1) {
            correctCount++;
            document.getElementById("ox").innerHTML = '<p class="true">正解！</p>';
            document.getElementById("quiz").innerHTML = '<p>' + selectedQuestion[3] + '</p>';
            quizCount++;
            if (quizCount < 10) {
                document.getElementById("next").innerHTML = '<p><button onClick="Q_(0);">次の問題へ</button></p>';
            } else {
                showResult();
            }
        } else if (oxq === 2) {
            incorrectQuestions.push(selectedQuestion);
            document.getElementById("ox").innerHTML = '<p class="false">不正解！</p>';
            document.getElementById("quiz").innerHTML = '<p>' + selectedQuestion[3] + '</p>';
            quizCount++;
            if (quizCount < 10) {
                document.getElementById("next").innerHTML = '<p><button onClick="Q_(0);">次の問題へ</button></p>';
            } else {
                showResult();
            }
        }
    }
</script>
```
`if (oxq === 0) {}`
引数が０なら（**「次の問題へ」ボタンを押したら**）、次の問題を表示するプログラムを実行します。

`else if (oxq === 1) {}`
引数が１なら（**正解の選択肢を選んだら**）、正解のプログラムを実行します。

`else if (oxq === 2) {}`
引数が２なら（**不正解の選択肢を選んだら**）、不正解のプログラムを実行します。

## 回答を終了したら回答数と正解数、誤答した問題を表示する

```html:main.html
<script>
function showResult() {
        document.getElementById("ox").innerHTML = '<p class="end">終了！</br>おつかれさまでした！</p>';
        document.getElementById("quiz").innerHTML = '<p>正解数: ' + correctCount + '/10</p>';

        if (incorrectQuestions.length > 0) {
            document.getElementById("quiz").innerHTML += '<p>誤答した問題:</p>';
            document.getElementById("quiz").innerHTML += '<ul>';
            incorrectQuestions.forEach(function (question) {
                document.getElementById("quiz").innerHTML += '<li>' + question[0] + '</li>';
            });
            document.getElementById("quiz").innerHTML += '</ul>';
        }
    }
</script>
```

このshowResult関数は、クイズに１０問答えて終了した後に呼び出されます。

`document.getElementById("ox").innerHTML = '<p class="end">終了！</br>おつかれさまでした！</p>';`
正解・不正解の表示エリアに、回答が終了したことを通知するメッセージを表示します。

`document.getElementById("quiz").innerHTML = '<p>正解数: ' + correctCount + '/10</p>';`
ここで１０問中何問正解したかを表示します。
先ほどの「引数で正解と不正解を判定する」で、引数が１なら（正解の選択肢を選んだら）実行される部分にある`correctCount++`で、正解した回数がカウントされています。

`if (incorrectQuestions.length > 0) {}`
誤答した問題があった場合、誤答した問題を表示します。incorrectQuestions配列には、誤答した各問題が格納されています。

# 結果
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="MWxWPKv" data-user="rmumjlxb-the-looper" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/rmumjlxb-the-looper/pen/MWxWPKv">
  石川クイズ</a> by azzz (<a href="https://codepen.io/rmumjlxb-the-looper">@rmumjlxb-the-looper</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

# 終わりに
それぞれの関数がどの動作につながっているか完全に理解できていないまま制作していましたが、言葉で表すことで理解を深めることができました。

# 参考資料
- ノコノコ,[JavaScript入門講座｜クイズを作ってみよう](https://janken.asotetu.work/js_quiz/
),学んで遊んでジャンケンポン,2022.08.18,参照2023.11.01