refactor(javascript): split babylon_setup into multiple files
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -1,21 +1,7 @@
|
||||
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.",
|
||||
},
|
||||
});
|
||||
import { sharedState } from "./game/state.js";
|
||||
import { seededRng, generateMazeGrid, findDeadEnds } from "./game/maze.js";
|
||||
import { gridCellToWorld, isWalkableCell } from "./game/grid.js";
|
||||
|
||||
// Initialize Babylon.js engine and scene
|
||||
const canvas = document.getElementById("renderCanvas");
|
||||
@@ -143,88 +129,6 @@ function clearLevelMeshes() {
|
||||
spawnGridPos = 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 gridCellToWorld(grid, x, y) {
|
||||
const halfW = (grid[0].length * cellSize) / 2;
|
||||
const halfH = (grid.length * cellSize) / 2;
|
||||
return new BABYLON.Vector3(
|
||||
x * cellSize - halfW + cellSize / 2,
|
||||
0,
|
||||
y * cellSize - halfH + cellSize / 2,
|
||||
);
|
||||
}
|
||||
|
||||
function isWalkableCell(grid, x, y) {
|
||||
return y >= 0 && y < grid.length && x >= 0 && x < grid[0].length && grid[y][x] === 0;
|
||||
}
|
||||
|
||||
function isReservedCell(x, y) {
|
||||
if (chestMap.has(`${x},${y}`)) return true;
|
||||
if (exitGridPos && exitGridPos.x === x && exitGridPos.y === y) return true;
|
||||
@@ -322,7 +226,7 @@ function placeExit(grid, seed) {
|
||||
console.warn("Exit selected on non-walkable cell", { x, y });
|
||||
return;
|
||||
}
|
||||
const exitWorld = gridCellToWorld(grid, x, y);
|
||||
const exitWorld = gridCellToWorld(grid, x, y, cellSize);
|
||||
const ex = exitWorld.x;
|
||||
const ez = exitWorld.z;
|
||||
const worldSpan = Math.max(grid[0].length, grid.length) * cellSize;
|
||||
@@ -386,7 +290,7 @@ function spawnCameraAt(grid) {
|
||||
}
|
||||
|
||||
spawnGridPos = bestCell;
|
||||
const spawnWorld = gridCellToWorld(grid, bestCell.x, bestCell.y);
|
||||
const spawnWorld = gridCellToWorld(grid, bestCell.x, bestCell.y, cellSize);
|
||||
const px = spawnWorld.x;
|
||||
const pz = spawnWorld.z;
|
||||
|
||||
|
||||
15
src/game/grid.js
Normal file
15
src/game/grid.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as BABYLON from "babylonjs";
|
||||
|
||||
export function gridCellToWorld(grid, x, y, cellSize) {
|
||||
const halfW = (grid[0].length * cellSize) / 2;
|
||||
const halfH = (grid.length * cellSize) / 2;
|
||||
return new BABYLON.Vector3(
|
||||
x * cellSize - halfW + cellSize / 2,
|
||||
0,
|
||||
y * cellSize - halfH + cellSize / 2,
|
||||
);
|
||||
}
|
||||
|
||||
export function isWalkableCell(grid, x, y) {
|
||||
return y >= 0 && y < grid.length && x >= 0 && x < grid[0].length && grid[y][x] === 0;
|
||||
}
|
||||
66
src/game/maze.js
Normal file
66
src/game/maze.js
Normal file
@@ -0,0 +1,66 @@
|
||||
export 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): 0 path, 1 wall
|
||||
export 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;
|
||||
}
|
||||
|
||||
export 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;
|
||||
}
|
||||
15
src/game/state.js
Normal file
15
src/game/state.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export 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.",
|
||||
},
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sharedState } from "./babylon_setup.js";
|
||||
import { sharedState } from "./game/state.js";
|
||||
|
||||
// Handler functions (same as p5_panel but without p5 scoping)
|
||||
function resetRun(message) {
|
||||
|
||||
Reference in New Issue
Block a user