pythonの学習がてらリバーシを作ってみる

Pythonの学習の一環としてリバーシを作成してみたので、どのように実装したのか書いていきます。

※この記事のコードをリファクタしたもの↓
[以前pythonを使ってリバーシを作成したコードを修正した](https://qiita.com/Miyakepp_098/items/ec17b7e3a776d51f45ea)


## 今回作成したコードを実行したもの
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1477630/1a2f03a3-4365-5b80-b0fb-8d73b2b48bc8.png)


## 作成したコード

```python
import numpy as np

# 定数宣言
BLACK = 1
WHITE = -1
EMPTY = 0

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

# 8x8のボードの初期状態を作成
def create_board():
    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(board):
    symbols = {
        EMPTY: "□",  # 空
        BLACK: "○",  # 黒
        WHITE: "●"   # 白
    }
    
    for row in board:
        row_symbols = [symbols[cell] for cell in row]
        print(" ".join(row_symbols))

# 指定した座標がボードの範囲内かどうかをチェック
def is_on_board(x, y):
    return 0 <= x < 8 and 0 <= y < 8

# 石を挟めるかどうかを確認する
def can_flip(board, x, y, current_color):
    if board[x][y] != EMPTY:
        return False
    
    opponent_color = -current_color
    for dx, dy in DIRECTIONS:
        nx, ny = x + dx, y + dy
        stones_to_flip = []
        while is_on_board(nx, ny) and board[nx][ny] == opponent_color:
            stones_to_flip.append((nx, ny))
            nx += dx
            ny += dy
        if is_on_board(nx, ny) and board[nx][ny] == current_color and len(stones_to_flip) > 0:
            return True
    return False

# 石を置いて反転させる
def place_and_flip_stones(board, x, y, current_color):
    if not can_flip(board, x, y, current_color):
        return False
    
    opponent_color = -current_color
    board[x][y] = current_color
    
    for dx, dy in DIRECTIONS:
        nx, ny = x + dx, y + dy
        stones_to_flip = []
        while is_on_board(nx, ny) and board[nx][ny] == opponent_color:
            stones_to_flip.append((nx, ny))
            nx += dx
            ny += dy
        if is_on_board(nx, ny) and board[nx][ny] == current_color:
            for flip_x, flip_y in stones_to_flip:
                board[flip_x][flip_y] = current_color
    return True

def can_place_stone(board, current_color):
    return np.any([can_flip(board, x, y, current_color) for x in range(8) for y in range(8)])

def find_valid_positions(board, current_color):
    valid_positions = []
    for x in range(8):
        for y in range(8):
            if can_flip(board, x, y, current_color):
                valid_positions.append((x, y))
    return valid_positions



# ゲーム開始
def game_loop():
    board = create_board()
    current_color = BLACK  # ゲーム開始は黒から
    
    while True:
        print_board(board)

        # 現在のプレイヤーに有効な手がなければターンを交代
        if not can_place_stone(board, current_color):
            print(f"{'黒' if current_color == BLACK else '白'}は有効な手がありません")
            current_color = -current_color

            # 交代後のプレイヤーも有効な手がなければゲーム終了
            if not can_place_stone(board, current_color):
                print("ゲーム終了！")
                break
            continue
        
        print(f"\n{'黒' if current_color == BLACK else '白'}のターンです:")
        
        # 有効な手を表示
        valid_positions = find_valid_positions(board, current_color)
        print(f"有効な手: {', '.join(f'{x}-{y}' for x, y in valid_positions)}")
        
        try:
            x, y = map(int, input("行と列を入力してください (例: 2-1): ").split('-'))
        except ValueError:
            print("無効な入力です！")
            continue

        if is_on_board(x, y) and place_and_flip_stones(board, x, y, current_color):
            current_color = -current_color
        else:
            print("無効な手です。もう一度試してください！")

    # ゲーム終了後の結果表示
    print_board(board)
    black_count = np.sum(board == BLACK)
    white_count = np.sum(board == WHITE)
    
    # 勝者の判定
    if black_count > white_count:
        result = "勝利:黒"
    elif white_count > black_count:
        result = "勝利:白"
    else:
        result = "引き分け"

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


# ゲーム開始
game_loop()

```

## コードの解説
#### 定数宣言
```python
BLACK = 1
WHITE = -1
EMPTY = 0
```

- BLACK: 黒の石を1で表す
- WHITE: 白の石を-1で表す
- EMPTY: 空いているマスを0で表す



#### 方向定義
```python
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)
])

```
- 各方向への移動ベクトルを示している

#### ボードの作成
```python
def create_board():
    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

```
- create_board: 8x8のボードを作成し、中央の4つのマスに初期状態（黒と白）を配置する関数


#### ボードの表示
```python
def print_board(board):
    symbols = {
        EMPTY: "□",  # 空
        BLACK: "○",  # 黒
        WHITE: "●"   # 白
    }
    
    for row in board:
        row_symbols = []
        for cell in row:
            row_symbols.append(symbols[cell])
        print(" ".join(row_symbols))

```
- print_board: 盤面を視覚的に表示する関数。BLACK, WHITE, EMPTY の値をそれぞれ「○」「●」「□」として表示する

#### ボード範囲チェック

```python
def is_on_board(x, y):
    return 0 <= x < 8 and 0 <= y < 8

```
- is_on_board: 与えられた座標 (x, y) が8x8のボードの範囲内にあるかどうかをチェックする関数。
#### 石をひっくり返せるか確認
```python
def can_flip(board, x, y, current_color):
    if board[x][y] != EMPTY:
        return False
    
    opponent_color = -current_color
    for dx, dy in DIRECTIONS:
        nx, ny = x + dx, y + dy
        stones_to_flip = []
        while is_on_board(nx, ny) and board[nx][ny] == opponent_color:
            stones_to_flip.append((nx, ny))
            nx += dx
            ny += dy
        if is_on_board(nx, ny) and board[nx][ny] == current_color and len(stones_to_flip) > 0:
            return True
    return False

```
- can_flip: 指定した場所 (x, y) に石を置くことで、相手の石を挟んでひっくり返すことができるかをチェックする関数
- 現在のマスが空いていない場合はFalseを返す
- 8つの方向に対して、相手の石が続いているかを確認し、その後自分の石があるかをチェック
- DIRECTIONSは、最初の方で定義したボード上の8つの方向を表す
- stones_to_flip は、相手の石を挟むために必要な石の位置を保存するリスト
- 全ての方向をチェックしても挟める石がない場合、すべての方向で相手の石を挟むことができなかった場合、指定された位置に石を置いても反転する石がないためFalseを返す


#### 石を置いて反転

```python
def place_and_flip_stones(board, x, y, current_color):
    if not can_flip(board, x, y, current_color):
        return False
    
    opponent_color = -current_color
    board[x][y] = current_color
    
    for dx, dy in DIRECTIONS:
        nx, ny = x + dx, y + dy
        stones_to_flip = []
        while is_on_board(nx, ny) and board[nx][ny] == opponent_color:
            stones_to_flip.append((nx, ny))
            nx += dx
            ny += dy
        if is_on_board(nx, ny) and board[nx][ny] == current_color:
            for flip_x, flip_y in stones_to_flip:
                board[flip_x][flip_y] = current_color
    return True
```
- place_and_flip_stones: 石を置いて、相手の石をひっくり返す関数
- 石を置く前に can_flip で置けるかを確認し、置ける場合はその位置に石を配置
- その後、8つの方向に対して相手の石をひっくり返す
- stones_to_flip リストに、挟める石の座標を追加
- opponent_color の石が続く限り、リストに座標を追加し続ける
- 最終的に、自分の色の石が続く位置で、リスト stones_to_flip の石を全て current_color に反転させる
- 


#### 有効な手があるか確認
```python
def can_place_stone(board, current_color):
    return np.any([can_flip(board, x, y, current_color) for x in range(8) for y in range(8)])

```
- can_place_stone: 現在のプレイヤーに有効な手があるかどうかを確認する関数。ボード全体をチェックして、置ける場所が一つでもあれば True を返す

#### 有効な手のリストを取得
```python
def find_valid_positions(board, current_color):
    valid_positions = []
    for x in range(8):
        for y in range(8):
            if can_flip(board, x, y, current_color):
                valid_positions.append((x, y))
    return valid_positions

```
- find_valid_positions: 現在のプレイヤーが石を置けるすべての有効な手のリストを返す関数

#### ゲームループ
```python
def game_loop():
    board = create_board()
    current_color = BLACK  # ゲーム開始は黒から
    
    while True:
        print_board(board)

        # 現在のプレイヤーに有効な手がなければターンを交代
        if not can_place_stone(board, current_color):
            print(f"{'黒' if current_color == BLACK else '白'}は有効な手がありません")
            current_color = -current_color

            # 交代後のプレイヤーも有効な手がなければゲーム終了
            if not can_place_stone(board, current_color):
                print("ゲーム終了！")
                break
            continue
        
        print(f"\n{'黒' if current_color == BLACK else '白'}のターンです:")
        
        # 有効な手を表示
        valid_positions = find_valid_positions(board, current_color)
        print(f"有効な手: {', '.join(f'{x}-{y}' for x, y in valid_positions)}")
        
        try:
            x, y = map(int, input("行と列を入力してください (例: 2-1): ").split('-'))
        except ValueError:
            print("無効な入力です！")
            continue

        if is_on_board(x, y) and place_and_flip_stones(board, x, y, current_color):
            current_color = -current_color
        else:
            print("無効な手です。もう一度試してください！")

    # ゲーム終了後の結果表示
    print_board(board)
    black_count = np.sum(board == BLACK)
    white_count = np.sum(board == WHITE)
    
    # 勝者の判定
    if black_count > white_count:
        result = "勝利:黒"
    elif white_count > black_count:
        result = "勝利:白"
    else:
        result = "引き分け"

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

```
- game_loop: ゲームのメインループ。黒から始まり、各プレイヤーが交互にターンを行う
- プレイヤーが有効な手がない場合、ターンを交代
- 両者とも有効な手がない場合、ゲームが終了し、結果を表示

## 最後に
Pythonの基本的な構文、数値計算ライブラリ numpy を学ぶことができた。ゲームロジックの実装を通して、Pythonの基本的な構造やアルゴリズムの理解を深めるのによいかも？