diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt
index bc5ffa4cc4afdaf382e2b626844667fb0fe96e3e..b984c4dbacfd0e25002201701f8d332c0df7fcac 100644 (file)
package ru.deadsoftware.cavedroid.game.world
-import com.badlogic.gdx.utils.TimeUtils
-import ru.deadsoftware.cavedroid.game.GameItems
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.world.Biome
+import ru.deadsoftware.cavedroid.game.model.world.generator.WorldGeneratorConfig
import kotlin.math.abs
+import kotlin.math.max
import kotlin.random.Random
-object GameWorldGenerator {
+class GameWorldGenerator(
+ private val config: WorldGeneratorConfig,
+ private val gameItemsHolder: GameItemsHolder,
+) {
- private const val BIOME_MIN_SIZE = 64
+ private val random = Random(config.seed)
- private enum class Biome {
- PLAINS,
- DESERT
- }
+ private val foreMap by lazy { Array(config.width) { Array(config.height) { gameItemsHolder.fallbackBlock } } }
+ private val backMap by lazy { Array(config.width) { Array(config.height) { gameItemsHolder.fallbackBlock } } }
+
+ private val heights by lazy { generateHeights() }
+ private val biomesMap by lazy { generateBiomes() }
+
+ private fun generateHeights(): IntArray {
+ val surfaceHeightRange = config.minSurfaceHeight .. config.maxSurfaceHeight
+ val result = IntArray(config.width)
+
+ result[0] = (config.minSurfaceHeight + config.maxSurfaceHeight) / 2
- private fun generateHeights(width: Int, min: Int, max: Int, random: Random) = IntArray(width).apply {
- set(0, (min + max) / 2)
- for (x in 1 until width) {
- val previous = get(x - 1)
+ for (x in 1 ..< config.width) {
+ val previous = result[x - 1]
var d = random.nextInt(-5, 6).let { if (it !in -4..4) it / abs(it) else 0 }
- if (previous + d !in min..max) { d = -d }
- if (lastIndex - x < abs(get(0) - previous) * 3) {
- d = get(0).compareTo(previous).let { if (it != 0) it / abs(it) else 0 }
+ if (previous + d !in surfaceHeightRange) { d = -d }
+
+ if (result.lastIndex - x < abs(result[0] - previous) * 3) {
+ d = result[0].compareTo(previous).let { if (it != 0) it / abs(it) else 0 }
}
- set(x, get(x - 1) + d)
+ result[x] = result[x - 1] + d
}
+
+ return result
}
- private fun generateBiomes(width: Int, random: Random) = buildMap<Int, Biome> {
+ private fun generateBiomes(): Map<Int, Biome> {
val xSequence = sequence {
var lastX = 0
var count = 0
- while (lastX < width - BIOME_MIN_SIZE - 1) {
+ while (lastX < config.width - config.minBiomeSize - 1) {
yield(lastX)
- lastX = random.nextInt(lastX + BIOME_MIN_SIZE, width)
+ lastX = random.nextInt(lastX + config.minBiomeSize, config.width)
count++
}
}
- return xSequence.associateWith { Biome.values()[random.nextInt(Biome.values().size)] }
+ return xSequence.associateWith { config.biomes.random(random) }
}
- private fun plainsBiome(
- foreMap: Array<IntArray>,
- backMap: Array<IntArray>,
- width: Int,
- height: Int,
- x: Int,
- xHeight: Int,
- random: Random,
- ) {
- foreMap[x][xHeight] = GameItems.getBlockId("grass")
- foreMap[x][height - 1] = GameItems.getBlockId("bedrock")
- backMap[x][xHeight] = GameItems.getBlockId("grass")
- backMap[x][height - 1] = GameItems.getBlockId("bedrock")
-
- for (y in xHeight + 1 until height - 1) {
+ private fun plainsBiome(x: Int) {
+ assert(x in 0 ..< config.width) { "x not in range of world width" }
+
+ val surfaceHeight = heights[x]
+
+ val grass = gameItemsHolder.getBlock("grass")
+ val bedrock = gameItemsHolder.getBlock("bedrock")
+ val dirt = gameItemsHolder.getBlock("dirt")
+ val stone = gameItemsHolder.getBlock("stone")
+
+ foreMap[x][surfaceHeight] = grass
+ foreMap[x][config.height - 1] = bedrock
+ backMap[x][surfaceHeight] = grass
+ backMap[x][config.height - 1] = bedrock
+
+ for (y in surfaceHeight + 1 ..< config.height - 1) {
foreMap[x][y] = when {
- y < xHeight + random.nextInt(5, 8) -> GameItems.getBlockId("dirt")
- else -> GameItems.getBlockId("stone")
+ y < surfaceHeight + random.nextInt(5, 8) -> dirt
+ else -> stone
}
backMap[x][y] = foreMap[x][y]
}
}
- private fun desertBiome(
- foreMap: Array<IntArray>,
- backMap: Array<IntArray>,
- width: Int,
- height: Int,
- x: Int,
- xHeight: Int,
- random: Random,
- ) {
- foreMap[x][xHeight] = GameItems.getBlockId("sand")
- foreMap[x][height - 1] = GameItems.getBlockId("bedrock")
- backMap[x][xHeight] = GameItems.getBlockId("sand")
- backMap[x][height - 1] = GameItems.getBlockId("bedrock")
-
- for (y in xHeight + 1 until height - 1) {
+ private fun desertBiome(x: Int) {
+ assert(x in 0 ..< config.width) { "x not in range of world width" }
+
+ val surfaceHeight = heights[x]
+
+ val sand = gameItemsHolder.getBlock("sand")
+ val bedrock = gameItemsHolder.getBlock("bedrock")
+ val sandstone = gameItemsHolder.getBlock("sandstone")
+ val stone = gameItemsHolder.getBlock("stone")
+
+
+ foreMap[x][surfaceHeight] = sand
+ foreMap[x][config.height - 1] = bedrock
+ backMap[x][surfaceHeight] = sand
+ backMap[x][config.height - 1] = bedrock
+
+ for (y in surfaceHeight + 1 ..< config.height - 1) {
foreMap[x][y] = when {
- y < xHeight + random.nextInt(5, 8) -> GameItems.getBlockId("sand")
- else -> GameItems.getBlockId("stone")
+ y < surfaceHeight + random.nextInt(5, 8) -> sand
+ y < surfaceHeight + random.nextInt(0, 2) -> sandstone
+ else -> stone
}
backMap[x][y] = foreMap[x][y]
}
+
+ if (surfaceHeight < config.seaLevel && random.nextInt(100) < 5) {
+ generateCactus(x)
+ }
+ }
+
+ private fun fillWater() {
+ val water = gameItemsHolder.getBlock("water")
+
+ for (x in 0 ..< config.width) {
+ for (y in config.seaLevel ..< config.height) {
+ if (foreMap[x][y] != gameItemsHolder.fallbackBlock) {
+ break
+ }
+
+ foreMap[x][y] = water
+ }
+ }
+ }
+
+ private fun generateCactus(x: Int) {
+ val cactus = gameItemsHolder.getBlock("cactus")
+ val cactusHeight = random.nextInt(3)
+ val h = heights[x] - 1
+
+ for (y in h downTo max(0, h - cactusHeight)) {
+ foreMap[x][y] = cactus
+ }
}
/**
- * Generates world of given width and height with given seed
- * @param width world width
- * @param height world height
- * @param seed seed for random number generator
- * @return pair of foreground and background layers
+ * Generate world
*/
- fun generate(width: Int, height: Int, seed: Long = TimeUtils.millis()): Pair<Array<IntArray>, Array<IntArray>> {
- val random = Random(seed)
- val foreMap = Array(width) { IntArray(height) }
- val backMap = Array(width) { IntArray(width) }
- val heightsMap = generateHeights(width, height / 2, height * 3 / 4, random)
- val biomesMap = generateBiomes(width, random)
-
+ fun generate(): Pair<Array<Array<Block>>, Array<Array<Block>>> {
var biome = Biome.PLAINS
- for (x in 0 until width) {
- val xHeight = heightsMap[x]
+ for (x in 0 until config.width) {
biome = biomesMap[x] ?: biome
when (biome) {
- Biome.PLAINS -> plainsBiome(foreMap, backMap, width, height, x, xHeight, random)
- Biome.DESERT -> desertBiome(foreMap, backMap, width, height, x, xHeight, random)
+ Biome.PLAINS -> plainsBiome(x)
+ Biome.DESERT -> desertBiome(x)
}
-
}
+
+ fillWater()
+
return Pair(foreMap, backMap)
}