質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

3733閲覧

Pythonで実装しているリバーシプログラムの非同期処理を実現したい

退会済みユーザー

退会済みユーザー

総合スコア0

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

0クリップ

投稿2018/03/20 08:55

前提・実現したいこと

Python3.6.4でリバーシのプログラムを作成しています。GUI部分はtkinterで実装し、マウスの左クリックで石を打てるようにしています。
後々重い処理を行うAIの作成を考えているため、AIの思考部分(?)を非同期で処理を行えるような設計にしておきたいのですが、
コードの書き方が分からず困っております。

該当のソースコード

Python

1#!/usr/bin/python3 2# -*- coding: utf8 -*- 3import tkinter as tk 4import sys 5import random 6 7 8class PlayerObject(object): 9 10 myTurn = None 11 def __init__(self, myTurn): 12 self.myTurn = myTurn 13 14 def placeDisc(self, board, global_turn, b_index,): 15 16 list_canplace = None 17 18 # 自分のターンの時、着手可能場所を取得 19 if(self.myTurn == global_turn): 20 list_canplace = board.getCanPlace(self.myTurn) 21 22 # 自分のターンじゃないときは何もしない 23 if(self.myTurn != global_turn or b_index is None): 24 return "None" 25 26 # 着手できる場所が無いときはパス 27 if(len(list_canplace) == 0): 28 return "Pass" 29 30 # クリックした場所に着手可能なとき 31 if(board.discs[b_index].type == "CanPlace"): 32 board.resetNewDisc() 33 board.reverseDisc(self.myTurn, b_index) 34 return "Done" 35 36 # クリックした場所に着手できないとき 37 elif(board.discs[b_index].type != "CanPlace"): 38 return "None" 39 40 41class Player(PlayerObject): 42 43 def __init__(self, myTurn): 44 super().__init__(myTurn) 45 46 47class AI(PlayerObject): 48 49 def __init__(self, myTurn): 50 super().__init__(myTurn) 51 52 def placeDisc(self, board, global_turn, b_index,): 53 # 自分のターンじゃないときは何もしない 54 if(self.myTurn != global_turn): 55 return "None" 56 57 list_canplace = board.getCanPlace(self.myTurn) 58 # 着手できる場所が無いときはパス 59 if(len(list_canplace) == 0): 60 return "Pass" 61 62 # 着手可能な場所からランダムに選択して打つ 63 random.shuffle(list_canplace) # 着手可能場所のリストをランダムにシャッフル 64 board.resetNewDisc() 65 board.reverseDisc(self.myTurn, list_canplace[0]) 66 return "Done" 67 68 69class GameManager(object): 70 71 root = None 72 frame = None 73 canvas = None 74 Board = None 75 Player1 = None 76 Player2 = None 77 Turn = None 78 clicked_board_index = None 79 pass_count = None 80 81 def __init__(self, root, Board, Player1, Player2): 82 self.root = root 83 self.frame = tk.Frame(width=960, height=720) 84 self.frame.place(x=0, y=0) 85 self.canvas = tk.Canvas(self.frame, width=720, height=720) 86 self.canvas.place(x=0, y=0) 87 88 self.Board = Board 89 self.Player1 = Player1 90 self.Player2 = Player2 91 92 self.Turn = "Black" 93 self.pass_count = 0 94 95 # 左クリック時にコールバック関数self.clickを呼び出す 96 self.root.bind("<Button-1>", self.click) 97 98 # ボードの着手可能場所を全て"空き"にする 99 def clean_board(self, ): 100 for index, disc in enumerate(self.Board.discs): 101 if(disc.type == "CanPlace"): 102 self.Board.discs[index].type = "Space" 103 104 def click(self, mouse): 105 106 # clicked out of the board 107 if(mouse.x > 720): 108 return 109 110 index = int(mouse.y / 90 + 1) * 10 + int(mouse.x / 90 + 1) 111 self.clicked_board_index = index 112 113 def draw(self, ): 114 115 self.canvas.delete("board") # ボードを消す 116 self.canvas.delete("disc") # ボード上の石を消す 117 118 # ボードを描画 119 self.canvas.create_rectangle(0, 0, 720, 720, fill='#1E824C', tag="board") 120 121 # ボードのマスを描画 122 for i in range(9): 123 self.canvas.create_line( 124 i * 90, 0, i * 90, 720, width=1.2, fill="Black", tag="board") 125 self.canvas.create_line( 126 0, i * 90, 720, i * 90, width=1.2, fill="Black", tag="board") 127 128 # ボードの丸印を描画 129 self.canvas.create_oval(180 - 4, 180 - 4, 180 + 4, 180 + 4, fill="Black", outline="Black", tag="board") 130 self.canvas.create_oval(180 - 4, 540 - 4, 180 + 4, 540 + 4, fill="Black", outline="Black", tag="board") 131 self.canvas.create_oval(540 - 4, 180 - 4, 540 + 4, 180 + 4, fill="Black", outline="Black", tag="board") 132 self.canvas.create_oval(540 - 4, 540 - 4, 540 + 4, 540 + 4, fill="Black", outline="Black", tag="board") 133 134 # 石を描画 135 for index, disc in enumerate(self.Board.discs): 136 center_x = 45 + int((index-1) % 10) * 90 137 center_y = 45 + int((index-10) / 10) * 90 138 if(disc.type == "Black"): 139 self.canvas.create_oval(center_x - 43, center_y - 43, center_x + 43, center_y + 43, fill="Black", outline="Black", tag="disc") 140 elif(disc.type == "White"): 141 self.canvas.create_oval(center_x - 44, center_y - 44, center_x + 44, center_y + 44, fill="White", outline="Black", tag="disc") 142 elif(disc.type == "CanPlace"): 143 self.canvas.create_oval(center_x - 5, center_y - 5, center_x + 5, center_y + 5, fill="OliveDrab1", outline="OliveDrab1", tag="disc") 144 # 最後に打たれた石の場合、マークを付ける 145 if(disc.newest_place): 146 self.canvas.create_oval(center_x - 8, center_y - 8, center_x + 8, center_y + 8, fill="Red", outline="Red", tag="disc") 147 148 self.canvas.pack() 149 150 # コンソール出力テスト 151 ''' 152 print(len(self.Board.discs)) 153 for index, disc in enumerate(self.Board.discs): 154 str = " " if index % 10 != 9 else '\n' 155 if(disc.type == "Ban"): 156 print("@" + str, end="") 157 elif(disc.type == "Space"): 158 print("*" + str, end="") 159 elif(disc.type == "Black"): 160 print("o" + str, end="") 161 elif(disc.type == "White"): 162 print("x" + str, end="") 163 else: 164 print("?" + str, end="") 165 ''' 166 167 def play(self, ): 168 169 p1_status = self.Player1.placeDisc(self.Board, self.Turn, self.clicked_board_index) 170 p2_status = self.Player2.placeDisc(self.Board, self.Turn, self.clicked_board_index) 171 172 if(p1_status == "Pass"): 173 self.pass_count += 1 174 self.Turn = "Black" if (self.Turn == "White") else "White" 175 elif(p1_status == "Done"): 176 self.pass_count = 0 177 self.Turn = "Black" if (self.Turn == "White") else "White" 178 179 if(p2_status == "Pass"): 180 self.pass_count += 1 181 self.Turn = "Black" if (self.Turn == "White") else "White" 182 elif(p2_status == "Done"): 183 self.pass_count = 0 184 self.Turn = "Black" if (self.Turn == "White") else "White" 185 186 self.draw() 187 188 if(self.pass_count < 2): 189 self.root.after(10, self.play) 190 191 192def main(): 193 194 Player1 = None # プレーヤー1 195 Player2 = None # プレーヤー2 196 197 args = sys.argv # コマンドライン引数 198 if(len(args) >= 3): 199 Player1 = Player("Black") if args[1] == '-player' else AI("Black") if args[1] == '-ai' else None 200 Player2 = Player("White") if args[2] == '-player' else AI("White") if args[2] == '-ai' else None 201 elif(len(args) == 2): 202 Player1 = AI("Black") if args[1] == '-training' else None 203 Player2 = AI("White") if args[1] == '-training' else None 204 if(Player1 is None or Player2 is None): 205 args = [] 206 207 if(len(args) < 2): 208 print("error!!") 209 print("If you want to play Player vs Player mode, execute below command") 210 print(" $python main.py -player -player") 211 print("") 212 print("If you want to play Player vs AI mode, execute below command") 213 print(" $python main.py -player -ai") 214 print("") 215 print("If you want to play AI vs AI mode, execute below command") 216 print(" $python main.py -ai -ai") 217 print("") 218 print("If you want to play AI training mode, execute below command") 219 print(" $python main.py -training") 220 print("") 221 sys.exit() 222 223 root = tk.Tk() # rootウィンドウを作成 224 root.title("VimRev") # rootウィンドウのタイトルを変える 225 root.geometry("960x720") # rootウィンドウの大きさを960x720に 226 root.resizable(0, 0) # 縦、横共に画面サイズの変更を禁止 227 228 board = Board() # ボードクラス 229 gameManager = GameManager(root, board, Player1, Player2) # ゲームマネージャークラス 230 gameManager.play() 231 232 root.mainloop() # メインループ 233 234 235if __name__ == '__main__': 236 main() 237

試したこと

キューやパイプを用いてプロセス間でデータのやり取りを行う方法など調べ、見様見真似で実装してみたのですが、スレッドやプロセスなどについて詳しくないため実装が困難でした。

現状ですと、例えばAIクラス中のplaceDisc関数の最初の方で10000回ループを回すと処理が奪われてしまい、その間マウスのクリックやウィンドウ移動などが出来なくなってしまいます。

やりたいこととしてはAI部分は別プロセス(?)で処理させておき、処理が終わったタイミングで結果を反映するといったことです。
AIの処理が行われている間も、マウスのクリックや他のイベント処理は呼び出せるようにしたいです。

もし宜しければ、Pythonでこのような処理を実現するために、どういった内容(プロセスやスレッド、通信?)を勉強すればいいかということも併せて教えていただければ幸いです。

プログラム全体の設計などにつきましても、今より良い方法やアドバイス等がありましたらご教示頂けると有難いです。

以上、宜しくお願い致します。

補足情報(FW/ツールのバージョンなど)

プログラムはPython3.6.4で実装しております。
このソースコードを公開している私のGithubのページ

Player vs Playerを行うには以下のコマンドを実行してください。

$Python source.py -player -player

Player vs AIでは以下のコマンドを実行してください。
(-player, -aiは先に書いた方が先手となります。)

$Python source.py -player -ai
tachikoma👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tachikoma

2018/03/20 09:10

非同期だと、aiの計算が終わるのを待たずにゲームを進める、、、ということですか?
退会済みユーザー

退会済みユーザー

2018/03/20 09:20

連絡有難う御座います。今の実装のままだと、AIが計算を行っている間はマウスのクリックやウィンドウの移動などのイベント処理ができず、固まってしまいます。そのためゲーム自体を進めるのでは無く、AIの計算は非同期で行わせておき、その間もマウス入力などを受け付けるようにしたいです。
mkgrei

2018/03/20 09:43

処理しているからといって、tkinterはマウスをキャプチャするのですか?
退会済みユーザー

退会済みユーザー

2018/03/20 09:50

連絡有難う御座います。self.root.bind("<Button-1>", self.click)という一行をGameManagerクラスのコンストラクタに書いているのですが、ウィンドウ内を左クリックした時にそのx, y座標がself.click関数に渡されます。今後、それらをAIの処理中も使用したいと考えております。また、現状のままですと、ウィンドウを移動する時なども固まってしまうため非同期の処理を行いたいと考えております。
guest

回答2

0

非同期で処理を行いたいとのことなので、
asyncio17.4. concurrent.futures – 並列タスク実行が使えるかと。

Webサーバー(tornado/django)を裏で立ち上げて、JSONで処理を投げるという手もあります。


GitHubを見ました。
3点ほど。
0. コマンドライン引数の処理はargparse
0. Diseクラスのtypeにenum型
0. GameManagerクラスのコンソール出力テストはBoardクラスの__str__関数をオーバーライドして、pprintで出力すると楽です。

投稿2018/03/20 10:24

編集2018/03/20 10:27
umyu

総合スコア5846

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/03/20 12:02

Githubを拝見いただきありがとうございます。 argparseやpprintの存在を知りませんでした。 スッキリしたコードに書き直してみようと思います。 webサーバーは私にとってはかなり難易度が高いですが、勉強して頑張ってみようと思います。 JSONでデータをやり取りできるのはかなり便利ですね。皆様の意見を聞いて検討しようと思います。
guest

0

ベストアンサー

確かめてみました。
tkinterのウィンドーに関わることはレインボーが回るんですね。

非同期処理ということですが、multiprocessingやthreadingを使うと、クラスをシリアライズするところで苦労します。


一番簡単なのは別プロセスで走らせることです。

ソケットか何かを通して、盤面データと打ち手をやり取りします。

この時にゲームAIをプロセスとして維持するセッション系、にするか
番が回ってくるたびにプロセスを走らせるステートレス系、にするか決める必要があります。

後者のほうが実装が簡単で、前者は相手が考えている間評価し続けることができるので強くなります。

socketやsubprocessを検討するとよいと思います。

投稿2018/03/20 09:54

mkgrei

総合スコア8560

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/03/20 10:13

回答有難うございます。調べたところ、難しそうですがソケット通信を実装すれば私のやりたいことが実現できそうです。 「python セッション」や「python ステートレス」で検索してもあまりそれらしい記事がヒットしなかったのですが、subprocessでコマンドを実行するのはステートレス系という認識で合っているでしょうか?
mkgrei

2018/03/20 10:27

セッションやステートレスはWebサイトを作る時によく出てくる概念です。 ここで言っている意味としては、 セッションの場合ずっと手を探索していて、自分の番になるたびにそれまでの探索でわかった情報を踏まえつつ最善手を打ちます。 ステートレスの場合、自分の番だよと知らされて初めて盤面を確認して最善と思われる手を打ちます。 --- subprocessの使い方次第でどちらにもできます。 先程コードを読んでみましたが、 見通しを良くするために、 画面⇔ドライバ⇔プレーヤー に分解する必要があるように思います。 --- どのような方法であれThreadingを使うことから逃れられないのですね… https://medium.com/swlh/lets-write-a-chat-app-in-python-f6783a9ac170 ソケットにものが来ていないか逐一確認しながらもブロックしない画面プロセス 全体のルールの整合性を管理しながら、画面を更新する情報を送ったり、プレーヤーからの指示を聞き入れたりするドライバプロセス ゲームを遊ぶだけのプレーヤープロセス を別々に実装する必要があるように思います。
退会済みユーザー

退会済みユーザー

2018/03/20 12:13

ご説明有難うございます。だんだんと実装のイメージが湧いてきました。 今後のことを考えると、セッションを頑張って実装した方がより強いAIを作成できそうですね。 私も設計方法に少し違和感を感じながらコーディングを行っていました。 一度、mkgrei様のような設計方法で、ソケットを使って一から作り直してみたいと思います。 ご回答、丁寧な説明有難うございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問