diff --git a/css/style.css b/css/style.css index 1a7019f..5eead56 100644 --- a/css/style.css +++ b/css/style.css @@ -44,16 +44,145 @@ canvas { height: min(62vh, 680px); } -#p5-panel { - display: flex; - justify-content: center; - padding: 8px 0 14px; +.control-panel { + padding: 16px; + overflow-y: auto; + max-height: 400px; } -#p5-panel canvas { - width: min(100%, 1000px); - height: auto; - border-radius: 12px; +.control-group { + margin-bottom: 20px; +} + +.control-group h3 { + margin: 0 0 12px; + font-size: 13px; + letter-spacing: 0.05em; + text-transform: uppercase; + color: #93a4b8; + font-weight: 600; +} + +.button-group { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.game-button { + padding: 10px 16px; + background: #17314c; + border: 1px solid #284055; + border-radius: 8px; + color: #eef5ff; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + flex: 1; + min-width: 100px; +} + +.game-button:hover { + background: #1f3a57; + border-color: #3a5070; + box-shadow: 0 4px 12px rgba(125, 180, 255, 0.15); +} + +.game-button:active { + background: #17314c; + border-color: #7db4ff; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3); +} + +.slider-group { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 12px; + font-size: 13px; + color: #dbe6f2; +} + +.slider-group label { + display: flex; + align-items: center; + gap: 8px; + width: 100%; +} + +.slider-group input[type="range"] { + flex: 1; + min-width: 100px; + height: 6px; + background: #1f3a57; + border: none; + border-radius: 3px; + outline: none; + -webkit-appearance: none; + cursor: pointer; +} + +.slider-group input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 18px; + height: 18px; + background: #79aef2; + border-radius: 50%; + cursor: pointer; + box-shadow: 0 2px 6px rgba(121, 174, 242, 0.4); +} + +.slider-group input[type="range"]::-moz-range-thumb { + width: 18px; + height: 18px; + background: #79aef2; + border: none; + border-radius: 50%; + cursor: pointer; + box-shadow: 0 2px 6px rgba(121, 174, 242, 0.4); +} + +.slider-group span { + min-width: 30px; + text-align: right; + color: #79aef2; + font-weight: 500; +} + +.status-display { + background: rgba(25, 40, 55, 0.5); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 8px; + padding: 12px; + font-size: 12px; +} + +.status-line { + display: flex; + justify-content: space-between; + padding: 6px 0; + color: #dbe6f2; +} + +.status-line strong { + color: #93a4b8; + min-width: 60px; +} + +.status-line span { + color: #79aef2; + font-weight: 500; +} + +.status-message { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid rgba(255, 255, 255, 0.08); + color: #91a4b8; + font-size: 11px; + line-height: 1.5; } @media (max-width: 720px) { diff --git a/index.html b/index.html index 2400ffa..00f244f 100644 --- a/index.html +++ b/index.html @@ -1,27 +1,67 @@ - + - + + - - P5.js Template + + Untitled Maze Game + +
+
+
Babylon Scene
+ +
- -
-
-
🐪
- -
+
+
Game Controls
+
+
+

Run Controls

+
+ + + +
+
-
-
🐫
-
- Multi sketch +
+

Settings

+
+ +
+
+ +
+
+ +
+
+ +
+

Status

+
+
Seed: 0
+
Level: 1
+
Time: 0.0s
+
Key: no
+
Adjust settings, then start a run.
+
+
-
-
+
+
+ + diff --git a/multi_sketch.html b/multi_sketch.html deleted file mode 100644 index fdc6937..0000000 --- a/multi_sketch.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - Untitled Maze Game - - -
-
-
Babylon Scene
- -
- -
-
p5 Control Panel
-
-
-
- - - - diff --git a/single_sketch.html b/single_sketch.html deleted file mode 100644 index 73b25f5..0000000 --- a/single_sketch.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - Single sketch - - - - - diff --git a/src/babylon_setup.js b/src/babylon_setup.js new file mode 100644 index 0000000..66f6fa8 --- /dev/null +++ b/src/babylon_setup.js @@ -0,0 +1,322 @@ +import * as BABYLON from "babylonjs"; + +// Shared game state +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.", + }, +}); + +// Initialize Babylon.js engine and scene +const canvas = document.getElementById("renderCanvas"); +const engine = new BABYLON.Engine(canvas, true); + +const scene = new BABYLON.Scene(engine); +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); + +new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene); + +// Placeholder sphere +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; + +// Ground +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; + +// Main render loop +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(); +}); + +// Maze data structures +let levelMeshes = []; +let chestMap = new Map(); // key: "x,y" -> {mesh, opened} +let keyChestKey = null; +let exitBox = null; +const cellSize = 2; + +function clearLevelMeshes() { + for (const m of levelMeshes) { + try { m.dispose(); } catch(e) {} + } + levelMeshes = []; + chestMap.clear(); + keyChestKey = null; + if (exitBox) { try { exitBox.dispose(); } catch(e){}; exitBox = null; } +} + +// Simple seeded RNG +function seededRng(seed) { + let s = seed % 2147483647; + if (s <= 0) s += 2147483646; + return function () { + s = (s * 16807) % 2147483647; + return (s - 1) / 2147483646; + }; +} + +// Maze generation (recursive backtracker) returning grid: 0 path, 1 wall +function generateMazeGrid(w, h, seed) { + if (w % 2 === 0) w += 1; + if (h % 2 === 0) h += 1; + const rng = seededRng(seed); + const grid = Array.from({ length: h }, () => Array(w).fill(1)); + + function carve(x, y) { + grid[y][x] = 0; + const dirs = [ [0,-2],[2,0],[0,2],[-2,0] ]; + for (let i = dirs.length -1; i > 0; i--) { + const j = Math.floor(rng() * (i+1)); + [dirs[i], dirs[j]] = [dirs[j], dirs[i]]; + } + for (const [dx,dy] of dirs) { + const nx = x + dx; + const ny = y + dy; + if (nx > 0 && nx < w-1 && ny > 0 && ny < h-1 && grid[ny][nx] === 1) { + grid[y + dy/2][x + dx/2] = 0; + carve(nx, ny); + } + } + } + + const sx = 1 + Math.floor(rng() * ((w-1)/2)) * 2; + const sy = 1 + Math.floor(rng() * ((h-1)/2)) * 2; + carve(sx, sy); + + const extra = Math.max(0, Math.floor((w*h) * 0.02)); + for (let i = 0; i < extra; i++) { + const rx = 1 + Math.floor(rng() * ((w-1)/2)) * 2; + const ry = 1 + Math.floor(rng() * ((h-1)/2)) * 2; + const dirs = [ [0,-1],[1,0],[0,1],[-1,0] ]; + const [dx,dy] = dirs[Math.floor(rng()*dirs.length)]; + const nx = rx + dx; + const ny = ry + dy; + if (nx > 0 && nx < w-1 && ny > 0 && ny < h-1) grid[ny][nx] = 0; + } + + return grid; +} + +function findDeadEnds(grid) { + const h = grid.length; + const w = grid[0].length; + const dead = []; + for (let y = 1; y < h-1; y++) { + for (let x = 1; x < w-1; x++) { + if (grid[y][x] !== 0) continue; + let neighbors = 0; + const deltas = [[0,1],[1,0],[0,-1],[-1,0]]; + for (const [dx,dy] of deltas) if (grid[y+dy][x+dx] === 0) neighbors++; + if (neighbors === 1) dead.push([x,y]); + } + } + return dead; +} + +function buildLevelFromGrid(grid) { + clearLevelMeshes(); + const h = grid.length; + const w = grid[0].length; + const halfW = (w * cellSize) / 2; + const halfH = (h * cellSize) / 2; + + const floor = BABYLON.MeshBuilder.CreateGround('levelGround', { width: w*cellSize, height: h*cellSize }, scene); + floor.position = new BABYLON.Vector3(0, 0, 0); + const fm = new BABYLON.StandardMaterial('floorMat', scene); + fm.diffuseColor = new BABYLON.Color3(0.08, 0.08, 0.09); + floor.material = fm; + levelMeshes.push(floor); + + const wallMat = new BABYLON.StandardMaterial('wallMat', scene); + wallMat.diffuseColor = new BABYLON.Color3(0.33, 0.28, 0.22); + + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + if (grid[y][x] === 1) { + const box = BABYLON.MeshBuilder.CreateBox(`wall_${x}_${y}`, { size: cellSize }, scene); + box.position = new BABYLON.Vector3(x*cellSize - halfW + cellSize/2, cellSize/2, y*cellSize - halfH + cellSize/2); + box.material = wallMat; + box.checkCollisions = true; + levelMeshes.push(box); + } + } + } +} + +function placeChestsOnDeadEnds(grid, deadEnds, minCount, seed) { + const rng = seededRng(seed); + for (let i = deadEnds.length - 1; i > 0; i--) { + const j = Math.floor(rng() * (i+1)); + [deadEnds[i], deadEnds[j]] = [deadEnds[j], deadEnds[i]]; + } + const chosen = deadEnds.slice(0, Math.min(minCount, deadEnds.length)); + const halfW = (grid[0].length * cellSize) / 2; + const halfH = (grid.length * cellSize) / 2; + + const chestMat = new BABYLON.StandardMaterial('chestMat', scene); + chestMat.diffuseColor = new BABYLON.Color3(0.75, 0.45, 0.15); + + for (const [x,y] of chosen) { + const c = BABYLON.MeshBuilder.CreateBox(`chest_${x}_${y}`, { width: cellSize*0.8, height: cellSize*0.6, depth: cellSize*0.6 }, scene); + c.position = new BABYLON.Vector3(x*cellSize - halfW + cellSize/2, cellSize*0.3, y*cellSize - halfH + cellSize/2); + c.material = chestMat; + c.isPickable = true; + levelMeshes.push(c); + chestMap.set(`${x},${y}`, { mesh: c, opened: false }); + } + + if (chosen.length > 0) { + const k = Math.floor(rng() * chosen.length); + const [kx, ky] = chosen[k]; + keyChestKey = `${kx},${ky}`; + const entry = chestMap.get(keyChestKey); + if (entry) { + const km = new BABYLON.StandardMaterial('keyChestMat', scene); + km.diffuseColor = new BABYLON.Color3(0.95, 0.8, 0.1); + entry.mesh.material = km; + } + } +} + +function placeExit(grid, seed) { + const dead = findDeadEnds(grid); + const rng = seededRng(seed+1); + if (dead.length === 0) return; + const idx = Math.floor(rng() * dead.length); + const [x,y] = dead[idx]; + const halfW = (grid[0].length * cellSize) / 2; + const halfH = (grid.length * cellSize) / 2; + const ex = x*cellSize - halfW + cellSize/2; + const ez = y*cellSize - halfH + cellSize/2; + const plane = BABYLON.MeshBuilder.CreateGround('exitZone', { width: cellSize*0.9, height: cellSize*0.9 }, scene); + plane.material = new BABYLON.StandardMaterial('exitMat', scene); + plane.material.emissiveColor = new BABYLON.Color3(0.9, 0.8, 0.2); + plane.position = new BABYLON.Vector3(ex, 0.01, ez); + exitBox = plane; + levelMeshes.push(plane); +} + +function spawnCameraAt(grid) { + const h = grid.length, w = grid[0].length; + for (let y = 1; y < h-1; y++) { + for (let x = 1; x < w-1; x++) { + if (grid[y][x] === 0) { + const halfW = (w * cellSize) / 2; + const halfH = (h * cellSize) / 2; + const px = x*cellSize - halfW + cellSize/2; + const pz = y*cellSize - halfH + cellSize/2; + try { + if (camera && camera.position) { + camera.position = new BABYLON.Vector3(px, 1.6, pz); + } + } catch (e) {} + return; + } + } + } +} + +function generateLevel() { + const cfg = sharedState.config; + const seed = cfg.seed; + const w = Math.max(9, cfg.mazeWidth); + const h = Math.max(9, cfg.mazeHeight); + const grid = generateMazeGrid(w, h, seed + cfg.level); + const dead = findDeadEnds(grid); + buildLevelFromGrid(grid); + placeChestsOnDeadEnds(grid, dead, cfg.minChestDeadEnds, seed + cfg.level); + placeExit(grid, seed + cfg.level); + spawnCameraAt(grid); + sharedState.runtime.message = `Level ${cfg.level} generated.`; + window.requestAnimationFrame(()=>{ /* let scene update */ }); +} + +// Expose API for p5 to call +window.mazeGameApi = { generateLevel }; + +// Pointer interaction for chests +scene.onPointerObservable.add((pi) => { + if (pi.type !== BABYLON.PointerEventTypes.POINTERDOWN) return; + const pick = scene.pick(scene.pointerX, scene.pointerY); + if (!pick || !pick.hit || !pick.pickedMesh) return; + const m = pick.pickedMesh; + if (!m.name.startsWith('chest_')) return; + const coords = m.name.split('_').slice(1).join(','); + const entry = chestMap.get(coords); + if (!entry) return; + if (entry.opened) { + sharedState.runtime.runActive = false; + sharedState.runtime.message = 'Opened chest again — game over.'; + return; + } + entry.opened = true; + const openedMat = new BABYLON.StandardMaterial('openedMat', scene); + openedMat.diffuseColor = new BABYLON.Color3(0.25,0.25,0.25); + entry.mesh.material = openedMat; + if (coords === keyChestKey) { + sharedState.runtime.hasKey = true; + sharedState.runtime.message = 'You found the key! Find the exit.'; + } else { + sharedState.runtime.message = 'This chest was empty.'; + } +}); + +// Level transition check +scene.registerBeforeRender(() => { + if (sharedState.runtime.runActive) { + sharedState.runtime.elapsedSeconds += engine.getDeltaTime() / 1000; + } + if (sharedState.runtime.hasKey && exitBox && camera && camera.position) { + const pos = camera.position; + const ex = exitBox.position.x, ez = exitBox.position.z; + const dist = Math.hypot(pos.x - ex, pos.z - ez); + if (dist < cellSize * 0.9) { + sharedState.config.level += 1; + sharedState.runtime.hasKey = false; + sharedState.runtime.elapsedSeconds = 0; + sharedState.runtime.message = `Level ${sharedState.config.level} starting.`; + generateLevel(); + } + } +}); + +// Export shared state for p5 to use +export { sharedState }; diff --git a/src/html_panel.js b/src/html_panel.js new file mode 100644 index 0000000..ad1fbdb --- /dev/null +++ b/src/html_panel.js @@ -0,0 +1,81 @@ +import { sharedState } from "./babylon_setup.js"; + +// Handler functions (same as p5_panel but without p5 scoping) +function resetRun(message) { + sharedState.runtime.runActive = true; + sharedState.runtime.hasKey = false; + sharedState.runtime.elapsedSeconds = 0; + sharedState.runtime.message = message; + sharedState.config.level = 1; + try { window.mazeGameApi.generateLevel(); } catch (e) { console.warn(e); } + updateDisplay(); +} + +function restartLevel(message) { + sharedState.runtime.hasKey = false; + sharedState.runtime.elapsedSeconds = 0; + sharedState.runtime.message = message; + try { window.mazeGameApi.generateLevel(); } catch (e) { console.warn(e); } + updateDisplay(); +} + +function randomizeSeed() { + sharedState.config.seed = Math.floor(Math.random() * 100000); + sharedState.runtime.message = `Seed set to ${sharedState.config.seed}.`; + updateDisplay(); +} + +// Update display from shared state +function updateDisplay() { + document.getElementById("value-width").textContent = sharedState.config.mazeWidth; + document.getElementById("value-height").textContent = sharedState.config.mazeHeight; + document.getElementById("value-deadends").textContent = sharedState.config.minChestDeadEnds; + + document.getElementById("status-seed").textContent = sharedState.config.seed; + document.getElementById("status-level").textContent = sharedState.config.level; + document.getElementById("status-time").textContent = sharedState.runtime.elapsedSeconds.toFixed(1); + document.getElementById("status-key").textContent = sharedState.runtime.hasKey ? "yes" : "no"; + document.getElementById("status-message").textContent = sharedState.runtime.message; +} + +// Initialize event listeners +document.getElementById("btn-start").addEventListener("click", () => { + resetRun("Run started."); +}); + +document.getElementById("btn-restart").addEventListener("click", () => { + restartLevel("Level restarted."); +}); + +document.getElementById("btn-randomize").addEventListener("click", () => { + randomizeSeed(); +}); + +document.getElementById("slider-width").addEventListener("input", (e) => { + const value = parseInt(e.target.value) | 1; + sharedState.config.mazeWidth = value; + document.getElementById("value-width").textContent = value; +}); + +document.getElementById("slider-height").addEventListener("input", (e) => { + const value = parseInt(e.target.value) | 1; + sharedState.config.mazeHeight = value; + document.getElementById("value-height").textContent = value; +}); + +document.getElementById("slider-deadends").addEventListener("input", (e) => { + const value = parseInt(e.target.value); + sharedState.config.minChestDeadEnds = value; + document.getElementById("value-deadends").textContent = value; +}); + +// Update status display on game loop +setInterval(() => { + if (sharedState.runtime.runActive) { + document.getElementById("status-time").textContent = sharedState.runtime.elapsedSeconds.toFixed(1); + document.getElementById("status-key").textContent = sharedState.runtime.hasKey ? "yes" : "no"; + } +}, 100); + +// Initial display +updateDisplay(); diff --git a/src/multi_sketch.js b/src/multi_sketch.js index c9bb558..f9e1abb 100644 --- a/src/multi_sketch.js +++ b/src/multi_sketch.js @@ -1,258 +1,3 @@ -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); -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); - -new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), 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; -}; +// Import Babylon.js game logic and HTML control panel +import "./babylon_setup.js"; +import "./html_panel.js"; diff --git a/src/single_sketch.js b/src/single_sketch.js deleted file mode 100644 index cc1aff4..0000000 --- a/src/single_sketch.js +++ /dev/null @@ -1,20 +0,0 @@ -import '../css/style.css'; -import {sketch} from 'p5js-wrapper'; - -sketch.setup = function(){ - createCanvas (800, 600); -} - -sketch.draw= function(){ - background(100); - fill(255, 0, 0); - noStroke(); - rectMode(CENTER); - rect(mouseX, mouseY, 50, 50); -} - -sketch.mousePressed = function(){ - console.log('here'); -} - - diff --git a/vite.config.js b/vite.config.js index 98aaddd..089aa67 100644 --- a/vite.config.js +++ b/vite.config.js @@ -6,9 +6,7 @@ module.exports = defineConfig({ build: { rollupOptions: { input: { - main: resolve(__dirname, 'index.html'), - single: resolve(__dirname, 'single_sketch.html'), - multi: resolve(__dirname, 'multi_sketch.html') + main: resolve(__dirname, 'index.html') } } }