about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2025-05-05 20:59:10 -0400
committerelioat <elioat@tilde.institute>2025-05-05 20:59:10 -0400
commit55ff39c80a6b36aa670d153f74c947b62c324e0c (patch)
tree8991b2e9870354ce9588eb943aa5e87c22ca2d50
parent180bc51ebcc629d7a02f8c14144e88e993d4e2e2 (diff)
downloadtour-55ff39c80a6b36aa670d153f74c947b62c324e0c.tar.gz
*
-rw-r--r--html/kgame/script.js219
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);