九九を覚えるためにアプリを作ってみた

# 九九を覚えるためにアプリを作ってみた
皆さん，九九覚えていますか?小学校二年生の時に覚えた人がほとんどだと思います．最近私の大学の友達がインド式計算にハマっていてそれにつられて私も覚え始めました．
空いた時間などに問題を出し合うのですが，答えが正しいかどうかを判定するために電卓を使っていましたが，めんどくせぇ
ということでPythonのTkinterをつかって寿司打のようなアプリを簡単に作成しました．

## コードの作成
まずは仕様を以下のように定めました．
* 制限時間は180秒
* 数字は10~99まで
* 最高スコアをテキストファイルに保存しておく．
* 5秒以内に応えられたらボーナスタイムを追加
* シーケンスバーを緑から赤色に変化させていく

という感じです．シーケンスバーについてはChatGPTに作成してもらいました．

以下がコードの全文です．

```python
import random
import time
import tkinter as tk
from tkinter import ttk

min_value = 10
max_value = 99
INITIAL_TIME_LIMIT = 180  # 3 minutes in seconds
BONUS_TIME = 5  # 5 seconds bonus

class KakezanGame:
    def __init__(self, root):
        self.root = root
        self.root.title("Kakezan Game")

        self.high_consecutive, self.high_total = self.get_highscore()

        self.setup_game()

    def get_highscore(self):
        try:
            with open('scoredata.txt', 'r') as f:
                high_consecutive, high_total = map(int, f.read().split(','))
                return high_consecutive, high_total
        except:
            return 0, 0

    def setup_game(self):
        self.time_left = INITIAL_TIME_LIMIT
        self.total_correct = 0
        self.consecutive_correct = 0
        self.incorrect_count = 0
        self.game_active = True

        # Create GUI elements
        self.highscore_label = ttk.Label(self.root, text=f"最高連続正解数: {self.high_consecutive}\n最高正解数: {self.high_total}")
        self.highscore_label.pack(pady=10)

        self.question_label = ttk.Label(self.root, text="")
        self.question_label.pack(pady=20)

        self.answer_entry = ttk.Entry(self.root)
        self.answer_entry.pack(pady=20)
        self.answer_entry.bind('<Return>', self.check_answer)

        self.canvas = tk.Canvas(self.root, width=300, height=20)
        self.canvas.pack(pady=20)
        self.progress_bar = self.canvas.create_rectangle(0, 0, 300, 20, fill='green')

        self.info_label = ttk.Label(self.root, text="")
        self.info_label.pack(pady=20)

        self.score_label = ttk.Label(self.root, text=f"連続正解数: {self.consecutive_correct}\n合計正解数: {self.total_correct}\n不正解数: {self.incorrect_count}")
        self.score_label.pack(pady=20)

        self.retry_button = ttk.Button(self.root, text="再挑戦", command=self.restart_game)
        # Note: The button is created but not packed initially. It will be shown only after the game is over.

        # Start game
        self.new_question()

    def new_question(self):
        self.num1 = random.randint(min_value, max_value)
        self.num2 = random.randint(min_value, max_value)
        self.question_label.config(text=f"{self.num1} と {self.num2} の積は？")
        self.answer_entry.delete(0, tk.END)
        self.answer_entry.focus_set()
        self.start_time = time.time()
        self.update_time_left()

    def check_answer(self, event):
        if not self.game_active:
            return

        try:
            answer = int(self.answer_entry.get())
            elapsed_time = time.time() - self.start_time
            if self.num1 * self.num2 == answer:
                self.total_correct += 1
                self.consecutive_correct += 1
                if elapsed_time <= 5:
                    self.time_left += BONUS_TIME
                    self.info_label.config(text="ボーナスタイム +5秒！")
                else:
                    self.info_label.config(text="")
                self.update_score()
                self.new_question()
            else:
                self.incorrect_count += 1
                self.info_label.config(text=f"不正解です。正答は {self.num1 * self.num2} でした。")
                self.update_score()
                self.new_question()
        except ValueError:
            self.info_label.config(text="数値を入力してください。")

    def update_time_left(self):
        if self.time_left > 0 and self.game_active:
            self.time_left -= 1

            # Change color of the progress bar
            fraction = self.time_left / INITIAL_TIME_LIMIT
            red = int(255 * (1 - fraction))
            green = int(255 * fraction)
            self.canvas.itemconfig(self.progress_bar, fill=f"#{red:02x}{green:02x}00")
            self.canvas.coords(self.progress_bar, 0, 0, 300 * fraction, 20)

            self.root.after(1000, self.update_time_left)
        elif self.time_left <= 0:
            self.game_over("時間切れです")

    def game_over(self, reason):
        self.game_active = False
        self.info_label.config(text=reason)
        self.score_label.config(text=f"連続正解数: {self.consecutive_correct}\n合計正解数: {self.total_correct}\n不正解数: {self.incorrect_count}")
        self.retry_button.pack(pady=20)  # Show the retry button
        
        # Check and update high scores
        if self.consecutive_correct > self.high_consecutive:
            self.high_consecutive = self.consecutive_correct
        if self.total_correct > self.high_total:
            self.high_total = self.total_correct
        self.highscore_label.config(text=f"最高連続正解数: {self.high_consecutive}\n最高正解数: {self.high_total}")
        self.save_highscore()

    def save_highscore(self):
        with open('scoredata.txt', 'w') as f:
            f.write(f"{self.high_consecutive},{self.high_total}")

    def update_score(self):
        self.score_label.config(text=f"連続正解数: {self.consecutive_correct}\n合計正解数: {self.total_correct}\n不正解数: {self.incorrect_count}")

    def restart_game(self):
        # Remove all GUI elements
        for widget in self.root.winfo_children():
            widget.destroy()

        # Set up the game again
        self.setup_game()

if __name__ == "__main__":
    root = tk.Tk()
    game = KakezanGame(root)
    root.mainloop()


```
実際のアプリの画面はこんな感じです．
![GUI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3128248/63c4b383-eb3c-8c4e-e0e8-26071f6c7d55.png)
テキストの入力欄に数字を打ち込んでいきます．

皆さんも一緒に99までの九九を暗算でできるようになりましょう！