Skip to content

Commit ed94a75

Browse files
committed
Overhauled the resource panel (reward bar, color-coded gauges, history graphs) and gave Dijkstra bots a think→move cycle with live path reveal and a thinking-popup indicator.
1 parent ade29b6 commit ed94a75

42 files changed

Lines changed: 4851 additions & 409 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

board-editor.html

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>Doodlebot Board Editor</title>
7+
<style>
8+
:root {
9+
--cell: 34px;
10+
--gap: 1px;
11+
}
12+
* { box-sizing: border-box; }
13+
body {
14+
margin: 0;
15+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
16+
background: #1e1f22;
17+
color: #e6e6e6;
18+
padding: 20px;
19+
}
20+
h1 { font-size: 18px; margin: 0 0 4px; }
21+
p.hint { margin: 0 0 16px; color: #9aa0a6; font-size: 13px; }
22+
23+
.layout { display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap; }
24+
25+
/* Palette */
26+
.palette { display: flex; flex-direction: column; gap: 8px; min-width: 160px; }
27+
.swatch {
28+
display: flex; align-items: center; gap: 10px;
29+
padding: 8px 10px; border-radius: 8px; cursor: pointer;
30+
border: 2px solid transparent; background: #2b2d31; user-select: none;
31+
}
32+
.swatch:hover { background: #34363c; }
33+
.swatch.active { border-color: #fff; }
34+
.swatch .chip {
35+
width: 22px; height: 22px; border-radius: 5px; flex: none;
36+
display: flex; align-items: center; justify-content: center;
37+
font-weight: 700; font-size: 13px; color: #111;
38+
}
39+
.swatch .label { font-size: 13px; }
40+
.swatch .key { margin-left: auto; font-size: 11px; color: #9aa0a6; }
41+
42+
.tools { margin-top: 8px; display: flex; flex-direction: column; gap: 8px; }
43+
button {
44+
font: inherit; font-size: 13px; padding: 8px 12px; border-radius: 8px;
45+
border: 1px solid #45474d; background: #2b2d31; color: #e6e6e6; cursor: pointer;
46+
}
47+
button:hover { background: #34363c; }
48+
49+
/* Board */
50+
.board-wrap { display: inline-block; }
51+
.board {
52+
display: grid;
53+
grid-template-columns: 26px repeat(16, var(--cell));
54+
grid-auto-rows: var(--cell);
55+
gap: var(--gap);
56+
background: #3a3c42;
57+
padding: var(--gap);
58+
border-radius: 6px;
59+
}
60+
.board .corner, .board .hdr { font-size: 11px; color: #9aa0a6; }
61+
.board .hdr, .board .corner {
62+
display: flex; align-items: center; justify-content: center; background: transparent;
63+
}
64+
.cell {
65+
background: #f4f4f4;
66+
display: flex; align-items: center; justify-content: center;
67+
font-weight: 700; font-size: 14px; color: #111; cursor: crosshair;
68+
border-radius: 2px;
69+
}
70+
.cell.t-empty { background: #f4f4f4; }
71+
.cell.t-object { background: #444; color: #eee; }
72+
.cell.t-bot { background: #4f8cff; color: #fff; }
73+
.cell.t-reward { background: #ffd23f; color: #4a3b00; }
74+
75+
/* Export */
76+
.export { margin-top: 20px; }
77+
textarea {
78+
width: 100%; min-height: 220px; background: #111214; color: #d6d6d6;
79+
border: 1px solid #45474d; border-radius: 8px; padding: 10px;
80+
font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12px; white-space: pre;
81+
}
82+
.row-actions { display: flex; gap: 8px; margin-bottom: 8px; align-items: center; }
83+
.copied { color: #6ee787; font-size: 12px; }
84+
</style>
85+
</head>
86+
<body>
87+
<h1>Doodlebot Board Editor</h1>
88+
<p class="hint">Pick a type, then click or click-drag on the grid to paint. Export copies a tab-separated board you can paste to Claude.</p>
89+
90+
<div class="layout">
91+
<div class="palette">
92+
<div class="swatch active" data-type="bot">
93+
<span class="chip t-bot">B</span><span class="label">Bot</span><span class="key">1</span>
94+
</div>
95+
<div class="swatch" data-type="object">
96+
<span class="chip t-object">x</span><span class="label">Object</span><span class="key">2</span>
97+
</div>
98+
<div class="swatch" data-type="reward">
99+
<span class="chip t-reward">r</span><span class="label">Reward</span><span class="key">3</span>
100+
</div>
101+
<div class="swatch" data-type="empty">
102+
<span class="chip t-empty">o</span><span class="label">Empty</span><span class="key">4</span>
103+
</div>
104+
<div class="tools">
105+
<button id="clear">Clear board</button>
106+
<button id="export">Export ↓</button>
107+
</div>
108+
</div>
109+
110+
<div class="board-wrap">
111+
<div class="board" id="board"></div>
112+
</div>
113+
</div>
114+
115+
<div class="export">
116+
<div class="row-actions">
117+
<button id="copy">Copy to clipboard</button>
118+
<span class="copied" id="copied"></span>
119+
</div>
120+
<textarea id="output" readonly spellcheck="false"></textarea>
121+
</div>
122+
123+
<script>
124+
const SIZE = 16;
125+
// type -> exported character
126+
const CHAR = { empty: "o", object: "x", bot: "B", reward: "r" };
127+
const TYPES = ["bot", "object", "reward", "empty"];
128+
129+
let current = "bot";
130+
let painting = false;
131+
const grid = Array.from({ length: SIZE }, () => Array(SIZE).fill("empty"));
132+
const cells = [];
133+
134+
const boardEl = document.getElementById("board");
135+
136+
function buildBoard() {
137+
boardEl.innerHTML = "";
138+
// top-left corner
139+
const corner = document.createElement("div");
140+
corner.className = "corner";
141+
boardEl.appendChild(corner);
142+
// column headers
143+
for (let c = 0; c < SIZE; c++) {
144+
const h = document.createElement("div");
145+
h.className = "hdr";
146+
h.textContent = c;
147+
boardEl.appendChild(h);
148+
}
149+
for (let r = 0; r < SIZE; r++) {
150+
const rowHdr = document.createElement("div");
151+
rowHdr.className = "hdr";
152+
rowHdr.textContent = r;
153+
boardEl.appendChild(rowHdr);
154+
cells[r] = [];
155+
for (let c = 0; c < SIZE; c++) {
156+
const cell = document.createElement("div");
157+
cell.className = "cell t-empty";
158+
cell.dataset.r = r;
159+
cell.dataset.c = c;
160+
cell.textContent = "o";
161+
boardEl.appendChild(cell);
162+
cells[r][c] = cell;
163+
}
164+
}
165+
}
166+
167+
function paint(r, c) {
168+
grid[r][c] = current;
169+
const cell = cells[r][c];
170+
cell.className = "cell t-" + current;
171+
cell.textContent = CHAR[current];
172+
}
173+
174+
boardEl.addEventListener("mousedown", (e) => {
175+
const cell = e.target.closest(".cell");
176+
if (!cell) return;
177+
painting = true;
178+
paint(+cell.dataset.r, +cell.dataset.c);
179+
e.preventDefault();
180+
});
181+
boardEl.addEventListener("mouseover", (e) => {
182+
if (!painting) return;
183+
const cell = e.target.closest(".cell");
184+
if (!cell) return;
185+
paint(+cell.dataset.r, +cell.dataset.c);
186+
});
187+
window.addEventListener("mouseup", () => { painting = false; });
188+
189+
// palette selection
190+
document.querySelectorAll(".swatch").forEach((sw) => {
191+
sw.addEventListener("click", () => {
192+
document.querySelectorAll(".swatch").forEach((s) => s.classList.remove("active"));
193+
sw.classList.add("active");
194+
current = sw.dataset.type;
195+
});
196+
});
197+
// number-key shortcuts 1..4
198+
window.addEventListener("keydown", (e) => {
199+
const idx = ["1", "2", "3", "4"].indexOf(e.key);
200+
if (idx === -1) return;
201+
const sw = document.querySelector(`.swatch[data-type="${TYPES[idx]}"]`);
202+
if (sw) sw.click();
203+
});
204+
205+
function exportBoard() {
206+
const header = "\t" + Array.from({ length: SIZE }, (_, c) => c).join("\t");
207+
const lines = [header];
208+
for (let r = 0; r < SIZE; r++) {
209+
const row = grid[r].map((t) => CHAR[t]).join("\t");
210+
lines.push(r + "\t" + row);
211+
}
212+
return lines.join("\n");
213+
}
214+
215+
const output = document.getElementById("output");
216+
document.getElementById("export").addEventListener("click", () => {
217+
output.value = exportBoard();
218+
});
219+
document.getElementById("clear").addEventListener("click", () => {
220+
for (let r = 0; r < SIZE; r++) for (let c = 0; c < SIZE; c++) {
221+
grid[r][c] = "empty";
222+
cells[r][c].className = "cell t-empty";
223+
cells[r][c].textContent = "o";
224+
}
225+
output.value = "";
226+
});
227+
document.getElementById("copy").addEventListener("click", async () => {
228+
if (!output.value) output.value = exportBoard();
229+
try {
230+
await navigator.clipboard.writeText(output.value);
231+
const c = document.getElementById("copied");
232+
c.textContent = "Copied!";
233+
setTimeout(() => (c.textContent = ""), 1500);
234+
} catch {
235+
output.select();
236+
document.execCommand("copy");
237+
}
238+
});
239+
240+
buildBoard();
241+
output.value = exportBoard();
242+
</script>
243+
</body>
244+
</html>

eslint.config.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import js from "@eslint/js";
2+
import globals from "globals";
3+
import reactHooks from "eslint-plugin-react-hooks";
4+
import reactRefresh from "eslint-plugin-react-refresh";
5+
import tseslint from "typescript-eslint";
6+
7+
export default tseslint.config(
8+
{ ignores: ["dist", "backup", "coverage"] },
9+
{
10+
files: ["**/*.{ts,tsx}"],
11+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
12+
languageOptions: {
13+
ecmaVersion: 2020,
14+
globals: { ...globals.browser, ...globals.node },
15+
},
16+
plugins: {
17+
"react-hooks": reactHooks,
18+
"react-refresh": reactRefresh,
19+
},
20+
rules: {
21+
...reactHooks.configs.recommended.rules,
22+
"react-refresh/only-export-components": [
23+
"warn",
24+
{ allowConstantExport: true },
25+
],
26+
},
27+
},
28+
);

0 commit comments

Comments
 (0)