以前pythonを使ってリバーシを作成したコードを修正した

以前の記事でPythonを使ってリバーシを作成したコードを修正しました。
記事 : [pythonの学習がてらリバーシを作ってみる](https://qiita.com/Miyakepp_098/items/c77d12735bb90fb58336)
## 実行画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1477630/75fe331a-2c95-7664-ea7b-d74da091889f.png)

## 修正後のコード
```python
import numpy as np

BLACK = 1
WHITE = -1
EMPTY = 0
PLAYER_NAME_BLACK = "Player1(黒)"
PLAYER_NAME_WHITE = "Player2(白)"
COLUMN_LABELS = ["A", "B", "C", "D", "E", "F", "G", "H"]

DIRECTIONS = np.array([
    (0, 1),   # 右 (Right)
    (1, 0),   # 下 (Down)
    (0, -1),  # 左 (Left)
    (-1, 0),  # 上 (Up)
    (1, 1),   # 右下 (Down-right)
    (1, -1),  # 左下 (Down-left)
    (-1, 1),  # 右上 (Up-right)
    (-1, -1)  # 左上 (Up-left)
])

class Board:
    """ゲームボードを管理するクラス。

    Attributes:
        board (np.ndarray): ボードの状態を表す2次元配列。
    """

    def __init__(self):
        """ボードを初期化し、初期状態を設定する。"""
        self.board = self.create_board()

    def create_board(self):
        """初期のボード状態を作成。

        Returns:
            np.ndarray: 初期状態のボード。
        """
        board = np.zeros((8, 8), dtype=int)
        board[3][3], board[4][4] = WHITE, WHITE
        board[3][4], board[4][3] = BLACK, BLACK
        return board
    
    def print_board(self, valid_positions=None):
        """ボードの状態を表示する。

        Args:
            valid_positions (list, optional): 有効な手の座標リスト。デフォルトはNone。
        """
        symbols = {
            EMPTY: "□",  # 空
            BLACK: "○",  # 黒
            WHITE: "●",  # 白
            "HINT": "⊡"  # 有効な手(ヒント)
        }

        for row in range(8):
            row_symbols = []
            for col in range(8):
                if valid_positions and (row, col) in valid_positions:
                    row_symbols.append(symbols["HINT"])
                else:
                    row_symbols.append(symbols[self.board[row][col]])
            print(" ".join(row_symbols) + f" {row}")

        print(" ".join(COLUMN_LABELS))
        
    def find_valid_positions(self, current_color):
        """現在のプレイヤーが置ける有効な手を全て見つける。

        Args:
            current_color (int): 現在のプレイヤーの色。

        Returns:
            list: 有効な手の座標リスト。
        """
        valid_positions = []
        for row in range(8):
            for col in range(8):
                if self.can_flip(row, col, current_color):
                    valid_positions.append((row, col))
        return valid_positions

    def place_and_flip_stones(self, row, col, current_color):
        """指定した位置に石を置き、挟む石を反転させる。

        Args:
            row (int): 行のインデックス。
            col (int): 列のインデックス。
            current_color (int): 現在のプレイヤーの色。

        Returns:
            bool: 石を置いて反転できた場合はTrue、できなかった場合はFalse。
        """
        stones_to_flip = self.get_stones_to_flip(row, col, current_color)

        if not stones_to_flip:
            return False

        self.board[row][col] = current_color
        for flip_row, flip_col in stones_to_flip:
            self.board[flip_row][flip_col] = current_color

        return True
    
    
    def can_flip(self, row, col, current_color):
        """指定した位置で石を挟むことができるか確認する。

        Args:
            row (int): 行のインデックス。
            col (int): 列のインデックス。
            current_color (int): 現在のプレイヤーの色。

        Returns:
            bool: 石を挟むことができればTrue、そうでなければFalse。
        """
        if self.board[row][col] != EMPTY:
            return False
        return len(self.get_stones_to_flip(row, col, current_color)) > 0
    
    def get_stones_to_flip(self, row, col, current_color):
        """指定した位置で挟むことができる石のリストを取得。

        Args:
            row (int): 行のインデックス。
            col (int): 列のインデックス。
            current_color (int): 現在のプレイヤーの色。

        Returns:
            list: 挟むことができる石の座標リスト。
        """
        opponent_color = -current_color
        stones_to_flip = []

        for d_row, d_col in DIRECTIONS:
            n_row, n_col = row + d_row, col + d_col
            temp_flip = []

            # 相手の石が連続する限り、石をリストに追加
            while self.is_on_board(n_row, n_col) and self.board[n_row][n_col] == opponent_color:
                temp_flip.append((n_row, n_col))
                n_row += d_row
                n_col += d_col

            # 相手の石の後に自分の石があれば、それまでの石を全て反転リストに追加
            if self.is_on_board(n_row, n_col) and self.board[n_row][n_col] == current_color and temp_flip:
                stones_to_flip.extend(temp_flip)

        return stones_to_flip
    
    def is_on_board(self, row, col):
        """指定した座標がボードの範囲内かどうかをチェック。

        Args:
            row (int): 行のインデックス。
            col (int): 列のインデックス。

        Returns:
            bool: 座標がボード内であればTrue、そうでなければFalse。
        """
        return 0 <= row < 8 and 0 <= col < 8

class Player:
    """プレイヤーを表すクラス。

    Attributes:
        name (str): プレイヤー名。
        color (int): プレイヤーの色。
    """

    def __init__(self, name, color):
        """プレイヤーの名前と色を初期化する。

        Args:
            name (str): プレイヤー名。
            color (int): プレイヤーの色。
        """
        self.name = name
        self.color = color

    def get_valid_moves(self, board):
        """現在のプレイヤーの有効な手を取得する。

        Args:
            board (Board): ゲームボードのインスタンス。

        Returns:
            list: プレイヤーの有効な手の座標リスト。
        """
        return board.find_valid_positions(self.color)


class Game:
    """オセロゲームを管理するクラス。

    Attributes:
        board (Board): ゲームボード。
        players (list): プレイヤーのリスト。
        current_player_color (int): 現在のプレイヤーの色。
    """

    def __init__(self):
        """ゲームを初期化し、ボードとプレイヤーを作成する。"""
        self.board = Board()
        self.players = [
            Player(PLAYER_NAME_BLACK, BLACK),
            Player(PLAYER_NAME_WHITE, WHITE)
        ]
        self.current_player_color = BLACK  # 最初のプレイヤーの色

    def switch_turn(self):
        """ターンを交代する。"""
        self.current_player_color = WHITE if self.current_player_color == BLACK else BLACK

    def get_current_player(self):
        """現在のプレイヤーを取得する。

        Returns:
            Player: 現在のプレイヤー。
        """
        for player in self.players:
            if player.color == self.current_player_color:
                return player
        return None

    def play(self):
        """ゲームのメインループを実行する。"""
        while True:
            current_player = self.get_current_player()
            valid_positions = current_player.get_valid_moves(self.board)
            self.board.print_board(valid_positions)

            if not valid_positions:
                print(f"{current_player.name}は有効な手がありません。")
                self.switch_turn()
                other_player = self.get_current_player()
                if not other_player.get_valid_moves(self.board):
                    print("ゲーム終了！")
                    break
                continue

            print(f"\n{current_player.name}のターンです:")
            valid_positions_str = [f"{COLUMN_LABELS[col]}-{row}" for row, col in valid_positions]
            print(f"有効な手: {', '.join(valid_positions_str)}")

            try:
                user_input = input("列と行を入力してください (例: A-2): ")
                col_char, row = user_input.split('-')
                col = COLUMN_LABELS.index(col_char.upper())
                row = int(row)
            except (ValueError, IndexError):
                print("無効な入力です！")
                continue

            if self.board.is_on_board(row, col) and self.board.place_and_flip_stones(row, col, current_player.color):
                self.switch_turn()
            else:
                print("無効な手です。もう一度試してください！")

        # 結果の表示
        self.board.print_board()
        black_count = np.sum(self.board.board == BLACK)
        white_count = np.sum(self.board.board == WHITE)

        if black_count > white_count:
            result = f"勝利: {PLAYER_NAME_BLACK}"
        elif white_count > black_count:
            result = f"勝利: {PLAYER_NAME_WHITE}"
        else:
            result = "引き分け"

        print(f"最終スコア: {PLAYER_NAME_BLACK} {black_count}, {PLAYER_NAME_WHITE} {white_count}")
        print(result)


if __name__ == "__main__":
    game = Game()  # ゲームを開始
    game.play()

```

## 修正箇所

### 1. クラス構造の導入
- Board, Player, Game の3つのクラスを作成
### 2. プレイヤー名の追加
- 出力にプレイヤー名が表示されるように変更
### 3. ボード表示の改善
- 有効な手の位置を示すためのシンボルを追加、行番号と列名の追加
### 4. 入力処理の改善
- rowとcolumnの形式で統一(x, y→row, column)
- 列をアルファベット (A-2) で指定する形式に変更
### 5. docstringの追加
- 関数やクラスが何をするのかを説明するためのコメントを追加
### 6. コードの整理
- 各機能をクラス内のメソッドに分割し、コードを整理
- 同様の処理を行っている部分がメソッドとして共通化し、重複したコードを削減