import init, { World, Pane, FullTile, Signal, available_tiles, } from "/stackline-wasm/pkg/stackline_wasm.js"; let promises = []; promises.push(init()); const canvas = document.getElementById("main-canvas"); const ctx = canvas.getContext("2d"); let font = new FontFace("Stackline Classic", "url(\"/font/StacklineClassic-Medium.otf\")"); promises.push(font.load()); await Promise.all(promises); font = await font.loaded; document.fonts.add(font); let world = World.deserialize(await (await fetch("/stackline/tests/other/prime.json")).json()); world.init(); // let world = World.new(); let cx = 0; let cy = 0; let zoom = 1; let grid = true; let click_x = 0, click_y = 0; let mouse_x = 0, mouse_y = 0; let mouse_down = false; let hovered = false; let selected = null; let running = null; let tile = world.get(4, 0); tile.signal = new Signal(); tile.state = "Active"; world.set(4, 0, tile); // let pane = Pane.empty(5, 5); // world.set_pane("main", pane); // let available = available_tiles(); // console.log(available); // for (let n = 0; n < available.length; n++) { // world.set( // n % 5, // ~~(n / 5), // new FullTile(available[n]) // ); // } console.log(world.toString()); console.log(world.serialize()); resize(); window.addEventListener("resize", resize); window.requestAnimationFrame(loop); function draw() { let zoom_factor = Math.ceil(Math.pow(2, zoom)); let tile_size = 10 * zoom_factor; ctx.fillStyle = "#202027"; ctx.fillRect(0, 0, canvas.width, canvas.height); function stroke_rect(x, y, width, height) { ctx.lineWidth = zoom_factor; ctx.strokeRect( Math.round(x * tile_size + cx + canvas.width / 2) - zoom_factor * 1.5, Math.round(y * tile_size + cy + canvas.height / 2) - zoom_factor * 1.5, Math.round(width * tile_size) + zoom_factor * 2, Math.round(height * tile_size) + zoom_factor * 2, ); } function fill_rect(x, y, width, height) { ctx.fillRect( Math.round(x * tile_size + cx + canvas.width / 2) - zoom_factor * 1, Math.round(y * tile_size + cy + canvas.height / 2) - zoom_factor * 1, Math.round(width * tile_size) + zoom_factor * 1, Math.round(height * tile_size) + zoom_factor * 1, ); } let panes = []; for (let name of world.panes()) { let pane = world.get_pane(name); panes.push({ x: pane.x, y: pane.y, width: pane.width, height: pane.height }); ctx.strokeStyle = "rgba(128, 128, 128, 0.5)"; stroke_rect( pane.x, pane.y, pane.width, pane.height, ); pane.free(); } let from_y = Math.floor((-cy - canvas.height / 2) / tile_size); let to_y = Math.ceil((-cy + canvas.height / 2) / tile_size); let from_x = Math.floor((-cx - canvas.width / 2) / tile_size); let to_x = Math.ceil((-cx + canvas.width / 2) / tile_size); let width = to_x - from_x + 1; let height = to_y - from_y + 1; // TODO: memoize let chars = world.draw(-from_x, -from_y, width, height); ctx.textAlign = "left"; ctx.textBaseline = "top"; ctx.font = `${tile_size}px Stackline Classic`; for (let y = from_y; y <= to_y; y++) { for (let x = from_x; x <= to_x; x++) { let x2 = x * tile_size + cx + canvas.width / 2; let y2 = y * tile_size + cy + canvas.height / 2; let index = 3 * (width * (y - from_y) + x - from_x); let ch = String.fromCharCode(chars[index]); if (ch !== " ") { let fg = to_color(chars[index + 1]); let bg = chars[index + 2] === 0 ? null : to_color(chars[index + 2]); if (bg) { ctx.fillStyle = `rgba(${bg.join(",")})`; ctx.fillRect( Math.round(x2), Math.round(y2), Math.round(x2 + tile_size) - Math.round(x2), Math.round(y2 + tile_size) - Math.round(y2), ); } ctx.fillStyle = `rgba(${fg.join(",")})`; ctx.fillText( ch, Math.round(x2), Math.round(y2), ); } else if (grid) { // Draw grid let inside = false; for (let pane of panes) { if (x >= pane.x && x < pane.x + pane.width && y >= pane.y && y < pane.y + pane.height) { inside = true; break; } } if (inside || width < 100 && height < 100) { ctx.fillStyle = inside ? "#404040" : "#303030"; ctx.fillRect( Math.round(x2 + tile_size / 2) - zoom_factor, Math.round(y2 + tile_size / 2) - zoom_factor, zoom_factor, zoom_factor ); } } } } if (hovered) { let [x, y] = get_hovered(); ctx.strokeStyle = "rgba(230, 255, 230, 0.2)"; ctx.fillStyle = "rgba(230, 255, 230, 0.07)"; stroke_rect(x, y, 1, 1); fill_rect(x, y, 1, 1); } } function resize() { canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; draw(); } function loop() { draw(); window.requestAnimationFrame(loop); } canvas.addEventListener("mousedown", (evt) => { click_x = mouse_x = evt.clientX; click_y = mouse_y = evt.clientY; mouse_down = true; }); canvas.addEventListener("mousemove", (evt) => { hovered = true; if (mouse_down) { // TODO: numerically stable solution cx += evt.clientX - mouse_x; cy += evt.clientY - mouse_y; } mouse_x = evt.clientX; mouse_y = evt.clientY; }); canvas.addEventListener("mouseup", (evt) => { mouse_down = false; let dist = Math.sqrt((mouse_x - click_x) ** 2 + (mouse_y - click_y) ** 2); if (dist < 10) { selected = get_hovered(); } }); canvas.addEventListener("mouseenter", (evt) => { hovered = true; }); canvas.addEventListener("mouseleave", (evt) => { mouse_down = false; hovered = false; }); canvas.addEventListener("wheel", (event) => { const ZOOM_STRENGTH = -0.005; let old_zoom = zoom; zoom += event.deltaY * ZOOM_STRENGTH; zoom = Math.min(Math.max(zoom, 0.0), 4.0); let delta = Math.ceil(Math.pow(2, zoom)) / Math.ceil(Math.pow(2, old_zoom)); cx *= delta; cy *= delta; }); window.addEventListener("keydown", (event) => { if (hovered) { if (event.code === "Space") { if (running !== null) { clearInterval(running); running = null; } else if (event.shiftKey) { world.init(); running = setInterval(() => { world.step(); }, 100); } else { world.init(); world.step(); } } else if (event.code === "KeyG") { grid = !grid; } } }); function get_hovered() { let zoom_factor = Math.ceil(Math.pow(2, zoom)); let tile_size = 10 * zoom_factor; let x = Math.floor((mouse_x - canvas.width / 2 - cx) / tile_size); let y = Math.floor((mouse_y - canvas.height / 2 - cy) / tile_size); return [x, y]; } function to_color(num) { let alpha = (num >> 24) & 0xff; let red = (num >> 16) & 0xff; let green = (num >> 8) & 0xff; let blue = num & 0xff; return [red, green, blue, alpha]; }