suchith's changes
#2
by suchith-koduru - opened
- README.md +1 -0
- api.py +17 -0
- backend/agent/llm_client.py +1 -1
- backend/engine.py +8 -2
- frontend/static/index.html +179 -23
- main.py +9 -0
- requirements.txt +1 -0
README.md
CHANGED
|
@@ -26,6 +26,7 @@ This project is built and submitted as part of the Hugging Face × Gradio **Buil
|
|
| 26 |
|
| 27 |
## 🔗 Submission Details
|
| 28 |
* **Live Space URL**: [https://build-small-hackathon-grid-royale.hf.space](https://build-small-hackathon-grid-royale.hf.space)
|
|
|
|
| 29 |
* **Demo Video:** *[todo]*
|
| 30 |
* **Social Post:** *[todo]*
|
| 31 |
|
|
|
|
| 26 |
|
| 27 |
## 🔗 Submission Details
|
| 28 |
* **Live Space URL**: [https://build-small-hackathon-grid-royale.hf.space](https://build-small-hackathon-grid-royale.hf.space)
|
| 29 |
+
* **Gradio Panel**: [https://build-small-hackathon-grid-royale.hf.space/gradio](https://build-small-hackathon-grid-royale.hf.space/gradio)
|
| 30 |
* **Demo Video:** *[todo]*
|
| 31 |
* **Social Post:** *[todo]*
|
| 32 |
|
api.py
CHANGED
|
@@ -17,6 +17,23 @@ app.add_middleware(
|
|
| 17 |
engine: Engine | None = None
|
| 18 |
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
class StartGameRequest(BaseModel):
|
| 21 |
grid_size: int = 20
|
| 22 |
max_turns: int = 50
|
|
|
|
| 17 |
engine: Engine | None = None
|
| 18 |
|
| 19 |
|
| 20 |
+
@app.on_event("startup")
|
| 21 |
+
async def _seed_default_game():
|
| 22 |
+
"""Pre-create a game so visitors see a live arena instead of a lobby."""
|
| 23 |
+
global engine
|
| 24 |
+
try:
|
| 25 |
+
cfg = GameConfig(grid_size=20, max_turns=100, max_agents=4, num_chests=15)
|
| 26 |
+
eng = Engine(config=cfg)
|
| 27 |
+
eng.generate_grid()
|
| 28 |
+
eng.scatter_chests()
|
| 29 |
+
for i, name in enumerate(_default_names(4)):
|
| 30 |
+
eng.spawn_agent(agent_id=f"agent_{i}", name=name)
|
| 31 |
+
engine = eng
|
| 32 |
+
except Exception as exc:
|
| 33 |
+
import logging
|
| 34 |
+
logging.getLogger("uvicorn.error").warning(f"Default game seed failed: {exc}")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
class StartGameRequest(BaseModel):
|
| 38 |
grid_size: int = 20
|
| 39 |
max_turns: int = 50
|
backend/agent/llm_client.py
CHANGED
|
@@ -47,7 +47,7 @@ PROVIDER_CONFIG = {
|
|
| 47 |
},
|
| 48 |
}
|
| 49 |
|
| 50 |
-
DEFAULT_PROVIDER = "modal"
|
| 51 |
|
| 52 |
|
| 53 |
def get_llm_client(provider: str | None = None) -> AsyncOpenAI:
|
|
|
|
| 47 |
},
|
| 48 |
}
|
| 49 |
|
| 50 |
+
DEFAULT_PROVIDER = os.getenv("LLM_PROVIDER", "modal")
|
| 51 |
|
| 52 |
|
| 53 |
def get_llm_client(provider: str | None = None) -> AsyncOpenAI:
|
backend/engine.py
CHANGED
|
@@ -59,10 +59,16 @@ class Engine:
|
|
| 59 |
pos=[x, y],
|
| 60 |
hp=self.env.config.base_hp,
|
| 61 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
brain = Agent(
|
| 63 |
agent_id=agent_id,
|
| 64 |
-
model=model or
|
| 65 |
-
provider=provider or
|
| 66 |
system_prompt=system_prompt,
|
| 67 |
config=self.env.config,
|
| 68 |
)
|
|
|
|
| 59 |
pos=[x, y],
|
| 60 |
hp=self.env.config.base_hp,
|
| 61 |
)
|
| 62 |
+
import os
|
| 63 |
+
default_provider = os.getenv("LLM_PROVIDER", "modal")
|
| 64 |
+
default_model = os.getenv(
|
| 65 |
+
"LLM_MODEL",
|
| 66 |
+
"qwen2.5:7b" if default_provider == "local" else "google/gemma-4-26B-A4B-it",
|
| 67 |
+
)
|
| 68 |
brain = Agent(
|
| 69 |
agent_id=agent_id,
|
| 70 |
+
model=model or default_model,
|
| 71 |
+
provider=provider or default_provider,
|
| 72 |
system_prompt=system_prompt,
|
| 73 |
config=self.env.config,
|
| 74 |
)
|
frontend/static/index.html
CHANGED
|
@@ -370,13 +370,24 @@ body {
|
|
| 370 |
.camera-mode-badge {
|
| 371 |
font-family: 'JetBrains Mono', monospace;
|
| 372 |
font-size: 9px; text-transform: uppercase; letter-spacing: 0.15em;
|
| 373 |
-
color: var(--
|
| 374 |
-
background: rgba(
|
| 375 |
-
border: 1px solid
|
| 376 |
-
padding:
|
| 377 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
}
|
| 379 |
-
.camera-mode-badge.visible { display: block; }
|
| 380 |
.winner-announce {
|
| 381 |
font-family: 'Space Grotesk', sans-serif;
|
| 382 |
font-size: 12px; font-weight: 700; color: var(--gold);
|
|
@@ -866,6 +877,29 @@ body {
|
|
| 866 |
flex: 1; overflow-y: auto; padding: 6px 10px;
|
| 867 |
}
|
| 868 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 869 |
/* ══════════════════════ CONTROLS ══════════════════════ */
|
| 870 |
.controls-bar {
|
| 871 |
display: flex; align-items: center; gap: 8px;
|
|
@@ -1153,7 +1187,8 @@ body {
|
|
| 1153 |
</div>
|
| 1154 |
|
| 1155 |
<div class="top-right">
|
| 1156 |
-
<
|
|
|
|
| 1157 |
<span class="winner-announce" id="winnerAnnounce"></span>
|
| 1158 |
</div>
|
| 1159 |
</div>
|
|
@@ -1265,6 +1300,50 @@ const AGENT_COLORS = {
|
|
| 1265 |
agent_6: '#00e5ff', agent_7: '#ff9100',
|
| 1266 |
};
|
| 1267 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1268 |
/* ═══════════ LOBBY PARTICLES ═══════════ */
|
| 1269 |
(function initLobbyParticles() {
|
| 1270 |
// Disabled for minimal, clean design
|
|
@@ -2205,19 +2284,38 @@ function enterFps() {
|
|
| 2205 |
app.controls.update();
|
| 2206 |
|
| 2207 |
document.getElementById('fpsHud').classList.add('active');
|
| 2208 |
-
|
| 2209 |
-
badge.textContent = '📹 CHASE CAM';
|
| 2210 |
-
badge.classList.add('visible');
|
| 2211 |
updateFpsHud(agent);
|
| 2212 |
buildSidebar();
|
| 2213 |
}
|
| 2214 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2215 |
function exitFps() {
|
| 2216 |
fpsMode = false;
|
| 2217 |
fpsCameraPos = null;
|
| 2218 |
fpsLookTarget = null;
|
| 2219 |
document.getElementById('fpsHud').classList.remove('active');
|
| 2220 |
-
|
| 2221 |
// Restore original orbit distances
|
| 2222 |
if (app) {
|
| 2223 |
app.controls.minDistance = 6;
|
|
@@ -2498,18 +2596,31 @@ function buildTabContent(aid, tab) {
|
|
| 2498 |
function buildTraceForAgent(aid) {
|
| 2499 |
const entries = traceHistory[aid] || [];
|
| 2500 |
if (!entries.length) return '<div class="empty-state">No trace data yet</div>';
|
| 2501 |
-
const color = AGENT_COLORS[aid] || '#888';
|
| 2502 |
return entries.map(e => {
|
| 2503 |
-
|
| 2504 |
-
|
| 2505 |
-
|
| 2506 |
-
|
| 2507 |
-
|
| 2508 |
-
|
| 2509 |
-
|
| 2510 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2511 |
}
|
| 2512 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2513 |
}).join('');
|
| 2514 |
}
|
| 2515 |
|
|
@@ -2561,9 +2672,22 @@ function buildTracePanel() {
|
|
| 2561 |
const name = agent?agent.name:aid, color=AGENT_COLORS[aid]||'#888';
|
| 2562 |
const entries = log.agents[aid]||[];
|
| 2563 |
entries.forEach(e => {
|
| 2564 |
-
if (e.phase==
|
| 2565 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2566 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2567 |
});
|
| 2568 |
}
|
| 2569 |
html += '</div>';
|
|
@@ -2596,6 +2720,7 @@ function selectAgent(id) {
|
|
| 2596 |
selectedId = id;
|
| 2597 |
activeTab = 'trace';
|
| 2598 |
buildSidebar();
|
|
|
|
| 2599 |
if (id && state) {
|
| 2600 |
const agent = (state.agents||[]).find(a=>a.id===id);
|
| 2601 |
if (agent && app) {
|
|
@@ -2948,6 +3073,37 @@ document.addEventListener('keydown', e => {
|
|
| 2948 |
});
|
| 2949 |
|
| 2950 |
window.addEventListener('resize', ()=>{ if (app) app.resize(); });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2951 |
</script>
|
| 2952 |
</body>
|
| 2953 |
</html>
|
|
|
|
| 370 |
.camera-mode-badge {
|
| 371 |
font-family: 'JetBrains Mono', monospace;
|
| 372 |
font-size: 9px; text-transform: uppercase; letter-spacing: 0.15em;
|
| 373 |
+
color: var(--text-dim);
|
| 374 |
+
background: rgba(255,255,255,0.04);
|
| 375 |
+
border: 1px solid var(--border);
|
| 376 |
+
padding: 4px 10px; border-radius: 5px;
|
| 377 |
+
cursor: pointer; transition: all 0.15s ease;
|
| 378 |
+
}
|
| 379 |
+
.camera-mode-badge:hover:not(:disabled) {
|
| 380 |
+
color: var(--purple); border-color: rgba(180,74,255,0.5);
|
| 381 |
+
background: rgba(180,74,255,0.08);
|
| 382 |
+
}
|
| 383 |
+
.camera-mode-badge.active {
|
| 384 |
+
color: var(--purple); border-color: rgba(180,74,255,0.5);
|
| 385 |
+
background: rgba(180,74,255,0.15);
|
| 386 |
+
box-shadow: 0 0 12px rgba(180,74,255,0.25);
|
| 387 |
+
}
|
| 388 |
+
.camera-mode-badge:disabled {
|
| 389 |
+
opacity: 0.4; cursor: not-allowed;
|
| 390 |
}
|
|
|
|
| 391 |
.winner-announce {
|
| 392 |
font-family: 'Space Grotesk', sans-serif;
|
| 393 |
font-size: 12px; font-weight: 700; color: var(--gold);
|
|
|
|
| 877 |
flex: 1; overflow-y: auto; padding: 6px 10px;
|
| 878 |
}
|
| 879 |
|
| 880 |
+
/* Visual trace chips */
|
| 881 |
+
.trace-chip {
|
| 882 |
+
display: inline-flex; align-items: center; gap: 3px;
|
| 883 |
+
padding: 1px 6px; border-radius: 8px;
|
| 884 |
+
font-size: 9px; font-weight: 700; letter-spacing: 0.04em;
|
| 885 |
+
font-family: 'JetBrains Mono', monospace;
|
| 886 |
+
}
|
| 887 |
+
.trace-chip.dmg { background: rgba(255,70,85,0.15); color: #ff7585; }
|
| 888 |
+
.trace-chip.score { background: rgba(251,191,36,0.15); color: #fbbf24; }
|
| 889 |
+
.trace-chip.pos { background: rgba(255,255,255,0.06); color: var(--text-dim); }
|
| 890 |
+
.trace-row.fail { border-left-color: #ff4655 !important; opacity: 0.72; }
|
| 891 |
+
.trace-row.fail .trace-label::before { content: '⚠ '; color: #ff4655; }
|
| 892 |
+
.trace-head {
|
| 893 |
+
display: flex; align-items: center; gap: 6px;
|
| 894 |
+
margin-bottom: 3px;
|
| 895 |
+
}
|
| 896 |
+
.trace-icon { font-size: 13px; line-height: 1; }
|
| 897 |
+
.trace-arrow { font-size: 15px; line-height: 1; font-weight: 700; }
|
| 898 |
+
.trace-tool { font-size: 9px; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 700; }
|
| 899 |
+
.trace-round { font-size: 9px; color: var(--text-muted); font-weight: 400; }
|
| 900 |
+
.trace-turn { font-size: 9px; color: var(--text-muted); margin-left: auto; }
|
| 901 |
+
.trace-result { font-size: 9.5px; color: var(--text-dim); margin-top: 2px; }
|
| 902 |
+
|
| 903 |
/* ══════════════════════ CONTROLS ══════════════════════ */
|
| 904 |
.controls-bar {
|
| 905 |
display: flex; align-items: center; gap: 8px;
|
|
|
|
| 1187 |
</div>
|
| 1188 |
|
| 1189 |
<div class="top-right">
|
| 1190 |
+
<button class="camera-mode-badge" id="newGameBtn" onclick="returnToLobby()" title="Open lobby to start a new game">⚙ NEW GAME</button>
|
| 1191 |
+
<button class="camera-mode-badge" id="cameraModeBadge" onclick="togglePov()" title="Toggle chase cam (V) — select an agent first" disabled>📹 OVERVIEW</button>
|
| 1192 |
<span class="winner-announce" id="winnerAnnounce"></span>
|
| 1193 |
</div>
|
| 1194 |
</div>
|
|
|
|
| 1300 |
agent_6: '#00e5ff', agent_7: '#ff9100',
|
| 1301 |
};
|
| 1302 |
|
| 1303 |
+
const TOOL_VIS = {
|
| 1304 |
+
observe: { icon: '👁', color: '#6ec5ff', label: 'OBSERVE' },
|
| 1305 |
+
think: { icon: '💭', color: '#a78bfa', label: 'THINK' },
|
| 1306 |
+
move: { icon: '➡', color: '#7dd3fc', label: 'MOVE' },
|
| 1307 |
+
attack: { icon: '⚔', color: '#ff4655', label: 'ATTACK' },
|
| 1308 |
+
dash: { icon: '💨', color: '#fbbf24', label: 'DASH' },
|
| 1309 |
+
shield: { icon: '🛡', color: '#34d399', label: 'SHIELD' },
|
| 1310 |
+
heal: { icon: '❤', color: '#f472b6', label: 'HEAL' },
|
| 1311 |
+
activate_ability: { icon: '✨', color: '#c084fc', label: 'ABILITY' },
|
| 1312 |
+
};
|
| 1313 |
+
const DEFAULT_VIS = { icon: '•', color: '#888', label: 'TOOL' };
|
| 1314 |
+
function ARROW_FOR(dx, dy) {
|
| 1315 |
+
dx = Math.sign(dx||0); dy = Math.sign(dy||0);
|
| 1316 |
+
if (dx===0 && dy<0) return '↑'; if (dx===0 && dy>0) return '↓';
|
| 1317 |
+
if (dx<0 && dy===0) return '←'; if (dx>0 && dy===0) return '→';
|
| 1318 |
+
if (dx<0 && dy<0) return '↖'; if (dx>0 && dy<0) return '↗';
|
| 1319 |
+
if (dx<0 && dy>0) return '↙'; if (dx>0 && dy>0) return '↘';
|
| 1320 |
+
return '·';
|
| 1321 |
+
}
|
| 1322 |
+
function pickVis(tool, args) {
|
| 1323 |
+
if (tool === 'activate_ability' && args && args.ability && TOOL_VIS[args.ability])
|
| 1324 |
+
return TOOL_VIS[args.ability];
|
| 1325 |
+
return TOOL_VIS[tool] || DEFAULT_VIS;
|
| 1326 |
+
}
|
| 1327 |
+
function outcomeChips(result) {
|
| 1328 |
+
const r = String(result || '');
|
| 1329 |
+
const chips = [];
|
| 1330 |
+
let fail = false;
|
| 1331 |
+
const score = r.match(/picked up (\d+) points?/i);
|
| 1332 |
+
if (score) chips.push(`<span class="trace-chip score">+${score[1]} pts</span>`);
|
| 1333 |
+
const loot = r.match(/picked up ability '([a-z_]+)'/i);
|
| 1334 |
+
if (loot) chips.push(`<span class="trace-chip score">+${esc(loot[1])}</span>`);
|
| 1335 |
+
const dmg = r.match(/for (\d+) damage/i);
|
| 1336 |
+
if (dmg) chips.push(`<span class="trace-chip dmg">−${dmg[1]} HP</span>`);
|
| 1337 |
+
const heal = r.match(/restored (\d+) HP/i);
|
| 1338 |
+
if (heal) chips.push(`<span class="trace-chip score">+${heal[1]} HP</span>`);
|
| 1339 |
+
const moved = r.match(/Moved to \((\d+),\s*(\d+)\)/i);
|
| 1340 |
+
if (moved) chips.push(`<span class="trace-chip pos">(${moved[1]},${moved[2]})</span>`);
|
| 1341 |
+
if (/eliminated|kill/i.test(r)) chips.push(`<span class="trace-chip dmg">☠ KILL</span>`);
|
| 1342 |
+
if (/shield (activated|broke)/i.test(r)) chips.push(`<span class="trace-chip score">🛡</span>`);
|
| 1343 |
+
if (/^(Invalid|cannot|don't have|can only|You cannot|Ability|No agent)/i.test(r) || /cooldown|no uses left/i.test(r)) fail = true;
|
| 1344 |
+
return { chips: chips.join(' '), fail };
|
| 1345 |
+
}
|
| 1346 |
+
|
| 1347 |
/* ═══════════ LOBBY PARTICLES ═══════════ */
|
| 1348 |
(function initLobbyParticles() {
|
| 1349 |
// Disabled for minimal, clean design
|
|
|
|
| 2284 |
app.controls.update();
|
| 2285 |
|
| 2286 |
document.getElementById('fpsHud').classList.add('active');
|
| 2287 |
+
updateCameraBadge();
|
|
|
|
|
|
|
| 2288 |
updateFpsHud(agent);
|
| 2289 |
buildSidebar();
|
| 2290 |
}
|
| 2291 |
|
| 2292 |
+
function updateCameraBadge() {
|
| 2293 |
+
const badge = document.getElementById('cameraModeBadge');
|
| 2294 |
+
if (!badge) return;
|
| 2295 |
+
if (fpsMode) {
|
| 2296 |
+
badge.textContent = '📹 EXIT CHASE';
|
| 2297 |
+
badge.classList.add('active');
|
| 2298 |
+
badge.disabled = false;
|
| 2299 |
+
badge.title = 'Exit chase cam (V)';
|
| 2300 |
+
} else if (selectedId) {
|
| 2301 |
+
badge.textContent = '📹 CHASE CAM';
|
| 2302 |
+
badge.classList.remove('active');
|
| 2303 |
+
badge.disabled = false;
|
| 2304 |
+
badge.title = 'Enter chase cam (V)';
|
| 2305 |
+
} else {
|
| 2306 |
+
badge.textContent = '📹 OVERVIEW';
|
| 2307 |
+
badge.classList.remove('active');
|
| 2308 |
+
badge.disabled = true;
|
| 2309 |
+
badge.title = 'Select an agent first';
|
| 2310 |
+
}
|
| 2311 |
+
}
|
| 2312 |
+
|
| 2313 |
function exitFps() {
|
| 2314 |
fpsMode = false;
|
| 2315 |
fpsCameraPos = null;
|
| 2316 |
fpsLookTarget = null;
|
| 2317 |
document.getElementById('fpsHud').classList.remove('active');
|
| 2318 |
+
updateCameraBadge();
|
| 2319 |
// Restore original orbit distances
|
| 2320 |
if (app) {
|
| 2321 |
app.controls.minDistance = 6;
|
|
|
|
| 2596 |
function buildTraceForAgent(aid) {
|
| 2597 |
const entries = traceHistory[aid] || [];
|
| 2598 |
if (!entries.length) return '<div class="empty-state">No trace data yet</div>';
|
|
|
|
| 2599 |
return entries.map(e => {
|
| 2600 |
+
if (e.phase !== 'exec') return '';
|
| 2601 |
+
const vis = pickVis(e.tool, e.args);
|
| 2602 |
+
const oc = outcomeChips(e.result);
|
| 2603 |
+
let extra = '';
|
| 2604 |
+
if (e.tool === 'move' || (e.tool === 'activate_ability' && e.args && e.args.ability === 'dash')) {
|
| 2605 |
+
const dx = e.args?.dx ?? 0, dy = e.args?.dy ?? 0;
|
| 2606 |
+
extra = `<span class="trace-arrow" style="color:${vis.color}">${ARROW_FOR(dx,dy)}</span>`;
|
| 2607 |
+
} else if (e.tool === 'activate_ability' && e.args && e.args.ability === 'attack' && (e.args.x!==undefined)) {
|
| 2608 |
+
extra = `<span class="trace-chip pos">→ (${e.args.x},${e.args.y})</span>`;
|
| 2609 |
+
} else if (e.tool === 'think') {
|
| 2610 |
+
const txt = String(e.args?.content || '').slice(0, 80);
|
| 2611 |
+
extra = txt ? `<span style="font-size:9.5px;color:var(--text-dim);font-style:italic;font-weight:400">${esc(txt)}${e.args?.content?.length>80?'…':''}</span>` : '';
|
| 2612 |
}
|
| 2613 |
+
const result = String(e.result || '').slice(0, 120);
|
| 2614 |
+
return `<div class="trace-row ${oc.fail?'fail':''}" style="border-left-color:${vis.color}">
|
| 2615 |
+
<div class="trace-head">
|
| 2616 |
+
<span class="trace-icon">${vis.icon}</span>
|
| 2617 |
+
<span class="trace-tool" style="color:${vis.color}">${vis.label}</span>
|
| 2618 |
+
${extra}
|
| 2619 |
+
${oc.chips}
|
| 2620 |
+
<span class="trace-turn">t${e.turn}·r${e.round}</span>
|
| 2621 |
+
</div>
|
| 2622 |
+
${result?`<div class="trace-result">${esc(result)}</div>`:''}
|
| 2623 |
+
</div>`;
|
| 2624 |
}).join('');
|
| 2625 |
}
|
| 2626 |
|
|
|
|
| 2672 |
const name = agent?agent.name:aid, color=AGENT_COLORS[aid]||'#888';
|
| 2673 |
const entries = log.agents[aid]||[];
|
| 2674 |
entries.forEach(e => {
|
| 2675 |
+
if (e.phase!=='exec') return;
|
| 2676 |
+
const vis = pickVis(e.tool, e.args);
|
| 2677 |
+
const oc = outcomeChips(e.result);
|
| 2678 |
+
let extra = '';
|
| 2679 |
+
if (e.tool === 'move' || (e.tool === 'activate_ability' && e.args && e.args.ability === 'dash')) {
|
| 2680 |
+
extra = `<span class="trace-arrow" style="color:${vis.color}">${ARROW_FOR(e.args?.dx, e.args?.dy)}</span>`;
|
| 2681 |
}
|
| 2682 |
+
html+=`<div class="trace-row ${oc.fail?'fail':''}" style="border-left-color:${color}">
|
| 2683 |
+
<div class="trace-head">
|
| 2684 |
+
<span class="trace-icon">${vis.icon}</span>
|
| 2685 |
+
<span style="color:${color};font-weight:700;font-size:10px">${esc(name)}</span>
|
| 2686 |
+
<span class="trace-tool" style="color:${vis.color}">${vis.label}</span>
|
| 2687 |
+
${extra}
|
| 2688 |
+
${oc.chips}
|
| 2689 |
+
</div>
|
| 2690 |
+
</div>`;
|
| 2691 |
});
|
| 2692 |
}
|
| 2693 |
html += '</div>';
|
|
|
|
| 2720 |
selectedId = id;
|
| 2721 |
activeTab = 'trace';
|
| 2722 |
buildSidebar();
|
| 2723 |
+
updateCameraBadge();
|
| 2724 |
if (id && state) {
|
| 2725 |
const agent = (state.agents||[]).find(a=>a.id===id);
|
| 2726 |
if (agent && app) {
|
|
|
|
| 3073 |
});
|
| 3074 |
|
| 3075 |
window.addEventListener('resize', ()=>{ if (app) app.resize(); });
|
| 3076 |
+
|
| 3077 |
+
function returnToLobby() {
|
| 3078 |
+
if (autoRunning) auto();
|
| 3079 |
+
gameStarted = false; selectedId = null; state = null;
|
| 3080 |
+
document.getElementById('lobby').classList.remove('hidden');
|
| 3081 |
+
const g = document.getElementById('game'); g.classList.remove('visible'); g.style.display = 'none';
|
| 3082 |
+
}
|
| 3083 |
+
|
| 3084 |
+
async function bootProbe() {
|
| 3085 |
+
try {
|
| 3086 |
+
const r = await fetch('/api/game/state');
|
| 3087 |
+
if (!r.ok) return;
|
| 3088 |
+
const s = await r.json();
|
| 3089 |
+
if (!s || !s.agents || !s.agents.length) return;
|
| 3090 |
+
state = s;
|
| 3091 |
+
gameStarted = true;
|
| 3092 |
+
nameMaterialCache = {};
|
| 3093 |
+
traceHistory = {};
|
| 3094 |
+
document.getElementById('lobby').classList.add('hidden');
|
| 3095 |
+
const game = document.getElementById('game');
|
| 3096 |
+
game.classList.add('visible'); game.style.display = 'flex';
|
| 3097 |
+
showLoading('Joining live arena...');
|
| 3098 |
+
try {
|
| 3099 |
+
await loadThree();
|
| 3100 |
+
if (!app) initScene();
|
| 3101 |
+
requestAnimationFrame(() => { if (app) app.resize(); renderScene(); });
|
| 3102 |
+
updateUI();
|
| 3103 |
+
} finally { hideLoading(); }
|
| 3104 |
+
} catch (_) { /* keep lobby */ }
|
| 3105 |
+
}
|
| 3106 |
+
bootProbe();
|
| 3107 |
</script>
|
| 3108 |
</body>
|
| 3109 |
</html>
|
main.py
CHANGED
|
@@ -1,8 +1,17 @@
|
|
| 1 |
import os
|
| 2 |
import uvicorn
|
|
|
|
| 3 |
from fastapi.staticfiles import StaticFiles
|
| 4 |
from api import app
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
app.mount("/", StaticFiles(directory="frontend/static", html=True), name="frontend")
|
| 7 |
|
| 8 |
if __name__ == "__main__":
|
|
|
|
| 1 |
import os
|
| 2 |
import uvicorn
|
| 3 |
+
import gradio as gr
|
| 4 |
from fastapi.staticfiles import StaticFiles
|
| 5 |
from api import app
|
| 6 |
|
| 7 |
+
with gr.Blocks(title="Grid Royale — Gradio Panel", theme=gr.themes.Base()) as demo:
|
| 8 |
+
gr.Markdown(
|
| 9 |
+
"## ⚔️ Grid Royale\n"
|
| 10 |
+
"The main spectator UI is at the [root of this Space](/).\n"
|
| 11 |
+
"This panel exists for hackathon REQ-02 compliance and quick health checks."
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
app = gr.mount_gradio_app(app, demo, path="/gradio")
|
| 15 |
app.mount("/", StaticFiles(directory="frontend/static", html=True), name="frontend")
|
| 16 |
|
| 17 |
if __name__ == "__main__":
|
requirements.txt
CHANGED
|
@@ -3,3 +3,4 @@ uvicorn>=0.20.0
|
|
| 3 |
pydantic>=2.0
|
| 4 |
openai>=1.0.0
|
| 5 |
python-dotenv>=1.0.0
|
|
|
|
|
|
| 3 |
pydantic>=2.0
|
| 4 |
openai>=1.0.0
|
| 5 |
python-dotenv>=1.0.0
|
| 6 |
+
gradio>=4.44.0
|