簡単なクイズアプリを作成してみた

# はじめに
python3の試験対策で覚えたことを利用して何かを作ろうと思い、簡単なクイズアプリを作成しました。

データクラスやリスト内包表記を実際にはどういう場面で使うのか？というヒントになればと思い作成したものです。

もっと作り込んでいけばGUIにしたり色々できると思いますが、「覚えたことをアウトプットするために作ったもの」ということで温かい目で見ていただければと思います。

# 1.クイズアプリの概要
・４択の問題に回答し、正解と解説を表示する。
・jsonデータに格納された問題をアプリケーションに取り込む。

# 2.プログラムの概要
・問題文と選択肢、解答、解説を格納したQuestionクラスを定義する。デコレータとしてdataclassを利用する。また、これに対応したjsonデータを準備する。

・dataclassにjsonのデータを引き渡すための関数read_jsonを定義する。ここでは、リスト内包表記によりQuestionクラスにjsonデータを引き渡す。また、クラス化したデータをさらにリストに格納している。
　やや複雑になってしまうが、リストには順序があるから、出題順を変更するときに便利である。

・正誤判定を行うための関数を定義する。シンプルにbool値を返すだけの関数である。

・出題時に問題文テキストと四択の選択肢を表示する関数を定義する。問題の配列を受け取り、必要に応じてリストになっているデータをランダムに並び替える。
　整列済みのリストに入っているクラスの問題部分のみを出力し、whileループで入力待ち処理を行う。
　正誤判定の関数を呼び出し、正誤判定を行う。
  判定結果と解説を出力する。以後、問題が尽きるまで繰り返す。

# 3.対象となるjsonデータの例
answerがユーザーのinputに入力した値と一致しているかどうかで正誤判定を行います。

```json:data.json
[
    {
      "question_text": "日本の首都はどこですか？",
      "choice1": "大阪",
      "choice2": "東京",
      "choice3": "京都",
      "choice4": "名古屋",
      "answer": 2,
      "explanation": "日本の首都は東京です。日本の政治・経済・文化の中心地でもあります。"
    },
    {
      "question_text": "富士山の高さはどれくらいですか？",
      "choice1": "3000メートル",
      "choice2": "3776メートル",
      "choice3": "5000メートル",
      "choice4": "2000メートル",
      "answer": 2,
      "explanation": "富士山の高さは3776メートルで、日本一の高さを誇ります。"
    },
    {
      "question_text": "日本の最長の川は何ですか？",
      "choice1": "信濃川",
      "choice2": "長良川",
      "choice3": "利根川",
      "choice4": "四万十川",
      "answer": 1,
      "explanation": "日本最長の川は信濃川で、長さは367キロメートルです。"
    },
    {
      "question_text": "日本で最も古い寺院はどこですか？",
      "choice1": "東大寺",
      "choice2": "法隆寺",
      "choice3": "清水寺",
      "choice4": "金閣寺",
      "answer": 2,
      "explanation": "法隆寺は日本最古の寺院で、世界遺産にも登録されています。"
    }
  ]
  ```


# 3.全体のコード

クラスを定義し、処理は関数し、後から呼び出しています。

デコレータ、アノテーション、リスト内包表記、例外処理、with句、enumerate文などが勉強した部分かな？と思います。

  ```python:main.py
import random
import json
from dataclasses import dataclass

@dataclass
class Question:
    # 問題文、選択肢、解答、解説を表すクラス
    question_text: str
    choice1: str
    choice2: str
    choice3: str
    choice4: str
    answer: int
    explanation: str

def read_json(file_path: str = "data.json") -> list:
    """
    JSONファイルを読み取り、Questionオブジェクトのリストを返す関数
    """
    with open(file_path, "r", encoding="utf-8") as file:
        data = json.load(file)
    
    questions_list = [
        Question(
            item["question_text"], 
            item["choice1"], 
            item["choice2"], 
            item["choice3"], 
            item["choice4"], 
            int(item["answer"]),  # 解答を整数型に変換
            item["explanation"]
        )
        for item in data # リスト内包による連続処理
    ]
    return questions_list

def evaluate_answer(user_answer: int, correct_answer: int) -> bool:
    """ 解答を判定する関数 """
    return user_answer == correct_answer

def present_questions(questions_list: list, order: str = "asc"):
    """
    問題を出題する関数
    :param questions_list: 出題する問題のリスト
    :param order: "asc"（昇順）または "random"（ランダム）で出題順を指定
    """
    if order == "random":
        random.shuffle(questions_list)
    elif order != "asc":
        raise ValueError("順序指定が無効です。「asc」または「random」を使用してください。")

    for i, question in enumerate(questions_list, 1):
        print(f"問題 {i}: {question.question_text}")
        print(f"1: {question.choice1}")
        print(f"2: {question.choice2}")
        print(f"3: {question.choice3}")
        print(f"4: {question.choice4}")
        
        # 入力待ち
        while True:
            try:
                user_answer = int(input("答えを入力してください（1～4の数字）: "))
                if user_answer < 1 or user_answer > 4:
                    raise ValueError
                break
            except ValueError:
                print("1～4の数字を入力してください。")
        
        # 判定
        if evaluate_answer(user_answer, question.answer):
            print("正解です！")
        else:
            print(f"不正解です... 正解は {question.answer} 番です。")
        print(f"解説: {question.explanation}\n")

# 使用例
questions = read_json("data.json")
present_questions(questions, order="asc")  # "asc" または "random" を指定可能
```

## まとめ

何かの足しになれば幸いです。