1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-09-22

以前の記事でPythonを使ってリバーシを作成したコードを修正しました。
記事 : pythonの学習がてらリバーシを作ってみる

実行画面

image.png

修正後のコード

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. コードの整理

  • 各機能をクラス内のメソッドに分割し、コードを整理
  • 同様の処理を行っている部分がメソッドとして共通化し、重複したコードを削減
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?