diff options
Diffstat (limited to '2026/games/liquidcake1/index.html')
| -rw-r--r-- | 2026/games/liquidcake1/index.html | 415 |
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> |
