diff options
author | elioat <elioat@tilde.institute> | 2025-05-05 20:59:10 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2025-05-05 20:59:10 -0400 |
commit | 55ff39c80a6b36aa670d153f74c947b62c324e0c (patch) | |
tree | 8991b2e9870354ce9588eb943aa5e87c22ca2d50 | |
parent | 180bc51ebcc629d7a02f8c14144e88e993d4e2e2 (diff) | |
download | tour-55ff39c80a6b36aa670d153f74c947b62c324e0c.tar.gz |
*
-rw-r--r-- | html/kgame/script.js | 219 |
1 files changed, 96 insertions, 123 deletions
diff --git a/html/kgame/script.js b/html/kgame/script.js index 6effae4..fcb4df4 100644 --- a/html/kgame/script.js +++ b/html/kgame/script.js @@ -56,9 +56,19 @@ document.addEventListener('DOMContentLoaded', () => { drawCells(); } - // --- K-like Interpreter --- + // Helper functions for array operations + const range = (n) => Array.from({length: n}, (_, i) => i); + const reshape = (arr, rows, cols) => { + const result = []; + for (let i = 0; i < rows; i++) { + result.push(arr.slice(i * cols, (i + 1) * cols)); + } + return result; + }; + const ravel = (arr) => arr.flat(); + const zip = (a, b) => a.map((x, i) => [x, b[i]]); - // Simple tokenizer (basic version) + // --- K-like Interpreter --- function tokenize(code) { // First, normalize whitespace code = code.replace(/\s+/g, ' ').trim(); @@ -86,67 +96,51 @@ document.addEventListener('DOMContentLoaded', () => { return filteredTokens; } - // Evaluator for expressions (recursive descent style) - // Handles numbers, !, +, -, *, %, (), G, where, and K-style adverbs function evaluateExpression(tokens) { if (!tokens || tokens.length === 0) throw new Error("Empty expression"); function parseAtom() { let token = tokens.shift(); if (!token) throw new Error("Unexpected end of expression"); - console.log('Parsing atom:', token, 'Remaining tokens:', tokens); // Debug log + console.log('Parsing atom:', token, 'Remaining tokens:', tokens); - if (token === '!') { // Iota (prefix) + if (token === '(') { + const value = parseAddSub(); // Start parsing inside parenthesis + if (tokens.length === 0) { + throw new Error("Missing closing parenthesis"); + } + const nextToken = tokens.shift(); + if (nextToken !== ')') { + throw new Error(`Expected closing parenthesis, got: ${nextToken}`); + } + return value; + } else if (token === '!') { // Iota (prefix) const operand = parseAtom(); if (typeof operand !== 'number' || !Number.isInteger(operand) || operand < 0) { throw new Error("Operand for ! (iota) must be a non-negative integer"); } - // Create a 2D array of indices - const result = []; - for (let row = 0; row < GRID_SIZE; row++) { - for (let col = 0; col < GRID_SIZE; col++) { - result.push([row, col]); - } - } - return result; + // Only generate indices that fit within our grid + const maxIndex = GRID_SIZE * GRID_SIZE; + return Array.from({length: Math.min(operand, maxIndex)}, (_, i) => i); } else if (token === "'") { // Each adverb const operand = parseAtom(); if (!Array.isArray(operand)) { throw new Error("Each adverb (') requires an array operand"); } - // Apply the operation to each element - return operand.map(x => { - if (Array.isArray(x)) { - return x.map(y => y); - } - return x; - }); + return operand.map(x => Array.isArray(x) ? x.map(y => y) : x); } else if (token === '/') { // Over adverb const operand = parseAtom(); if (!Array.isArray(operand)) { throw new Error("Over adverb (/) requires an array operand"); } - // Reduce the array return operand.reduce((a, b) => { if (Array.isArray(a) && Array.isArray(b)) { return a.map((x, i) => x + b[i]); } return a + b; }); - } else if (token === '(') { - console.log('Found opening parenthesis, remaining tokens:', tokens); // Debug log - const value = parseAddSub(); // Start parsing inside parenthesis - console.log('After parsing inside parentheses, next token:', tokens[0]); // Debug log - if (tokens.length === 0) { - throw new Error("Missing closing parenthesis"); - } - const nextToken = tokens.shift(); - if (nextToken !== ')') { - throw new Error(`Expected closing parenthesis, got: ${nextToken}`); - } - return value; } else if (token === 'G') { - // Return the 2D matrix as a flat array of [row,col] pairs + // Return the grid as a flat array of [row,col] pairs const result = []; for (let row = 0; row < GRID_SIZE; row++) { for (let col = 0; col < GRID_SIZE; col++) { @@ -154,26 +148,74 @@ document.addEventListener('DOMContentLoaded', () => { } } return result; - } else if (token === 'where') { // Special 'where G=1' form + } else if (token === 'where') { if (tokens.shift() !== 'G') throw new Error("Expected 'G' after 'where'"); if (tokens.shift() !== '=') throw new Error("Expected '=' after 'where G'"); if (tokens.shift() !== '1') throw new Error("Expected '1' after 'where G='"); + // Find all coordinates where G[row][col] === 1 const indices = []; for(let row = 0; row < GRID_SIZE; row++) { for(let col = 0; col < GRID_SIZE; col++) { if (G[row][col] === 1) { - indices.push([row, col]); + indices.push(row * GRID_SIZE + col); // Convert to 1D index } } } return indices; - } else if (/^-?\d+$/.test(token)) { // Integer + } else if (/^-?\d+$/.test(token)) { return parseInt(token, 10); } else { throw new Error(`Unrecognized token: ${token}`); } } + function applyOperation(a, b, op) { + const isAList = Array.isArray(a); + const isBList = Array.isArray(b); + + const scalarOp = (x, y) => { + let result; + switch (op) { + case '+': result = x + y; break; + case '-': result = x - y; break; + case '*': result = x * y; break; + case '%': result = y === 0 ? 0 : x % y; break; + case '/': result = y === 0 ? 0 : Math.floor(x / y); break; // Use integer division + case '<': result = x < y ? 1 : 0; break; + case '>': result = x > y ? 1 : 0; break; + case '=': result = x === y ? 1 : 0; break; + default: throw new Error(`Unknown operator: ${op}`); + } + console.log(`Operation ${op}: ${x} ${op} ${y} = ${result}`); + return result; + }; + + // Handle scalar operations + if (!isAList && !isBList) { + return scalarOp(a, b); + } + + // Handle array operations + const arrayOp = (arr, val) => { + if (Array.isArray(arr)) { + return arr.map(x => arrayOp(x, val)); + } + return scalarOp(arr, val); + }; + + if (isAList && !isBList) { + return arrayOp(a, b); + } else if (!isAList && isBList) { + return arrayOp(b, a); + } else { + // Both are arrays + if (a.length !== b.length) { + throw new Error(`List length mismatch for operator ${op}: ${a.length} vs ${b.length}`); + } + return a.map((x, i) => arrayOp(x, b[i])); + } + } + function parseMulDivMod() { let left = parseAtom(); while (tokens.length > 0 && (tokens[0] === '*' || tokens[0] === '%' || tokens[0] === '/')) { @@ -204,62 +246,15 @@ document.addEventListener('DOMContentLoaded', () => { return left; } - const result = parseComparison(); - if (tokens.length > 0) { - throw new Error(`Unexpected tokens remaining: ${tokens.join(' ')}`); - } - return result; - } - - // Helper for arithmetic operations (handles scalar/list combinations) - function applyOperation(a, b, op) { - const isAList = Array.isArray(a); - const isBList = Array.isArray(b); - - if (!isAList && !isBList) { // Scalar op Scalar - switch (op) { - case '+': return a + b; - case '-': return a - b; - case '*': return a * b; - case '%': return b === 0 ? NaN : a % b; // Modulo - case '/': return b === 0 ? NaN : a / b; // Division - case '<': return a < b ? 1 : 0; // Convert boolean to 0/1 - case '>': return a > b ? 1 : 0; - case '=': return a === b ? 1 : 0; - default: throw new Error(`Unknown operator: ${op}`); - } - } else if (isAList && !isBList) { // List op Scalar - return a.map(val => { - if (Array.isArray(val)) { - return val.map(x => applyOperation(x, b, op)); - } - return applyOperation(val, b, op); - }); - } else if (!isAList && isBList) { // Scalar op List - return b.map(val => { - if (Array.isArray(val)) { - return val.map(x => applyOperation(a, x, op)); - } - return applyOperation(a, val, op); - }); - } else { // List op List - if (a.length !== b.length) throw new Error(`List length mismatch for operator ${op}: ${a.length} vs ${b.length}`); - return a.map((val, i) => { - if (Array.isArray(val) && Array.isArray(b[i])) { - return val.map((x, j) => applyOperation(x, b[i][j], op)); - } - return applyOperation(val, b[i], op); - }); - } + return parseComparison(); } // Main execution function function executeK(code) { code = code.trim(); - if (!code) return; // Ignore empty input + if (!code) return; try { - // Specific Cases: G : 0 or G : 1 if (code === 'G : 0') { G = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(0)); setOutput("Grid cleared.", "success"); @@ -271,7 +266,6 @@ document.addEventListener('DOMContentLoaded', () => { return; } - // General Case: G @ indices : values const assignMatch = code.match(/^G\s*@\s*(.+?)\s*:\s*(.+)$/); if (assignMatch) { const indexExpr = assignMatch[1].trim(); @@ -288,48 +282,27 @@ document.addEventListener('DOMContentLoaded', () => { return; } - let assignments = 0; - for (let i = 0; i < indicesArray.length; i++) { - const idx = indicesArray[i]; - // Handle both 1D and 2D indices - let row, col; - if (Array.isArray(idx)) { - [row, col] = idx; - } else { - row = Math.floor(idx / GRID_SIZE); - col = idx % GRID_SIZE; - } - - // Basic validation - if (typeof row !== 'number' || !Number.isInteger(row) || - typeof col !== 'number' || !Number.isInteger(col)) { - console.warn(`Skipping invalid index: ${idx}`); - continue; - } + // Vectorized assignment + const assignments = indicesArray.reduce((count, idx, i) => { + // Convert scalar index to 2D coordinates + const row = Math.floor(idx / GRID_SIZE); + const col = idx % GRID_SIZE; if (row >= 0 && row < GRID_SIZE && col >= 0 && col < GRID_SIZE) { - // Cycle through values if needed const valueToAssign = valuesArray[i % valuesArray.length]; - if (valueToAssign === 0 || valueToAssign === 1) { - G[row][col] = valueToAssign; - assignments++; - } else { - console.warn(`Skipping invalid value assignment (${valueToAssign}) at index [${row},${col}]. Must be 0 or 1.`); - } - } else { - console.warn(`Skipping out-of-bounds index: [${row},${col}]`); + console.log(`Assigning at [${row},${col}]: value=${valueToAssign}, modulo2=${valueToAssign % 2}`); + // Use modulo 2 directly for binary values + G[row][col] = valueToAssign % 2; + return count + 1; } - } - setOutput(`OK. Performed ${assignments} assignments.`, "success"); + return count; + }, 0); + setOutput(`OK. Performed ${assignments} assignments.`, "success"); } else { - // Try evaluating as a general expression (e.g., for debugging) - // This won't modify G unless the expression itself involves an assignment (not implemented here) - const result = evaluateExpression(tokenize(code)); - setOutput(`Evaluated: ${JSON.stringify(result)}`, "info"); - // throw new Error("Invalid syntax. Expected 'G : 0', 'G : 1', or 'G @ indices : values'"); + const result = evaluateExpression(tokenize(code)); + setOutput(`Evaluated: ${JSON.stringify(result)}`, "info"); } - } catch (error) { setOutput(`Error: ${error.message}`, "error"); console.error("K execution error:", error); |