オセロGUI

オセロ(リバーシ)を学習するAIを作成する方法はいくつかありますが、目的(強いAIを作りたいのか、学習プロセスを体験したいのか)や技術レベルによって選ぶアプローチが変わります。以下に代表的な方法を難易度別に紹介します。


目次

🔰 初級:ルールベース + ミニマックス法

特徴:

  • 学習ではなく「強いプレイヤー」を作る方法
  • 実装が比較的簡単

ステップ:

  1. オセロのルールを実装
    • ボード(8×8)
    • 石を置ける場所の判定
    • 石のひっくり返し
    • パス・ゲーム終了など
  2. 評価関数の実装
    • 例: 石の数、角の数、辺の数、位置ごとの価値など
  3. ミニマックス法 + αβ枝刈り
    • 木構造を深さnまで探索し、相手が最善手を選ぶと仮定して評価
    • 速度を上げるためにα-β枝刈りを使う

🧠 中級:強化学習(Q学習)

特徴:

  • 実際にプレイさせて、勝ち負けから学ばせる
  • 基本的なAIの「学習」を体験できる

ステップ:

  1. 状態(State)と行動(Action)を定義
    • 状態:ボードの形(8×8で64マス、黒/白/空白)
    • 行動:合法手(石を置く場所)
  2. Qテーブル(またはQ関数)を作成
    • Q(s, a):状態sで行動aを取ったときの「価値」
  3. 報酬設計
    • 勝ったら+1、負けたら-1、引き分けは0など
  4. エージェントを自己対戦させてQ値を更新
    • ε-greedyで探索と活用のバランスを取る

🤖 上級:深層強化学習(Deep Q Network / AlphaZero方式)

特徴:

  • ニューラルネットで状態の評価を学習
  • 非常に強いAIが作れる(AlphaZero方式など)
  • 計算資源・実装難易度ともに高め

方法:

  1. ニューラルネットで状態を評価
    • 入力:ボード状態(8×8×チャネル数)
    • 出力:各マスの手の確率 + 勝率予測
  2. 自己対戦で学習データを生成
    • モンテカルロ木探索(MCTS)で良い手を探す
  3. 経験を使ってニューラルネットを更新
    • ポリシー(手の確率)とバリュー(勝率)を同時に学習
  4. 強くなったモデルでまた自己対戦

使用言語・ライブラリの例:

  • Python(おすすめ)
  • NumPy、TensorFlow / PyTorch(深層学習用)
  • pygame(GUI作るなら)

どの方法に興味ある?

もし「実際にコードを見てみたい」とか「簡単なQ学習で作ってみたい」などあれば、その方向でコードやチュートリアルも作れるよ。どのレベルで進めたい?

了解!それじゃあ、PythonでオセロAIを作るステップをわかりやすく、順番に学べるようにしていくね。
まずは基礎から始めて、徐々にAIを入れていくスタイルで進めるのがおすすめ。


✅ ステップ1:オセロの基本ルールをPythonで実装する

まずは「オセロ盤」と「石を置く・裏返す処理」を作ります。

1.1 オセロの盤を作る

import numpy as np

# 0 = 空, 1 = 黒, -1 = 白
board = np.zeros((8, 8), dtype=int)

# 初期配置
board[3, 3] = board[4, 4] = -1  # 白
board[3, 4] = board[4, 3] = 1   # 黒

def print_board(board):
    for row in board:
        print(' '.join(['●' if x==1 else '○' if x==-1 else '.' for x in row]))

print_board(board)

1.2 石を置ける場所の判定 + 裏返し処理(次ステップ)


進め方の提案

以下のような流れで進めるとスムーズです:

  1. 基本ルール実装
    • 盤面の表示
    • 石を置ける合法手の判定
    • 石の裏返し処理
    • ターンの切り替え
    • パスと終了判定
  2. 簡単なAI実装
    • ランダムAI
    • 評価関数 + ミニマックス法
  3. Q学習AI実装
    • Qテーブルベースの自己学習
  4. 深層強化学習(オプション)
    • ニューラルネットによる強化学習

まずはステップ1の「合法手の判定と石の裏返し処理」からやってみる?
それとも、フルでルール部分だけ完成したコードを見たい?

OK!それじゃあ、Pythonでオセロのルールを実装して、2人対戦ができるコードを作ってみよう。
コンソールで交互に「白」「黒」が手を入力して遊べるようになってるよ!


🧩 完成コード(基本ルール + 2人対戦)

import numpy as np

# 定数
EMPTY, BLACK, WHITE = 0, 1, -1
DIRECTIONS = [(-1, -1), (-1, 0), (-1, 1),
              ( 0, -1),          ( 0, 1),
              ( 1, -1), ( 1, 0), ( 1, 1)]

# 初期化
def init_board():
    board = np.zeros((8, 8), dtype=int)
    board[3, 3] = WHITE
    board[3, 4] = BLACK
    board[4, 3] = BLACK
    board[4, 4] = WHITE
    return board

# 盤面表示
def print_board(board):
    print("  " + " ".join(str(i) for i in range(8)))
    for i, row in enumerate(board):
        print(i, end=" ")
        print(" ".join(['●' if x == BLACK else '○' if x == WHITE else '.' for x in row]))

# 範囲内チェック
def in_bounds(x, y):
    return 0 <= x < 8 and 0 <= y < 8

# 石を置けるか確認 + 裏返す石を返す
def get_flips(board, x, y, player):
    if board[x, y] != EMPTY:
        return []

    flips = []
    for dx, dy in DIRECTIONS:
        nx, ny = x + dx, y + dy
        line = []
        while in_bounds(nx, ny) and board[nx, ny] == -player:
            line.append((nx, ny))
            nx += dx
            ny += dy
        if in_bounds(nx, ny) and board[nx, ny] == player and line:
            flips.extend(line)

    return flips

# 合法手リスト
def get_legal_moves(board, player):
    moves = []
    for x in range(8):
        for y in range(8):
            if get_flips(board, x, y, player):
                moves.append((x, y))
    return moves

# 手を打つ
def make_move(board, x, y, player):
    flips = get_flips(board, x, y, player)
    if not flips:
        return False
    board[x, y] = player
    for fx, fy in flips:
        board[fx, fy] = player
    return True

# 終了判定
def is_game_over(board):
    return not get_legal_moves(board, BLACK) and not get_legal_moves(board, WHITE)

# 石の数カウント
def count_pieces(board):
    blacks = np.sum(board == BLACK)
    whites = np.sum(board == WHITE)
    return blacks, whites

# メイン対戦ループ
def play_game():
    board = init_board()
    player = BLACK

    while not is_game_over(board):
        print_board(board)
        moves = get_legal_moves(board, player)

        if not moves:
            print("パス: ", "黒" if player == BLACK else "白")
            player = -player
            continue

        print(f"{'黒●' if player == BLACK else '白○'}の番です。合法手: {moves}")
        try:
            x, y = map(int, input("x y を入力 (例: 2 3): ").split())
        except:
            print("形式が正しくありません。")
            continue

        if (x, y) not in moves:
            print("その位置には置けません。")
            continue

        make_move(board, x, y, player)
        player = -player

    print_board(board)
    black_count, white_count = count_pieces(board)
    print(f"ゲーム終了! 黒: {black_count} 白: {white_count}")
    if black_count > white_count:
        print("黒の勝ち!")
    elif white_count > black_count:
        print("白の勝ち!")
    else:
        print("引き分け!")

# 実行
play_game()

✅ このコードでできること

  • コンソール上で2人対戦できる(白・黒交互に手入力)
  • 合法手チェックや石の裏返しもOK
  • パス処理・ゲーム終了処理あり
  • 結果表示まで完了!

💡 次にやると面白いこと

  • 入力ミス対策の強化(例: 数字以外や範囲外)
  • AIプレイヤーを追加(黒 vs ランダムAI)
  • GUI版(tkinterpygame)で見た目を良くする
  • Q学習を使ってAIに学習させる ←ここもやりたいなら教えて!

続きや改良したい部分があれば、遠慮なく言ってね!どこから進めたい?

このコードは コンソールでのオセロ対戦ですね!
ここからは、これを tkinter を使ってGUI化して、クリック操作で石を置けるようにしてみましょう!


✅ 目的:tkinterでオセロ盤をGUIで表示&操作

以下は、あなたの既存ロジックを活かしつつ tkinter を使って作ったGUIバージョンです。


🧩 完全なサンプルコード(GUI版)

import tkinter as tk
import numpy as np

# 定数
EMPTY, BLACK, WHITE = 0, 1, -1
DIRECTIONS = [(-1, -1), (-1, 0), (-1, 1),
              ( 0, -1),          ( 0, 1),
              ( 1, -1), ( 1, 0), ( 1, 1)]

CELL_SIZE = 60
BOARD_SIZE = 8

# オセロ盤初期化
def init_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 in_bounds(x, y):
    return 0 <= x < 8 and 0 <= y < 8

def get_flips(board, x, y, player):
    if board[x, y] != EMPTY:
        return []
    flips = []
    for dx, dy in DIRECTIONS:
        nx, ny = x + dx, y + dy
        line = []
        while in_bounds(nx, ny) and board[nx, ny] == -player:
            line.append((nx, ny))
            nx += dx
            ny += dy
        if in_bounds(nx, ny) and board[nx, ny] == player and line:
            flips.extend(line)
    return flips

def get_legal_moves(board, player):
    return [(x, y) for x in range(8) for y in range(8) if get_flips(board, x, y, player)]

def make_move(board, x, y, player):
    flips = get_flips(board, x, y, player)
    if not flips:
        return False
    board[x, y] = player
    for fx, fy in flips:
        board[fx, fy] = player
    return True

def count_pieces(board):
    return np.sum(board == BLACK), np.sum(board == WHITE)

# GUI アプリ
class OthelloGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("オセロ")
        self.canvas = tk.Canvas(root, width=CELL_SIZE*8, height=CELL_SIZE*8, bg="darkgreen")
        self.canvas.pack()
        self.canvas.bind("<Button-1>", self.click)
        self.board = init_board()
        self.current_player = BLACK
        self.draw_board()

    def draw_board(self):
        self.canvas.delete("all")
        for i in range(BOARD_SIZE):
            for j in range(BOARD_SIZE):
                x1, y1 = j * CELL_SIZE, i * CELL_SIZE
                x2, y2 = x1 + CELL_SIZE, y1 + CELL_SIZE
                self.canvas.create_rectangle(x1, y1, x2, y2, outline="black")
                if self.board[i, j] == BLACK:
                    self.canvas.create_oval(x1+5, y1+5, x2-5, y2-5, fill="black")
                elif self.board[i, j] == WHITE:
                    self.canvas.create_oval(x1+5, y1+5, x2-5, y2-5, fill="white")

        # プレイヤー表示
        self.root.title(f"オセロ - {'黒●' if self.current_player == BLACK else '白○'}の番")

    def click(self, event):
        row, col = event.y // CELL_SIZE, event.x // CELL_SIZE
        if (row, col) in get_legal_moves(self.board, self.current_player):
            make_move(self.board, row, col, self.current_player)
            self.current_player = -self.current_player
            if not get_legal_moves(self.board, self.current_player):
                self.current_player = -self.current_player
                if not get_legal_moves(self.board, self.current_player):
                    self.end_game()
        self.draw_board()

    def end_game(self):
        black, white = count_pieces(self.board)
        msg = f"ゲーム終了! 黒: {black}, 白: {white}\n"
        if black > white:
            msg += "黒の勝ち!"
        elif white > black:
            msg += "白の勝ち!"
        else:
            msg += "引き分け!"
        self.canvas.unbind("<Button-1>")
        self.canvas.create_text(240, 240, text=msg, fill="yellow", font=("Arial", 20), anchor="center")

# 実行
root = tk.Tk()
app = OthelloGUI(root)
root.mainloop()

✅ できること

  • マスをクリックして石を置ける
  • 白黒交互に自動で切り替わる
  • 合法手のみ置ける
  • パス処理・終了判定あり
  • 勝敗表示もあり!

🔜 次にできること

  • 合法手をハイライト表示
  • AIプレイヤーとの対戦
  • マスの数や色をカスタマイズ
  • ボタンで「リセット」や「戻る」などを追加

試してみてどうだった? 他にも欲しい機能や改良ポイントがあれば教えてね!

了解!それなら、片方のプレイヤー(例えば白)をAIにして、もう片方(黒)は人間が操作するオセロを作りましょう。


✅ AIの内容(今回はシンプルに)

まずは簡単な戦略:

合法手の中から、ひとつランダムに選ぶAI(初心者レベル)

※必要なら強いAI(ミニマックスなど)も後で作れます!


🎮 人間 vs AI(白がAI)のオセロ:コード変更点だけ

あなたの OthelloGUI クラスに以下を加えればOKです👇


🧠 ① random モジュールをインポート

import random

🧠 ② AIの手を選んで打つ関数を作成

def ai_move(self):
    moves = get_legal_moves(self.board, self.current_player)
    if not moves:
        return False
    x, y = random.choice(moves)
    make_move(self.board, x, y, self.current_player)
    self.current_player = -self.current_player
    return True

🧠 ③ click() メソッドにAIを組み込む(人間が黒、AIが白)

def click(self, event):
    row, col = event.y // CELL_SIZE, event.x // CELL_SIZE
    if self.current_player == BLACK:
        if (row, col) in get_legal_moves(self.board, self.current_player):
            make_move(self.board, row, col, self.current_player)
            self.current_player = -self.current_player
            self.draw_board()

            # AIの番(白)に移ったらAIに打たせる
            self.root.after(500, self.run_ai_if_needed)
    self.draw_board()

def run_ai_if_needed(self):
    if self.current_player == WHITE and get_legal_moves(self.board, WHITE):
        self.ai_move()
        self.draw_board()
        # 連続でパスだった場合、黒に戻す
        if not get_legal_moves(self.board, BLACK):
            if not get_legal_moves(self.board, WHITE):
                self.end_game()
            else:
                self.root.after(500, self.run_ai_if_needed)

✅ これで実現できること

  • 黒(人間)はマウスで石を置く
  • 白(AI)は合法手の中からランダムで置く
  • パス処理も含まれている
  • ゲーム終了まで自動進行

🔜 次にやりたいこと

  • 強いAIにしたい(ミニマックス+評価関数)
  • AI同士で対戦
  • ゲームログ保存/再生
  • 「ヒント」機能の追加

必要ならこのコード全体を1つにまとめて渡せるよ!
それとも、次は「強いAI」にレベルアップしてみる?

了解!以下は、人間 vs AI(白) のオセロ完全コードです。
GUIは tkinter、AIはランダムに合法手から選ぶシンプルなものです。


✅ フルコード:GUIオセロ(黒=人間、白=AI)

import tkinter as tk
import numpy as np
import random

# 定数定義
EMPTY, BLACK, WHITE = 0, 1, -1
DIRECTIONS = [(-1, -1), (-1, 0), (-1, 1),
              ( 0, -1),          ( 0, 1),
              ( 1, -1), ( 1, 0), ( 1, 1)]

CELL_SIZE = 60
BOARD_SIZE = 8

# 盤の初期化
def init_board():
    board = np.zeros((8, 8), dtype=int)
    board[3, 3] = WHITE
    board[3, 4] = BLACK
    board[4, 3] = BLACK
    board[4, 4] = WHITE
    return board

def in_bounds(x, y):
    return 0 <= x < 8 and 0 <= y < 8

def get_flips(board, x, y, player):
    if board[x, y] != EMPTY:
        return []
    flips = []
    for dx, dy in DIRECTIONS:
        nx, ny = x + dx, y + dy
        line = []
        while in_bounds(nx, ny) and board[nx, ny] == -player:
            line.append((nx, ny))
            nx += dx
            ny += dy
        if in_bounds(nx, ny) and board[nx, ny] == player and line:
            flips.extend(line)
    return flips

def get_legal_moves(board, player):
    return [(x, y) for x in range(8) for y in range(8) if get_flips(board, x, y, player)]

def make_move(board, x, y, player):
    flips = get_flips(board, x, y, player)
    if not flips:
        return False
    board[x, y] = player
    for fx, fy in flips:
        board[fx, fy] = player
    return True

def count_pieces(board):
    return np.sum(board == BLACK), np.sum(board == WHITE)

# GUIクラス
class OthelloGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("オセロ - 黒(人間) vs 白(AI)")
        self.canvas = tk.Canvas(root, width=CELL_SIZE*8, height=CELL_SIZE*8, bg="darkgreen")
        self.canvas.pack()
        self.canvas.bind("<Button-1>", self.click)
        self.board = init_board()
        self.current_player = BLACK
        tk.Button(root, text="終了", command=root.quit).pack()
        self.draw_board()

    def draw_board(self):
        self.canvas.delete("all")
        for i in range(BOARD_SIZE):
            for j in range(BOARD_SIZE):
                x1, y1 = j * CELL_SIZE, i * CELL_SIZE
                x2, y2 = x1 + CELL_SIZE, y1 + CELL_SIZE
                self.canvas.create_rectangle(x1, y1, x2, y2, outline="black")
                if self.board[i, j] == BLACK:
                    self.canvas.create_oval(x1+5, y1+5, x2-5, y2-5, fill="black")
                elif self.board[i, j] == WHITE:
                    self.canvas.create_oval(x1+5, y1+5, x2-5, y2-5, fill="white")
        self.root.title(f"オセロ - {'黒(人間)●' if self.current_player == BLACK else '白(AI)○'}の番")

    def click(self, event):
        if self.current_player != BLACK:
            return  # 黒(人間)の番以外は無視
        row, col = event.y // CELL_SIZE, event.x // CELL_SIZE
        if (row, col) in get_legal_moves(self.board, self.current_player):
            make_move(self.board, row, col, self.current_player)
            self.current_player = -self.current_player
            self.draw_board()
            self.root.after(500, self.run_ai_if_needed)

    def run_ai_if_needed(self):
        if self.current_player == WHITE:
            if get_legal_moves(self.board, WHITE):
                self.ai_move()
                self.draw_board()
                if not get_legal_moves(self.board, BLACK):
                    if not get_legal_moves(self.board, WHITE):
                        self.end_game()
                    else:
                        self.root.after(500, self.run_ai_if_needed)
            else:
                self.current_player = BLACK
        elif not get_legal_moves(self.board, BLACK):
            if not get_legal_moves(self.board, WHITE):
                self.end_game()

    def ai_move(self):
        moves = get_legal_moves(self.board, self.current_player)
        if moves:
            x, y = random.choice(moves)
            make_move(self.board, x, y, self.current_player)
            self.current_player = -self.current_player

    def end_game(self):
        black, white = count_pieces(self.board)
        msg = f"ゲーム終了! 黒: {black}, 白: {white}\n"
        if black > white:
            msg += "黒の勝ち!"
        elif white > black:
            msg += "白の勝ち!"
        else:
            msg += "引き分け!"
        self.canvas.unbind("<Button-1>")
        self.canvas.create_text(240, 240, text=msg, fill="yellow", font=("Arial", 20), anchor="center")

# 実行
if __name__ == "__main__":
    root = tk.Tk()
    app = OthelloGUI(root)
    root.mainloop()

✅ 実行方法

  1. このコードを othello_gui_ai.py のようなファイル名で保存。
  2. ターミナルで:
python othello_gui_ai.py

✅ 機能まとめ

  • 黒(人間)がマウスで操作
  • 白(AI)が自動で手を選ぶ(ランダム)
  • パス、終了処理、勝敗表示あり
  • 終了ボタン付き

「もっと強いAIにしたい」「ヒント機能がほしい」「AI同士で戦わせたい」などもOK!
次どうしたい?

もちろん!以下が、白(AI)がランダムに手を選んで打つ部分のコードです👇


🧠 該当コード:AIの手を選んで実行する部分

def ai_move(self):
    moves = get_legal_moves(self.board, self.current_player)
    if moves:
        x, y = random.choice(moves)
        make_move(self.board, x, y, self.current_player)
        self.current_player = -self.current_player

🔍 1行ずつ丁寧に解説!


moves = get_legal_moves(self.board, self.current_player)

  • 現在の盤面 self.board に対して、
  • 今のプレイヤー(AI=白)の合法手(置ける位置)をすべて取得します。
  • 戻り値は、例えば [(2, 3), (4, 5), ...] のようなリスト。

if moves:

  • 合法手が1つ以上あるときだけ中に入る。
  • 置ける場所がなければ AIはパスになります(何もしない)。

x, y = random.choice(moves)

  • moves の中からランダムに1つを選びます。
  • 例:(2, 3) が選ばれた場合 → (x, y) = (2, 3)

make_move(self.board, x, y, self.current_player)

  • 実際にAIの手を盤面に反映させる関数。
  • 石を置いて、挟んだ相手の石を裏返します。

self.current_player = -self.current_player

  • 手番を交代させます。
  • BLACK = 1WHITE = -1 に、
  • WHITE = -1BLACK = 1 に切り替わります。

✅ 全体の流れ(図解)

AIの番(白)
   ↓
合法手一覧を取得 → 例: [(2, 3), (5, 4), ...]
   ↓
ランダムに1手選ぶ → 例: (2, 3)
   ↓
その位置に置いて、相手の石を裏返す
   ↓
手番交代(→黒の番へ)

🎯 ポイントまとめ

  • random.choice() がランダムAIの心臓部
  • 強くするには → ランダムではなく「評価値に基づいて選ぶ」ようにすればOK

「評価関数で一番良い手を選ぶAI」も欲しければすぐ追加できますよ!
強いAIにしてみますか?

よかったらシェアしてね!
目次