DEADSOFTWARE

Refactor world generator
[cavedroid.git] / core / src / ru / deadsoftware / cavedroid / game / world / GameWorldGenerator.kt
1 package ru.deadsoftware.cavedroid.game.world
3 import ru.deadsoftware.cavedroid.game.GameItems
4 import ru.deadsoftware.cavedroid.game.model.world.Biome
5 import ru.deadsoftware.cavedroid.game.model.world.generator.WorldGeneratorConfig
6 import kotlin.math.abs
7 import kotlin.math.max
8 import kotlin.random.Random
10 class GameWorldGenerator(
11 private val config: WorldGeneratorConfig,
12 ) {
14 private val random = Random(config.seed)
16 private val foreMap by lazy { Array(config.width) { IntArray(config.height) } }
17 private val backMap by lazy { Array(config.width) { IntArray(config.height) } }
19 private val heights by lazy { generateHeights() }
20 private val biomesMap by lazy { generateBiomes() }
22 private fun generateHeights(): IntArray {
23 val surfaceHeightRange = config.minSurfaceHeight .. config.maxSurfaceHeight
24 val result = IntArray(config.width)
26 result[0] = (config.minSurfaceHeight + config.maxSurfaceHeight) / 2
28 for (x in 1 ..< config.width) {
29 val previous = result[x - 1]
30 var d = random.nextInt(-5, 6).let { if (it !in -4..4) it / abs(it) else 0 }
32 if (previous + d !in surfaceHeightRange) { d = -d }
34 if (result.lastIndex - x < abs(result[0] - previous) * 3) {
35 d = result[0].compareTo(previous).let { if (it != 0) it / abs(it) else 0 }
36 }
38 result[x] = result[x - 1] + d
39 }
41 return result
42 }
44 private fun generateBiomes(): Map<Int, Biome> {
45 val xSequence = sequence {
46 var lastX = 0
47 var count = 0
49 while (lastX < config.width - config.minBiomeSize - 1) {
50 yield(lastX)
52 lastX = random.nextInt(lastX + config.minBiomeSize, config.width)
53 count++
54 }
55 }
57 return xSequence.associateWith { config.biomes.random(random) }
58 }
60 private fun plainsBiome(x: Int) {
61 assert(x in 0 ..< config.width) { "x not in range of world width" }
63 val surfaceHeight = heights[x]
65 val grassId = GameItems.getBlockId("grass")
66 val bedrockId = GameItems.getBlockId("bedrock")
67 val dirtId = GameItems.getBlockId("dirt")
68 val stoneId = GameItems.getBlockId("stone")
70 foreMap[x][surfaceHeight] = grassId
71 foreMap[x][config.height - 1] = bedrockId
72 backMap[x][surfaceHeight] = grassId
73 backMap[x][config.height - 1] = bedrockId
75 for (y in surfaceHeight + 1 ..< config.height - 1) {
76 foreMap[x][y] = when {
77 y < surfaceHeight + random.nextInt(5, 8) -> dirtId
78 else -> stoneId
79 }
80 backMap[x][y] = foreMap[x][y]
81 }
82 }
84 private fun desertBiome(x: Int) {
85 assert(x in 0 ..< config.width) { "x not in range of world width" }
87 val surfaceHeight = heights[x]
89 val sandId = GameItems.getBlockId("sand")
90 val bedrockId = GameItems.getBlockId("bedrock")
91 val sandstoneId = GameItems.getBlockId("sandstone")
92 val stoneId = GameItems.getBlockId("stone")
95 foreMap[x][surfaceHeight] = sandId
96 foreMap[x][config.height - 1] = bedrockId
97 backMap[x][surfaceHeight] = sandId
98 backMap[x][config.height - 1] = bedrockId
100 for (y in surfaceHeight + 1 ..< config.height - 1) {
101 foreMap[x][y] = when {
102 y < surfaceHeight + random.nextInt(5, 8) -> sandId
103 y < surfaceHeight + random.nextInt(0, 2) -> sandstoneId
104 else -> stoneId
106 backMap[x][y] = foreMap[x][y]
109 if (surfaceHeight < config.seaLevel && random.nextInt(100) < 5) {
110 generateCactus(x)
114 private fun fillWater() {
115 val waterId = GameItems.getBlockId("water")
117 for (x in 0 ..< config.width) {
118 for (y in config.seaLevel ..< config.height) {
119 if (foreMap[x][y] != 0) {
120 break
123 foreMap[x][y] = waterId
128 private fun generateCactus(x: Int) {
129 val cactusId = GameItems.getBlockId("cactus")
130 val cactusHeight = random.nextInt(5)
131 val h = heights[x] - 1
133 for (y in h downTo max(0, h - cactusHeight)) {
134 foreMap[x][y] = cactusId
138 /**
139 * Generate world
140 */
141 fun generate(): Pair<Array<IntArray>, Array<IntArray>> {
142 var biome = Biome.PLAINS
144 for (x in 0 until config.width) {
145 val xHeight = heights[x]
146 biome = biomesMap[x] ?: biome
148 when (biome) {
149 Biome.PLAINS -> plainsBiome(x)
150 Biome.DESERT -> desertBiome(x)
154 fillWater()
156 return Pair(foreMap, backMap)