diff --git a/css/style.css b/css/style.css
index d050623..1a7019f 100644
--- a/css/style.css
+++ b/css/style.css
@@ -1,61 +1,68 @@
html,
body {
margin: 0;
- padding: 5px;
- align-items: center;
- text-align: center;
+ min-height: 100%;
}
+
+body.maze-page {
+ background:
+ radial-gradient(circle at top, #1c2733 0%, #0a0f15 55%, #06080c 100%);
+ color: #e8eef6;
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+}
+
canvas {
display: block;
}
-#one {
- display: block;
- width: 800px;
- height: 600px;
+.maze-layout {
+ width: min(1100px, calc(100vw - 24px));
+ margin: 12px auto 20px;
+ display: grid;
+ gap: 12px;
}
-#two {
- display: block;
- margin-top: 10px;
- width: 800px;
- height: 600px;
+.panel {
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ border-radius: 16px;
+ background: rgba(7, 12, 18, 0.72);
+ box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35);
+ overflow: hidden;
}
-/* Options in index.html */
-.options {
- display: inline-block;
- clear: both;
- margin-left: auto;
- margin-right: auto;
- text-align: center;
+.panel-label {
+ padding: 10px 14px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+ font-size: 12px;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: #93a4b8;
}
-.option {
- float: left;
- padding: 50px;
+#renderCanvas {
+ width: 100%;
+ height: min(62vh, 680px);
}
-.emoji {
- font-size: 100px;
- vertical-align: middle;
- line-height: 2;
- clear: both;
- margin: auto;
+#p5-panel {
+ display: flex;
+ justify-content: center;
+ padding: 8px 0 14px;
}
-.description {
- margin: auto;
- margin-top: -50px;
-
- font-family: 'Times New Roman', Times, serif;
- font-size: 25px;
- letter-spacing: 0.6px;
- word-spacing: 2px;
- color: #000000;
- font-weight: normal;
- text-decoration: none;
- font-style: normal;
- font-variant: small-caps;
- text-transform: none;
+#p5-panel canvas {
+ width: min(100%, 1000px);
+ height: auto;
+ border-radius: 12px;
+}
+
+@media (max-width: 720px) {
+ .maze-layout {
+ width: calc(100vw - 16px);
+ margin: 8px auto 16px;
+ }
+
+ #renderCanvas {
+ height: 52vh;
+ }
}
diff --git a/multi_sketch.html b/multi_sketch.html
index 20f233a..fdc6937 100644
--- a/multi_sketch.html
+++ b/multi_sketch.html
@@ -2,12 +2,24 @@
+
- Vite App
+ Untitled Maze Game
-
+
+
+
+
+
+
+
-
diff --git a/src/multi_sketch.js b/src/multi_sketch.js
index e8f11db..c9bb558 100644
--- a/src/multi_sketch.js
+++ b/src/multi_sketch.js
@@ -1,16 +1,258 @@
import * as BABYLON from "babylonjs";
+import { sketch } from "p5js-wrapper";
+
+const sharedState = (window.mazeGameState ??= {
+ config: {
+ seed: Math.floor(Math.random() * 100000),
+ level: 1,
+ mazeWidth: 11,
+ mazeHeight: 11,
+ minChestDeadEnds: 2,
+ },
+ runtime: {
+ runActive: false,
+ hasKey: false,
+ elapsedSeconds: 0,
+ message: "Adjust settings, then start a run.",
+ },
+});
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
-const camera = new BABYLON.ArcRotateCamera("cam", 0, 0, 10, BABYLON.Vector3.Zero(), scene);
+scene.clearColor = new BABYLON.Color4(0.05, 0.07, 0.1, 1);
+
+const camera = new BABYLON.ArcRotateCamera(
+ "cam",
+ -Math.PI / 2,
+ Math.PI / 2.4,
+ 10,
+ BABYLON.Vector3.Zero(),
+ scene,
+);
camera.attachControl(canvas, true);
-const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
+new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
-const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {}, scene);
+const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2 }, scene);
+const sphereMaterial = new BABYLON.StandardMaterial("sphereMaterial", scene);
+sphereMaterial.diffuseColor = new BABYLON.Color3(0.2, 0.55, 0.95);
+sphereMaterial.emissiveColor = new BABYLON.Color3(0.05, 0.12, 0.2);
+sphere.material = sphereMaterial;
+
+const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 14, height: 14 }, scene);
+const groundMaterial = new BABYLON.StandardMaterial("groundMaterial", scene);
+groundMaterial.diffuseColor = new BABYLON.Color3(0.12, 0.14, 0.17);
+groundMaterial.specularColor = BABYLON.Color3.Black();
+ground.material = groundMaterial;
engine.runRenderLoop(() => {
+ const level = sharedState.config.level;
+ sphere.rotation.y += 0.01;
+ sphere.scaling.x = 1 + (level - 1) * 0.05;
+ sphere.scaling.z = 1 + (level - 1) * 0.05;
+ sphereMaterial.diffuseColor = sharedState.runtime.hasKey
+ ? new BABYLON.Color3(0.25, 0.8, 0.45)
+ : new BABYLON.Color3(0.2, 0.55, 0.95);
scene.render();
});
+
+window.addEventListener("resize", () => {
+ engine.resize();
+});
+
+const panelSize = { width: 1000, height: 270 };
+const buttonRects = {};
+const sliderRects = {};
+let draggingSlider = null;
+
+function clamp(value, min, max) {
+ return Math.min(max, Math.max(min, value));
+}
+
+function valueToX(rect, min, max, value) {
+ const ratio = (value - min) / (max - min);
+ return rect.x + ratio * rect.width;
+}
+
+function xToValue(rect, min, max, x) {
+ const ratio = clamp((x - rect.x) / rect.width, 0, 1);
+ return Math.round(min + ratio * (max - min));
+}
+
+function resetRun(message) {
+ sharedState.runtime.runActive = true;
+ sharedState.runtime.hasKey = false;
+ sharedState.runtime.elapsedSeconds = 0;
+ sharedState.runtime.message = message;
+ sharedState.config.level = 1;
+}
+
+function restartLevel(message) {
+ sharedState.runtime.hasKey = false;
+ sharedState.runtime.elapsedSeconds = 0;
+ sharedState.runtime.message = message;
+}
+
+function randomizeSeed() {
+ sharedState.config.seed = Math.floor(Math.random() * 100000);
+ sharedState.runtime.message = `Seed set to ${sharedState.config.seed}.`;
+}
+
+function drawButton(rect, label, active = false) {
+ push();
+ stroke(active ? "#7db4ff" : "#284055");
+ strokeWeight(2);
+ fill(active ? "#17314c" : "#101820");
+ rect(rect.x, rect.y, rect.w, rect.h, 12);
+ noStroke();
+ fill("#eef5ff");
+ textAlign(CENTER, CENTER);
+ textSize(16);
+ text(label, rect.x + rect.w / 2, rect.y + rect.h / 2 + 1);
+ pop();
+}
+
+function drawSlider(rect, label, value, min, max, suffix = "") {
+ const knobX = valueToX(rect, min, max, value);
+ push();
+ fill("#dbe6f2");
+ noStroke();
+ textAlign(LEFT, BOTTOM);
+ textSize(15);
+ text(`${label}: ${value}${suffix}`, rect.x, rect.y - 8);
+ stroke("#33495e");
+ strokeWeight(6);
+ line(rect.x, rect.y + rect.h / 2, rect.x + rect.w, rect.y + rect.h / 2);
+ noStroke();
+ fill("#79aef2");
+ circle(knobX, rect.y + rect.h / 2, 20);
+ pop();
+}
+
+sketch.setup = function () {
+ createCanvas(panelSize.width, panelSize.height).parent("p5-panel");
+ pixelDensity(1);
+ textFont("monospace");
+ noLoop();
+};
+
+sketch.draw = function () {
+ background("#091018");
+
+ noStroke();
+ fill("#dce7f4");
+ textAlign(LEFT, TOP);
+ textSize(18);
+ text("Maze game settings", 18, 14);
+
+ fill("#91a4b8");
+ textSize(12);
+ text(
+ "This panel controls run settings and mirrors the shared state for the Babylon scene.",
+ 18,
+ 38,
+ );
+
+ const buttonY = 64;
+ buttonRects.start = { x: 18, y: buttonY, w: 140, h: 38 };
+ buttonRects.restart = { x: 168, y: buttonY, w: 140, h: 38 };
+ buttonRects.seed = { x: 318, y: buttonY, w: 160, h: 38 };
+
+ drawButton(buttonRects.start, "Start run", sharedState.runtime.runActive);
+ drawButton(buttonRects.restart, "Restart level");
+ drawButton(buttonRects.seed, "Randomize seed");
+
+ sliderRects.width = { x: 18, y: 154, w: 420, h: 22 };
+ sliderRects.height = { x: 18, y: 206, w: 420, h: 22 };
+ sliderRects.deadEnds = { x: 500, y: 154, w: 420, h: 22 };
+
+ drawSlider(sliderRects.width, "Maze width", sharedState.config.mazeWidth, 9, 31, " cells");
+ drawSlider(sliderRects.height, "Maze height", sharedState.config.mazeHeight, 9, 31, " cells");
+ drawSlider(
+ sliderRects.deadEnds,
+ "Minimum chest dead-ends",
+ sharedState.config.minChestDeadEnds,
+ 1,
+ 10,
+ );
+
+ fill("#dce7f4");
+ textSize(14);
+ textAlign(LEFT, TOP);
+ text(`Seed: ${sharedState.config.seed}`, 500, 196);
+ text(`Level: ${sharedState.config.level}`, 500, 220);
+ text(`Time: ${sharedState.runtime.elapsedSeconds.toFixed(1)}s`, 640, 220);
+ text(`Key: ${sharedState.runtime.hasKey ? "yes" : "no"}`, 780, 220);
+
+ fill("#93a4b8");
+ textSize(12);
+ text(`Status: ${sharedState.runtime.message}`, 18, 242, 960);
+};
+
+sketch.mousePressed = function () {
+ const x = mouseX;
+ const y = mouseY;
+
+ const isInside = (rect) => x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h;
+
+ if (isInside(buttonRects.start)) {
+ resetRun("Run started.");
+ redraw();
+ return;
+ }
+
+ if (isInside(buttonRects.restart)) {
+ restartLevel("Level restarted.");
+ redraw();
+ return;
+ }
+
+ if (isInside(buttonRects.seed)) {
+ randomizeSeed();
+ redraw();
+ return;
+ }
+
+ if (isInside(sliderRects.width)) {
+ draggingSlider = "width";
+ sharedState.config.mazeWidth = xToValue(sliderRects.width, 9, 31, x) | 1;
+ redraw();
+ return;
+ }
+
+ if (isInside(sliderRects.height)) {
+ draggingSlider = "height";
+ sharedState.config.mazeHeight = xToValue(sliderRects.height, 9, 31, x) | 1;
+ redraw();
+ return;
+ }
+
+ if (isInside(sliderRects.deadEnds)) {
+ draggingSlider = "deadEnds";
+ sharedState.config.minChestDeadEnds = xToValue(sliderRects.deadEnds, 1, 10, x);
+ redraw();
+ }
+};
+
+sketch.mouseDragged = function () {
+ if (draggingSlider === "width") {
+ sharedState.config.mazeWidth = xToValue(sliderRects.width, 9, 31, mouseX) | 1;
+ redraw();
+ }
+
+ if (draggingSlider === "height") {
+ sharedState.config.mazeHeight = xToValue(sliderRects.height, 9, 31, mouseX) | 1;
+ redraw();
+ }
+
+ if (draggingSlider === "deadEnds") {
+ sharedState.config.minChestDeadEnds = xToValue(sliderRects.deadEnds, 1, 10, mouseX);
+ redraw();
+ }
+};
+
+sketch.mouseReleased = function () {
+ draggingSlider = null;
+};