【初心者向け】PythonでGUIアプリケーションを作成する

# はじめに
PythonのGUIライブラリの **Tkinter** を使用して、じゃんけんゲームを作成しました。
「Python　じゃんけん」と検索すると、基礎から応用まで多くの解説記事を見つけることができます。

しかしながら、その記事の多くはCLIベースのアプリケーションであり、GUIをどのように作りこむかまで解説している記事はあまり多くないことが分かりました。
そこで今回は、じゃんけんのGUIアプリケーションを作成し、記事へまとめることにしました。

筆者はPython初心者で、今回が初めてのアプリケーションの開発経験でした。
今回学んだことを整理する意味合いも込めて、GUIアプリケーションの作成手順や、苦戦した部分についてまとめています。
この記事を読んでいる皆さんにとって参考になれば幸いです。
<br>

:::note info
**この記事で分かること**
・Tkinterの基本操作
・Python初心者（筆者）がじゃんけんゲームを作成して
　つまづいたポイントとその解決策
:::
:::note info
使用環境：Windows11, Python3.12.4
前提：アプリケーションはローカルで動かす
:::

# じゃんけんアプリ
### 使用モジュール、ライブラリについて
<dl>
  <dt>random：</dt>
  <dd>疑似乱数を生成するモジュールです。今回はCPU側のじゃんけんの手を決める際に使用しました。
  </dd>
</dl>

<dl>
  <dt>Tkinter：</dt>
  <dd>PythonのGUIライブラリの一つです。
Pythonに標準で入っているライブラリのため、新たにインストールする必要がありません。シンプルな文法かつ学習コストが低い点から、今回のアプリケーション作成に適していると考えました。</dd>
</dl>

<dl>
  <dt>PIL（Pillow）：</dt>
  <dd>画像ファイルの読み込みや修正といった画像処理を行うPythonライブラリです。</dd>

　　　__Image：__ 画像の読み込みや保存、加工を行います。
　　__ImageTk：__  ImageオブジェクトをTkinterが理解できるオブジェクトに変更し、
　　　　　　 　画面に表示します。</dd>
</dl>

:::note warn
PILは標準ライブラリではないので、別途インストールが必要です。
:::
```
pip install Pillow
```




---
### Tkinterでウィンドウを作成
まずはアプリを動かすウィンドウを表示させます。
必要なモジュールをインポートし、以下のようなコードを書きます。
```php
import tkinter as tk 
import random as rd
from PIL import Image, ImageTk
```

```php
# ウィンドウの表示
root = tk.Tk()
root.geometry("500x500")
root.title("じゃんけんゲーム")
root.resizable(0,0)
...
# この間にプログラムが入ります
...
# ウィンドウのループ処理
root.mainloop()
```

<dl>
  <dt>.geometry("<横幅>×<高さ>")：</dt>
  <dd>ウィンドウの大きさをピクセルで指定します。 </dd>
</dl>

<dl>
  <dt>.title("アプリケーション名")：</dt>
  <dd>アプリケーションのタイトルを表示します。 </dd>
</dl>

<dl>
  <dt>.resizable(x,y)：</dt>
  <dd>xy方向にウィンドウの拡大が可能かを設定します。（0=不可, 1=可） </dd>
</dl>


<dl>
  <dt>.mainloop()：</dt>
  <dd>アプリケーションの待機とイベントの処理を行います。ユーザーがクリックや入力などのアクション（イベント）を起こすまで、アプリケーションはウィンドウを開いたままにする必要があります。そこで、mainloopメソッド内の処理をループし続けることでウィンドウを表示させています。 </dd>
</dl>


<br>

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3898247/e7ccafc5-0a9b-30f3-d574-70cae9533ec2.png">


ウィンドウを表示させることができました。
ここからじゃんけんに必要な画像やボタンを追加します。

---
### ウィジェットの表示
ボタンや画像、テキストフィールドなどの画面上のパーツをウィジェットと呼びます。
作成したウィンドウにウィジェットを配置するために、以下のコードを書きます。
<br>
```php
# グーボタン作成
buttonR = tk.Button(root, text="グー")
buttonR.place(x=80, y=300)


# グー画像の挿入
imageR = Image.open('imgs/gu.png')
imageR = ImageTk.PhotoImage(imageR)
tk.Label(root, image=imageR).place(x=0, y=230)


# テキストの挿入
text_labal = tk.Label(root, text='最初はグー！ じゃんけん！', font = ("Arial", 18, "bold"), anchor="center")
text_labal.place(x=120, y=200)
```
<dl>
  <dt>tk.Label(親要素, テキストなどのオプション)：</dt>
  <dd>テキストや画像などのウィジェットを表示させます。 </dd>
</dl>

<dl>
  <dt>.place(x,y)：</dt>
  <dd> 作成したウィジェットを特定の位置（x,y座標）に配置します。 </dd>
</dl>

<br>

じゃんけんに必要なウィジェットを追加していくと、以下のように見た目になります。

<img width= 300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3898247/e4278413-b41e-4c41-d416-887a927ef4ba.png">



:::note warn
Tkinter(8.6~)はPNGに対応しています。
しかし標準ではJPEGに対応できないため、JPEGファイルを使用するにはPILのインストールが必要になります。
:::
今回は画像処理や加工のためにPILをインストールしているので、JPEGファイルでも対応可能です。

https://mulberrytassel.com/python-practice-pil-2/

JPEG画像を使用したい方はこちらの記事が参考になります。

---
### じゃんけん勝敗の定義
じゃんけんの手の出し方は全部で9通りなので、その勝敗を辞書型で配列に格納します。
この定義をもとに勝敗判定を行います。

```php
hands = [グー, チョキ, パー]
win, draw, lose = '勝ち', 'あいこ', '負け'
Rules = {
    (0,0): draw, (0,1): win, (0,2): lose,
    (1,0): lose, (1,1): draw, (1,2): win,
    (2,0): win, (2,1): lose, (2,2): draw
}
```

### 勝敗の表示
random関数を使用することで、相手のじゃんけんの手をランダムに決めています。
Rulesを参照してプレイヤーとCPUの手に対応する勝敗を判定し、それに対応したテキストと画像を表示します。

```php
images = [imageR, imageS, imageP]

def judge(player):
    cpu = random.randint(0,2)

    result = Rules[(player, cpu)]

    CPUimage.configure(image=images[cpu])

    if result == draw:
        text_labal.configure(text= 'あいこでしょ！', fg = "purple",)
                             
    elif result == win:
        text_labal.configure(text= '勝ち！', fg = "red",)
        showRetry()

    else:
        text_labal.configure(text= '負け！', fg = "blue",)
        showRetry()

```

# つまづいた点
### ①じゃんけんの手の素材を変更すると、画像サイズとレイアウトが大幅にくずれた
<br>
<img width=300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3898247/4c8cb025-444c-2c74-0c8b-d497d4d2e802.png">


使用するじゃんけんの手の画像を変更したところ、上のように画像が大きく表示され、レイアウトがくずれてしまいました。コード内で画像サイズは調整していたのですが、調べてみたところそのサイズ調整に使用した関数が原因であると分かりました。
<br>

<dl>
  <dt>原因：subsample関数を用いてサイズ調整をしたから</dt>
  <dd>subsample(n) は引数に指定した整数倍分、解像度を縮小して画像サイズを変更する関数です。よって使用するファイルの元のサイズによって変更後の画像サイズは異なって見えます。 </dd>
</dl>

<br>

<dl>
  <dt>解決策：resize関数を用いてピクセル単位で画像サイズを指定する</dt>
  <dd>resize((横幅,高さ)) と指定することで、ウィンドウやウィジェットの大きさに合わせた細かい調整ができました。
使用する画像ファイルをすべて決まった比率で縮小したいときなどは、subsample関数が役立つと思います。 </dd>
</dl>

<br>

```php
# 変更前
imageR = tk.PhotoImage(file = "imgs/gu.png").subsample(7)
tk.Label(root, image=imageR).place(x=20, y=250)


# 変更後
imageR = Image.open('imgs/gu.png').resize((140,140))
imageR = ImageTk.PhotoImage(imageR)
tk.Label(root, image=imageR).place(x=20, y=250)

```

<img width= 300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3898247/d942e96f-9e04-7f45-ea1d-c8d7cd2ec37b.png">

<img width= 300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3898247/240b8a2d-cf50-36f6-6ee6-82cf1d67d4b2.png">


（左）subsample（右）resizeを用いた画像サイズの調整
<br>
両者を比較すると、どちらも同程度の画像サイズで表示されていますが、その画質に差があることが分かります。subsample関数は、画像の解像度を縮小して新たなオブジェクトを生成する関数なので、もとの画像サイズより極端に小さくすると画質の粗さが目立ってしまうかもしれません。


---

### ②勝敗が決まった後でも他の手のボタンが機能し、後出しが可能になってしまう
<br>
<dl>
  <dt>解決策：勝敗が付いたら手のボタンが機能しないようにする</dt>
  <dd>じゃんけんで後出しができてしまうとゲームとして成り立ちませんね。勝ちか負けになったらリトライボタンを表示し、手のボタンは押すことができなくなるよう設定します。 </dd>
</dl>
<br>

```php
retryButton = tk.Button(root, text='リトライ', command=retry)

def showRetry():
    retryButton.place(x= 220, y=150)
    buttonR['state'] = 'disabled'
    buttonS['state'] = 'disabled'
    buttonP['state'] = 'disabled'
```
<br>
<img width=300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3898247/fc41fc02-14d1-2cbd-499c-9e63cf141c06.png">

retryボタンが押された際に、無効になったボタンをもとに戻します。
```php
def retry():
    retryButton.place_forget()
    buttonR['state'] = 'active'
    buttonS['state'] = 'active'
    buttonP['state'] = 'active'
    text_labal.configure(text='最初はグー！ じゃんけん！')

```
<img width= 300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3898247/e4278413-b41e-4c41-d416-887a927ef4ba.png">

# まとめ
今回はPythonでGUIアプリケーションを作成しました。
一つずつ基本知識を学びながら作成をしたので、個人的には苦戦した部分が多くありました。やはり解説記事や動画を見るだけでなく、実際に手を動かすことでPythonに関する知識や理解を深めることができたと感じます。

Pythonを勉強してみたいという方は、ぜひ今回の記事を参考にしてアプリケーション作成に挑戦してみてください。