HTML、CSS、JavaScriptクイズゲームWEBアプリ(JavaScript)

【JavaScriptコード解説】クイズゲーム作成(3/3)

クイズゲームのアイキャッチ画像03 HTML、CSS、JavaScript

WEBブラウザで動くクイズゲームのコード解説(JavaScript)

前回作成したWEBブラウザゲームのコード解説(JavaScript)です。

  • ブラウザゲームのURL
  • GitHubのURL

GitHubのPagesで公開しているのでゲームプレイできます。

クイズゲーム

コード概要

  • クイズデータを配列で定義
  • QuizGameクラスによるクイズゲームの実装
  • DOM操作を利用したユーザーインターフェースの制御
  • イベントリスナーを使用した対話的な機能の実現

クイズデータファイル (quiz-data.js)の解説

クリックするとコードが表示されます。

// クイズデータを格納する配列
const quizData = [
    // 問題1
    {
        question: "日本の首都は?", // 問題文
        choices: ["大阪", "東京", "京都", "名古屋"], // 選択肢
        correctAnswer: 1 // 正解のインデックス(0から始まるため、1は2番目の「東京」を指す)
    },
    // 問題2
    {
        question: "世界で最も人口の多い国は?", // 問題文
        choices: ["インド", "アメリカ", "中国", "ロシア"], // 選択肢
        correctAnswer: 2 // 正解のインデックス(0から始まるため、2は3番目の「中国」を指す)
    },
    // 問題3
    {
        question: "1 + 1 = ?", // 問題文
        choices: ["1", "2", "3", "4"], // 選択肢
        correctAnswer: 1 // 正解のインデックス(0から始まるため、1は2番目の「2」を指す)
    }
];

配列の宣言

const quizData = [
    // ... 配列の中身
];
  • const: 変数を宣言する際に使用するキーワード。再代入不可の定数を作成。
  • quizData: 配列を格納する変数名。
  • []: 配列を作成する記号。

この部分は、quizDataという名前の定数配列を宣言しています。この配列にクイズの問題データが格納されます。

オブジェクトの構造

{
    question: "日本の首都は?",
    choices: ["大阪", "東京", "京都", "名古屋"],
    correctAnswer: 1
}
  • {}: オブジェクトを作成する記号。
  • question: 問題文を格納するプロパティ。
  • choices: 選択肢を格納する配列プロパティ。
  • correctAnswer: 正解のインデックスを格納するプロパティ。

これは1つのクイズ問題を表すオブジェクトです。問題文、選択肢、正解のインデックスがそれぞれのプロパティに格納されています。

選択肢の配列

["大阪", "東京", "京都", "名古屋"]
  • []: 配列を作成する記号。
  • 各要素はカンマ,で区切られています。

これは選択肢を格納する配列です。各選択肢は文字列(ダブルクォーテーション""で囲まれた文字)として格納されています。

コメント

// 問題1
  • //: 一行コメントを示す記号。

これはコードの説明や注釈を書くためのコメントです。プログラムの実行には影響しませんが、コードの理解を助けます。

全体の構造

このコードは、3つのクイズ問題を含む配列を定義しています。各問題はオブジェクトとして表現され、問題文、選択肢、正解のインデックスを持っています。この構造により、プログラムは簡単にクイズデータにアクセスし、問題を表示したり、回答をチェックしたりすることができます。

クイズゲームロジックファイル (quiz-game.js)の解説

クリックするとコードが表示されます。

class QuizGame {
    constructor(quizData) {
        this.quizData = quizData; // クイズデータを保存
        this.currentQuestionIndex = 0; // 現在の問題インデックス
        this.score = 0; // スコア

        // HTML要素の取得
        this.questionElement = document.getElementById('question');
        this.choicesElement = document.getElementById('choices');
        this.resultElement = document.getElementById('result');
        this.nextButton = document.getElementById('next-button');

        // 次の問題ボタンにイベントリスナーを追加
        this.nextButton.addEventListener('click', () => this.loadNextQuestion());
        this.loadQuestion(); // 最初の問題を読み込む
    }

    loadQuestion() {
        const currentQuestion = this.quizData[this.currentQuestionIndex];
        this.questionElement.textContent = currentQuestion.question; // 問題文を設定
        this.choicesElement.innerHTML = ''; // 選択肢をクリア

        // 選択肢ボタンを生成
        currentQuestion.choices.forEach((choice, index) => {
            const button = document.createElement('button');
            button.textContent = choice;
            button.addEventListener('click', () => this.checkAnswer(index));
            this.choicesElement.appendChild(button);
        });

        this.resultElement.textContent = ''; // 結果表示をクリア
        this.nextButton.style.display = 'none'; // 次の問題ボタンを非表示
    }

    checkAnswer(choiceIndex) {
        const currentQuestion = this.quizData[this.currentQuestionIndex];
        if (choiceIndex === currentQuestion.correctAnswer) {
            this.score++; // スコアを増やす
            this.resultElement.textContent = '正解!';
        } else {
            this.resultElement.textContent = '不正解。正解は: ' + currentQuestion.choices[currentQuestion.correctAnswer];
        }

        this.disableChoices(); // 選択肢を無効化
        this.nextButton.style.display = 'block'; // 次の問題ボタンを表示
    }

    disableChoices() {
        // すべての選択肢ボタンを無効化
        const buttons = this.choicesElement.getElementsByTagName('button');
        for (let button of buttons) {
            button.disabled = true;
        }
    }

    loadNextQuestion() {
        this.currentQuestionIndex++; // 次の問題へ
        if (this.currentQuestionIndex < this.quizData.length) {
            this.loadQuestion(); // 次の問題を読み込む
        } else {
            this.showFinalResult(); // 全問題終了時の結果表示
        }
    }

    showFinalResult() {
        this.questionElement.textContent = 'クイズ終了!';
        this.choicesElement.innerHTML = ''; // 選択肢をクリア
        this.resultElement.textContent = `あなたの得点: ${this.score} / ${this.quizData.length}`;
        this.nextButton.style.display = 'none'; // 次の問題ボタンを非表示
    }
}

// DOMの読み込みが完了したらQuizGameインスタンスを作成
document.addEventListener('DOMContentLoaded', () => {
    new QuizGame(quizData);
});

クラス定義

class QuizGame {
    // ... クラスの中身
}
  • class: クラスを定義するキーワード
  • QuizGame: クラス名

クラスとは、関連するデータ(プロパティ)と機能(メソッド)をまとめた設計図のようなものです。この場合、QuizGameという名前のクラスを定義しています。このクラスは、クイズゲームの全ての機能を含んでいます。クラスを使うことで、コードを整理し、再利用しやすくすることができます。

コンストラクタ

constructor(quizData) {
    this.quizData = quizData;
    this.currentQuestionIndex = 0;
    this.score = 0;

    // HTML要素の取得
    this.questionElement = document.getElementById('question');
    this.choicesElement = document.getElementById('choices');
    this.resultElement = document.getElementById('result');
    this.nextButton = document.getElementById('next-button');

    // 次の問題ボタンにイベントリスナーを追加
    this.nextButton.addEventListener('click', () => this.loadNextQuestion());
    this.loadQuestion();
}
  • constructor: クラスのインスタンスが作成されるときに自動的に呼び出される特別なメソッド
  • this: クラスのインスタンス自身を指す
  • document.getElementById(): HTML要素をIDで取得するメソッド
  • addEventListener(): イベントリスナーを追加するメソッド

コンストラクタは、クラスのインスタンスが作成されるときに実行される特別なメソッドです。ここでは以下のことを行っています:

  1. クイズデータ(quizData)をクラスのプロパティとして保存
  2. 現在の問題のインデックスとスコアを初期化
  3. HTML上の必要な要素(問題文、選択肢、結果表示、次へボタン)を取得し、クラスのプロパティとして保存
  4. 「次へ」ボタンがクリックされたときの動作を設定(loadNextQuestionメソッドを呼び出す)
  5. 最初の問題を読み込む(loadQuestionメソッドを呼び出す)

これらの初期化処理により、クイズゲームを開始する準備が整います。

問題読み込みメソッド

loadQuestion() {
    const currentQuestion = this.quizData[this.currentQuestionIndex];
    this.questionElement.textContent = currentQuestion.question;
    this.choicesElement.innerHTML = '';

    currentQuestion.choices.forEach((choice, index) => {
        const button = document.createElement('button');
        button.textContent = choice;
        button.addEventListener('click', () => this.checkAnswer(index));
        this.choicesElement.appendChild(button);
    });

    this.resultElement.textContent = '';
    this.nextButton.style.display = 'none';
}
  • loadQuestion(): 現在の問題を画面に表示するメソッド
  • forEach(): 配列の各要素に対して処理を行うメソッド
  • document.createElement(): 新しいHTML要素を作成するメソッド
  • appendChild(): 子要素を追加するメソッド

このメソッドは、現在の問題を画面に表示する役割を果たします。具体的には:

  1. 現在の問題データを取得
  2. 問題文をHTML上に表示
  3. 以前の選択肢をクリア
  4. 新しい選択肢ボタンを作成し、それぞれにクリックイベントを設定
  5. 作成した選択肢ボタンをHTML上に追加
  6. 結果表示をクリア
  7. 「次へ」ボタンを非表示に

これにより、ユーザーは新しい問題と選択肢を見ることができ、回答する準備が整います。

回答チェックメソッド

checkAnswer(choiceIndex) {
    const currentQuestion = this.quizData[this.currentQuestionIndex];
    if (choiceIndex === currentQuestion.correctAnswer) {
        this.score++;
        this.resultElement.textContent = '正解!';
    } else {
        this.resultElement.textContent = '不正解。正解は: ' + currentQuestion.choices[currentQuestion.correctAnswer];
    }

    this.disableChoices();
    this.nextButton.style.display = 'block';
}
  • checkAnswer(): ユーザーの回答を確認するメソッド
  • if-else: 条件分岐を行う制御構文

このメソッドは、ユーザーが選択した回答が正解かどうかをチェックします:

  1. 現在の問題データを取得
  2. ユーザーの選択(choiceIndex)と正解(correctAnswer)を比較
  3. 正解の場合、スコアを増やし、「正解!」と表示
  4. 不正解の場合、正解を表示
  5. 全ての選択肢ボタンを無効化(disableChoicesメソッドを呼び出し)
  6. 「次へ」ボタンを表示

これにより、ユーザーは自分の回答が正解かどうかを即座に知ることができます。

選択肢無効化メソッド

disableChoices() {
    const buttons = this.choicesElement.getElementsByTagName('button');
    for (let button of buttons) {
        button.disabled = true;
    }
}
  • disableChoices(): 全ての選択肢ボタンを無効化するメソッド
  • getElementsByTagName(): 指定したタグ名を持つ全ての要素を取得するメソッド
  • for...of: 配列やイテラブルオブジェクトの要素を順に処理するループ

このメソッドは、全ての選択肢ボタンを無効化します:

  1. 選択肢の親要素から全てのボタン要素を取得
  2. 各ボタンに対して、disabledプロパティをtrueに設定

これにより、ユーザーが回答後に再度選択できないようにし、1問につき1回だけ回答できるようにします。

次の問題読み込みメソッド

loadNextQuestion() {
    this.currentQuestionIndex++;
    if (this.currentQuestionIndex < this.quizData.length) {
        this.loadQuestion();
    } else {
        this.showFinalResult();
    }
}
  • loadNextQuestion(): 次の問題を読み込むメソッド
  • if-else: 条件分岐を行う制御構文

このメソッドは、次の問題に進むか、クイズを終了するかを決定します:

  1. 現在の問題インデックスを1増やす
  2. まだ問題が残っているか確認
  3. 問題が残っている場合、次の問題を読み込む(loadQuestionメソッドを呼び出す)
  4. 全ての問題が終了している場合、最終結果を表示(showFinalResultメソッドを呼び出す)

これにより、クイズを順番に進めていき、全問題が終わったら結果を表示します。

最終結果表示メソッド

showFinalResult() {
    this.questionElement.textContent = 'クイズ終了!';
    this.choicesElement.innerHTML = '';
    this.resultElement.textContent = `あなたの得点: ${this.score} / ${this.quizData.length}`;
    this.nextButton.style.display = 'none';
}
  • showFinalResult(): クイズの最終結果を表示するメソッド
  • テンプレートリテラル(“): 文字列内に変数を埋め込むための記法

このメソッドは、クイズ終了時にユーザーの最終スコアを表示します:

  1. 問題文表示領域に「クイズ終了!」と表示
  2. 選択肢をクリア
  3. 結果表示領域にユーザーの得点を表示(正解数と全問題数)
  4. 「次へ」ボタンを非表示に

これにより、ユーザーは自分の最終成績を確認することができます。

インスタンス生成

document.addEventListener('DOMContentLoaded', () => {
    new QuizGame(quizData);
});
  • addEventListener(): イベントリスナーを追加するメソッド
  • DOMContentLoaded: HTMLドキュメントの読み込みと解析が完了したときに発火するイベント
  • new: 新しいオブジェクトを作成するキーワード

この部分は、HTMLドキュメントの読み込みが完了したときにQuizGameクラスの新しいインスタンスを作成します:

  1. DOMContentLoadedイベントのリスナーを設定
  2. イベントが発火したら、QuizGameクラスの新しいインスタンスを作成
  3. 作成時にquizDataを引数として渡す

これにより、ページの読み込みが完了したタイミングでクイズゲームが開始されます。HTMLの準備ができてからゲームを開始することで、エラーを防ぎ、スムーズな動作を保証します。

まとめ

コードの特徴

  • データ構造:
    • クイズデータをオブジェクトの配列として定義
    • 各問題に対して質問、選択肢、正解を含む構造
  • クラスベース設計:
    • QuizGameクラスによるカプセル化
    • コンストラクタでの初期化とメソッドによる機能分割
  • DOM操作:
    • getElementByIdによる要素の取得
    • innerHTMLtextContentを使用したコンテンツ更新
    • createElementによる動的な要素生成
  • イベント処理:
    • ボタンクリックに対するイベントリスナーの設定
    • DOMContentLoadedイベントを利用した初期化
  • 状態管理:
    • 現在の問題インデックスとスコアの追跡
    • 問題の進行に応じたUI更新
  • モジュール化:
    • 機能ごとに分割されたメソッド(loadQuestion, checkAnswer等)

このブログ記事は、Webブラウザで動作するクイズゲームの内部ロジックと動作の仕組みを理解するのに役立ちます。JavaScriptを使ったインタラクティブなWebアプリケーションの基本的な構造と実装方法を学ぶことができます。

特に注目すべき点は、オブジェクト指向プログラミングの概念です。QuizGameクラスを使用することで、関連する機能とデータを一つのまとまりとして管理し、コードの再利用性と保守性を高めています。また、イベントリスナーを使用して、ユーザーの操作に応じて適切な処理を行う方法も学べます。

このコードを理解し、自分で修正や拡張を加えてみることで、JavaScriptプログラミングのスキルを向上させることができます。例えば、問題数を増やしたり、タイマー機能を追加したり、スコアの保存機能を実装したりするなど、様々な拡張が可能です。実際にコードを書いて動かしてみることが、最も効果的な学習方法です。

コメント

タイトルとURLをコピーしました