Merge pull request #358 from ninjatao/map_gen
Add celluar automica map generator.
This commit is contained in:
commit
3ce89e1928
5 changed files with 173 additions and 16 deletions
|
@ -57,7 +57,7 @@
|
||||||
{
|
{
|
||||||
name:"Fish",
|
name:"Fish",
|
||||||
resourceType:"Bonus",
|
resourceType:"Bonus",
|
||||||
terrainsCanBeFoundOn:["Coast"],
|
terrainsCanBeFoundOn:["Coast", "Lakes"],
|
||||||
food:1,
|
food:1,
|
||||||
improvement:"Fishing Boats",
|
improvement:"Fishing Boats",
|
||||||
improvementStats:{food:1},
|
improvementStats:{food:1},
|
||||||
|
|
|
@ -74,8 +74,12 @@ class HexMath {
|
||||||
return hexesToReturn
|
return hexesToReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDistance(origin: Vector2, destination: Vector2): Int { // Yes, this is a dumb implementation. But I can't be arsed to think of a better one right now, other stuff to do.
|
fun getDistance(origin: Vector2, destination: Vector2): Int {
|
||||||
return max(abs(origin.x-destination.x),abs(origin.y-destination.y) ).toInt()
|
val relative_x = origin.x-destination.x
|
||||||
|
val relative_y = origin.y-destination.y
|
||||||
|
if (relative_x * relative_y >= 0)
|
||||||
|
return max(abs(relative_x),abs(relative_y)).toInt()
|
||||||
|
else
|
||||||
|
return (abs(relative_x) + abs(relative_y)).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,148 @@ import com.unciv.ui.utils.getRandom
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
enum class MapType {
|
||||||
|
Perlin,
|
||||||
|
Default,
|
||||||
|
Pangaea,
|
||||||
|
}
|
||||||
|
|
||||||
|
class CelluarAutomataRandomMapGenerator(): SeedRandomMapGenerator() {
|
||||||
|
var landProb = 0.55f
|
||||||
|
var numSmooth = 4
|
||||||
|
var mapType = MapType.Default
|
||||||
|
|
||||||
|
constructor(type: MapType): this() {
|
||||||
|
mapType = type
|
||||||
|
if (mapType < MapType.Default) {
|
||||||
|
mapType = MapType.Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generateMap(distance: Int): HashMap<String, TileInfo> {
|
||||||
|
val mapVectors = HexMath().getVectorsInDistance(Vector2.Zero, distance)
|
||||||
|
val landscape = HashMap<Vector2, TerrainType>()
|
||||||
|
|
||||||
|
//init
|
||||||
|
for (vector in mapVectors) {
|
||||||
|
landscape[vector] = generateInitTerrain(vector, distance)
|
||||||
|
}
|
||||||
|
|
||||||
|
//smooth
|
||||||
|
for (loop in 0..numSmooth) {
|
||||||
|
for (vector in mapVectors) {
|
||||||
|
if (HexMath().getDistance(Vector2.Zero, vector) < distance) {
|
||||||
|
val neighborLands = HexMath().getAdjacentVectors(vector).count {landscape[it] == TerrainType.Land}
|
||||||
|
if (landscape[vector] == TerrainType.Land) {
|
||||||
|
if (neighborLands < 3)
|
||||||
|
landscape[vector] = TerrainType.Water
|
||||||
|
} else {
|
||||||
|
if (neighborLands > 3)
|
||||||
|
landscape[vector] = TerrainType.Land
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
landscape[vector] = TerrainType.Water
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val map = HashMap<Vector2, TileInfo>()
|
||||||
|
for (vector in mapVectors)
|
||||||
|
map[vector] = generateTile(vector,landscape[vector]!!)
|
||||||
|
|
||||||
|
divideIntoAreas(6, 0f, map)
|
||||||
|
|
||||||
|
val mapToReturn = HashMap<String, TileInfo>()
|
||||||
|
for(tile in map) {
|
||||||
|
tile.value.setTransients()
|
||||||
|
mapToReturn[tile.key.toString()] = tile.value
|
||||||
|
}
|
||||||
|
|
||||||
|
setWaterTiles(mapToReturn)
|
||||||
|
|
||||||
|
for(tile in mapToReturn.values) randomizeTile(tile,mapToReturn)
|
||||||
|
|
||||||
|
return mapToReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateInitTerrain(vector: Vector2, distance: Int): TerrainType {
|
||||||
|
var type: TerrainType
|
||||||
|
if (mapType == MapType.Pangaea) {
|
||||||
|
val distanceFactor = (HexMath().getDistance(Vector2.Zero, vector) * 1.8 / distance).toFloat()
|
||||||
|
type = if (Random().nextDouble() < landProb.pow(distanceFactor)) TerrainType.Land else TerrainType.Water
|
||||||
|
} else { //default
|
||||||
|
if (HexMath().getDistance(Vector2.Zero, vector) > 0.9f * distance)
|
||||||
|
type = if (Random().nextDouble() < 0.1) TerrainType.Land else TerrainType.Water
|
||||||
|
else if (HexMath().getDistance(Vector2.Zero, vector) > 0.85f * distance)
|
||||||
|
type = if (Random().nextDouble() < 0.2) TerrainType.Land else TerrainType.Water
|
||||||
|
else
|
||||||
|
type = if (Random().nextDouble() < landProb) TerrainType.Land else TerrainType.Water
|
||||||
|
}
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateTile(vector: Vector2, type: TerrainType): TileInfo {
|
||||||
|
val tile=TileInfo()
|
||||||
|
tile.position=vector
|
||||||
|
if (type == TerrainType.Land)
|
||||||
|
tile.baseTerrain = ""
|
||||||
|
else
|
||||||
|
tile.baseTerrain = "Ocean"
|
||||||
|
return tile
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setWaterTiles(map: HashMap<String, TileInfo>) {
|
||||||
|
//define lakes
|
||||||
|
var waterTiles = map.values.filter { it.isWater() }.map { it.position }
|
||||||
|
var tilesInArea = ArrayList<Vector2>()
|
||||||
|
var tilesToCheck = ArrayList<Vector2>()
|
||||||
|
while (waterTiles.isNotEmpty()) {
|
||||||
|
val tile = waterTiles.getRandom()
|
||||||
|
tilesInArea.add(tile)
|
||||||
|
tilesToCheck.add(tile)
|
||||||
|
waterTiles -= tile
|
||||||
|
|
||||||
|
while (tilesToCheck.isNotEmpty()) {
|
||||||
|
val tileChecking = tilesToCheck.getRandom()
|
||||||
|
for (vector in HexMath().getVectorsAtDistance(tileChecking,1).filter { !tilesInArea.contains(it) and waterTiles.contains(it) }) {
|
||||||
|
tilesInArea.add(vector)
|
||||||
|
tilesToCheck.add(vector)
|
||||||
|
waterTiles -= vector
|
||||||
|
}
|
||||||
|
tilesToCheck.remove(tileChecking)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tilesInArea.size <= 10) {
|
||||||
|
for (vector in tilesInArea) {
|
||||||
|
map[vector.toString()]!!.baseTerrain = "Lakes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tilesInArea.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Coasts
|
||||||
|
for (tile in map.values.filter { it.baseTerrain == "Ocean" }) {
|
||||||
|
if (HexMath().getVectorsInDistance(tile.position,2).any { hasLandTile(map,it) }) {
|
||||||
|
tile.baseTerrain = "Coast"
|
||||||
|
tile.setTransients()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun randomizeTile(tileInfo: TileInfo, map: HashMap<String, TileInfo>){
|
||||||
|
if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f){
|
||||||
|
tileInfo.baseTerrain = "Mountain"
|
||||||
|
tileInfo.setTransients()
|
||||||
|
}
|
||||||
|
addRandomTerrainFeature(tileInfo)
|
||||||
|
addRandomResourceToTile(tileInfo)
|
||||||
|
maybeAddAncientRuins(tileInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class PerlinNoiseRandomMapGenerator:SeedRandomMapGenerator(){
|
class PerlinNoiseRandomMapGenerator:SeedRandomMapGenerator(){
|
||||||
override fun generateMap(distance: Int): HashMap<String, TileInfo> {
|
override fun generateMap(distance: Int): HashMap<String, TileInfo> {
|
||||||
|
@ -276,11 +418,11 @@ open class RandomMapGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun hasLandTile(map: HashMap<String, TileInfo>, vector: Vector2): Boolean {
|
fun hasLandTile(map: HashMap<String, TileInfo>, vector: Vector2): Boolean {
|
||||||
return map.containsKey(vector.toString()) && map[vector.toString()]!!.getBaseTerrain().type == TerrainType.Land
|
return map.containsKey(vector.toString()) && map[vector.toString()]!!.getBaseTerrain().type == TerrainType.Land
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setWaterTiles(map: HashMap<String, TileInfo>) {
|
open fun setWaterTiles(map: HashMap<String, TileInfo>) {
|
||||||
for (tile in map.values.filter { it.baseTerrain == "Ocean" }) {
|
for (tile in map.values.filter { it.baseTerrain == "Ocean" }) {
|
||||||
if (HexMath().getVectorsInDistance(tile.position,2).any { hasLandTile(map,it) }) {
|
if (HexMath().getVectorsInDistance(tile.position,2).any { hasLandTile(map,it) }) {
|
||||||
tile.baseTerrain = "Coast"
|
tile.baseTerrain = "Coast"
|
||||||
|
@ -289,7 +431,7 @@ open class RandomMapGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun randomizeTile(tileInfo: TileInfo, map: HashMap<String, TileInfo>){
|
open fun randomizeTile(tileInfo: TileInfo, map: HashMap<String, TileInfo>){
|
||||||
if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f){
|
if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f){
|
||||||
tileInfo.baseTerrain = "Mountain"
|
tileInfo.baseTerrain = "Mountain"
|
||||||
tileInfo.setTransients()
|
tileInfo.setTransients()
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.unciv.logic.map
|
||||||
import com.badlogic.gdx.math.Vector2
|
import com.badlogic.gdx.math.Vector2
|
||||||
import com.unciv.logic.GameInfo
|
import com.unciv.logic.GameInfo
|
||||||
import com.unciv.logic.HexMath
|
import com.unciv.logic.HexMath
|
||||||
|
import com.unciv.logic.map.MapType
|
||||||
import com.unciv.logic.civilization.CivilizationInfo
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
import com.unciv.models.gamebasics.GameBasics
|
import com.unciv.models.gamebasics.GameBasics
|
||||||
import com.unciv.ui.NewGameScreen
|
import com.unciv.ui.NewGameScreen
|
||||||
|
@ -31,13 +32,13 @@ class TileMap {
|
||||||
get() = tileList
|
get() = tileList
|
||||||
|
|
||||||
|
|
||||||
constructor(distance: Int, mapType: NewGameScreen.NewGameParameters.MapType) {
|
constructor(distance: Int, mapType: MapType) {
|
||||||
val map:HashMap<String,TileInfo>
|
val map:HashMap<String,TileInfo>
|
||||||
|
|
||||||
if(mapType==NewGameScreen.NewGameParameters.MapType.WithWater)
|
if(mapType==MapType.Perlin)
|
||||||
map = PerlinNoiseRandomMapGenerator().generateMap(distance)
|
map = PerlinNoiseRandomMapGenerator().generateMap(distance)
|
||||||
|
else
|
||||||
else map = SeedRandomMapGenerator().generateMap(distance,0f)
|
map = CelluarAutomataRandomMapGenerator(mapType).generateMap(distance)
|
||||||
|
|
||||||
tileList.addAll(map.values)
|
tileList.addAll(map.values)
|
||||||
// tileList.addAll(AlexanderRandomMapGenerator().generateMap(distance,0.8f).values)
|
// tileList.addAll(AlexanderRandomMapGenerator().generateMap(distance,0.8f).values)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener
|
||||||
import com.badlogic.gdx.utils.Array
|
import com.badlogic.gdx.utils.Array
|
||||||
import com.unciv.GameStarter
|
import com.unciv.GameStarter
|
||||||
import com.unciv.logic.GameInfo
|
import com.unciv.logic.GameInfo
|
||||||
|
import com.unciv.logic.map.MapType
|
||||||
import com.unciv.models.gamebasics.GameBasics
|
import com.unciv.models.gamebasics.GameBasics
|
||||||
import com.unciv.models.gamebasics.Nation
|
import com.unciv.models.gamebasics.Nation
|
||||||
import com.unciv.models.gamebasics.Translations
|
import com.unciv.models.gamebasics.Translations
|
||||||
|
@ -20,15 +21,11 @@ import kotlin.concurrent.thread
|
||||||
class NewGameScreen: PickerScreen(){
|
class NewGameScreen: PickerScreen(){
|
||||||
|
|
||||||
class NewGameParameters{
|
class NewGameParameters{
|
||||||
enum class MapType{
|
|
||||||
LandOnly,
|
|
||||||
WithWater
|
|
||||||
}
|
|
||||||
var difficulty="Prince"
|
var difficulty="Prince"
|
||||||
var nation="Babylon"
|
var nation="Babylon"
|
||||||
var mapRadius=20
|
var mapRadius=20
|
||||||
var numberOfEnemies=3
|
var numberOfEnemies=3
|
||||||
var mapType=MapType.WithWater
|
var mapType=MapType.Perlin
|
||||||
}
|
}
|
||||||
|
|
||||||
val newGameParameters=NewGameParameters()
|
val newGameParameters=NewGameParameters()
|
||||||
|
@ -123,6 +120,19 @@ class NewGameScreen: PickerScreen(){
|
||||||
val newGameOptionsTable = Table()
|
val newGameOptionsTable = Table()
|
||||||
newGameOptionsTable.skin = skin
|
newGameOptionsTable.skin = skin
|
||||||
|
|
||||||
|
newGameOptionsTable.add("{Map type}:".tr())
|
||||||
|
val mapTypes = LinkedHashMap<String, MapType>()
|
||||||
|
for (type in MapType.values()) {
|
||||||
|
mapTypes[type.toString()] = type
|
||||||
|
}
|
||||||
|
val mapTypeSelectBox = TranslatedSelectBox(mapTypes.keys, "Perlin", skin)
|
||||||
|
mapTypeSelectBox.addListener(object : ChangeListener() {
|
||||||
|
override fun changed(event: ChangeEvent?, actor: Actor?) {
|
||||||
|
newGameParameters.mapType = mapTypes[mapTypeSelectBox.selected.value]!!
|
||||||
|
}
|
||||||
|
})
|
||||||
|
newGameOptionsTable.add(mapTypeSelectBox).pad(10f).row()
|
||||||
|
|
||||||
newGameOptionsTable.add("{World size}:".tr())
|
newGameOptionsTable.add("{World size}:".tr())
|
||||||
val worldSizeToRadius = LinkedHashMap<String, Int>()
|
val worldSizeToRadius = LinkedHashMap<String, Int>()
|
||||||
worldSizeToRadius["Tiny"] = 10
|
worldSizeToRadius["Tiny"] = 10
|
||||||
|
|
Loading…
Reference in a new issue