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()