import numpy as np
# 盤面の設定
BOARD_SIZE = 8 # 盤面のサイズ(8x8)
EMPTY = '・' # 空白を表す文字
BLACK = 'B' # 黒のコマを表す文字
WHITE = 'W' # 白のコマを表す文字
class Reversi:
def __init__(self):
# 8x8の盤面を作成し、全てを空白で初期化
self.board = np.full((BOARD_SIZE, BOARD_SIZE), EMPTY)
# 初期配置(中央の4マス)
self.board[3, 3] = WHITE
self.board[3, 4] = BLACK
self.board[4, 3] = BLACK
self.board[4, 4] = WHITE
self.current_player = BLACK # 現在の手番プレイヤー
self.cpu_player = None # CPUが担当するコマの色
def display_board(self):
# 盤面を全角文字で表示
# 横軸の数字を全角に変換
horiz_coords = "".join([str(i) for i in range(BOARD_SIZE)])
horiz_coords_fullwidth = horiz_coords.replace("0", "0").replace("1", "1").replace("2", "2").replace("3", "3").replace("4", "4").replace("5", "5").replace("6", "6").replace("7", "7")
print("#" + horiz_coords_fullwidth)
# 縦軸の数字を全角に変換
for i, row in enumerate(self.board):
vert_coord_fullwidth = str(i).replace("0", "0").replace("1", "1").replace("2", "2").replace("3", "3").replace("4", "4").replace("5", "5").replace("6", "6").replace("7", "7")
print(vert_coord_fullwidth + "".join(row))
# 指定された座標にコマを置けるかどうかを判定
def is_valid_move(self, x, y, player):
# 座標が盤面内か、かつ、その場所が空白かを確認
if not (0 <= x < BOARD_SIZE and 0 <= y < BOARD_SIZE and self.board[y, x] == EMPTY):
return False
# 8方向を探索して、ひっくり返せるコマがあるか確認
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx, ny = x + dx, y + dy
count = 0
# 相手のコマが連続して続くかを確認
while 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[ny, nx] != player and self.board[ny, nx] != EMPTY:
nx += dx
ny += dy
count += 1
# 相手のコマが1つ以上あり、その先に自分のコマがあれば有効な手
if count > 0 and 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[ny, nx] == player:
return True
return False
# 現在のプレイヤーが置ける場所のリストを取得
def get_valid_moves(self, player):
moves = []
for y in range(BOARD_SIZE):
for x in range(BOARD_SIZE):
if self.is_valid_move(x, y, player):
moves.append((x, y))
return moves
# 実際にコマを置いて、ひっくり返す処理
def make_move(self, x, y, player):
if not self.is_valid_move(x, y, player):
return False
self.board[y, x] = player
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx, ny = x + dx, y + dy
to_flip = []
# 相手のコマを一時的にリストに格納
while 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[ny, nx] != player and self.board[ny, nx] != EMPTY:
to_flip.append((nx, ny))
nx += dx
ny += dy
# 自分のコマで挟めていれば、相手のコマをひっくり返す
if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[ny, nx] == player:
for fx, fy in to_flip:
self.board[fy, fx] = player
return True
# 現在のコマ数を数える
def get_score(self):
black_count = np.sum(self.board == BLACK)
white_count = np.sum(self.board == WHITE)
return black_count, white_count
# ゲームが終了したかを判定(両プレイヤーが置ける場所がない場合)
def is_game_over(self):
return not self.get_valid_moves(BLACK) and not self.get_valid_moves(WHITE)
# プレイヤーの交代
def switch_player(self):
self.current_player = BLACK if self.current_player == WHITE else WHITE
# 確定石の数を評価する関数
def evaluate_stable_stones(self, player):
opponent = BLACK if player == WHITE else WHITE
stable_count = 0
# 角の石はひっくり返されないため、重要度が高い
corners = [(0, 0), (0, 7), (7, 0), (7, 7)]
for y, x in corners:
if self.board[y, x] == player:
stable_count += 10
elif self.board[y, x] == opponent:
stable_count -= 10
# 辺の石(簡易評価)
edges = []
for i in range(8):
edges.append((0, i))
edges.append((7, i))
edges.append((i, 0))
edges.append((i, 7))
for y, x in edges:
if self.board[y, x] == player:
stable_count += 1
elif self.board[y, x] == opponent:
stable_count -= 1
return stable_count
# ミニマックス法
def minimax(self, depth, is_maximizing_player):
# 探索深度が0、またはゲームが終了したら評価値を返す
if depth == 0 or self.is_game_over():
return self.evaluate_stable_stones(self.cpu_player)
# 現在の手番で置ける場所がなければパス
current_moves = self.get_valid_moves(self.current_player)
if not current_moves:
self.switch_player()
score = self.minimax(depth - 1, not is_maximizing_player)
self.switch_player()
return score
# CPU(最大化プレイヤー)のターン
if is_maximizing_player:
max_eval = -float('inf')
for x, y in current_moves:
original_board = self.board.copy()
self.make_move(x, y, self.current_player)
self.switch_player()
eval = self.minimax(depth - 1, False) # 相手の手番(最小化)へ
self.board = original_board
self.current_player = self.cpu_player
max_eval = max(max_eval, eval)
return max_eval
# 相手プレイヤー(最小化プレイヤー)のターン
else:
min_eval = float('inf')
for x, y in current_moves:
original_board = self.board.copy()
self.make_move(x, y, self.current_player)
self.switch_player()
eval = self.minimax(depth - 1, True) # CPUの手番(最大化)へ
self.board = original_board
self.current_player = BLACK if self.cpu_player == WHITE else WHITE
min_eval = min(min_eval, eval)
return min_eval
# ミニマックス法を使って最善の手を探索
def find_best_move(self):
best_eval = -float('inf')
best_move = None
possible_moves = self.get_valid_moves(self.cpu_player)
if not possible_moves:
return None
# すべての可能な手に対して評価値を計算
for x, y in possible_moves:
original_board = self.board.copy()
self.make_move(x, y, self.cpu_player)
self.switch_player()
# 3手読みなので深さ2(現在の手を含めると3手)でミニマックスを呼び出し
eval = self.minimax(2, False)
self.board = original_board
self.current_player = self.cpu_player
if eval > best_eval:
best_eval = eval
best_move = (x, y)
return best_move
# ゲームのメインループ
def play(self):
while True:
player_choice = input("先手(B)で良いですか?(y/n): ").lower()
if player_choice == 'y':
self.cpu_player = WHITE
print("先手(B)になりました。")
break
elif player_choice == 'n':
self.cpu_player = BLACK
print("後手(W)になりました。")
break
else:
print("無効な入力です。'y'か'n'を入力してください。")
while not self.is_game_over():
self.display_board()
# プレイヤーのターン
if self.current_player != self.cpu_player:
print(f"貴方の番です({self.current_player})。")
valid_moves = self.get_valid_moves(self.current_player)
if not valid_moves:
print("置ける場所がないため、パスします。")
self.switch_player()
continue
while True:
try:
y = int(input("縦:"))
x = int(input("横:"))
if (x, y) in valid_moves:
self.make_move(x, y, self.current_player)
break
else:
print("無効な座標です。もう一度入力してください。")
except (ValueError, IndexError):
print("無効な入力です。数字を入力してください。")
# CPUのターン
else:
print(f"CPUの番です({self.current_player})。")
valid_moves = self.get_valid_moves(self.current_player)
if not valid_moves:
print("置ける場所がないため、CPUはパスします。")
self.switch_player()
continue
best_move = self.find_best_move()
if best_move:
x, y = best_move
print(f"CPUは({y}, {x})に置きます。")
self.make_move(x, y, self.current_player)
else:
print("CPUはパスします。")
self.switch_player()
print("ゲーム終了!")
self.display_board()
black_score, white_score = self.get_score()
print(f"黒コマ:{black_score}")
print(f"白コマ:{white_score}")
if black_score > white_score:
print("黒の勝利です!")
elif white_score > black_score:
print("白の勝利です!")
else:
print("引き分けです。")
while True:
retry = input("リトライしますか?(y/n) ").lower()
if retry == 'y':
print("ゲームを再開します。")
self.__init__() # ゲームを初期化して再開
self.play()
break
elif retry == 'n':
print("ゲームを終了します。")
break
else:
print("無効な入力です。'y'か'n'を入力してください。")
if __name__ == "__main__":
game = Reversi()
game.play()