diff options
| author | LLLL Colonq <llll@colonq> | 2026-08-26 23:47:59 -0400 |
|---|---|---|
| committer | LLLL Colonq <llll@colonq> | 2026-08-26 23:47:59 -0400 |
| commit | 0d2d8cd897e94bd94e0b3ae7c6ce9aa580a44a0a (patch) | |
| tree | e9039d1c3aedca515d6cb499451d4b73aca4f5fd /2026/games_submissions/liquidcake1 | |
| parent | 242943e5b023ce40eb3be826c418a478d4672f78 (diff) | |
Add a_tension_span
Diffstat (limited to '2026/games_submissions/liquidcake1')
| -rw-r--r-- | 2026/games_submissions/liquidcake1/LICENSE | 21 | ||||
| -rw-r--r-- | 2026/games_submissions/liquidcake1/funge.mjs | 42 | ||||
| -rw-r--r-- | 2026/games_submissions/liquidcake1/index.html | 415 | ||||
| -rw-r--r-- | 2026/games_submissions/liquidcake1/instructions.mjs | 265 | ||||
| -rw-r--r-- | 2026/games_submissions/liquidcake1/interpreter.mjs | 449 | ||||
| -rw-r--r-- | 2026/games_submissions/liquidcake1/jit.mjs | 259 | ||||
| -rw-r--r-- | 2026/games_submissions/liquidcake1/queue.mjs | 30 | ||||
| -rw-r--r-- | 2026/games_submissions/liquidcake1/wasm.mjs | 51 |
8 files changed, 1532 insertions, 0 deletions
diff --git a/2026/games_submissions/liquidcake1/LICENSE b/2026/games_submissions/liquidcake1/LICENSE new file mode 100644 index 0000000..847e49b --- /dev/null +++ b/2026/games_submissions/liquidcake1/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 liquidcake1 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/2026/games_submissions/liquidcake1/funge.mjs b/2026/games_submissions/liquidcake1/funge.mjs new file mode 100644 index 0000000..a6fb723 --- /dev/null +++ b/2026/games_submissions/liquidcake1/funge.mjs @@ -0,0 +1,42 @@ +let file_name = process.argv[2]; + +import { readFileSync } from "fs"; + +let field_s = readFileSync(file_name, "utf8"); +//console.log(field_s); + +let field = field_s.split("\n"); +field.pop(); +field = field.map( x => x.split("").map( c => c.charCodeAt(0) ) ); +//console.log(field); + +import { Interpreter } from "./interpreter.mjs"; +import { load_wasm } from "./wasm.mjs"; +import { overlay as SOCK_overlay } from "./overlays/SOCK.mjs"; +import { overlay as S_overlay } from "./overlays/S.mjs"; +import { overlay as WS_overlay } from "./overlays/WS.mjs"; + +let interpreter = new Interpreter(); + +interpreter.load_overlay("S", S_overlay); +interpreter.load_overlay("SOCK", SOCK_overlay); +interpreter.load_overlay("WS", WS_overlay); + +let instance = await load_wasm(interpreter); + +interpreter.field = field; +interpreter.input_queue.push(["set_speed", 100]); +//interpreter.input_queue.push(["set_speed", 90]); +interpreter.input_queue.push(["pause"]); +interpreter.input_queue.push(["unpause"]); +interpreter.add_handler("char_out", function(arg) { process.stdout.write(arg); }); +//interpreter.add_handler("output_field", function(lines) { lines.forEach(function(line) {process.stdout.write(line + "\n"); })}); +interpreter.add_handler("terminate", function() { process.exit(0); }); +interpreter.go(); +process.stdin.on('data', function (chunk) { + chunk.forEach(x => interpreter.input_queue.push(["stdin", x])); +}); +/*setTimeout(function () { + console.log("sending @"); + interpreter.input_queue.push(["stdin", "@".charCodeAt(0)]); +}, 2000);*/ diff --git a/2026/games_submissions/liquidcake1/index.html b/2026/games_submissions/liquidcake1/index.html new file mode 100644 index 0000000..8bff36c --- /dev/null +++ b/2026/games_submissions/liquidcake1/index.html @@ -0,0 +1,415 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <script language="javascript" type="module"> + import { Interpreter } from "./interpreter.mjs"; + let interpreter = new Interpreter(); + interpreter.add_handler("char_out", out); + interpreter.add_handler("error_occurred", oute); + interpreter.add_handler("cell_changed", handle_set_cell); + interpreter.add_handler("output_field", console.log); + interpreter.add_handler("thread_created", setup_thread_gui); + interpreter.add_handler("thread_paused", handle_thread_paused); + interpreter.add_handler("thread_state_updated", update_thread_state); + interpreter.add_handler("thread_state_synced", handle_thread_state_synced); + interpreter.add_handler("terminate", handle_thread_state_synced); + interpreter.add_handler("thread_dead", handle_thread_dead); + interpreter.max_loops = 1000; + window.befunge_interpreter = interpreter; + var start_time; + var end_time; + var difficulty; + async function handle_message(ev) { + console.log("handle_message", ev); + console.log("handle_message", ev.data); + console.log("handle_message", ev.data.op); + if (ev.data.op == "start") { + winner = loser = false; + console.log("Picking level"); + await pick_level(); + set_speed(); + console.log("Picked level; explaining started."); + window.parent.postMessage({op: "started", verb: "catch!"}); + difficulty = ev.data.difficulty; + let timeout; + if (difficulty < 10) { + timeout = 150000 / (10 + difficulty); + } else { + timeout = 5000 + 150000 / difficulty; + } + console.log("Starting... with difficulty ", difficulty, "which is", timeout); + start_time = new Date().getTime(); + end_time = start_time + timeout; + check_time(); + go(); + let code = document.getElementById("code"); + code.style.animation = "none"; + window.setTimeout(function () { + code.style.animation = ""; + }, 10); + console.log("Started."); + } + } + function check_time() { + if (winner) { + console.log("Check time: winner"); + return; + } + let now = new Date().getTime(); + let fraction_done = (now - start_time) / (end_time - start_time); + let bomb = document.getElementById("bomb"); + if (fraction_done > 1) { + console.log("Check time: loser"); + loser = true; + bomb.innerText = "💥"; + interpreter.pause(); + return; + } + let ticks = 10 - Math.ceil(fraction_done * 10); + bomb.innerText = "💣" + ".".repeat(ticks) + "💥"; + window.setTimeout(check_time, 50); + + } + async function load() { + document.getElementById("speed").addEventListener("input", set_speed); + document.addEventListener("mousemove", function (e) { + let follower = document.getElementById("follower"); + follower.style.left = e.pageX + "px"; + follower.style.top = e.pageY + "px"; + }); + set_speed(); + window.addEventListener("message", handle_message); + window.parent.postMessage({op: "ready"}); + } + let levels = [ + '"!dlrow ,olleH",,,,,,,,,,,,,@', + '"H","e","l","l","o",","," "v\n @,"!","d","l","r","o","w",<', + '0>:2g:,"!"v\n ^_@#\\+1\\-<\nHello, world!', + ]; + let current_level; + let removed; + let placed_row = -1; + let placed_col = -1; + function test_level(content) { + let test_int = new Interpreter(); + let out = ""; + let fulfil; + let test_stopping = false; + test_int.add_handler("char_out", function (c) { out += c; }); + test_int.add_handler("thread_paused", function () { if (test_stopping) return; test_stopping = true; test_int.stop(); }); + test_int.add_handler("thread_dead", function (thread) { console.log("dead!"); fulfil([out, thread.tick_count]); }); + test_int.max_loops = 10000; + test_int.set_speed(100) + let lines = content.split("\n"); + let width = 0; + for(let i=0; i<lines.length; i++) { + if (lines[i].length > width) { + width = lines[i].length; + } + } + for(let i=0; i<lines.length; i++) { + test_int.field.push([]); + for(let j=0; j<width; j++) { + if (j < lines[i].length) { + test_int.field[i].push(lines[i].charCodeAt(j)) + } else { + test_int.field[i].push(32); + } + } + } + let p = new Promise(function(f) { fulfil = f; test_int.go(); }); + return p; + } + function ticks_to_speed(ticks) { + let speed = Math.log(ticks / 100) / Math.log(1.1); + if (ticks < 100) { + speed -= 20; + } + return speed; + } + async function pick_level() { + let level_idx = Math.floor(Math.random() * levels.length); + const base_level = levels[level_idx]; + let base_res = await test_level(base_level); + if (base_res[0] == "Hello, world!") { + console.log("Pre-edit validated!"); + } else { + console.log("Ooops!"); + } + let tries = 0; + while(tries < 100) { + tries += 1; + let idx = Math.floor(Math.random() * base_level.length); + removed = base_level[idx]; + if (removed == "\n") { + continue; + } + let replace; + if (difficulty < 10) { + replace = " "; + } else { + replace = String.fromCharCode(Math.floor(Math.random() * 94) + 33); + } + current_level = base_level.slice(0, idx) + replace + base_level.slice(idx + 1); + console.log(idx); + let current_res = await test_level(current_level); + if (current_res[0] == "Hello, world!") { + console.log("Ooops! Try again!"); + } else { + console.log("Post-edit validated!"); + document.getElementById("to_place").innerText = removed; + document.getElementById("follower_cell").innerText = removed; + let ticks = current_res[1]; + if (ticks > base_res[1] * 2) { ticks = base_res[1] * 2; } + document.getElementById("speed").value = ticks_to_speed(ticks); + if (interpreter.instructions_raw.hasOwnProperty(removed)) { + document.getElementById("place_description").innerText = interpreter.instructions_raw[removed].desc; + } else { + document.getElementById("place_description").innerText = "not an instruction"; + + } + console.log("level picked!"); + return; + } + } + } + function set_speed(e) { + let raw_speed = document.getElementById("speed").value; + interpreter.set_speed(raw_speed); + } + function setup_thread_gui(thread) { + const this_thread = thread; + let thb = document.createElement("th"); + let table = document.createElement("table"); + let tr = document.createElement("tr"); + let th = document.createElement("th"); + tr.appendChild(thb); + tr.appendChild(th); + table.appendChild(tr); + let thread_info = document.createElement("p"); + thread_info.appendChild(table); + document.getElementById("threads").appendChild(thread_info); + thread.info_elt = thread_info; + } + function update_thread_state(thread) { + let tr = thread.info_elt.children[0].children[0]; + tr.children[1].innerText = `(${("" + thread.col).padStart(3, "\u00a0")},${("" + thread.row).padStart(3, "\u00a0")})`; + while(tr.children.length > thread.stack.length + 2) { + tr.removeChild(tr.children[tr.children.length - 1]); + } + while(tr.children.length < thread.stack.length + 2) { + tr.appendChild(document.createElement("td")); + } + thread.stack.forEach(function (val, idx) { + if (val > 32 && val < 127) { + val = String.fromCharCode(val); + } + tr.children[thread.stack.length - idx + 1].innerText = val; + }); + } + let winner = false; + let loser = false; + let just_placed = false; + async function go() { + if (winner || loser) { + if (winner) { + console.log("We are winner!"); + } else { + console.log("We are loser!"); + } + window.parent.postMessage({op: "done", win: winner}); + document.getElementById("out").classList.remove("right"); + placed_col = placed_row = -1; + placed_td = undefined; + let table = document.getElementById("table"); + while(table.children.length > 0) { + table.removeChild(table.children[table.children.length-1]); + interpreter.field.pop(); + } + document.getElementById("out").innerText = ""; + return; + } + if (document.getElementById("out").innerText == "Hello, world!") { + // WIN! + winner = true; + document.getElementById("out").classList.add("right"); + placed_td.classList.add("right"); + placed_td.classList.remove("wrong"); + //console.log(dead_thread_ticks, ticks_to_speed(dead_thread_ticks / 10)); + interpreter.set_speed(ticks_to_speed(dead_thread_ticks / 5)); + } else if (just_placed) { + // Run once, super fast. We'll get back here immediately. + interpreter.set_speed(100); + just_placed = false; + load_level(current_level); + document.getElementById("out").innerText = ""; + interpreter.go(); + return; + } else { + set_speed(); + } + document.getElementById("outlast").innerHTML = document.getElementById("out").innerHTML; + document.getElementById("out").innerText = ""; + load_level(current_level); + interpreter.go(); + } + function set_cell(col, row, val) { + interpreter.set_cell(col, row, val); + } + function handle_set_cell(col, row, val) { + let val_str = String.fromCharCode(val); + document.getElementById("table").children[row].children[col].innerText = val_str == " " ? "\u00a0" : val_str; + let title = `(${col},${row})=${val} (${val_str})`; + if (interpreter.instructions_raw[val_str] !== undefined) + title += ": " + interpreter.instructions_raw[val_str].desc; + document.getElementById("table").children[row].children[col].title = title; + } + function out(s) { + document.getElementById("out").appendChild(document.createTextNode(s)); + } + function oute(s) { + if (s == "ERROR: Normal termination!" || s.startsWith("State was: ")) { + return; + } + let span = document.createElement("p"); + span.className = "error"; + span.innerText = s; + document.getElementById("out").appendChild(span); + } + let highlighted_cells = []; + function handle_thread_state_synced(threads) { + threads ||= []; + let new_highlighted_cells = threads.map(thread => document.getElementById("table").children[thread.row].children[thread.col]); + highlighted_cells.filter(x => !new_highlighted_cells.includes(x)).forEach( + cell => cell.classList.remove("active")); + highlighted_cells = new_highlighted_cells; + highlighted_cells.forEach(cell => cell.classList.add("active")); + } + let stopping; + function handle_thread_paused(paused_thread) { + if (stopping) { + // We'll unpause and then pause again... + return; + } + stopping = true; + interpreter.stop(); + } + let dead_thread_ticks; + function handle_thread_dead(dead_thread) { + dead_thread_ticks = dead_thread.tick_count; + stopping = false; + document.getElementById("threads").removeChild(dead_thread.info_elt); + go(); + } + let placed_td; + function click_cell(col, row, td) { + if (winner) { + return; + } + placed_col = col; + placed_row = row; + if (placed_td) { + placed_td.classList.remove("wrong"); + } + placed_td = td; + td.classList.add("wrong"); + set_cell(placed_col, placed_row, removed.charCodeAt(0)); + just_placed = true; + interpreter.pause(); + } + function load_level(content) { + const lines = content.split("\n"); + const height = lines.length; + let width = 0; + for(let i=0; i<height; i++) { + if (lines[i].length > width) { + width = lines[i].length; + } + } + let table = document.getElementById("table"); + while(table.children.length > height) { + table.removeChild(table.children[table.children.length-1]); + interpreter.field.pop(); + } + while(table.children.length < height) { + table.appendChild(document.createElement("tr")); + interpreter.field.push([]); + } + for(let i=0; i<height; i++) { + let line; + if (i >= lines.length) + line = ""; + else + line = lines[i]; + let row = table.children[i]; + while(row.children.length > width) { + row.removeChild(row.children[row.children.length-1]); + interpreter.field[i].pop(); + } + while(row.children.length < width) { + const td = document.createElement("td"); + const rown = i; + const coln = row.children.length; + td.addEventListener("click", function() { click_cell(coln, rown, td); }); + row.appendChild(td); + interpreter.field[i].push(32); + } + for(let j=0; j<width; j++) { + let val; + if (j >= line.length) + val = " "; + else + val = line[j]; + if (j == placed_col && i == placed_row) { + val = removed; + } + set_cell(j, i, val.charCodeAt(0)); + } + } + } + window.addEventListener("load", load); + </script> +<style> +body { font-size: 4vh; } +table { border: black solid 1px; padding: 0px; } +th { font-family: monospace; font-weight: normal; } +td { font-family: monospace; background: lightgrey; width: 1em; padding: 0px; text-align: center; } +.out1 > p { display: inline-block; margin-top: 0; margin-bottom: 0; min-height: 1em; } +.out1 > div { font-family: monospace; border: black solid 1px; display: inline-block; white-space-collapse: preserve; } +.out1 > p:empty::before { + content: ""; + display: inline-block; +} +.error { background-color: red; margin: 0px; } +.active { background-color: lightpink; } +.wrong { background-color: orange; } +.right { background-color: green; } +.follower { position: absolute; transform: translate(-50%, -50%); pointer-events: none; } +body { cursor: none; } +#threads { border: black solid 1px; padding: 0px; min-height: 2em;} +#threads > p { margin: 0px; } +@keyframes fadeOut { + from { opacity: 1; } + to { opacity: 0; } +} + +#code { position: absolute; font-weight: bold; font-size: 50vh; pointer-events: none; animation: fadeOut 2s ease-out forwards; background: white; pointer-events: none; } +</style> + <title>Befunge</title> + </head> + <body> + <div id="code">Code!</div> + <div style="display: inline-block;"><big>Placing:</big></div><table style="display: inline-block;"><tr><td id="to_place"></td><th>Which does: <div id="place_description" style="display: inline-block;"></div></th></tr></table> + <div style="display: inline-block;">Speed <input type="range" min="-100" max="150" value="40" id="speed" style="height: 1em;"/></div> + <br/> + <div id="threads"></div> + <table id="table"> + </table> + <div class="out1"><p>Output:</p><div id="out"></div></div> + <div class="out1"><p>Previous:</p><div id="outlast"></div></div> + <div class="out1"><p>Target:</p><div>Hello, world!</div></div> + <div id="follower" class="follower"><table><tr><td id="follower_cell">x</td></tr></table></div> + <div>Don't get exploded: <code id="bomb">💣......💥</code></div> + </body> +</html> diff --git a/2026/games_submissions/liquidcake1/instructions.mjs b/2026/games_submissions/liquidcake1/instructions.mjs new file mode 100644 index 0000000..58c6aab --- /dev/null +++ b/2026/games_submissions/liquidcake1/instructions.mjs @@ -0,0 +1,265 @@ +export let instructions_raw = { + "+": { + impl: function (thread) { thread.stack.push(thread.pop() + thread.pop()); }, + desc: "b a → b + a", + can_jit: true, + stack_min: 2, + stack_return: 1, + unchecked_js_code: "stack[stack.length - 2] += stack.pop();", + }, + "-": { + impl: function (thread) { thread.stack.push(-thread.pop() + thread.pop()); }, + desc: "b a → b - a", + can_jit: true, + stack_min: 2, + stack_return: 1, + unchecked_js_code: "stack[stack.length - 2] -= stack.pop();", + }, + "*": { + impl: function (thread) { thread.stack.push(thread.pop() * thread.pop()); }, + desc: "b a → b * a", + can_jit: true, + stack_min: 2, + stack_return: 1, + unchecked_js_code: "stack[stack.length - 2] *= stack.pop();", + }, + "/": { + impl: function (thread) { let a = thread.pop(); let b = thread.pop(); thread.stack.push(Math.round(b / a)); }, + desc: "b a → b // a", + can_jit: true, + stack_min: 2, + stack_return: 1, + unchecked_js_code: "stack[stack.length - 2] /= stack.pop();", + }, + "%": { + impl: function (thread) { let a = thread.pop(); let b = thread.pop(); thread.stack.push(b % a); }, + desc: "b a → b % a", + can_jit: true, + stack_min: 2, + stack_return: 1, + unchecked_js_code: "stack[stack.length - 2] %= stack.pop();", + }, + "!": { + impl: function (thread) { thread.stack.push(thread.pop() == 0 ? 1 : 0); }, + desc: "a → a == 0 ? 1 : 0", + can_jit: true, + stack_min: 1, + stack_return: 1, + unchecked_js_code: "stack[stack.length - 1] = stack[stack.length - 1] ? 1 : 0;", + }, + "`": { + impl: function (thread) { thread.stack.push(thread.pop() < thread.pop() ? 1 : 0); }, + desc: "b a → b > a ? 1 : 0", + can_jit: true, + stack_min: 2, + stack_return: 1, + unchecked_js_code: "stack[stack.length - 2] = stack.pop() < stack[stack.length - 1] ? 0 : 1;", + }, + "<": { + impl: function (thread) { thread.cold = -1; thread.rowd = 0; }, + desc: "() → (); go left", + stack_min: 0, + stack_return: 0, + unchecked_js_code: "", + can_jit: true, + }, + ">": { + impl: function (thread) { thread.cold = 1; thread.rowd = 0; }, + desc: "() → (); go right", + stack_min: 0, + stack_return: 0, + unchecked_js_code: "", + can_jit: true, + }, + "^": { + impl: function (thread) { thread.cold = 0; thread.rowd = -1; }, + desc: "() → (); go up", + stack_min: 0, + stack_return: 0, + unchecked_js_code: "", + can_jit: true, + }, + "v": { + impl: function (thread) { thread.cold = 0; thread.rowd = 1; }, + desc: "() → (); go down", + stack_min: 0, + stack_return: 0, + unchecked_js_code: "", + can_jit: true, + }, + "?": { + impl: function (thread) { let x = Math.floor(Math.random() * 4); thread.cold = ((x % 2) * 2 - 1) * Math.floor(x / 2); thread.rowd = ((x % 2) * 2 - 1) * ( 1 - Math.floor(x / 2)); }, + desc: "() → (); go random cardinal", + can_jit: false, + }, + "_": { + impl: function (thread) { thread.rowd = 0; thread.cold = thread.pop() == 0 ? 1 : -1; }, + desc: "a → (); a == 0 ? go right : go left", + can_jit: false, + }, + "|": { + impl: function (thread) { thread.cold = 0; thread.rowd = thread.pop() == 0 ? 1 : -1; }, + desc: "a → (); a == 0 ? go down : go up", + can_jit: false, + }, + '"': { + impl: function (thread) { thread.mode = "string"; }, + desc: "; toggle string mode", + can_jit: false, // TODO this should be OK, but we'll need JIT for string mode. + stack_min: 0, + stack_return: 0, + }, + ":": { + impl: function (thread) { thread.stack.push(thread.stack[thread.stack.length-1]); }, + desc: "a → a a", + can_jit: true, + stack_min: 1, + stack_return: 2, + unchecked_js_code: "stack.push(stack[stack.length-1]);", + }, + "\\": { + impl: function (thread) { let a = thread.pop(); var b = thread.pop(); thread.stack.push(a, b); }, + desc: "b a → a b", + can_jit: true, + stack_min: 2, + stack_return: 2, + unchecked_js_code: "stack.push(stack.pop(), stack.pop());", + }, + "$": { + impl: function (thread) { thread.pop(); }, + desc: "a → ()", + can_jit: true, + stack_min: 1, + stack_return: 0, + unchecked_js_code: "stack.pop();", + }, + ".": { + impl: function (thread) { thread.interpreter.out(thread.pop() + " "); }, + desc: "a → (); output a", + can_jit: false, + }, + ",": { + impl: function (thread) { thread.interpreter.out(String.fromCharCode(thread.pop())); }, + desc: "a → (); output chr(a)", + can_jit: false, + }, + "#": { + impl: function (thread) { thread.col += thread.cold; thread.row += thread.rowd; }, + desc: "; jump one cell", + can_jit: true, + stack_min: 0, + stack_return: 0, + unchecked_js_code: "", + }, + "g": { + impl: function (thread) { let row = thread.pop(); var col = thread.pop(); thread.stack.push(thread.interpreter.get_field(thread, col, row)); }, + desc: "col row → field[row][col]", + can_jit: true, + stack_min: 2, + stack_return: 1, + unchecked_js_code: "{let row = stack.pop(); stack[stack.length-1] = (thread.interpreter.get_field(thread, stack[stack.length-1], row));}", + }, + "p": { + impl: function (thread) { let row = thread.pop(); var col = thread.pop(); var val = thread.pop(); thread.interpreter.set_field(thread, col, row, val); }, + desc: "val col row → (); field[row][col] = val", + can_jit: true, + stack_min: 3, + stack_return: 0, + unchecked_js_code: function (thread_state) { + return ` + { + let row = stack.pop(); + let col = stack.pop(); + thread.interpreter.set_field(thread, col, row, stack.pop()); + for (let i=0; i<jit.path.length; i++) { + if (jit.path[i][0] == row && jit.path[i][1] == col) { + thread.row = ${thread_state.row} + ${thread_state.rowd}; + thread.col = ${thread_state.col} + ${thread_state.cold}; + thread.rowd = ${thread_state.rowd}; + thread.cold = ${thread_state.cold}; + return; + } + } + }`}, + }, + "t": { + impl: function (thread) { thread.interpreter.new_thread(thread.split_thread()); }, + desc: "; create new thread in reverse direction", + can_jit: false, + }, + // "&" input character TODO + "~": { // TODO handle EOF properly (push nothing) + impl: function (thread) { + if (thread.interpreter.stdin_queue.length > 0) { + console.log("Have data!"); + thread.stack.push(thread.interpreter.stdin_queue.pop()); + } else { + console.log("Have no data!"); + return new Promise(r => thread.interpreter.stdin_waiters.push(r)).then(function (char_code) {thread.stack.push(char_code); thread.blocked = false;}); + } + }, + desc: "() → x; read a character from input", + can_jit: false, + }, + "@": { + impl: function (thread) { thread.interpreter.output_error(thread, "Normal termination!"); thread.running = false; }, + desc: "; stop current thread", + can_jit: false, + }, + "]": { + impl: function (thread) { thread.interpreter.output_error(thread, "Debug!"); }, + desc: "; dump thread debug", + can_jit: false, + }, + " ": { + impl: function (thread) { }, + desc: "; no-op", + can_jit: true, + stack_min: 0, + stack_return: 0, + unchecked_js_code: "", + }, + "(": { + impl: function (thread) { + const n = thread.pop(); + let x = 0; + for(let i=0; i<n; i++) { + x = x * 256 + thread.pop(); + } + if (thread.interpreter.overlays[x]) { + // We don't support ")" anyway so for now don't + // need to know how to undo... + for(let [k, v] of Object.entries(thread.interpreter.overlays[x])) { + thread.overlays[k.charCodeAt(0)] = v.impl; + } + thread.stack.push(x); + thread.stack.push(1); + } else { + // Failure, reverse direction. + thread.cold *= -1; + thread.rowd *= -1; + } + }, + desc: "xn ... x1 n → f=(x1 + x2*256 + ... + xn*256^(n-1)) 1 | (); load semantic f or reverse", + can_jit: false, // Varargs approach makes JIT hard! + }, +}; + +// Add numbers 0-9 +for(let i=0; i<10; i++) { + const j = i; // Prevent any capture shenanigans. + instructions_raw[i] = { + impl: function (state) { state.stack.push(j); }, + desc: `() → ${i}`, + can_jit: true, + stack_min: 0, + stack_return: 1, + unchecked_js_code: `stack.push(${i});`, + }; +} + +// Fast access instructions array. +export let instructions = {}; +for(let [s, val] of Object.entries(instructions_raw)) { + instructions[s.charCodeAt(0)] = val.impl; +} diff --git a/2026/games_submissions/liquidcake1/interpreter.mjs b/2026/games_submissions/liquidcake1/interpreter.mjs new file mode 100644 index 0000000..703b2d7 --- /dev/null +++ b/2026/games_submissions/liquidcake1/interpreter.mjs @@ -0,0 +1,449 @@ +// JIT TODO: +// * OK, it's probably about 6x faster with JIT than without, but we can go another 4-5x faster by loop unrolling. +// * We don't bounds-check the stack before executing a JITted routine. +// * We can probably do a lot better in speed by unrolling and aliasing the stack. Name top N stack vars a1 to aN, +// then unpack the top N into those, then run code, then pack aN to a?. +// * We don't have support for branches, even just the dumb "primary only, bail otherwise" variety. +// * We don't ever remove JIT. We should destroy JIT when a cell is modified. +// * If a cell is modified _DURING_ a JITted sequence, what do? If external, we can just claim it doesn't matter. +// If the thread self-modified, we'll need a means to abort the JITted sequence early. +// * There is no JIT visualisation. + +import { instructions, instructions_raw } from "./instructions.mjs"; +import { Queue } from "./queue.mjs"; +import { Jit } from "./jit.mjs"; + +function gen_fingerprint(s) { + let x = 0; + for(let c of Array.from(s).map(x => x.charCodeAt(0))) { + x = x * 256 + c; + } + return x; +} + +function deep_copy(obj) { + return JSON.parse(JSON.stringify(obj)); +} + +function shallow_copy(obj) { + return Object.fromEntries(Object.entries(obj)); +} + +function shallowish_copy(obj, i) { + if (i > 0) { + return shallowish_copy(obj, i - 1); + } else { + return shallow_copy(obj); + } +} + +class Thread { + running = true; + mode = "normal"; + jit = null; + tick_count = 0; + + constructor(interpreter, col, row, cold, rowd, stack, overlays) { + this.interpreter = interpreter; + this.col = col; + this.row = row; + this.cold = cold; + this.rowd = rowd; + this.stack = stack; + this.overlays = overlays || {}; + } + + split_thread() { + let thread = new Thread( + this.interpreter, + this.col, + this.row, + -this.cold, + -this.rowd, + Array.from(this.stack), + shallow_copy(this.overlays), + ); + thread.tick_forwards(); + thread.tick_count = this.tick_count; // Ensure new thread has equal priority to old thread. + return thread; + } + + tick_forwards() { + this.col += this.cold; + this.row += this.rowd; + if (this.row == this.interpreter.field.length && this.rowd > 0) this.row = 0; + else if (this.row == -1 && this.rowd < 0) this.row = this.interpreter.field.length - 1; + if (this.col == this.interpreter.field[this.row].length && this.cold > 0) this.col = 0; + else if (this.col == -1 && this.cold < 0) this.col = this.interpreter.field[this.row].length - 1; + } + + pop() { + if (this.stack.length > 0) { + return this.stack.pop(); + } else { + this.interpreter.error(this, "Stack underflow"); + return 0; + } + } +} + +export class Interpreter { + max_loops = 1000000000000; + ticks = 0; + paused_wake = null; + paused_event = null; + slice_loops = 1; + slice_sleep = 20; + running = false; + threads = []; + events = {}; + awake_sleep = null; + stats = []; + field = []; + instructions = shallow_copy(instructions); + instructions_raw = shallow_copy(instructions_raw); + overlays = {}; + input_queue = new Queue(); // Queue things like: change speed, input, etc. + stdin_queue = new Queue(); // Chars from stdin. + stdin_waiters = []; + handlers = {}; + + constructor() { + this.jit = new Jit(this); + } + + load_overlay(fingerprint, overlay) { + this.overlays[gen_fingerprint(fingerprint)] = overlay; + } + + add_handler(event_name, f) { + if (this.handlers[event_name] === undefined) { + this.handlers[event_name] = []; + } + this.handlers[event_name].push(f); + } + + trigger_event(event_name, ...args) { + //console.log(`Event: ${event_name} -- ${args}`); + let handlers = this.handlers[event_name]; + if (handlers === undefined) { + //console.log(`Unhandled event: ${event_name} -- ${args}`); + } else { + handlers.forEach(f => f(...args)); + } + } + + set_speed(raw_speed) { + if (raw_speed > 0) { + this.slice_sleep = 1; + this.slice_loops = Math.floor(1.1 ** raw_speed); + } else { + this.slice_sleep = Math.floor(1.1 ** -raw_speed); + this.slice_loops = 1; + } + this.trigger_event("speed_changed", raw_speed, this.slice_sleep, this.slice_loops); + } + + toggle_pause(e) { + if (this.paused_wake === null) { + this.pause(); + } else { + this.unpause(); + } + } + + pause() { + let thisthis = this; + this.paused_event = new Promise(function (r) { + thisthis.paused_wake = r; + // We shouldn't be in the sleep loop if paused. + if (thisthis.awake_sleep != null) thisthis.awake_sleep(); + }); + // These really need outputting from the main loop, not here! + this.trigger_event("paused", true); + this.threads.forEach(function (thread) { + thisthis.trigger_event("thread_paused", thread, true); + }); + } + + unpause() { + let old_wake = this.paused_wake; + let thisthis = this; + this.paused_wake = null; + this.trigger_event("paused", false); + this.threads.forEach(function (thread) { + thisthis.trigger_event("thread_paused", thread, false) + }); + if (old_wake) + old_wake(); + } + + async sleep_until_unpaused(all_blocked) { // TODO??? + if (!this.paused_event) + await this.sleep(all_blocked ? 50 : this.slice_sleep); + if (this.paused_event) { + let paused_event = this.paused_event; + this.paused_event = null; + let unpause_data = await paused_event; + return unpause_data; + } + } + + step(e) { + let old_wake = this.paused_wake; + this.pause(); + if (old_wake !== null) { + old_wake({count: 1}); + } + } + + step_thread(thread) { + let old_wake = this.paused_wake; + this.pause(); + if (old_wake !== null) { + old_wake({threads: [thread], count: 1}); + } + } + + async go() { + //console.log(this.threads); + this.stop(); + if (this.running_promise != null) { + console.log("Waiting for last thread to exit"); + await this.running_promise; + console.log("Last thread has exited"); + } + let thread = new Thread(this, 0, 0, 1, 0, []); + this.new_thread(thread); + this.trigger_event("started"); + this.main_loop(); + } + + new_thread(thread) { + this.threads.push(thread); + this.trigger_event("thread_created", thread); + } + + stop() { + if (this.threads.length > 0) { + this.trigger_event("stopped"); + } + this.threads.forEach(function (thread) { thread.running = false; }); + this.unpause(); + if (this.awake_sleep != null) this.awake_sleep(); + } + + check(state, col, row) { + if (row < 0 || row >= this.field.length) this.error(state, `Row ${row} is out of bounds`); + //else if (col < 0 || col >= this.field[row].length) this.error(state, `Col ${col} is out of bounds`); + else return true; + } + + set_field(state, col, row, val) { + if (this.check(state, col, row)) { + this.set_cell(col, row, val); + } else { + console.log(`out of bounds access ${state} ${col} ${row} ${val}`); + } + } + + set_cell(col, row, val) { + let val_str = String.fromCharCode(val); + this.field[row][col] = val; + let title = `(${col},${row})=${val} (${val_str})`; + if (this.instructions_raw[val_str] !== undefined) + title += ": " + this.instructions_raw[val_str].desc; + this.jit.cell_changed(row, col); + this.trigger_event("cell_changed", col, row, val); + } + + get_field(state, col, row, val) { + if (this.check(state, col, row)) { + let val = this.field[row][col]; + return val === undefined ? 0 : val; + } + } + + out(s) { + this.trigger_event("char_out", s); + //console.log("Out: " + s); + } + oute(s) { + this.trigger_event("error_occurred", s); + //console.log("Err: " + s); + } + + error(thread, message) { + thread.running = false; + this.output_error(thread, message); + } + + output_error(thread, message) { + this.oute(`ERROR: ${message}`); + this.oute(`State was: row=${thread.row} col=${thread.col} stack=${thread.stack}`); + this.output_field(); + } + + output_field() { + let lines = []; + for(let row=0; row<this.field.length; row++) { + let s = ""; + for(let col=0; col<this.field[row].length; col++) { + let c = this.field[row][col]; + s += (c > 31 && c < 127 ? String.fromCharCode(this.field[row][col]) : "?"); + } + lines.push(s); + } + this.trigger_event("output_field", lines); + } + + async sleep(ms) { + await new Promise(r => { this.awake_sleep = r; setTimeout(r, ms) }); + this.awake_sleep = null; + } + + process_events() { + while (this.input_queue.length > 0) { + let item = this.input_queue.pop(); + if (item[0] == "set_speed") { + this.set_speed(item[1]); + } else if (item[0] == "pause") { + this.pause(); + } else if (item[0] == "unpause") { + this.unpause(); + } else if (item[0] == "stdin") { + if (this.stdin_waiters.length > 0) { + this.stdin_waiters.shift()(item[1]); + } else { + this.stdin_queue.push(item[1]); + } + } + } + } + + async main_loop() { + let ticks = 0; + this.running = true; + let running_wake = null; + this.running_promise = new Promise(r => { running_wake = r; }); + let highlighted_cells = []; + let start_time = new Date().getTime(); + let all_blocked = false; + while(this.threads.length > 0) { + this.process_events(); + let slice_loops = this.slice_loops; + let limit_threads = null; + let need_sleep = true; + + this.threads.forEach(thread => this.trigger_event("thread_state_updated", thread)); + this.trigger_event("thread_state_synced", this.threads); + + if (ticks > this.max_loops) { + this.ticks += ticks; + ticks = 0; + let end_time = new Date().getTime(); + this.oute(`Ran out of ticks! (${ticks} > ${this.max_loops})`); + console.log(`${ticks} in ${end_time - start_time} is ${ticks / (end_time - start_time) / 1000} MHz`); + this.pause(); + this.output_field(); + } + + let unpause_data = await this.sleep_until_unpaused(all_blocked); + if (unpause_data) { + console.log(`Unpaused; resetting ticks to 0 from ${ticks}`); + start_time = new Date().getTime(); + if (unpause_data.count !== undefined) { + need_sleep = false; + slice_loops = unpause_data.count; + } + if (unpause_data.threads !== undefined) + limit_threads = unpause_data.threads; + } + + let count = 0; + let dead_threads = []; + while(count < slice_loops && this.threads.length > 0) { + // This will _not_ iterate over newly-added threads. + all_blocked = true; + let to_iter = limit_threads !== null ? limit_threads : this.threads; + to_iter = to_iter.filter(thread => !thread.blocked); + if (to_iter.length == 0) { + break; + } + to_iter.forEach(function (thread) { if (!thread.waiter) { all_blocked = false; } }); + let min_count = Math.min(...to_iter.map(x => x.tick_count)); + to_iter.forEach((thread, i) => { + if (thread.tick_count == min_count) { + let tick_count = this.tick(thread, slice_loops - count, i); + count += tick_count; + thread.tick_count += tick_count; + } + }); + let new_dead_threads = this.threads.filter(t => !t.running); + if (new_dead_threads) { + dead_threads.push(...new_dead_threads); + this.threads = this.threads.filter(t => t.running); + } + } + + dead_threads.forEach( + dead_thread => this.trigger_event("thread_dead", dead_thread)); + + ticks += count; + } + running_wake(); + //console.log(`exited main_loop with ticks ${ticks}`); + this.ticks += ticks; + let end_time = new Date().getTime(); + //console.log(`${ticks} in ${end_time - start_time} is ${ticks / (end_time - start_time) / 1000} MHz`); + this.output_field(); + this.trigger_event("terminate"); + } + + tick(thread, target_ticks, thread_num) { + if (thread.waiter) { + try { + if (thread.waiter(thread)) { + thread.waiter = null; + } + } catch (err) { + this.error(thread, "Exception caught during waiter: " + err.message); + //console.log(err.stack); + } + return 1; + } + if (thread.mode == "normal") { + let symbol = this.field[thread.row][thread.col] || 32; + let instruction = thread.overlays[symbol] || this.instructions[symbol]; + //console.log(`Executing ${String.fromCharCode(symbol)} at ${thread.col},${thread.row} stack_length=${thread.stack.length} stack_tail=${thread.stack.slice(-10)}`); + //console.log("Executing " + String.fromCharCode(symbol)); + let jit_return = this.jit.step_jit(thread, target_ticks); + if (jit_return > 0) { + return jit_return; + } + if (instruction == null) { + this.error(thread, "Invalid instruction: " + symbol); + } else { + try { + let ret = instruction(thread, thread_num); + if (ret && ret.constructor === Promise) { + thread.blocked = true; + } + } catch (err) { + this.error(thread, "Exception caught during " + symbol + ": " + err.message); + //console.log(err.stack); + } + } + } else if (thread.mode == "string") { + let symbol = this.field[thread.row][thread.col]; + if (symbol == '"'.charCodeAt(0)) { + thread.mode = "normal"; + } else { + thread.stack.push(symbol); + } + } + thread.tick_count += 1; + thread.tick_forwards(); + return 1; + } +} diff --git a/2026/games_submissions/liquidcake1/jit.mjs b/2026/games_submissions/liquidcake1/jit.mjs new file mode 100644 index 0000000..da0d2fb --- /dev/null +++ b/2026/games_submissions/liquidcake1/jit.mjs @@ -0,0 +1,259 @@ +// JIT TODO: +// * OK, it's probably about 6x faster with JIT than without, but we can go another 4-5x faster by loop unrolling. +// * We don't bounds-check the stack before executing a JITted routine. +// * We can probably do a lot better in speed by unrolling and aliasing the stack. Name top N stack vars a1 to aN, +// then unpack the top N into those, then run code, then pack aN to a?. +// * We don't have support for branches, even just the dumb "primary only, bail otherwise" variety. +// * We don't ever remove JIT. We should destroy JIT when a cell is modified. +// * If a cell is modified _DURING_ a JITted sequence, what do? If external, we can just claim it doesn't matter. +// If the thread self-modified, we'll need a means to abort the JITted sequence early. +// * There is no JIT visualisation. + +function diagonalise_positive(row, col) { + /* + 0 1 3 6 + 2 4 7 + 5 8 + 9 + */ + let shell = row + col; + let prev_shell_indices = shell * (shell + 1) / 2; + return prev_shell_indices + row; +} + +function dediagonalise_positive(diagonal_index) { + // n (n + 1) / 2 = x; + let shell = Math.floor((Math.sqrt(8 * diagonal_index + 1) - 1) / 2); + let row_0_index = diagonalise_positive(0, shell); + let row = diagonal_index - row_0_index; + let col = shell - row; + return [row, col]; +} + +function diagonalise_any(row, col) { + /* + 35 21 11 23 39 + 20 10 4 12 24 + 9 3 0 1 5 + 18 8 2 6 14 + 31 17 7 15 27 + */ + let shell = Math.abs(row) + Math.abs(col); + if (shell == 0) return 0; + let prev_shell_indices = 2 * shell * (shell - 1); + if (row >= 0) { + return prev_shell_indices + shell + 1 - col; + } else { + return prev_shell_indices + 3 * shell + 1 + col; + } +} + +function dediagonalise_any(diagonal_index) { + if (diagonal_index == 0) { + return [0, 0]; + } + // cumulative past shell size is (shell-1)^2 + shell^2 + // so 2s^2 - 2s + 1 = n + // (2 + (4-8(1-n))^0.5)/4 = ((2n-1)^0.5+1)/2 + let shell = Math.floor((Math.sqrt(2*diagonal_index-1)+1)/2); + let row_0_pos_index = diagonalise_any(0, shell); + let col = shell - diagonal_index + row_0_pos_index; + if (col < -shell) { + col = -2 * shell - col; + } + let row = diagonal_index - row_0_pos_index; + if (row > shell) { + row = 2 * shell - row; + } + if (row < -shell) { + row = -2 * shell - row; + } + return [row, col]; +} + +function diagonalise_with_dir(row, col, rowd, cold) { + return 4 * diagonalise_positive(row, col) + diagonalise_any(rowd, cold); +} + +function dediagonalise_with_dir(diagonal_index) { + let dir_diag = diagonal_index % 4; + let pos_diag = diagonal_index / 4; + return dediagonalise_any(pos_diag).concat(dediagonalise_positive(dir_diag)); +} + +class CellStats { + hit_count = 1; + jit_starts = []; + jit = null; +} + +class JitPath { + path = []; + code = ""; + stack_req = 0; + stack_delta = 0; + instruction_count = 0; + constructor() { + } +} + +class JitFragment { + end_row = null; + end_col = null; + end_rowd = null; + end_cold = null; + instruction_count = null; + path = null; + stack_req = null; + code = null; + jit_starts = []; // Should just be the initial cell, but kept for consistency + + constructor(thread, instruction_count, path, stack_req, code) { + // TODO: let [end_row, end_col, end_rowd, end_cold] = dediagonalise_with_dir(resting_place); + this.end_row = thread.row; + this.end_col = thread.col; + this.end_rowd = thread.rowd; + this.end_cold = thread.cold; + this.instruction_count = instruction_count; + this.path = path; + this.stack_req = stack_req; + this.code = code; + } + + maybe_run_in_thread(thread, target_ticks) { + // TODO include this fragment in the JIT code itself. + // TODO should store and check thread fingerprints! + if (this.instruction_count > target_ticks || thread.stack.length < this.stack_req) { + // Checking requirements not met. Just interpret. + //console.log(`Not running JIT. ${this.instruction_count} > ${target_ticks} || ${thread.stack.length} < ${this.stack_req}`); + return 0; + } + try { + this.call(thread); + } catch (err) { + thread.interpreter.error(thread, `Exception caught in JIT: ${err.message}`); + // TODO: Stop thread?! + } + thread.tick_count += this.instruction_count; + thread.row = this.end_row; + thread.col = this.end_col; + thread.rowd = this.end_rowd; + thread.cold = this.end_cold; + return this.instruction_count; + } +} + +export class Jit { + cell_stats = {}; + path = null; + threshold = 10000000000; + interpreter = null; + + constructor(interpreter) { + this.interpreter = interpreter; + } + + step_jit(thread, target_ticks) { + let diagonal_index = diagonalise_with_dir(thread.row, thread.col, thread.rowd, thread.cold); + let jit_path = thread.jit_path; + if (jit_path === undefined) { + let cell_stats = this.cell_stats[diagonal_index]; + // Run jitted code, if present, else increment count and possibly start a JIT follow. + if (!cell_stats) { + this.cell_stats[diagonal_index] = new CellStats(); + return 0; + } else if (cell_stats.jit) { + return cell_stats.jit.maybe_run_in_thread(thread, target_ticks); + } else { + cell_stats.hit_count += 1; + if (cell_stats.hit_count < this.threshold) { + return 0; + } + //console.log(`loop found at ${diagonal_index}?`); + thread.jit_path = jit_path = new JitPath(); + // Fall through to below now. + } + } + //console.log(`${jit_path}`); + if (jit_path.path.length > 0 && diagonal_index == jit_path.path[0]) { + // We completed a path. + let jit = this.complete_jit(thread); + // Run it, else we'll run the current instruction and then begin a new + // loop at the next cell, which will prevent evaluation of this one! + return jit.maybe_run_in_thread(thread, target_ticks); + } + // We're following a path. + // TODO this should not need to do string futzing. + let symbol = this.interpreter.field[thread.row][thread.col]; + if (thread.overlays[symbol] !== undefined) { + // TODO + //console.log("Stopping JIT as we're entering an instruction from an overlay."); + this.complete_jit(thread); + return 0; + } + let raw_instruction = this.interpreter.instructions_raw[String.fromCharCode(symbol)]; + if (raw_instruction === undefined) { + //console.log("Stopping JIT as we're entering an unknown instruction."); + this.complete_jit(thread); + return 0; + } + if (!raw_instruction.can_jit) { + //console.log("Stopping JIT as we're entering an instruction I don't understand."); + //console.log(jit_path); + this.complete_jit(thread); + return 0; + } + jit_path.code += `// ${String.fromCharCode(symbol)} at row ${thread.row}, col ${thread.col}, heading ${thread.rowd}, ${thread.cold}\n`; + let real_code = raw_instruction.unchecked_js_code; + if (typeof real_code == 'function') { + real_code = real_code(thread); + } + jit_path.code += `${real_code}\n`; + jit_path.stack_req = Math.max(jit_path.stack_req, -jit_path.stack_delta - raw_instruction.stack_min); + jit_path.stack_delta += raw_instruction.stack_return - raw_instruction.stack_min; + jit_path.path.push(diagonal_index); + jit_path.instruction_count += 1; + //console.log(jit_path); + return 0; + } + + complete_jit(thread) { + let jit_path = thread.jit_path; + thread.jit_path = undefined; + if (jit_path.path.length == 0) { + //console.log("JIT path is empty."); + return; + } + console.log("Performing JIT compile, length " + jit_path.path.length); + //console.log(jit_path); + // TODO: jit_path.compile() + let code = `jit.call=function (thread) {\nlet stack = thread.stack;\n${jit_path.code}\n}`; + //console.log(code); + let jit = new JitFragment(thread, jit_path.instruction_count, jit_path.path, jit_path.stack_req, code); + //console.log(jit); + eval(code); + //console.log(jit); + let starting_place = jit_path.path[0]; + this.cell_stats[jit_path.path[0]].jit = jit; + for(let i=0; i<jit_path.path.length; i++) { + this.cell_stats[jit_path.path[i]].jit_starts.push(starting_place); + } + // Drop out of follow mode, now. + return jit; + } + + cell_changed(row, col) { + let dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]]; + for(let i=0; i<4; i++) { + let rowd = dirs[i][0]; + let cold = dirs[i][1]; + let cell_diagonal_index = diagonalise_with_dir(row, col, rowd, cold); + let cell_stats = this.cell_stats[cell_diagonal_index]; + if (cell_stats) { + for(let j=0; j<cell_stats.jit_starts.length; j++) { + this.cell_stats[cell_stats.jit_starts[j]] = undefined; + } + } + } + } +} diff --git a/2026/games_submissions/liquidcake1/queue.mjs b/2026/games_submissions/liquidcake1/queue.mjs new file mode 100644 index 0000000..e6c3efe --- /dev/null +++ b/2026/games_submissions/liquidcake1/queue.mjs @@ -0,0 +1,30 @@ +export class Queue { + /* Structure is a (hopefully short) list of (longer) lists. + * Queue to the last list. + * Hold index into first list. + */ + length = 0; + index = -1; + content = [[]]; + max_len = 100; + push(x) { + let content = this.content; + if (content[content.length - 1].length > this.max_len) { + content.push([]); + } + content[content.length - 1].push(x); + this.length += 1; + } + pop() { + this.index += 1; + if (this.index >= this.content[0].length) { + if (this.content.length == 1) { + throw("empty"); + } + this.content.shift(); + this.index = 0; + } + this.length -= 1; + return this.content[0][this.index]; + } +} diff --git a/2026/games_submissions/liquidcake1/wasm.mjs b/2026/games_submissions/liquidcake1/wasm.mjs new file mode 100644 index 0000000..18bf547 --- /dev/null +++ b/2026/games_submissions/liquidcake1/wasm.mjs @@ -0,0 +1,51 @@ +export function load_wasm(interpreter) { + /* + (memory $memory 1) + (export "memory" (memory $memory)) + (func (export "load_first_item_in_mem") (param) (result i32) + i32.const 0 + + ;; load first item in memory and return the result + i32.load + ;; store 10000 in the first location in memory + i32.const 0 + i32.const 10000 + i32.store + ) + */ + /* + (module +(func $pop (import "my_namespace" "pop") (param i32) (result i32)) +(func $push (import "my_namespace" "push") (param i32 i32)) +(func (export "plus") (param i32) + local.get 0 + local.get 0 + call $pop + local.get 0 + call $pop + i32.add + call $push +)) +*/ + //let wasm_string = "AGFzbQEAAAABBwFgAn9/AX8DAgEABwoBBmFkZFR3bwAACgkBBwAgACABagsACgRuYW1lAgMBAAA="; + let wasm_string = "AGFzbQEAAAABDwNgAX8Bf2ACf38AYAF/AAIoAgxteV9uYW1lc3BhY2UDcG9wAAAMbXlfbmFtZXNwYWNlBHB1c2gAAQMCAQIHCAEEcGx1cwACChEBDwAgACAAEAAgABAAahABCwAcBG5hbWUBDAIAA3BvcAEEcHVzaAIHAwAAAQACAA=="; + // Fake minus version + //let wasm_string = "AGFzbQEAAAABDwNgAX8Bf2ACf38AYAF/AAIoAgxteV9uYW1lc3BhY2UDcG9wAAAMbXlfbmFtZXNwYWNlBHB1c2gAAQMCAQIHCAEEcGx1cwACChEBDwAgACAAEAAgABAAaxABCwAcBG5hbWUBDAIAA3BvcAEEcHVzaAIHAwAAAQACAA=="; + let wasm_array = new TextEncoder().encode(atob(wasm_string)) + const importObject = { + my_namespace: { + pop: threadNo => interpreter.threads[threadNo].pop(), + push: (threadNo, i) => interpreter.threads[threadNo].stack.push(i), + } + }; + return WebAssembly.compile(wasm_array).then((mod) => + WebAssembly.instantiate(mod, importObject).then(instance => { + patch_interpreter(interpreter, instance); + return instance; + }) + ); +} +function patch_interpreter(interpreter, instance) { + interpreter.instructions_raw["+"].impl = function (thread, thread_num) { instance.exports.plus(thread_num); }; + interpreter.instructions["+".charCodeAt(0)] = function (thread, thread_num) { instance.exports.plus(thread_num); }; +} |
