以前の記事でPythonを使ってリバーシを作成したコードを修正しました。
記事 : 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. コードの整理
- 各機能をクラス内のメソッドに分割し、コードを整理
- 同様の処理を行っている部分がメソッドとして共通化し、重複したコードを削減