オセロ(リバーシ)を学習するAIを作成する方法はいくつかありますが、目的(強いAIを作りたいのか、学習プロセスを体験したいのか)や技術レベルによって選ぶアプローチが変わります。以下に代表的な方法を難易度別に紹介します。
🔰 初級:ルールベース + ミニマックス法
特徴:
- 学習ではなく「強いプレイヤー」を作る方法
- 実装が比較的簡単
ステップ:
- オセロのルールを実装
- ボード(8×8)
- 石を置ける場所の判定
- 石のひっくり返し
- パス・ゲーム終了など
- 評価関数の実装
- 例: 石の数、角の数、辺の数、位置ごとの価値など
- ミニマックス法 + αβ枝刈り
- 木構造を深さnまで探索し、相手が最善手を選ぶと仮定して評価
- 速度を上げるためにα-β枝刈りを使う
🧠 中級:強化学習(Q学習)
特徴:
- 実際にプレイさせて、勝ち負けから学ばせる
- 基本的なAIの「学習」を体験できる
ステップ:
- 状態(State)と行動(Action)を定義
- 状態:ボードの形(8×8で64マス、黒/白/空白)
- 行動:合法手(石を置く場所)
- Qテーブル(またはQ関数)を作成
- Q(s, a):状態sで行動aを取ったときの「価値」
- 報酬設計
- 勝ったら+1、負けたら-1、引き分けは0など
- エージェントを自己対戦させてQ値を更新
- ε-greedyで探索と活用のバランスを取る
🤖 上級:深層強化学習(Deep Q Network / AlphaZero方式)
特徴:
- ニューラルネットで状態の評価を学習
- 非常に強いAIが作れる(AlphaZero方式など)
- 計算資源・実装難易度ともに高め
方法:
- ニューラルネットで状態を評価
- 入力:ボード状態(8×8×チャネル数)
- 出力:各マスの手の確率 + 勝率予測
- 自己対戦で学習データを生成
- モンテカルロ木探索(MCTS)で良い手を探す
- 経験を使ってニューラルネットを更新
- ポリシー(手の確率)とバリュー(勝率)を同時に学習
- 強くなったモデルでまた自己対戦
使用言語・ライブラリの例:
- 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 石を置ける場所の判定 + 裏返し処理(次ステップ)
進め方の提案
以下のような流れで進めるとスムーズです:
- 基本ルール実装
- 盤面の表示
- 石を置ける合法手の判定
- 石の裏返し処理
- ターンの切り替え
- パスと終了判定
- 簡単なAI実装
- ランダムAI
- 評価関数 + ミニマックス法
- Q学習AI実装
- Qテーブルベースの自己学習
- 深層強化学習(オプション)
- ニューラルネットによる強化学習
まずはステップ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版(
tkinter
やpygame
)で見た目を良くする - 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()
✅ 実行方法
- このコードを
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にしてみますか?