Skip to content

Instantly share code, notes, and snippets.

@shiracamus
Last active February 7, 2025 09:24
Show Gist options
  • Save shiracamus/79f2c74ede6e065478a43e4753742566 to your computer and use it in GitHub Desktop.
Save shiracamus/79f2c74ede6e065478a43e4753742566 to your computer and use it in GitHub Desktop.
import random
import numpy as np
import pygame
from abc import ABC, abstractmethod
class Color:
GREEN = (34, 139, 34)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
HIGHLIGHT = (200, 200, 200, 128) # 半透明のハイライト
TEXT = (255, 255, 255) # スコアの文字色
BG = (50, 50, 50) # スコア表示の背景色
class Piece:
EMPTY, BLACK, WHITE, INVALID = 0, 1, 2, 3
OPPONENT = {BLACK: WHITE, WHITE: BLACK}
CHAR = {EMPTY: ".", BLACK: "X", WHITE: "O"}
class Cell:
SIZE = 60
class OthelloBoard:
SIZE = 8
WIDTH = HEIGHT = SIZE * Cell.SIZE
DIRECTIONS = ((-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1))
def __init__(self, cells=None):
if cells is None:
cells = np.zeros((self.SIZE, self.SIZE), dtype=int)
cells[3, 3], cells[4, 4] = Piece.WHITE, Piece.WHITE
cells[3, 4], cells[4, 3] = Piece.BLACK, Piece.BLACK
self._cells = cells
def copy(self):
"""コピーしたOthelloBoardを返す"""
return OthelloBoard(np.copy(self._cells))
def score(self, piece):
"""pieceの数を返す"""
return np.sum(self._cells == piece)
def __getitem__(self, pos):
"""pos位置の駒を返す、pos位置が不正ならPiece.INVALIDを返す"""
row, col = pos
if 0 <= row < self.SIZE and 0 <= col < self.SIZE:
return self._cells[row, col]
else:
return Piece.INVALID
def movable_places(self, piece):
"""pieceを置ける位置を枚挙する"""
for row in range(self.SIZE):
for col in range(self.SIZE):
if any(self._flipable_places(piece, row, col)):
yield row, col
def _flipable_places(self, piece, row, col):
"""pieceをrow,colに置いたときに挟んだ相手駒の位置(row,col)を枚挙する"""
if self[row, col] == Piece.EMPTY:
opponent = Piece.OPPONENT[piece]
for dr, dc in self.DIRECTIONS:
places = []
r, c = row + dr, col + dc
while self[r, c] == opponent:
places.append((r, c))
r, c = r + dr, c + dc
if self[r, c] == piece:
yield from places
def move(self, piece, row, col):
"""pieceを位置(row,col)に置けるなら相手駒をひっくり返してTrueを返す"""
flip_places = list(self._flipable_places(piece, row, col))
if not flip_places:
return False
self._cells[row, col] = piece
for r, c in flip_places:
self._cells[r, c] = piece
return True
def draw(self, screen):
"""描画する"""
for i in range(1, self.SIZE):
pygame.draw.line(screen, Color.BLACK,
(i * Cell.SIZE, 0),
(i * Cell.SIZE, self.HEIGHT), 2)
pygame.draw.line(screen, Color.BLACK,
(0, i * Cell.SIZE),
(self.WIDTH, i * Cell.SIZE), 2)
for r in range(self.SIZE):
for c in range(self.SIZE):
if self[r, c] == Piece.BLACK:
pygame.draw.circle(screen, Color.BLACK,
(c * Cell.SIZE + Cell.SIZE // 2,
r * Cell.SIZE + Cell.SIZE // 2),
Cell.SIZE // 2 - 5)
elif self[r, c] == Piece.WHITE:
pygame.draw.circle(screen, Color.WHITE,
(c * Cell.SIZE + Cell.SIZE // 2,
r * Cell.SIZE + Cell.SIZE // 2),
Cell.SIZE // 2 - 5)
def print(self):
"""表示する"""
for cells in self._cells:
print(*[Piece.CHAR[piece] for piece in cells])
class Player(ABC):
def __init__(self, piece):
self._piece = piece
self._name = "Black" if piece == Piece.BLACK else "White"
def __str__(self):
return self._name
def can_play(self, board):
"""board上に自駒を置けるならTrueを返す"""
return any(board.movable_places(self._piece))
def play(self, board):
"""パスするかboardに自駒を置き、相手の番になるならTrueを返す"""
if not self.can_play(board):
print(f"{self}: pass")
return True
return board.move(self._piece, *self._select_place(board))
@abstractmethod
def _select_place(self, board):
"""board上の駒を置く位置を選んで返す"""
pass
class Human(Player):
def _select_place(self, board):
"""マウスクリックで駒の置く位置を選択"""
event = pygame.event.wait()
if event.type == pygame.QUIT:
raise KeyboardInterrupt()
if event.type == pygame.MOUSEBUTTONDOWN:
x, y = pygame.mouse.get_pos()
return y // Cell.SIZE, x // Cell.SIZE
return -1, -1 # 無効な位置を返して再選択させる
class RandomAI(Player):
def _select_place(self, board):
"""board上の駒の置ける位置からランダムに選択"""
return random.choice(list(board.movable_places(self._piece)))
class MinmaxAI(Player):
def _select_place(self, board):
"""簡易Minimax: 1手先を読んで一番石が増える位置を選択"""
def simulate_move(place):
temp_board = board.copy()
temp_board.move(self._piece, *place)
return temp_board.score(self._piece)
return max(board.movable_places(self._piece), key=simulate_move)
# AIの種類
AI = {
"0": Human(Piece.WHITE),
"1": RandomAI(Piece.WHITE),
"2": MinmaxAI(Piece.WHITE),
}
class InfoBoard:
WIDTH = 200
@classmethod
def draw(cls, screen, font, player, black_score, white_score):
"""描画する"""
othello_board_size = OthelloBoard.SIZE * Cell.SIZE
pygame.draw.rect(screen, Color.BG, (othello_board_size, 0,
cls.WIDTH, othello_board_size))
screen.blit(font.render(f"Player: {player}", True, Color.TEXT),
(OthelloBoard.SIZE * Cell.SIZE + 20, 50))
screen.blit(font.render(f"Black: {black_score}", True, Color.TEXT),
(OthelloBoard.SIZE * Cell.SIZE + 20, 100))
screen.blit(font.render(f"White: {white_score}", True, Color.TEXT),
(OthelloBoard.SIZE * Cell.SIZE + 20, 150))
class OthelloGame:
def __init__(self, black_player, white_player):
self._board = OthelloBoard()
self._player = black_player
self._turn = {black_player: white_player, white_player: black_player}
def can_play(self):
"""どちらかのプレイヤーが駒を置けるならならTrueを返す"""
return (self._player.can_play(self._board) or
self._turn[self._player].can_play(self._board))
def play(self):
"""プレイヤーが駒を置く。パスするか駒を置いたらプレイヤー交代"""
if self._player.play(self._board):
self._player = self._turn[self._player]
def _scores(self):
"""黒駒と白駒の数を返す"""
return (self._board.score(Piece.BLACK),
self._board.score(Piece.WHITE))
def draw(self, screen, font):
"""描画する"""
screen.fill(Color.GREEN)
self._board.draw(screen)
InfoBoard.draw(screen, font, self._player, *self._scores())
def print_result(self):
"""勝敗結果を表示する"""
self._board.print()
black_score, white_score = self._scores()
print(f"Black({Piece.CHAR[Piece.BLACK]}):{black_score},",
f"White({Piece.CHAR[Piece.WHITE]}):{white_score}")
print("Black Wins!" if black_score > white_score else
"White Wins!" if white_score > black_score else
"It's a Draw!")
class Screen:
WIDTH = OthelloBoard.WIDTH + InfoBoard.WIDTH
HEIGHT = OthelloBoard.HEIGHT
def main():
mode = input("対戦相手選択 (0: 人, 1: ランダムAI, 2: Minimax AI) → ")
pygame.init()
pygame.display.set_caption("オセロ")
screen = pygame.display.set_mode((Screen.WIDTH, Screen.HEIGHT))
font = pygame.font.Font(None, 36)
try:
game = OthelloGame(Human(Piece.BLACK), AI.get(mode, AI["0"]))
while game.can_play():
game.play()
game.draw(screen, font)
pygame.display.flip()
game.print_result()
except KeyboardInterrupt:
pass
pygame.quit()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment