diff --git a/index.html b/index.html
index 92cf312..4ab2367 100644
--- a/index.html
+++ b/index.html
@@ -45,6 +45,24 @@
width: 65px;
}
+ .reset-btn {
+ width: calc(100% - 16px);
+ margin: 12px 8px;
+ padding: 6px;
+ background: #333;
+ color: #aaa;
+ border: 1px solid #555;
+ border-radius: 3px;
+ font-family: monospace;
+ font-size: 12px;
+ cursor: pointer;
+ }
+
+ .reset-btn:hover {
+ background: #444;
+ color: #ddd;
+ }
+
#gui-container {
flex: 1;
}
diff --git a/src/Config.ts b/src/Config.ts
index 141539a..bfbc4ee 100644
--- a/src/Config.ts
+++ b/src/Config.ts
@@ -1,4 +1,6 @@
-export default {
+const STORAGE_KEY = "ants-simulation-config";
+
+const defaults = {
worldSize: 1024,
antsCount: 12,
simulationStepsPerSecond: 60,
@@ -17,3 +19,41 @@ export default {
repellentMaxPerCell: 10,
repellentThreshold: 0.01,
};
+
+type ConfigType = typeof defaults;
+
+function loadSaved(): Partial {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ if (raw) return JSON.parse(raw);
+ } catch {
+ // corrupted data, ignore
+ }
+ return {};
+}
+
+const saved = loadSaved();
+const Config: ConfigType = { ...defaults, ...saved };
+
+export function saveConfig(): void {
+ const toSave: Partial = {};
+ for (const key of Object.keys(defaults) as (keyof ConfigType)[]) {
+ if (Config[key] !== defaults[key]) {
+ // biome-ignore lint/suspicious/noExplicitAny: generic key/value copy
+ (toSave as any)[key] = Config[key];
+ }
+ }
+ if (Object.keys(toSave).length > 0) {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave));
+ } else {
+ localStorage.removeItem(STORAGE_KEY);
+ }
+}
+
+export function resetConfig(): void {
+ Object.assign(Config, defaults);
+ localStorage.clear();
+}
+
+export { defaults };
+export default Config;
diff --git a/src/GUI.ts b/src/GUI.ts
index 89b0942..90e296b 100644
--- a/src/GUI.ts
+++ b/src/GUI.ts
@@ -1,5 +1,5 @@
import GUI from "lil-gui";
-import Config from "./Config";
+import Config, { resetConfig, saveConfig } from "./Config";
type EventHandler = () => void;
@@ -16,33 +16,50 @@ class GUIController {
.add(Config, "worldSize", 256, 4096)
.name("World size")
.step(1)
- .onChange(() => this.emit("reset"));
+ .onChange(() => this.saveAndEmit("reset"));
simFolder
.add(Config, "antsCount", 0, 22)
.name("Ants count 2^")
.step(1)
- .onChange(() => this.emit("reset"));
+ .onChange(() => this.saveAndEmit("reset"));
simFolder
.add(Config, "scentFadeOutFactor", 0, 0.01)
.name("Pheromone evaporation factor")
.step(0.0001)
- .onChange(() => this.emit("reset"));
+ .onChange(() => this.saveAndEmit("reset"));
simFolder
.add(Config, "scentBlurRadius", 0, 0.5)
.name("Pheromone diffusion factor")
.step(0.01)
- .onChange(() => this.emit("reset"));
+ .onChange(() => this.saveAndEmit("reset"));
simFolder
.add(Config, "simulationStepsPerSecond", 1, 500)
.name("Simulation steps per second")
- .step(1);
+ .step(1)
+ .onChange(() => saveConfig());
const controlsFolder = this.gui.addFolder("Controls");
- controlsFolder.add(Config, "brushRadius", 1, 100).name("Brush radius");
+ controlsFolder
+ .add(Config, "brushRadius", 1, 100)
+ .name("Brush radius")
+ .onChange(() => saveConfig());
simFolder.open();
controlsFolder.open();
+
+ const resetBtn = document.createElement("button");
+ resetBtn.textContent = "Reset to defaults";
+ resetBtn.className = "reset-btn";
+ resetBtn.addEventListener("click", () => {
+ resetConfig();
+ for (const c of this.gui.controllersRecursive()) {
+ c.updateDisplay();
+ }
+ this.emit("reset");
+ });
+ // biome-ignore lint/style/noNonNullAssertion: gui-container exists in index.html
+ document.getElementById("gui-container")!.appendChild(resetBtn);
}
on(event: string, handler: EventHandler): void {
@@ -53,6 +70,11 @@ class GUIController {
this.listeners.get(event)!.push(handler);
}
+ private saveAndEmit(event: string): void {
+ saveConfig();
+ this.emit(event);
+ }
+
private emit(event: string): void {
const handlers = this.listeners.get(event);
if (handlers) {