Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions movensys_sample/doc/1a_robopoly_simulation.md
Original file line number Diff line number Diff line change
@@ -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/<MANIPULATOR_MODEL>/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
Expand Down
3 changes: 3 additions & 0 deletions movensys_sample/doc/1c_robopoly_real.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
2 changes: 1 addition & 1 deletion movensys_sample/movensys_robopoly/game/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
91 changes: 75 additions & 16 deletions movensys_sample/movensys_robopoly/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:",
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1950,20 +1987,21 @@ 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;
if (isUtility) {
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) {
Expand Down Expand Up @@ -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));
}
}

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"board_id": "final",
"tile_count": 14,
"seed_money": 500,
"seed_money": 999999999999,
"start_bonus": 100,
"physical_image": "board.png",
"layout": {
Expand Down
Loading