上記タイトルの本が本屋さんにあったので、面白そうなので図書館で借りてみた。
『囲碁ディープラーニングプログラミング』
機械学習とは何か、とか、難しい話が最初にある。
第3章からは、実際に囲碁プログラムを作成する。
当然のことながら、Python3 についての説明や、掲載されているコードについての 詳しい説明はない。
なぜそういうコードが必要なのか、そのコードの狙いは何か、そういうのは説明されている。
僕は Pythonは、2年ほど前にちょっとかじった程度なので、ほとんど忘れている。
文法書も手元にはない。ネット情報だけがたよりである。
そこで、本文に載っているコードに、ネットで調べたことや、そのコードを読み解くために 必要なメモを記入した。
更に、各クラスのプロパティやメソッドについての説明、特に、メソッドについては、 引数と返り値をメモに書いた。
それらをまとめて、自分のブログに置いといたら、そのうち役に立つかもしれない。
ディープラーニングにいくまでの入り口のところでウロウロしてしまった。
でも、これはこれで、楽しい。
dlgo/gotypes.py (p55)
import enum
# print(Player.black) ==> Player.black
# print(Player.black.name) ==> black
# print(Player.black.value) ==> 1
class Player( enum.Enum ):
black = 1
white = 2
# a が Player.black だとすると、a.other は Player.white となる
@property
def other( self ):
return Player.black if self == Player.white else Player.white
プレーヤーを Player.black と Player.white で表現できる。
dlgo/gotypes.py(つづき)
from collections import namedtuple
# クラス Point型の定義
# namedtupleを引数にもってインスタンスを作成
# namedtuple(typename, field_names, ...) -- 名前付きフィールドをもつタプルのファクトリ関数
# field_namesには、'x y'や'x, y'などの文字列をわたすことができる
# (例)
# >>> a = Point(row=5, col=5) # <== インスタンスの作成のしかた
# >>> print(a) # Point(row=5, col=5)
# >>> print(a.row) # 5
# >>> b = a.neighbors()
# >>> print(b)
# [Point(row=4, col=5), Point(row=6, col=5), Point(row=5, col=4), Point(row=5, col=6)]
class Point( namedtuple( 'Point', 'row col')):
def neighbors( self ):
return [
Point( self.row - 1, self.col ),
Point( self.row + 1, self.col ),
Point( self.row, self.col - 1 ),
Point( self.row, self.col + 1 ),
]
碁盤の点を Point(row, col) の形式で表現できる。
また、Point(row, col).neighbors() とすると、上下左右の点を取得できる。
dlgo/goboard_slow.py (p56)
# Move(着手)
# Move.play(point) -- そのポイントをポイントとする
# Move.pass_turn -- パスする (self.is_pass が true になる)
# Move.resign -- 投了する (self.is_resign が true になる)
class Move():
def __init__( self, point=None, is_pass=False, is_resign=False ):
assert (point is not None) ^ is_pass ^ is_resign # <1>
self.point = point # <2>
self.is_play = (self.point is not None) # <3>
self.is_pass = is_pass
self.is_resign = is_resign
# <1> 排他的論理和 -- すべてが1だと0になる
# <2> self -- インスタンス自身のこと。
# <3> is_play -- その点は空ではない
# is_play, is_pass, is_resign は、True / False の値をとる
# クラスメソッド -- インスタンス化せずにクラスから直接呼び出すことができる
# cls -- クラスのこと。呼び出すときの引数は point から。
# play(打つ) -- プロパティself.point に 引数point をセット。
@classmethod
def play( cls, point ):
return Move( point=point )
# pass(パス)
@classmethod
def pass_turn( cls ):
return Move( is_pass=True )
# resign(投了)
@classmethod
def resign( cls ):
return Move( is_resign=True )
Moveは、ここでは、「着手」の役割を持たせている。
クラス「Move」 コンストラクタ引数: point, is_pass, is_resign プロパティ: point -- Point(row, col) is_play -- True / False is_pass -- True / False is_resign -- True / False クラスメソッド: Move.play(point) 引数: point 返り値: Move.point に point をセットしたインスタンス そのポイントに石をおく すると、そのポイントはNoneではないので、is_play は True となる。 Move.pass_turn 引数: なし 返り値: Move.is_pass に Trueをセットしたインスタンス パスをする Move.resign 引数: なし 返り値: Move.is_resign に Trueをセットしたインスタンス 投了する
dlgo/goboard_slow.py(つづき)
# 石の連 GoString
# color -- 石の色
# stones -- 石の集合 setを使っている。集合をあらわすデータ型だそうだ。
# liberties -- 呼吸点の集合
# (例)
# ren1 = GoString(white, 3, 8) -- ren1は白で、3つの石があり、呼吸点は8個である。
# ren1.remove_liberty(point) -- 呼吸点を1つ削除する。
class GoString():
def __init__( self, color, stones, liberties ):
self.color = color
self.stones = set(stones)
self.liberties = set(liberties)
# 呼吸点を削除
# removeは集合型のメソッド。要素を削除する。
def remove_liberty( self, point ):
self.liberties.remove( point )
# 呼吸点を追加
# add -- 集合に要素を追加する
def add_liberty( self, point ):
self.liberties.add( point )
# 2つの連を合体
def merged_with( self, go_string ):
assert go_string.color == self.color # <1>
combined_stones = self.stones | go_string.stones # <2>
return GoString( # <3>
self.color,
combined_stones,
( self.liberties | go_string.liberties ) - combined_stones )
# <1> 石の色が同じかをチェック
# <2> | -- 論理和 combined_stones は、石の集合の論理和である。
# <3> 新しくクラスをインタンス化して、それを返している
# 第3引数 -- 両方の呼吸点の集合の論理和をとって、石の集合との差集合を求めている。
# 論理和をとる場合、重複したものは1つとみなされる。
# 差集合をとる場合、a - b なら、a集合のなかに b集合と重複するものがある場合、
# それは削除される。
# (参考) -- https://uxmilk.jp/14834
# インスタンス.num_liberties -- 集合のlen(要素数)を返す
@property
def num_liberties( self ):
return len( self.liberties )
# 2つのインスタンスを比較できる -- a.__eq__(b)
# isinstance( object, class) -- 第1引数のオブジェクトが、
# 第2引数の型のインスタンスであれば true を返す
def __eq__( self, other ):
return isinstance( other, GoString ) and \
self.color == other.color and \
self.stones == other.stones and \
self.liberties == other.liberties
GoString コンストラクタ: color, stones, liberties プロパティ: ocolor -- Player.black / Player.white stones -- 石の集合 set liberties -- 呼吸点の集合 set メソッド: remove_liberty( point ) -- 呼吸点からそのポイントを削除 引数: point 返り値: なし (liberties.remove(point)) add_liberty( point ) -- そのポイントを呼吸点に追加 引数: point 返り値: なし (liberties.add(point)) merged_with( go_string ) -- その連を吸収合体 引数: go_string 返り値: go_string __eq__( other ) -- 2つの連が同じかどうか( othier -- 他の連) 引数: go_string 返り値: boolean プロパティメソッド: num_liberties -- 呼吸点の数を返す 引数: なし 返り値: int -- len(self.liberties)
dlgo/goboard_slow.py(つづき)
# 盤面
# num_rows, num_cols -- 格子線の数
# _grid -- 盤面の情報を辞書リストでもっている。
# keyは、Point(row=2, col=3)などのPoint。
# 値は、オブジェクトで、GoString情報をもっている
class Board():
def __init__( self, num_rows, num_cols ):
self.num_rows = num_rows
self.num_cols = num_cols
self._grid = {} # 辞書 -- {キー: 値, ...}
# _grid -- Pointをkeyとした辞書である。
# value には、GoStringが入っている。
# 呼吸点のために隣接する点をチェック
# player -- (ex) Player.black
# point -- (ex) Point(row=1, col=1)
def place_stone( self, player, point ):
assert self.is_on_grid(point) # pointが盤上にあるかどうか
assert self._grid.get(point) is None # pointがまだ打たれていないかどうか
adjacent_same_color = [] # 同じ色のリスト
adjacent_opposite_color = [] # 相手の色のリスト
liberties = [] # 呼吸点のリスト
# print( Point(3, 5).neighbors() )
# [ Point(row=15, col=6), Point(row=17, col=6), Point(row=16, col=5), Point(row=16, col=7) ]
for neighbor in point.neighbors():
if not self.is_on_grid( neighbor ): # <1>
continue
neighbor_string = self._grid.get( neighbor ) # <2>
if neighbor_string is None: # <3>
liberties.append( neighbor )
elif neighbor_string.color == player: # <4>
if neighbor_string not in adjacent_same_color:
adjacent_same_color.append( neighbor_string )
else:
if neighbor_string not in adjacent_opposite_color:
adjacent_opposite_color.append( neighbor_string )
new_string = GoString( player, [point], liberties ) # <5>
# <1> neighborが盤上の点ではなかったら、パス
# <2> _grid.get( key ) -- 指定されたkeyがあれば、その連情報を返す。
# なければ、Noneを返す。
# 値は、GoStringである。
# <3> もしneighbor_stringがNoneであれば、呼吸点のリストに追加
# <4> もしneighbor_stringの色がプレイヤーと同じであれば
# adjacent_sama_color, adjacent_oppsite_colorにいれる
# <5> 新しい連をつくる
#
for same_color_string in adjacent_same_color: # <6>
new_string = new_string.merged_with( same_color_string )
for new_string_point in new_string.stones: # <7>
self._grid[ new_string_point ] = new_string
for other_color_string in adjacent_opposite_color: # <8>
other_color_string.remove_liberty( point )
for other_color_string in adjacent_opposite_color: # <9>
if other_color_string.num_liberties == 0:
self._remove_string( other_color_string )
# <6> 同じ色の隣接する連をマージする
# <7> 新しくできた連のそれぞれのポイントに、連の情報をそれぞれセットする。
# <8> 敵の色の隣接する連の呼吸点を減らす
# <9> 敵の色の連の呼吸点が 0 になっている場合は、それを取り除く
def is_on_grid( self, point ):
return 1 <= point.row <= self.num_rows and \
1 <= point.col <= self.num_cols
# 盤上の点の連情報をかえす
# その点に石がある場合はPlayerの色、それ以外はNoneを返す
def get( self, point ):
string = self._grid.get( point )
if string is None:
return None
return string.color
# ある点における石の連全体を返す。
# その点に石がある場合はGoString、なければNoneを返す
def get_go_string( self, point ):
string = self._grid.get( point )
if string is None:
return None
return string
# 連を取り除く
# 連を取り除くと、相手の石が呼吸点を得ることができる
# string -- 取られる連
def _remove_string( self, string ):
for point in string.stones: # <1>
for neighbor in point.neighbors(): # <2>
neighbor_string = self._grid.get( neighbor ) # <3>
if neighbor_string is None:
continue
if neighbor_string is not string: # <4>
neighbor_string.add_liberty( point )
self._grid[ point ] = None # <5>
# <1> string.stones --- stonesはpointの集合
# <2> point.neighbors() --- 隣の点のリスト
# <3> neighbor_string -- 隣の連の情報
# <4> neighbor_string が 取られる連でなかったら、相手の連の呼吸点の集合に追加する。
# <5> 石を取り除いたので、そのポイントは None になる
Board コンストラクタ: num_rows, num_cols プロパティ: num_rows -- 行の数 num_cols -- 列の数 _grid -- 点の情報。辞書。 key : Point(row, col) value : go_string -- (color, stones, liberties) _grid.get(point)で、そのポイントの連の色を得ることができる。 _grid.get_go_string(point)で、そのポイントの連情報を得ることができる。 メソッド: place_stone( player, point ) -- 盤上に石を置く 引数: player -- Player.black あるいは Player.white point -- Point(row, col) 返り値: なし。(Board._gridの情報を書き換えるから) メソッド内変数:adjacent_same_color -- 同じ色のリスト adjacent_oppsite_color -- 相手の色のリスト liberties -- 呼吸点のリスト neighbor_string -- 隣接点の値 _grid.get(neighbor) 上下左右の隣接点(neighbor_string=連)を調べる 空なら、呼吸点に加える。 同じ色の連であれば、adjacent_same_colorリストに加える。 相手の色の連であれば、adjacent_oppsite_colorリストに加える。 new_string(連)をつくる。GoString( player, [point], liberties ) adjacent_same_colorリストの中に同じ色の連があれば、new_string(連)とその連を合体させる。 新しくできた連のそれぞれのポイントに、新しい連の情報をセットする。 相手の色の連の呼吸点リストから、今のポイントを削除する。 もし、相手の色の連の呼吸点の数がゼロになれば、相手の色の連を削除する。 is_on_grid( point ) -- そのポイントが盤上にあれば True、なければ False 引数: point -- Point(row, col) 返り値: boolean get( point ) -- そのポイントの連の色を返す。 引数: point 返り値: string.color get_go_string( point ) -- そのポイントの連情報を返す。(color, stones, liberties) 引数: point 返り値: string _remove_string( string ) -- 連を取り除く。 引数: string -- GoStringのこと。 返り値: なし。(Board._gridの情報を書き換える) 取り除く対象の連のそれぞれのポイントをNoneにする。 その際、そのポイントの隣の点を調べ、もし、相手の連であれば、その連に呼吸点を追加する。
dlgo/goboard_slow.py(つづき)
# ゲーム情報
class GameState():
def __init__( self, board, next_player, previous, move ):
self.board = board # 盤面の情報
self.next_player = next_player # 次のプレーヤー
self.previous_state = previous # 前のゲーム状態
self.last_move = move # 最後に行われた着手
# 着手を適用したあと、新しい GameState を返す
def apply_move( self, move ):
if move.is_play: # <1>
next_board = copy.deepcopy( self.board )
next_board.place_stone( self.next_player, move.point )
else:
next_board = self.board
return GameState( next_board, self.next_player.other, self, move )
# <1> 着手が is_play、つまり Move.play(point)で指し手をした場合、
# pointはNoneではなくなるので、is_play は True となる。
# board_size -- 9 / 19 などの数値
@classmethod
def new_game( cls, board_size ):
if isinstance( board_size, int ): # <1>
board_size = ( board_size, board_size )
board = Board( *board_size ) # <2>
return GameState( board, Player.black, None, None )
# <1> board_sizeオブジェクトが int型のインスタンスであれば True
# <2> *board_size -- 9 9 どうもタプルやリストの中の値だけをとりだしてくれるみたい
# 終局しているか判定
def is_over( self ):
if self.last_move is None: # <1>
return False
if self.last_move.is_resign: # <2>
return True
second_last_move = self.previous_state.last_move # <3>
if second_last_move is None:
return False
return self.last_move.is_pass and second_last_move.is_pass # <4>
# <1> 最後の着手がまだの場合は False
# <2> 最後の着手が「投了」の場合は True
# <3> 前回の盤面の最後の着手を second_last_move とする
# <4> last_move と second_last_move がともに パス なら True を
# そうでなければ False を返す
# 自殺手のルールを強制する
def is_move_self_capture( self, player, move ):
if not move.is_play: # <1>
return False
next_board = copy.deepcopy( self.board )
next_board.place_stone( player, move.point )
new_string = next_board.get_go_string( move.point )
return new_string.num_liberties == 0
# <1> Move.play(point)であれば is_play は True になっている。
# 現在のゲーム状態はコウのルールに違反しているか?
@property
def situation( self ):
return ( self.next_player, self.board )
# player -- Player.black / Player.white
# move -- Move.play(row, col)
def does_move_violate_ko( self, player, move ):
if not move.is_play:
return False
next_board = copy.deepcopy( self.board ) # <1>
next_board.place_stone( player, move.point ) # <2>
next_situation = ( player.other, next_board ) # <3>
past_state = self.previous_state # <4>
while past_state is not None: # <5>
if past_state.situation == next_situation: # <6>
return True
past_state = past_state.previous_state # <7>
return False # <8>
# <1> 現在の盤をdeepcopyして、next_boardとする。
# <2> next_boardに次の指し手を実行(石を置く)。
# <3> next_board と player.other をタプルにして next_situation とする。
# <4> 前回の盤を past_state とする。
# <5> 前回の盤が None でない限り実行。
# <6> past_sitate.situation と next_situation が同じであれば、
# つまり、「コウ」であれば、True を返す。
# <7> past_stateを past_stateの更に前の盤(previous_state)とする。
# それで、もう一度、past_state.situation と next_situation を比べる。
# <8> 同じゲーム状況が無ければ、「コウ」ではないので、False を返す。
# この着手は指定されたゲーム状態に対して有効か?
def is_valid_move( self, move ):
if self.is_over(): # <1>
return False
if move.is_pass or move.is_resign: # <2>
return True
return (
self.board.get( move.point ) is None and # <3>
not self.is_move_self_capture( self.next_player, move ) and # <4>
not self.does_move_violate_ko( self.next_player, move )) # <5>
# <1> 終局の場合 False(無効)
# <2> 着手がパスあるいは投了の場合 True(有効)
# <3> 指し手の点に石(連)が無い場合
# <4> 指し手が自殺手である場合
# <5> 指し手がコウである場合
# <3><4><5>がともに成立することって、あるのかな?
GameState コンストラクタ: board, next_player, previous, move プロパティ: board -- 盤面の情報 next_player -- プレーヤー previous_state -- 前の盤面の情報 last_move -- 前回の指し手 メソッド: apply_move( move ) 引数: move -- Move.play(row, col)、あるいは Move.pass_turn、Move.resign 返り値: GameState( next_board, next_player.other, self, move) 指し手を行った場合、盤面をディープコピーしてnext_boardとする。 盤上に石を置く。 それ以外(パス・投了の場合)は、盤面をそのまま引き継ぐ。 そして、新しく GameState のインスタンスを作成する。 new_game( board_size ) -- クラスメソッド。 引数: board_size -- 9、19 などの数値 返り値: GameState( board, Player.black, None, None ) board -- (19, 19) あるいは (9, 9)のタプル。 is_over -- 終局しているか、判定。 引数: なし 返り値: boolean -- 投了もしくは双方がパスの場合 True、すなわち終局。 is_move_self_capture -- 自殺手のルールを強制する 引数: player -- Player.black / Player.white move -- Move.play(row, col), あるいは Move.pass_turn、Move.resign 返り値: boolean (呼吸点がゼロになった = True) situation -- プロパティ・メソッド 引数: なし 返り値: タプル ( self.next_player, self.board ) next_playerと盤面情報を返す does_move_violate_ko 引数: player, move 返り値: boolean -- コウであれば True、コウでなければ False を返す。 is_valid_move 引数: move -- Move.play(row, col) 返り値: boolean -- 終局なら False。パスあるいは投了なら True 石を置いたポイントが None であり、なおかつ、 プレーヤーの指し手が自殺手であり、なおかつ、 プレーヤーの指し手がコウである場合、True -- こんな場合って、ある?
dlgo/agent/helper.py (p69)
from dlgo.gotypes import Point
# 盤上の指定された点は、眼か?
def is_point_an_eye( board, point, color ):
if board.get( point ) is not None: # <1>
return False
for neighbor in point.neighbors():
if board.is_on_grid( neighbor ):
neighbor_color = board.get( neighbor )
if neighbor_color != color: # <2>
return False
friendly_corners = 0
off_board_corners = 0
corners = [ # <3>
Point( point.row - 1, point.col - 1),
Point( point.row - 1, point.col + 1),
Point( point.row + 1, point.col - 1),
Point( point.row + 1, point.col + 1),
]
for corner in corners:
if board.is_on_grid( corner ):
corner_color = board.get( corner )
if corner_color == color: # <4>
friendly_corners += 1
else:
off_board_corners += 1 # <5>
if off_board_corners > 0: # <6>
return off_board_corners + friendly_corners == 4
return friendly_corners >= 3 # <7>
# <1> 眼は空の点
# <2> 隣接するすべての点の色は、味方の色であること
# <3> corners -- その点の四隅の点
# <4> そのコーナーの色が味方の色であれば、friendly_corners をプラス1
# <5> そのコーナーが盤上の点ではなかったら、off_board_corners をプラス1
# <6> off_board_cornersが1つでもあれば、off_board_corners と
# friendly_corners の合計が 4 ならば True,そうじゃなければ False を返す
# <7> friendly_corners が 3 以上なら True を返す
is_point_an_eye( board, point, color ) -- 「眼」であるかどうかの判定 引数: board, point, color 返り値: boolean -- 「眼」であれば True。でなければ False。 pointの盤面情報が None でなかったら、すなわち、石あるいは連であったら、False つまり、空の点であること。 そのポイントの隣の点が自身の色ではなかったら、false。つまり、上下左右が 自分の色の石であること。 四隅のうち、三つ以上が自分の色の石であれば、True。 辺に自身があれば、すなわち四隅のうち、いくつから盤面から外れている場合、 外れている隅と自分の色の隅が合計で四でれば、True。
agent/base.py
# 対局ボットのためのインターフェース
class Agent():
def __init__( self ):
pass
# この基底クラスをもとに派生クラスを作る場合
# この抽象メソッドが派生クラスでオーバーライドされることを
# 要求している。
# その場合、この例外を記述しておくことが求められる。
def select_move( self, game_state ):
raise NotImplementedError()
Agent コンストラクタ: なし select_move -- インターフェース 引数: game_state -- GameStateのインスタンスを引数にとる。
dlgo/agent/nave.py
import random
from dlgo.agent.base import Agent
from dlgo.agent.helpers import is_point_an_eye
from dlgo.goboard_slow import Move
from dlgo.gotypes import Point
# candidates -- 有効な着手ができる候補リスト
# もし、candidatesが無ければ、パスする
# ボットは、candidatesリストの中からランダムに手を選ぶ
class RandomBot( Agent ):
def select_move( self, game_state ):
"""Choose a random valid move that preserves our own eyes.
自分の眼を維持するランダムな有効な着手を選択する"""
candidates = [] # 候補
for r in range( 1, game_state.board.num_rows + 1 ):
for c in range( 1, game_state.board.num_cols + 1 ):
candidate = Point( row=r, col=c )
if game_state.is_valid_move( Move.play( candidate ) ) and \
not is_point_an_eye( game_state.board, # <1>
candidate,
game_state.next_player ):
candidates.append( candidate ) # <2>
if not candidates: # <3>
return Move.pass_turn()
return Move.play( random.choice( candidates )) # <4>
# <1> 要するに、候補点が着手可能な点であるか
# not is_point_an_eye つまり、眼には打てないということ。
# <2> 盤上のすべての着手可能な点を「候補点」として candidates リストに入れている。
# <3> もしも、候補点が無ければ、パスせざるを得ん。
# <4> 候補点のリストの中から、ランダムに選んで、「指し手」としている。
RandomBot( Agent ) 親クラスAgentを継承。 select_move( game_state ) 引数: game_state -- GameStateクラスのインスタンス 返り値: Move.play( random.choice( candidates )) -- 選択した指し手 有効な指し手で、眼ではない点ものをリストにして、その中からランダムに 指し手を選ぶ。 もし、指し手が無ければ、パス。
dlgo/utils.py
from dlgo import gotypes
COLS = 'ABCDEFGHJKLMNOPQRST'
STONE_TO_CHAR = {
None: '.',
gotypes.Player.black: 'x',
gotypes.Player.white: 'o',
}
def print_move( player, move ):
if move.is_pass:
move_str = 'passes'
elif move.is_resign:
move_str = 'resigns'
else:
move_str = '%s%d' % ( COLS[move.point.col - 1], move.point.row )
print( '%s %s' % ( player, move_str ))
def print_board( board ):
for row in range( board.num_rows, 0, -1 ):
bump = " " if row <= 9 else ""
line = []
for col in range( 1, board.num_cols + 1 ):
stone = board.get( gotypes.Point( row=row, col=col )) # <1>
line.append( STONE_TO_CHAR[stone] )
print( '%s%d %s' % (bump, row, ''.join(line)) )
print( ' ' + ''.join(COLS[ :board.num_cols ]))
# <1> board.get -- 盤上の格子に石があれば、その色を返す。なければ None
print_move( player, move ) 引数: player, move 返り値: なし 副作用: 画面にプレーヤーと着手の点を文字情報として表示 print_board( board ) 引数: board -- クラスBoardのインスタンス 返り値: なし 副作用: 画面に盤面情報を表示。
bot_v_bot.py
#from dlgo.agent import base, helpers, naive
from dlgo import agent
from dlgo import goboard_slow
from dlgo import gotypes
from dlgo.utils import print_board, print_move
from explain import explain
import time
import sys
def main():
board_size = 9
game = goboard_slow.GameState.new_game( board_size )
bots = {
gotypes.Player.black: agent.RandomBot(),
gotypes.Player.white: agent.RandomBot(),
}
count = 0
while not game.is_over():
count += 1
time.sleep(0.3)
print( chr(27) + "[2J" )
print_board( game.board ) # <1>
bot_move = bots[ game.next_player ].select_move( game )
print_move( game.next_player, bot_move )
game = game.apply_move( bot_move )
if count is 50: # 50回で止めてみた
sys.exit(0)
# <1> botが着手する前の盤面を描く。
if __name__ == '__main__': # <2>
main()
# <2> このファイルがimportされた場合、「import bot_v_bot」
# __name__には、「bot_v_bot」がはいる。
# もし、「python3 bot_v_bot.py」として起動した場合、
# __name__ には、「__main__」がはいる。
main() 引数: なし 返り値: なし board_sizeを決定 game -- ゲーム状態を新規作成 bots -- 黒と白のボットを作成 geme.is_over() になるまで繰り返す。--- (A) 画面の消去。 盤面( game.board )を描く。 黒あるいは白の指し手を決定。 指し手を文字情報として表示。 指し手を適用して、gameに反映。 (A)にもどる。
以上で、スクリプトは終わり。
実行は、以下。
$ python3 bot_v_bot.py
9 oo..x.o.o
8 oxx...o.x
7 .....o.o.
6 xx.oo.ox.
5 xoo.x..x.
4 xoo.xo.xo
3 xxx.x.xoo
2 x.oxxxooo
1 x..o.x.x.
ABCDEFGHJ
Player.white F6
これは、開始からカウントを数え、50回で止めてみたところである。
また、実行途中のオブジェクトのようすを見るために、以下の関数を使った。
explain.py
# explain.py
# オブジェクトの中身を表示する
# 出典
# python でよくわからないオブジェクトの中身を表示する
# https://qiita.com/halhorn/items/7b8351c5eafbfa28d768
import types
def explain(item, shows_private=False, shows_method=False):
'''
与えた python オブジェクトの詳細を表示します。
'''
print('EXPLAIN ------------------')
print(item)
print(type(item))
print('ATTRIBUTES:')
for d in dir(item):
if d == 'type':
continue
if not shows_private and d.startswith('_'):
continue
attr = getattr(item, d)
if not shows_method and (
isinstance(attr, types.MethodType) or
isinstance(attr, types.BuiltinMethodType) or
isinstance(attr, types.CoroutineType) or
isinstance(attr, types.FunctionType) or
isinstance(attr, types.BuiltinFunctionType) or
isinstance(attr, types.GeneratorType)
):
continue
print('{}:\t{}'.format(d, attr))
たとえば、
from explain import explain
としておいて、
explain(neighbor_string)
sys.exit(0)
などで、みることができる。
このような表示になる。
EXPLAIN ------------------
<dlgo.goboard_slow.GoString object at 0x7f071f8190b8>
<class 'dlgo.goboard_slow.GoString'>
ATTRIBUTES:
color: Player.black
liberties: {Point(row=3, col=7), Point(row=5, col=7), Point(row=4, col=6), Point(row=4, col=8)}
num_liberties: 4
stones: {Point(row=4, col=7)}
(出典) python でよくわからないオブジェクトの中身を表示する
https://qiita.com/halhorn/items/7b8351c5eafbfa28d768