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 - + +
+
+
Babylon Scene
+ +
+ +
+
p5 Control Panel
+
+
+
+ - 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; +};