Pythonのtkinterでタイピングゲームを作成してみた
Pythonの基礎構文(繰り返し・条件分岐)やクラス、関数を使用して内部処理を実装し、UI部分はtkinterでデスクトップで動くタイピングゲームを作成したので、コードの解説をしていきたいと思います。
完成イメージ
ゲームの流れとしては
- ユーザーが「ゲーム開始」ボタンをクリック
- タイマーが開始され、問題が表示される
- ユーザーが入力し、正解すると次の問題に進む
- 制限時間が終了すると、ゲームオーバーとなりスコアが表示される
ファイル構成
main.py
typeBrain.py
ui.py
data.py
score.txt
- main.py: これはプログラムのメインファイルです。ゲームの実行やその他のモジュールの呼び出しを行う中心的な役割を果たします。
- typeBrain.py: このファイルはゲームのロジック部分を担当しています。タイピングの正誤判定、スコア計算、時間管理などの機能を実装しています。
- ui.py: ユーザーインターフェース(UI)に関する処理を行うファイルです。
- data.py: タイピングゲームの問題文が含まれています。
- score.txt: プレイヤーのスコアを保存するためのテキストファイルです。
メイン処理(main.py)
main.pyの役割としては、必要なモジュールを集約し、オブジェクトを初期化するものとなります。
from data import typing_data
from typeBrain import TypeBrain
from ui import Ui
typing=TypeBrain(typing_data)
ui=Ui(typing)
typeBrainの引数には、問題文typing_data(data.py)を渡しており、uiの引数には、typingを渡しており、TypeBrainで作成したオブジェクトを参照できるようにしています。
内部処理(typeBrain.py)
このTypeBrainクラスは、タイピングゲームのコアロジックを実装しています。主な機能は以下の通りです
class TypeBrain:
def __init__(self,typing_data):
self.question_data=typing_data
self.score=0
self.question_num=0
self.time=60
self.current_question=None
def next_question(self):
self.current_question=self.question_data[self.question_num]
self.question_num+=1
return self.current_question #次の質問を返す
def check_answer(self,entry):
if self.current_question["question"]==entry:
self.score+=1
return True
else:
return False
def update_score(self,high_score):
if self.score>high_score:
with open("Day82~Portfolio/Day85_typing_game/score.txt",mode="w")as file:
file.write(str(self.score))
- 初期化 (__init__):問題データ(引数)、スコア、問題番号、制限時間、現在の問題を初期化、制限時間は60秒に設定しています。
- 次の問題を取得 (next_question):問題リストから次の問題を取得し、問題番号を1つ進めます。現在の問題を返します。
- 回答をチェック (check_answer):ユーザーの入力と現在の問題を比較します。正解の場合、スコアを1増やしてTrueを返します。不正解の場合、Falseを返します。
- スコアの更新 (update_score):現在のスコアがハイスコアを超えた場合、新しいハイスコアをファイルに書き込みます。
ユーザーが入力するたびにcheck_answerメソッドが呼び出され、正誤判定とスコア計算が行われます。次の問題に進む際にnext_questionメソッドが呼び出されます。ゲーム終了時にupdate_scoreメソッドが呼び出され、必要に応じてハイスコアが更新されます。
UI実装(ui.py)
このコードは、タイピングゲームのユーザーインターフェース(UI)を実装しています。Tkinterを使用してGUIを作成しています。引数で渡ってきたTypeBrainのオブジェクトを参照することができます。
import tkinter as tk
from tkinter import messagebox
from tkinter import font as tkfont
class Ui:
def __init__(self, typing):
self.typing = typing
self.window = tk.Tk()
self.window.title("タイピングゲーム")
self.window.config(padx=20, pady=20, bg="#F0F0F0")
self.window.geometry("600x400") # ウィンドウサイズを設定
# フォントの設定
self.title_font = tkfont.Font(family="Helvetica", size=16, weight="bold")
self.normal_font = tkfont.Font(family="Helvetica", size=12)
self.large_font = tkfont.Font(family="Helvetica", size=14)
# フレームの作成
self.header_frame = tk.Frame(self.window, bg="#F0F0F0")
self.header_frame.pack(fill=tk.X, pady=(0, 20))
self.content_frame = tk.Frame(self.window, bg="#F0F0F0")
self.content_frame.pack(expand=True, fill=tk.BOTH)
# ヘッダー部分(スコア、タイマー)
self.time_label = tk.Label(self.header_frame, text="60", font=self.title_font, bg="#F0F0F0", fg="#333333")
self.time_label.pack(side=tk.LEFT, padx=(0, 20))
self.score_label = tk.Label(self.header_frame, text=f"スコア: {self.typing.score}", font=self.normal_font, bg="#F0F0F0", fg="#333333")
self.score_label.pack(side=tk.LEFT, padx=(0, 20))
self.high_score = self.load_high_score()
self.high_score_label = tk.Label(self.header_frame, text=f"ハイスコア: {self.high_score}", font=self.normal_font, bg="#F0F0F0", fg="#333333")
self.high_score_label.pack(side=tk.LEFT)
# 問題表示部分
self.question_jpn_label = tk.Label(self.content_frame, text="", font=self.large_font, bg="#F0F0F0", fg="#333333")
self.question_jpn_label.pack(pady=(0, 10))
self.question_romaji_label = tk.Label(self.content_frame, text="", font=self.normal_font, bg="#F0F0F0", fg="#666666")
self.question_romaji_label.pack(pady=(0, 20))
# 入力欄
self.type_entry = tk.Entry(self.content_frame, width=40, font=self.normal_font)
self.type_entry.pack(pady=(0, 20))
self.type_entry.bind("<KeyRelease>", self.keypress)
# スタートボタン
self.start_button = tk.Button(self.content_frame, text="ゲーム開始", command=self.start_game, font=self.normal_font, bg="#4CAF50", fg="white", activebackground="#45a049")
self.start_button.pack()
self.next_question_view()
self.window.mainloop()
def load_high_score(self):
try:
with open("Day82~Portfolio/Day85_typing_game/score.txt", mode="r") as file:
return int(file.read().strip() or 0)
except (FileNotFoundError, ValueError):
return 0
def start_game(self):
self.start_button.pack_forget() # スタートボタンを隠す
self.type_entry.focus_set()
self.count_down()
# ここでゲーム開始のロジックを実装
def next_question_view(self):
self.current_quesiton=self.typing.next_question()
self.question_jpn_label.config(text=self.current_quesiton["question"])
def keypress(self,e):
entry=self.type_entry.get()
self.feedback(self.typing.check_answer(entry))
def feedback(self,is_result):
if is_result: #回答と入力が一致した場合は次の問題へ進む
self.score_label.config(text=f"スコア:{self.typing.score}")
self.next_question_view()
self.type_entry.delete(0,tk.END)
def count_down(self):
if self.typing.time<0:
self.typing.update_score(self.high_score)
messagebox.showinfo("メッセージ", f"ゲーム終了です。スコアは {self.typing.score} です。")
self.window.destroy()
return
self.time_label.config(text=self.typing.time)
self.timer=self.window.after(1000,self.count_down)
self.typing.time-=1
- ウィンドウの設定(__init__):self.window=tk.TK()でTkinterのオブジェクトを作成してタイトル、サイズ、背景色などを設定しています。
- UI要素の作成(__init__):
- ヘッダー部分(__init__):時間、スコア、ハイスコアを表示。スコアは、typingオブジェクト内にある変数の値を取得しており、ハイスコアはscore.txtから取得して表示しています。
- コンテンツ部分(__init__):問題、入力欄、スタートボタンを配置。入力欄はself.type_entry.bind(“<KeyRelease>”, self.keypress)で、キーボード入力があった都度、判定処理が実行されるようにしています。スタートボタンがクリックされると command=self.start_gameの処理でゲーム開始の処理が実行されます。
- start_game():ゲーム開始時の処理。スタートボタンを非表示として、入力欄にフォーカスを当てます。その後、self.count_down()を実行します。
- next_question_view():次の問題を表示
- keypress():キー入力時の処理。キーボード入力の都度、入力欄の値を取得し、feedbackメソッドを呼び出します。引数には、check_answer(entry)の正誤の戻り値を渡します。
- feedback():回答のフィードバック処理。正解していた場合は、スコア、問題番号、入力欄の更新を行います。
- count_down():タイマー機能。self.window.after(1000,self.count_down)で1秒ごとにカウントダウンを実行します。タイマーが0秒以下になると、スコア更新の実行を行いウィンドウを終了します。
- load_high_score():ファイルからハイスコアを読み込み。テキストファイルからスコアを読み込み、int型に変換しています。
データ保存(data.py、score.txt)
このdata.pyファイルは、タイピングゲームで使用される問題データを定義しています。
typing_dataという名前のリストが定義されています。リストの各要素は辞書形式で、”question”キーに対応する値として問題文が格納されています。
typing_data = [
{
"question": "The quick brown fox jumps over the lazy dog.",
},
{
"question": "To be or not to be, that is the question.",
},
{
"question": "All that glitters is not gold.",
}
]
score.txtは、空のファイルを用意しておく必要があります。