diff --git a/movensys_sample/doc/1a_robopoly_simulation.md b/movensys_sample/doc/1a_robopoly_simulation.md index 150ddb1..3410bfd 100644 --- a/movensys_sample/doc/1a_robopoly_simulation.md +++ b/movensys_sample/doc/1a_robopoly_simulation.md @@ -1,30 +1,34 @@ -# Running Robopoly Game +# Running Robopoly Game ## Step 1: Movensys-manipulator -check `movensys-manipulator/doc` 1_ and 2_ -Run `movensys-manipulator/doc/6a_yolo_simulation.md` step 1-4 +Follow 1_setup.md in `movensys-manipulator/doc` and set to `export MOVENSYS_ROS_VERSION=general` in ~/.bashrc configuration -## Step 2: Run VLM package -Run `movensys_vlm/doc/running.md` +## Step 2: Open Isaac Sim +Open `~/workspaces/movensys-simulation//7a_robopoly_simulation.usd` and press play button. -## Step 3: Running movensys_robopoly +### Step 3: Build containers on NVIDIA Blackwell GPU +```bash +cd ~/workspaces/movensys-intelligence/movensys_sample/doc +./run_robopoly.sh build_nvidia ``` -export MOVENSYS_PNP_DRY_RUN=0 -cd ~/workspaces/movensys-intelligence/movensys_sample/movensys_robopoly/docker -docker compose down -docker compose build -docker compose up -d -``` - -## Step 4: Enjoy the robopoly game -1. Click `Toggle is_YOLO` and check `is_YOLO` is set to ON. -2. Click Reset game and play the game. - - - +## Step 4: Run simulator bridge +``` +mros ros2 launch movensys_manipulator_moveit_config sim_bridge.launch.py simulator:=isaacsim use_sim_time:=true +``` +## Step 5: Launch MoveIt2's OMPL + API +``` +mros ros2 launch movensys_manipulator_moveit_config moveit.launch.py use_sim_time:=true +``` +## Step 6: Launch Yolo detector +``` +mros ros2 launch movensys_manipulator_perception yolo_dice_and_cube_detector.launch.py use_sim_time:=true +``` +## Step 7: Enjoy the robopoly game +1. Click `Toggle is_YOLO` and check `is_YOLO` is set to ON. +2. Click Reset game and play the game. # Running Robopoly Game w/o moving robot arm diff --git a/movensys_sample/doc/1c_robopoly_real.md b/movensys_sample/doc/1c_robopoly_real.md index a4318f2..bc92063 100644 --- a/movensys_sample/doc/1c_robopoly_real.md +++ b/movensys_sample/doc/1c_robopoly_real.md @@ -12,16 +12,19 @@ cd ~/workspaces/movensys-intelligence/movensys_sample/doc ### Step 2a: Build containers on Nvidia env (Terminal 2) ```bash +cd ~/workspaces/movensys-intelligence/movensys_sample/doc ./run_robopoly.sh build_nvidia ``` ### Step 2b-1: Build vllm containers on Intel env (Terminal 2) ```bash +cd ~/workspaces/movensys-intelligence/movensys_sample/doc ./run_robopoly.sh build_intel_vllm ``` ### Step 2b-2: Build other containers (Terminal 3) ```bash +cd ~/workspaces/movensys-intelligence/movensys_sample/doc ./run_robopoly.sh build_intel ``` diff --git a/movensys_sample/movensys_robopoly/game/rules.py b/movensys_sample/movensys_robopoly/game/rules.py index ff6b9ac..b84129c 100644 --- a/movensys_sample/movensys_robopoly/game/rules.py +++ b/movensys_sample/movensys_robopoly/game/rules.py @@ -33,7 +33,7 @@ HOTEL_PRICE = 3 * TIER_PRICE # tier 3 → $300 cumulative TAX_AMOUNT = 100 # flat tax for Non-Free Parking (§4.3) CHANCE_AMOUNT = 200 # chance card payout magnitude (§4.4) -LAPS_TO_WIN = 2 # end-of-game lap-count cap (§6.2) +LAPS_TO_WIN = 1000 # end-of-game lap-count cap (§6.2) def tier_of(p) -> int: diff --git a/movensys_sample/movensys_robopoly/static/app.js b/movensys_sample/movensys_robopoly/static/app.js index 3f995fe..2383a81 100644 --- a/movensys_sample/movensys_robopoly/static/app.js +++ b/movensys_sample/movensys_robopoly/static/app.js @@ -839,7 +839,7 @@ function announceFromEvent(env) { const t = payload.totals || {}; text = `🤝 Draw — both players at $${t.user ?? "?"}`; } else { - const reason = payload.reason === "lap_cap" ? " (2 laps)" : ""; + const reason = payload.reason === "lap_cap" ? " (1000 laps)" : ""; text = `🏆 ${payload.winner} wins the game!${reason}`; } announce(text, "win", { sticky: true }); @@ -888,6 +888,39 @@ let currentState = null; // while the Buy modal is open waiting for a choice). let turnInFlight = false; +// Auto-mode = robot-vs-robot. When ON, the user's turns are driven by the +// same deterministic robot logic as the robot's turns: the dice endpoint +// switches to /api/dice/roll_robot (physical pick-and-place of the die +// instead of only reading the human-thrown face), and AWAIT_DECISION is +// resolved by pickRobotDecision() instead of popping the Buy modal. The +// game then plays itself, turn after turn, until someone wins. +// Toggled by the "A" hotkey (works in both debug and game mode); ?auto=1 +// enables it on load. +let autoMode = new URLSearchParams(location.search).get("auto") === "1"; + +function setAutoMode(on) { + autoMode = !!on; + const msg = autoMode + ? "Auto-mode ON — robot vs robot" + : "Auto-mode OFF — user vs robot"; + appendChat({ role: "sys", text: msg }); + if (document.body.classList.contains("game-mode")) flashGameOverlay(msg); + // Keep the URL in sync so a reload preserves the mode. + const url = new URL(location.href); + if (autoMode) url.searchParams.set("auto", "1"); + else url.searchParams.delete("auto"); + history.replaceState(null, "", url.toString()); + // Turning it on mid-turn: clear the idempotency key so the auto-driver + // can re-fire for the current step (e.g. the user is sitting idle on + // their own TURN_START), then kick it. + if (autoMode) { + vlmPlayerLastTurnKey = null; + maybeAutoTriggerRobotTurn(); + } +} + +function toggleAutoMode() { setAutoMode(!autoMode); } + function renderState(state) { if (state.config && typeof state.config.is_YOLO === "boolean") { isYOLO = state.config.is_YOLO; @@ -966,9 +999,11 @@ function openStream() { current_tier: env.payload.current_tier ?? 0, max_tier: env.payload.max_tier, }; - // Robot decides via the VLM agent loop; keep the JS state but skip - // the modal so the operator only ever sees buy choices for the user. - if (currentState?.turn === "robot") pendingDecision = decision; + // Robot decides deterministically; keep the JS state but skip the + // modal so the operator only ever sees buy choices for the user. + // In auto-mode the user is robot-driven too, so suppress the modal + // for both players and let maybeAutoTriggerRobotTurn() decide. + if (currentState?.turn === "robot" || autoMode) pendingDecision = decision; else showDecision(decision); } // Close the modal when *any* client (script, second tab, agent loop) @@ -1127,7 +1162,9 @@ document.getElementById("btn-roll-dice").addEventListener("click", async () => { // has a clear view. No pickup, no drop. // - Robot turn → /api/dice/roll_robot. The arm physically picks // up, lifts, drops the die, then reads the rolled face. - const diceEndpoint = (currentState && currentState.turn === "user") + // - Auto-mode → roll_robot for BOTH players: the user's turn is now + // played by the robot, so it physically rolls the die too. + const diceEndpoint = (currentState && currentState.turn === "user" && !autoMode) ? "/api/dice/read_robot" : "/api/dice/roll_robot"; console.log("[roll-chain] dice step:", @@ -1718,7 +1755,7 @@ Choice meaning (cumulative cost from unowned = rent opponent pays): build_hotel tier 3, $300 land + hotel skip no purchase Upgrade delta from owned = $100 × (target_tier − current_tier). -Seed $500, GO bonus $100, tax $100, chance ±$200, 2 laps to win. Rent $150/$300/$450 per tier. +Seed $500, GO bonus $100, tax $100, chance ±$200, 1000 laps to win. Rent $150/$300/$450 per tier. Constraints: - decision_pending.kind=="utility" → only buy or skip are legal. @@ -1950,9 +1987,9 @@ function parseVoiceDecision(text) { // is the fallback when cash is tight. Hotels never fire from unowned // (the two-step buy + build server txn risks a partial failure at the // $200 boundary); they only happen on revisit. -function pickRobotDecision(state, pending) { - const liquid = state.players?.robot?.balance ?? 0; - const lap = state.lap_count?.robot ?? 0; +function pickRobotDecision(state, pending, player = "robot") { + const liquid = state.players?.[player]?.balance ?? 0; + const lap = state.lap_count?.[player] ?? 0; const currentTier = pending.current_tier ?? 0; const maxTier = pending.max_tier ?? 3; const isUtility = pending.kind === "utility" || maxTier === 1; @@ -1960,10 +1997,11 @@ function pickRobotDecision(state, pending) { return (currentTier === 0 && liquid >= 200) ? "buy" : "skip"; } + // Two-player game: whoever isn't `player` is the opponent. let oppHotels = 0, ownHotels = 0, ownTotal = 0; for (const p of Object.values(state.properties || {})) { if (!p.owner) continue; - if (p.owner === "robot") { + if (p.owner === player) { ownTotal++; if (p.has_hotel) ownHotels++; } else if (p.has_hotel) { @@ -2103,22 +2141,39 @@ const SOLO_MODE = new URLSearchParams(location.search).get("solo") === "1"; function maybeAutoTriggerRobotTurn() { if (SOLO_MODE) return; // external driver owns the game; stand down if (!currentState || currentState.winner) return; - if (currentState.turn !== "robot") return; + const player = currentState.turn; + // The robot always auto-plays. In auto-mode (robot-vs-robot) the user's + // turns are driven by the same logic so the whole game plays itself. + const shouldDrive = player === "robot" || (autoMode && player === "user"); + if (!shouldDrive) return; const pendingPid = pendingDecision ? pendingDecision.property_id : ""; - const key = `${currentState.turn}|${currentState.fsm}|${currentState.turn_number}|${pendingPid}`; + const key = `${player}|${currentState.fsm}|${currentState.turn_number}|${pendingPid}`; if (vlmPlayerLastTurnKey === key) return; if (currentState.fsm === "TURN_START") { vlmPlayerLastTurnKey = key; - vlmPlayerAct("It's your turn (robot). Roll the dice and move your cube."); + if (player === "robot") { + vlmPlayerAct("It's your turn (robot). Roll the dice and move your cube."); + } else { + // Auto-mode user turn: skip the VLM round-trip (roll_and_move is + // deterministic) and fire the same Roll-dice chain the robot uses. + // The dice endpoint switches to roll_robot because autoMode is on. + const btn = document.getElementById("btn-roll-dice"); + if (btn && !btn.disabled) { + appendChat({ role: "sys", text: "Auto-mode → user rolls" }); + btn.click(); + } + } } else if (currentState.fsm === "AWAIT_DECISION" && pendingDecision) { vlmPlayerLastTurnKey = key; // Deterministic strategy lives in pickRobotDecision — no VLM call. // The choice is legal by construction (skip is always legal), so we // can submitDecision directly and AWAIT_DECISION always advances. - const choice = pickRobotDecision(currentState, pendingDecision); - appendChat({ role: "sys", text: `Robot decision → ${choice}` }); + // In auto-mode this also handles the user's buys, using the acting + // player's own cash / holdings. + const choice = pickRobotDecision(currentState, pendingDecision, player); + appendChat({ role: "sys", text: `Auto ${player} decision → ${choice}` }); submitDecision(choice, choice === "build" ? 1 : 0) - .catch((err) => console.warn("[robot-decide]", err)); + .catch((err) => console.warn("[auto-decide]", err)); } } @@ -2632,6 +2687,10 @@ function setupHotkeys() { const k = e.key.toLowerCase(); if (k === "escape") { closeChatOverlay(); return; } + // A: toggle auto-mode (robot vs robot). Works in both debug and game + // mode; when turned on it immediately starts driving the current turn. + if (k === "a") { e.preventDefault(); toggleAutoMode(); return; } + const isGame = document.body.classList.contains("game-mode"); // Game mode: C reveals/hides the overlay without recording. Z/X always // act as hold-to-record hotkeys *and* additionally open the overlay diff --git a/movensys_sample/movensys_robopoly/static/assets/boards/board_final.json b/movensys_sample/movensys_robopoly/static/assets/boards/board_final.json index bda428e..643f0be 100644 --- a/movensys_sample/movensys_robopoly/static/assets/boards/board_final.json +++ b/movensys_sample/movensys_robopoly/static/assets/boards/board_final.json @@ -1,7 +1,7 @@ { "board_id": "final", "tile_count": 14, - "seed_money": 500, + "seed_money": 999999999999, "start_bonus": 100, "physical_image": "board.png", "layout": {