タイトル画像
琴葉茜

前回で一応じゃんけんゲームのアプリが完成したけど、色々機能を追加したらプログラムが複雑になっちゃったよね

琴葉葵

そうだよねー
どこからどこまでがボタンの機能か分かりにくいし、なんか更新するプログラムが何箇所もあって見にくいし

琴葉茜

ということで、今回はプログラムをもう少し簡潔に整えていこう!

琴葉茜

まずは変数名について
「hands」「hand_images」「win_rules」ってこのプログラムにおいては絶対に変更しない要素だよね

琴葉葵

確かに、じゃんけんの手の種類が増えたり勝ち方が変わっちゃうことってないもんね

琴葉茜

そうだね!こういう、プログラムの動作中に絶対変更することがないものを「定数」っていうんだ

琴葉茜

他のプログラムではちゃんと区別を付けられるんだけど、Pythonは変数と定数で扱いの違いは無いんだよね

琴葉葵

じゃあ別にわざわざ改めなくても良いんじゃない??

琴葉茜

まぁ命名の仕方はなんでも良いんだけど、「こうやって名前をつけた方が皆が分かりやすくなるから良いよ」っていう暗黙の作法みたいなのがあるんだ

琴葉葵

ほうほう
じゃあその作法を覚えておけば、パッと見て定数か変数かってのが分かるようになるんだね

琴葉茜

そゆこと
ちなみに「定数」は全部の文字を大文字にして、単語ごとをアンダーバーで繋いだ形にすれば良いよ!

# 定数
HANDS = ["グー", "チョキ", "パー"]
HAND_IMAGES = {
    "グー": "janken_gu.png",
    "チョキ": "janken_choki.png",
    "パー": "janken_pa.png"
}
WIN_RULES = {
    "グー": "チョキ",
    "チョキ": "パー",
    "パー": "グー"
}
琴葉葵

定数を使ってる場所もちゃんと変更するのを忘れないようにしないとだね!!

琴葉茜

次は各ボタンの処理とスコアの管理についての改善方法を考えようか

琴葉葵

ボタンの処理の部分だと、if文の中にたくさんプログラムを書いてるから分かりにくくなってるよね

琴葉葵

スコアの管理についても、「global」を使うのはあまり良くなさそうだったし、使わないでうまく出来る方法はないのかなぁ

琴葉茜

まさにその2つが今回の改善ポイントだね!
その問題を一気に解決してくれるのが「class」なんだよ

琴葉葵

そういえばじゃんけんゲームを作る前に紹介してたね!?

琴葉茜

今回の場合、スコアを管理する変数をゲーム全体で扱えるのが良くて、「各ボタンにそれぞれ機能を持たせるイメージ」が重要になるよね

琴葉葵

オブジェクト指向...!!

琴葉茜

今回はプログラム全体を「class」にすることで、葵が言った改善ポイントをクリアできるようになるんだ

琴葉茜

だけど、1つずつ説明すると混乱するかもしれないから、今回は改修後の全体のプログラムを先に出しちゃうね

import TkEasyGUI as eg
import random

# 定数
HANDS = ["グー", "チョキ", "パー"]
HAND_IMAGES = {
    "グー": "janken_gu.png",
    "チョキ": "janken_choki.png",
    "パー": "janken_pa.png"
}
WIN_RULES = {
    "グー": "チョキ",
    "チョキ": "パー",
    "パー": "グー"
}

class JankenGame:

    def __init__(self):
        # スコア変数
        self.win_count = 0
        self.lose_count = 0
        self.draw_count = 0
        self.window = None

    def check_hands(self,player_hand, cpu_hand):
        if player_hand == cpu_hand:
            self.draw_count += 1
            return "あいこだよ"
        elif WIN_RULES[player_hand] == cpu_hand:
            self.win_count += 1
            return "勝ち!!"
        else:
            self.lose_count += 1
            return "負け..."
    
    def update_hand_image(self):
        # 処理後のじゃんけんの手の画像の更新
        self.window["win_score"].update(f"勝ち: {self.win_count}")
        self.window["lose_score"].update(f"負け: {self.lose_count}")
        self.window["draw_score"].update(f"あいこ: {self.draw_count}")

    def play_janken(self, player):
        # じゃんけんの手のボタンを押した時の処理
        cpu = random.choice(HANDS)
        self.window["player_hand"].update(HAND_IMAGES[player])
        self.window["cpu_hand"].update(HAND_IMAGES[cpu])
        self.window["result"].update(f"プレイヤーの手は「{player}」、CPUの手は「{cpu}」です")

        eg.popup(self.check_hands(player, cpu))
        self.update_hand_image()

    def score_reset(self):
        # リセットボタンを押した時の処理
        self.window["player_hand"].update(data=b"")
        self.window["cpu_hand"].update(data=b"")
        self.window["result"].update("")

        self.win_count = 0
        self.lose_count = 0
        self.draw_count = 0
        self.update_hand_image()
        eg.popup("スコアをリセットしました!")

    def create_layout(self):
        # レイアウト(画面構成)
        col1 = eg.Column([[eg.Image(HAND_IMAGES["グー"], size=(100, 100))], [eg.Button("グー")]])
        col2 = eg.Column([[eg.Image(HAND_IMAGES["チョキ"], size=(100, 100))], [eg.Button("チョキ")]])
        col3 = eg.Column([[eg.Image(HAND_IMAGES["パー"], size=(100, 100))], [eg.Button("パー")]])

        layout = [
            [eg.Text("じゃんけんの手を選ぼう!")],
            [col1, col2, col3],
            [eg.Text("プレイヤーの手"),
             eg.Image(None, size=(100, 100),key="player_hand"),
             eg.Text("vs"),
             eg.Image(None, size=(100, 100), key="cpu_hand"),
             eg.Text("CPUの手")],
            [eg.Text("", key="result")],
            [eg.Text("勝ち: 0", key="win_score"),
             eg.Text("負け: 0", key="lose_score"),
             eg.Text("あいこ: 0", key="draw_score")],
            [eg.Button("リセット")]
        ]
        return layout

    def run(self):
        # ウィンドウを作成
        self.window = eg.Window("じゃんけんアプリ", self.create_layout())

        # イベントループ(閉じるまで表示)
        while True:
            event, values = self.window.read()
            if event in HANDS:
                self.play_janken(event)
            if event == "リセット":
                self.score_reset()
            if event is eg.WINDOW_CLOSED:
                break

        # ウィンドウを閉じる
        self.window.close()

# ゲームを実行
game = JankenGame()
game.run()
琴葉葵

Oh...なんかガラッと変わった...

琴葉茜

大丈夫、落ち着いて見てみようか
まず、メインでプログラムが動く場所は「def run(self):」のところだよ!

琴葉葵

あ、確かに
じゃんけんの手のボタンには「self.play_janken(event)」
リセットボタンには「self.score_reset()」
って関数だけが入ってるね

琴葉茜

そうだね!ボタンに対して1つの機能が付いているって形になってるから、その機能を変更したい時に何を見たら良いか分かりやすいでしょ

琴葉葵

クラスの中の関数も、元からあったプログラムをほぼそのまま関数名を付けただけなんだね!

琴葉茜

今回は元の形を出来るだけキープした方が分かりやすいと思ったからね!
あとは、クラスの中で扱いたいインスタンス変数や関数に対しては「self」が付いてるって違いくらいかな

琴葉葵

この「self」を付けるのが面倒そうだけどね...

琴葉茜

まぁプログラミング中はそう感じるかもだけど、クラスを使うことで新しい機能を追加する時や機能を変更したい時に便利になるから慣れることをオススメするよ

琴葉葵

1から考えるのは難しいから、今回みたいに変数の管理が難しいって感じ始めたら考えてみるようにする!

琴葉茜

最後にデザインの部分だけど、オブジェクトが全体的に左に寄ってて綺麗じゃないでしょ

琴葉葵

確かに、気になる人は気になるかもね

琴葉茜

そこで、オブジェクトを中央揃えにする方法を教えるよ!

琴葉茜

...といっても、これはすごく簡単で、「eg.Window()」の引数に「element_justification="center"」を追加するだけで良いよ!!

self.window = eg.Window("じゃんけんアプリ", self.create_layout(), element_justification="center")
解説画像1
琴葉葵

それだけでこんなに綺麗になるんだ!
てか、クラスにしたからプログラムのどこを変えたら良いのか分かりやすくなってる!!

琴葉茜

それなら良かった
見つけられない人はクラス内の関数「def run(self):」に注目してみてね!