できるだけChatGPTだけで作るリバーシ(オセロ)ソフト その3
今回は前回の探索関数、評価関数のざっくりとした修正と、簡易的な評価関数の作成を行います。
前回の記事
MonteCarloクラスの修正
前回の記事では、明確なコードを作ってくれなかったので、Reversiクラスから移植して対応してみました。
そこで、ソースコードを貼り付けて以下の確認を行いました。
返ってきた内容はこんな感じです。
色々返ってきましたが、ざっくり言うと(特に前半は)これじゃ動かないよってことです。
また、評価関数について、今回は簡易的なものを用意しようと以下のお願いを投げてみました。
リバーシでは、必ずではないですがここに置くとあまりよくない、ここに置くといいという目安的な評価が存在していると考えます。それこそ、角を取るといいとかは一番わかりやすいところでしょうか。
今回はこれを事前に定義し、それだけを元にして評価値を算出しようと言うことです。あくまで、簡易的なものです。
で、それを確認した結果が以下の通り。
コード部分は以下の通り
class MonteCarlo:
# ... (省略)
CELL_WEIGHTS = [
[120, -20, 20, 5, 5, 20, -20, 120],
[-20, -40, -5, -5, -5, -5, -40, -20],
[ 20, -5, 15, 3, 3, 15, -5, 20],
[ 5, -5, 3, 3, 3, 3, -5, 5],
[ 5, -5, 3, 3, 3, 3, -5, 5],
[ 20, -5, 15, 3, 3, 15, -5, 20],
[-20, -40, -5, -5, -5, -5, -40, -20],
[120, -20, 20, 5, 5, 20, -20, 120]
]
def evaluate(self, board):
# 各セルの評価値を計算し、合計値を評価値として返す
total_score = 0
for x in range(8):
for y in range(8):
total_score += board[x][y] * self.CELL_WEIGHTS[x][y]
return total_score
# ... (省略)
ロジックとしてはかなりシンプルになったのではないかと思います。
本来ならちゃんと石の数を数えて善し悪しを図るものですが、これだけでもコンピュータの思考っぽくなります。
で、最終的にMonteCarloクラスは以下のようになりました。
class MonteCarlo:
CELL_WEIGHTS = [
[120, -20, 20, 5, 5, 20, -20, 120],
[-20, -40, -5, -5, -5, -5, -40, -20],
[ 20, -5, 15, 3, 3, 15, -5, 20],
[ 5, -5, 3, 3, 3, 3, -5, 5],
[ 5, -5, 3, 3, 3, 3, -5, 5],
[ 20, -5, 15, 3, 3, 15, -5, 20],
[-20, -40, -5, -5, -5, -5, -40, -20],
[120, -20, 20, 5, 5, 20, -20, 120]
]
def __init__(self, board, player, depth):
self.board = board
self.player = player
self.depth = depth
def get_possible_moves(self, board, player):
possible_moves = []
for x in range(8):
for y in range(8):
if self.place_piece(x, y, player, board, actual=False):
possible_moves.append((x, y))
return possible_moves
def place_piece(self, x, y, player, board=None, actual=False):
if board is None:
board = self.board
if x < 0 or x >= 8 or y < 0 or y >= 8 or board[x][y] != 0:
return False
directions = [(0, 1), (1, 0), (0, -1), (-1, 0), (1, 1), (-1, -1), (1, -1), (-1, 1)]
to_flip = []
for dx, dy in directions:
i, j, flips = x + dx, y + dy, []
while 0 <= i < 8 and 0 <= j < 8 and board[i][j] == -player:
flips.append((i, j))
i += dx
j += dy
if 0 <= i < 8 and 0 <= j < 8 and board[i][j] == player and flips:
to_flip.extend(flips)
if not to_flip:
return False
if actual:
board[x][y] = player
for i, j in to_flip:
board[i][j] = player
return True
def simulate(self):
best_move = None
max_score = -float('inf')
for move in self.get_possible_moves(self.board, self.player):
score = self.playout(move, self.depth)
if score > max_score:
max_score = score
best_move = move
return best_move
def playout(self, move, depth):
if depth == 0:
return self.evaluate(self.board)
simulated_board = [row[:] for row in self.board]
x, y = move
self.place_piece(x, y, self.player, board=simulated_board, actual=True)
opponent = -self.player
opponent_moves = self.get_possible_moves(simulated_board, opponent)
if opponent_moves:
opponent_move = random.choice(opponent_moves)
self.place_piece(opponent_move[0], opponent_move[1], opponent, board=simulated_board, actual=True)
return -self.playout(opponent_move, depth - 1)
else:
return self.evaluate(simulated_board) # 対戦相手が行動できない場合は現在のボードの評価を返します
def evaluate(self, board):
# 各セルの評価値を計算し、合計値を評価値として返す
total_score = 0
for x in range(8):
for y in range(8):
total_score += board[x][y] * self.CELL_WEIGHTS[x][y]
return total_score
このあと、新規チャットにてコードの問題点を確認しましたが、速度改善やリファクタリングなどコードの読みやすさの点を指摘されました。
このコードだけをとっても、まだまだ改善はできそうです。
元のコードとセットで動かしてみましたが、現時点ではエラーなく動きそうです。さっと触った感じでは、ランダムの時に比べて格段に強くなっています。
さて、次回はコードのブラッシュアップと、ファミコンソフト「オセロ」との対局をしてみようと思います。
この記事が気に入ったらサポートをしてみませんか?