今回はかなり難しくてできなかったのでいい復習になった。
それぞれの機能をわけて1つのアプリとして作ります。
今回はDAY17でやったクイズアプリを、コンソールではなく
tkinterのGUIを使ってより遊びやすくするアプリを作成します。
また質問もTRIVIA APIから毎回取得して
毎回違う問題を出すようにします。
UIはこんな感じ。
APIからデータを取得するファイルを作成する
data.py
import requests
params = {
"amount": 10,
"category": 18,
"difficulty": "easy",
"type": "boolean"
}
response = requests.get("https://opentdb.com/api.php", params=params)
response.raise_for_status()
response = response.json()
quiz_list = response["results"]
今回のAPIはキーも要らず簡単なものなので、シンプルに。
requestsでapiのエンドポイントにアクセス。
raise_for_statusをすることでアクセスエラーが起きた時にどのエラーかを教えてくれるようにしています。
quiz_listで余計な部分は飛ばして整形しています。
クイズ内容を質問・答えのセットとしたオブジェクトとして管理する
整形したjsonをアプリで出力しやすくするためにオブジェクト化します。
クイズモデルとして1つのクラスを作成します。
quiz_model.py
class Question:
def __init__(self, q_text, q_answer):
self.text = q_text
self.answer = q_answer
これはjsonの中にあるクイズ内容と答えをセットにするテンプレみたいなものを作っています。
main.py
from data import quiz_list
from quiz_model import Question
quiz_bank = []
for quiz in quiz_list:
q_text = quiz["question"]
q_ans = quiz["correct_answer"]
new_quiz = Question(q_text, q_ans)
quiz_bank.append(new_quiz)
quiz = QuizMachine(quiz_bank)
起動ファイルでjsonデータとモデルデータをインポートして、
for文で1つずつ質問と答えをとりだし、new_quizでオブジェクト化をして
quiz_bankのリストに入れていきます。
最後のQuizMachineでは整形したデータを取得して
クイズゲームを担当する機能で、次に実装していきます。
クイズを扱う機能のファイルを作成する
quiz_machine.py
import html
class QuizMachine:
def __init__(self, quiz_bank):
self.question_number = 0
self.score = 0
self.quiz_bank = quiz_bank
self.current_question = None
def still_has_questions(self):
return self.question_number < len(self.quiz_bank)
def next_question(self):
self.current_question = self.quiz_bank[self.question_number]
self.question_number += 1
q_text = html.unescape(self.current_question.text)
return f"Q.{self.question_number}: {q_text}"
def check_answer(self, user_answer):
correct_answer = self.current_question.answer
if user_answer.lower() == correct_answer.lower():
self.score += 1
return True
else:
return False
QuizMachineというクラスを作ります。
整形したデータはmain.pyでさらに出題しやすいように抽出されたデータを作るのですが、
そのデータからクイズを出す機能を作ります。
主に
- データを取得する
- スコアの設定
- データがある分次々と出題する
- 正解かを判断する
です。
上から説明すると、init部分では初期状態として、スコアと取得したデータ、今何問目かを初期化します。selfの次にquiz_bankがある通り、このクラスを使う時は()にquiz_bankを入れることで機能します。
still_has_questionは取得したデータの数を数えて、その分だけ数を出力するもの。
出力されなかったらクイズ終了の表示をUIのファイルがする予定。
next_questionでは初期値のquestion_numberのリストの内容を取得してから1を足して
質問内容を出力しています。
これをすることでリストとしては0番目の内容が見た目は1番目として見せることができます。
htmlをインポートしているのは、質問の中には "+= 1" のように
ウェブサイト上で表記するための記号がある場合があります。
それをpython上でもきちんと表示されるように変換するためのファンクション = unescapeを使うためにインポートしています。
UIを作成する
design.py
from tkinter import *
from quiz_machine import QuizMachine
THEME_COLOR = "#efefef"
class QuizUI:
def __init__(self, quiz_brain: QuizMachine):
self.quiz = quiz_brain
self.window = Tk()
self.window.title("Quizzler")
self.window.config(padx=20, pady=20, bg=THEME_COLOR)
self.score_label = Label(text="Score: 0", fg="#333", bg=THEME_COLOR)
self.score_label.grid(row=0, column=0, columnspan=2)
self.canvas = Canvas(width=300, height=250, bg="white")
self.question_text = self.canvas.create_text(
150,
125,
width=280,
text="Some Question Text",
fill="#333",
font=("Arial", 20, "italic")
)
self.canvas.grid(row=1, column=0, columnspan=2, pady=50)
true_image = PhotoImage(file="images/maru.png")
self.true_button = Button(image=true_image, highlightthickness=0, command=self.true_pressed)
self.true_button.grid(row=2, column=0)
false_image = PhotoImage(file="images/batsu.png")
self.false_button = Button(image=false_image, highlightthickness=0, command=self.false_pressed)
self.false_button.grid(row=2, column=1)
self.get_next_question()
self.window.mainloop()
UIにQuizMachineをインポートすることでUIファイルでQuizMachineの機能が使えるようになります。
innitに(self, quiz_brain: QuizMachine)を入れて、
self.quiz = quiz_brainを入れることで継承ができます。
あとはtkinterの今まで使った機能と同じ感じ。
丸ボタンを押すとtrue_pressed、バツボタンでfalse_pressedという機能を発火します。
一通りのUIができたので、最後にクイズを取得する機能 = get_next_question()を入れて
innit部分は終了。
次に、UI上の変化として
を実装していきます。
design.py
def get_next_question(self):
self.canvas.config(bg="white")
if self.quiz.still_has_questions():
self.score_label.config(text=f"Score: {self.quiz.score}")
q_text = self.quiz.next_question()
self.canvas.itemconfig(self.question_text, text=q_text, fill="#333")
else:
self.score_label.config(text=f"Final Score: {self.quiz.score}")
self.canvas.itemconfig(self.question_text, text=f"Quiz is End!\nYour Score is {self.quiz.score}", fill="#333")
self.true_button.config(state="disabled")
self.false_button.config(state="disabled")
def true_pressed(self):
self.give_feedback(self.quiz.check_answer("True"))
def false_pressed(self):
is_right = self.quiz.check_answer("False")
self.give_feedback(is_right)
def give_feedback(self, is_right):
if is_right:
self.canvas.config(bg="blue")
self.canvas.itemconfig(self.question_text, fill="#fff")
else:
self.canvas.config(bg="red")
self.canvas.itemconfig(self.question_text, fill="#fff")
self.window.after(1000, self.get_next_question)
self.quiz.機能 は、QuizMachineに記述したdefの機能を読み込ませています。
一番最初に背景を白に設定しているのは答えを押した後に背景が青か赤になってしまっているのでそれをクリアするためです。
最初にstill_has_questionsを使うことでクイズがあるかどうかを判断しています。
そこからクイズがあればテキストをnext_question()を起動して
return として出力している Q1: クイズ のテキストをq_text の変数に格納し、
クイズ表示部分に表示をしています。
true_pressed, false_pressedはcheck_answer()を起動して
True,またはFalseを入れることで
correct_answer と同じかどうかをチェックしてあっていたらスコア+1でTrueを返す、
間違っていたらFalseを返します。
という機能にしています。
自分の答えが合っていると+1をしてTrueを返す quiz.check_answerですが、
Falseの時だけ is_rightという機能が付いています。
それは下のgive_feedbackの部分で正解だったら青背景、間違っていたら赤背景にするためです。
UI部分もできたのでmain.pyの最後にUIのクラスを追加して出来上がり。
ファイルを行き交う継承とか結構ややこしい。。。
Gitはこちら