EU向けの同意バナーを停止しました
【Python】オセロをつくってみた

【Python】オセロをつくってみた

2020年1月29日

こんにちは、コンテンツクリエイターのともすけです。

以下の仕様で実装した、なんちゃってオセロのコードを公開します。コードは最適化されていませんので、ご了承の上ご覧ください。貼り付けたコードは、念のためここからコピペしても動作することを確認済みです。

  • 必ず白から始まります。
  • 相手が置けなかったとき、再度自分の番となるようにしたつもりです。
  • 両方手詰まりとなったときを考慮したつもりです。
  • AIは組み込んでいません。
  • GUIを使うので、tkinterを使用しています。
  • 動作確認は、 windows版python3.7をインストールし、cygwinで動かしています。
import tkinter as tk
import time        # 石をめくる速度を変えようと思って入れましたが、うまくいかず
# -*- coding: utf-8 -*-

color = 1

# 座標とベクトルを格納するためのクラス
class Position:
    def __init__(self,x,y,dx,dy):
        self.x  = x+dx
        self.y  = y+dy
        self.dx = dx
        self.dy = dy

def checkZero(a,b):
    if a==0:
        return 0
    else:
        return b

def clearMap(target):
    global mark_map, reserve_map
    for x in range(8):
        for y in range(8):
            if target==0:
                mark_map[x][y] = 0
            else:
                reserve_map[x][y] = 0

def reverse():
    if color==1:
        return 2
    else:
        return 1

def getColor(color):
    if color==1:
        return "white"
    elif color==2:
        return "black"
    else:
        return "no stone"

def same(a,b):
    if a==b:
        return True
    else:
        return False

#このプログラムのメインです。クリックしたところに置けるか判定します
def okeruka(mine, pos, flag, indent):
    global stone_db
    global reserve_map
    global mark_map
    ret = -10
    dx_list = [-1,0,1]
    dy_list = [-1,0,1]
    print("{}x={}, y={}, stone={}".format(indent, pos.x, pos.y, getColor(stone_db[pos.x][pos.y])))
    indent += "  "
    if stone_db[pos.x][pos.y]==0 and flag==0:
        mark_map[pos.x-1][pos.y-1] = 1
        for dx in dx_list:
            for dy in dy_list:
                if dx==0 and dy==0:
                    continue
                _pos = Position(pos.x, pos.y, dx, dy)
                ret = checkZero(ret, okeruka(mine, _pos, 1, indent))
    else:
        if stone_db[pos.x][pos.y]==0 or stone_db[pos.x][pos.y]==-1: # 石がない
            clearMap(1)
            return -1
        else:   # 石がある
            if flag==1: # 同色はダメ、異色はOK
                if stone_db[pos.x][pos.y]==mine:    # 同色
                    return -2
                else:
                    _pos = Position(pos.x, pos.y, pos.dx, pos.dy)
                    reserve_map[pos.x-1][pos.y-1] = 1
                    ret = okeruka(mine, _pos, 2, indent)
            elif flag==2:   # 同色、異色どちらもOK
                if stone_db[pos.x][pos.y]==mine:
                    for i in range(8):
                        for j in range(8):
                            if reserve_map[i][j]==1:
                                #mark_map[i][j] = reserve_map[i][j]
                                mark_map[i][j] = 1
                    print("reserve_map")
                    for y in range(8):
                        for x in range(8):
                            print("{} ".format(reserve_map[x][y]), end="")
                        print("")
                            
                    clearMap(1) # clear reserve_map
                    return 0
                else:
                    _pos = Position(pos.x, pos.y, pos.dx, pos.dy)
                    reserve_map[pos.x-1][pos.y-1] = 1
                    ret = okeruka(mine, _pos, 2, indent)
    return ret

# 石を置くためのメソッドです
def placeStone(x,y,color):
    canvas.create_oval(offset_x+pitch*x+margin,     offset_y+pitch*y+margin,
                       offset_x+pitch*(x+1)-margin, offset_y+pitch*(y+1)-margin,
                       fill = color)
    print("write ({},{})".format(x,y))
 
def drawStone(mode):
    global stone_db, mark_map
    global stone_status
    global finish, finish2
    black = 0
    white = 0
    if mode==0:
        for x in range(8):
            for y in range(8):
                if stone_db[x+1][y+1]==1:
                    placeStone(x, y, "white")
                elif stone_db[x+1][y+1]==2:
                    placeStone(x, y, "black")
    else:
        for x in range(8):
            for y in range(8):
                if mark_map[x][y]==1:
                    if stone_db[x+1][y+1]==1:
                        placeStone(x, y, "white")
                    elif stone_db[x+1][y+1]==2:
                        placeStone(x, y, "black")
                    #time.sleep(1)
    for x in range(8):
        for y in range(8):
            if stone_db[x+1][y+1]==1:
                white += 1
            elif stone_db[x+1][y+1]==2:
                black += 1
    stone_status.set("白:{}, 黒:{}".format(white,black))
    if (white+black)==64 or finish2==1:
        finish = 1
        if white > black:
            judge_text.set("白の勝ちです")
        elif black > white:
            judge_text.set("黒の勝ちです")
        else:
            judge_text.set("引き分けです")

 
def on_click(event):
    global finish, finish2
    global color
    global mark_map, stone_db
    global next_user

    if finish==1:
        return 0

    x = event.x
    y = event.y
 
    click_x  = -1
    click_y  = -1
 
    for i in range(8):
        for j in range(8):
            if x >= (offset_x + i*pitch + margin) and x <= (offset_x + (i+1)*pitch - margin) and \
               y >= (offset_y + j*pitch + margin) and y <= (offset_y + (j+1)*pitch - margin):
                   click_x = i+1
                   click_y = j+1
                   break
 
    pos = Position(click_x, click_y, 0, 0)
    ret = okeruka(color, pos, 0, "")
    if ret==0:
        print("x={}, y={}, can place".format(click_x, click_y))
        print("--- mark_map ---")
        for y in range(8):
            for x in range(8):
                print("{} ".format(mark_map[x][y]), end="")
                if mark_map[x][y]==1:
                    stone_db[x+1][y+1] = color
                    #print("x={}, y={}, placed {}".format(x+1,y+1,getColor(color)))
            print("")

        drawStone(1)
        color = reverse()
        if color==1:
            next_user.set("白の番です")
        else:
            next_user.set("黒の番です")

        print("next color is {}".format(getColor(color)))
    else:
        print("x={}, y={}, ret={}, cannot place".format(click_x, click_y, ret))

    clearMap(0)
    clearMap(1)

    ret = -1
    for x in range(8):
        for y in range(8):
            pos = Position(x+1, y+1, 0, 0)
            ret = checkZero(ret, okeruka(color, pos, 0, ""))
    clearMap(0)
    clearMap(1)
    
    if ret==0:  # 相手の石は置ける
        pass
    else:       # 相手の石は置けないので、自分の石が置けるか確認
        ret = -1
        color = reverse()
        for x in range(8):
            for y in range(8):
                pos = Position(x+1, y+1, 0, 0)
                ret = checkZero(ret, okeruka(color, pos, 0, ""))
        clearMap(0)
        clearMap(1)
        if ret==0:
            if color==1:
                next_user.set("黒が置けないので、白の番です")
            else:
                next_user.set("白が置けないので、黒の番です")
        else:
            finish2 = 1
            drawStone(1)    # 石の状態をチェックさせる(途中終了)

 
### start
offset_x = 10
offset_y = 10
margin   = 10
pitch    = 10
 
root = tk.Tk()
root.title("オセロ")
canvas = tk.Canvas(root, width = 700, height = 700)

canvas.pack()
canvas.bind("<Button-1>",on_click)

### label
next_user = tk.StringVar()
label = tk.Label(root, textvariable=next_user)
label.place(x=10, y=660)
next_user.set("白の番です")

stone_status = tk.StringVar()
label2 = tk.Label(root, textvariable=stone_status)
label2.place(x=200, y=660)

judge_text = tk.StringVar()
label3 = tk.Label(root, textvariable=judge_text)
label3.place(x=400, y=660)

### status
finish  = 0     # 通常の終了フラグ
finish2 = 0     # 途中の終了フラグ

# 盤面描画
offset_x = 10
offset_y = 10
pitch    = 80
_width   = 0.0
for i in range(9):
    if i==0 or i==8:
        _width = 3.0
    else:
        _width= 1.0
         
    canvas.create_line(offset_x,           offset_y + i*pitch,
                  offset_x + pitch*8, offset_y + i*pitch,
                  width = _width)
    canvas.create_line(offset_x + i*pitch, offset_y,
                  offset_x + i*pitch, offset_y + pitch*8,
                  width = _width)
 
### 石の配置
#   -1 エリア外
#    0 何も置いてない
#    1 白
#    2 黒
stone_db    = [[0] * 10 for i in range(10)]    # とりあえず全面「何も置いてない」
mark_map    = [[0] * 8  for i in range(8)]     # 石を置き換えるデータマップ
reserve_map = [[0] * 8  for i in range(8)]     # 仮の、石を置き換えるデータマップ
#print("DBG(0) : mark_map.id={}".format(id(mark_map)))
for x in range(10):
    for y in range(10):
        if x==0 or x==9 or y==0 or y==9:
            stone_db[x][y] = -1
        if (x==4 and y==4) or (x==5 and y==5):
            stone_db[x][y] = 1
        if (x==4 and y==5) or (x==5 and y==4):
            stone_db[x][y] = 2
 
### 最初の石の配置
#placeStone(3,3,"black")
#placeStone(4,4,"black")
#placeStone(4,3,"white")
#placeStone(3,4,"white")
drawStone(0)
 
root.mainloop()