このページでは、ソケット通信を利用した「じゃんけんアプリ」の作り方について解説していきます。
今回は一人プレイ用のじゃんけんアプリの作り方を解説していきます。要は「サーバー(コンピューター)とじゃんけんの対戦をするアプリ」となります。
このサイトでは、下記ページでソケット通信や Tkinter での GUI アプリの作り方について解説しています。せっかくなので、これらを組み合わせたアプリの開発の仕方について説明したいと考え、第1弾として一人プレイ用の「じゃんけんアプリ」の解説ページを作ってみました!
ソケット通信と聞くと難しそうだし地味なイメージがあるかと思いますが、ゲームと組み合わせて開発を行うことで楽しくソケット通信も学ぶことが可能です!是非このページで、ソケット通信や GUI アプリの簡単な作り方について学んでいっていただければと思います!
ただ、最後に紹介するスクリプトは Windows or Mac で実行することを想定したものになっていますので、この点はご注意いただければと思います。
Contents
開発する「じゃんけんアプリ」
最初に、このページで開発する「じゃんけんアプリ」の仕様について解説していきます。
前述のとおり、今回開発するのは「一人プレイ用のじゃんけんアプリ」になります。ソケット通信を利用し、クライアント・サーバーモデルを採用した構成のアプリとしたいと思います。さらに、クライアントとサーバーの間は TCP で通信を行います。もちろん、これらのサーバーとクライアントは異なる PC 上で動作させることも可能です。
プレイヤーの対戦相手となるのがサーバーで、このサーバーは常駐プログラムとし、クライアントプログラムが起動したときにクライアントプログラムと接続を確立します。その後、クライアントからのデータの受信を待ち続ける作りとします。さらに、クライアントから受信したデータはユーザーが選択した「じゃんけんの手」として扱い、サーバーがランダムに決めた手と、クライアントのじゃんけんの手からじゃんけんの結果を判断します。そして、サーバーが決めた手とじゃんけんの結果をクライアントに返却するという作りにしていきたいと思います。
それに対し、ユーザーが操作するのはクライアントプログラムとなり、このプログラムの GUI からじゃんけんの手をユーザーが選択できるようにしていきます。さらに、ユーザーがじゃんけんの手を選んだ後に、じゃんけんの結果を表示するようなクライアントを開発していきます。この GUI は Tkinter を利用して開発していきます。
クライアント
次は、クライアントとサーバーの内部の処理について考えていきましょう!
最初に、クライアント側で必要になる処理の流れを整理していきます。ここからは、特にソケット通信においては具体的なメソッド名を用いて説明していきます。各メソッドの詳細を知りたい方は別途下記ページを参照していただければと思います。
Pythonでのソケット通信(ポート番号・プロトコル・サーバー / クライアント)まず、クライアントはプログラム起動時にボタンを表示してユーザーからのじゃんけんの手の選択を受け付ける処理が必要となります。これは、Tkinter を利用すれば簡単に実現可能です。さらに、プログラム起動時にサーバーに接続要求を送信する必要があります。これは、socket
モジュールにおける socket
クラスの connect
メソッドにより実現できます。
さらに、ボタンがクリックされた際に、ユーザーが選択したじゃんけんの手に応じたデータをサーバーに socket
クラスの sendall
メソッドにより送信し、さらに recv
メソッドで「サーバーが選んだじゃんけんの手」と「じゃんけんの結果」をサーバーから受信します。
データを受信した際には、サーバーのじゃんけんの手と結果を表示します。これによって、ユーザーがじゃんけんの結果を確認することができるようになります。
もし、じゃんけんの結果が引き分け(あいこ)であれば、再度ユーザーからじゃんけんの手の選択待ちを行います。そして、ユーザーがじゃんけんの手を選択すれば、再度上記と同じ流れで処理を行うことになります。
以上がクライアントの基本的な処理の流れとなります。細かいことを言うと、ボタンの無効化や有効化等も必要になるのですが、このあたりに関しては実際のスクリプトを示す際に説明を補足していきたいと思います。
ボタンを表示したり、ボタンがクリックされた際にデータを送信したり、送受信するデータがじゃんけんの手やじゃんけんの結果を示すデータであったりはしますが、基本的に通信の流れに関しては下記ページの TCP 通信のクライアント で示したスクリプトと同様になります。
Pythonでのソケット通信(ポート番号・プロトコル・サーバー / クライアント)スポンサーリンク
サーバー
続いてサーバー側の処理の流れを説明していきます。
サーバーは、起動したらソケットの生成等の準備を行った後に socket
クラスの accept
メソッドを実行してクライアントからの接続要求の受信待ちを行います。サーバーがこの状態の間にクライアントが connect
メソッドで接続要求を送信すれば、サーバーとの間で接続が確立され、以降データの送受信を行うことが可能となります。
接続が確立されれば、次はクライアントからじゃんけんの手を示すデータが送信されてくるのを recv
メソッドで待ち続けます。クライアントからデータが送信されてくれば、サーバーのじゃんけんの手をランダムに決定し、さらに両者のじゃんけんの手からじゃんけんの結果を判断します。そして、サーバーが選んだ手とじゃんけんの結果を示すデータをクライアントに送信します。
じゃんけんの結果が引き分け以外の場合であれば、サーバーは確立した接続を切断し、また他のクライアントからの接続要求の受信待ちを行うために accept
メソッドを実行します。このように、クライアントとの一連の通信が終了した後に再び accept
メソッドを実行するようにループを組んでやればサーバーを常駐させた状態(ずっとプログラムが起動している状態)にしておくことができます。
じゃんけんの結果が引き分けの場合は、再度クライアントからのデータの受信を待つために recv
を実行し、以降は最初のじゃんけんを行った時と同様の処理を繰り返し行うことになります。
特に通信部分に着目すれば、上記に関しては TCP 通信を行うサーバーの典型的な処理の流れとなります。例えば、下記ページの TCP 通信のサーバー で紹介したスクリプトの大部分を流用し、じゃんけん用のカスタマイズ(じゃんけんの手やじゃんけんの結果を送受信したり、じゃんけんの結果を判断したりするように変更する)を行うだけで、じゃんけんアプリのサーバーは作成可能です。
Pythonでのソケット通信(ポート番号・プロトコル・サーバー / クライアント)クライアント・サーバー間の通信
次に、クライアントとサーバー間で行う通信についてまとめておきます。
ここで示すものが、いわゆるアプリケーション層のプロトコルと呼ばれるものになります。私が独自で定義したプロトコルですが、これらに従って通信が行われるようにクライアントとサーバーを開発することで、じゃんけんを成立させるための通信を実現することができるようになります。
通信シーケンス
クライアントとサーバー間で行う通信の流れは、シーケンス図で表せば下記のようになります。
見ていただければ分かるように、通信の流れは非常に単純です。あえて難しい点を挙げるのであればじゃんけんの結果が引き分けの時に、接続を確立したままじゃんけんを続けるように制御する必要がある点になると思います。下記においてはループを行うことで、その制御を実現しています。
通信データ
次に、クライアントサーバー間で送受信するデータについてまとめておきたいと思います。
クライアントからは「じゃんけんの手」を示すデータをサーバーに送信する必要があります。この送信するデータは下記のように定義したいと思います。
じゃんけんの手 | 送信するデータ |
グー | 'stone' |
チョキ | 'scissors' |
パー | 'paper' |
また、サーバーからクライアントに対しては「じゃんけんの手」と「じゃんけんの結果」を示すデータを送信する必要があります。じゃんけんの手は上記で定義した通りで良いとして、じゃんけんの結果を示すデータに関しては下記のように定義したいと思います。下記からも分かるように、この「じゃんけんの結果」はクライアント(ユーザー)に対する結果となります。
じゃんけんの結果 | 送信するデータ |
クライアントの勝ち | 'win' |
クライアントの負け | 'lose' |
引き分け | 'even' |
さらに、サーバーからクライアントに対して「じゃんけんの手」と「じゃんけんの結果」を示すデータを送信する際には、下記のように2つを :
区切りで結合するフォーマットでデータを送信することにしたいと思います。
'じゃんけんの手:じゃんけんの結果'
例えば、サーバーの選んだじゃんけんの手がグーで、さらにじゃんけんの結果が引き分けの場合は、サーバーはクライアントに対して下記のようなデータを送信することとします。
'stone:even'
このように定義しておけば、クライアントはサーバーから受信したデータを :
を区切り文字として分割することで、サーバーが選んだじゃんけんの手とじゃんけんの結果それぞれを得ることができます。
ただ、ソケット通信で送受信可能なデータはバイト型のデータのみとなる点に注意してください。ここでは文字列として送受信するデータを定義していますので、実際には送信側は上記で定義した文字列をエンコードしてから送信する必要があり、受信側は受信したデータをデコードして文字列に変換して扱う必要があります。
GUI
あとは、クライアント側の GUI について説明しておきたいと思います。この GUI は下記のような構成のものとしたいと思います。
ユーザーは stone_button
or scissors_button
or paper_button
をクリックすることでじゃんけんの手を選択することができます。そして、相手のじゃんけんの手(サーバーが選んだじゃんけんの手)が server_hand_message
ラベルに表示され、さらにじゃんけんの結果やユーザーに伝えたいメッセージが game_message
ラベルに表示されるようにします。
また、ボタンに関しては、サーバーにデータを送信するタイミングで無効化することで、ユーザーから連続でボタンが押されることを防ぐようにします。また、じゃんけんの結果が引き分けの場合はボタンを有効化し、ユーザーが再度じゃんけんの手を選択できるようにします。
非常にシンプルな GUI になっていますし、質素な見た目になっていますが、例えばボタンにじゃんけんの手を表す画像を表示したり、ラベルの色を結果に応じて変化させたりするとリッチな見た目な GUI に仕立てることも可能です。今回はそこまで GUI にはこだわりませんが…。
ちなみに、Tkinter で扱うボタンやラベルについては下記ページで解説していますので、詳細を知りたい方は下記ページを参照していただければと思います。
Tkinterの使い方:ボタンウィジェット(Button)の使い方 Tkinterの使い方:ラベルウィジェット(Label)の使い方また、ボタンやラベル等のウィジェットの配置方法については下記ページで解説していますので、こちらに関しても詳細を知りたい方は下記ページを参照していただければと思います。今回は使用するのは grid
メソッドのみとなります。
スポンサーリンク
じゃんけんアプリのサンプルスクリプト
大体どんなアプリを開発しようとしているかがイメージできたでしょうか?
次に、じゃんけんアプリのサンプルスクリプトを紹介していきたいと思います。
ここまで説明してきたように、今回紹介するアプリはクライアントサーバーモデルとしており、サーバーとクライアントのスクリプトの2つを紹介していきます。
サーバーのサンプルスクリプト
まず紹介するのがサーバーのサンプルスクリプトになります。
スクリプト
サーバーのサンプルスクリプトは下記となります。以降、このスクリプトのファイル名を server.py
として解説を進めていきます。
import socket
import random
# じゃんけんの手を示すデータのリスト
hands = ['stone', 'scissors', 'paper']
def judge(client_hand, server_hand):
# じゃんけんの結果を判断
if client_hand == server_hand:
return 'even'
elif client_hand == 'stone' and server_hand == 'scissors':
return 'win'
elif client_hand == 'scissors' and server_hand == 'paper':
return 'win'
elif client_hand == 'paper' and server_hand == 'stone':
return 'win'
else:
return 'lose'
# ソケットを生成してバインド・リッスン
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', 40001))
sock.listen(5)
# 無限ループで常駐させる
while True:
# 接続要求の受付 / 接続の確立
e_sock, addr = sock.accept()
# 結果が引き分けの間じゃんけんを継続
result = 'even'
while result == 'even':
# クライアントからじゃんけんの手を受信
recv_data = e_sock.recv(1024)
client_hand = recv_data.decode()
# サーバーのじゃんけんの手をランダムに決定
server_hand = random.choice(hands)
# じゃんけんの結果を判断
result = judge(client_hand, server_hand)
# サーバーの手と結果を結合して送信
hand_result = f'{server_hand}:{result}'
send_data = hand_result.encode()
e_sock.sendall(send_data)
e_sock.close()
sock.close()
スクリプトの説明
上記で示した server.py
の説明を行っておきます。
ただ、この server.py
に関しては、送受信するデータやデータ受信後に行う処理がじゃんけんに特化したものになっているくらいで、TCP 通信を行う基本的なサーバーそのものになっています。で、この TCP 通信を行う基本的なサーバーの作り方は下記ページで解説していますので、これに関して詳しく知りたい方は下記ページを参照していただければと思います。
あえて server.py
の注意点を挙げるとすれば、じゃんけんの結果が引き分けの間はクライアントとの通信を継続する必要があるという点になります。そのため、while result == 'even'
で結果が引き分けの間ループするようにスクリプトを作成しています。さらに、サーバーの場合は常駐させておく(常に起動させておいてクライアントからの接続を待ち続ける)という作りにすることが多く、今回もサーバーを常駐させるため、while result == 'even'
の外側でさらに while True
で無限ループするようにしています。つまり、2重のループを組む必要があり、この点が、じゃんけんアプリにおけるサーバー開発におけるややこしい点になるかなぁと思います。
クライアントのサンプルスクリプト
続いてクライアント側のサンプルスクリプトを紹介していきます。
スクリプト
クライアントのサンプルスクリプトは下記となります。以降、このスクリプトのファイル名を client.py
として解説を進めていきます。この client.py
では connect
メソッド実行時に引数に '127.0.0.1'
を指定しているので同じ PC 上で動作するサーバーと通信を行うことになります。他の PC 上で動作するサーバーと通信を行うようにしたいのであれば、'127.0.0.1'
の部分を、その PC の IP アドレスに変更してください。
import socket
import tkinter
# 文字のサイズ調整用
font = ('', 40)
class Janken:
def __init__(self, master, sock):
'''初期化を行う'''
self.master = master
self.create_widgets()
self.sock = sock
def create_widgets(self):
'''ウィジェットの作成と配置を行う'''
# グー選択用のボタン
self.stone_button = tkinter.Button(
self.master,
text='グー',
width=10,
font=font,
command=self.choice_stone
)
self.stone_button.grid(row=0, column=0, padx=10, pady=10)
# チョキ選択用のボタン
self.scissors_button = tkinter.Button(
self.master,
text='チョキ',
width=10,
font=font,
command=self.choice_scissors
)
self.scissors_button.grid(row=0, column=1, padx=10, pady=10)
# パー選択用のボタン
self.paper_button = tkinter.Button(
self.master,
text='パー',
width=10,
font=font,
command=self.choice_paper
)
self.paper_button.grid(row=0, column=2, padx=10, pady=10)
# 相手の手を表示するためのラベル
self.server_hand_message = tkinter.Label(
self.master,
text='相手の手:???',
font=font
)
self.server_hand_message.grid(row=1, column=0, columnspan=3, padx=10, pady=10)
# ゲームの説明を表示するためのラベル
self.game_message = tkinter.Label(
self.master,
text='じゃんけんの手を選んでください',
font=font
)
self.game_message.grid(row=2, column=0, columnspan=3, padx=10, pady=10)
def start(self, hand):
'''サーバーとじゃんけんを行う'''
# 連打防止のためボタンを無効化
self.stone_button.config(state=tkinter.DISABLED)
self.scissors_button.config(state=tkinter.DISABLED)
self.paper_button.config(state=tkinter.DISABLED)
# ユーザーが選択した手を示すデータを送信
send_data = hand.encode()
self.sock.sendall(send_data)
# サーバーからサーバーの手とじゃんけんの結果を受信
recv_data = self.sock.recv(1024)
server_hand_result = recv_data.decode()
# サーバーの手とじゃんけんの結果を分離
server_hand = server_hand_result.split(':')[0]
result = server_hand_result.split(':')[1]
# 相手の手をラベルに表示
server_hand_str = {'stone': 'グー','scissors': 'チョキ', 'paper': 'パー'}[server_hand]
self.server_hand_message.config(text=f'相手の手:{server_hand_str}')
if result == 'even':
# 再度じゃんけんの手が選択できるようにボタンを有効化
self.stone_button.config(state=tkinter.NORMAL)
self.scissors_button.config(state=tkinter.NORMAL)
self.paper_button.config(state=tkinter.NORMAL)
# 再度じゃんけんの手を選ぶ必要があることを表示
self.game_message.config(text='再度じゃんけんの手を選んでください')
elif result == 'win':
# ユーザーの勝ちであることをを表示
self.game_message.config(text='あなたの勝ちです!')
# ゲームは終了したのでソケットはクローズ
self.sock.close()
else:
# ユーザーの負負けあることを表示
self.game_message.config(text='あなたの負けです...')
# ゲームは終了したのでソケットはクローズ
self.sock.close()
def choice_stone(self):
self.start('stone')
def choice_scissors(self):
self.start('scissors')
def choice_paper(self):
self.start('paper')
# ソケットを生成してサーバーに接続要求を送信
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 40001))
# GUIの表示
master = tkinter.Tk()
app = Janken(master, sock)
# メインループでイベント待ち
master.mainloop()
スクリプトの説明
クライアントに関しては、サーバー側に比べて難しそうに見えるかもしれませんが、こちらも単純なスクリプトになっています。
このスクリプトで行っていることは大きく分けて下記の4つのみです。
- ①:サーバーとの接続の確立(起動時)
- ②:各種ウィジェットの作成と配置
- ③:サーバーとの通信(ボタンクリック時)
- ④:ボタンの無効化 / 有効化
まず、上記の client.py
では最初にソケットを生成し、connect
でサーバーに対して接続要求を送信するようになっています。これが①に該当します。
そして、その後、Janken
クラスのコンストラクタを実行して Janken
クラスのインスタンスの生成を行っています。このコンストラクタの実行によって Janken
の __init__
が実行され、さらに __init__
から create_widgets
が実行され、各種ボタンやラベルの作成及び配置が行われるようになっています。これが②に該当する処理となります。
ここで作成されるボタン・ラベルは、GUI の節で示した下図のとおりに配置されており、下の図の各種ウィジェットの名前は Janken
クラスのインスタンスのデータ属性と一致させているので、下の図と create_widgets
メソッドの処理とを見比べてみると、大体 create_widgets
で何をやっているかが理解していただけるのではないかと思います。
ボタンに関しては tkinter.Button
を実行することで生成できるのですが、ここでポイントになるのが command
引数になります。この command
引数に関数やメソッドを指定しておけば、そのボタンがクリックされた時に command
に指定した関数 or メソッドが自動的に実行されるようになります。
なので、グー
ボタンをクリックすれば choice_stone
、チョキ
ボタンをクリックすれば choice_scissors
、パー ボタンをクリックすれば choice_paper
が実行されることになります。そして、これらのメソッドでは、選択されたじゃんけんの手を示すデータ(通信データ で定義したデータ)を引数 hand
に指定して start
メソッドを実行するようになっています。そして、この start
メソッドで③に該当する処理を行っています。
具体的には、この start
メソッドでは引数 hand
をサーバーに送信し、サーバーから受信したデータからサーバーのじゃんけんの手とじゃんけんの結果を取得し、その結果に応じて server_hand_message
と game_message
ラベルの表示内容の変更を行なっています。ラベルの表示内容の変更は、ラベルウイジェットに text
引数を指定して config
メソッドを実行させることで実現可能です。
また、start
メソッドの先頭では各種ボタンの無効化を行っており、じゃんけんの結果が引き分けの場合のみ、再びボタンの有効化を行うようにしています。ここの処理が④に該当します。これによって、引き分けの場合には再度ユーザーがじゃんけんの手の選択を行えるようになります。つまり、ユーザーがゲームを継続できるようになります。ボタンの状態(有効 / 無効)の変更は、ボタンウイジェットに state
引数を指定して config
メソッドを実行させることで実現可能です。
こんな感じで、処理を分解してみると大したことは1つも行っておらず、実は単純なスクリプトになっています。
スポンサーリンク
動作確認
最後に、ここまで説明およびスクリプトの紹介を行ってきた「じゃんけんアプリ」の動作確認を行っていきます!
まず、server.py
と client.py
を同じフォルダ内に保存し、さらにターミナルやコマンドプロンプト・PowerShell 等の CLI アプリを2つ起動し、両方の CLI アプリでスクリプトを保存したフォルダに移動してください。
続いて、一方の CLI アプリで下記を実行してください。これにより、じゃんけんアプリのサーバーが起動することになります
python server.py
次に、他方の CLI アプリで下記を実行してください。
python client.py
これにより、まずクライアントとサーバーの間で接続が確立されたのち、下の図のような GUI ウィンドウが起動することになります。ちなみに、下の図のは Mac で client.py
を実行した場合に起動するウィンドウで、WIndows の場合は多少見た目が異なると思います。
あとは、リアルでじゃんけんを行うとき同様、好きな手を選んでボタンをクリックしてください。
すると、下の図のように、サーバーが選んだ手と結果が画面に表示されるはずです。引き分け以外の場合はこれでゲーム終了です。ボタンは無効化されているのでクリック不可となっていることも確認できると思います。
引き分けの場合は、ボタンが再度有効化されているため、もう一度ボタンをクリックしてゲームをプレイしてください。
引き分け以外の結果になれば、ボタンが無効化された状態がキープされゲームがプレイできなくなることが確認できるはずです。
ゲームが終了したら、クライアントのウィンドウの閉じるボタンをクリックして終了させてください。もう一度じゃんけんがプレイしたいのであれば、再度 client.py
を実行してやれば良いです。サーバーが起動している間は、ゲームが終了しても再度クライアントを起動しなおすことでゲームを再プレイすることができるようになっています。
サーバー側を終了させたい場合は、server.py
を実行している方の CLI アプリで control
+ c
を入力して強制終了させてください。
今回はサーバーとクライアントを同じ PC 上で動作させましたが、別に他の PC 上で動作させることも可能で、その場合は client.py
の connect
の引数に指定している 127.0.0.1
の部分を、サーバーを起動している PC の IP アドレスに変更してください。これにより、他の PC 上で動作しているサーバーとの間で通信を行ってじゃんけんをプレイすることができるようになります。
サーバーの並列処理の重要性
最後に、これは余談になるのですが、もう1つ試しておきたいことがあります。これを試すとサーバーでの並列処理の重要性が理解していただけると思います。
今度は CLI アプリを3つ起動し、1つのアプリで下記コマンドを実行してサーバーを起動し、
python server.py
さらにサーバーを起動していない2つのアプリで下記コマンドを実行してクライアントを2つ起動してください。
python client.py
クライアントを2つ起動することで、2つの GUI ウィンドウが起動すると思います。ここで、”後から起動した方のウィンドウ” で、いずれかのボタンをクリックしてみてください。クリックしてみれば分かると思いますが、この場合はサーバーが選んだ手やじゃんけんの結果が表示されないはずです。
次は、最初に起動した方の GUI ウィンドウのボタンをクリックしてみてください。この場合は、サーバーが選んだ手やじゃんけんの結果がすぐに表示されるはずです。と同時に、最初にボタンをクリックした方の GUI にもサーバーが選んだ手やじゃんけんの結果が表示されることが確認できるはずです。
なぜ、こんなことになるのでしょうか?この原因が気になるという方は是非下記ページを読んでみてください!
現状のサーバーは、1つのクライアントのみと通信を行ってじゃんけんを実現する作りになっていますが、下記ページで紹介しているマルチプロセッシングを導入すれば、複数のクライアントと同時に通信を行い、複数のユーザーが同時にじゃんけんをプレイすることができるようになります。複数のクライアントと同時に通信を行うことが必要となる場面も結構ありますし、ソケット通信に関係なくマルチプロセッシングの知識があると開発できるプログラムの幅が広がりますので、このあたりの知識も身につけておくと良いと思います!
【Python / ソケット通信】マルチプロセッシングでサーバーの処理を並列化まとめ
このページでは、ソケット通信と GUI を利用した「じゃんけんアプリ」の作り方について解説しました!
割と単純な作りのアプリになりますが、それでもソケット通信と GUI の両方を学ぶことができますし、さらにゲームなので楽しく開発しながらプログラミングを学べるという点も、このアプリを開発するメリットになると思います。
今回は、まずはソケット通信や GUI の入門編として一人プレイ用のじゃんけんアプリの作り方について解説しましたが、下記ページでは2人プレイ可能なじゃんけんアプリの作り方について解説しています。ソケット通信の使い方等の難易度は上がりますが、実際に作ってみると学べることも多いと思いますので是非下記ページも読んでみてください!
【Python】2人プレイ用じゃんけんアプリの作り方