6日でできる 新Java入門|改良1:別のクイズ問題にする

問題データを差し替えるだけで、クイズゲームはアニメクイズアプリへ進化する

ここまで作成してきたクイズゲームでは、県庁所在地、世界史、日本史といった学習系のジャンルを選択し、CUIで問題に答える仕組みを作ってきました。

このクイズゲームは、すでに次のような基本機能を持っています。

機能内容
ジャンル選択起動後にクイズジャンルを選べる
ファイル読み込み選んだジャンルに応じて問題ファイルを読み込む
ランダム出題問題をランダムに出題する
選択肢生成正解1つと誤答3つで4択を作る
チートモードcheat 指定時に正解を事前表示する
クラス化QuizGame、QuizGenre、QuizManager に役割分担している

ここからは、このクイズゲームをさらに発展させて、GUIで遊べるアプリへ改造していく流れに入ります。
その第一歩として、今回の記事では「別のクイズ問題にする」改良を行います。

具体的には、これまでの 県庁所在地、世界史、日本史 の学習系クイズを、次の3つのアニメクイズに差し替えます。

新しいジャンル使用するテキストファイル
ドラゴンボール・クイズdragonball_quiz.txt
鬼滅の刃・クイズkimetsu_quiz.txt
ジョジョの奇妙な冒険・クイズjojo_quiz.txt

今回のポイントは、クイズ処理そのものを大きく作り直すのではなく、問題データとジャンル情報を差し替えることです。
すでにクラス化されている構造を活かせば、QuizManager.java の出題処理はそのまま使い、QuizGame.java と QuizGenre.java を中心に修正するだけで、アニメクイズへ切り替えられます。

つまり、今回の改良では「プログラムの仕組みを変える」のではなく、「扱う問題データを変える」ことが中心になります。

今回の改良で目指す形

これまでのクイズゲームは、次のようなジャンル構成でした。

番号変更前のジャンル使用ファイル
1県庁所在地クイズprefectures.txt
2世界史クイズworld_history.txt
3日本史クイズjapanese_history.txt

今回の改良後は、次のように変わります。

番号変更後のジャンル使用ファイル
1ドラゴンボール・クイズdragonball_quiz.txt
2鬼滅の刃・クイズkimetsu_quiz.txt
3ジョジョの奇妙な冒険・クイズjojo_quiz.txt

ここで大切なのは、ファイルの中身の形式を変えないことです。

これまでと同じように、各テキストファイルは次の形式で作成します。

問題文,正解

たとえば、dragonball_quiz.txt なら次のような形です。

孫悟空のサイヤ人としての名前,カカロット
悟空の長男,孫悟飯

この形式を守っておけば、QuizManager.java の読み込み処理を変更する必要がありません。
つまり、問題ジャンルを変えても、読み込み・出題・選択肢生成・採点の仕組みはそのまま使えます。

問題追加の考え方

今回のテーマは「別のクイズ問題にする」です。
そのため、プログラムの改造というよりも、問題データの差し替えとジャンル設定の変更が中心です。

処理のイメージは次のようになります。

作業内容
新しい問題ファイルを作るアニメ作品ごとのクイズデータを用意する
QuizGame.java を修正する画面に表示するジャンル名を変更する
QuizGenre.java を修正する読み込むファイル名とタイトルを変更する
QuizManager.java は基本的にそのままファイル形式が同じなので出題処理を再利用できる

この構成にしておくと、今後さらに別のアニメクイズやゲームクイズ、IT用語クイズなどを追加する場合も、同じ考え方で拡張できます。

クラスごとの変更範囲

今回の改良で、それぞれのクラスがどのように関係するかを整理しておきます。

ファイル名変更の有無内容
QuizGame.java変更ありジャンル表示をアニメクイズ用に変更する
QuizGenre.java変更ありファイル名、タイトル、ジャンル情報を変更する
QuizManager.java基本的に変更なし既存の出題処理をそのまま使う
dragonball_quiz.txt新規作成ドラゴンボールの問題を保存する
kimetsu_quiz.txt新規作成鬼滅の刃の問題を保存する
jojo_quiz.txt新規作成ジョジョの奇妙な冒険の問題を保存する

QuizManager.java を変更しなくてもよいのは、3つの新しい問題ファイルがこれまでと同じ 問題文,正解 の形式になっているからです。

このように、データ形式を統一しておくと、プログラム本体を大きく変更せずに問題だけ差し替えることができます。

QuizGame.java の変更ポイント

QuizGame.java は、プログラムの入口になるクラスです。
main()メソッド の中で、チートモード判定、ジャンル選択、QuizManager の起動を行います。

今回変更するのは、ジャンル選択画面の表示です。

変更前のジャンル表示

変更前は、学習系のクイズジャンルを表示していました。

System.out.println("ジャンルを選んでください:");
System.out.println("1: 県庁所在地クイズ");
System.out.println("2: 世界史クイズ");
System.out.println("3: 日本史クイズ");
System.out.print("==> ");

変更後のジャンル表示

変更後は、アニメクイズ用のジャンル名に変更します。

System.out.println("ジャンルを選んでください:");
System.out.println("1: ドラゴンボール・クイズ");
System.out.println("2: 鬼滅の刃・クイズ");
System.out.println("3: ジョジョの奇妙な冒険・クイズ");
System.out.print("==> ");

ここで行っているのは、画面表示の変更だけです。
入力処理や QuizManager を作る処理は、以前と同じまま使えます。

QuizGame.java の完成形

ファイル名:QuizGame.java

import java.util.Scanner;

public class QuizGame {
    public static void main(String[] args) throws Exception {
        boolean cheatMode = (args.length > 0 && args[0].equals("cheat"));

        System.out.println("ジャンルを選んでください:");
        System.out.println("1: ドラゴンボール・クイズ");
        System.out.println("2: 鬼滅の刃・クイズ");
        System.out.println("3: ジョジョの奇妙な冒険・クイズ");
        System.out.print("==> ");

        Scanner sc = new Scanner(System.in);
        int genre = 0;

        while (true) {
            try {
                genre = Integer.parseInt(sc.nextLine());
                if (genre >= 1 && genre <= 3) {
                    break;
                }
                System.out.print("1~3の数字で選んでください。\n==> ");
            } catch (Exception e) {
                System.out.print("数字を入力してください。\n==> ");
            }
        }

        QuizGenre quizGenre = QuizGenre.fromInt(genre);

        QuizManager manager = new QuizManager(
            quizGenre.getFileName(),
            quizGenre.getTitle(),
            quizGenre.getQuestionCount(),
            cheatMode
        );

        manager.startQuiz();
    }
}

QuizGame.java の処理の流れ

QuizGame.java の処理は、ジャンル名が変わっても基本的には同じです。

順番処理内容
1チートモード判定args に cheat があるか確認する
2ジャンル表示アニメクイズの3ジャンルを表示する
3入力受付1~3 の番号を受け取る
4QuizGenre取得入力番号に対応するジャンル情報を取得する
5QuizManager生成ファイル名、タイトル、問題数、チートモードを渡す
6クイズ開始manager.startQuiz() を実行する

このように、QuizGame.java はゲームの入り口として、ユーザーがどのクイズを遊ぶかを決める役割を持っています。

QuizGenre.java の変更ポイント

QuizGenre.java は、ジャンルごとの設定情報をまとめている enum です。
今回の改良では、この QuizGenre.java の中身をアニメクイズ用に変更します。

変更前の QuizGenre

変更前は、県庁所在地、世界史、日本史のジャンル情報を持っていました。

public enum QuizGenre {
    PREFECTURE(1, "prefectures.txt", "次の都道府県の県庁所在地はどれ?", 10),
    WORLD_HISTORY(2, "world_history.txt", "世界史クイズ:次の解答群で正しいものはどれ?", 10),
    JAPANESE_HISTORY(3, "japanese_history.txt", "日本史クイズ:次の解答群で正しいものはどれ?", 10);

変更後の QuizGenre

変更後は、読み込むファイル名とタイトルをアニメクイズ用に差し替えます。

public enum QuizGenre {
    DRAGONBALL(1, "dragonball_quiz.txt", "ドラゴンボール・クイズ", 10),
    KIMETSU(2, "kimetsu_quiz.txt", "鬼滅の刃・クイズ:次の解答群で正しいものはどれ?", 10),
    JOJO(3, "jojo_quiz.txt", "ジョジョの奇妙な冒険・クイズ:次の解答群で正しいものはどれ?", 10);

実際に扱う内容はアニメクイズへ変わっています。

分かりやすくするために、次のように enum 名も変更します。

変更前の名前変更後の名前
PREFECTUREDRAGONBALL
WORLD_HISTORYKIMETSU
JAPANESE_HISTORYJOJO

QuizGenre.java の完成形

ファイル名:QuizGenre.java

public enum QuizGenre {
    DRAGONBALL(1, "dragonball_quiz.txt", "ドラゴンボール・クイズ", 10),
    KIMETSU(2, "kimetsu_quiz.txt", "鬼滅の刃・クイズ:次の解答群で正しいものはどれ?", 10),
    JOJO(3, "jojo_quiz.txt", "ジョジョの奇妙な冒険・クイズ:次の解答群で正しいものはどれ?", 10);

    private int code;
    private String fileName;
    private String title;
    private int questionCount;

    QuizGenre(int code, String fileName, String title, int questionCount) {
        this.code = code;
        this.fileName = fileName;
        this.title = title;
        this.questionCount = questionCount;
    }

    public String getFileName() {
        return fileName;
    }

    public String getTitle() {
        return title;
    }

    public int getQuestionCount() {
        return questionCount;
    }

    public static QuizGenre fromInt(int code) {
        for (QuizGenre g : QuizGenre.values()) {
            if (g.code == code) {
                return g;
            }
        }
        return PREFECTURE;
    }
}

QuizGenre.java で管理している情報

QuizGenre.java では、各ジャンルに必要な情報をまとめて管理しています。

項目意味
codeユーザーが入力する番号
fileName読み込む問題ファイル名
title画面に表示するクイズタイトル
questionCount出題数

今回のアニメクイズでは、次のような対応になります。

codefileNametitlequestionCount
1dragonball_quiz.txtドラゴンボール・クイズ10
2kimetsu_quiz.txt鬼滅の刃・クイズ:次の解答群で正しいものはどれ?10
3jojo_quiz.txtジョジョの奇妙な冒険・クイズ:次の解答群で正しいものはどれ?10

QuizGame.java は、ユーザーの入力番号をもとに QuizGenre.fromInt(genre) を呼び出します。
そして、取得した QuizGenre から fileName、title、questionCount を取り出して QuizManager に渡します。

QuizManager.java は基本的に変更しない

今回の改良では、QuizManager.java は基本的に変更しません。

理由は、QuizManager.java が特定のジャンル名に依存していないからです。
QuizManager は、渡された fileName を読み込み、渡された title を表示し、問題と正解の組み合わせを使ってクイズを出題します。

つまり、dragonball_quiz.txt でも、kimetsu_quiz.txt でも、jojo_quiz.txt でも、次の形式さえ守っていれば同じように処理できます。

問題文,正解

この設計のよいところは、問題ジャンルを増やしても、クイズ本体の処理を使い回せることです。

QuizManager.java の確認

ファイル名:QuizManager.java

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;

public class QuizManager {
    private String fileName;
    private String title;
    private int questionCount;
    private boolean cheatMode;
    private ArrayList<String> questions = new ArrayList<String>();
    private ArrayList<String> answers = new ArrayList<String>();

    public QuizManager(String fileName, String title, int questionCount, boolean cheatMode) throws Exception {
        this.fileName = fileName;
        this.title = title;
        this.questionCount = questionCount;
        this.cheatMode = cheatMode;
        loadQuestions();
    }

    private void loadQuestions() throws Exception {
        BufferedReader br = new BufferedReader(new FileReader(fileName));
        String line;

        while ((line = br.readLine()) != null) {
            int idx = line.indexOf(',');
            questions.add(line.substring(0, idx));
            answers.add(line.substring(idx + 1));
        }

        br.close();
    }

    public void startQuiz() {
        int score = 0;
        Random rand = new Random();
        Scanner sc = new Scanner(System.in);
        ArrayList<String> qs = new ArrayList<String>(questions);
        ArrayList<String> as = new ArrayList<String>(answers);

        for (int i = 1; i <= questionCount; i++) {
            if (qs.size() == 0) {
                break;
            }

            int qIdx = rand.nextInt(qs.size());
            String questionText = qs.remove(qIdx);
            String correctAns = as.remove(qIdx);

            ArrayList<String> choices = new ArrayList<String>();
            ArrayList<String> backupAnswers = new ArrayList<String>(as);

            for (int j = 0; j < 3 && backupAnswers.size() > 0; j++) {
                int cIdx = rand.nextInt(backupAnswers.size());
                choices.add(backupAnswers.remove(cIdx));
            }

            int insertIdx = rand.nextInt(choices.size() + 1);
            choices.add(insertIdx, correctAns);

            System.out.println(i + "問目:" + title);
            System.out.println("「" + questionText + "」");

            for (int j = 0; j < choices.size(); j++) {
                System.out.println((j + 1) + ":" + choices.get(j));
            }

            if (cheatMode) {
                System.out.println("【チートモード】正解は「" + correctAns + "」です。");
            }

            int answer = 0;

            while (true) {
                System.out.print("==> ");
                try {
                    answer = Integer.parseInt(sc.nextLine());
                    if (answer >= 1 && answer <= 4) {
                        break;
                    }
                    System.out.println("1~4の数字で選んでください。");
                } catch (Exception e) {
                    System.out.println("数字を入力してください。");
                }
            }

            answer--;

            if (answer == insertIdx) {
                score++;
                System.out.println("正解です!");
            } else {
                System.out.println("不正解。正解は「" + correctAns + "」です。");
            }

            System.out.println();
        }

        System.out.println("全" + questionCount + "問終了!あなたの得点は " + score + " 点です。");
    }
}

QuizManager.java を変更しなくてよい理由

QuizManager.java がそのまま使える理由を整理すると、次のようになります。

理由内容
fileName を外部から受け取るQuizGenre が指定したファイルを読み込める
title を外部から受け取るジャンルごとのタイトルを表示できる
問題形式が同じ問題文,正解 の形なら共通処理で読める
出題処理が汎用的アニメでも歴史でも同じ流れで出題できる
correctAns を使って判定する正解の内容が何であっても判定できる

このように、QuizManager.java は特定ジャンルに依存しない作りになっています。
そのため、今回のように問題ジャンルを差し替えるときに強い構造になっています。

dragonball_quiz.txt を作成する

まず、ドラゴンボール用の問題ファイルを作成します。
ファイル名は dragonball_quiz.txt です。

Eclipseでは、このファイルをプロジェクト直下に配置します。

ファイル名:dragonball_quiz.txt

孫悟空のサイヤ人としての名前,カカロット
悟空の長男,孫悟飯
悟空の次男,孫悟天
ベジータの息子,トランクス
ピッコロの種族,ナメック星人
ドラゴンボールを7つ集めると現れる龍,神龍
亀仙人が悟空に教えた代表的な技,かめはめ波
悟空が最初に修行した武道の師匠,亀仙人
悟空の妻,チチ
クリリンの妻,人造人間18号
フリーザが支配していた軍団,フリーザ軍
惑星ベジータを滅ぼした人物,フリーザ
ナメック星で登場したドラゴンボールの龍,ポルンガ
ベジータの父,ベジータ王
悟空が界王から教わった技,界王拳
悟空が界王から教わった元気を集める技,元気玉
サイヤ人が大猿に変身する条件,満月を見ること
フリーザ編で悟空が初めて変身した姿,超サイヤ人
セルが吸収して完全体になった人造人間,人造人間17号と人造人間18号
セルゲームでセルを倒した人物,孫悟飯
魔人ブウを復活させた魔導師,バビディ
魔人ブウ編で悟空とベジータが合体した戦士,ベジット
フュージョンで悟空とベジータが合体した戦士,ゴジータ
悟空の瞬間移動を教えた種族,ヤードラット星人
天下一武道会で悟空が少年時代に戦ったライバル,天津飯
天津飯の相棒,餃子
ヤムチャの代表的な技,狼牙風風拳
ピッコロ大魔王が若返るために使ったもの,ドラゴンボール
神様とピッコロがもともと同じ存在だった星,ナメック星
悟飯が少年時代に修行した師匠,ピッコロ
ラディッツが地球に来た理由,悟空を仲間に戻すため
ナッパと一緒に地球へ来たサイヤ人,ベジータ
フリーザの最終形態と戦った超サイヤ人,孫悟空
ギニュー特戦隊の隊長,ギニュー
ギニューが使う体を入れ替える技,ボディチェンジ
人造人間16号を作った科学者,ドクター・ゲロ
未来トランクスが警告した脅威,人造人間
ミスター・サタンの娘,ビーデル
悟飯が高校時代に名乗ったヒーロー名,グレートサイヤマン
破壊神ビルスの付き人,ウイス
第7宇宙の破壊神,ビルス
悟空が神の気を得て変身した姿,超サイヤ人ゴッド
青い髪の超サイヤ人形態,超サイヤ人ブルー
力の大会で悟空が到達した境地,身勝手の極意
ベジータが力の大会で見せた進化形態,超サイヤ人ブルー進化
第11宇宙の最強戦士,ジレン
全王が持つ力,宇宙を消す力
ブルマの父が創設した会社,カプセルコーポレーション
ドラゴンレーダーを作った人物,ブルマ

このファイルでは、左側が問題文、右側が正解です。
QuizManager.java はカンマの位置を探し、左側を questions、右側を answers に保存します。

kimetsu_quiz.txt を作成する

次に、鬼滅の刃用の問題ファイルを作成します。

ファイル名:kimetsu_quiz.txt

鬼になった炭治郎の妹,竈門禰豆子
主人公の名前,竈門炭治郎
炭治郎が使う基本の呼吸,水の呼吸
炭治郎の家に伝わる舞,ヒノカミ神楽
炭治郎を鬼殺隊へ導いた水柱,冨岡義勇
炭治郎を育てた師匠,鱗滝左近次
炭治郎と同期の金髪の隊士,我妻善逸
善逸が使う呼吸,雷の呼吸
猪の被り物をした隊士,嘴平伊之助
伊之助が使う呼吸,獣の呼吸
鬼殺隊の最高位の剣士たち,柱
鬼の始祖,鬼舞辻無惨
鬼を倒すための刀,日輪刀
鬼が苦手とする花,藤の花
炎柱の名前,煉獄杏寿郎
音柱の名前,宇髄天元
霞柱の名前,時透無一郎
恋柱の名前,甘露寺蜜璃
蛇柱の名前,伊黒小芭内
風柱の名前,不死川実弥
岩柱の名前,悲鳴嶼行冥
蟲柱の名前,胡蝶しのぶ
水柱の名前,冨岡義勇
鬼殺隊の当主,産屋敷耀哉
炭治郎が最終選別を受けた山,藤襲山
善逸の師匠,桑島慈悟郎
善逸の兄弟子,獪岳
炭治郎の同期で栗花落カナヲの所属,蝶屋敷
胡蝶しのぶの姉,胡蝶カナエ
栗花落カナヲが使う呼吸,花の呼吸
那田蜘蛛山で登場した下弦の伍,累
夢を見せる血鬼術を使う下弦の壱,魘夢
遊郭編で登場した上弦の陸の兄,妓夫太郎
遊郭編で登場した上弦の陸の妹,堕姫
刀鍛冶の里で登場した上弦の肆,半天狗
刀鍛冶の里で登場した上弦の伍,玉壺
上弦の参の鬼,猗窩座
上弦の弐の鬼,童磨
上弦の壱の鬼,黒死牟
黒死牟の人間時代の名前,継国巌勝
日の呼吸を使った伝説の剣士,継国縁壱
炭治郎の耳飾りの模様,花札のような模様
禰豆子が日光を克服した場所,刀鍛冶の里
無限列車で炭治郎たちと共闘した柱,煉獄杏寿郎
遊郭編で炭治郎たちと共闘した柱,宇髄天元
刀鍛冶の里で炭治郎と共闘した柱,時透無一郎と甘露寺蜜璃
鬼殺隊員が使う連絡役の鳥,鎹鴉
炭治郎の嗅覚で分かる攻撃の隙,隙の糸
伊之助が育った環境,山
禰豆子が入って移動する箱,背負い箱
鬼殺隊の隊服の上に羽織る服,羽織

こちらも形式は同じです。
ジャンルが変わっても 問題文,正解 というルールを守ることで、QuizManager.java の処理をそのまま使えます。

jojo_quiz.txt を作成する

最後に、ジョジョの奇妙な冒険用の問題ファイルを作成します。

ファイル名:jojo_quiz.txt

第1部の主人公,ジョナサン・ジョースター
第1部の宿敵,ディオ・ブランドー
第1部の舞台となる国,イギリス
石仮面によって生まれる存在,吸血鬼
ジョナサンが習得した呼吸法,波紋
ジョナサンに波紋を教えた師匠,ウィル・A・ツェペリ
第2部の主人公,ジョセフ・ジョースター
ジョセフの得意な戦い方,頭脳戦
柱の男のリーダー格,カーズ
柱の男の一人で風を操る戦士,ワムウ
柱の男の一人で熱を操る戦士,エシディシ
究極生命体になった柱の男,カーズ
第3部の主人公,空条承太郎
承太郎のスタンド,スタープラチナ
DIOのスタンド,ザ・ワールド
第3部で一行が目指した場所,エジプト
承太郎の祖父,ジョセフ・ジョースター
花京院典明のスタンド,ハイエロファントグリーン
ジャン=ピエール・ポルナレフのスタンド,シルバーチャリオッツ
モハメド・アヴドゥルのスタンド,マジシャンズレッド
イギーのスタンド,ザ・フール
第4部の主人公,東方仗助
仗助のスタンド,クレイジー・ダイヤモンド
第4部の舞台,杜王町
虹村億泰のスタンド,ザ・ハンド
岸辺露伴のスタンド,ヘブンズ・ドアー
吉良吉影のスタンド,キラークイーン
吉良吉影が静かに暮らしたいと考える職業,会社員
第5部の主人公,ジョルノ・ジョバァーナ
ジョルノのスタンド,ゴールド・エクスペリエンス
ブローノ・ブチャラティのスタンド,スティッキィ・フィンガーズ
ナランチャ・ギルガのスタンド,エアロスミス
グイード・ミスタのスタンド,セックス・ピストルズ
レオーネ・アバッキオのスタンド,ムーディー・ブルース
パンナコッタ・フーゴのスタンド,パープル・ヘイズ
第5部の舞台,イタリア
パッショーネのボス,ディアボロ
ディアボロのスタンド,キング・クリムゾン
第6部の主人公,空条徐倫
徐倫のスタンド,ストーン・フリー
第6部の舞台となる刑務所,グリーン・ドルフィン・ストリート刑務所
エンリコ・プッチの最終的なスタンド,メイド・イン・ヘブン
第7部の主人公の一人,ジョニィ・ジョースター
ジョニィのスタンド,タスク
ジャイロ・ツェペリが使う技術,鉄球
第7部のレース名,スティール・ボール・ラン
第8部の主人公,東方定助
第8部の舞台,杜王町
東方定助のスタンド,ソフト&ウェット
ジョジョシリーズで精神エネルギーが形になった能力,スタンド
ジョースター家の宿命の印,星形のアザ

このファイルを選ぶと、ジョジョの奇妙な冒険に関する問題が出題されます。
出題の流れは他のジャンルと同じです。

問題ファイルを追加するときのルール

今回のように別ジャンルのクイズを追加するときは、テキストファイルの作り方がとても大切です。

ルール理由
1行に1問を書くreadLine() で1行ずつ読み込むため
問題文と正解をカンマで区切るindexOf(',') で分割しているため
ファイル名を QuizGenre.java と一致させるFileReader が指定ファイルを開くため
Eclipseではプロジェクト直下に置く相対パスで読み込むため
文字コードをUTF-8にする日本語の文字化けを防ぐため

特に注意したいのは、カンマです。
現在の QuizManager.java は、1行の中にある最初のカンマを探して、問題文と正解を分けています。

int idx = line.indexOf(',');
questions.add(line.substring(0, idx));
answers.add(line.substring(idx + 1));

そのため、次のような形式を守る必要があります。

問題文,正解

コロンやタブで区切ると、現在のプログラムでは正しく読み込めません。

実行方法

コマンドラインで実行する場合

通常モードで実行する場合は、次のようにします。

java QuizGame

チートモードで実行する場合は、次のようにします。

java QuizGame cheat

チートモードでは、問題と選択肢を表示したあとに、正解も表示されます。

Eclipseで実行する場合

実行構成からプログラムの引数に cheat を入力すると、チートモードで実行できます。

メニューの 実行 → 実行構成 を開きます。

チートモードで確認したい場合は、引数タブのプログラムの引数に cheat と入力して実行します。

通常モードの場合は、プログラムの引数を空にします。

実行結果のイメージ

たとえば、ドラゴンボール・クイズを選んだ場合、次のような流れになります。

ジャンルを選んでください:
1: ドラゴンボール・クイズ
2: 鬼滅の刃・クイズ
3: ジョジョの奇妙な冒険・クイズ
==> 1
1問目:ドラゴンボール・クイズ
「孫悟空のサイヤ人としての名前」
1:ベジータ
2:カカロット
3:ナメック星人
4:神龍
==> 2
正解です!

チートモードで実行した場合は、次のように正解が表示されます。

ジャンルを選んでください:
1: ドラゴンボール・クイズ
2: 鬼滅の刃・クイズ
3: ジョジョの奇妙な冒険・クイズ
==> 1
1問目:ドラゴンボール・クイズ
「孫悟空のサイヤ人としての名前」
1:ベジータ
2:カカロット
3:ナメック星人
4:神龍
【チートモード】正解は「カカロット」です。
==> 2
正解です!

このように、チートモードの仕組みもそのまま利用できます。
問題ファイルが変わっても、correctAns に正解が入る構造は同じだからです。

今回の改良で変わる部分と変わらない部分

今回の改良は、クイズゲームの見た目や出題内容を大きく変えます。
しかし、内部の基本処理はほとんどそのままです。

項目変更内容
ジャンル表示学習系クイズからアニメクイズへ変更
読み込むファイルprefectures.txt などから anime 系ファイルへ変更
問題内容県庁所在地・歴史からアニメ作品の問題へ変更
出題処理変更なし
選択肢生成変更なし
正誤判定変更なし
チートモード変更なし
QuizManager.java基本的に変更なし

このように、変わる部分と変わらない部分を分けて考えると、改造の見通しがよくなります。

今回のような改良では、次の考え方がとても大切です。

考え方内容
データを差し替える問題ファイルを変えることで出題内容を変える
設定を差し替えるQuizGenre.java でファイル名とタイトルを変える
共通処理を使い回すQuizManager.java の出題ロジックはそのまま使う
表示だけ変えるQuizGame.java のジャンル名を変更する

これは、実際のアプリ開発でもよく使う考え方です。
アプリの処理本体は変えずに、データや設定を差し替えることで別の内容に対応させる方法です。

図:問題データ差し替えでアニメクイズに変更する流れ

今回の改良は、問題データの差し替えと QuizGenre.java の設定変更が中心です。

↓クリックすると拡大表示されます。

この図が示していること

県庁所在地・歴史クイズで使っていたデータファイルを、アニメクイズ用のデータファイルに差し替える流れを示しています。

この図から分かること

QuizManager.java の出題処理は変更せず、QuizGenre.java のファイル名とタイトル、そしてテキストファイルの中身を変更するだけで、別ジャンルのクイズに対応できることが分かります。

図:アニメクイズ3ジャンルの構成

↓クリックすると拡大表示されます。

この図が示していること

QuizGame.java、QuizGenre.java、QuizManager.java、3つのアニメクイズ用テキストファイルがどのように連携しているかを示しています。

この図から分かること

ジャンル選択で選ばれた番号が QuizGenre.java でファイル名に変換され、そのファイルを QuizManager.java が読み込んでクイズを実行する流れが分かります。

問題追加で注意したいポイント

カンマを区切りとして使う

現在の読み込み処理は、カンマを基準に問題文と正解を分けています。
そのため、問題文や正解の中に不要なカンマを入れると、意図しない位置で分割される可能性があります。

よい例です。

主人公の名前,竈門炭治郎

避けたい例です。

主人公の名前,フルネーム,竈門炭治郎

後者のようにカンマが複数あると、現在のコードでは最初のカンマで分割されます。
そのため、問題文と正解の区切り以外ではカンマを使わないようにします。

ファイル名を完全に一致させる

QuizGenre.java に書いたファイル名と、実際に作成したファイル名は完全に一致している必要があります。

QuizGenre.java の指定実際に必要なファイル
dragonball_quiz.txtdragonball_quiz.txt
kimetsu_quiz.txtkimetsu_quiz.txt
jojo_quiz.txtjojo_quiz.txt

大文字と小文字、アンダースコア、拡張子まで一致している必要があります。
特に環境によっては、大文字小文字の違いも別ファイルとして扱われるため注意します。

ファイルを置く場所を確認する

Eclipseで実行する場合、これらのテキストファイルはプロジェクト直下に配置します。
src フォルダの中ではなく、プロジェクトの一番上の階層に置く想定です。

配置場所が違うと、FileReader がファイルを見つけられず、エラーになります。

問題数を十分に用意する

QuizGenre.java では questionCount が 10 になっています。
つまり、1回のプレイで10問出題する想定です。

ただし、4択を作るためには、正解以外の候補も必要です。
そのため、各ジャンルには10問ぴったりではなく、ある程度多めの問題を用意しておくと安心です。

今回の各アニメクイズファイルには十分な問題数があるため、10問出題するには問題ありません。

この改良から学べるJavaの考え方

今回の「別のクイズ問題にする」改良では、Javaプログラムを拡張するときに大切な考え方を学べます。

学べること内容
データ駆動の考え方プログラム本体ではなく、データを変えて動作を変える
enum による設定管理QuizGenre.java にジャンル情報をまとめる
クラスの責任分担QuizGame、QuizGenre、QuizManager の役割を保つ
共通処理の再利用QuizManager.java を変更せずに別ジャンルへ対応する
ファイル形式の重要性問題文,正解 の形式をそろえることで処理を共通化する

特に大切なのは、問題を増やすために毎回 QuizManager.java を改造しなくてよいという点です。

もしジャンルごとに出題処理を書き換えていたら、新しいクイズを追加するたびにコードが複雑になります。
しかし、今回のようにファイル形式を統一し、QuizGenre.java でファイル名を管理しておけば、問題データの差し替えだけでクイズの内容を変えられます。

これは、GUI化へ進む前の重要な準備にもなります。
画面をGUIにしても、内部でどの問題ファイルを読み、どのタイトルで出題するかという考え方は同じだからです。

今回の改良では、クイズゲームの中身を学習系からアニメ系へ差し替えました。
次の段階では、このアニメクイズをGUIで遊べるようにしていくことで、CUIアプリからGUIアプリへ発展させる準備が整っていきます。