この記事の目的

スライドパズルAIの実験に使ったソースコード全体を公開します。ファイルを開いたときだけコードを読み込みます。

使用したモデル

  • DQN: Stable-Baselines3。観測は 4×4 のタイル番号、行動は空きマスの上下左右の4種類、報酬はタイルごとに正しい位置まで残ったマンハッタン距離を足した値の負数です。1回目の記事の無限の繰り返しは、この報酬から生まれました。
  • 模倣学習(BC): 人が直接解いた記録と、C++ の教師(A*)が作った解答を DQN のポリシーに先に学習
  • Planner・Executor: PyTorch の CNN 二つ(畳み込み 32→64 チャネル)。Planner は次にそろえるタイルを、Executor はそのタイルを動かす一手を選びます。ポリシーが間違えた局面を教師の答えでラベル付けし直す DAgger でデータを補強しました。
  • ワールドモデル: C++ エンジンで先の手をシミュレーションし、盤の進み具合(0〜1)を予測する ValueNet の点数で手を選びます。
  • VLM: Hugging Face の Qwen2.5-VL-3B-Instruct。パズルの盤の画像と打てる手のリストを渡し、次の手をJSONで受け取りました。

補助として使ったもの

  • C++ の教師(PuzzleTeacher・Planner・Executor): デモの生成と採点の基準。研究モードでは、エージェントが出した手を上書きしません。
  • agent_loop_guard: DQN・VLM が同じ盤を繰り返したら、マンハッタン距離を減らす手で抜け出させる安全装置
  • Webゲームのお任せ解答: 学習モデルではなく、ブラウザ内で動く重み付き A* 探索(マンハッタン距離 + 線形衝突)

もう一度動かした結果

模倣学習は40回反復しても、教師と同じ手を選んだ割合が71.1%で止まりました。

模倣学習の正答率。教師(A*)の解答 71.1%、Executor の解答 63.1%
模倣学習の正答率。教師(A*)の解答 71.1%、Executor の解答 63.1%

80手シャッフルした盤を同じ条件でもう一度解かせました。学習したモデルは1局も終えられず、ループのルールをコードで入れた C++ Executor だけがすべて解きました。

80手シャッフルした盤を最後までそろえた割合。DQN・マンハッタン・ランダム 0/50、Planner・Executor のニューラルネット 0/10、C++ Executor 50/50
80手シャッフルした盤を最後までそろえた割合。DQN・マンハッタン・ランダム 0/50、Planner・Executor のニューラルネット 0/10、C++ Executor 50/50

Planner・Executor のニューラルネットは、次のタイル選びで教師と83%、一手選びで40%一致し、1行目すら最後までそろえられませんでした。ワールドモデルは10局すべてを解きましたが、ValueNet を切って動かしても手順はまったく同じでした。盤を解いたのは ValueNet ではなく、内部で動く C++ Executor です。

Qwen2.5-VL-3B には盤の絵と打てる手を渡し、毎手直接尋ねました。4手しかシャッフルしていない盤も解けませんでした。20局すべてで2マスを往復するか、2×2 の4マスを延々と回し続けました。ループの回し方は知っていても、いつ止めるべきかは分かっていません。

Qwen2.5-VL-3B の繰り返しパターン。4手シャッフル 往復4・回転6、10手シャッフル 往復2・回転8、完成 0
Qwen2.5-VL-3B の繰り返しパターン。4手シャッフル 往復4・回転6、10手シャッフル 往復2・回転8、完成 0

フォルダ構成

  • src: Qt6/C++ のパズルエンジン、Python 用の C API、Planner・Executor・Teacher、Qt の画面と TCP 学習サーバー
  • ルートの .py: Gymnasium 環境、デモの生成、DQN・BC・Planner/Executor・ワールドモデルの学習、Qt 接続クライアント、評価シミュレーション
  • web: Next.js のWebパズル画面

サーバーのデプロイ設定、監視・自動化スクリプト、学習データとモデルの重み、パズルの絵のファイルは公開範囲から除外しました。広告のようなWeb運用のコードと設定ファイル、デバッグ用ツールも除いたため、一部の import と README の scripts のパスは公開していないファイルを指しています。

全ソース

73ファイル · 19,290行 — フォルダを開いてファイルを押すとコードを読み込みます。

プロジェクトルート41ファイル
agent_loop_guard.pypython · 275行 · 9KB

コードを読み込んでいます…

CMakeLists.txttext · 64行 · 2KB

コードを読み込んでいます…

dqn_sim.pypython · 257行 · 9KB

コードを読み込んでいます…

engine_world_model.pypython · 256行 · 8KB

コードを読み込んでいます…

generate_executor_demos.pypython · 128行 · 4KB

コードを読み込んでいます…

generate_pe_recovery_demos.pypython · 199行 · 6KB

コードを読み込んでいます…

generate_planner_demos.pypython · 77行 · 2KB

コードを読み込んでいます…

generate_teacher_demos.pypython · 207行 · 6KB

コードを読み込んでいます…

generate_wm_labels.pypython · 101行 · 3KB

コードを読み込んでいます…

local_puzzle_env.pypython · 223行 · 8KB

コードを読み込んでいます…

pe_dagger_collect.pypython · 217行 · 8KB

コードを読み込んでいます…

pe_nets.pypython · 145行 · 5KB

コードを読み込んでいます…

pe_segment_sim.pypython · 159行 · 5KB

コードを読み込んでいます…

pe_sim.pypython · 243行 · 8KB

コードを読み込んでいます…

pe_targets.pypython · 71行 · 2KB

コードを読み込んでいます…

plan_agent.pypython · 310行 · 11KB

コードを読み込んでいます…

plan_obs.pypython · 18行 · 661B

コードを読み込んでいます…

plan_puzzle_env.pypython · 101行 · 4KB

コードを読み込んでいます…

plan_sim.pypython · 262行 · 8KB

コードを読み込んでいます…

planner_obs.pypython · 17行 · 556B

コードを読み込んでいます…

PROJECT_PURPOSE.mdmarkdown · 149行 · 7KB

コードを読み込んでいます…

puzzle_c_bindings.pypython · 193行 · 7KB

コードを読み込んでいます…

qt_dqn_sim.pypython · 293行 · 10KB

コードを読み込んでいます…

qt_pe_client.pypython · 738行 · 23KB

コードを読み込んでいます…

qt_rl_client.pypython · 198行 · 7KB

コードを読み込んでいます…

qt_vlm_client.pypython · 494行 · 18KB

コードを読み込んでいます…

qt_vlm_sim.pypython · 324行 · 11KB

コードを読み込んでいます…

qt_wm_client.pypython · 200行 · 7KB

コードを読み込んでいます…

README.mdmarkdown · 139行 · 5KB

コードを読み込んでいます…

requirements-vlm.txttext · 8行 · 153B

コードを読み込んでいます…

requirements.txttext · 4行 · 40B

コードを読み込んでいます…

segment_plan_ko.pypython · 42行 · 2KB

コードを読み込んでいます…

train_bc_from_demos.pypython · 145行 · 5KB

コードを読み込んでいます…

train_pe.pypython · 327行 · 11KB

コードを読み込んでいます…

train_plan_bc.pypython · 152行 · 5KB

コードを読み込んでいます…

train_puzzle_rl.pypython · 358行 · 13KB

コードを読み込んでいます…

train_world_model.pypython · 135行 · 5KB

コードを読み込んでいます…

wm_nets.pypython · 54行 · 2KB

コードを読み込んでいます…

world_model_agent.pypython · 183行 · 7KB

コードを読み込んでいます…

world_model_sim.pypython · 135行 · 4KB

コードを読み込んでいます…

world_obs.pypython · 38行 · 1KB

コードを読み込んでいます…

src/19ファイル
src/demo_sim.cppc++ · 540行 · 18KB

コードを読み込んでいます…

src/main_window.cppc++ · 2,089行 · 66KB

コードを読み込んでいます…

src/main_window.hc++ · 196行 · 7KB

コードを読み込んでいます…

src/main.cppc++ · 68行 · 2KB

コードを読み込んでいます…

src/puzzle_c_api.cppc++ · 400行 · 12KB

コードを読み込んでいます…

src/puzzle_engine.cppc++ · 546行 · 15KB

コードを読み込んでいます…

src/puzzle_engine.hc++ · 110行 · 4KB

コードを読み込んでいます…

src/puzzle_executor.cppc++ · 519行 · 19KB

コードを読み込んでいます…

src/puzzle_executor.hc++ · 53行 · 2KB

コードを読み込んでいます…

src/puzzle_plan_log.hc++ · 24行 · 547B

コードを読み込んでいます…

src/puzzle_planner.cppc++ · 4,414行 · 152KB

コードを読み込んでいます…

src/puzzle_planner.hc++ · 53行 · 2KB

コードを読み込んでいます…

src/puzzle_solve_waves.cppc++ · 110行 · 3KB

コードを読み込んでいます…

src/puzzle_solve_waves.hc++ · 33行 · 1KB

コードを読み込んでいます…

src/puzzle_teacher.cppc++ · 748行 · 22KB

コードを読み込んでいます…

src/puzzle_teacher.hc++ · 31行 · 940B

コードを読み込んでいます…

src/puzzle_tile_action.hc++ · 95行 · 3KB

コードを読み込んでいます…

src/puzzle_widget.cppc++ · 148行 · 4KB

コードを読み込んでいます…

src/puzzle_widget.hc++ · 47行 · 1KB

コードを読み込んでいます…

web/13ファイル
web/app/globals.csscss · 50行 · 912B

コードを読み込んでいます…

web/app/layout.tsxtsx · 33行 · 969B

コードを読み込んでいます…

web/app/page.module.csscss · 173行 · 3KB

コードを読み込んでいます…

web/app/page.tsxtsx · 95行 · 3KB

コードを読み込んでいます…

web/app/play/page.tsxtsx · 338行 · 9KB

コードを読み込んでいます…

web/app/play/play.module.csscss · 152行 · 2KB

コードを読み込んでいます…

web/components/PuzzleBoard.module.csscss · 27行 · 396B

コードを読み込んでいます…

web/components/PuzzleBoard.tsxtsx · 37行 · 1KB

コードを読み込んでいます…

web/lib/levels.tstypescript · 134行 · 4KB

コードを読み込んでいます…

web/lib/progress.tstypescript · 37行 · 1KB

コードを読み込んでいます…

web/lib/puzzle.tstypescript · 145行 · 4KB

コードを読み込んでいます…

web/lib/solver.tstypescript · 181行 · 5KB

コードを読み込んでいます…

web/package.jsonjson · 25行 · 557B

コードを読み込んでいます…