タイトル画像
琴葉茜

さて、これで一通りじゃんけんゲームは完成したことだし、このゲームを誰かに遊んでもらえるようにしようか!

琴葉葵

え、別にそのままプログラムを渡せば遊んでもらえないの??

琴葉茜

葵さんや、最初の頃を思い出してほしいんだけど、他の人はどうやってPythonのプログラムを動かしたら良いか分かるかな??

琴葉葵

あ、そういえば、ちゃんとPythonを動かせる環境を用意しないと出来ないじゃん!

琴葉茜

そうだよね
Pythonはどのパソコンにも入ってるわけじゃないからプログラムを渡しただけじゃダメなんだよ

琴葉茜

だからここでは、自分で作ったPythonのプログラムを誰でも使えるように「実行ファイル化」する方法を紹介するね!

琴葉葵

実行ファイル??

琴葉茜

実行ファイルは、Windowsだと「〇〇.exe」、Macだと「〇〇.app」って拡張子が付いてるファイルのことで、それをダブルクリックすることで実行するアプリだよ

琴葉葵

ああ!パソコンのソフトとかスマホのアプリとかか!!

琴葉茜

そうだね
それで、この実行ファイルにしてくれるライブラリが「PyInstaller」だよ

解説画像1
琴葉葵

ライブラリってことは、今のプログラムを何か変更したりするの?

琴葉茜

「PyInstaller」はプログラム自体は変更せずに、ターミナル(コマンドプロンプト)上で使うよ

琴葉茜

基本的には実行ファイル化したいプログラムがあるカレントディレクトリまで移動して、「特定のコマンド」を実行してあげれば完成するんだけど、その前に注意してほしいことがあるんだ

琴葉葵

注意してほしいこと??

琴葉茜

まず1つ目は「PyInstallerを実行したOSに依存する実行ファイルになる」ってことだね

琴葉茜

Windowsではexeファイル、Macではappファイルになるから、Windowsユーザーが作成したアプリをMacユーザーは使えないんだ

琴葉葵

それは地味に困るかも

琴葉茜

ちなみにMac同士であっても、Intel製かAppleシリコン製で違っていれば使えないから気をつけてね

琴葉葵

(...その辺はよく分からない)

琴葉茜

次に2つ目なんだけど、こっちの方が重要で、「そのままPyInstallerを使うとアプリに必要ない外部ライブラリが全て実行ファイルに入ってしまう」ってことだよ

琴葉葵

え、それってつまり、今回のじゃんけんゲームを実行ファイル化したら、その中に使ってない「OpenCV」とかが入っちゃうってこと!?

琴葉茜

そういうこと
だから、このじゃんけんゲームも実行ファイル化したら容量が数GBとかになっちゃうかも

琴葉葵

ただじゃんけんできるだけなのにそれはヤバい

琴葉茜

...ということで、その問題を解決する方法として新しく「仮想環境」っていうのを紹介するよ!

解説画像2
琴葉葵

なんか難しそうなのが現れたな!?

琴葉茜

仮想環境っていうのは、自分の家に「何かを作るための一時的な作業部屋」を作るイメージだよ

琴葉葵

ほうほう
なんか秘密基地っぽいね!

琴葉茜

その部屋は最初は空っぽで、何かを作るために余計な道具は持ち込まず、最低限必要の道具だけで作業をするって感じだね

琴葉葵

分かるような分からないような...

琴葉茜

じゃあここからは、実際に動かしてみながら説明するね

琴葉茜

今、すでにターミナルのカレントディレクトリは今回作った「janken.py」がある場所と同じ場所だとするよ

琴葉葵

そのまま続けてきてるので大丈夫だと思います...!

琴葉茜

じゃあまずは、そのカレントディレクトリに仮装環境を用意しよう!
ターミナルで次のコマンドを実行しよう

Windowsの場合

python -m venv janken_venv

Macの場合

python3 -m venv janken_venv
琴葉茜

これでまずは「janken_venv」っていう名前の仮想環境が用意できたよ!

琴葉葵

あっ!janken.pyを置いてるフォルダに「janken_venv」っていうフォルダができてるね!!

琴葉茜

ちなみにさっきのコマンドの1番最後の「janken_venv」ってところは仮想環境の名前を決めるとことだから、別の名前にしても大丈夫だよ

琴葉葵

これで今、作業部屋を用意できたって感じなんだね!

琴葉茜

そんな感じだね!じゃあ次は作った仮想環境の中に入るコマンドを実行してみよう

Windowsの場合

.\janken_venv\Scripts\activate

Macの場合

source janken_venv/bin/activate
琴葉葵

あ、なんかターミナルの1番左に「(janken_venv)」ってのが出てきた!これが中に入ったっていう目印なんだね!

琴葉茜

そういうこと
この状態でWindowsなら「pip list」、Macなら「pip3 list」を実行してみると、何もないのが確認できると思うよ!

琴葉葵

ほんとだ!今まで色んなライブラリをpipで入れてきたのに何もない!!

琴葉茜

今、仮想環境の中は初めてパソコンにPythonを入れた時と同じ状態になってるから、改めてpipのコマンドで「TkEasyGUI」を追加しよう!

Windowsの場合

pip install TkEasyGUI

Macの場合

pip3 install TkEasyGUI
琴葉葵

おっけー!ちゃんとインストールできたよ!

琴葉茜

じゃあこの状態で、janken.pyが実行できるか確認してみようか
実行方法は今までと変わらないよ!

琴葉葵

大丈夫そう!!

琴葉茜

そしたら次は、「PyInstaller」をインストールしよう!

Windowsの場合

pip install pyinstaller

Macの場合

pip3 install pyinstaller
琴葉葵

インストール完了!!

琴葉茜

次に、今回はゲーム内に画像を使用してるけど、実行ファイルで動かした時にはそのまま読み込んでくれないって問題があるから、少し調整を加えるよ

import TkEasyGUI as eg
import random
import sys, os # 追加

# PyInstaller対応:開発中・実行ファイルの両方で正しいパスを返す
def resource_path(relative_path):
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)
    return os.path.join(os.path.abspath("."), relative_path)


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

完璧には分からないけど、画像の場所を細かく指定するために「絶対パス」にしているんだね!

琴葉茜

ちょっと手間はかかるけど、今後画像を使うツールを作る時は「resource_path()」を丸パクリすれば良いよ!

琴葉茜

これであとは実行ファイル化をするだけだよ
次のコマンドを実行してみよう!

Windowsの場合

pyinstaller janken.py --noconsole --clean --add-data "janken_gu.png;." --add-data "janken_choki.png;." --add-data "janken_pa.png;."

Macの場合

pyinstaller janken.py --noconsole --clean --add-data "janken_gu.png:." --add-data "janken_choki.png:." --add-data "janken_pa.png:."
琴葉葵

なんか長いコマンドだね汗
あ、なんか大量に文字が出てきてる!!

琴葉茜

今回は画像を使ってるから「--add-data」を使ってるけど、使わない場合は「pyinstaller janken.py --noconsole --clean」だけで良いよ!

琴葉葵

あ、しばらく待ってたら終わった!!なんかフォルダの中に色んなものが追加されてるね

琴葉茜

その中に「dist」ってフォルダはないかな?

琴葉葵

あるよ!...あ!中に「janken.exe」ってのがあるよ!!

琴葉茜

お!見つかったね!!じゃあそれをダブルクリックしてみよう

琴葉葵

おおー!!ちゃんと動いた!!

琴葉茜

これで実行ファイル化の完了だよ!その「dist」ってフォルダを誰かに渡してあげると、その人もダブルクリックだけで使えるようになるよ

琴葉葵

やったね!!

琴葉茜

ちなみに、PyInstallerのコマンドに付けられるオプションは他にもあるから、自分のやりたいことに合わせて調べてみてね!

琴葉葵

りょうかい!!ところで、仮想環境から抜け出す方法はないの??

琴葉茜

あ、忘れてたね汗
仮想環境から抜け出す時は次のコマンドを実行すればOKだよ!

deactivate
琴葉葵

脱出完了!!

琴葉茜

さて、これでひとまずじゃんけんゲームが完成だよ!ここからは、「CPUがじゃんけんの手を出す確率を過去にプレイヤーが出した手の傾向で変化させる」とか「何回勝負で勝ち負けを決める」みたいに自分でオリジナル要素を追加してみてね!

琴葉葵

自分で考えるのはなかなかハードルが高そう...

琴葉茜

もし自分で1から考えるのが難しいなら、「ChatGPT」とかの生成AIをどんどん活用したら良いと思うよ!

琴葉葵

学習用のサイトでそれ言っちゃうのね...

琴葉茜

まぁ基礎はしっかり学べてると思うしね!今まではどう聞いて良いか分からなかっただろうし、そのプログラムが正しいかどうかって分別が付かなかったと思うけど、きっと今なら大丈夫じゃないかな

琴葉葵

まずは何かを作りきる方が大事だもんね!頑張ってみる!!

完成した全体のプログラム

import TkEasyGUI as eg
import random
import sys
import os

# PyInstaller対応:開発中・実行ファイルの両方で正しいパスを返す
def resource_path(relative_path):
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)
    return os.path.join(os.path.abspath("."), relative_path)


# 定数
HANDS = ["グー", "チョキ", "パー"]
HAND_IMAGES = {
    "グー": resource_path("janken_gu.png"),
    "チョキ": resource_path("janken_choki.png"),
    "パー": resource_path("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(), element_justification="center")

        # イベントループ(閉じるまで表示)
        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()