Pythonで作成！令和6年度共通テストのn進数タイマー

# はじめに
![Screenshot 2024-09-19 at 15-30-27 - 2024_or_19_sugaku1A.pdf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885292/2e2838f5-22b2-98a8-69fe-41d8d21ca87c.png)
　今回の記事では、Pythonを使って令和6年度共通テストのn進数タイマーを、デザインにも凝って要件定義、仕様策定、実装の3つに分けて解説します！これを実装するにあたり、@Tadataka_Takahashi様の[Pythonで作るポップなポモドーロタイマー](https://qiita.com/Tadataka_Takahashi/items/556e0277017677cef68a)を一部参考にして実装しました！

 https://qiita.com/Tadataka_Takahashi/items/556e0277017677cef68a

:::note info
[令和6年度共通テスト　数学I・A本試験問題](https://www.dnc.ac.jp/albums/abm.php?d=666&f=abm00004685.pdf&n=2024_or_19_sugaku1A.pdf)はこちら！
[令和6年度共通テスト　数学I・A本試験の解答](https://www.dnc.ac.jp/albums/abm.php?d=661&f=abm00004293.pdf&n=R6_%E6%95%B0%E5%AD%A6%E2%85%A0%E3%83%BB%E6%95%B0%E5%AD%A6A_%E6%9C%AC%E8%A9%A6%E9%A8%93%E3%81%AE%E6%AD%A3%E8%A7%A3.pdf)はこちら！
:::

## 1.要件定義
　まず、n進数タイマーについて基本的な要件を定義します。
### 1.1機能要件
　1.タイマーを3つ用意する。
　2.スタートした時点では000と表示する。
　3.タイマーがスタートした後、表示される数が1秒ごとに1ずつ増やす。
　4.3桁で表示できる最大の数が表示された1秒後に、表示が000に戻る。
　5.タイマーの表示が000に戻った後も、3~4を繰り返す。
　6.タイマーの開始、停止、リセット、設定機能。
### 1.2非機能要件
　1.ユーザーフレンドリーなインターフェース
　2.視覚的にわかりやすいデザイン
　3.操作が直感的で簡単であること
　4.プログラムの安定性と信頼性
## 2.仕様策定
　要件に基づき、具体的な仕様を決定します。
### 2.1タイマー仕様
　・タイマーは1秒ごとにカウントアップ
　・設定で決めた進数に応じて繰り上げ
### 2.2タイマーのUI仕様
![スクリーンショット 2024-09-19 160250.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885292/51f16daf-735f-3f3b-0a1e-772e4aaac6b6.png)
　・ウィンドウサイズ:1000x400
 
　・表示要素：
　　◦タイトル
　　◦3つのタイマー
　　◦現在の時間
  
　・操作要素：
　　◦開始
　　◦停止
　　◦リセット
　　◦設定
### 2.3設定仕様
　・タイマーの進数を設定
　・決定で変更
### 2.4設定UI仕様
![スクリーンショット 2024-09-19 163109.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885292/fe93cc03-403d-e0af-e934-0cc6e151f4c6.png)
　・ウィンドウサイズ:450x300
 
　・表示要素：
　　◦タイトル
　　◦3つのタイマーの進数の設定ラベル
  
　・操作要素：
　　◦3つのタイマーの進数の設定テーブル
　　◦決定
### 2.5デザイン仕様
　・カラーパレット
　　◦背景色:寒色(#87cefa)
　　◦メインカラー:鮮明な色(#4169e1)
　　◦アクセントカラー:補色となるような色(#ff7f50)
　・フォント: 読みやすいサンセリフフォント（例：Helvetica）

## 3.実装
　仕様に基づき、Pythonコードを実装します。以下は実装例です。
```main.py
import tkinter as tk
from tkinter import ttk, messagebox

class CountUpTimer:
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("n進数タイマー")
        self.window.config(padx=20, pady=20, bg="#87cefa")
        self.window.geometry("1000x400")

        self.bases = [10, 10, 10]  # デフォルトの進数(左から 10 10 10進数)
        self.timers_running = False  # タイマーのスタート初期値化
        self.timer_values = [0, 0, 0]  # タイマーの値の初期値化
        self.timer_labels = []
        self.setup_ui()

    def setup_ui(self):
        # フォントとカラーの設定
        TITLE_FONT = ("Helvetica", 28, "bold")
        TIMER_FONT = ("Helvetica", 64, "bold")
        BUTTON_FONT = ("Helvetica", 14, "bold")
        MAIN_COLOR = "#4169e1"
        SECONDARY_COLOR = "#7fffd4"
        ACCENT_COLOR = "#ff7f50"
        
        self.title_label = tk.Label(text="n進数タイマー", fg=MAIN_COLOR, bg="#87cefa", font=TITLE_FONT)
        self.title_label.pack()

        # タイマーフレームのコンテナ
        self.timer_frame_container = tk.Frame(self.window, bg="#87cefa")
        self.timer_frame_container.pack(pady=20)

        # タイマーフレームとラベル
        for i in range(3):
            frame = tk.Frame(self.timer_frame_container, bg="#0000cd", bd=2, relief="raised")
            frame.pack(side=tk.LEFT, padx=10)
            label = tk.Label(frame, text="000", fg=MAIN_COLOR, bg="#0000cd", font=TIMER_FONT, width=5)  # Fixed width
            label.pack(pady=20)
            self.timer_labels.append(label)

        # ボタン群のフレーム
        self.button_frame = tk.Frame(self.window, bg="#87cefa")
        self.button_frame.pack(pady=20)

        # ボタンたち
        self.start_button = tk.Button(self.button_frame, text="開始", padx=30, pady=10, command=self.start_timers, font=BUTTON_FONT, bg=SECONDARY_COLOR, width=10)
        self.start_button.pack(side=tk.LEFT, padx=5)

        self.stop_button = tk.Button(self.button_frame, text="停止", padx=30, pady=10, command=self.stop_timers, font=BUTTON_FONT, bg=SECONDARY_COLOR, width=10)
        self.stop_button.pack(side=tk.LEFT, padx=5)

        self.reset_button = tk.Button(self.button_frame, text="リセット", padx=30, pady=10, command=self.reset_timers, font=BUTTON_FONT, bg=SECONDARY_COLOR, width=10)
        self.reset_button.pack(side=tk.LEFT, padx=5)

        self.option_button = tk.Button(self.button_frame, text="設定", padx=30, pady=10, command=self.option_setup, font=BUTTON_FONT, bg=ACCENT_COLOR, width=10)
        self.option_button.pack(side=tk.LEFT, padx=5)

    def start_timers(self):
        if not self.timers_running:
            self.timers_running = True
            self.update_timers()

    def stop_timers(self):
        self.timers_running = False

    def reset_timers(self):
        self.timers_running = False
        self.timer_values = [0, 0, 0]
        self.update_timer_labels()

    def update_timers(self):
        if self.timers_running:
            for i in range(3):
                self.timer_values[i] += 1
                # 進数に基づいた繰り上げ
                if self.timer_values[i] >= self.bases[i] ** 3:
                    self.timer_values[i] = 0
            self.update_timer_labels()
            self.window.after(1000, self.update_timers)  # カウントアップ

    def update_timer_labels(self):
        for i, label in enumerate(self.timer_labels):
            value = self.timer_values[i]
            label.config(text=self.convert_to_base(value, self.bases[i]))

    def convert_to_base(self, value, base):
        chars = "0123456789ABCDEF"
        result = ""
        for _ in range(3):
            result = chars[value % base] + result
            value //= base
        return result.zfill(3)

    def option_setup(self):
        # 新しいウィンドウの作成
        settings_window = tk.Toplevel(self.window)
        settings_window.title("設定")
        settings_window.config(bg="#87cefa")
        settings_window.geometry("450x300")

        LABEL_FONT = ("Helvetica", 18)
        BUTTON_FONT = ("Helvetica", 14, "bold")
        ACCENT_COLOR = "#ff7f50"

        label = tk.Label(settings_window, text="各タイマーの進数を設定してください。", font=LABEL_FONT, bg="#87cefa")
        label.grid(row=0, column=0, columnspan=3, pady=10)

        base_selectors = []
        for i in range(3):
            timer_label = tk.Label(settings_window, text=f"タイマー {i+1} の進数: ", font=LABEL_FONT, bg="#87cefa")
            timer_label.grid(row=i + 1, column=0, padx=10, pady=10)

            base_options = [str(n) for n in range(2, 17)]  # Options from base 2 to 16
            base_selector = ttk.Combobox(settings_window, values=base_options, font=("Helvetica", 14), width=5)
            base_selector.set(str(self.bases[i]))  # Set default value
            base_selector.grid(row=i + 1, column=1, padx=10, pady=10)
            base_selectors.append(base_selector)

        apply_button = tk.Button(settings_window, text="決定", command=lambda: self.apply_settings(base_selectors, settings_window), font=BUTTON_FONT, bg=ACCENT_COLOR)
        apply_button.grid(row=4, column=0, columnspan=3, pady=20)

    def apply_settings(self, selectors, window):
        try:
            for i, selector in enumerate(selectors):
                base = int(selector.get())
                if base < 2 or base > 16:
                    raise ValueError
                self.bases[i] = base
            window.destroy()
        except ValueError:
            messagebox.showerror("入力エラー", "2から16の間の数値を入力してください。")

    def run(self):
        self.window.mainloop()

if __name__ == "__main__":
    timer = CountUpTimer()
    timer.run()

```
## 4.実装のポイント
　n進数タイマーの実装について、以下のポイントに注意しました。
### 4.1クラスベースの設計
　CountUpTimerクラスを中心に、アプリケーションの全機能をまとめ、再利用性と保守性を向上させた。
### 4.2UIデザインの考慮
　色やフォントを適切に選択し、視覚的に魅力的なインターフェースを作成した。また、ボタンのサイズや配置を工夫し、使いやすさを向上させた。
### 4.3無効な入力の防止
　無効な進数入力に対するエラーハンドリングを行い、プログラムの安全性を向上させた。
## 5.次回の展望
　・問題が解けるように、任意のタイマーの組が同時に000となるような秒数の表示
　・クロスプラットフォーム対応（Webアプリケーションやモバイルアプリへの展開）
次回には、誰でもこの問題について解けるようなアプリケーションに拡張していきたい。
## 6.まとめ
　実際に要件定義をし、仕様策定をして実装する。といった作業をするのは今回が初めてでした。そのため、自身が気が付いていないバグが存在したり、不足の機能があると思います。ですが、完成までこぎつけることができたため、大変満足です！皆さんもPythonで日常の問題に対してアプリケーション化してみましょう！良いPythonライフを！
## 7.exe化したものの配布
　[exe化したもの](https://drive.google.com/drive/folders/1-0IYyp8ucVRDsm8bFFuxDXF34w6BKiEX?usp=sharing)はこちら！手順は下記に記します。
　・すべてダウンロード
　・ファイルを展開
　・distファイルを開く
　・mainファイルを開く
　・main.exeを起動

## 8.コメント対応:実装
　実際に私自身は全然できない方です。そのため、指摘事項、改善の内容があるならば、コメント欄を参照ください。
 メインUI
 ![スクリーンショット 2024-09-19 160250.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885292/51f16daf-735f-3f3b-0a1e-772e4aaac6b6.png)
　設定UI仕様
![スクリーンショット 2024-09-19 163109.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885292/fe93cc03-403d-e0af-e934-0cc6e151f4c6.png)
 メインプログラム
 ```main.py
import tkinter as tk
from tkinter import ttk, messagebox

class CountUpTimer:
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("n進数タイマー")
        self.window.config(padx=20, pady=20, bg="#87cefa")
        self.window.geometry("1000x400")

        self.bases = [10, 10, 10]  # デフォルトの進数(左から 10 10 10進数)
        self.timers_running = False  # タイマーのスタート初期値化
        self.timer_values = [0, 0, 0]  # タイマーの値の初期値化
        self.timer_labels = []
        self.setup_ui()

    def setup_ui(self):
        # フォントとカラーの設定
        TITLE_FONT = ("Helvetica", 28, "bold")
        TIMER_FONT = ("Helvetica", 64, "bold")
        BUTTON_FONT = ("Helvetica", 14, "bold")
        MAIN_COLOR = "#4169e1"
        SECONDARY_COLOR = "#7fffd4"
        ACCENT_COLOR = "#ff7f50"
        
        self.title_label = tk.Label(text="n進数タイマー", fg=MAIN_COLOR, bg="#87cefa", font=TITLE_FONT)
        self.title_label.pack()

        # タイマーフレームのコンテナ
        self.timer_frame_container = tk.Frame(self.window, bg="#87cefa")
        self.timer_frame_container.pack(pady=20)

        # タイマーフレームとラベル
        for i in range(3):
            frame = tk.Frame(self.timer_frame_container, bg="#0000cd", bd=2, relief="raised")
            frame.pack(side=tk.LEFT, padx=10)
            label = tk.Label(frame, text="000", fg=MAIN_COLOR, bg="#0000cd", font=TIMER_FONT, width=5)  # Fixed width
            label.pack(pady=20)
            self.timer_labels.append(label)

        # ボタン群のフレーム
        self.button_frame = tk.Frame(self.window, bg="#87cefa")
        self.button_frame.pack(pady=20)

        # ボタンたち
        self.start_button = tk.Button(self.button_frame, text="開始", padx=30, pady=10, command=self.start_timers, font=BUTTON_FONT, bg=SECONDARY_COLOR, width=10)
        self.start_button.pack(side=tk.LEFT, padx=5)

        self.stop_button = tk.Button(self.button_frame, text="停止", padx=30, pady=10, command=self.stop_timers, font=BUTTON_FONT, bg=SECONDARY_COLOR, width=10)
        self.stop_button.pack(side=tk.LEFT, padx=5)

        self.reset_button = tk.Button(self.button_frame, text="リセット", padx=30, pady=10, command=self.reset_timers, font=BUTTON_FONT, bg=SECONDARY_COLOR, width=10)
        self.reset_button.pack(side=tk.LEFT, padx=5)

        self.option_button = tk.Button(self.button_frame, text="設定", padx=30, pady=10, command=self.option_setup, font=BUTTON_FONT, bg=ACCENT_COLOR, width=10)
        self.option_button.pack(side=tk.LEFT, padx=5)

    def start_timers(self):
        if not self.timers_running:
            self.timers_running = True
            self.update_timers()

    def stop_timers(self):
        self.timers_running = False

    def reset_timers(self):
        self.timers_running = False
        self.timer_values = [0, 0, 0]
        self.update_timer_labels()

    def update_timers(self):
        if self.timers_running:
            for i in range(3):
                self.timer_values[i] += 1
                # 進数に基づいた繰り上げ
                if self.timer_values[i] >= self.bases[i] ** 3:
                    self.timer_values[i] = 0
            self.update_timer_labels()
            self.window.after(1000, self.update_timers)  # カウントアップ

    def update_timer_labels(self):
        for i, label in enumerate(self.timer_labels):
            value = self.timer_values[i]
            label.config(text=self.convert_to_base(value, self.bases[i]))

    def convert_to_base(self, value, base):
        chars = "0123456789ABCDEF"
        result = ""
        for _ in range(3):
            result = chars[value % base] + result
            value //= base
        return result.zfill(3)

    def option_setup(self):
        # 新しいウィンドウの作成
        settings_window = tk.Toplevel(self.window)
        settings_window.title("設定")
        settings_window.config(bg="#87cefa")
        settings_window.geometry("450x300")

        LABEL_FONT = ("Helvetica", 18)
        BUTTON_FONT = ("Helvetica", 14, "bold")
        ACCENT_COLOR = "#ff7f50"

        label = tk.Label(settings_window, text="各タイマーの進数を設定してください。", font=LABEL_FONT, bg="#87cefa")
        label.grid(row=0, column=0, columnspan=3, pady=10)

        base_selectors = []
        for i in range(3):
            timer_label = tk.Label(settings_window, text=f"タイマー {i+1} の進数: ", font=LABEL_FONT, bg="#87cefa")
            timer_label.grid(row=i + 1, column=0, padx=10, pady=10)

            base_options = [str(n) for n in range(2, 17)]  # Options from base 2 to 16
            base_selector = ttk.Combobox(settings_window, values=base_options, font=("Helvetica", 14), width=5)
            base_selector.set(str(self.bases[i]))  # Set default value
            base_selector.grid(row=i + 1, column=1, padx=10, pady=10)
            base_selectors.append(base_selector)

        apply_button = tk.Button(settings_window, text="決定", command=lambda: self.apply_settings(base_selectors, settings_window), font=BUTTON_FONT, bg=ACCENT_COLOR)
        apply_button.grid(row=4, column=0, columnspan=3, pady=20)

    def apply_settings(self, selectors, window):
        try:
            for i, selector in enumerate(selectors):
                base = int(selector.get())
                if base < 2 or base > 16:
                    raise ValueError
                self.bases[i] = base
            window.destroy()
        except ValueError:
            messagebox.showerror("入力エラー", "2から16の間の数値を入力してください。")

    def run(self):
        self.window.mainloop()

if __name__ == "__main__":
    timer = CountUpTimer()
    timer.run()

```