78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
|
|
import { createNoise2D } from 'simplex-noise'
|
|||
|
|
import { WORLD_TILES } from '../config'
|
|||
|
|
import { TileType } from '../types'
|
|||
|
|
|
|||
|
|
/** Simple seeded PRNG (mulberry32) */
|
|||
|
|
function mulberry32(seed: number): () => number {
|
|||
|
|
return () => {
|
|||
|
|
seed |= 0
|
|||
|
|
seed = (seed + 0x6D2B79F5) | 0
|
|||
|
|
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed)
|
|||
|
|
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
|
|||
|
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function classify(e: number, m: number): TileType {
|
|||
|
|
if (e < 0.22) return TileType.DEEP_WATER
|
|||
|
|
if (e < 0.30) return TileType.SHALLOW_WATER
|
|||
|
|
if (e < 0.38) return TileType.SAND
|
|||
|
|
if (e > 0.82) return TileType.ROCK
|
|||
|
|
// land – split by moisture
|
|||
|
|
if (m > 0.62 && e > 0.48) return TileType.FOREST
|
|||
|
|
if (m > 0.38) return TileType.DARK_GRASS
|
|||
|
|
return TileType.GRASS
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function generateTerrain(seed: number): number[] {
|
|||
|
|
const prng1 = mulberry32(seed)
|
|||
|
|
const prng2 = mulberry32(seed ^ 0xDEADBEEF)
|
|||
|
|
|
|||
|
|
const elevNoise = createNoise2D(prng1)
|
|||
|
|
const moistNoise = createNoise2D(prng2)
|
|||
|
|
|
|||
|
|
const size = WORLD_TILES
|
|||
|
|
const tiles = new Array<number>(size * size)
|
|||
|
|
|
|||
|
|
for (let y = 0; y < size; y++) {
|
|||
|
|
for (let x = 0; x < size; x++) {
|
|||
|
|
// Multi-octave elevation
|
|||
|
|
const nx = x / size
|
|||
|
|
const ny = y / size
|
|||
|
|
const e =
|
|||
|
|
(elevNoise(nx * 4, ny * 4) * 1.0 +
|
|||
|
|
elevNoise(nx * 8, ny * 8) * 0.5 +
|
|||
|
|
elevNoise(nx * 16, ny * 16) * 0.25) / 1.75
|
|||
|
|
const eNorm = (e + 1) / 2 // -1..1 → 0..1
|
|||
|
|
|
|||
|
|
const m = moistNoise(nx * 6 + 10, ny * 6 + 10)
|
|||
|
|
const mNorm = (m + 1) / 2
|
|||
|
|
|
|||
|
|
tiles[y * size + x] = classify(eNorm, mNorm)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return tiles
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** Find a walkable spawn tile near the world center */
|
|||
|
|
export function findSpawn(tiles: number[]): { tileX: number; tileY: number } {
|
|||
|
|
const center = Math.floor(WORLD_TILES / 2)
|
|||
|
|
const walkable = new Set([TileType.GRASS, TileType.DARK_GRASS, TileType.SAND])
|
|||
|
|
|
|||
|
|
for (let r = 0; r < center; r++) {
|
|||
|
|
for (let dy = -r; dy <= r; dy++) {
|
|||
|
|
for (let dx = -r; dx <= r; dx++) {
|
|||
|
|
if (Math.abs(dx) !== r && Math.abs(dy) !== r) continue
|
|||
|
|
const tx = center + dx
|
|||
|
|
const ty = center + dy
|
|||
|
|
if (tx < 0 || ty < 0 || tx >= WORLD_TILES || ty >= WORLD_TILES) continue
|
|||
|
|
if (walkable.has(tiles[ty * WORLD_TILES + tx])) {
|
|||
|
|
return { tileX: tx, tileY: ty }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return { tileX: center, tileY: center }
|
|||
|
|
}
|