diff options
Diffstat (limited to 'html/kgame')
-rw-r--r-- | html/kgame/index.html | 59 | ||||
-rw-r--r-- | html/kgame/script.js | 437 | ||||
-rw-r--r-- | html/kgame/style.css | 90 |
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 |