できるだけChatGPTだけで作るリバーシ(オセロ)ソフト その6(半分自前)
CPUらしく定石を設定しよう
強くするにはどうすればいいかをいくつか考えていましたが、まずは定石を入れようという結論に達しました。
で、ChatGPTに聞いてみたところ、以下のような結果に。
たぶん、この辺りは自前である程度準備しないと無理そうだという結論になりました。
で、今回は序盤定石に関してサイトの情報を基にChatGPTにプログラムを書いてもらおうという方向に。
今回参考にしたのは下記のサイト
ここに書いてある「F5D6~」は将棋でいうところの棋譜で、横軸をa~h、縦軸を1~8として置く石の順番を示しています。
この手順をテキストファイルやDBに登録して、盤面から適用するものを読み出すことで序盤は思考せずに強くできるという寸法です。
データの保持方法
今回はあまり深いことは考えず、わかりやすいようにDBに保持しようと思います。
テーブルとしては、盤面の状態、次に打つ手を主キーとしたテーブルでいいかなと。
まず、盤面の状態。
ここまで書いてきた(ChatGPTに書かせてきた)プログラムでは、盤面全部の配列を持っています。
これをハッシュ化することで、1つの文字列として保持できるので、検索しやすいようにできます。
そして、分岐がある次の手を第2主キーとしてやることで一意の情報ができます。
黒白どちらかなのかは盤面から把握できますが、念のため項目に持たせてもいいかもしれません。
登録するものは正式名称があるものを登録しておくといいでしょう。
探索関数の修正
DBに登録した定石情報がある場合、それをさいようします。複数あればランダムで選ぶようにしましょう。
同一盤面がなくなったところから、これまでの探索関数を使うという形にすると、序盤はほぼノータイムで指すことができるはずです。
ここまでの話をChatGPTで確認する
ここまでの話をChatGPTに投げて、テーブル定義、ハッシュ化、検索などを行っていきます。
最終的に作成したのは、これ。
テーブル定義だけはこちらで行いました。
90度、270度とありますが、実際には斜め対角線上に折り込んだ位置です(最初はこれでめちゃくちゃ苦労した)。
ここの部分を作ってもらうのにとにかく時間がかかったため、なかなか公開できない状態でした。
これをやっておかないと、初手4か所どこに置いても同じ定石になるようにできないためです。
import sqlite3
BOARD_SIZE = 8
def translate_coordinate_pair(coordinate_pair):
x_mapping = {'A': '0', 'B': '1', 'C': '2', 'D': '3',
'E': '4', 'F': '5', 'G': '6', 'H': '7'}
y_mapping = {'1': '0', '2': '1', '3': '2', '4': '3',
'5': '4', '6': '5', '7': '6', '8': '7'}
coordinate_pair = coordinate_pair.upper()
return x_mapping[coordinate_pair[0]] + y_mapping[coordinate_pair[1]]
def rotate_180(coord):
x, y = int(coord[0]), int(coord[1])
# 180度回転の定義(変更なし)
new_x = 7 - x
new_y = 7 - y
return str(new_x) + str(new_y)
def fixed_rotate_90(coord):
x, y = int(coord[0]), int(coord[1])
new_x = y
new_y = x
return str(new_x) + str(new_y)
def fixed_rotate_270(coord):
x, y = int(coord[0]), int(coord[1])
new_x = 7 - y
new_y = 7 - x
return str(new_x) + str(new_y)
def process_line_swapped(line):
n = len(line)
if n % 2 != 0:
raise ValueError("Line length should be even.")
translated_pairs = [translate_coordinate_pair(line[i:i+2]) for i in range(0, n, 2)]
rotated_90_pairs = [fixed_rotate_90(pair) for pair in translated_pairs]
rotated_180_pairs = [rotate_180(pair) for pair in translated_pairs]
rotated_270_pairs = [fixed_rotate_270(pair) for pair in translated_pairs]
return "".join(translated_pairs), "".join(rotated_90_pairs), "".join(rotated_180_pairs), "".join(rotated_270_pairs)
def coordinates_to_move_string(coordinates):
"""
Convert a coordinate string like '5435' to a move string like 'F5D6'.
Args:
- coordinates: a string of concatenated x,y coordinates like '5435'.
Returns:
- A string representing the moves like 'F5D6'.
"""
moves = []
for i in range(0, len(coordinates), 2):
x = coordinates[i]
y = coordinates[i+1]
# Convert to ASCII characters
column = chr(int(x) + ord('A'))
row = str(int(y) + 1)
moves.append(column + row)
return ''.join(moves)
def to_bitboards(board):
white_bitboard = 0
black_bitboard = 0
for i in range(BOARD_SIZE):
for j in range(BOARD_SIZE):
position = i * BOARD_SIZE + j
if board[i][j] == -1: # If it's a white piece
white_bitboard |= (1 << position)
elif board[i][j] == 1: # If it's a black piece
black_bitboard |= (1 << position)
return white_bitboard, black_bitboard
def process_file(input_file):
conn = sqlite3.connect("othello_data.db")
c = conn.cursor()
with open(input_file, 'r') as infile:
board = init_board()
for line in infile:
line = line.strip()
translated, rotated_90, rotated_180, rotated_270 = process_line_swapped(line)
trn = coordinates_to_move_string(translated)
r90 = coordinates_to_move_string(rotated_90)
r180 = coordinates_to_move_string(rotated_180)
r270 = coordinates_to_move_string(rotated_270)
#print(f"Translated: {trn}")
#print(f"Rotated 90: {r90}")
#print(f"Rotated 180: {r180}")
#print(f"Rotated 270: {r270}")
data = [
(translated, trn),
(rotated_90, r90),
(rotated_180, r180),
(rotated_270, r270)
]
for moves, rotation in data:
process_moves(moves, rotation, c)
conn.commit()
conn.close()
def process_moves(moves, rotation, cursor):
idx = 0
board = init_board()
player = 1
while idx < len(moves):
black, white = to_bitboards(board)
if black == 0:
print(board)
x = moves[idx:idx+1]
y = moves[idx+1:idx+2]
make_move(board, int(x), int(y), player)
cursor.execute("INSERT OR IGNORE INTO jouseki VALUES (?, ?, ?)", (black, white, rotation[idx:idx+2]))
idx += 2
player *= -1
def init_board():
board = [[0 for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)]
board[3][3] = -1
board[4][4] = -1
board[3][4] = 1
board[4][3] = 1
return board
def make_move(board, x, y, player):
if place_piece(board, x, y, player, True):
player *= -1
return True
return False
def place_piece(board, x, y, player, actual=False):
# 指定した座標に石を置くためのメソッド
# x, y: 石を置く座標
# player: 現在のプレイヤー (1 または -1)
# actual: 実際に石を置くかどうかを示すフラグ
if x < 0 or x >= BOARD_SIZE or y < 0 or y >= BOARD_SIZE or board[x][y] != 0:
return False
to_flip = []
DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0), (1, 1), (-1, -1), (1, -1), (-1, 1)]
for dx, dy in DIRECTIONS:
i, j, flips = x + dx, y + dy, []
while 0 <= i < BOARD_SIZE and 0 <= j < BOARD_SIZE and board[i][j] == -player:
flips.append((i, j))
i += dx
j += dy
if 0 <= i < BOARD_SIZE and 0 <= j < BOARD_SIZE 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
if __name__ == '__main__':
input_filename = "joseki.txt"
process_file(input_filename)
読み込ませるファイルは
F5D6
F5D6C3
F5D6C3D3C4
F5D6C3D3C4B5
F5D6C3D3C4B3
F5D6C3D3C4B3D2F4
F5D6C3D3C4B3C5
F5D6C3D3C4F4E3
F5D6C3D3C4F4E3F3E6B4
F5D6C3D3C4F4E3F3E6C6
といった感じで、先に紹介したサイトにあるものをテキストエディタなど駆使してシンプルにしたものです。
同じような感じで対局棋譜も読ませてDBに出力させておけば、使えるかなという感じです。
めちゃくちゃ引っ張りましたが、ここからは自前で組み込みながら作っていくことになるので、ひとまずChatGPTだけでのプログラムはいったんここまで。
あとは、この出力したものを利用して序盤定石、中盤の同一局面対応などを組み込んでいけば、そこそこの強さのものができるんじゃないかなと思っています。
ただ、機械学習はここまでが準備。ここから推論で次の1手を打てるかだと思っています。ボチボチ時間を作って自分なりのものを作ろうと思います。
この記事が気に入ったらサポートをしてみませんか?