about summary refs log tree commit diff stats
path: root/html/kgame
diff options
context:
space:
mode:
Diffstat (limited to 'html/kgame')
-rw-r--r--html/kgame/index.html59
-rw-r--r--html/kgame/script.js437
-rw-r--r--html/kgame/style.css90
3 files changed, 586 insertions, 0 deletions
diff --git a/html/kgame/index.html b/html/kgame/index.html
new file mode 100644
index 0000000..233c1d6
--- /dev/null
+++ b/html/kgame/index.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>K-Grid Painter</title>
+    <link rel="stylesheet" href="style.css">
+</head>
+<body>
+    <h1>K-Grid Painter</h1>
+    <p>Control the 50x50 grid below using simplified K-like commands.</p>
+    <p>The grid state is represented by the variable <code>G</code> (a list of 2500 integers, 0 or 1).</p>
+    <p>Example commands:</p>
+    <ul>
+        <li><code>G : 0</code>             (Clear grid)</li>
+        <li><code>G : 1</code>             (Fill grid)</li>
+        
+        <li><strong>Basic Operations:</strong></li>
+        <li><code>G @ 0 : 1</code>         (Turn on cell at index 0)</li>
+        <li><code>G @ (!10) : 1</code>     (Turn on first 10 cells - demonstrates ! operator)</li>
+        <li><code>G @ (100 + !50) : 1</code> (Turn on cells 100-149 - demonstrates + operator)</li>
+        <li><code>G @ (50 * !50) : 1</code> (Turn on first column - demonstrates * operator)</li>
+        <li><code>G @ (!2500) : (!2500) % 2</code> (Alternating vertical columns - demonstrates % operator)</li>
+        
+        <li><strong>Comparison Operators:</strong></li>
+        <li><code>G @ (!2500) : ((!2500) % 50) < 25</code> (Left half filled - demonstrates < operator)</li>
+        <li><code>G @ (!2500) : ((!2500) % 50) > 25</code> (Right half filled - demonstrates > operator)</li>
+        <li><code>G @ (!2500) : ((!2500) % 50) = 25</code> (Middle column - demonstrates = operator)</li>
+        
+        <li><strong>Complex Patterns:</strong></li>
+        <li><code>G @ (50 * !50 + 25) : 1</code> (Vertical line in middle - demonstrates row/col math)</li>
+        <li><code>G @ (!50 + 25 * 50) : 1</code> (Horizontal line in middle - demonstrates row/col math)</li>
+        <li><code>G @ (50 * !50 + !50) : 1</code> (Diagonal line from top-left - demonstrates row/col math)</li>
+        <li><code>G @ (50 * !50 + (49 - !50)) : 1</code> (Diagonal line from top-right - demonstrates - operator)</li>
+        <li><code>G @ (!2500) : ((!2500) / 50 + (!2500) % 50) % 2</code> (Checkerboard pattern - demonstrates / and % operators)</li>
+        
+        <li><strong>Unary Operators:</strong></li>
+        <li><code>G @ (!25) : ~(!25)</code> (First cell 1, rest 0 - demonstrates ~ not operator)</li>
+        <li><code>G @ (!25) : |(!25)</code> (Reverse sequence - demonstrates | reverse operator)</li>
+        <li><code>G @ (!25) : $(!25)</code> (Rotate sequence - demonstrates $ rotate operator)</li>
+        <li><code>G @ (!25) : #(!25)</code> (Reshape sequence - demonstrates # reshape operator)</li>
+        
+        <li><strong>Operator Composition:</strong></li>
+        <li><code>G @ (!25) : ~((!25) / 5 + (!25) % 5) % 2</code> (Inverted checkerboard - demonstrates ~ with math)</li>
+        <li><code>G @ (!25) : |((!25) / 5 + (!25) % 5) % 2</code> (Flipped checkerboard - demonstrates | with math)</li>
+        <li><code>G @ (!25) : $((!25) / 5 + (!25) % 5) % 2</code> (Rotated checkerboard - demonstrates $ with math)</li>
+        <li><code>G @ (!25) : #((!25) % 2)</code> (Alternating columns reshaped - demonstrates # with math)</li>
+    </ul>
+
+    <canvas id="gridCanvas"></canvas>
+    <div class="input-area">
+        <input type="text" id="kInput" placeholder="Enter K-like code (e.g., G @ !10 : 1)" size="60">
+        <button id="runButton">Run</button>
+    </div>
+    <pre id="output"></pre>
+
+    <script src="script.js"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/html/kgame/script.js b/html/kgame/script.js
new file mode 100644
index 0000000..ed71eeb
--- /dev/null
+++ b/html/kgame/script.js
@@ -0,0 +1,437 @@
+document.addEventListener('DOMContentLoaded', () => {
+    const GRID_SIZE = 50;
+    const CELL_SIZE = 10; // Adjust for desired visual size
+    const CANVAS_WIDTH = GRID_SIZE * CELL_SIZE;
+    const CANVAS_HEIGHT = GRID_SIZE * CELL_SIZE;
+
+    const canvas = document.getElementById('gridCanvas');
+    const ctx = canvas.getContext('2d');
+    const input = document.getElementById('kInput');
+    const runButton = document.getElementById('runButton');
+    const output = document.getElementById('output');
+
+    canvas.width = CANVAS_WIDTH;
+    canvas.height = CANVAS_HEIGHT;
+
+    // --- Grid State ---
+    let G = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(0)); // 2D matrix grid state
+
+    // --- Drawing ---
+    function drawGridLines() {
+        ctx.strokeStyle = '#eee'; // Light gray grid lines
+        ctx.lineWidth = 1;
+
+        for (let i = 0; i <= GRID_SIZE; i++) {
+            // Vertical lines
+            ctx.beginPath();
+            ctx.moveTo(i * CELL_SIZE + 0.5, 0);
+            ctx.lineTo(i * CELL_SIZE + 0.5, CANVAS_HEIGHT);
+            ctx.stroke();
+
+            // Horizontal lines
+            ctx.beginPath();
+            ctx.moveTo(0, i * CELL_SIZE + 0.5);
+            ctx.lineTo(CANVAS_WIDTH, i * CELL_SIZE + 0.5);
+            ctx.stroke();
+        }
+    }
+
+    function drawCells() {
+        ctx.fillStyle = '#333'; // Color for 'on' cells
+        for (let row = 0; row < GRID_SIZE; row++) {
+            for (let col = 0; col < GRID_SIZE; col++) {
+                if (G[row][col] === 1) {
+                    ctx.fillRect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE);
+                }
+            }
+        }
+    }
+
+    function redraw() {
+        // Clear canvas
+        ctx.fillStyle = '#fff'; // Background color
+        ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
+
+        drawGridLines();
+        drawCells();
+    }
+
+    // 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]]);
+
+    // --- K-like Interpreter ---
+    function tokenize(code) {
+        // First, normalize whitespace
+        code = code.replace(/\s+/g, ' ').trim();
+        
+        // Define all operators and special characters
+        const operators = ['+', '-', '*', '/', '%', '(', ')', '!', '@', ':', '=', '<', '>', "'", '|', '$', '#', '~'];
+        
+        // Add spaces around all operators
+        operators.forEach(op => {
+            // Use a regex that ensures we don't double-space
+            const regex = new RegExp(`\\${op}`, 'g');
+            code = code.replace(regex, ` ${op} `);
+        });
+        
+        // Normalize spaces again
+        code = code.replace(/\s+/g, ' ').trim();
+        
+        // Split into tokens
+        const tokens = code.split(' ');
+        
+        // Filter out empty tokens and log for debugging
+        const filteredTokens = tokens.filter(t => t.length > 0);
+        console.log('Tokenized:', filteredTokens);
+        
+        return filteredTokens;
+    }
+
+    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");
+
+            if (token === '(') {
+                const value = parseAddSub();
+                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");
+                }
+                const maxIndex = GRID_SIZE * GRID_SIZE;
+                const result = Array.from({length: Math.min(operand, maxIndex)}, (_, i) => i);
+                console.log(`Iota generated array of length ${result.length}, first few values:`, result.slice(0, 5));
+                return result;
+            } else if (token === '~') { // Not operator
+                const operand = parseAtom();
+                if (Array.isArray(operand)) {
+                    const result = operand.map(x => {
+                        const val = x === 0 ? 1 : 0;
+                        console.log(`Not operation: ${x} -> ${val}`);
+                        return val;
+                    });
+                    console.log(`Not operation on array, first few results:`, result.slice(0, 5));
+                    console.log(`Input array first few values:`, operand.slice(0, 5));
+                    return result;
+                } else {
+                    const result = operand === 0 ? 1 : 0;
+                    console.log(`Not operation (scalar): ${operand} -> ${result}`);
+                    return result;
+                }
+            } else if (/^-?\d+$/.test(token)) {
+                return parseInt(token, 10);
+            } else {
+                throw new Error(`Unrecognized token: ${token}`);
+            }
+        }
+
+        function parseUnary() {
+            let token = tokens[0];
+            if (token === '|' || token === '$' || token === '#') {
+                tokens.shift();
+                const operand = parseUnary();
+                let result;
+                switch (token) {
+                    case '|':
+                        result = [...operand].reverse();
+                        break;
+                    case '$':
+                        if (!Array.isArray(operand)) {
+                            throw new Error("Rotate operator ($) requires an array operand");
+                        }
+                        const size = Math.sqrt(operand.length);
+                        if (size * size !== operand.length) {
+                            throw new Error("Rotate operator ($) requires a square array");
+                        }
+                        result = [];
+                        for (let col = 0; col < size; col++) {
+                            for (let row = size - 1; row >= 0; row--) {
+                                result.push(operand[row * size + col]);
+                            }
+                        }
+                        break;
+                    case '#':
+                        if (!Array.isArray(operand)) {
+                            throw new Error("Reshape operator (#) requires an array operand");
+                        }
+                        result = [];
+                        for (let i = 0; i < GRID_SIZE; i++) {
+                            result.push(operand.slice(i * GRID_SIZE, (i + 1) * GRID_SIZE));
+                        }
+                        result = result.flat();
+                        break;
+                }
+                return result;
+            }
+            return parseAtom();
+        }
+
+        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;
+                    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}`);
+                }
+                return result;
+            };
+
+            // Handle scalar operations
+            if (!isAList && !isBList) {
+                return scalarOp(a, b);
+            }
+
+            // Handle array operations
+            const arrayOp = (arr, val) => {
+                if (Array.isArray(arr)) {
+                    const result = arr.map(x => arrayOp(x, val));
+                    if (op === '/' || op === '%') {
+                        console.log(`Array operation ${op} with ${val}, first few results:`, result.slice(0, 5));
+                    }
+                    return result;
+                }
+                return scalarOp(arr, val);
+            };
+
+            if (isAList && !isBList) {
+                const result = arrayOp(a, b);
+                if (op === '+') {
+                    console.log(`Array + scalar operation, first few values:`, {
+                        array: a.slice(0, 5),
+                        scalar: b,
+                        result: result.slice(0, 5)
+                    });
+                }
+                return result;
+            } else if (!isAList && isBList) {
+                const result = arrayOp(b, a);
+                if (op === '+') {
+                    console.log(`Scalar + array operation, first few values:`, {
+                        scalar: a,
+                        array: b.slice(0, 5),
+                        result: result.slice(0, 5)
+                    });
+                }
+                return result;
+            } else {
+                // Both are arrays
+                if (a.length !== b.length) {
+                    throw new Error(`List length mismatch for operator ${op}: ${a.length} vs ${b.length}`);
+                }
+                const result = a.map((x, i) => {
+                    const val = arrayOp(x, b[i]);
+                    if (op === '+') {
+                        console.log(`Adding values at index ${i}: ${x} + ${b[i]} = ${val}`);
+                    }
+                    return val;
+                });
+                if (op === '+') {
+                    console.log(`Array addition, first few results:`, result.slice(0, 5));
+                }
+                return result;
+            }
+        }
+
+        function parseMulDivMod() {
+            let left = parseUnary();
+            while (tokens.length > 0 && (tokens[0] === '*' || tokens[0] === '%' || tokens[0] === '/')) {
+                const op = tokens.shift();
+                const right = parseUnary();
+                left = applyOperation(left, right, op);
+            }
+            return left;
+        }
+
+        function parseAddSub() {
+            let left = parseMulDivMod();
+            while (tokens.length > 0 && (tokens[0] === '+' || tokens[0] === '-')) {
+                const op = tokens.shift();
+                const right = parseMulDivMod();
+                left = applyOperation(left, right, op);
+            }
+            return left;
+        }
+
+        function parseComparison() {
+            let left = parseAddSub();
+            while (tokens.length > 0 && (tokens[0] === '<' || tokens[0] === '>' || tokens[0] === '=')) {
+                const op = tokens.shift();
+                const right = parseAddSub();
+                left = applyOperation(left, right, op);
+            }
+            return left;
+        }
+
+        function parseNot() {
+            let left = parseComparison();
+            while (tokens.length > 0 && tokens[0] === '~') {
+                tokens.shift();
+                left = Array.isArray(left) ? left.map(x => x === 0 ? 1 : 0) : (left === 0 ? 1 : 0);
+            }
+            return left;
+        }
+
+        return parseNot();
+    }
+
+    // Main execution function
+    function executeK(code) {
+        code = code.trim();
+        if (!code) return;
+
+        try {
+            if (code === 'G : 0') {
+                G = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(0));
+                setOutput("Grid cleared.", "success");
+                return;
+            }
+            if (code === 'G : 1') {
+                G = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(1));
+                setOutput("Grid filled.", "success");
+                return;
+            }
+
+            const assignMatch = code.match(/^G\s*@\s*(.+?)\s*:\s*(.+)$/);
+            if (assignMatch) {
+                const indexExpr = assignMatch[1].trim();
+                const valueExpr = assignMatch[2].trim();
+                const steps = [];
+
+                // Parse and evaluate indices
+                steps.push(`1. Evaluating indices expression: ${indexExpr}`);
+                const indices = evaluateExpression(tokenize(indexExpr));
+                steps.push(`   → Generated ${indices.length} indices`);
+
+                // Parse and evaluate values
+                steps.push(`2. Evaluating values expression: ${valueExpr}`);
+                const values = evaluateExpression(tokenize(valueExpr));
+                steps.push(`   → Generated ${Array.isArray(values) ? values.length : 1} values`);
+
+                const indicesArray = Array.isArray(indices) ? indices : [indices];
+                const valuesArray = Array.isArray(values) ? values : [values];
+
+                if (indicesArray.length === 0) {
+                    setOutput("Warning: Assignment applied to empty index list.", "info", steps);
+                    return;
+                }
+
+                // Vectorized assignment
+                const assignments = indicesArray.reduce((count, idx, i) => {
+                    const row = Math.floor(idx / GRID_SIZE);
+                    const col = idx % GRID_SIZE;
+                    
+                    if (row >= 0 && row < GRID_SIZE && col >= 0 && col < GRID_SIZE) {
+                        const valueToAssign = valuesArray[i % valuesArray.length];
+                        if (count < 5) { // Only log first few assignments
+                            console.log(`Assignment [${row},${col}]: ${valueToAssign} (from index ${idx})`);
+                        }
+                        G[row][col] = valueToAssign % 2;
+                        return count + 1;
+                    }
+                    return count;
+                }, 0);
+
+                steps.push(`3. Assignment complete:`);
+                steps.push(`   → Applied ${assignments} assignments to the grid`);
+                steps.push(`   → Each value was taken modulo 2 to ensure binary (0/1) values`);
+
+                setOutput(`OK. Performed ${assignments} assignments.`, "success", steps);
+            } else {
+                const result = evaluateExpression(tokenize(code));
+                setOutput(`Evaluated: ${JSON.stringify(result)}`, "info", [
+                    `1. Evaluated expression: ${code}`,
+                    `2. Result: ${JSON.stringify(result)}`
+                ]);
+            }
+        } catch (error) {
+            setOutput(`Error: ${error.message}`, "error", [
+                `1. Error occurred while executing: ${code}`,
+                `2. Error details: ${error.message}`
+            ]);
+            console.error("K execution error:", error);
+        }
+    }
+
+    // --- Output Helper ---
+    function setOutput(message, type = "info", steps = []) {
+        const outputDiv = document.getElementById('output');
+        
+        // Create a container for the message and steps
+        const container = document.createElement('div');
+        container.className = type;
+        
+        // Add the main message
+        const messageDiv = document.createElement('div');
+        messageDiv.textContent = message;
+        container.appendChild(messageDiv);
+        
+        // Add steps if provided
+        if (steps.length > 0) {
+            const stepsDiv = document.createElement('div');
+            stepsDiv.className = 'steps';
+            steps.forEach(step => {
+                const stepDiv = document.createElement('div');
+                stepDiv.className = 'step';
+                stepDiv.textContent = step;
+                stepsDiv.appendChild(stepDiv);
+            });
+            container.appendChild(stepsDiv);
+        }
+        
+        // Clear previous output and add new content
+        outputDiv.innerHTML = '';
+        outputDiv.appendChild(container);
+    }
+
+    // --- Event Listeners ---
+    function handleRun() {
+         const code = input.value;
+         executeK(code);
+         redraw();
+         // Optional: Clear input after running
+         // input.value = '';
+     }
+
+    input.addEventListener('keydown', (event) => {
+        if (event.key === 'Enter') {
+            handleRun();
+        }
+    });
+
+    runButton.addEventListener('click', handleRun);
+
+
+    // --- Initial Draw ---
+    setOutput("Grid initialized. Enter commands below.", "info");
+    redraw();
+});
\ No newline at end of file
diff --git a/html/kgame/style.css b/html/kgame/style.css
new file mode 100644
index 0000000..dfb0f33
--- /dev/null
+++ b/html/kgame/style.css
@@ -0,0 +1,90 @@
+body {
+    font-family: sans-serif;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    margin: 1em;
+    background-color: #f4f4f4;
+}
+
+h1 {
+    margin-bottom: 0.5em;
+}
+
+p, ul {
+     max-width: 600px;
+     text-align: left;
+     margin-bottom: 0.5em;
+ }
+
+li {
+    margin-bottom: 0.3em;
+}
+
+code {
+    font-family: monospace;
+    background-color: #e0e0e0;
+    padding: 0.1em 0.3em;
+    border-radius: 3px;
+}
+
+canvas {
+    border: 1px solid #333;
+    margin-top: 1em;
+    background-color: #fff;
+    /* Prevent blurry rendering */
+    image-rendering: -moz-crisp-edges;
+    image-rendering: -webkit-crisp-edges;
+    image-rendering: pixelated;
+    image-rendering: crisp-edges;
+}
+
+.input-area {
+    margin-top: 1em;
+    display: flex;
+    align-items: center;
+}
+
+#kInput {
+    padding: 8px;
+    font-size: 1em;
+    font-family: monospace;
+    margin-right: 5px;
+    border: 1px solid #ccc;
+    border-radius: 4px;
+}
+
+#runButton {
+    padding: 8px 15px;
+    font-size: 1em;
+    cursor: pointer;
+    background-color: #4CAF50;
+    color: white;
+    border: none;
+    border-radius: 4px;
+}
+
+#runButton:hover {
+    background-color: #45a049;
+}
+
+#output {
+    margin-top: 1em;
+    padding: 10px;
+    background-color: #e0e0e0;
+    border: 1px solid #ccc;
+    min-height: 3em;
+    width: 500px; /* Adjust as needed */
+    white-space: pre-wrap; /* Wrap long lines */
+    word-wrap: break-word; /* Break long words */
+    font-family: monospace;
+    color: #d00; /* Default to error color */
+}
+
+#output.success {
+    color: #080; /* Green for success */
+}
+
+#output.info {
+    color: #333; /* Default black/gray for info */
+}
\ No newline at end of file