// Level system — puzzle definitions, verification, and progression import { state } from './state.js'; import { evaluate } from './gates.js'; // Level definitions export const LEVELS = [ { id: 'buffer', category: 'Logic Basics', title: 'Buffer', description: 'Connect input to output directly. Pass the value through.', availableGates: ['INPUT', 'OUTPUT'], testCases: [ { inputs: { 0: 0 }, outputs: { 0: 0 } }, { inputs: { 0: 1 }, outputs: { 0: 1 } } ], hints: ['Use only one INPUT and one OUTPUT gate.'], difficulty: 1 }, { id: 'not_gate', category: 'Logic Basics', title: 'NOT Gate', description: 'Build a NOT gate using only NAND gates. Invert the input signal.', availableGates: ['INPUT', 'OUTPUT', 'NAND'], testCases: [ { inputs: { 0: 0 }, outputs: { 0: 1 } }, { inputs: { 0: 1 }, outputs: { 0: 0 } } ], hints: ['Connect both inputs of a NAND gate to the same signal.'], difficulty: 1 }, { id: 'and_gate', category: 'Logic Basics', title: 'AND Gate', description: 'Build an AND gate using only NAND gates. AND = NAND with inverted output.', availableGates: ['INPUT', 'OUTPUT', 'NAND'], testCases: [ { inputs: { 0: 0, 1: 0 }, outputs: { 0: 0 } }, { inputs: { 0: 0, 1: 1 }, outputs: { 0: 0 } }, { inputs: { 0: 1, 1: 0 }, outputs: { 0: 0 } }, { inputs: { 0: 1, 1: 1 }, outputs: { 0: 1 } } ], hints: ['NOT(NAND) = AND. You need 2 NAND gates.', 'Use a NAND gate as a NOT on the output.'], difficulty: 2 }, { id: 'or_gate', category: 'Logic Basics', title: 'OR Gate', description: 'Build an OR gate using NAND gates. OR = NAND(NOT A, NOT B).', availableGates: ['INPUT', 'OUTPUT', 'NAND'], testCases: [ { inputs: { 0: 0, 1: 0 }, outputs: { 0: 0 } }, { inputs: { 0: 0, 1: 1 }, outputs: { 0: 1 } }, { inputs: { 0: 1, 1: 0 }, outputs: { 0: 1 } }, { inputs: { 0: 1, 1: 1 }, outputs: { 0: 1 } } ], hints: ['Invert both inputs first, then NAND them.', 'You need 3 NAND gates total.'], difficulty: 2 }, { id: 'xor_gate', category: 'Logic Basics', title: 'XOR Gate', description: 'Build an XOR gate using NAND gates. True when inputs differ.', availableGates: ['INPUT', 'OUTPUT', 'NAND'], testCases: [ { inputs: { 0: 0, 1: 0 }, outputs: { 0: 0 } }, { inputs: { 0: 0, 1: 1 }, outputs: { 0: 1 } }, { inputs: { 0: 1, 1: 0 }, outputs: { 0: 1 } }, { inputs: { 0: 1, 1: 1 }, outputs: { 0: 0 } } ], hints: ['XOR = (A NAND B) NAND (NOT(A NAND NOT B) NAND NOT(NOT A NAND B))', 'This requires 4-5 NAND gates.'], difficulty: 3 }, { id: 'half_adder', category: 'Arithmetic', title: 'Half Adder', description: 'Build a half adder. Two outputs: Sum (A XOR B) and Carry (A AND B).', availableGates: ['INPUT', 'OUTPUT', 'NAND'], testCases: [ { inputs: { 0: 0, 1: 0 }, outputs: { 0: 0, 1: 0 } }, // 0+0: sum=0, carry=0 { inputs: { 0: 0, 1: 1 }, outputs: { 0: 1, 1: 0 } }, // 0+1: sum=1, carry=0 { inputs: { 0: 1, 1: 0 }, outputs: { 0: 1, 1: 0 } }, // 1+0: sum=1, carry=0 { inputs: { 0: 1, 1: 1 }, outputs: { 0: 0, 1: 1 } } // 1+1: sum=0, carry=1 ], hints: ['Two independent functions: Sum=XOR, Carry=AND.', 'You need 2 OUTPUT gates.'], difficulty: 3 }, { id: 'full_adder', category: 'Arithmetic', title: 'Full Adder', description: 'Build a full adder with carry-in. Outputs: Sum (3-input XOR) and Carry.', availableGates: ['INPUT', 'OUTPUT', 'NAND'], testCases: [ { inputs: { 0: 0, 1: 0, 2: 0 }, outputs: { 0: 0, 1: 0 } }, // 0+0+0 { inputs: { 0: 0, 1: 0, 2: 1 }, outputs: { 0: 1, 1: 0 } }, // 0+0+1 { inputs: { 0: 0, 1: 1, 2: 0 }, outputs: { 0: 1, 1: 0 } }, // 0+1+0 { inputs: { 0: 0, 1: 1, 2: 1 }, outputs: { 0: 0, 1: 1 } }, // 0+1+1 { inputs: { 0: 1, 1: 0, 2: 0 }, outputs: { 0: 1, 1: 0 } }, // 1+0+0 { inputs: { 0: 1, 1: 0, 2: 1 }, outputs: { 0: 0, 1: 1 } }, // 1+0+1 { inputs: { 0: 1, 1: 1, 2: 0 }, outputs: { 0: 0, 1: 1 } }, // 1+1+0 { inputs: { 0: 1, 1: 1, 2: 1 }, outputs: { 0: 1, 1: 1 } } // 1+1+1 ], hints: ['Reuse your half adder logic. A full adder = half adder + half adder + OR.'], difficulty: 4 }, { id: 'two_bit_adder', category: 'Components', title: '2-bit Adder', description: 'Chain two full adders to add two 2-bit numbers.', availableGates: ['INPUT', 'OUTPUT', 'FULL_ADDER'], // FULL_ADDER is saved component testCases: [ { inputs: { 0: 0, 1: 0, 2: 0, 3: 0 }, outputs: { 0: 0, 1: 0, 2: 0 } }, // 00+00=000 { inputs: { 0: 0, 1: 0, 2: 0, 3: 1 }, outputs: { 0: 0, 1: 0, 2: 1 } }, // 00+01=001 { inputs: { 0: 0, 1: 1, 2: 0, 3: 1 }, outputs: { 0: 0, 1: 1, 2: 1 } }, // 01+01=010 { inputs: { 0: 1, 1: 1, 2: 0, 3: 1 }, outputs: { 0: 1, 1: 1, 2: 1 } }, // 11+01=100 { inputs: { 0: 1, 1: 1, 2: 1, 3: 1 }, outputs: { 0: 0, 1: 1, 2: 1 } } // 11+11=110 ], hints: ['Use two FULL_ADDER components chained together.', 'First adder has no carry-in (set to 0).'], difficulty: 3 } ]; // Progress tracking export const progress = { unlockedLevels: ['buffer'], completedLevels: [], currentLevel: null, customComponents: {} // { name -> component definition } }; /** * Get all available levels for display */ export function getAllLevels() { return LEVELS; } /** * Get a level by ID */ export function getLevel(id) { return LEVELS.find(l => l.id === id); } /** * Check if a level is unlocked */ export function isLevelUnlocked(levelId) { return progress.unlockedLevels.includes(levelId); } /** * Check if a level is completed */ export function isLevelCompleted(levelId) { return progress.completedLevels.includes(levelId); } /** * Verify if the current circuit passes all test cases for a level */ export function verifyLevel(levelId) { const level = getLevel(levelId); if (!level) return { passed: false, results: [], message: 'Level not found' }; const results = []; let allPassed = true; for (const testCase of level.testCases) { const result = runTestCase(level, testCase); results.push(result); if (!result.passed) allPassed = false; } return { passed: allPassed, results, message: allPassed ? 'All tests passed! ✓' : `${results.filter(r => r.passed).length}/${results.length} tests passed` }; } /** * Run a single test case */ function runTestCase(level, testCase) { // Find INPUT gates const inputGates = state.gates.filter(g => g.type === 'INPUT'); // Find OUTPUT gates const outputGates = state.gates.filter(g => g.type === 'OUTPUT'); // Check if we have the right number of inputs and outputs const inputIds = Object.keys(testCase.inputs).map(Number).sort((a, b) => a - b); const outputIds = Object.keys(testCase.outputs).map(Number).sort((a, b) => a - b); if (inputGates.length !== inputIds.length || outputGates.length !== outputIds.length) { return { passed: false, inputs: testCase.inputs, expectedOutputs: testCase.outputs, actualOutputs: {}, error: `Expected ${inputIds.length} inputs and ${outputIds.length} outputs, got ${inputGates.length} and ${outputGates.length}` }; } // Set input values for (let i = 0; i < inputIds.length; i++) { if (inputGates[i]) { inputGates[i].value = testCase.inputs[i]; } } // Evaluate circuit state.gates.forEach(g => { if (g.type !== 'INPUT' && g.type !== 'CLOCK') g.value = 0; }); state.gates.forEach(g => evaluate(g)); // Check outputs const expected = testCase.outputs; let passed = true; const actualOutputs = {}; for (let i = 0; i < outputIds.length; i++) { const actualValue = outputGates[i]?.value || 0; actualOutputs[i] = actualValue; if (actualValue !== expected[i]) { passed = false; } } return { passed, inputs: testCase.inputs, expectedOutputs: expected, actualOutputs }; } /** * Complete a level and unlock the next one */ export function completeLevel(levelId) { const levelIndex = LEVELS.findIndex(l => l.id === levelId); if (levelIndex >= 0 && !progress.completedLevels.includes(levelId)) { progress.completedLevels.push(levelId); } // Unlock next level if (levelIndex < LEVELS.length - 1) { const nextId = LEVELS[levelIndex + 1].id; if (!progress.unlockedLevels.includes(nextId)) { progress.unlockedLevels.push(nextId); } } } /** * Register a custom component (saved circuit) */ export function registerComponent(name, gateTypes, connections) { progress.customComponents[name] = { name, gateTypes, connections }; } /** * Get a custom component */ export function getComponent(name) { return progress.customComponents[name]; } /** * Get all custom components */ export function getAllComponents() { return progress.customComponents; } /** * Reset progress (dev/testing) */ export function resetProgress() { progress.unlockedLevels = ['buffer']; progress.completedLevels = []; progress.currentLevel = null; progress.customComponents = {}; }