了解!以下は、人間 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()
✅ 実行方法
- このコードを
othello_gui_ai.py
のようなファイル名で保存。 - ターミナルで:
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 = 1
→WHITE = -1
に、WHITE = -1
→BLACK = 1
に切り替わります。
✅ 全体の流れ(図解)
AIの番(白) ↓ 合法手一覧を取得 → 例: [(2, 3), (5, 4), ...] ↓ ランダムに1手選ぶ → 例: (2, 3) ↓ その位置に置いて、相手の石を裏返す ↓ 手番交代(→黒の番へ)
🎯 ポイントまとめ
random.choice()
がランダムAIの心臓部- 強くするには → ランダムではなく「評価値に基づいて選ぶ」ようにすればOK
「評価関数で一番良い手を選ぶAI」も欲しければすぐ追加できますよ!
強いAIにしてみますか?
とても良い質問です!
AI(人工知能)や機械学習を学ぶときは、段階を踏んで進めるのが一番効率的です。
✅ ゴール別で考えると良い
まず、あなたが目指すのはどちらですか?
- 自分でAIプログラムを書きたい(コード重視)
- 理論や仕組みをしっかり理解したい(数学や理論重視)
- 機械学習でアプリやゲームに応用したい(応用重視)
初心者なら、まず「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 Learning | TensorFlow / PyTorch |
⑤ | 実プロジェクトに応用 | Kaggle / GitHub公開 |
📩 もしよければ教えて!
- どんなことにAIを使いたい?
- ゲームAIが好き? 画像? 言語?
- 数学はどのくらい好き or 苦手?
そこを教えてもらえたら、あなた専用の学習プランを作ります!
気軽に答えてね!
Step 3. AI的思考(強化学習やゲームAI) を 組み込む
素晴らしい!このコードはすでにオセロの人間 vs ランダムAIが実装されていますが、ここに 強化学習(Q学習)ベースのAI を組み込むのが Step 3 の挑戦になります。
✅ 方向性:Q学習でAIを強化
まずはシンプルな強化学習(Q学習)で以下のように進めましょう:
【目標】
- AIが「ランダムな手」ではなく、「Q値(勝ちやすさ)に基づいた手」を選ぶ
- 学習フェーズとプレイフェーズを分けて実装
🔁 Step 1:Q学習用クラスの準備
以下を次のステップで作成していきます:
- 状態 → 行動 → 報酬 のQテーブル(辞書)を使って管理
- 学習ループ(ランダム対戦など) で Q 値を更新
- 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_learner
は QLearner
クラス(後で作成)です。
次のステップ:やるべきこと
- ✅ 現在のコードベースはOK
- 🔜 Q学習用の学習コード(CLIでもOK)を作る
- 🔜 学習したQテーブルを保存・読み込みできるようにする
- 🔜 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同士の大会モードとかも作れますよ🔥