tkinterを使って三目並べを作ってみました。
完成したものはこんな感じです。
これの作り方の核となる部分を書いてみます。
メインウィンドウを作る
はじめに、メインウィンドウ作ります。
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super(App, self).__init__()
self.title("三目並べ")
self.geometry("{}x{}+{}+{}".format(360, 400, 450, 100))
def run(self):
self.mainloop()
if __name__ == "__main__":
app = App()
app.run()
このプログラムを実行すると、下のウィンドウが立ち上がります。
このウィンドウに盤面とボタンを貼り付けていきます。

前準備
def set_variables(self):
self.result = 0
self.playerTurn = 1
self.board2info = [0] * 9
self.symbol = " ox"
self.alpstr = "abcdefghi"
self.winner = ["", "あなた", "引き分け", "CPU"]
self.playerTurnが1ならユーザのターン、-1ならCPUのターン。
self.board2infoには盤面の情報が入る。0は空白、1は〇、-1は✖。
盤面の作成
Canvasを使って盤面を作成します。
長方形のCanvasのオブジェクトを作った後に、create_rectangleメソッドで3x3の9個の正方形を作成し、Canvasのオブジェクト上に作っていきます。
def set_board(self):
self.board = tk.Canvas(self, bg="white", width=340, height=340)
self.tag2pos = {}
position = [(20, 20, 120, 120), (120, 20, 220, 120), (220, 20, 320, 120),
(20, 120, 120, 220), (120, 120, 220, 220), (220, 120, 320, 220),
(20, 220, 120, 320), (120, 220, 220, 320), (220, 220, 320, 320)]
for tag, pos in zip(self.alpstr, position):
self.tag2pos[tag] = pos[:2]
self.board.create_rectangle(*pos, fill='green yellow', outline='green yellow', tags=tag)
self.board.tag_bind(tag, "<ButtonPress-1>", self.pressed)
for x in range(120, 320, 100):
self.board.create_line(x, 20, x, 320)
for y in range(120, 320, 100):
self.board.create_line(20, y, 320, y)
self.board.place(x=10, y=0)
tag_bindを使ってバインディングの設定をします。先ほど正方形を作るときにtagの設定をしていました。
このtagは作成した正方形を識別するために必要なものです。
今回は以下のようにtagの設定をしました。
これらの正方形にtag_bindを使って、9個の正方形と左クリックを紐つけています。
こうすることにより、正方形が左クリックされたとき、self.pressedメソッドが呼ばれるようになります。
ゲームの流れ
人間とCPUが交互に〇と✖を書き込んでいくように実装しました。
プログラムを動かすと、ウィンドウが立ち上がりすでにゲームができる状態です。
この状態で盤面のマスをクリックをすると、〇がクリックしたマスに書き込まれます。
この処理はself.pressedメソッドで行われます。
def pressed(self, event):
if self.thread.keylock:
return
item_id = self.board.find_closest(event.x, event.y)
tag = self.board.gettags(item_id[0])[0]
action = self.alpstr.index(tag)
state = self.board2info[action]
if state in [-1, 1]:
return
self.update_board(action, tag)
if self.result:
self.thread.keylock = 1
return
self.thread()
盤面がクリックされると、self.pressedメソッドが呼ばれます。
はじめのself.thread.keylockの役割はユーザがマスをクリックできるかを判定しています。CPUのターンやゲームが終了したときにクリックできないようにしています。
次に、どこがクリックされたのかを見ています。クリックされたところにすでに〇か✖が書き込まれていた場合、何もしません。
クリックされたところが空白ならば盤面の更新をします。
def update_board(self, action, tag):
self.board2info[action] = self.playerTurn
self.draw_symbol(tag)
self.check_result()
self.playerTurn = -self.playerTurn
ここでは盤面の情報を更新した後、記号をCanvas上に描いてゲームの決着がついたかを見ています。
import threading
import time
import random
import tkinter as tk
class Thread(threading.Thread):
def __init__(self):
super(Thread, self).__init__()
self.is_running = True
self.thlock = 1
self.keylock = 0
self.parent = None
self.start()
def __call__(self):
self.keylock = 1
self.unlock()
def set_parent(self, parent):
self.parent = parent
def lock(self):
self.thlock = 1
def unlock(self):
self.thlock = 0
def is_lock(self):
return self.thlock
def random_choice(self):
time.sleep(0.5)
i = random.choice([i for i, v in enumerate(self.parent.board2info) if v == 0])
tag = self.parent.alpstr[i]
self.parent.update_board(i, tag)
if self.parent.result:
self.lock()
return
self.parent.playerTurn = 1
self.lock()
self.keylock = 0
def alpha_zero(self):
pass
def run(self):
while self.is_running:
if self.is_lock():
continue
self.random_choice()
table = """
Turn {}
|{:^3s}|{:^3s}|{:^3s}|
-------------
|{:^3s}|{:^3s}|{:^3s}|
-------------
|{:^3s}|{:^3s}|{:^3s}|
"""
table_r = """
結果: {}
|{:^3s}|{:^3s}|{:^3s}|
-------------
|{:^3s}|{:^3s}|{:^3s}|
-------------
|{:^3s}|{:^3s}|{:^3s}|
"""
def check(board):
wins = [[0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6]]
for i in range(len(wins)):
if board[wins[i][0]]==board[wins[i][1]]==board[wins[i][2]]==1:
return 1
elif board[wins[i][0]]==board[wins[i][1]]==board[wins[i][2]]==-1:
return 1
return [2, 0][0 in board]
class App(tk.Tk):
def __init__(self):
super(App, self).__init__()
self.title("三目並べ")
self.geometry("{}x{}+{}+{}".format(360, 400, 450, 100))
self.set_variables()
self.set_board()
self.set_button()
self.thread = Thread()
self.thread.set_parent(self)
def set_variables(self):
self.result = 0
self.playerTurn = 1
self.board2info = [0] * 9
self.symbol = " ox"
self.alpstr = "abcdefghi"
self.winner = ["", "あなた", "引き分け", "CPU"]
def set_board(self):
self.board = tk.Canvas(self, bg="white", width=340, height=340)
self.tag2pos = {}
position = [(20, 20, 120, 120), (120, 20, 220, 120), (220, 20, 320, 120),
(20, 120, 120, 220), (120, 120, 220, 220), (220, 120, 320, 220),
(20, 220, 120, 320), (120, 220, 220, 320), (220, 220, 320, 320)]
for tag, pos in zip(self.alpstr, position):
self.tag2pos[tag] = pos[:2]
self.board.create_rectangle(*pos, fill='green yellow', outline='green yellow', tags=tag)
self.board.tag_bind(tag, "<ButtonPress-1>", self.pressed)
for x in range(120, 320, 100):
self.board.create_line(x, 20, x, 320)
for y in range(120, 320, 100):
self.board.create_line(20, y, 320, y)
self.board.place(x=10, y=0)
def set_button(self):
self.reset = tk.Button(self, text="reset", relief="groove", command=self.clear)
self.reset.place(x=170, y=360)
self.quit_program = tk.Button(self, text="quit", relief="groove", command=self.close)
self.quit_program.place(x=320, y=360)
def clear(self):
self.board.delete("all")
self.set_variables()
self.set_board()
self.thread.keylock = 0
def draw_symbol(self, tag):
symbol = self.symbol[self.playerTurn]
x, y = self.tag2pos[tag]
self.board.create_text(x+50, y+50,
font=("Helvetica", 60),
text=symbol)
def pressed(self, event):
if self.thread.keylock:
return
item_id = self.board.find_closest(event.x, event.y)
tag = self.board.gettags(item_id[0])[0]
action = self.alpstr.index(tag)
state = self.board2info[action]
if state in [-1, 1]:
return
self.update_board(action, tag)
if self.result:
self.thread.keylock = 1
return
self.thread()
def update_board(self, action, tag):
self.board2info[action] = self.playerTurn
self.draw_symbol(tag)
self.check_result()
self.playerTurn = -self.playerTurn
def check_result(self):
result = check(self.board2info)
winner = "Turn {}".format("?")
if result:
winner = self.winner[result] if result == 2 else self.winner[self.playerTurn]
print(table_r.format(winner, *[[" ", "o", "x"][i] for i in self.board2info]))
else:
print(table.format("?", *[[" ", "o", "x"][i] for i in self.board2info]))
self.result = result
def close(self):
self.thread.is_running = 0
self.quit()
def run(self):
self.mainloop()
if __name__ == "__main__":
app = App()
app.run()
ちなみに今回はThreadを使っています。
なぜThreadを使ったかというと、思考に時間が少しかかるAIを実装するためです。
AIを実装した三目並べはThreadを使わなくてもtkinterのafterやupdateメソッドを使えば
実装できますが、AIの思考時間が長いとアプリケーションが固まってしまい停止するので、回避策としてThreadを使います。
自分はあまりThreadについての知識がないので、今回書いたプログラムがいいのかわかりませんが、うまくいったので、これでよしとします。
続き
AlphaZeroを使って三目並べを学習させたAIと対戦できるプログラムを書いたので、近日中に書く予定です。
www.pytry3g.com