refactor(javascript): split babylon_setup into multiple files

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
pobadoba
2026-05-06 01:58:12 +09:00
parent 176ad34214
commit 47d4ba8bfe
5 changed files with 102 additions and 102 deletions

View File

@@ -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
View 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
View 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
View 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.",
},
});

View File

@@ -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) {