summaryrefslogtreecommitdiff
path: root/2026/games/liquidcake1/index.html
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2026-08-29 04:06:51 -0400
committerLLLL Colonq <llll@colonq>2026-08-29 04:06:51 -0400
commit761a3172bf4c15aed3836e4d1cd6f815b651b0e0 (patch)
treecc90491529e83349d84d42a8e9982e5f0f38338e /2026/games/liquidcake1/index.html
parent0d2d8cd897e94bd94e0b3ae7c6ce9aa580a44a0a (diff)
Fix
Diffstat (limited to '2026/games/liquidcake1/index.html')
-rw-r--r--2026/games/liquidcake1/index.html415
1 files changed, 415 insertions, 0 deletions
diff --git a/2026/games/liquidcake1/index.html b/2026/games/liquidcake1/index.html
new file mode 100644
index 0000000..8bff36c
--- /dev/null
+++ b/2026/games/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>