feat: add Turing Complete-style puzzle system
Add progressive puzzle mode alongside the existing sandbox: - 8 levels from basic gates to 2-bit adder - Truth table verification with pass/fail feedback - Gate restrictions per level - Custom components system (save circuits as reusable chips) - Save/load circuits as JSON - Level selection sidebar with difficulty ratings - Mode toggle: Sandbox (free play) vs Puzzle (guided levels) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
149
js/saveLoad.js
Normal file
149
js/saveLoad.js
Normal file
@@ -0,0 +1,149 @@
|
||||
// Save/Load system — export and import circuits and progress
|
||||
import { state } from './state.js';
|
||||
import { progress } from './levels.js';
|
||||
|
||||
/**
|
||||
* Save complete application state to JSON
|
||||
*/
|
||||
export function saveState() {
|
||||
return {
|
||||
timestamp: new Date().toISOString(),
|
||||
circuit: {
|
||||
gates: state.gates,
|
||||
connections: state.connections,
|
||||
nextId: state.nextId
|
||||
},
|
||||
camera: {
|
||||
camX: state.camX,
|
||||
camY: state.camY,
|
||||
zoom: state.zoom
|
||||
},
|
||||
components: state.customComponents || {},
|
||||
progress: {
|
||||
unlockedLevels: progress.unlockedLevels,
|
||||
completedLevels: progress.completedLevels,
|
||||
currentLevel: progress.currentLevel,
|
||||
customComponents: progress.customComponents
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load application state from JSON
|
||||
*/
|
||||
export function loadState(data) {
|
||||
if (!data || !data.circuit) {
|
||||
return { success: false, error: 'Invalid save data' };
|
||||
}
|
||||
|
||||
try {
|
||||
// Load circuit
|
||||
state.gates = JSON.parse(JSON.stringify(data.circuit.gates));
|
||||
state.connections = JSON.parse(JSON.stringify(data.circuit.connections));
|
||||
state.nextId = data.circuit.nextId;
|
||||
|
||||
// Load camera
|
||||
if (data.camera) {
|
||||
state.camX = data.camera.camX;
|
||||
state.camY = data.camera.camY;
|
||||
state.zoom = data.camera.zoom;
|
||||
}
|
||||
|
||||
// Load components
|
||||
if (data.components) {
|
||||
state.customComponents = JSON.parse(JSON.stringify(data.components));
|
||||
}
|
||||
|
||||
// Load progress
|
||||
if (data.progress) {
|
||||
progress.unlockedLevels = data.progress.unlockedLevels || ['buffer'];
|
||||
progress.completedLevels = data.progress.completedLevels || [];
|
||||
progress.currentLevel = data.progress.currentLevel || null;
|
||||
progress.customComponents = data.progress.customComponents || {};
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
return { success: false, error: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export as JSON file (for download)
|
||||
*/
|
||||
export function exportAsFile(filename = 'logic-circuit.json') {
|
||||
const data = saveState();
|
||||
const json = JSON.stringify(data, null, 2);
|
||||
const blob = new Blob([json], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import from file (returns Promise)
|
||||
*/
|
||||
export function importFromFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.target.result);
|
||||
const result = loadState(data);
|
||||
resolve(result);
|
||||
} catch (err) {
|
||||
reject(new Error('Failed to parse JSON'));
|
||||
}
|
||||
};
|
||||
reader.onerror = () => reject(new Error('Failed to read file'));
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Export circuit as a simple netlist (text format)
|
||||
*/
|
||||
export function exportAsNetlist() {
|
||||
let netlist = '# Logic Circuit Netlist\n\n';
|
||||
netlist += '## Gates\n';
|
||||
|
||||
state.gates.forEach(gate => {
|
||||
netlist += `${gate.type} ${gate.id} at (${gate.x.toFixed(0)}, ${gate.y.toFixed(0)})\n`;
|
||||
});
|
||||
|
||||
netlist += '\n## Connections\n';
|
||||
state.connections.forEach(conn => {
|
||||
netlist += `${conn.from}:${conn.fromPort} -> ${conn.to}:${conn.toPort}\n`;
|
||||
});
|
||||
|
||||
return netlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy state to clipboard as JSON (useful for sharing)
|
||||
*/
|
||||
export function copyToClipboard() {
|
||||
const data = saveState();
|
||||
const json = JSON.stringify(data);
|
||||
navigator.clipboard.writeText(json).then(() => {
|
||||
alert('State copied to clipboard');
|
||||
}).catch(() => {
|
||||
alert('Failed to copy to clipboard');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste state from clipboard
|
||||
*/
|
||||
export async function pasteFromClipboard() {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
const data = JSON.parse(text);
|
||||
return loadState(data);
|
||||
} catch (e) {
|
||||
return { success: false, error: e.message };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user