開発mdx-playgroundメモ

将棋の参考書を読んでいて、「鬼手1」と呼ばれる一手の局面を盤面で動かしながら追いたくなった。紙の上で駒を動かしてもいいのだが、本筋(参考図ルート)と実戦譜が分岐するタイプの解説で、手を戻したり進めたりが多い。Vue で盤面を組んでしまえばクリックで往復できる。

apps/web/app/pages/shogi-board-demo.vue に作った。


まずは棋譜パーサーと SVG 駒で序盤5手

最初は構造の確認だけしたい。棋譜パーサーを書き、SVG で駒を作り、序盤5手だけ進めるところまで動かした。

駒は最初プレーンな五角形だった。並べてみたら、どう見ても将棋盤に見えない。木目のグラデーションを敷き、縁に立体感を出して影を落とし、文字は書道風のフォントに差し替えた。形も「上が尖って肩がある縦長五角形」に整えると、急に将棋駒の顔になった。

譜面は最初は1カラムで縦に並んでいた。読みづらい。中央寄せで2:1グリッドに再構成し、解説は盤の下、コントロールと棋譜リストは右に sticky で貼り付けた。盤面を見たまま手を進められるようになった。


鬼手1の局面を Codex と Gemini に読ませた

「鬼手1」は藤井八冠 vs 広瀬八段(2021/4/9)の一局から取られている。書籍にはその局面の図が載っているので、ここから盤面の SFEN を作りたい。

最初は Codex CLI に画像を投げた。GPT-5.5 を --image オプションで叩く。

codex exec -m gpt-5.5 --image shogi-pos.png "この局面のSFENを出力して"

Codex は局面を読み、駒の配置を返してきた。

並行して Gemini にも同じ画像を投げた結果が HTML テーブルで返ってきていた。Gemini の出力には「8四は後手の飛車」と書かれている。Codex の出力では同じマスが「△桂」になっていた。

両方は両立しない。盤面を見比べる必要があったが、ここで Codex 側を採用してしまった。後でコマを動かしてみると駒の動きが将棋ルール上おかしい。気づいて画像を見直すと、8四は確かに飛車だった。Gemini が正しかった。修正して、Codex の局面読み取りを過信しないという教訓を1つ拾った。


鬼手1のシナリオ: 参考図ルート

書籍の主軸は参考図ルートで、▲9五角 → △8三飛 → ▲7三角左成 → △同金 → ▲9五桂、と進む。盤面に乗せると駒が綺麗に絡む。書籍の図はこのルートを5手で示している。

これを「鬼手1参考図」としてコンテンツに追加した。


実戦譜が「意味わからない」状態になった

書籍には実戦譜も併記されている。手順を写経して盤に流し込んだら、途中で駒が動けないマスに進んでいる。将棋ルール上整合しない手が混じっている、ように見えた。

写経のミスかと思って3回読み直したが、紙の手順通りに入れているように見える。これ以上は単独で詰められないと判断して、外の棋譜DBで答え合わせをすることにした。


agent-browser で shogidb2 から KIF を取得

まずは Google で当該対局の棋譜を引こうとした。WebFetch で投げると bot 検知でブロックされる。Bing も同様に弾かれた。

ここで agent-browser に切り替えた。自分のログイン済み Chrome に繋がっている方の経路だ。Google・Bing は agent-browser でも検知されるが、shogidb2 のサイトは直接アクセスできた。当該対局のページに飛び、KIF と SFEN をブラウザ経由で取り出した。

agent-browser open "https://shogidb2.com/games/..."
agent-browser wait --load networkidle
agent-browser eval 'document.querySelector("...").innerText'

shogidb2 の SFEN は1手ごとに記録されている。書籍の手順と並べると、書籍側の表記が一部ズレていた。実戦譜は75手まである。各手の SFEN を差分解析して、SFEN差分→指し手変換ロジックを書き、75手の実戦譜を完全復元した。

コンテンツの切替UI を整え、「基本5手 / 鬼手1参考図 / 鬼手1全棋譜75手」の3モードに分けた。


駒移動アニメーションで TransitionGroup と格闘した

最初の盤面は <table> で組んでいた。駒を動かすときに位置が瞬間移動する。これでは「どこからどこへ動いたか」が追いにくい。

div grid + absolute レイヤー 方式に書き直した。駒1つずつに固有 ID を振って TransitionGroup でラップすると、再配置時に CSS で滑らかに動く想定だった。

ところが TransitionGroup が transform を内部的に上書きする。FLIP アニメ(First/Last/Invert/Play)で駒の移動を表現する仕組みのため、自分が :style で書いた transform が上書きされて消える。駒が回転するアニメーションを乗せたかったが、TransitionGroup と inline transform が競合して、回転が一瞬で吹き飛ぶ。

加えて、古い inline transform が DOM に残る現象も起きた。こちらは :style で常に明示上書きすることで解決した。

回転アニメは諦めた。代わりに「移動先・移動元のマスをハイライト」する方式に切り替え、先手の手は青、後手の手は赤で塗った。盤面の駒は瞬間移動するが、どこからどこへ動いたかは色で追える。アニメーションを諦めた瞬間、UI が一気に落ち着いた。


7コミットを意味単位で分割

1日で _redirects の自動生成、VOICEVOX 辞書、measure-deploy、日記コンテンツ、計画書メモ、.claude 設定、将棋デモ、と7種類の変更が混ざっていた。1コミットにまとめるとレビューが効かないので、意味単位で7コミットに分割した。git add -p で1ハンクずつ振り分けて、コミットメッセージは変更の意図だけ書いた。


今日の学び

  • 局面の画像を AI に読ませるなら、Codex と Gemini の出力を必ず突き合わせる。片方を信じて先に進むと、ルール違反の局面で気づくまでにタイムロスが効いてくる。
  • WebFetch が bot 検知で止まるサイトは、agent-browser に切り替えると一発で通ることが多い。Google・Bing は agent-browser 越しでも厳しい。
  • TransitionGroup の FLIP は inline transform を奪いに来る。駒の transform を自前で握りたいなら TransitionGroup を諦めて、配色や枠線で「動き」を表現する方が早い。
  • 1日の差分が意味の違う変更を7つ抱えていたら、git add -p でコミットを割る。後から差分を読み直す自分が助かる。

会計士・税理士業務の文脈に橋を架けるなら、AI に資料の数値を読ませた結果はそのまま信じない、別 AI かオリジナル資料で必ず突き合わせる、というところが今日の要点になる。