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 }
|
||
}
|