Skip to content

Latest commit

 

History

History
525 lines (398 loc) · 21.2 KB

File metadata and controls

525 lines (398 loc) · 21.2 KB

Plan: Per-Bot Resource Metrics (Battery, Memory, CPU Temp)

Context

Adding battery %, memory MB, and CPU temp gauges per bot in the final game. Only the local player's bot is shown in a panel overlaid on the top-left corner of the grid. Two consequences: battery death (permanent stop) and cooldown (temporary freeze when CPU or memory maxes out, then auto-resume).


Resource definitions

Resource Random start Dijkstra start Max Change per tick
Battery % 100 100 100 -1% every move
Memory MB 0 MB 128 MB 256 MB 0 (random) / +5-10 MB (Dijkstra)
CPU C 50 70 100 +/-3-5 random (random) / +3-5 only (Dijkstra)

Key decisions

  • Battery death: grayscale filter on bot sprite + red OFFLINE badge in panel
  • Cooldown trigger: red tint (CSS filter) on bot sprite + yellow "COOLING DOWN" message in panel
  • Cooldown: bot fully frozen; resources drain back to starting values per tick; then auto-resumes
  • Panel: always visible from page load (shows starting values before game starts)
  • Phase reset: all resources reset to starting values at each phase transition
  • Movement type change: resources reset immediately to new starting values
  • Panel: shows only local player's bot (window.currentBotId)

Changes

1. virtual-board/grid.js — defaultBot

Add three fields to defaultBot (lines 81-93):

battery_pct: 100,
memory_mb:   0,
cpu_temp:    50,

These serialize to Firebase automatically because the entire bot object is passed unfiltered to update() in replace_bot (firebase-sync.js line 295).


2. virtual-board/bot-movement.js — resource logic and modified move()

Add module-level flag: let is_cooling_down = false; Reset it inside stopMovingBot_virtual so it never bleeds across phases.

Add exported helper getStartingResources(movement_type) — returns { battery_pct: 100, memory_mb, cpu_temp }:

  • "random" -> { memory_mb: 0, cpu_temp: 50 }
  • "dijkstra" -> { memory_mb: 128, cpu_temp: 70 }

Add updateBotResources(bot) — mutates bot in place (consistent with how move_bot/turn_bot in grid.js mutate in place):

  • battery: Math.max(0, bot.battery_pct - 1)
  • memory: unchanged if random; Math.min(256, bot.memory_mb + randInt(5,10)) if dijkstra
  • cpu: clamp(bot.cpu_temp + randSignedInt(3,5), 0, 100) if random; Math.min(100, bot.cpu_temp + randInt(3,5)) if dijkstra

Add drainResourcesToStart(bot) — called each tick during cooldown. Decreases cpu by randInt(3,5) and memory by randInt(5,10) toward starting values. When both reach starting values, sets is_cooling_down = false and removes the .bot-overheating class from the bot DOM element.

Add exported updateResourcePanel(bot) — updates panel DOM elements from bot fields. Shows/hides #coolingBadge (orange, "COOLING DOWN") based on is_cooling_down and #offlineBadge (red, "OFFLINE") based on battery_pct <= 0. Guards with early return if elements don't exist. Also exposed as window.updateResourcePanel (same pattern as window.startMovingBot etc.).

Modified move() inside startMovingBot_virtual:

function move() {
    const bot = window.grid.bots[bot_id]?.[0];
    if (!bot) return;

    if (bot.battery_pct <= 0) {
        stopMovingBot_virtual(bot_id);
        updateResourcePanel(bot);
        return;
    }

    if (is_cooling_down) {
        drainResourcesToStart(bot); // sets is_cooling_down = false when done, removes bot-overheating class
        if (window.emitReplaceBot) window.emitReplaceBot(bot);
        updateResourcePanel(bot);
        return;
    }

    const next_move = window.grid.get_next_move_using_policies(bot_id, 1);
    if (!next_move) return;
    const { bot: updated_bot } = window.grid.apply_next_move_to_bot(
        bot_id,
        next_move,
    );

    updateBotResources(updated_bot);

    if (updated_bot.cpu_temp >= 100 || updated_bot.memory_mb >= 256) {
        is_cooling_down = true;
        // Apply red tint to bot sprite
        const botEl = document.getElementById(`bot-${bot_id}`);
        if (botEl) botEl.classList.add("bot-overheating");
    }

    if (window.emitReplaceBot) window.emitReplaceBot(updated_bot);
    updateResourcePanel(updated_bot);
}

3. virtual-board/phase-manager.js — reset on phase advance

In window.onPhaseChanged within the if (phase > prevPhase) block (line 130), after clear_grid(), add:

if (window.currentBotId && window.grid?.bots[window.currentBotId]) {
    const bot = window.grid.bots[window.currentBotId][0];
    Object.assign(bot, getStartingResources(bot.movement_type), {
        battery_pct: 100,
    });
    if (window.emitReplaceBot) window.emitReplaceBot(bot);
    window.updateResourcePanel?.(bot);
}

Import getStartingResources from ./bot-movement.js.


4. virtual-board/test-index.js — reset on movement type change

In the radio change handler (lines 133-149), after update_bot_movement_type:

const bot = window.grid.bots[window.currentBotId][0];
Object.assign(bot, getStartingResources(value), { battery_pct: 100 });
updateResourcePanel(bot);

Import getStartingResources, updateResourcePanel from ./bot-movement.js.


5. virtual-board/virtualMode.html — panel HTML

Add #resourcePanel as the first child inside #gridContainer (line 187). #gridContainer has position: relative (test-style.css line 70) so an absolutely-positioned child anchors to its top-left corner without affecting layout:

<div id="gridContainer" ...>
    <div id="resourcePanel">
        <div class="resource-row">
            <span class="resource-label">Battery</span>
            <div class="resource-bar-track">
                <div class="resource-bar" id="bar-battery"></div>
            </div>
            <span class="resource-value" id="val-battery">100%</span>
        </div>
        <div class="resource-row">
            <span class="resource-label">Memory</span>
            <div class="resource-bar-track">
                <div class="resource-bar" id="bar-memory"></div>
            </div>
            <span class="resource-value" id="val-memory">0 MB</span>
        </div>
        <div class="resource-row">
            <span class="resource-label">CPU Temp</span>
            <div class="resource-bar-track">
                <div class="resource-bar" id="bar-cpu"></div>
            </div>
            <span class="resource-value" id="val-cpu">50C</span>
        </div>
        <div id="coolingBadge" style="display:none;">COOLING DOWN</div>
        <div id="offlineBadge" style="display:none;">OFFLINE</div>
    </div>
    <!-- existing grid objects append here -->
</div>

6. virtual-board/virtualMode-style.css — panel styles

Add at the bottom of the file:

#resourcePanel {
    position: absolute;
    top: 8px;
    left: 8px;
    z-index: 10;
    background: rgba(0, 0, 0, 0.65);
    border-radius: 6px;
    padding: 8px 10px;
    min-width: 160px;
    pointer-events: none;
}
.resource-row {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 4px;
}
.resource-label {
    color: #ccc;
    font-size: 11px;
    width: 56px;
    flex-shrink: 0;
}
.resource-bar-track {
    flex: 1;
    height: 8px;
    background: #444;
    border-radius: 4px;
    overflow: hidden;
}
.resource-bar {
    height: 100%;
    background: #4caf50;
    border-radius: 4px;
    transition: width 0.3s;
}
.resource-bar.warning {
    background: #ff9800;
}
.resource-bar.danger {
    background: #e53935;
}
.resource-value {
    color: #fff;
    font-size: 11px;
    width: 44px;
    text-align: right;
    flex-shrink: 0;
}
#coolingBadge {
    color: #000;
    background: #ff9800;
    border-radius: 4px;
    text-align: center;
    font-size: 12px;
    font-weight: bold;
    padding: 2px 6px;
    margin-top: 4px;
}
#offlineBadge {
    color: #fff;
    background: #c00;
    border-radius: 4px;
    text-align: center;
    font-size: 12px;
    font-weight: bold;
    padding: 2px 6px;
    margin-top: 4px;
}
/* Red tint overlay during cooldown */
.bot-overheating {
    filter: sepia(1) saturate(5) hue-rotate(-20deg) opacity(0.85);
}
.bot-offline {
    filter: grayscale(100%) opacity(0.5);
}

7. virtual-board/game-setup.js — panel hookup and offline class

In setupNewBot (line 103), after drawBot returns DOM_ID, apply grayscale for dead bots:

if (bot.battery_pct <= 0) {
    document.getElementById(DOM_ID)?.classList.add("bot-offline");
}

In onReplaceBot (line 185), after setupNewBot(bot), update panel for local bot:

if (bot.id === window.currentBotId) window.updateResourcePanel?.(bot);

In setupSelectedBot (line 87), populate the panel on initial bot registration:

window.updateResourcePanel?.(bot);

Files to modify

File Change
virtual-board/grid.js Add battery_pct, memory_mb, cpu_temp to defaultBot
virtual-board/bot-movement.js Resource helpers + panel fn + modified move() + is_cooling_down flag
virtual-board/phase-manager.js Reset resources on phase advance; import getStartingResources
virtual-board/test-index.js Reset resources on radio change; import helpers
virtual-board/game-setup.js updateResourcePanel calls + .bot-offline class in setupNewBot
virtual-board/virtualMode.html Add #resourcePanel inside #gridContainer
virtual-board/virtualMode-style.css Panel, bar, badge, .bot-offline CSS

Verification

  1. Serve: python3 -m http.server 8080, open two tabs, join room.
  2. Panel visible on load with starting values (100%, 0 MB, 50C) before clicking Start Moving.
  3. Switch radio to Dijkstra: panel immediately shows 128 MB / 70C.
  4. Phase 1 (random): battery drops 1%/tick, memory stays 0, CPU fluctuates. Panel updates every 500ms.
  5. Phase 2 (Dijkstra): battery drops, memory climbs 5-10 MB/tick, CPU climbs.
  6. Max out CPU or memory: bot freezes, red tint appears on sprite, orange "COOLING DOWN" badge shown in panel. Values drain per tick back to starting values, then bot resumes and tint/badge clear automatically.
  7. Battery hits 0: bot permanently frozen, sprite greyscale, red OFFLINE badge in panel.
  8. Phase transition (1->2): resources reset to starting values for newly selected movement type.
  9. Other player's bot moving: local panel unchanged.

(Previous plan archived below)

Plan: Async Dijkstra Loading — Bot Waits in Place Until Graph is Ready

Context

Currently, a player using "Utility-based planning" (Dijkstra) must manually click "Load bot info!" before the game can start. This pre-loading step blocks the Start Moving button for all players until every Dijkstra user has processed the graph. The UX is confusing and introduces an asymmetric delay.

The desired behavior: the game starts for everyone at the same time regardless of planning type. The Dijkstra bot simply stays still on the grid while its graph computes in the background; once done, it begins moving normally. The "Load bot info!" button and the require_graph Firebase gate are removed entirely.


What changes

1. Remove the require_graph pre-loading gate

The entire requireGraphRef system (require_graph in Firebase, requires_graph_load on VirtualGrid, the Firebase listener that disables the Start button, and reset_default_require_graph) exists solely to enforce pre-loading. All of it can be removed or bypassed.

virtual-board/test-index.js

  • Remove the loadBotButton click handler (lines 123–130).
  • In the Dijkstra radio selection handler (lines 138–143): remove the calls to window.grid.reset_default_require_graph() and window.live_updates.reset_default_require_graph(), and remove body.setAttribute("needs-loading", ""). The radio handler just needs to call claim_dijkstra as it already does.
  • The noMovement guard in changeMovingBotsButton already checks bot.movement_type === null — no change needed there.

firebase-sync.js

  • Remove the onValue(this.requireGraphRef, ...) listener (lines 174–189) that disables changeMovingBotsButton and shows/hides loadBotButton. This is the key gate to remove.
  • Keep requireGraphRef declared (it's still used by reset_default_require_graph on the sync class, called from start_game_for_everyone) — or remove that call too. The safest approach: just remove the onValue listener so the button is never blocked.

firebase-game-flow.js

  • In stop_moving_bot (lines 22–36): remove the block that re-disables changeMovingBotsButton based on num_require and shows "Load bot info!" — since that button and flow no longer exist.
  • In start_game_for_everyone (line 42): remove set(this.requireGraphRef, {}) — no need to clear it.

virtual-board/virtualMode.html

  • Remove or hide the #loadBotButton element entirely (line 182).

virtual-board/test-style.css

  • Remove the body:not([needs-loading]) #loadBotButton { display: none; } rule (lines 190–192) — dead after button is removed.

2. Run Dijkstra asynchronously when the game starts

Instead of pre-loading synchronously on button click, trigger update_all_coin_graphs in the background when the bot starts moving, and hold the bot in place until it finishes.

virtual-board/bot-movement.js

The startMovingBot function sets up a setInterval(move, 500) tick. Modify it so that when the bot's movement_type is Dijkstra, it first kicks off graph computation asynchronously, then begins the interval only after the graphs are ready.

Since update_all_coin_graphs is synchronous but blocking, wrap it in a setTimeout(..., 0) to yield to the browser before computing, then start the interval in the callback. This keeps the UI responsive (the timer starts, other bots move) while the Dijkstra bot just hasn't started its interval yet.

Concrete change in startMovingBot (bot-movement.js):

// Before (simplified):
intervals[bot_id] = setInterval(move, 500);

// After:
if (
    window.grid.bots[bot_id]?.[0]?.movement_type ===
    MOVEMENT_VALUES.DIJKSTRA.value
) {
    setTimeout(() => {
        window.grid.update_all_coin_graphs(bot_id);
        window.grid.change_require_graph(bot_id, false);
        intervals[bot_id] = setInterval(move, 500);
    }, 0);
} else {
    intervals[bot_id] = setInterval(move, 500);
}

MOVEMENT_VALUES is already a global set by grid.js (plain <script>), so it's accessible in bot-movement.js.

virtual-board/grid.js

  • No changes needed. update_all_coin_graphs and the movement tick (get_next_move_using_policiesdistance_to_object) work correctly once the graphs exist; the bot just won't have an interval firing until they're ready.

Files to modify

File Change
virtual-board/bot-movement.js Branch on Dijkstra in startMovingBot: compute graphs async via setTimeout, then start interval
virtual-board/test-index.js Remove loadBotButton handler; remove reset_default_require_graph calls and needs-loading attribute from radio handler
firebase-sync.js Remove onValue(requireGraphRef) listener that disables the Start button
firebase-game-flow.js Remove set(requireGraphRef, {}) in start_game_for_everyone; remove require_graph button-disable logic in stop_moving_bot
virtual-board/virtualMode.html Remove #loadBotButton element
virtual-board/test-style.css Remove needs-loading CSS rule

Verification

  1. Serve: python3 -m http.server 8080, open two browser tabs, join the same room.
  2. In tab A: place a bot, select "Goal-based planning" (random). In tab B: place a bot, select "Utility-based planning" (Dijkstra).
  3. Confirm "Load bot info!" button is gone for both players.
  4. Both players click "Start Moving!" — confirm the game starts immediately for both without any extra step.
  5. Tab A's bot (random) begins moving at the first tick. Tab B's bot (Dijkstra) stays still for one tick while graphs compute, then begins moving.
  6. Verify no JS errors in the console during graph computation.
  7. Run through all 3 phases to confirm the flow works consistently across phase transitions.

Context

The final game has 3 phases. Before a phase can start, the player clicks "Start Moving!" — which already has a guard that blocks progression if the player's bot has no movement type selected (showing the "Oops!" modal). The request is to extend this same guard to also require that the grid has at least 1 coin (reward) and at least 1 obstacle placed before the player can signal ready.

The "Oops!" modal (#needToSelectMovementModal) already exists in virtualMode.html and has a single text body line. We'll append a second reason to that body and expand the JS guard to cover the new rule.

This constraint applies only to the final game (when window.tutorial is falsy). Tutorial phases should not be affected.


Changes

1. virtual-board/virtualMode.html — expand the modal body

Current modal body (line 108–110):

<div class="modal-body text-black">
    Need to select a type of movement for your bot!
</div>

Replace with two named reason paragraphs so JS can show/hide them independently:

<div class="modal-body text-black">
    <p id="oopsNoMovement" class="mb-2">
        Need to select a type of movement for your bot!
    </p>
    <p id="oopsNoRewardOrObstacle" class="mb-0" style="display:none;">
        The grid needs at least <strong>1 reward</strong> and
        <strong>1 obstacle</strong> before you can start!
    </p>
</div>

2. virtual-board/test-index.js — extend the button guard

Current guard (lines 106–113):

changeMovingBotsButton.addEventListener("click", async () => {
    const bot = window.grid.bots[window.currentBotId]?.[0];
    if (!bot || bot.movement_type === null) {
        needToSelectMovementHandler.show();
        return;
    }
    await changeMovingBotsHandler();
});

Replace with:

changeMovingBotsButton.addEventListener("click", async () => {
    const bot = window.grid.bots[window.currentBotId]?.[0];
    const noMovement = !bot || bot.movement_type === null;
    const missingItems =
        !window.tutorial &&
        (Object.keys(window.grid.coins).length < 1 ||
            Object.keys(window.grid.obstacles).length < 1);

    if (noMovement || missingItems) {
        document.getElementById("oopsNoMovement").style.display = noMovement
            ? ""
            : "none";
        document.getElementById("oopsNoRewardOrObstacle").style.display =
            missingItems ? "" : "none";
        needToSelectMovementHandler.show();
        return;
    }
    await changeMovingBotsHandler();
});

Key points:

  • Both conditions can be true simultaneously; both paragraphs will show.
  • The !window.tutorial guard skips the coin/obstacle check in tutorial pages.
  • Coin and obstacle counts come from Object.keys(window.grid.coins).length and Object.keys(window.grid.obstacles).length — these are already the canonical counts used elsewhere in the codebase (grid.js).

Files to modify

File Change
virtual-board/virtualMode.html Split modal body into two <p> tags with IDs
virtual-board/test-index.js Extend changeMovingBotsButton click guard

No other files need changes. The Firebase sync layer, phase-manager, and bot-movement are unaffected — the guard fires before any of them are invoked.


Verification

  1. Serve the app: python3 -m http.server 8080
  2. Open http://localhost:8080/virtual-board/doodlebotGame.html and create/join a room with 2 players.
  3. No bot + no reward/obstacle → click "Start Moving!" → modal shows both messages.
  4. Bot placed, movement selected, but no coin → modal shows only the reward/obstacle message.
  5. Bot placed, movement selected, but no obstacle → modal shows only the reward/obstacle message.
  6. Bot placed, movement selected, 1+ coin, 1+ obstacle → modal does not appear, phase starts normally.
  7. Open a tutorial page → verify the coin/obstacle check is skipped (modal only appears for missing movement type).