X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=core%2Fsrc%2Fru%2Fdeadsoftware%2Fcavedroid%2Fgame%2Fworld%2FGameWorldGenerator.kt;h=b984c4dbacfd0e25002201701f8d332c0df7fcac;hb=1c004c0ce7e183e773b5b486295c25e39732e899;hp=bf37f454c3ad36851a40062e5d74b2ffad7cdfde;hpb=1f0038c3cd76a2df8cf74aafcea9aea656d440a4;p=cavedroid.git diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt index bf37f45..b984c4d 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt +++ b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt @@ -1,135 +1,158 @@ 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) - 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) + result[0] = (config.minSurfaceHeight + config.maxSurfaceHeight) / 2 + + 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 { + private fun generateBiomes(): Map { 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, - backMap: Array, - 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, - backMap: Array, - 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(foreMap: Array, width: Int, height: Int, waterLevel: Int) { - for (x in 0 until width) { - for (y in waterLevel until height) { - if (foreMap[x][y] != 0) { + 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] = GameItems.getBlockId("water") + 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> { - val random = Random(seed) - val foreMap = Array(width) { IntArray(height) } - val backMap = Array(width) { IntArray(width) } - val heightsMap = generateHeights(width, height / 4, height * 3 / 4, random) - val biomesMap = generateBiomes(width, random) - + fun generate(): Pair>, Array>> { 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(foreMap, width, height, height / 2) + fillWater() return Pair(foreMap, backMap) }