このページでは Python で tkinter を利用して4択クイズアプリを開発する方法について解説していきたいと思います。
スクリプトにバグがありましたので修正しています(2020/12/11)
- 修正前:
self.choice_value = tkinter.BooleanVar()
- 修正後:
self.choice_value = tkinter.IntVar
()
Contents
開発するアプリ
まず今回開発していくアプリについて紹介していきたいと思います。
アプリ起動画面
アプリを起動すると下のような画面が表示されます。
上側に表示される文字列がクイズの「問題文」で、真ん中付近に表示される四つの文字列がその問題文に対する解答の「選択肢」になります。
4つの選択肢から1つのみを選択できるようにするように各選択肢にはラジオボタンを設けています。
下側に表示するボタンが解答の選択を確定したことをアプリに伝える「OK ボタン」になります。
スポンサーリンク
クイズのランダム表示
「問題文」と「選択肢」は事前に用意したクイズの中からランダムに選択して表示するようにしています。
解答結果の表示
さらに選択肢から解答選択後に「OK」ボタンを押された際には、選択した解答が正解か不正解かをメッセージボックスでユーザーに伝えます。
クイズの再表示
さらにユーザーが解答結果を確認してメッセージボックスのボタンを押すと再度クイズをランダムに選択して表示します。
スポンサーリンク
スポンサーリンク
アプリの終了
ただし既に表示したクイズは表示しないようにしています。
用意したクイズを全て出題し終わったら、下図のような画面を表示し、「OK ボタン」が押されたらアプリを終了します。
4択クイズアプリの作り方
では、ここまで紹介してきた4択クイズアプリをどのようにして開発するのか?ここについて続いて解説していきます。
クイズデータの事前準備
まずはクイズのデータの事前準備をしておきます。
今回作成するアプリではクイズの下記の情報を CSV ファイルから読み込むようにしたいと思います。
- 問題文
- 解答の選択肢0
- 解答の選択肢1
- 解答の選択肢2
- 解答の選択肢3
- 解答の選択肢番号
CSV ファイルの各行が1つのクイズを表し、コンマ区切りで左から順に下記の情報をファイルに記述しています。
問題文,解答の選択肢0,解答の選択肢1,解答の選択肢2,解答の選択肢3,解答の選択肢番号
具体的な CSV ファイルの中身は下記のようになります。
日本で一番狭い都道府県は?,大阪府,東京都,香川県,沖縄県,2 日本で一番広い県は?,長野県,神奈川県,岩手県,福島県,2 日本で一番多い苗字は?,田中,高橋,鈴木,佐藤,3 みかん生産量が一番多い都道府県は?,和歌山県,熊本県,愛媛県,静岡県,0
スポンサーリンク
クラス構成
今回作成するスクリプトでは、クラスとしては Quiz
クラスのみを用意したいと思います。
別にクラス化する必然性もないのですが、属性(メンバ)としてデータを保持しておけるため、メソッドの引数等も減らせて楽なのでクラス化しています。
ウィジェットのインスタンス以外で Quiz
クラスの属性としては下記の4つを使用しています。
master
:親ウィジェットquiz_list
:クイズデータをリスト化したデータnow_quiz
:現在表示中のクイズデータchoice_value
:制御変数オブジェクト(選択中の選択肢番号)
クイズデータの読み込み
ではアプリを作っていきたいと思います。
まずは先ほど準備したクイズの情報を CSV ファイルから読み込みます。
やることは下記の3つです。
- CSV ファイルを開く
- CSV データとしてファイルのデータ読み込み
- CSV データをリスト化
これは、例えば下記の getQuiz
メソッドにより実現することができます。
def getQuiz(self):
'''クイズの情報を取得する'''
# ファイルを開く
try:
f = open(CSV_FILE)
except FileNotFoundError:
return None
# CSVデータとしてファイル読み込み
csv_data = csv.reader(f)
# CSVの各行をリスト化
for quiz in csv_data:
self.quiz_list.append(quiz)
f.close()
CSV_FILE
には事前にクイズデータを格納したファイルパスを設定しています。
ポイントは csv.reader
のあたりです。これは Python3 の標準モジュールである csv
に用意された、CSV ファイルの読み込みを行う関数になります。
読み込んだデータは CSV データという抽象的なものでイテラブルなオブジェクトになります。
上記のように for
文を実行すると、読み込んだ CSV ファイルの行データを取得することができます。
この行データはコンマで区切られた文字列を要素とするリストになります。
例えば前述のクイズデータの例であれば、for
文の1回目のループにおける quiz
は下記のようなリストになります。
['日本で一番狭い都道府県は?','大阪府','東京都','香川県','沖縄県','2']
この quiz
をループの度に self.quiz_list
に追加していますので、ループが終わった段階の self.quiz_list
は読み込んだクイズデータが全て格納された2次元リストになります。
[
['日本で一番狭い都道府県は?','大阪府','東京都','香川県','沖縄県','2'],
['日本で一番広い県は?','長野県','神奈川県','岩手県','福島県','2'],
['日本で一番多い苗字は?','田中','高橋','鈴木','佐藤','3'],
['みかん生産量が一番多い都道府県は?','和歌山県','熊本県','愛媛県','静岡県','0']
]
なので、今後クイズデータを参照する際にはファイルを読み込む必要はなく、この self.quiz_list
からデータを取得してやれば良いことになります。
ウィジェットの作成と配置
続いてウィジェットの作成と配置をしていきます。
前述の通り、アプリの画面は下の図のようになります。
この画面を実現するために、アプリ全体としては下の図のようにウィジェットを用意し、配置しています。
アプリの大枠はフレーム(Frame
)とボタン(Button
)で構成し、この2つは pack
メソッドで配置しています。
アプリの大枠を構成するフレームとボタンを作成・配置するメソッドは、例として下記の createWidgets
メソッドで実現することができます。
def createWidgets(self):
'''ウィジェットを作成・配置する'''
# フレームを作成する
self.frame = tkinter.Frame(
self.master,
width=400,
height=200,
)
self.frame.pack()
# ボタンを作成する
self.button = tkinter.Button(
self.master,
text="OK",
command=self.checkAnswer
)
self.button.pack()
フレームの中のクイズの問題文と選択肢については次のクイズのランダム表示で説明します。
スポンサーリンク
クイズのランダム表示
次はクイズを表示していきます。まずは考え方から説明していきます。
まあこれもウィジェットを生成・配置するだけなのですが、クイズを “ランダムに” 表示するというところがポイントになります。
ランダムにクイズを表示するために Python3 標準モジュールの random
を利用します。
random.randrange(N)
を実行すれば、0
〜 N - 1
までの整数値をランダムに取得することができます。
ですので、クイズデータの事前準備で用意したクイズの数(つまり CSV ファイルの行数)を random.randrange
関数の引数に指定して実行すれば、ランダムに表示するクイズを選択することができます。
で、クイズデータの読み込みで self.quiz_list
で各クイズの情報(問題文・選択肢・解答番号)がリストとして格納されていますので、len(self.quiz_list)
が用意されているクイズの数(もっと厳密に言うと “まだ表示していないクイズの数”)になります。
したがって、この len
の結果を random.randrange
関数の引数に指定し乱数を取得し、self.quiz_list
からその乱数の要素を取得すれば、ランダムにクイズの情報を取得することができることになります。
# まだ表示していないクイズからクイズ情報をランダムに取得
num_quiz = random.randrange(len(self.quiz_list))
quiz = self.quiz_list[num_quiz]
あとはこの取得したクイズの情報を表示してやれば良いだけですね!
この辺りを考慮してクイズをランダムに表示するメソッド例が下記の showQuiz
になります。
def showQuiz(self):
'''問題と選択肢を表示'''
# まだ表示していないクイズからクイズ情報をランダムに取得
num_quiz = random.randrange(len(self.quiz_list))
quiz = self.quiz_list[num_quiz]
# 問題を表示するラベルを作成
self.problem = tkinter.Label(
self.frame,
text=quiz[0]
)
self.problem.grid(
column=0,
row=0,
columnspan=4,
pady=10
)
# 選択肢を表示するラジオボタンを4つ作成
self.choices = []
for i in range(4):
# ラジオボタンウィジェットを作成・配置
choice = tkinter.Radiobutton(
self.frame,
text=quiz[i+1],
variable=self.choice_value,
value=i
)
choice.grid(
row=1,
column=i,
padx=10,
pady=10,
)
# ウィジェットを覚えておく
self.choices.append(choice)
# 表示したクイズは再度表示しないようにリストから削除
self.quiz_list.remove(quiz)
# 現在表示中のクイズを覚えておく
self.now_quiz = quiz
この showQuiz
におけるポイントは「クイズの情報の表示」と「ラジオボタン」です。この2つについて解説していきます。
クイズの情報の表示
問題文はラベルウィジェットで、選択肢はラジオボタンウィジェットで表示しています。ラジオボタンは選択肢の数分(つまり4つ)作成する必要があるので for
文のループの中でウィジェット作成と配置を行っています。
各ウィジェット作成する際の text
引数(tkinter.Label
・tkinter.Radiobutton
の text
引数)にランダムに取得したクイズ情報のリスト quiz
の要素を指定していますので、ランダムにクイズ情報を表示することができるようになっています。
quiz
の各要素には下記がそれぞれ格納されています。
quiz[0]
:問題文quiz[1]
:解答の選択肢0quiz[2]
:解答の選択肢1quiz[3]
:解答の選択肢2quiz[4]
:解答の選択肢3quiz[5]
:解答の選択肢番号
また、一度出題したクイズが再度出題されないように下記でクイズのリスト self.quiz_list
から出題したクイズの要素を削除するようにしています。
# 表示したクイズは再度表示しないようにリストから削除
self.quiz_list.remove(quiz)
次にクイズを表示するときには self.quiz_list
には出題済みのクイズが削除されていますので、ランダムにクイズを選んでも再び同じクイズが選ばれることはありません。
ラジオボタン
ラジオボタンは今回のクイズのように「同じグループの複数のボタンの中から1つだけ選択させる」場合に便利なウィジェットになります。
同じグループに所属するラジオボタンは1つのみしか選択できないようになっています。
この1つしか選択できないようにする制御は、どのラジオボタンがどのグループに所属しているかを設定しておけば tkinter 本体が勝手にやってくれます。
後はユーザーが選択したボタンを判別することができれば、ユーザーが選択したボタンに応じて実行する処理を切り替えるようなことができます。
今回の場合は、ユーザーが選択した選択肢のボタンボタンに応じて解答結果表示する(正解 or 不正解)ような制御を行なっています。
このラジオボタンを作る時のポイントは下記の2つになると思います。
- グループの設定
- 選択されているボタンの判別
これらは両方ともラジオボタン作成時に実行する tkinter.Radiobutton()
実行時の「引数指定」と「制御変数オブジェクト」により実現することができます。
制御変数オブジェクトとは下記のクラスのインスタンスになります
tkinter.IntVar
tkinter.StringVar
tkinter.DoubleVar
tkinter.BooleanVar
このインスタンス生成は Quiz
クラスのコンストラクタで行い、属性 self.choice_value
で保持するようにしています
まず「グループの設定」ですが、これは tkinter.Radiobutton
の引数 variable
で設定することができます。この variable
には制御変数オブジェクトを指定します。
variable
引数に同じ制御変数オブジェクトを指定すれば、作成されるラジオボタンが同じグループに所属することになります。
逆に別のグループに所属させたい場合は別の制御変数オブジェクトを指定すれば良いです。
「選択されているボタンの判別」に関しても tkinter.Radiobutton()
の引数と制御変数オブジェクトが関わってきます。
制御変数オブジェクトは get
メソッドを持っており、この get
メソッドを実行すると値を取得することができます。
この時取得される値は、選択中のラジオボタンを作成するときに tkinter.Radiobutton()
の引数 value
で指定した値になります。
ですので、各ボタンを作成するときに tkinter.Radiobutton()
の引数 value
にそれぞれ異なる値を設定し、後から制御変数オブジェクトで get
で値を取得すれば、どのボタンが選択中であるかを判断することができます。
今回は value
には、それぞれのラジオボタンに対応する選択肢番号を指定するようにしています。
この辺りを実装しているのが上記 getQuiz
メソッドの tkinter.Radiobutton()
実行部分になります。
解答結果の表示
続いて、ラジオボタンで解答選択後に「OK」が押されたときの処理について説明していきます。
クイズアプリですので、解答選択後に「OK」ボタンが押された時には、選択されている解答が正解であるか不正解であるかのメッセージを表示するようにします。
まず行うのが選択された解答が正解しているかどうかの確認です。
クイズのランダム表示で解説したように、どのラジオボタンが選択中であるかは tkinter.Radiobutton()
の引数 variable
で指定した制御変数オブジェクトの get
メソッドから取得することができます。
また正解の解答番号はクイズのランダム表示で解説したようにクイズ情報のリストの第5要素に格納されています。現在表示中のクイズ情報のリストは self.now_quiz
で保持していますので、self.now_quiz[5]
が正解の解答番号となります。
なので、この2つを比較し、一致する場合は正解のメッセージを、一致しない場合は不正解のメッセージを表示してやれば良いことになります。
このメッセージは tkinter の messagebox
モジュールを利用して表示しています。
messagebox.showinfo
で正解メッセージを、messagebox.showerror
で不正解メッセージを表示しています。
クイズの再表示
さらにメッセージ確認のボタンが押された際には次のクイズを表示するようにします。
これは、一旦現在のクイズを非表示にし、次のクイズを表示してやることで実現できます。
クイズの非表示は、クイズの問題文を表示しているウィジェットを削除することで実現できます。
このクイズの非表示を行うメソッドの例は下記の deleteQuiz
になります。
def deleteQuiz(self):
'''問題と選択肢を削除'''
# 問題を表示するラベルを削除
self.problem.destroy()
# 選択肢を表示するラジオボタンを削除
for choice in self.choices:
choice.destroy()
さらに次のクイズを表示するにはクイズのランダム表示で紹介した showQuiz
メソッドを実行するだけで良いです。
解答結果の表示及びクイズの再表示を行うメソッドの例は下記の checkAnswer
になります。
def checkAnswer(self):
'''解答が正解かどうかを表示し、次のクイズを表示する'''
# 正解かどうかを確認してメッセージを表示
if self.choice_value.get() == int(self.now_quiz[5]):
messagebox.showinfo("結果", "正解です!!")
else:
messagebox.showerror("結果", "不正解です...")
# 表示中のクイズを非表示にする
self.deleteQuiz()
if self.quiz_list:
# まだクイズがある場合は次のクイズを表示する
self.showQuiz()
else:
# もうクイズがない場合はアプリを終了する
self.endAppli()
この checkAnswer
が「OK」ボタンを押した時に実行されるようにするために、「OK」ボタンを作成する時に引数 command
で checkAnswer
を指定しています(具体的なスクリプトはウィジェットの作成と配置で紹介した createWidgets
メソッドの tkinter.Button()
実行部分をご参照ください)。
スポンサーリンク
スポンサーリンク
アプリの終了
ただ、次のクイズを表示しようと思っても既に全て出題済みで、もう表示可能なクイズが存在しない場合があります。
その時は「クイズがもう無い」ことを画面に表示し、表示後に「OK」ボタンが押されたらアプリを終了させるようにしています。
これを行うために、クイズの再表示で紹介した checkAnswer
メソッドから下記のendAppli
メソッドを実行するようにしています。
def endAppli(self):
'''アプリを終了する'''
# クイズがもうないことを表示
self.problem = tkinter.Label(
self.frame,
text="クイズは全て出題ずみです"
)
self.problem.grid(
column=0,
row=0,
padx=10,
pady=10
)
# OKボタンのcommandを変更
self.button.config(
command=self.master.destroy
)
ポイントは下記部分で「OK」ボタンウィジェットのインスタンスである self.button
に config
メソッドを実行させているところです。
# OKボタンのcommandを変更
self.button.config(
command=self.master.destroy
)
config
は、実行したウィジェットの設定を後から変更するメソッドになります。
上記では、ボタンが押されたときに実行する関数・メソッドを指定する command
を アプリのメインウィンドウである self.master
の destroy
メソッドに変更しています。
したがって、この処理実行後に「OK」ボタンが押された時には解答結果を表示する処理が実行されるのではなく、アプリ終了処理が実行されるようになります。
4択クイズアプリのスクリプト
では最後にスクリプト全体を紹介していきたいと思います。
スクリプト
ここまで紹介してきた作り方に従って作成した4択クイズアプリのスクリプトは下記になります。
import tkinter
from tkinter import messagebox
import random
import csv
# クイズの情報を格納したファイル
CSV_FILE = "quiz.csv"
class Quiz():
def __init__(self, master):
'''コンストラクタ
master:クイズ画面を配置するウィジェット
'''
# 親ウィジェット
self.master = master
# クイズデータリスト
self.quiz_list = []
# 現在表示中のクイズ
self.now_quiz = None
# 現在選択中の選択肢番号
self.choice_value = tkinter.IntVar()
self.getQuiz()
self.createWidgets()
self.showQuiz()
def getQuiz(self):
'''クイズの情報を取得する'''
# ファイルを開く
try:
f = open(CSV_FILE)
except FileNotFoundError:
return None
# CSVデータとしてファイル読み込み
csv_data = csv.reader(f)
# CSVの各行をリスト化
for quiz in csv_data:
self.quiz_list.append(quiz)
f.close()
def createWidgets(self):
'''ウィジェットを作成・配置する'''
# フレームを作成する
self.frame = tkinter.Frame(
self.master,
width=400,
height=200,
)
self.frame.pack()
# ボタンを作成する
self.button = tkinter.Button(
self.master,
text="OK",
command=self.checkAnswer
)
self.button.pack()
def showQuiz(self):
'''問題と選択肢を表示'''
# まだ表示していないクイズからクイズ情報をランダムに取得
num_quiz = random.randrange(len(self.quiz_list))
quiz = self.quiz_list[num_quiz]
# 問題を表示するラベルを作成
self.problem = tkinter.Label(
self.frame,
text=quiz[0]
)
self.problem.grid(
column=0,
row=0,
columnspan=4,
pady=10
)
# 選択肢を表示するラジオボタンを4つ作成
self.choices = []
for i in range(4):
# ラジオボタンウィジェットを作成・配置
choice = tkinter.Radiobutton(
self.frame,
text=quiz[i+1],
variable=self.choice_value,
value=i
)
choice.grid(
row=1,
column=i,
padx=10,
pady=10,
)
# ウィジェットを覚えておく
self.choices.append(choice)
# 表示したクイズは再度表示しないようにリストから削除
self.quiz_list.remove(quiz)
# 現在表示中のクイズを覚えておく
self.now_quiz = quiz
def deleteQuiz(self):
'''問題と選択肢を削除'''
# 問題を表示するラベルを削除
self.problem.destroy()
# 選択肢を表示するラジオボタンを削除
for choice in self.choices:
choice.destroy()
def checkAnswer(self):
'''解答が正解かどうかを表示し、次のクイズを表示する'''
# 正解かどうかを確認してメッセージを表示
if self.choice_value.get() == int(self.now_quiz[5]):
messagebox.showinfo("結果", "正解です!!")
else:
messagebox.showerror("結果", "不正解です...")
# 表示中のクイズを非表示にする
self.deleteQuiz()
if self.quiz_list:
# まだクイズがある場合は次のクイズを表示する
self.showQuiz()
else:
# もうクイズがない場合はアプリを終了する
self.endAppli()
def endAppli(self):
'''アプリを終了する'''
# クイズがもうないことを表示
self.problem = tkinter.Label(
self.frame,
text="クイズは全て出題ずみです"
)
self.problem.grid(
column=0,
row=0,
padx=10,
pady=10
)
# OKボタンのcommandを変更
self.button.config(
command=self.master.destroy
)
app = tkinter.Tk()
quiz = Quiz(app)
app.mainloop()
スポンサーリンク
スクリプトの設定
スクリプト先頭付近の下記で読み込むクイズデータのファイルパスを設定することができます。
# クイズの情報を格納したファイル
CSV_FILE = "quiz.csv"
初期設定では、スクリプトを実行するフォルダと同じフォルダの quiz.csv
ファイルを読み込むようになっています。
ご自身のスクリプト環境に合わせてファイルパスの設定やクイズデータの準備を行ってください。
クイズデータはクイズデータの事前準備で紹介した形式の CSV ファイルとして作成してください。クイズデータの事前準備に紹介しているクイズデータをコピペしてファイルを作ればスクリプトは実行可能なはずです。下記にも再掲しておきます。
日本で一番狭い都道府県は?,大阪府,東京都,香川県,沖縄県,2 日本で一番広い県は?,長野県,神奈川県,岩手県,福島県,2 日本で一番多い苗字は?,田中,高橋,鈴木,佐藤,3 みかん生産量が一番多い都道府県は?,和歌山県,熊本県,愛媛県,静岡県,0
スクリプトの実行
スクリプト実行時に引数は不要です。単純に python から上記スクリプトをコピペして作成した .py ファイルを実行すれば良いです。
スクリプトを実行すると下の画面が表示され、クイズをプレイすることができます。
まとめ
このページでは、python で tkinter を用いた4択クイズアプリの作り方について解説しました。
4択クイズアプリと聞くとなんだか簡単そうに感じるかもしれませんが、下記のような要素を含んでおり、実際にプログラミングしてみると学べることも多いと思います。
- ランダムにクイズを選択する処理
- ラジオボタンを利用したユーザーからの選択受付
といっても、楽しみながら学べるのはゲーム開発の醍醐味です。
おそらく実際にこのアプリを動かしてみると気に入らない点もあると思います。
是非その点を工夫して改善してみてください!ちょっとした工夫を繰り返すことでプログラミングの力を大きく伸ばすことができますよー!
オススメ参考書(PR)
簡単なアプリやゲームを作りながら Python について学びたいという方には、下記の Pythonでつくる ゲーム開発 入門講座 がオススメです!ちなみに私が Python を始めるときに最初に買った書籍です!
下記ようなゲームを作成しながら Python の基本が楽しく学べます!素材もダウンロードして利用できるため、作成したゲームの見た目にも満足できると思います。
- すごろく
- おみくじ
- 迷路ゲーム
- 落ち物パズル
- RPG
また本書籍は下記のような構成になっているため、Python 初心者でも内容を理解しやすいです。
- プログラミング・Python の基礎から解説
- 絵を用いた解説が豊富
- ライブラリの使い方から解説(tkitner と Pygame)
- ソースコードの1行1行に注釈
ゲーム開発は楽しくプログラミングを学べるだけでなく、ゲームで学んだことは他の分野のプログラミングにも活かせるものが多いですし(キーボードの入力受付のイベントや定期的な処理・画像や座標を扱い方等)、逆に他の分野のプログラミングで学んだ知識を活かしやすいことも特徴だと思います(例えばコンピュータの動作に機械学習を取り入れるなど)。
プログラミングを学ぶのにゲーム開発は相性抜群だと思います。
Python の基礎や tkinter・Pygame の使い方をご存知なのであれば、下記の 実践編 をいきなり読むのもアリです。
実践編 では「シューティングゲーム」や「アクションゲーム」「3D カーレース」等のより難易度の高いゲームを作りながらプログラミングの力をつけていくことができます!
また、単にゲームを作るのではなく、対戦相手となるコンピュータの動作のアルゴリズムにも興味のある方は下記の「Pythonで作って学べるゲームのアルゴリズム入門」がオススメです。
この本はゲームのコンピュータ(AI)の動作アルゴリズム(思考ルーチン)に対する入門解説本になります。例えばオセロゲームにおけるコンピュータが、どのような思考によって石を置く場所を決めているか等の基本的な知識を得ることが出来ます。
プログラミングを挫折せずに続けていくためには楽しさを味わいながら学習することが大事ですので、特にゲームに興味のある方は、この辺りの参考書と一緒に Python を学んでいくのがオススメです!