人間 vs 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にしてみますか?

とても良い質問です!
AI(人工知能)や機械学習を学ぶときは、段階を踏んで進めるのが一番効率的です。


✅ ゴール別で考えると良い

まず、あなたが目指すのはどちらですか?

  1. 自分でAIプログラムを書きたい(コード重視)
  2. 理論や仕組みをしっかり理解したい(数学や理論重視)
  3. 機械学習でアプリやゲームに応用したい(応用重視)

初心者なら、まず「1」や「3」から入るのが楽しく学べておすすめです。


🧠 ステップ別:最も良い学習ルート

🔰 Step 1. Pythonと基礎ライブラリの習得

  • Python:AI/機械学習のメイン言語
  • 必要なライブラリ:
    • numpy(数値計算)
    • matplotlib(グラフ)
    • pandas(データ分析)

👉 ここまでは、あなたがすでにクリア済みっぽいですね!


🤖 Step 2. 機械学習の基礎を学ぶ

使うライブラリ:

  • scikit-learn(初心者に最適!)
  • TensorFlow / PyTorch(本格的にやるなら)

学ぶ内容:

  • 教師あり学習(分類・回帰)
  • 教師なし学習(クラスタリングなど)
  • 評価指標(正解率、混同行列など)

おすすめ教材:


🧠 Step 3. AI的思考(強化学習やゲームAI)

  • 強化学習(Q学習、Deep Q-Network など)
  • 自作オセロAIやチェスAIなどの応用
  • OpenAI Gym を使った実験

オセロやゲーム系に興味があるなら:

  • ✅「Q学習」や「ミニマックス法」がおすすめ
  • 小さい環境(盤面など)で学ぶと分かりやすい!

🎓 Step 4. 本格的なモデル・研究・実装へ

  • ニューラルネットワーク、CNN、RNN
  • 自然言語処理、画像認識、生成AI(GPTなど)
  • Kaggle(コンペ参加)
  • 論文読み(ArXivなど)

🛠️ 具体的な始め方(迷ったらこれ)

ステップやることツール/教材
Pythonの復習Progate / ドットインストール
機械学習の初歩scikit-learn + Jupyter
オセロAIを強化ミニマックス法・Q学習を実装
Deep LearningTensorFlow / PyTorch
実プロジェクトに応用Kaggle / GitHub公開

📩 もしよければ教えて!

  • どんなことにAIを使いたい?
  • ゲームAIが好き? 画像? 言語?
  • 数学はどのくらい好き or 苦手?

そこを教えてもらえたら、あなた専用の学習プランを作ります!
気軽に答えてね!

Step 3. AI的思考(強化学習やゲームAI) を 組み込む

素晴らしい!このコードはすでにオセロの人間 vs ランダムAIが実装されていますが、ここに 強化学習(Q学習)ベースのAI を組み込むのが Step 3 の挑戦になります。


✅ 方向性:Q学習でAIを強化

まずはシンプルな強化学習(Q学習)で以下のように進めましょう:

【目標】

  • AIが「ランダムな手」ではなく、「Q値(勝ちやすさ)に基づいた手」を選ぶ
  • 学習フェーズとプレイフェーズを分けて実装

🔁 Step 1:Q学習用クラスの準備

以下を次のステップで作成していきます:

  1. 状態 → 行動 → 報酬 のQテーブル(辞書)を使って管理
  2. 学習ループ(ランダム対戦など) で Q 値を更新
  3. GUIでは、学習済みのQ値を使って AI が手を選ぶ

🎮 GUIにQ学習を組み込むには?

以下のようにして ai_move() を書き換える形になります:

ランダムAI → Q学習AI に変更(例)

def ai_move(self):
    moves = get_legal_moves(self.board, self.current_player)
    if moves:
        # Qテーブルに基づいて最も良い手を選択
        best_move = self.q_learner.select_best_move(self.board, moves, self.current_player)
        make_move(self.board, *best_move, self.current_player)
        self.current_player = -self.current_player

ここで self.q_learnerQLearner クラス(後で作成)です。


次のステップ:やるべきこと

  1. 現在のコードベースはOK
  2. 🔜 Q学習用の学習コード(CLIでもOK)を作る
  3. 🔜 学習したQテーブルを保存・読み込みできるようにする
  4. 🔜 GUI側にQ値ベースAIを組み込む

✋ 次どうする?

もし「Q学習のコード(学習ループ)を作ってほしい」なら、すぐに書けます。
以下のようなこともできます:

  • Qテーブルの初期化方法
  • 報酬の設計(勝ち/負け/引き分けなど)
  • Q値の更新式
  • 保存/読み込みの仕組み

👇 たとえばこう聞いてくれてもOK:

Q学習でオセロAIを学習させるコードをください(CLIで)

もしくは、

Q値ベースで動くように GUI を拡張してください

どう進めたい?

Q値ベースで動くように GUI を拡張してください

了解です!
では、現在のGUIオセロに Q学習(Q値ベース)AI を組み込む形で拡張していきます。


✅ 今回の実装内容

  • QLearner クラスを追加(Q値の管理、選択、学習)
  • 学習済みQ値をファイルから読み込み(q_table.npy
  • AIは「合法手の中で最もQ値が高い手」を選択
  • ランダム要素を混ぜることも可能(epsilon による制御)

🔧 拡張した全コード(Q値ベースのAI付き)

import tkinter as tk
import numpy as np
import random
import os

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)

# --- Q学習 AI クラス ---
class QLearner:
    def __init__(self, epsilon=0.1):
        self.q_table = self.load_q_table()
        self.epsilon = epsilon  # 探索率

    def board_to_key(self, board):
        return tuple(board.flatten())

    def load_q_table(self):
        if os.path.exists("q_table.npy"):
            return np.load("q_table.npy", allow_pickle=True).item()
        return {}

    def select_best_move(self, board, legal_moves, player):
        state_key = self.board_to_key(board * player)  # 白黒反転で統一
        if random.random() < self.epsilon:
            return random.choice(legal_moves)

        q_values = self.q_table.get(state_key, {})
        best_move = None
        best_score = -float('inf')
        for move in legal_moves:
            score = q_values.get(move, 0.0)
            if score > best_score:
                best_score = score
                best_move = move

        return best_move if best_move else random.choice(legal_moves)

# --- GUI クラス ---
class OthelloGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("オセロ - 黒(人間) vs 白(Q学習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
        self.q_learner = QLearner()
        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:
            move = self.q_learner.select_best_move(self.board, moves, self.current_player)
            make_move(self.board, *move, 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()

🔁 Q値を活かすには?

このコードでは q_table.npy というファイルから Q値を読み込みます。
まだ学習してない場合は空のQ表が使われます。


✏️ 次にやるべきこと

  • Q学習による学習フェーズのコード(別ファイルで実装可能)
  • 勝敗に応じてQ値を更新して保存する
  • 学習後に q_table.npy を使ってプレイ!

必要であれば、次は:

Q値を学習するコードを教えて!

と言ってください。シンプルな学習ループもすぐに用意できます。
学習ループ、可視化、保存の仕組みまでバッチリ作ります!

Q値を学習するコードを教えて!

Q学習(Q-Learning)でオセロAIを学習させる基本コードの構成を紹介します。以下は「人間 vs Q学習AI」ではなく、「AI自身が繰り返し自己対戦してQ値を学習する」コードです。


🔧 Q学習でオセロAIを学習させる基本コード(簡略版)

import numpy as np
import random
import pickle

# 定数定義
EMPTY, BLACK, WHITE = 0, 1, -1
BOARD_SIZE = 8
ALPHA = 0.1       # 学習率
GAMMA = 0.9       # 割引率
EPSILON = 0.1     # 探索率(ε-greedy)

# 方向
DIRECTIONS = [(-1, -1), (-1, 0), (-1, 1),
              ( 0, -1),          ( 0, 1),
              ( 1, -1), ( 1, 0), ( 1, 1)]

# Qテーブル(状態をキー、行動をバリュー)
Q = {}

# 状態の変換(numpy配列→文字列)
def board_to_state(board, player):
    return board.tobytes() + bytes([player])

# 盤面初期化
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

# Q学習の一試合
def play_game():
    board = init_board()
    player = BLACK
    history = []

    while True:
        legal_moves = get_legal_moves(board, player)
        if not legal_moves:
            if not get_legal_moves(board, -player):
                break
            player = -player
            continue

        state = board_to_state(board, player)

        # Qテーブルに状態がなければ初期化
        if state not in Q:
            Q[state] = {move: 0.0 for move in legal_moves}

        # ε-greedyで行動選択
        if random.random() < EPSILON:
            move = random.choice(legal_moves)
        else:
            move = max(Q[state], key=Q[state].get)

        history.append((state, move))
        make_move(board, move[0], move[1], player)
        player = -player

    # 勝敗による報酬(黒が学習対象)
    black_score = np.sum(board == BLACK)
    white_score = np.sum(board == WHITE)
    reward = 1 if black_score > white_score else -1 if black_score < white_score else 0

    # Q値更新(逆順でバックプロパゲーション)
    for state, action in reversed(history):
        if action not in Q[state]:
            Q[state][action] = 0.0
        Q[state][action] += ALPHA * (reward - Q[state][action])
        reward *= GAMMA  # 割引

# 学習ループ
for i in range(10000):
    play_game()
    if i % 1000 == 0:
        print(f"{i}試合完了")

# Qテーブル保存(任意)
with open("q_table.pkl", "wb") as f:
    pickle.dump(Q, f)

🔍 次にやるべきこと

  • GUIに統合してAIがQ値に基づいて動くようにする(→希望があれば追加)
  • エピソード報酬のチューニング(最終スコア、途中の利得など)
  • 学習対象を白に変更するには、rewardの評価を変更

必要なら、これを tkinter の GUI に組み込んで、**「人間 vs 学習済みAI」**にもできます。やってみたいですか?

GUIに統合してAIがQ値に基づいて動くようにする

了解です!それでは、前回のTkinterベースのオセロGUIに「Q値で動くAIプレイヤー(白)」を組み込んだバージョンを紹介します。


✅ Q学習ベースAIを組み込んだGUIオセロ(黒:人間、白:Q学習AI)

以下は「事前に学習済みのQテーブル(q_table.pkl)」を使って、白AIがQ値に基づいて手を打つバージョンです。

import tkinter as tk
import numpy as np
import random
import pickle
import os

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

# 状態変換
def board_to_state(board, player):
    return board.tobytes() + bytes([player])

# 盤面操作
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)

# Qテーブル読み込み
Q = {}
if os.path.exists("q_table.pkl"):
    with open("q_table.pkl", "rb") as f:
        Q = pickle.load(f)
else:
    print("警告: 学習済みQテーブルが見つかりません。")

# GUIクラス
class OthelloGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("オセロ - 黒: 人間 vs 白: Q学習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(8):
            for j in range(8):
                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:
            moves = get_legal_moves(self.board, WHITE)
            if moves:
                self.q_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 q_ai_move(self):
        moves = get_legal_moves(self.board, WHITE)
        state = board_to_state(self.board, WHITE)
        if state in Q:
            q_values = Q[state]
            legal_q = {move: q_values.get(move, 0) for move in moves}
            best_move = max(legal_q, key=legal_q.get)
        else:
            best_move = random.choice(moves)
        make_move(self.board, best_move[0], best_move[1], WHITE)
        self.current_player = -self.current_player

    def end_game(self):
        black, white = count_pieces(self.board)
        msg = f"ゲーム終了! 黒: {black}, 白: {white}\n"
        msg += "黒の勝ち!" if black > white else "白の勝ち!" if white > black else "引き分け!"
        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()

📦 必要なもの

  • q_table.pkl: 事前に学習させたQ値テーブル(前回のコードで作成)
  • numpy, tkinter, pickle(標準ライブラリ)

✅ 次のステップ(希望があれば)

  • 対戦ログの記録
  • Q値の学習をGUI経由でも行えるようにする
  • 強化学習アルゴリズムをDeep Q-Networkに拡張(DQN)
  • 自己対戦モードをGUIに追加

やりたい方向(学習もGUIから行いたい?もっと強いAIを使いたい?)があれば、それに合わせてさらに発展できます!

自己対戦モードをGUIに追加

了解です!以下は、Q学習AIによる自己対戦モード付きの完全なオセロGUIです。人間 vs AI も可能で、ボタンで「自己対戦モード」に切り替えることができます。


✅ 機能概要

  • GUI(Tkinter)
  • Q学習AIによる手の選択(簡易Qテーブル使用)
  • 「自己対戦モード」ボタンでAI vs AIに切り替え
  • 結果を画面中央に表示

🧠 Qテーブルについて

この例では、サンプル用にランダム初期化された Qテーブルを使って AI が手を選びます。実際の Q学習を行ったデータを q_table に差し替えることで、学習済みAIに変更可能です。


✅ フルコード(Python 3)

import tkinter as tk
import numpy as np
import random
import pickle

# 定数
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("オセロ(黒:人間 / 白: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)
        tk.Button(root, text="終了", command=root.quit).pack()
        tk.Button(root, text="自己対戦モード", command=self.start_self_play).pack()
        self.board = init_board()
        self.current_player = BLACK
        self.auto_play = False
        self.q_table = {}  # サンプルとして空のQテーブルを使う(必要に応じて差し替え)
        self.draw_board()

    def draw_board(self):
        self.canvas.delete("all")
        for i in range(8):
            for j in range(8):
                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.auto_play or 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 and not self.auto_play:
            if get_legal_moves(self.board, WHITE):
                self.q_ai_move()
                self.draw_board()
            self.current_player = -self.current_player
            if not get_legal_moves(self.board, BLACK) and not get_legal_moves(self.board, WHITE):
                self.end_game()

    def q_ai_move(self):
        state = tuple(self.board.flatten())
        moves = get_legal_moves(self.board, self.current_player)
        if not moves:
            return
        best_move = None
        best_q = -float('inf')
        for move in moves:
            q = self.q_table.get((state, move), 0)  # Qテーブル参照(未学習なら0)
            if q > best_q:
                best_q = q
                best_move = move
        x, y = best_move if best_move else random.choice(moves)
        make_move(self.board, x, y, self.current_player)

    def start_self_play(self):
        self.auto_play = True
        self.board = init_board()
        self.current_player = BLACK
        self.draw_board()
        self.root.after(500, self.self_play_step)

    def self_play_step(self):
        if not self.auto_play:
            return
        moves = get_legal_moves(self.board, self.current_player)
        if moves:
            state = tuple(self.board.flatten())
            best_move = max(moves, key=lambda m: self.q_table.get((state, m), 0))
            x, y = best_move
            make_move(self.board, x, y, self.current_player)
        self.current_player = -self.current_player
        self.draw_board()
        if not get_legal_moves(self.board, BLACK) and not get_legal_moves(self.board, WHITE):
            self.end_game()
        else:
            self.root.after(300, self.self_play_step)

    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.auto_play = False
        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()

🔁 補足:学習済みQテーブルの読み込み

以下のようにQテーブルをファイルから読み込んで使えます(pickle で保存したもの):

with open("q_table.pkl", "rb") as f:
    self.q_table = pickle.load(f)

必要であれば、「Q学習でこのQテーブルを作るコード」もご用意します!
続きをやりたい内容があれば、何でも教えてください!

Q学習でこのQテーブルを作るコード

いいですね!では、オセロAIのためのQ学習コードをご紹介します。
このコードは、自己対戦によってQテーブルを学習し、pickleで保存できるようになっています。GUIとは独立した学習モジュールです。


✅ 機能概要

  • 盤面状態を tuple(board.flatten()) として Qテーブルの状態に
  • 行動は (x, y) の合法手
  • Q学習の基本式に従って更新
  • 結果を q_table.pkl として保存

📦 依存:標準ライブラリのみ(numpy, pickle


✅ フルコード:Q学習器

import numpy as np
import random
import pickle

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

BOARD_SIZE = 8

# Q学習パラメータ
alpha = 0.1       # 学習率
gamma = 0.9       # 割引率
epsilon = 0.1     # ε-greedy探索率
episodes = 10000  # 学習試行数

# 初期化
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)

# 報酬関数(勝ち = +1、負け = -1、引き分け = 0)
def get_reward(board, player):
    black, white = count_pieces(board)
    if black > white:
        return 1 if player == BLACK else -1
    elif white > black:
        return 1 if player == WHITE else -1
    else:
        return 0

# ε-greedyで行動選択
def select_action(q_table, state, moves):
    if random.random() < epsilon:
        return random.choice(moves)
    q_values = [q_table.get((state, move), 0) for move in moves]
    max_q = max(q_values)
    best_moves = [move for move, q in zip(moves, q_values) if q == max_q]
    return random.choice(best_moves)

# 学習ループ
q_table = {}

for episode in range(episodes):
    board = init_board()
    player = BLACK
    state_history = []

    while True:
        state = tuple(board.flatten())
        moves = get_legal_moves(board, player)
        if not moves:
            player = -player
            if not get_legal_moves(board, player):
                break  # 両者パス → ゲーム終了
            continue

        action = select_action(q_table, state, moves)
        next_board = board.copy()
        make_move(next_board, action[0], action[1], player)
        next_state = tuple(next_board.flatten())
        next_moves = get_legal_moves(next_board, -player)

        # 報酬計算(終局後でなければ中間報酬0)
        reward = 0
        done = False
        if not next_moves and not get_legal_moves(next_board, player):
            reward = get_reward(next_board, player)
            done = True

        # Q更新
        old_q = q_table.get((state, action), 0)
        future_q = 0
        if not done:
            future_q = max([q_table.get((next_state, a), 0) for a in next_moves], default=0)
        new_q = old_q + alpha * (reward + gamma * future_q - old_q)
        q_table[(state, action)] = new_q

        board = next_board
        player = -player

    if (episode + 1) % 1000 == 0:
        print(f"学習中... {episode + 1}/{episodes} エピソード完了")

# 保存
with open("q_table.pkl", "wb") as f:
    pickle.dump(q_table, f)

print("✅ 学習完了!Qテーブルを 'q_table.pkl' に保存しました。")

🔁 次のステップ

  • q_table.pkl を GUI の OthelloGUI に読み込むには:
with open("q_table.pkl", "rb") as f:
    self.q_table = pickle.load(f)

__init__ 内に追加!


✨ 補足(強化学習をさらに進めたい方へ)

  • 状態をもっと抽象化する(左右対称などを考慮)
  • Neural Network を使った Deep Q-Learning への拡張
  • 勝率・評価などのログを追加して学習可視化

やってみて動かなかったり、改良したくなったら、いつでも聞いてください!
学習が進んだら、AI同士の大会モードとかも作れますよ🔥

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