Files
snake-python/templates/files/js/Thinking.js
T
2026-04-07 03:25:10 +02:00

111 lines
4.4 KiB
JavaScript

class ThinkingPanel {
constructor(thinkingEl) {
this._el = thinkingEl;
}
get element() { return this._el; }
render(turn, replay) {
if (!turn) {
this._el.innerHTML = "<p class=\"section-title\">Select a game to inspect reasoning.</p>";
this._syncMonoOffset();
return;
}
const reasoning = turn.my_thinking;
const reasons = this._extractReasoningList(reasoning);
const reasonList = reasons.map((item) => `<li>${item}</li>`).join("");
const gameMeta = replay && replay.game ? replay.game : {};
const snakeType = Utils.safeString(gameMeta.your_snake_type);
const snakeVersion = Utils.safeString(gameMeta.your_snake_version);
this._el.innerHTML = `
<div class="think-grid">
<div class="chip"><span class="k">Chosen Move</span><span class="v">${Utils.safeString(turn.my_move)}</span></div>
<div class="chip"><span class="k">Snake Type</span><span class="v">${snakeType}</span></div>
<div class="chip"><span class="k">Snake Version</span><span class="v">${snakeVersion}</span></div>
<div class="chip"><span class="k">Observed At</span><span class="v">${Utils.formatObservedAtLocal(turn.observed_at)}</span></div>
<div class="chip"><span class="k">Food Count</span><span class="v">${Array.isArray(turn.food) ? turn.food.length : 0}</span></div>
<div class="chip"><span class="k">Hazard Count</span><span class="v">${Array.isArray(turn.hazards) ? turn.hazards.length : 0}</span></div>
</div>
<section class="snakes-section">
<table class="score-table">
<colgroup>
<col style="width:32%">
<col style="width:10%">
<col style="width:10%">
<col style="width:20%">
<col style="width:6%">
<col style="width:7%">
<col style="width:7%">
</colgroup>
<thead><tr><th>Snake</th><th>Latency</th><th>Move</th><th>Health</th><th>Length</th><th>Head</th><th>Tail</th></tr></thead>
<tbody>${SnakeTable.buildSnakesRows(turn, replay)}</tbody>
</table>
</section>
<section class="scores-section">
<p class="section-title">Move Scores</p>
<table class="score-table">
<colgroup><col style="width:50%"><col style="width:50%"></colgroup>
<thead><tr><th>Move</th><th>Score</th></tr></thead>
<tbody>${MoveTable.buildScoresRows(reasoning)}</tbody>
</table>
</section>
<section>
<p class="section-title">Decision Summary</p>
<ul class="reason-list">${reasonList}</ul>
</section>
<section class="raw-block">
<p class="section-title">Raw Reasoning Payload</p>
<pre class="mono">${JSON.stringify(reasoning, null, 2)}</pre>
</section>
`;
this._syncMonoOffset();
}
highlightSnake(snakeId) {
const section = this._el.querySelector(".snakes-section");
if (!section) return;
section.querySelectorAll(".snake-row.highlighted").forEach((r) => r.classList.remove("highlighted"));
section.classList.remove("has-highlight");
if (!snakeId) return;
const row = section.querySelector(`[data-snake-id="${CSS.escape(snakeId)}"]`);
if (row) {
row.classList.add("highlighted");
section.classList.add("has-highlight");
}
}
syncMonoOffset() {
this._syncMonoOffset();
}
_syncMonoOffset() {
const mono = this._el.querySelector(".mono");
if (!mono) return;
const rect = mono.getBoundingClientRect();
const bottomPaddingPx = 36;
const offset = Math.max(120, Math.round(rect.top + bottomPaddingPx));
document.documentElement.style.setProperty("--mono-vh-offset", `${offset}px`);
}
_extractReasoningList(reasoning) {
const parts = [];
if (!reasoning || typeof reasoning !== "object") {
return ["No reasoning recorded by this snake implementation."];
}
if (reasoning.reason) parts.push(`Reason: ${reasoning.reason}`);
if (reasoning.mode) parts.push(`Mode: ${reasoning.mode}`);
if (reasoning.health !== undefined) parts.push(`Health: ${reasoning.health}`);
if (reasoning.length !== undefined) parts.push(`Length: ${reasoning.length}`);
if (reasoning.occupancy !== undefined) parts.push(`Occupancy: ${reasoning.occupancy}`);
if (reasoning.ms_remaining !== undefined) parts.push(`Time left: ${reasoning.ms_remaining}ms`);
if (parts.length === 0) parts.push("Structured reasoning not provided; showing raw payload below.");
return parts;
}
}