TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

【バイブコーディング】レトロ戦略ゲームを作る その7(グラフベースマップ生成)

前回、制約ベースのWave Function Collapse (WFC)アルゴリズムでマップの自動生成を試したが、戦略的なマップは生成できなかった。

今回は、グラフベースの手法を試す。

戦略グラフ

グラフベースの手法は、本拠地、争奪エリアのようなノードと、ノードをつなぐエッジで構成される。 事前にゲームとして戦略的になるグラフを構築してから、制約ベースなどの手法で生成を行う。

あらかじめプレイヤーがどこで戦い、どう移動し、何を取り合うかを表現するため、ゲームとして公平で戦略的なマップが生成できる。

バイブコーディング

Pythonでグラフベースでマップ生成を行うスクリプトを生成した。

レトロターン制戦略ゲームのマップを自動生成するツールを作成したい。

マップのフォーマットは、mapsに実際のマップデータがある。

地形の種類には、
plain
mountain
forest
road
beach
river
bridge
sea
があり、建物には、
capital
factory
city
port
airport
がある。
建物には、ownerがあり、占領状態を表す。
capitalは、redとblueで、それぞれ1つずつという制約がある。

ゲームのルールは、design/deep-research-report.mdに記載している。

ゲームのマップは戦略性が重要なため、グラフベースの手法と制約ベースを組み合わせて実現したい。

参考として、生成手法の初期調査レポートは、design/map-generation.mdにある。

まずは、実現可能な実装方針を立ててください。

海の中に川ができたりしたため、いくつか制約を加えたことで、以下のようなマップが生成できるようになった。

改善

未拠点の拠点が少なく、首都が近距離で向かい合っているマップが多かったため、より戦略的なグラフが生成できるように、生成→確認→修正指示を繰り返した。

以下のようなマップが生成できるようになった。

争奪エリアが複数配置されており、ゲームとして遊べるマップになっている。 拠点間の導線も確保されている。

まとめ

グラフベース手法を用いて、戦略性を考慮したマップを生成する手法を試した。 制約ベースと組み合わせることで不自然な地形を改善し、実用的なマップ生成が可能になった。

空港のあるマップで未占領の空港がなかったり、島のマップの地形が単調だったりと、改良の余地はまだあるが、いったん遊べる形になったので、ゲームに組み込むつもりである。

以下は、Codexで生成したアルゴリズムの解説である。

マップ自動生成アルゴリズム実装詳細

概要

本プロジェクトのマップ生成器は、design/map-generation.md の方針に沿って、以下の4段階で動く。

  1. 既存 maps コーパスを分析して、サイズ分布・HQ配置帯・中立施設比率などの事前分布を作る。
  2. 生成対象の ARCHETYPECAPITAL_LAYOUT に応じて、戦略グラフ (StrategyGraph) を組み立てる。
  3. 戦略グラフをヘクスマップ上の骨格地形へ落とし込み、施設・道路・海域・障害地形を配置する。
  4. 後処理で制約違反を修復し、戦略ノードが失われないように安定化させ、最後に validator で検証する。

実装の中心は hf_map_gen/generator.py にあり、グローバル戦略性は戦略グラフ層、局所的な整形は builder / repair 層、既存マップ分布への追従は corpus.py が担当する。

主要モジュール

hf_map_gen/generator.py

生成アルゴリズム本体。以下を持つ。

  • 生成対象定義
    • ARCHETYPES
    • CAPITAL_LAYOUTS
  • 戦略グラフのデータ構造
    • StrategyNode
    • StrategyEdge
    • SeaRegionPlan
    • HQPositionBand
    • ObjectiveTheatre
    • StrategyGraph
  • グリッド編集用の GridBuilder
  • アーキタイプ別の graph 生成
  • stacked / side_by_side それぞれの骨格生成
  • 中立施設配置
  • 道路生成
  • 海・川・橋・港・海岸の修復
  • archipelago 系の専用安定化

hf_map_gen/corpus.py

既存 maps を統計化し、生成時の prior を作る。

主に次を集計する。

  • サイズ頻度
  • 地形比率
  • HQ の cross-axis / rear-depth 帯
  • レイアウト別の中立施設比率
  • 中立施設の帯域分布
  • 中立施設の front cluster profile

hf_map_gen/validate.py

完成マップの静的制約を検証する。

主な検証項目は以下。

  • red / bluecapital がそれぞれ1つずつ存在すること
  • 建物の capturePointsowner 整合
  • factory は必ず所有者を持つこと
  • beach は必ず sea に接すること
  • riversea に3辺以上接しないこと
  • port は十分大きい sea 連結成分に接すること
  • bridge の左右が許可地形集合に入ること
  • 高海率マップに応じた land component 数上限

hf_map_gen/metrics.py

生成結果の戦略的な要約指標を計算する。

  • 収入差
  • 中立都市までの歩兵距離差
  • 空港・港までの距離
  • capital 間の陸路有無
  • sea / mountain
  • chokepoint 数

hf_map_gen/cli.py

CLI の analyze, generate, validate を提供する。

データモデル

StrategyNode

戦略上意味を持つ地点を表す。kindcoord、必要なら side を持つ。

現在使っている代表的な node 種別:

  • hq
  • frontier_hub
  • contested_neutral_cluster
  • approach_neutral_cluster
  • strategic_objective
  • sea_region_anchor
  • beach_landing_zone
  • central_island_objective
  • secondary_island_objective
  • forward_staging_island
  • island_chain
  • naval_neutral_slot
  • home_island_anchor
  • home_neutral_cluster
  • lane_hub
  • open_field_pocket
  • cover_belt
  • soft_barrier
  • flank_pressure_zone
  • urban_core
  • city_belt
  • industrial_pocket
  • road_junction
  • bridge_chokepoint
  • pass_chain
  • highland_basin

StrategyEdge

ノード間の軍事的関係を表す。経路そのものではなく、経路の意味を持つ。

代表例:

  • support_relation
  • contested_front
  • secondary_route
  • advance_lane
  • screen_line
  • delay_barrier
  • flank_route
  • sea_lane

ObjectiveTheatre

中立施設や争奪地帯を、単なる座標1点ではなく「戦域」として扱うための構造。

保持する情報:

  • role
  • anchor
  • slot_coords
  • neutral_budget
  • land_radius
  • required_land_tiles
  • required_slot_count

この構造により、graph が要求する施設数に見合う land capacity を先に地形へ反映できる。

コーパス分析の使い方

生成器は完全ランダムではなく、既存マップから抽出した分布を prior として使う。

サイズ選択

choose_size() は、ユーザーが width / height を指定しない場合、コーパスで頻度が高いサイズから選ぶ。

HQ 位置帯

HQPositionBand は、capital の位置を固定座標ではなく帯域として表す。

  • stacked
    • 横方向のばらつき
    • 上下配置時の後方深度
  • side_by_side
    • 縦方向のばらつき
    • 左右配置時の後方深度

この帯は corpus.py で既存マップの 10〜90 パーセンタイルから求めている。

中立施設の prior

中立施設に関しては以下を使う。

  • neutral_ratio_by_layout
  • neutral_band_weights_by_layout
  • neutral_axis_weights_by_layout
  • neutral_front_cluster_profile_by_layout

これにより、単純な均等散布ではなく、既存 maps の front/approach/edge 分布に近い budget 配分ができる。

生成パイプライン詳細

1. エントリポイント

generate_map() が乱数 seed と引数を受け取り、まずサイズを決める。

その後、以下のどちらかへ分岐する。

  • _generate_stacked_map()
  • _generate_side_by_side_map()

2. 戦略グラフ生成

_build_strategy_graph() が各アーキタイプに応じた graph を組み立てる。

2.1 共通で決めるもの

  • naval_weight
  • naval_profile
  • port_policy
  • layout_policy
  • blue_hq
  • 中立施設 budget
  • contested cluster order / profile
  • objective theatre

2.2 layout_policy

現在の policy は以下。

  • strict_symmetric
  • offset_symmetric
  • balanced_asymmetric

ただし一部 archetype、特に island_invasion / stacked は専用 policy を使い、正面近距離対峙を避ける。

2.3 blue_hq の決定

red_hq を先にサンプルし、blue_hq は layout と policy に応じて決める。

ここで以下のルールを課している。

  • 近距離の軸正面対峙を避ける
  • stacked / side_by_side ごとに対角寄りの配置も許す
  • island_invasion / stacked では diagonal separation を強める

2.4 中立施設 budget

_neutral_budget() が、マップ面積と corpus 由来の neutral ratio から次を決める。

  • contested_neutral_budget
  • approach_neutral_budgets
  • neutral_factory_budget

注意点として、既存 maps に未占領 factory は存在しないため、現在は中立 factory を生成しない。

3. archetype ごとの graph

central_plains

陸戦主体。以下の node を重視する。

  • frontier_hub
  • contested_neutral_cluster
  • approach_neutral_cluster
  • lane_hub
  • open_field_pocket
  • cover_belt
  • soft_barrier
  • flank_pressure_zone

意図は、平地一色ではなく、突破路・遮蔽帯・緩い遅滞地形を graph として先に置くこと。

mountain_divide

中央山脈や分断地形を持つ陸戦型。主戦線を峠・谷で制御する。

river_bridge_chokepoints

川線と橋前線を主役にする。橋を局所 chokepoint として graph に持ち、骨格では river / bridge を先に固定する。

dense_city_war

都市帯と道路網を主役にする。urban_core, city_belt, industrial_pocket, road_junction を graph に持つ。

highland_passes

高地塊と複数の pass を作る。pass_chainhighland_basin が主役。

strait_invasion

海峡型海戦 map。単一固定ではなく variant を持つ。

  • naval_assault
  • coastal_pressure
  • blocked_strait

variant ごとに port_policynaval_weight が異なる。

coastal_logistics

海はあるが主戦場は沿岸補給線。port は存在しても、海軍主戦場に寄せすぎない。

island_invasion

最も専用処理が多い archetype。

graph 上で以下を持つ。

  • sea_region_anchor
  • beach_landing_zone
  • forward_staging_island
  • island_chain
  • central_island_objective
  • secondary_island_objective
  • naval_neutral_slot
  • home_island_anchor
  • home_neutral_cluster

ここでは、capital のある home island と、中央 objective island、側面 objective island を別の戦域として扱う。

4. objective theatre と coverage

strategic_objective は中央1点ではなく、複数の sector を使うように設計されている。

4.1 _default_objective_theatres()

land map 向けの標準 theatre を作る。

4.2 _build_island_objective_theatres()

island_invasion 専用。landing, central, flank, counter_flank に budget と必要 land capacity を与える。

4.3 _augment_objectives_with_coverage()

graph が map の一部しか使っていない場合、coverage candidate から追加 objective を補う。

この層により、特定 archetype だけではなく、全体として map の使用領域が広がるようにしている。

5. 骨格地形生成

戦略グラフを作った後、layout ごとの骨格 builder が地形に落とす。

stacked

_generate_stacked_map() が担当する。

大まかな順序:

  1. 戦略グラフ生成
  2. archetype 別 builder で初期骨格生成
  3. mirror / finalize
  4. 中央地形や archetype 装飾を適用
  5. blue 側 home cluster の再配置
  6. neutral / road / bridge / river / port 修復
  7. archipelago 系後処理

side_by_side

_generate_side_by_side_map() が担当する。

stacked と同じ責務だが、中央 strait や coast の向きが異なるため、side 専用 builder と修復を使う。

6. 地形 painter の役割

graph を tile へ落とすとき、いくつかの汎用 painter を使う。

  • _paint_terrain_disc()
  • _paint_terrain_strip()
  • _paint_island_region()
  • _draw_road_path()
  • _carve_center_strait()
  • _carve_center_vertical_river()
  • _carve_center_mountain_divide()

重要なのは、単なるノイズ埋めではなく、graph node / edge に対応する corridor や region を直接描いている点である。

7. neutral 配置

中立施設は後付けランダムではなく、graph budget から配置される。

通常 map

_place_neutral_facilities() が以下の順で置く。

  1. objective theatre に紐づく neutral
  2. contested cluster の neutral
  3. approach cluster の neutral
  4. まだ足りなければ contested cluster を再走査

island_invasion

archipelago_assault では、通常の contested / approach に加えて、objective theatre と home island 用の neutral を持つ。

  • _place_objective_neutral_facilities()
  • _place_archipelago_home_neutrals()

これにより、中央 island だけでなく home island にも neutral city を持たせられる。

8. road 生成

_paint_roads() は capital 間を安易に直結しないように設計されている。

基本方針:

  • HQ -> approach -> frontier -> contested lane を局所的につなぐ
  • 敵 HQ 側の幹線と無条件には合流させない
  • archetype によっては専用 road painter を使う

例:

  • dense_city_war は都市帯用の道路を追加
  • highland_passes は峠用道路を追加

9. archipelago 系の後処理

island_invasion は後処理の責務が重い。

9.1 目的

  • home port を残す
  • capital 間の land path を断つ
  • central / flank objective を残す
  • home island を痩せさせすぎない
  • neutral theatre を消さない

9.2 主な関数

  • _enforce_island_invasion_objectives()
  • _stabilize_island_home_ports()
  • _stabilize_stacked_naval_ports()
  • _stabilize_side_naval_ports()
  • _ensure_archipelago_capital_separation()
  • _stabilize_archipelago_strategic_fragments()
  • _materialize_archipelago_home_islands()
  • _restore_archipelago_home_rear()

9.3 recent fix の考え方

home island については、単なる early paint だと final sea-corridor stabilization に削られる。そこで現在は以下の二層に分けている。

  • graph が home_island_anchor / home_neutral_cluster を持つ
  • 後段で rear shelfrear-only restore を使い、port や中央海路を壊さずに home island の基底面積を戻す

この設計により、capital の島を広く保ちつつ、port と capital separation の制約も守れる。

10. repair の役割

repair は「戦略性を決める層」ではなく、「制約違反や局所破綻を除去する層」である。

代表的な処理:

  • _repair_bridges()
  • _repair_rivers()
  • _repair_beaches()
  • _repair_ports()
  • _repair_small_land_fragments()

graph が不十分な場合に repair だけで帳尻を合わせると壊れやすいので、最近の実装では objective theatre や home island などの戦略的要件は graph に上げ、repair は safety net にとどめている。

11. validation とメトリクス

cmd_generate() は生成後に必ず以下を行う。

  1. summarize_map() で地形・建物内訳を出力
  2. compute_metrics() で戦略指標を出力
  3. validate_map() で静的制約を検証

これにより、生成アルゴリズムは「map を作る」だけでなく、「ゲーム的に許容されるか」を同時にチェックする。

12. 現在の実装方針の要点

強み

  • タイル単位ランダム生成ではなく、戦略グラフ先行
  • コーパス prior を用いた HQ / neutral 分布調整
  • archetype ごとの専用 graph grammar
  • objective theatre による複数争奪点の管理
  • archipelago での home island / objective island の分離
  • validator による強い制約チェック

意図的な設計判断

  • 中立 factory は生成しない
  • port は graph が要求した海域でのみ成立させる
  • bridge は横断前提で左右地形制約を持つ
  • beach / river / port は既存 maps の観察を基に静的制約を持つ
  • 非対称配置は許すが、戦略グラフが追従する形にする

今後の改善余地

  • static metrics に加えて簡易 simulation を導入する
  • archetype ごとの reject / reroll 基準を明示的にする
  • income gap や objective distance による graph-level reject を強化する
  • code volume が大きい generator.py を archetype / repair / neutral / naval 単位に分割する

13. 実行フローのまとめ

最後に、generate コマンドの実行フローを簡潔にまとめる。

  1. cli.py がコーパスを分析する。
  2. generate_map() がサイズ、layout、archetype を決める。
  3. _build_strategy_graph() が HQ、frontier、objective、sea region、neutral budget を決める。
  4. layout 別 builder が graph をヘクス地形へ落とす。
  5. neutral, road, port, river, beach, bridge を graph-aware に整える。
  6. validate_map()compute_metrics() で結果を確認し、JSON を保存する。

この構成により、現行実装は「既存 maps に寄せた prior を持つ graph-based + constraint-aware generator」として整理できる。

GPS将棋(OpenShogiLib)のdf-pn(詰将棋)をUSI mateエンジンとして移植

以前に、GPS将棋(OpenShogiLib)のdf-pnをdlshogiのAperyベースに移植することを試みたが、変更が多すぎて途中で挫折した。

Aperyベースにするのは一旦保留して、単体のUSI mateエンジンとして移植することにした。

Windows向けの移植

元のOpenShogiLibは、古いLinux向けのソースになっているため、そのままだとWindows向けにビルドできない。
以前に手動で対応しようとしたが、df-pnに関連するソースを抜き出すだけでも大変なため断念した。

Codexを使えば、自動でdf-pnに関連するソースを抽出して、Windowsでビルドエラーを解消もできそうだと思い、移植を試みた。

Codexで移植

元のOpenShogiLibから、まずdf-pnに関連する処理の解説を作成し、その解説を参考資料として、移植を実施した。
USI エンジンとして実行できるようにするため、USIの仕様も与えた。

oslのソースを元に、Df-pnアルゴリズムによるUSI mateエンジンを実装してください。

- oslmateフォルダにあるWindowsのコマンドラインプロジェクトに実装する
- oslのソースからDf-pnに関連するソースのみを移植する
- oslとソースフォルダ階層、ソースファイル名は合わせる
- oslのソースはnamespaceも含め、基本的に変更しない
- oslは、古いC++で記述されているため、Windowsのコマンドラインプロジェクトでコンパイルエラーになる箇所は修正する
- oslには、USIプロトコルは実装されていないため、新規に実装が必要。USIプロトコルの仕様はreferences/usi.mdにある。usi,setoption,isready,isready,go mate,stop,quitのみ実装する。
- ビルドコマンド: msbuild oslmate.slnx /t:Build /p:Configuration=Debug /p:Platform=x64

### 参考
oslフォルダ: oslのソース(Df-pnに無関係のソースも含む)
references/gpsshogi-dfpn.md: oslのDf-pnに関連するソースの解説
references/usi.md: USIプロトコルの仕様

ビルドエラーが解消され、実行できるバイナリが生成された。

assert対応

探索を実行すると、assertでエラーになったため、Visual StudioのGitHub Copilotで原因分析したところ、16 バイト整列のエラーだったため、alignas(16) を追加して解消した。

その1か所修正したことで、実行できるようになった。

position sfen lnsgk1snl/1r4g2/p1pppp1pp/6pP1/1p7/2P6/PPGPPPP1P/6SR1/LN+b1KG1NL w bs 11
go mate
checkmate B*6h 5i5h 6h5g+ 5h5i 7i6h

SSE2対応

OpenShogiLibはSSEに対応しているが、古いC++用のコードになっているため、OSL_USE_SSEマクロを有効にしてもビルドできない。

Codexで、SSE2に対応したintrinsicを使用したコードに修正することで、ビルドできるようになった。

Windows向けバイナリ公開

ShogiGUIなどに詰将棋エンジンとして登録して使用できるように、GitHubでWindowsバイナリを公開した。

こちらからダウンロードできる。

github.com

まとめ

GPS将棋(OpenShogiLib)のdf-pnを単体のUSI詰みエンジンとして移植した。
Codexを活用して必要なソース抽出・Windows対応・USI実装を行い、ビルド・実行することができた。
GitHubでWindows向けバイナリを公開した。

【バイブコーディング】レトロ戦略ゲームを作る その6(マップ自動生成)

前回は、バイブコーディングでAIをルールベースで実装して、SPSAでパラメータチューニングすることで遊びごたえのあるAIを実装した。

今回は、マップの自動生成を試す。

マップの自動生成

まず、ChatGPTで、ゲームのマップ自動生成の手法について調査した。

  • 戦略グラフを作る(Graph-based generation)
  • マルコフ近傍生成
  • 制約充足 + バックトラッキング
  • 進化的アルゴリズム
  • Quality Diversity (QD)

といった手法が提案された。

ターン制戦略ゲームでは、マップの公平性や戦略性が必要なため、全体構造を先に決めることが重要なため、単一手法ではなく、

「戦略構造を先に固定する手法」+「制約ベース生成」+「軽量評価(必要ならQD)」

のハイブリッドが良いとのこと。

それぞれの手法について、知見がないため、一つずつ試していくことにする。

最近のゲームで採用されている制約ベース生成の手法として、「Wave Function Collapse (WFC)」が有名ということなので、まずは、WFCを試してみる。

Wave Function Collapse (WFC)

Wave Function Collapseは、日本語にすると「波動関数の崩壊」という名前が付いているが、量子力学の波動関数に着想を得た手法のようである。

以下のような手順でマップを生成する。

1. 学習元のマップからNxNの局所パターンを抽出する
2. 初期状態では、すべてのセルはすべてのパターンを取りうる状態なる
3. エントロピーが最小(取りうるパターンの範囲が狭い)のセルを選択し、パターンを確定する
4. 確定したパターンを隣接するセルに伝播する(そのセルのパターンが絞られる)
5. 3.~4.を繰り返す

途中で、選べるパターンがなくなり、生成が失敗する場合が場合があるが、その場合はやり直すことになる。

ゲームボーイウォーズのマップからパターンを学習

WFCには、パターンを学習するマップが必要になるため、以前にネット上のマップ画像からデータ化したゲームボーイウォーズのマップを学習に使うことにする。

実装

WFCはオープンソースになっているが、ゲームボーイウォーズのルールに特化したカスタマイズを行うため、Pythonで再実装した。

WFCアルゴリズムでマップを自動生成するツールをPython(uv環境)で実装してください。

WFCアルゴリズムの解説は、wfc.mdにあります。
参考として、WFCアルゴリズムのC#実装は、WaveFunctionCollapseにあります。

パターンの学習元となるマップデータは、mapsディレクトリにあります。
生成結果は同じJSON形式で出力してください。

マップの制約:
- capitalは、ownerがredとblueで一つずつ
- それぞれのcapitalの位置は接近しすぎず、マップの中央付近にはない

初回の実装では、以下のようなマップが生成された。

赤の首都の周りに生産拠点がなく、自軍の占領都市が首都から離れていて、修正が必要である。

制約を加える指示を行い、生成結果を確認を繰り返した。

以下の制約を加えることはできますか?
- 2つのcapitalがseaで隔たる場合は、capitalの近くにportが必要
- portは、seaに隣接している
マップの縁に出現しやすい地形が考慮されていません。
マップの縁はパターンで考慮されていますか?
capitalの近くには異なるownerの建物は出現しにくいという制約を加えたい。
redとblueのownerの数は近くする制約を加えることはできますか?
未占領の建物(city,port,airport)の数を、マップの広さに対して制限したい

他にも、多数指示を行った。

最終的に、生成されたマップの例は以下の通り。

港の有無は、オプションで指定できるようにした。

港ありのマップ:

マップの縁のパターンに引きずられて海が多くなるため、海の割合をオプションで下げられるようにした。

海の出現を下げたマップ:


未占領の都市は、分散して出現するように制約を入れているが、ハード制約ではなくソフト制約のため、局所パターンに引きずられて思ったようなマップにはなっていない。

砂浜が陸地に出現していたり、川が海の中にあったりと、まだ足りていない制約もある。

WFCの限界

ゲームとして成立するように、未占領の都市の距離がそれぞれの首都から同じになるような制約など加えたが、局所パターンを元に生成するため思った通りには生成できなかった。

自然な地形を生成するには良いが、戦略性を盛り込むのは別の手法が必要ということがわかった。

まとめ

WFC(Wave Function Collapse)を使ったマップの自動生成を試した。
各種制約(首都配置、港条件、所有バランスなど)を追加しながら調整し、一定の品質のマップが生成できるようになった。
ただしWFCは局所パターン依存のため戦略性の制御が難しく、ゲーム性を満たすには他手法が必要であることがわかった。

次は、グラフベースの手法を試したい。

【バイブコーディング】レトロ戦略ゲームを作る その5(AIを強くする)

前回、マップの保存/読み込みに対応して、ゲームボーイウォーズのマップを変換して取り込めるようにした。
しかし、AIが弱すぎてゲームとして楽しめなった。

今回は、AIを強化する。

ターン制戦略ゲームのAI

ゲームボーイウォーズのようなターン制戦略ゲームは、ユニットの数が多く、行動の選択が広いため、将棋AIのような全探索をするのが難しい。
また、遠くの未占領の都市を数ターンかけて目指すような長期的な目標が必要になる。

自ターンのみでも、最大40ユニットの探索が必要で、これを数ターン分探索しないと、結果が見えない。
将棋AIでも探索深さは20から30程度のため、これを探索で行うのはほぼ不可能である。

そのため、ルールベース+評価関数+局所探索の組み合わせで作るのが実用的である。

将来的には、ルールベースは、方策をニューラルネットワークが学習して、評価関数もニューラルネットワークの学習にすることができる。

今回は、ゲームボーイウォーズのアルゴリズムを調査して、ルールベースとして組み込み、ルールの重みをSPSAでチューニングすることにする。

ゲームボーイウォーズのAIアルゴリズム

ChatGPTでDeep Researchを行い、ゲームボーイウォーズのAIアルゴリズムを解析したサイトの情報を調査した。

以下のようなアルゴリズムであることがわかった。

基本設計

  • ミニマックスのような深い探索ではなく
    • → ルールベース + ヒューリスティック評価による貪欲法
  • 各ターンで「最もスコアの高い行動」を選択

探索の特徴

  • 基本は 1手読み(浅い探索)
  • 反撃ダメージは内部的に考慮(疑似的に1.5手相当)
  • GBの制約(約4MHz / RAM 8KB)により深い探索は困難

行動決定フロー

  • 各ユニットごとに:
    • 移動可能マスを列挙
    • 攻撃・占領・待機などの候補を生成
  • 各候補をスコア化
  • 最大スコアの行動を選択(貪欲)

評価関数(主な要素)

  • 与ダメージ(敵コスト換算)
  • 撃破ボーナス
  • 被ダメージ・リスク
  • 占領価値(特に首都は高評価)
  • 生産拠点の制圧・妨害
  • 敵本拠地への接近
  • 危険エリア回避(高IQ時)

重要なヒューリスティック

  • 生産拠点上の敵ユニットを最優先で攻撃
    • → AIの挙動を強く特徴づけるルール
  • 生産妨害(拠点に乗る)も高評価
  • 占領(特に首都)を強く優先

難易度(IQ)の違い

  • IQ100:
    • 単純・攻撃寄り
    • 危険回避が弱い
  • IQ200:
    • 危険回避あり
    • 占領・戦略評価が強化
    • より多くの候補を比較

ルールベース挙動

  • 特定条件でユニット生産が変化(例:対空ユニット)
  • 索敵ON/OFFで視界・行動が変化
  • 占領可能ユニットは限定(歩兵系)

全体的特徴

  • 局所最適に強いが、長期戦略は弱い
  • 囮や誘導に弱い(単純優先ルールのため)
  • 計算量を抑えた設計(GBハード制約に適合)

Codexで実装

ゲームボーイウォーズのAIアルゴリズムの調査結果を入力して、CodexでAIを実装した。
テストプレイを行い、おかしな行動を見つけては修正指示を行った。

与えた指示は、以下のようなものである。

design\aI-analysis-report.md を元にAIを実装してください。
まず、現在のAIの実装(HexFront.AI)を確認してください。
design\aI-analysis-report.mdのアルゴリズムを実装するための方針を立ててください。
それから実装に取り掛かってください。
索敵は無視してよいです。
テストプレイしたところ、AIが歩兵しか生産しないので、何か間違っていないか確認してください。
開始ターンでいきなり戦車Bを1台だけ作ってターンエンドします。定跡では1ターン目は歩兵を作らないと占領地を増やせないので不利です。
戦車Bと装甲車の生産はどのような基準で選択されますか?
調整するパラメータやルールは入っていますか?
相手のユニットを見て、ユニット間のダメージ相性も考慮して加点・減点するルールは入れられますか?
たとえば、装甲車は歩兵に強いので、相手が歩兵ばかりであれば加点し、相手に装甲車がいれば戦車が有利なので戦車に加点します。装甲車、戦車に特化したルールではなく、相手ユニットの状況とダメージ表から汎用的なルールを構築して欲しいです。
以下のルールや仕組みを実装してください。
- 次のターンで相手に占領されないようにブロックするルール
- 次の近くの占領目標に向けて歩兵が分散して目指すルール(同一の目標値に向かわないような重み調整)
- 資金を温存して、現在の状況に適した強い兵器を次ターンに生産することへの加点(現状は戦車Bばかり生産されがち)
- 今後、機械学習でパラメータ調整するので、できるだけ重みはパラメータとして調整できる余地を作る
こちらが歩兵しか生産していないのに、AIは戦車Bを作ってくる。装甲車が作られないのは不自然。パラメータを調整することで、歩兵しかいない場合に、装甲車が優先される余地のある実装になっているか確認してください。
その余地を作るため、TankB/装甲車の固定ボーナスや matchupScore 係数も AiWeights 側へ出して、相性評価の比重を上げられるそのように修正してください。
戦車がダメージ相性のより装甲車を攻撃しに向かわずに、別の方向に向かっているように見受けられます。相性の良い相手を攻撃目標として、移動するルールは入っていますか?
相性の良い相手を攻撃目標として移動することに加点するようなルールを加えてください。遠すぎる相手は相手の移動先が不確実なので考慮すべきではないことに注意してください。パラメータは調整可能としてください。
占領要員は足りている判定のパラメータを調整して、もう少し歩兵が生産が促されるようにしてください。
day <= 2のように固定しきい値にするのではなく、重みとして加算、減点されるパラメータとしてください。
今度は、歩兵生産の圧力が強すぎるようです。生産の空があるのに、歩兵を生産せずにターン終了するのは避けたいが、歩兵しか生産しなくなるのは避けたい。
相手のユニット編成に対して、すでに数が十分なのに同じユニットばかりを作り続けるようになっていないか確認してください。
思考時間改善

デバッグビルドで思考が遅いため、無駄な処理がないか確認を行った。

結果は同じで強さを維持しつつ、効率的な枝刈りできる箇所はありますか?
重複計算削減を行ってください。

実装結果

上記のようにゲームボーイウォーズのAIアルゴリズムをベースに実装して、修正指示することで、遊べるレベルになった。

歩兵で分散して占領したり、拠点に砲台を生産したり、資金をためて戦車Aを生産したりするようになった。

SPSAによるチューニング

ルールベースで実装したアルゴリズムには、以下のようなパラメータがある。
これらを手動で調整するのは難しい。
そこで、チェスAIや将棋AIの探索パラメータチューニングで使用されている、SPSAで自動調整することを試みる。

調整可能パラメータ

全体評価
  • minimumUsefulActionScore: 最善手の点数がこれ未満なら行動せずターン終了しやすくする閾値。
  • strategicMaterialWeight: 盤面全体の総戦力差をどれだけ重視するか。
  • strategicFundsWeight: 現金差をどれだけ重視するか。
  • strategicIncomeWeight: 収入差をどれだけ重視するか。
  • strategicObjectiveProgressWeight: 敵施設への接近度を戦略評価にどれだけ入れるか。
移動と前進
  • mobilityCaptureAdvanceWeight: 占領可能ユニットの前進をどれだけ褒めるか。
  • mobilityCombatAdvanceWeight: 戦闘ユニットの前進をどれだけ褒めるか。
  • cargoCaptureAdvanceWeight: 輸送中の占領ユニット前進をどれだけ褒めるか。
  • cargoCombatAdvanceWeight: 輸送中の戦闘ユニット前進をどれだけ褒めるか。
  • enemyProductionBlockBonus: 敵の生産施設を塞ぐ位置取りのボーナス。
  • nextTurnCaptureThreatWeight: 次ターンに敵へ取られそうな施設価値をどれだけ嫌うか。
施設目標の評価
  • captureObjectiveValueScale: 施設自体の価値をどれだけ強く見るか。
  • captureObjectiveDistancePenalty: 目標施設まで遠いことへの減点。
  • captureObjectiveCongestionWeight: 同じ目標へ味方が集まりすぎることへの減点。
  • captureObjectiveProgressWeight: 施設占領に近づく行動をどれだけ褒めるか。
将来生産と相性
  • futureBuildCostWeight: 将来作れるユニットのコスト価値をどれだけ見るか。
  • futureProductionSavingsWeight: 今使わず貯金する価値をどれだけ見るか。
  • productionMatchupOffenseWeight: 対ユニット相性の攻撃面をどれだけ重視するか。
  • productionMatchupScale: 全体の相性スコアを生産判断へどれだけ反映するか。
  • productionCounterDemandScale: 敵構成への対抗需要をどれだけ大きく見積もるか。
  • productionCounterSurplusTolerance: 対抗ユニットの過剰保有をどこまで許すか。
  • productionCounterSaturationPenaltyScale: 対抗戦力の作りすぎへの減点。
  • productionCounterSameTypePenaltyScale: 同一タイプ作りすぎへの減点。
個別生産ボーナス
  • productionTankBBonus: TankB を作る追加ボーナス。
  • productionArmoredCarBonus: ArmoredCar を作る追加ボーナス。
  • productionTankAHalfFundsPenaltyWeight: 所持金の半分超を TankA に使うときの減点重み。
  • productionNonCaptureHalfFundsPenaltyWeight: 非占領ユニットへ大金を使うときの減点重み。
占領戦力の目標数
  • desiredCapturingUnitsBase: 欲しい占領ユニット数の基本値。
  • desiredCapturingUnitsPerEnemyFacilityDivisor: 敵施設数から必要占領数を増やす比率。
  • desiredCapturingUnitsMin: 必要占領数の下限。
  • desiredCapturingUnitsMax: 必要占領数の上限。
  • openingCapturePhaseTarget: 序盤の占領フェーズとみなす目標数。
占領ユニット生産の圧力
  • productionCaptureNeedBonusBase: 占領役不足時の基礎ボーナス。
  • productionCaptureNeedBonusPerShortfall: 占領役不足 1 体ごとの追加ボーナス。
  • productionCaptureSurplusPenaltyBase: 占領役過多時の基礎減点。
  • productionCaptureSurplusPenaltyPerExtra: 占領役余剰 1 体ごとの追加減点。
  • productionInfantryBaseBonus: 歩兵生産の基本ボーナス。
  • productionInfantryNoInfantryBonus: 歩兵が 0 のとき歩兵を強く作らせるボーナス。
  • productionInfantrySurplusPenalty: 歩兵過多時の減点。
  • productionEngineerSurplusPenalty: 工兵過多時の減点。
どの占領役を優先するか
  • captureUnitOpenTargetWeight: 空いている施設が多いとき占領役を増やす重み。
  • captureUnitCloseRaceWeight: 競争が激しい施設争いで占領役を増やす重み。
  • captureUnitBehindWeight: 施設数で負けているとき占領役を増やす重み。
  • infantryOpenTargetWeight: 上記のうち特に歩兵へ載せる追加重み。
  • infantryCloseRaceWeight: 近い競争時に歩兵を優先する重み。
  • infantryBehindWeight: 劣勢時に歩兵を優先する重み。
  • captureReserveUnits: 必要占領数に上乗せする予備枠。
  • captureSatisfiedPressureScale: 占領不足でないときに占領圧力をどれだけ弱めるか。
貯金判断の緩和
  • captureSavingsOpenTargetReliefWeight: 空き施設が多いとき貯金減点をどれだけ緩めるか。
  • captureSavingsCloseRaceReliefWeight: 競争が近いとき貯金減点をどれだけ緩めるか。
  • captureSavingsBehindReliefWeight: 施設数で負けているとき貯金減点をどれだけ緩めるか。
  • captureSavingsShortfallReliefWeight: 占領役不足時に貯金減点をどれだけ緩めるか。
追撃・位置取り
  • matchupPursuitMaxRelevantDistance: 追撃評価で見る最大距離。
  • matchupPursuitOffenseWeight: 追撃先での攻撃有利をどれだけ重視するか。
  • matchupPursuitDefenseWeight: 追撃先で受ける反撃リスクをどれだけ重視するか。
  • matchupPursuitProgressWeight: 追撃に有利な位置へ移動する価値をどれだけ褒めるか。

SPSAの実装

事前に、ChatGPTでSPSAのアルゴリズムを調査し、疑似コードで実装したものを用意した。
それを入力として、CodexでSPSAを使用してパラメータチューニングを行うコマンドラインツールを実装した。

ゲームボーイウォーズのマップから変換したマップの一覧をランダムで選択して自己対戦を行うようにした。

試したところ、何分経っても1イテレーションが完了しない。
コマメ島のみにしたところ、1イテレーション30秒くらいで完了した。

広いマップでAIの強さがほぼ同じだと、均衡するため、なかなか決着がつかなくなるためと思われる。
終盤も拠点を砲台で固めるなどすると、なかなか決着が付かなくなる。

終盤向けのルールを追加するなどで対策できるかもしれないが、とりあえず狭いマップで調整することにした。

コマメ島でチューニング

まずは、コマメ島のみでチューニングを行った。

Loaded 1 scenarios
Iterations=5 pairs-per=4 max-days=60 max-plies=15360 threads=4 seed=1
[1/5] W/L/D=4/4/0 score=0 time=29.177s (0.486m)
[2/5] W/L/D=2/5/1 score=-3 time=35.721s (0.595m)
[3/5] W/L/D=4/1/3 score=3 time=33.252s (0.554m)
[4/5] W/L/D=3/4/1 score=-1 time=34.340s (0.572m)
[5/5] W/L/D=3/3/2 score=0 time=30.483s (0.508m)
...(略)

初めのイテレーションは、30秒ほどで完了している。

100イテレーションまわしたパラメータでは、2分くらいかかるようになり、Red/Blueを入れ替えて対戦するため、結果は引き分けになっている。

[1/100] W/L/D=4/4/0 score=0 time=130.032s (2.167m)
[2/100] W/L/D=4/4/0 score=0 time=114.390s (1.906m)
[3/100] W/L/D=4/4/0 score=0 time=102.957s (1.716m)
[4/100] W/L/D=4/4/0 score=0 time=96.196s (1.603m)
[5/100] W/L/D=4/4/0 score=0 time=115.316s (1.922m)

テストプレイ

100イテレーションまわしてチューニングしたパラメータで、テストプレイしたところ、装甲車に戦車Bをぶつけてきたり、ロケットランチャーを生産したりと、かなり手ごわくなっていた。


まとめ

AIが弱すぎたので、AIの強化を行った。
ゲームボーイウォーズ系のAIは全探索が困難なため、ルールベース+評価関数+局所探索で実装した。
SPSAでパラメータを自動調整し、手ごわく遊びごたえのあるAIが実装できた。
すべてバイブコーディングで実装しており、自分では1行もコードを書いていない。

AIは改良の余地がまだまだあるが、次はマップの自動生成を実装したい。

【dlshogi】ResNet+Transformerモデルのアーキテクチャ改良

昨年の世界コンピュータ将棋選手権、電竜戦では、モデルサイズを大きくすることで強くすることを試みたが、精度は上がるものの探索速度が落ちることで強くできなかった

そこで、現在一番強い40ブロック512フィルタのモデルサイズで、モデルアーキテクチャを変えることで強くすることを試みた。

アーキテクチャ変更

電竜戦の後、様々なモデルアーキテクチャの変更を実験した。

Gated Attention

以前に、記事にしたGated Attentionはわずかに効果があるため取り入れた。
Attention層で、入力に応じた要素ごとの重みを乗じることで、重要でない駒の関係にSoftmaxの確率が割り当てられることを防ぐことができる。

SwiGLU FFN

FFNで、ゲートを学習可能にすることで、重要な情報のみを通すようにする。
LLMで標準的な手法となっている。

中間のブロックにTransformerを配置

角の効きを早期に捉えるために、10ブロック間隔で、ResNetをTransformerに置き換えるようにした。

相対位置バイアス

Transformerで角の効きを捉えるには、遮蔽している駒も合わせて捉える必要がある。
Transformerは、離れた位置の駒の関係を捉えることができるが、間にある駒は捉えることができない。
相対的な位置関係を相対位置バイアスで学習することで、FFNで位置関係を考慮することができ、遮蔽を捉えることができる。

絶対位置バイアス

入玉を正確に判断するには、自駒が相手の陣にいることを正確に識別する必要がある。
ResNetはプーリングを行っていないため、駒の座標に応じた表現はできているが、より明確にモデルに座標の情報を与えることにした。

ラージカーネル

ResNetは、通常効率を重視して3x3カーネルが用いられるが、5x5や7x7といったラージカーネルは精度改善に高い効果がある。
特に、入力側で、ラージカーネルを用いることで、早期にグローバルな特徴を捉えることができる。
dlshogiの入力層は、チャンネルサイズが中間層より小さいため、入力層のみをラージカーネルに置き換えることで、効率を大きく落とすことなく、精度を向上させることができた。

SEBlock

かなり前に実験して精度が上がることを確認していたが、探索速度が落ちて強くならなかったので採用していなかったが、5ブロック間隔で挿入することで十分な効果があることが確認できた。


他にも、group convや、Nested Bottleneckや、Pre Activationなど試したが、あまり効果がないか逆効果だったので採用しなかった。

訓練結果

訓練データ

アーキテクチャを改良したモデルで、40ブロック512フィルタのモデルを学習した。

訓練データは、60ブロック768フィルタのモデルから蒸留したデータと自己対局のデータ、NNUE系との対局データを使用した。
重複を平均化して、42.4億局面になった。

比較対象

以下と比較を行う。

  • pre59_40x512 : 改良前のResNet+Transformerモデル、40ブロック512フィルタ
  • pre59_50x640 : 改良前のResNet+Transformerモデル、50ブロック640フィルタ
  • pre66_60x768 : 改良前のResNet+Transformerモデル、60ブロック768フィルタ

今回学習したモデルは、pre68_40x512である。

なお、以前のモデルとは訓練データが異なるため、純粋なモデルアーキテクチャの比較にはなっていない。

精度


step val/policy_loss
pre59_40x512 9071084 1.253049373626709
pre59_50x640 9071084 1.2316398620605469
pre66_60x768 9160159 1.2185360193252563
pre68_40x512 9333303 1.2487926483154297


step val/value_loss
pre59_40x512 9071084 0.7068616151809692
pre59_50x640 9071084 0.7094608545303345
pre66_60x768 9160159 0.7174099683761597
pre68_40x512 9333303 0.7074635028839111


step val/result_loss
pre59_40x512 9071084 0.4324478209018707
pre59_50x640 9071084 0.42958199977874756
pre66_60x768 9160159 0.42668843269348145
pre68_40x512 9333303 0.43083781003952026


step val/policy_accuracy
pre59_40x512 9071084 0.5731422901153564
pre59_50x640 9071084 0.5795454382896423
pre66_60x768 9160159 0.5846914649009705
pre68_40x512 9333303 0.576049268245697


step val/value_accuracy
pre59_40x512 9071084 0.7828457355499268
pre59_50x640 9071084 0.7846994400024414
pre66_60x768 9160159 0.7863633632659912
pre68_40x512 9333303 0.7839466333389282


step val/policy_entropy
pre59_40x512 9071084 1.0936312675476074
pre59_50x640 9071084 1.076419711112976
pre66_60x768 9160159 1.0464400053024292
pre68_40x512 9333303 1.0613174438476562


step val/value_entropy
pre59_40x512 9071084 0.4637340009212494
pre59_50x640 9071084 0.4600807726383209
pre66_60x768 9160159 0.4531950354576111
pre68_40x512 9333303 0.462144672870636


floodgateの棋譜に対する評価精度は、改良前の40ブロックモデルと50ブロックモデルの中間くらいになっている。

パラメータ数

ONNXにした後、以下のスクリプトで算出したパラメータ数は以下の通り。

def count_parameters(model):
    total_params = 0
    for initializer in model.graph.initializer:
        dims = initializer.dims
        param_count = np.prod(dims)
        total_params += param_count
    return total_params
モデル パラメータ数
pre59_40x512 213891495
pre59_50x640 423872679
pre66_60x768 739767207
pre68_40x512 192373303

パラメータ数は、改良前の40ブロックモデルと比較して、89.9%になっている。
ResNetをTransformerに置き換えるブロックが増えているため、パラメータ数が減っている。
計算量は、Transformerの方が大きいため、探索速度はパラメータ数だけでは測れない。

探索速度

40ブロックモデルの、改良前後のNPSは以下の通り。
floodgateから抽出した100局面で、5回測定した平均。

改良前 改良後
平均 11659 10658
中央値 11702 10658
最大値 12821 16298
最小値 10779 9831

NPSは、平均で91.4%に低下している。

強さ

dlshogiの定跡の36手目から80手目から抽出した中終盤互角局面集で、持ち時間400秒、1手2秒加算で連続対局した結果は以下の通り。

# PLAYER          :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)    W    D    L  D(%)
1 hayabusa-8th    :    52.9   23.2   285.5     482    59      99  277   17  188     4
2 pre68_40x512    :     1.2   22.2   246.0     487    51     100  231   30  226     6
3 pre60_40x512    :   -54.2   22.6   200.5     495    41     ---  187   27  281     5

White advantage = 147.58 +/- 13.45
Draw rate (equal opponents) = 5.63 % +/- 0.91

hayabusa-8thは、基準のためリーグに加えた氷彗の8スレッド。
dlshogiは、H100 PCI、1GPU。

改良前のモデルに比べて、R+55.4になっている。

探索速度は落ちているが、精度が向上したことで、強くなった。

改良前のモデルは、ファインチューニングしたモデルだが、改良後はファインチューニング前のため、まだ強くできる余地がある。

まとめ

dlshogiのモデルアーキテクチャ改良を行った。
Gated AttentionやSwiGLU、Transformer配置、位置バイアス、ラージカーネルなどを導入したことで精度が改善した。
探索速度は低下したが、精度向上により最終的にレーティングが+55.4向上し、強くすることができた。

さらにファインチューニングすることで強くできるか確認したい。

【バイブコーディング】レトロ戦略ゲームを作る その4(マップ保存/読み込み)

前回、レトロターン制戦略ゲームにマップエディタを実装した。

今回は、マップの保存と読み込みを実装する。
また、テスト用にゲームボーイウォーズのマップを再現して、プレイできることを確認する。

マップの保存と読み込み

マップエディタで、右クリックのメニューから、マップの保存と読み込みができるようにした。

質問:

EditorSceneで、右クリックメニューからマップの保存と読み込みができるようにしたい。
どのように実装すればよいか検討してください。

回答:

おすすめは、右クリックメニューの UI は MainWindow、ファイル状態は ScenarioEditorDocument、表示更新は EditorScene に分ける形です。
...(略)

そのまま実装を指示して、問題なく実装できた。

ゲームボーイウォーズのマップの変換

ゲームボーイウォーズの全マップを画像で掲載しているサイトがあったので、その画像を元に、テンプレートマッチングで地形をデータ化して、今回実装したマップのJson形式に変換を行った。

Codexが使用上限になっていたので、ChatGPTでPythonスクリプトを実装した。
サンプルのJsonを与えるだけで、変換方法は何も指示していないが、変換スクリプトが実装された。

{
    "width": 16,
    "height": 16,
    "day": 0,
    "currentSide": "red",
    "forcedWinner": null,
    "funds": [
        0,
        0
    ],
    "tiles": [
        {
            "coord": {
                "col": 0,
                "row": 0
            },
            "terrain": "sea",
            "owner": null,
            "originalOwner": null,
            "capturePoints": 0
        },
(略)

このような形式に変換できた。

ゲームボーイウォーズのマップ読み込み

コマメ島のマップが読み込みできた。


テストプレイ

テストプレイしたところ、ゲームルールの実装がいろいろ間違っていることがわかった。

  • ゲーム開始時に所持金が0になる
  • 首都や工場で正しいユニットが生産できない
  • ユニットを作ると相手が全滅になってゲームが終了する
  • 輸送ヘリが森や山で降車できる
  • ユニットが与えるダメージが明らかにおかしい

ダメージや降車については、GeminiのDeep Researchの結果が怪しいので、ChatGPTでWebで調べて検証を依頼したところ、間違いが散見された。
GeminiのDeep Researchは信用できない。
ChatGPTでDeep Researchをやり直して、結果をWebで検証させることで、仕様を修正した。

修正版の仕様を元に、Codexで実装に反映した。

実際に最後まで遊んでみる

コマメ島のマップで最後まで遊べるか確認した。

AIが弱く、戦車Bしか作成しない。
あっさり首都を占領して勝てた。
AIが弱すぎて面白くないが、最後までゲームボーイウォーズと同じ挙動でプレイできることは確認できた。


まとめ

マップエディタに保存・読み込み機能を実装した。
ゲームボーイウォーズのマップを画像からテンプレートマッチングで変換し、実際に読み込み・プレイ可能にした。
テストで仕様や実装の誤りを修正し、最終的に一通り遊べる状態を確認した。

AIが弱すぎるので、次はAIをある程度強くしたい。
マップの自動生成も行えるようにしたい。

【バイブコーディング】レトロ戦略ゲームを作る その3(マップエディタ)

前回、レトロ戦略ゲームの画像素材を生成AIで制作したものに置き換えたことで、地形やユニットの識別ができるようになった。

今回は、マップを編集する機能を実装する。

マップエディタ

実装方針

マップをどのように実装するのがよいかCodexに相談したところ、対戦画面にはフィールドと操作パネルがあるので、UIはそのままで、シーンだけを別に管理するのが良いとのことだった。

質問:

マップエディタを追加したいです。
どのように実装するのが良いですか?

回答:

おすすめは、対局用 BattleScene に編集機能を足す のではなく、EditorScene を別で作る構成です。
今のコードベースだと、データはすでに ScenarioData (line 240) と JSON 変換があるので、エディタはこれを直接編集するのが一番素直です。

...(略)
切り替え操作

バトルシーンとエディットシーンの切り替え方法は、ボタン追加を提案されたが、ゲーム操作と混じると分かりにくくなるので、右クリックのコンテキストメニューでも良いか聞いたところ、良いと思うとのことだったので、その方針にする。

質問:

シーンの切り替えの操作はどうしますか?

回答:

おすすめは、常時見えている専用ボタン ではなく、文脈に応じた遷移 にすることです。

基本はこうです。

Battle -> Editor
Commands に Edit Map を置く
押すと「現在マップを編集するか」「初期マップを編集するか」を選べる

...(略)


質問:

右クリックしたときのコンテキストメニューを開いてそこからシーンを切り替えるのはどうでしょうか?

回答:

ありです。むしろこのゲームにはかなり合っています。

左クリック主体のゲームなので、右クリックを「場面切替や補助コマンドの入口」にするのは自然です。
常設ボタンを増やすより UI を汚しにくいのも利点です。

...(略)

実装

実装の詳細を検討させた後、初版の実装範囲が提示されたので、初版実装を依頼した。

実装結果

操作パネルに多少ボタンの重なりがあったので、修正を依頼したところ、基本的な操作ができる状態になった。

マップサイズ

マップサイズが固定で、変更できなかったため、マップサイズ変更の方法を相談した。

サイズを数値入力して、アンカーを設定して、拡張するのが良いとのことだったので、その方針を採用した。

マップサイズ変更の操作はどこから行うのが良いか相談したところ、Toolsにボタン追加を提案されたが、マップ編集と混じるのは良くないので、ヘッダーのマップサイズをクリックするのはどうか聞いたところ良いとのことだったので採用した。

ヘッダーのマップサイズをクリックするとポップオーバーで編集できるようになった。


表示領域外へのスクロール

マップサイズが表示領域外の場合、マウスの中央ボタンをパンすることでスクロールできるようになっている。
しかし、表示領域外に続いていてスクロールできることが視覚的にわかりにくい。

ChatGPTにスクリーンショットを与えてどのような方法があるか聞いたところ、「edge fade」と呼ばれる手法が良く使われるとのことだった。
そこで、Codexに「edge fade」の実装を依頼した。

表示領域の枠の黒のマージン部分に、フェードアウトする形でマップの続きが描かれるようになった。
これで、スクロール可能であることが視覚的にわかるようになった。


まとめ

Codexと相談しながらマップエディタを実装した。
マップサイズ変更やスクロール処理も相談しながら実装することで、わかりやすいUIが実装できた。
マップの保存や読み込みもできるようにしたい。