feat: 甲子園拡張機能の本格対応 (Epic #738: MockClient + ゴールデン契約 + Ruby v2 + RemoteClient)#761
Conversation
甲子園拡張機能のブロックがクリック/実行時に意味のある値を返すようにした。 KoshienClient をインターフェース化し、未接続時用の固定値クライアント MockClient を実装してブロックメソッドを接続した(将来の RemoteClient と 差し替え可能な構造)。 - map=0(空間), mapAll=15x15 全空間文字列, mapFrom=マップ文字列をパース - targetCoordinate: player=1:1 / goal=13:13 / other_player=enemy=7:7 - calcRoute/calcGoalRoute/locateObjects は指定リスト変数へ実際に書き込む - getMapArea/moveTo/setItem/turnOver/setMessage は安全な no-op Refs #739 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
甲子園拡張機能の calc_route / calc_goal_route / locate_objects は、リスト引数を
バージョン非依存で list("$名前") とハードコードしており、Ruby v2 では
「list() syntax is only available in Ruby version 1」エラーになっていた。
v2 ではリストはグローバル配列変数 $名前 で表現するため:
- generator: version === '2' のとき list("$名前") ではなく $名前 を出力
- converter: v2 では引数の配列変数 ($名前 = data_variable) を convertToListBlock で
リスト化して受理 (v1 の list() ブロックと同じリスト名に解決)
v1 の挙動は不変 (既存 roundtrip テスト緑)。v2 roundtrip テストを追加。
Refs #743
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…al server 過去にゲームサーバー再実装が「仕様不明確」で頓挫した反省を踏まえ、実ゲームサーバーの 挙動をゴールデン JSON として記録し、クライアントが従うべき契約をテストで固定する基盤を 追加する (#740)。 - tmp/smalruby-koshien/game_server (Ruby 3.1) の in-process API ドライバ (SpecHelper::Server) を流用した記録ドライバで、決定的シナリオ (SK_RANDOM_SEED=1) を 実行し各 API の返り値を fixture 化: - move_basic: move_to は予約で、座標は turnTransition 後に確定 (before [1,1] → after [2,1]) - get_map_area: getMapArea は 15x15 の自分マップ (未探索 -1) + enemy + other_player を返す - two_actions: 1 ターン 2 行動 (move + getMapArea) - test/unit/extensions/koshien_golden_contract.js (tap): 上記の実挙動を検証し、 将来の RemoteClient (#741) が replay できる契約の基盤とする。 - 記録ドライバ/サーバー起動手順は notes/koshien に整備 (git 管理外)。 Refs #740 / Epic #738 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/issue-741-koshien-remote-client
…th mock fallback 甲子園拡張機能が、設定された実ゲームサーバーと通信して実際にゲームを進められるよう にする (#741)。未接続時は固定値の MockClient にフォールバックするため、サーバーに つながなくても AI を作成できる。 - remote-client.js: ai_lib.rb の HTTP プロトコルを JS へ移植した RemoteClient。 - 非同期: connectGame / getMapArea / moveTo / setDynamite / setBomb / setMessage / turnOver。返り値で my_map / x / y / goal / enemy / other_player を更新。 - 同期レポーター: map / mapAll / mapFrom / targetCoordinate はキャッシュ状態を読む (ai_lib と同じく get_map_area が my_map を埋め、map(...) がそれを参照)。 - calc_route はダイクストラ (壁=通行不可、水=重い、未探索=さらに重い) を移植。 locate_objects は my_map 走査。 - index.js: runtime.getKoshienRemoteOptions() が endpoint を返せば RemoteClient、 なければ MockClient を使う (オフラインで AI 作成可能)。 - テスト: #740 のゴールデン fixture (実サーバー応答) を注入 fetch で流し込み、 RemoteClient が実応答を正しく解釈することを検証 (契約テスト)。バックエンド選択も検証。 ベース: #739 (MockClient) + #740 (ゴールデン) の上に構築。#741 は最後にマージ予定。 Refs #741 / Epic #738 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2 では甲子園プログラムをクラス表現 (class < Smalruby3::Sprite) の中に置くため、
ゲーム接続をフラットな文ではなく「ブロックを取るイベント hat」で表現する。
- generator: koshien_connectGame は v2 で
`koshien.when_connect_game(name:) do ... end` を生成し、AI 本体を do...end の
サブスタックに包む (block.isStatement=true → scrub_ が next 連鎖を字下げ + end)。
v1 は従来どおりフラット `koshien.connect_game(name:)`。
- converter:
- `koshien.when_connect_game(name:) do ... end` (v2) を hat + サブスタックとして解析。
- フラット `koshien.connect_game(name:)` は v1 で statement、v2 ではエラー
(when_connect_game を使うよう促す)。
- v2 roundtrip テスト (when_connect_game + リスト$配列) / v2 フラットエラー /
v1 フラット維持 を追加。
ブロック opcode/構造は不変のため、既存 sb3 プロジェクトはそのまま読み込める。
Refs #743
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…llback 接続できれば実サーバー、できなければ Mock 値、という #741 の中核挙動を実装。 - コンストラクタ: MockClient を常に保持し、endpoint 設定時のみ RemoteClient を併設。 起動時は RemoteClient (あれば) を使う。 - connectGame: remote のときは実サーバーへ非同期接続し、失敗 (到達不可/CORS/エラー) したら MockClient へフォールバック (つながなくても AI が動く)。mock のときは従来どおり同期接続。 - getMapArea / setItem / turnOver が client の結果 (remote では Promise) を返すように 変更し、シーケンサが await して順序を保てるようにした。 - テスト: 接続成功で RemoteClient 維持 + 実座標、接続失敗で MockClient へフォールバック + 固定値。tap 全 110 assert pass。 Refs #741 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
接続先は「プログラム」ではなく「Smalruby の設定」で行う方針 (#741) の土台。 接続設定 (接続先URL / プレイヤー側 / ゲームコード) を localStorage に保存し、 VM 拡張が読む contract `vm.runtime.getKoshienRemoteOptions()` を供給する。 - src/lib/koshien-connection.js: - load/saveKoshienConnection: localStorage 永続化 (side は 1/2 に正規化)。 - buildKoshienRemoteOptions: endpoint 未設定なら null (= Mock)、設定時は {endpoint, side, gameCode, playerId(自動生成・安定)} を返す。 - wireKoshienRemoteOptions(vm): runtime に getter を差し込む (拡張が読む)。 - testKoshienConnection(endpoint): 設定モーダルの「接続テスト」用の到達性確認。 - 単体テスト (jest, 5 件) pass。 後続 (本 PR or 別 PR): 甲子園メニューに「接続設定」項目 + 設定モーダル UI (URL/側/コード/テストボタン) + wireKoshienRemoteOptions の起動時呼び出し。 Refs #741 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
接続先を「Smalruby の設定」で行う方針 (#741) の UI を実装。甲子園拡張機能を有効にすると 表示される甲子園メニューに「接続設定」項目を追加し、設定モーダルで接続先 URL / プレイヤー側 (1/2) / ゲームコードを設定する。 - src/components/koshien-settings-modal/: 設定モーダル (URL/側/コード + 接続テストボタン)。 保存で localStorage 永続化 + wireKoshienRemoteOptions(vm) を再実行。本文に不透明背景 (modals ルール準拠)。 - reducers/modals.js: koshienSettingsModal の open/close を追加。 - menu-bar.jsx: 甲子園メニューに「接続設定」項目 + ハンドラ + Redux 接続。 - components/gui.jsx, containers/gui.jsx: モーダルのマウントと state/dispatch マッピング。 - vm-manager-hoc.jsx: VM 初期化時に wireKoshienRemoteOptions(vm) を呼び、起動時から 保存済み設定を拡張が読めるようにする (Smalruby マーカー付き)。 - locales (ja/ja-Hira/en) + prettier ホワイトリスト + マーカー一覧を更新。 - 単体テスト: モーダルのフィールド描画 + 保存で localStorage 永続化 + runtime getter 設置。 注: 設定変更は次回の拡張機能ロード (リロード) 時に反映される (拡張は構築時に一度読む)。 Refs #741 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oteClient HTTP test
test:unit は `tap ./test/unit/*.js`(再帰なし)のため、test/unit/extensions/ 配下の
koshien テストは CI で実行されていなかった。mesh と同じく top-level へ移動して確実に走らせる。
- test/unit/extensions/koshien_{golden_contract,remote_client}.js を test/unit/ へ移動
(require パス修正)。
- RemoteClient の実 HTTP 経路を検証する統合テストを追加:
- test/fixtures/koshien/mock-server.js: CORS 付き Koshien API モックサーバー
(canned 応答, CLI 起動可: `npm run koshien:mock-server [port]`)。
- test/unit/koshien_remote_client_http.js: in-process モックサーバーに対して
RemoteClient を実 fetch で駆動(connect/getMapArea/move/turnOver)。
- prettier ホワイトリスト / マーカー一覧 / package.json スクリプトを更新。
- 全 koshien vm テスト 89 assert pass、lint 緑。
Refs #741
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…level) test:unit は `tap ./test/unit/*.js`(再帰なし)のため test/unit/extensions/ 配下の テストが CI で実行されていなかった。golden contract テストを top-level へ移動して 確実に走らせる(require パス修正・prettier ホワイトリスト/マーカー更新)。51 assert pass。 Refs #740 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on loaded 甲子園拡張機能を v2 対応にしたため、「koshien 有効時は Ruby バージョンを変更できない」 という guard / アラートは不要になった。settings-menu と mobile-drawer の両方から削除し、 未使用になったメッセージ koshienCannotChangeRubyVersion とロケール(ja/ja-Hira/en)も除去。 v1↔v2 切替時の「v2機能(class/module)使用中は v1 に戻せない」guard は維持。 Refs #743 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…en blocks When not connected to a real game server the koshien blocks previously returned flat placeholders (all-zero map, [src,dst] route, fixed [7:7]). During the first explanation a user is almost always disconnected, so clicking a block gave no sense of what it really returns. Now the mock derives every value from a single source of truth — a fixed, believable 15x15 sample map (MOCK_MAP) with walls, water, a goal and scattered items — via new pure helpers in map-utils.js (shared-ready so the remote client can adopt the same Dijkstra/scan logic): - map / map_all / map_from -> real cell codes from the sample map - calc_goal_route / calc_route -> actual shortest path (multi-waypoint), honoring the EXCEPT_CELLS list - locate_objects -> the items really present within range - target_coordinate -> positions consistent with the map Phase 2 (pseudo state): move_to moves the mock player, set_item marks the map, turn_over advances a counter, so repeated runs feel alive. Reset timing: the mock world resets to its initial state on green flag (PROJECT_START), on stop (PROJECT_STOP_ALL), and when connect_game runs — the natural "new game" moments, so a demo can be replayed cleanly. Tests: rewrote extension_koshien.js expectations for the believable values and added koshien_map_utils.js for the pure helpers (146 assertions, lint clean). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The mock sample map was a hand-made 15x15 grid. The real Smalruby Koshien field is 17x17 surrounded by an unbreakable wall border (verified against the 2024 default map "map_01": top/left edge = code 2, bottom/right edge = code 1; breakable walls are 5). - MOCK_MAP is now map_01 with its item layer merged into letters (a-e/A-D), exactly how the real server encodes my_map (ITEM_MARKS). Player starts (5,1) and (10,1), goal '3' at (8,9), enemy guards the goal. - map-utils.locateObjects: sq_size is the full square side, not a radius (real game uses radius = (sq_size-1)/2); fixed an over-wide scan window. Tests updated to the real map values + a new assertion that the whole field is bordered by unbreakable walls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The mock now mirrors the real game's exploration: the player's "my map" starts fully unexplored (all -1) and is revealed 5x5 at a time by get_map_area. map / map_all / calc_route / locate_objects all work off this gradually-revealed my map, so locate_objects finds nothing until the area is explored and calc_route plans through fog (unexplored = passable). moveTo passability is checked against the ground-truth map, and set_item marks the ground truth (visible once that cell is revealed). Reset clears the my map back to fully unexplored. map-utils gains createUnexploredGrid + revealArea (clamped window, matching the server's getMapArea range). Tests reworked to the explore-then-read flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Selecting the Koshien extension while in Ruby v2 popped an alert
("only available for Ruby v1") and refused to load it. Now that koshien
supports v2 (class representation + when_connect_game), this guard is
obsolete. Removed the rubyVersion check, its message, the unused
rubyVersion prop/mapStateToProps, and the locale strings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switching to Ruby v2 with the koshien extension loaded is now allowed (koshien supports v2), so the stale "shows alert / blocks the change" test is replaced with one asserting the change goes through without an alert. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…client feat(scratch-vm): 甲子園ブロックの固定値/状態実装(MockClient)
…golden-contract # Conflicts: # packages/scratch-vm/.prettierignore
…n-contract test(scratch-vm): 甲子園 実サーバーのゴールデン記録 + 契約テスト基盤
feat(scratch-gui): 甲子園 Ruby v2 対応(when_connect_game イベント hat + リスト引数 $配列構文)
…remote-client # Conflicts: # .claude/rules/scratch-vm/smalruby-prettier-files.md # packages/scratch-gui/src/locales/en.js # packages/scratch-gui/src/locales/ja-Hira.js # packages/scratch-gui/src/locales/ja.js # packages/scratch-vm/.prettierignore # packages/scratch-vm/src/extensions/koshien/index.js # packages/scratch-vm/test/unit/extension_koshien.js
…e-client feat(scratch-vm): 甲子園 実ゲームサーバー通信モード(RemoteClient)[Draft/最後にマージ]
|
🚀 Preview deployed: https://smalruby.jp/smalruby3-editor/topic/koshien-epic-738/ |
…s player1 The "Test AI" modal embedded the Koshien WEB viewer with no query params, so it always ran the default AI. The viewer accepts `?player1=data:<base64(utf8)>` (game_viewer/js/player-ai-client.js): a data: blob is decoded to Ruby source, and with no param it falls back to the default AI. - koshien-test-url.js: encodeAiToPlayerParam / buildKoshienTestUrl helpers (UTF-8-safe base64, blank code -> base URL so the default AI still runs). - koshien-test-modal.jsx: connect to vm + rubyVersion, generate the current editing target's runnable Ruby (generatePreviewCode, current version) and pass it as player1. player2 stays the default AI. Regenerated on Reload. - unit tests round-trip the param exactly as the game viewer decodes it. Refs #762 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ai-param feat(koshien): 「AIを試す」で現スプライト・現バージョンの生成AIをplayer1で渡す (#762)
|
update branch して、最新の develop に追従してください。 そして、残作業を明確にしてください。 |
| */ | ||
| async connectGame (playerName) { | ||
| if (playerName) this._playerName = playerName; | ||
| const url = `${this._endpoint}/api/manage/connectGame`; |
There was a problem hiding this comment.
ご指摘の残作業(ゲームサーバー側 smalruby3 ライブラリの更新)について、tracking Issue を本リポジトリに用意済みです:
- feat(koshien-web): game server smalruby3 を新旧両対応に (koshien.xxx + when_connect_game + $配列 / 旧 Sprite.new do…end 維持) #763 game server smalruby3 を新旧両対応に(
koshien.xxx+when_connect_game+$配列/ 旧Sprite.new do…end維持)— 本クライアントが呼ぶ/api/manage/connectGame等の受け側 - chore(koshien-web): tmp/smalruby-koshien-web をdevpodでローカル実行可能化し新旧APIを動作確認 (rbwasm) #764
tmp/smalruby-koshien-webを devpod でローカル実行可能化し新旧 API を動作確認(rbwasm)
あわせて #761 全体の 3 点も対応済みです(詳細は #761 (comment) の bot コメント参照):
① develop へ update branch(マージ済み・0 commits behind)/② EPIC #738 本文の checkbox(#739/#740/#741/#743 を [x])+ 追加作業(#762)+ 残作業(#763/#764)セクション追記/③ 残作業の明確化。
editor 側(scratch-vm / scratch-gui)は本 Epic で実装完了です。実ゲーム進行には #763 / #764(別リポジトリ NaCl-Ltd/smalruby-koshien-web 側)がフォローアップとして残ります。
|
@takaokouji レビューありがとうございます。3 点すべて対応しました。 ① develop へ update branch最新 ② EPIC #738 の説明・checkbox 更新
③ 残作業の明確化(ゲームサーバー側 smalruby3 ライブラリ更新)ご指摘の「
editor 側(scratch-vm / scratch-gui)の実装は本 Epic で完了。実ゲーム進行には上記 #763 / #764(別リポジトリ NaCl-Ltd/smalruby-koshien-web 側の作業)が前提として残ります。EPIC #738 本文の「残作業」セクションにも明記しました。 |
🤖 autopilot status
Linked issue #738. Maintained by autopilot (single writer); do not edit. |
|
ゲームサーバー側を修正して新旧のAPIの両方で、本PRを使って動作確認できたら本 PR をマージする。 |
…-738 # Conflicts: # .claude/rules/scratch-gui/smalruby-prettier-files.md # .claude/rules/scratch-vm/smalruby-prettier-files.md
…s stage globals 「AIを試す」(koshien-test-modal) は編集中スプライト単体のみを Ruby に出力していたため、 「すべてのスプライトでつかう」グローバル変数/リスト(例 $最短経路)を使う koshien AI が 未初期化(nil)のまま実行され落ちていた。 generatePreviewCode をステージ + 編集中スプライトの2ターゲット出力に変更し、 ステージがグローバル変数/リストを初期化するようにした(v1/v2 両対応)。 また v2 でスクリプト無し・グローバル変数/リストありのステージが class Stage を生成せず def initialize を出力しないバグを修正(_hasInitializableState で空コードでも wrap)。 Closes #827 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(koshien): 「AIを試す」でステージ+スプライトを出力しグローバル初期化する (#827)
"AIを試す" wrapped the menu item in RubyDownloader and ran a save/download before opening the modal. The modal builds a base64 `?player1=data:...` URL from the current sprite + stage, so saving is unnecessary. Make the menu item open the modal directly and drop getTestAIHandler / handleTestAISaveFinished. "Save AI" / "Save AI as..." keep their save behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rray syntax
v1 generated plain array syntax ($name[N], 0-indexed) for all list element
and indexed operations, but the v1 Ruby->Blocks converter rejects array
syntax. Round-tripping a v1 program (e.g. the koshien AI list read
list("$最短経路")[2]) produced $最短経路[1], which fails to re-convert in v1.
Make data-list.js (and data_listcontents) version-aware: v1 emits the
list("$name") wrapper with 1-indexed access; v2 keeps $name[0-indexed].
Covers push/insert/delete_at/replace/item/index/length/include?/clear/empty?.
Aligns with the documented v1 spec (smalruby-language-spec-v1-diff).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(ruby-generator): v1 でリスト操作を list() ラッパー記法で出力する (#839)
fix(koshien): 「AIを試す」で不要な保存処理が走る — モーダルを開くだけにする (#840)
…-epic-738 feat: 甲子園拡張機能の本格対応 (Epic #738: MockClient + ゴールデン契約 + Ruby v2 + RemoteClient)
Summary
Epic #738「スモウルビー甲子園 拡張機能の本格対応」の取り込み PR。甲子園拡張機能を
「何もしないスタブ」から「未接続 = 意味のある固定値で動く / 接続 = 実ゲームサーバーと
実通信」へ。差し替え可能な
KoshienClientインターフェース +MockClient(オフライン)/
RemoteClient(実サーバー HTTP)アーキテクチャ。このトピックブランチ
topic/koshien-epic-738に、最新 develop(upstream v13.7.2 再整合#749/#752 を含む)へ追従させた上で、以下 4 PR を順に統合済み。各 PR は develop 再整合後の
CI をすべて green で通している。
取り込んだ PR(マージ順)
when_connect_gameイベント hat + リスト引数$配列構文)develop 再整合への追従で行ったこと
4 PR は再整合(modern Blockly / scratch-blocks v2.1.19 への HAT/EVENT 描画作り直し)前の
develop に対して作られていたため、最新 develop を各ブランチに merge し直して CI を回した。
主な統合ポイント:
koshien/index.js: feat(scratch-vm): 甲子園 実ゲームサーバー通信モード(RemoteClient)[Draft/最後にマージ] #746 は feat(scratch-vm): 甲子園ブロックの固定値/状態実装(MockClient) #742 の初期モック上に積まれていたため、feat(scratch-vm): 甲子園ブロックの固定値/状態実装(MockClient) #742 最終形(mapUtils ベースの fog-of-war モック)と feat(scratch-vm): 甲子園 実ゲームサーバー通信モード(RemoteClient)[Draft/最後にマージ] #746 の RemoteClient 配線を constructor で統合。
_mockClient(常設)/_remoteClient(endpoint 設定時)/_client = remote || mock+_fallbackToMock()+ green flag/stop での_resetMockWorld()(接続中は no-op)を両立。extension_koshien.js: feat(scratch-vm): 甲子園 実ゲームサーバー通信モード(RemoteClient)[Draft/最後にマージ] #746 のバックエンド選択テストと feat(scratch-vm): 甲子園ブロックの固定値/状態実装(MockClient) #742 のモック状態/リセットテストを統合。feat(scratch-vm): 甲子園 実ゲームサーバー通信モード(RemoteClient)[Draft/最後にマージ] #746 が前提にしていた旧モックの固定値(
map(0:0)=0/ player1:1)を、統合後の新モックの実値(
map(0:0)=-1未探索 / player 初期5:1)に更新。(feat(scratch-vm): 甲子園 実ゲームサーバー通信モード(RemoteClient)[Draft/最後にマージ] #746)のキー・エントリを union。feat(scratch-vm): 甲子園ブロックの固定値/状態実装(MockClient) #742 で記載漏れだった
koshien_map_utils.jsもprettier-files.md に補完。
Test Coverage
(extension_koshien / koshien_map_utils / golden_contract / remote_client / remote_client_http)、
再整合共有テスト(runtime_extension_hat_shape / extension_conversion)108 pass、
scratch-gui feat(scratch-vm): 甲子園 実ゲームサーバー通信モード(RemoteClient)[Draft/最後にマージ] #746 テスト 8 pass、scratch-vm/scratch-gui lint green
Related Issues
Epic #738(Sub: #739 / #740 / #741 / #743)
closes #738