【Python】tkinterで4択クイズアプリを開発

4択クイズアプリ開発方法の解説ページアイキャッチ

このページでは Python で tkinter を利用して4択クイズアプリを開発する方法について解説していきたいと思います。

更新履歴

スクリプトにバグがありましたので修正しています(2020/12/11)

  • 修正前:self.choice_value = tkinter.BooleanVar()
  • 修正後:self.choice_value = tkinter.IntVar()

開発するアプリ

まず今回開発していくアプリについて紹介していきたいと思います。

アプリ起動画面

アプリを起動すると下のような画面が表示されます。

4択クイズアプリの起動画面

上側に表示される文字列がクイズの「問題文」で、真ん中付近に表示される四つの文字列がその問題文に対する解答の「選択肢」になります。

問題文と選択肢の配置

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 は下記のようなリストになります。

quizの中身
['日本で一番狭い都道府県は?','大阪府','東京都','香川県','沖縄県','2']

この quiz をループの度に self.quiz_list に追加していますので、ループが終わった段階の self.quiz_list は読み込んだクイズデータが全て格納された2次元リストになります。

self.quiz_listの中身
[
['日本で一番狭い都道府県は?','大阪府','東京都','香川県','沖縄県','2'],
['日本で一番広い県は?','長野県','神奈川県','岩手県','福島県','2'],
['日本で一番多い苗字は?','田中','高橋','鈴木','佐藤','3'],
['みかん生産量が一番多い都道府県は?','和歌山県','熊本県','愛媛県','静岡県','0']
]

なので、今後クイズデータを参照する際にはファイルを読み込む必要はなく、この self.quiz_list からデータを取得してやれば良いことになります。

ウィジェットの作成と配置

続いてウィジェットの作成と配置をしていきます。

前述の通り、アプリの画面は下の図のようになります。

4択クイズアプリの起動画面

この画面を実現するために、アプリ全体としては下の図のようにウィジェットを用意し、配置しています。

ウィジェット構成

アプリの大枠はフレーム(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) を実行すれば、0N - 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.Labeltkinter.Radiobuttontext 引数)にランダムに取得したクイズ情報のリスト quiz の要素を指定していますので、ランダムにクイズ情報を表示することができるようになっています。

quiz の各要素には下記がそれぞれ格納されています。

  • quiz[0]:問題文
  • quiz[1]:解答の選択肢0
  • quiz[2]:解答の選択肢1
  • quiz[3]:解答の選択肢2
  • quiz[4]:解答の選択肢3
  • quiz[5]:解答の選択肢番号

また、一度出題したクイズが再度出題されないように下記でクイズのリスト self.quiz_list から出題したクイズの要素を削除するようにしています。

クイズの削除
# 表示したクイズは再度表示しないようにリストから削除
self.quiz_list.remove(quiz)

次にクイズを表示するときには self.quiz_list には出題済みのクイズが削除されていますので、ランダムにクイズを選んでも再び同じクイズが選ばれることはありません。

ラジオボタン

ラジオボタンは今回のクイズのように「同じグループの複数のボタンの中から1つだけ選択させる」場合に便利なウィジェットになります。

同じグループに所属するラジオボタンは1つのみしか選択できないようになっています。

ラジオボタンの説明図

この1つしか選択できないようにする制御は、どのラジオボタンがどのグループに所属しているかを設定しておけば tkinter 本体が勝手にやってくれます。

後はユーザーが選択したボタンを判別することができれば、ユーザーが選択したボタンに応じて実行する処理を切り替えるようなことができます。

今回の場合は、ユーザーが選択した選択肢のボタンボタンに応じて解答結果表示する(正解 or 不正解)ような制御を行なっています。

このラジオボタンを作る時のポイントは下記の2つになると思います。

  • グループの設定
  • 選択されているボタンの判別

これらは両方ともラジオボタン作成時に実行する tkinter.Radiobutton() 実行時の「引数指定」と「制御変数オブジェクト」により実現することができます。

MEMO

制御変数オブジェクトとは下記のクラスのインスタンスになります

  • 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」ボタンを作成する時に引数 commandcheckAnswer を指定しています(具体的なスクリプトはウィジェットの作成と配置で紹介した 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.buttonconfig メソッドを実行させているところです。

ボタンの設定変更
# OKボタンのcommandを変更
self.button.config(
	command=self.master.destroy
)

config は、実行したウィジェットの設定を後から変更するメソッドになります。

上記では、ボタンが押されたときに実行する関数・メソッドを指定する command を アプリのメインウィンドウである self.masterdestroy メソッドに変更しています。

したがって、この処理実行後に「OK」ボタンが押された時には解答結果を表示する処理が実行されるのではなく、アプリ終了処理が実行されるようになります。

4択クイズアプリのスクリプト

では最後にスクリプト全体を紹介していきたいと思います。

スクリプト

ここまで紹介してきた作り方に従って作成した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 ファイルを実行すれば良いです。

スクリプトを実行すると下の画面が表示され、クイズをプレイすることができます。

4択クイズアプリの起動画面

まとめ

このページでは、python で tkinter を用いた4択クイズアプリの作り方について解説しました。

4択クイズアプリと聞くとなんだか簡単そうに感じるかもしれませんが、下記のような要素を含んでおり、実際にプログラミングしてみると学べることも多いと思います。

  • ランダムにクイズを選択する処理
  • ラジオボタンを利用したユーザーからの選択受付

といっても、楽しみながら学べるのはゲーム開発の醍醐味です。

おそらく実際にこのアプリを動かしてみると気に入らない点もあると思います。

是非その点を工夫して改善してみてください!ちょっとした工夫を繰り返すことでプログラミングの力を大きく伸ばすことができますよー!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です