instructed/tilemap/examples/biome_demo.cpp
szdytom b6656f5023
feat: Add biome-based terrain generation and Perlin noise implementation
- Introduced a new biome system with various biome types and properties.
- Implemented terrain generation using Perlin noise to create diverse landscapes.
- Added examples for basic tilemap generation and biome analysis.
- Created helper functions for displaying tiles and biomes in the console.
- Enhanced the TileMap class to support chunk-based tile management.
- Developed a PerlinNoise class for generating smooth noise patterns.
- Updated generation configuration to include climate parameters for biomes.
- Implemented error handling for out-of-bounds access in TileMap.

Signed-off-by: szdytom <szdytom@qq.com>
2025-08-01 14:28:36 +08:00

227 lines
6.9 KiB
C++

#include "biome.h"
#include "generation.h"
#include "tile.h"
#include "tilemap.h"
#include <iomanip>
#include <iostream>
#include <map>
// Helper function to get tile character for display
char get_tile_char(const istd::Tile &tile) {
switch (tile.type) {
case 0:
return ' '; // empty
case 1:
return '^'; // mountain
case 2:
return 'T'; // wood
case 3:
return '.'; // sand
case 4:
return '~'; // water
default:
return '?';
}
}
// Helper function to get biome character for display
char get_biome_char(istd::BiomeType biome) {
switch (biome) {
case istd::BiomeType::Desert:
return 'D';
case istd::BiomeType::Savanna:
return 'S';
case istd::BiomeType::TropicalRainforest:
return 'R';
case istd::BiomeType::Grassland:
return 'G';
case istd::BiomeType::DeciduousForest:
return 'F';
case istd::BiomeType::TemperateRainforest:
return 'M';
case istd::BiomeType::Tundra:
return 'U';
case istd::BiomeType::Taiga:
return 'A';
case istd::BiomeType::ColdRainforest:
return 'C';
default:
return '?';
}
}
// Function to analyze biome distribution in a chunk
void analyze_chunk_biomes(
const istd::TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y
) {
std::cout << "Analyzing chunk (" << static_cast<int>(chunk_x) << ","
<< static_cast<int>(chunk_y)
<< ") biome distribution:" << std::endl;
// Create a temporary generator to get climate data
istd::GenerationConfig config;
config.seed = 12345;
istd::TerrainGenerator temp_generator(config);
// Count biomes in each sub-chunk
std::map<istd::BiomeType, int> biome_counts;
for (std::uint8_t sub_y = 0; sub_y < 4; ++sub_y) {
for (std::uint8_t sub_x = 0; sub_x < 4; ++sub_x) {
// Calculate global position for this sub-chunk's center
double global_x = static_cast<double>(
chunk_x * istd::Chunk::size + sub_x * 16 + 8
);
double global_y = static_cast<double>(
chunk_y * istd::Chunk::size + sub_y * 16 + 8
);
// Get climate values (we need to recreate this logic since the
// generator's method is private)
istd::PerlinNoise temp_noise(config.seed + 1000);
istd::PerlinNoise humidity_noise(config.seed + 2000);
double temperature = temp_noise.octave_noise(
global_x * config.temperature_scale,
global_y * config.temperature_scale, 3, 0.5
);
double humidity = humidity_noise.octave_noise(
global_x * config.humidity_scale,
global_y * config.humidity_scale, 3, 0.5
);
istd::BiomeType biome
= istd::determine_biome(temperature, humidity);
biome_counts[biome]++;
std::cout << get_biome_char(biome);
if (sub_x == 3) {
std::cout << std::endl;
}
}
}
std::cout << std::endl << "Biome legend:" << std::endl;
std::cout << "D=Desert, S=Savanna, R=TropicalRainforest, G=Grassland"
<< std::endl;
std::cout << "F=DeciduousForest, M=TemperateRainforest, U=Tundra, A=Taiga, "
"C=ColdRainforest"
<< std::endl;
std::cout << std::endl << "Sub-chunk counts:" << std::endl;
for (const auto &[biome, count] : biome_counts) {
const auto &props = istd::get_biome_properties(biome);
std::cout << "- " << props.name << ": " << count << " sub-chunks"
<< std::endl;
}
std::cout << std::endl;
}
// Function to show terrain sample from a specific sub-chunk
void show_subchunk_terrain(
const istd::TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y,
std::uint8_t sub_x, std::uint8_t sub_y
) {
std::cout << "Terrain sample from chunk(" << static_cast<int>(chunk_x)
<< "," << static_cast<int>(chunk_y) << ") sub-chunk("
<< static_cast<int>(sub_x) << "," << static_cast<int>(sub_y)
<< "):" << std::endl;
auto [start_x, start_y]
= istd::subchunk_to_tile_start(istd::SubChunkPos(sub_x, sub_y));
// Show 8x8 sample from the center of the sub-chunk
std::uint8_t sample_start_x = start_x + 4;
std::uint8_t sample_start_y = start_y + 4;
for (std::uint8_t y = sample_start_y; y < sample_start_y + 8; ++y) {
for (std::uint8_t x = sample_start_x; x < sample_start_x + 8; ++x) {
istd::TilePos pos{chunk_x, chunk_y, x, y};
istd::Tile tile = tilemap.get_tile(pos);
std::cout << get_tile_char(tile);
}
std::cout << std::endl;
}
std::cout << std::endl;
}
int main() {
try {
std::cout << "=== Biome-Based Terrain Generation Demo ===" << std::endl;
// Create a 6x6 chunk tilemap
std::uint8_t map_size = 6;
istd::TileMap tilemap(map_size);
// Configure generation with biome system
istd::GenerationConfig config;
config.seed = 42;
config.temperature_scale = 0.003; // Larger climate features
config.humidity_scale = 0.004;
std::cout << "Generating " << static_cast<int>(map_size) << "x"
<< static_cast<int>(map_size)
<< " chunk map with biome system..." << std::endl;
// Generate the map using the new biome-based system
istd::map_generate(tilemap, config);
std::cout << "Generation complete!" << std::endl << std::endl;
// Analyze biome distribution in a few chunks
std::cout << "=== Biome Analysis ===" << std::endl;
analyze_chunk_biomes(tilemap, 1, 1);
analyze_chunk_biomes(tilemap, 4, 4);
// Show terrain samples from different sub-chunks
std::cout << "=== Terrain Samples ===" << std::endl;
std::cout
<< "Legend: ' '=empty, '^'=mountain, 'T'=wood, '.'=sand, '~'=water"
<< std::endl
<< std::endl;
show_subchunk_terrain(tilemap, 1, 1, 0, 0); // Top-left sub-chunk
show_subchunk_terrain(tilemap, 1, 1, 3, 3); // Bottom-right sub-chunk
show_subchunk_terrain(tilemap, 4, 4, 1, 2); // Different chunk
// Show overall statistics
std::cout << "=== Map Statistics ===" << std::endl;
std::cout << "- Map size: " << static_cast<int>(map_size) << "x"
<< static_cast<int>(map_size) << " chunks" << std::endl;
std::cout << "- Sub-chunks per chunk: 4x4 (16 total)" << std::endl;
std::cout << "- Tiles per sub-chunk: 16x16 (256 total)" << std::endl;
std::cout << "- Total sub-chunks: "
<< static_cast<int>(map_size) * static_cast<int>(map_size)
* 16
<< std::endl;
std::cout << "- Total tiles: "
<< static_cast<int>(map_size) * static_cast<int>(map_size)
* static_cast<int>(istd::Chunk::size)
* static_cast<int>(istd::Chunk::size)
<< std::endl;
// Test coordinate conversion functions
std::cout << std::endl << "=== Coordinate System Test ===" << std::endl;
// Test tile to sub-chunk conversion
istd::SubChunkPos sub_pos = istd::tile_to_subchunk(25, 40);
std::cout << "Tile (25,40) is in sub-chunk ("
<< static_cast<int>(sub_pos.sub_x) << ","
<< static_cast<int>(sub_pos.sub_y) << ")" << std::endl;
// Test sub-chunk to tile conversion
auto [tile_start_x, tile_start_y]
= istd::subchunk_to_tile_start(istd::SubChunkPos(2, 1));
std::cout << "Sub-chunk (2,1) starts at tile ("
<< static_cast<int>(tile_start_x) << ","
<< static_cast<int>(tile_start_y) << ")" << std::endl;
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}