Source code for simulation_framework.src.world.generator

from __future__ import annotations

import random
from typing import List, Optional

import numpy as np
from opensimplex import OpenSimplex

from .terrain import TerrainType
from .tile import ResourceDeposit, Tile


[docs] class WorldGenerator: def __init__(self, seed: Optional[int] = None): self.seed = seed if seed is not None else random.randint(0, 1000000) self.noise = OpenSimplex(seed=self.seed) random.seed(self.seed) np.random.seed(self.seed)
[docs] def generate_perlin_noise( self, width: int, height: int, octaves: int = 4, scale: float = 0.05 ) -> np.ndarray: noise_array = np.zeros((height, width)) for y in range(height): for x in range(width): value = 0 amplitude = 1 frequency = scale for _ in range(octaves): value += self.noise.noise2(x * frequency, y * frequency) * amplitude amplitude *= 0.5 frequency *= 2 noise_array[y, x] = value noise_array = (noise_array - noise_array.min()) / ( noise_array.max() - noise_array.min() ) return noise_array
[docs] def map_noise_to_terrain(self, noise_array: np.ndarray) -> List[List[Tile]]: height, width = noise_array.shape tiles = [] moisture_noise = self.generate_perlin_noise( width, height, octaves=3, scale=0.03 ) temperature_noise = self.generate_perlin_noise( width, height, octaves=2, scale=0.02 ) for y in range(height): row = [] for x in range(width): elevation = noise_array[y, x] moisture = moisture_noise[y, x] temperature = temperature_noise[y, x] terrain_type = self._determine_terrain(elevation, moisture, temperature) tile = Tile(x, y, terrain_type) self._add_resources_to_tile(tile) row.append(tile) tiles.append(row) return tiles
def _determine_terrain( self, elevation: float, moisture: float, temperature: float ) -> TerrainType: if elevation < 0.3: return TerrainType.WATER elif elevation > 0.8: return TerrainType.MOUNTAIN elif temperature > 0.6 and moisture < 0.4: return TerrainType.DESERT elif moisture > 0.5 and elevation < 0.7: return TerrainType.FOREST else: return TerrainType.GRASS def _add_resources_to_tile(self, tile: Tile) -> None: """Add resources to tile with staggered respawn times""" if tile.terrain_type == TerrainType.WATER: if random.random() < 0.3: resource = ResourceDeposit("fish", 20) # Stagger initial spawn: some resources "already respawning" resource.last_harvested = -random.randint(0, 80) tile.add_resource(resource) elif tile.terrain_type == TerrainType.FOREST: if random.random() < 0.5: # Balanced probability for realistic distribution resource = ResourceDeposit("wood", 30) resource.last_harvested = -random.randint(0, 80) tile.add_resource(resource) if random.random() < 0.3: resource = ResourceDeposit("berries", 10) resource.last_harvested = -random.randint(0, 80) tile.add_resource(resource) elif tile.terrain_type == TerrainType.MOUNTAIN: if random.random() < 0.4: # Balanced probability for realistic distribution resource = ResourceDeposit("stone", 50) resource.last_harvested = -random.randint(0, 80) tile.add_resource(resource) if random.random() < 0.2: resource = ResourceDeposit("iron_ore", 15) resource.last_harvested = -random.randint(0, 80) tile.add_resource(resource) if random.random() < 0.05: resource = ResourceDeposit("gold_ore", 5) resource.last_harvested = -random.randint(0, 80) tile.add_resource(resource) elif tile.terrain_type == TerrainType.GRASS: if random.random() < 0.2: # Balanced probability for realistic distribution resource = ResourceDeposit("herbs", 15) resource.last_harvested = -random.randint(0, 80) tile.add_resource(resource)
[docs] def generate_world( self, width: int, height: int, add_spawn_zones: bool = True ) -> List[List[Tile]]: elevation_noise = self.generate_perlin_noise(width, height) tiles = self.map_noise_to_terrain(elevation_noise) if add_spawn_zones: self._add_spawn_zones(tiles) return tiles
def _add_spawn_zones(self, tiles: List[List[Tile]]) -> None: height = len(tiles) width = len(tiles[0]) if height > 0 else 0 if width == 0 or height == 0: return safe_spots = [] for y in range(height): for x in range(width): if tiles[y][x].can_pass(): safe_spots.append((x, y)) if not safe_spots: return num_agent_spawns = min(5, len(safe_spots) // 10) agent_spawns = random.sample(safe_spots, min(num_agent_spawns, len(safe_spots))) for x, y in agent_spawns: tiles[y][x].add_spawn_zone("agent_spawn") remaining_spots = [s for s in safe_spots if s not in agent_spawns] if remaining_spots: num_npc_spawns = min(10, len(remaining_spots) // 5) npc_spawns = random.sample( remaining_spots, min(num_npc_spawns, len(remaining_spots)) ) for x, y in npc_spawns: tiles[y][x].add_spawn_zone("npc_spawn")