python~tkinterを用いたオセロ(リバーシ)の実装例~

初めに

こんにちは、keymaleです。今回はtkinterを用いたオセロの実装です。まだ作り始めたばっかりでだいぶお粗末な部分はありますが、とりあえずオセロとして動いたのでコードを載せたいと思います。脱初心者としてオセロを実装しましたが、なかなかいい例題だったと思います。ルールの実装と確認とか、変数の定義とか、クラスとか、GUI化とか基礎知識でここまでできる確認と、自分のコード力把握にもってこいだと思います。近々こいつで強化学習して最強のオセロCPUを作りたいと思います。

pythonで実装したオセロのコード

実装したコードは以下になります。

import sys,os
import tkinter as tk
class reversi():
    def __init__(self):
        self.row_num = 8
        
        self.xsize = 600
        self.ysize = 600
        self.tagstr = 'abcdefgh'
        self.item_id = 0
        self.turn = '●'
        self.board = [['##' for i in range (self.row_num + 2)] for j in range(self.row_num + 2)]
        self.board[self.row_num//2][self.row_num//2:self.row_num//2+2] =['○','●']
        self.board[self.row_num//2+1][self.row_num//2:self.row_num//2+2] =['●','○']
        self.coordinate = {'x':-1,'y':-1}
        self.dispalay_tk()
            
    def double_check(self,color):
        if self.board[self.coordinate['y']][self.coordinate['x']] == '##':
            return True
        else:
            print('そこはもう置いてあるでしょうが!!!\n\n')
            return False
        
    def around_check(self,color,x,y):
        empty_cnt = 0
        reversible_cnt = 0
        reversible_tgt = []
        temp_tgt =[]
        pass_flag = False
        for ax in range(x-1,x+2):
            for ay in range(y-1,y+2):
                if ax != x or ay != y:
                    if  self.board[ay][ax] != '##' and self.board[ay][ax] != color:
                        empty_cnt += 1
                        temp_tgt += [[ax,ay]]
        if empty_cnt == 0:
            pass_flag = False
        else:
            reversible_tgt_temp = []
            for bx,by in temp_tgt:
                reversible_tgt_temp = [[bx,by]]
                difx = x - bx
                dify = y - by
                temp_reversible_cnt = 1
                cx = bx - difx
                cy = by - dify
                while True:
                    if cx == 0 or cx == self.row_num + 1 or cy == 0 or cy == self.row_num + 1:
                        break
                    elif self.board[cy][cx] == '##':
                        break
                    elif self.board[cy][cx] != color:
                        temp_reversible_cnt += 1
                        reversible_tgt_temp += [[cx,cy]]
                    else:
                        reversible_cnt += temp_reversible_cnt
                        reversible_tgt += reversible_tgt_temp
                        pass_flag = True
                        break
                    cx = cx - difx
                    cy = cy - dify
                    
        return pass_flag,reversible_cnt,reversible_tgt
        
    def input_calc(self,color):
        pass_flag = False
        reversible_tgt_sum = []
        for x in range(1,self.row_num + 1):
            for y in range(1,self.row_num + 1):
                if self.board[y][x] == '##':
                    pass_check, reversible_cnt,reversible_tgt = self.around_check(color = color, x = x, y = y)
                    if pass_check:
                        reversible_tgt_sum += reversible_tgt
                        pass_flag = True
        if pass_flag == False:
            self.comment.set('もう置くとこないからPASSやな')
            print('もう置くとこないからPASSやな')
        while pass_flag:
            if self.double_check(color = color):
                pass_check, reversible_cnt,reversible_tgt = self.around_check(color = color,x = self.coordinate['x'], y = self.coordinate['y'])
                if pass_check:
                    self.board[self.coordinate['y']][self.coordinate['x']] = color
                    for ax,ay in reversible_tgt:
                        self.board[ay][ax] = color
                    print(reversible_tgt)
                    print(str(reversible_cnt) + '個ひっくり返したで')
                    self.comment.set(str(reversible_cnt) + '個ひっくり返したで')
                    pass_flag = False
                    break
                else:
                    print('置けません')
                    self.comment.set('置けまへーん!!')
                    pass_flag = True
                    break
            else:
                pass_flag = True
                break
        self.refresh_board()
        return pass_flag
        
    def pressed(self,event):
        self.item_id = self.canvas.find_closest(event.x, event.y)
        self.tag = self.canvas.gettags(self.item_id[0])[0]
        px = self.tag2pos(self.tag[0])
        py = self.tag2pos(self.tag[1])
        print(self.tag,px,py)
        self.coordinate['x'] = px
        self.coordinate['y'] = py
        if self.input_calc(color = self.turn) == False:
            if self.turn == '●':
                self.turn = '○'
            else:
                self.turn = '●'
        self.refresh_board()
        
        
        
    def pos2tag(self, pos):
        for p, t in zip(list(range(1,self.row_num+1)),self.tagstr):
            if pos == p : 
                return t
            
    def tag2pos(self, tag):
        for p, t in zip(list(range(1,self.row_num+1)),self.tagstr):
            if tag == t : 
                return p
            
    def refresh_board(self):
        for num_row in range(1,self.row_num+1):
            for num_line in range(1,self.row_num+1):
                if self.board[num_row][num_line] == '●':
                    tag = self.pos2tag(num_line) + self.pos2tag(num_row)
                    self.canvas.create_oval(*self.tag_pos[tag], fill='black',tags = self.tag)
                elif self.board[num_row][num_line] =='○':
                    tag = self.pos2tag(num_line) + self.pos2tag(num_row)
                    self.canvas.create_oval(*self.tag_pos[tag], fill='white',tags = self.tag)
        self.display_comment()
    
    def cnt_board(self):
        cnt_b = 0
        cnt_w = 0
        for i in range(1,self.row_num+1):
            cnt_b += self.board[i].count('●')
            cnt_w += self.board[i].count('○')
        return cnt_b, cnt_w
    
    def display_comment(self):
        cnt_b,cnt_w = self.cnt_board()
        Static1 = tk.Label(text=self.turn+'のターンです')
        Static2 = tk.Label(textvariable=self.comment)
        self.cnt_bw.set('● : ○ = '+str(cnt_b)+' : '+str(cnt_w))
        Static_cntbw = tk.Label(textvariable=self.cnt_bw)
        Static1.place(x=self.xsize/2-200, y=self.ysize -90)
        Static2.place(x=self.xsize/2-200, y=self.ysize -65)
        Static_cntbw.place(x=self.xsize/2, y=self.ysize -90)

        
    def dispalay_tk(self):
        self.root = tk.Tk()
        self.comment = tk.StringVar()
        self.cnt_bw = tk.StringVar()
        self.comment.set('楽しい楽しいオセロの始まりやで')
        reversi_xsize = 400
        reversi_ysize = 400
        xedge = (self.xsize - reversi_xsize)/2
        yedge = (self.ysize - reversi_ysize)/2
        button = tk.Button(self.root, text = 'Python/Tkinter')
        button.pack()
        self.root.title("リバーシ")
        self.root.minsize(self.xsize, self.ysize)
        self.canvas = tk.Canvas(bg="green", width=reversi_xsize, height=reversi_ysize)
        self.canvas.place(x=xedge,y=yedge)
        
        self.tag_pos = {}
        for  xstr,x in zip(self.tagstr,list(range(1,self.row_num+1))):
            for ystr,y in zip(self.tagstr,list(range(1,self.row_num+1))):
                self.tag = xstr + ystr
                self.tag_pos[self.tag] = (x-1)*(reversi_ysize/(self.row_num)), (y-1)*(reversi_ysize/(self.row_num)), x*(reversi_ysize/(self.row_num)), y*(reversi_ysize/(self.row_num)),
                self.canvas.create_rectangle(*self.tag_pos[self.tag], fill='green', tags = self.tag)
                self.canvas.tag_bind(self.tag, "<ButtonPress-1>", self.pressed)
        self.refresh_board()
    

    def run_display(self):
        self.root.mainloop()

reversi = reversi()
reversi.run_display()

jupyter-notebookで書いています。実行するとこんな感じの画面がでます。

交互に黒と白の駒を打っていくだけです。打てないときはPASSされます。置けないところに置こうとすると警告が出ます。
かなり簡易的ですが、今日はこの辺で終わりにします。まだまだ改良予定なので、改良するたびに更新します。

改良版を公開しました。CPU対戦機能などを実装したので是非こちらもご参考お願い致します。