Caves!
This commit is contained in:
parent
b5e0527986
commit
b2b3c74d94
4 changed files with 133 additions and 4 deletions
|
@ -0,0 +1,74 @@
|
||||||
|
package group.ouroboros.potrogue.builders
|
||||||
|
|
||||||
|
import group.ouroboros.potrogue.blocks.GameBlock
|
||||||
|
import group.ouroboros.potrogue.extensions.sameLevelNeighborsShuffled
|
||||||
|
import group.ouroboros.potrogue.world.World
|
||||||
|
import org.hexworks.zircon.api.data.Position3D
|
||||||
|
import org.hexworks.zircon.api.data.Size3D
|
||||||
|
|
||||||
|
// We take the worldSize from the outside world. This is useful because later it can be parameterized.
|
||||||
|
class WorldBuilder (private val worldSize: Size3D) {
|
||||||
|
|
||||||
|
private val width = worldSize.xLength
|
||||||
|
private val height = worldSize.zLength
|
||||||
|
// We maintain a Map of Blocks which we will use when we build the World
|
||||||
|
private var blocks: MutableMap<Position3D, GameBlock> = mutableMapOf()
|
||||||
|
|
||||||
|
// With makeCaves we create a fluent interface so that the users of WorldBuilder can use it in a similar manner as we build Tiles and Components in Zircon.
|
||||||
|
fun makeCaves(): WorldBuilder {
|
||||||
|
return randomizeTiles()
|
||||||
|
.smooth(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we build the World we take a visible size which will be used by the GameArea.
|
||||||
|
fun build(visibleSize: Size3D): World = World(blocks, visibleSize, worldSize)
|
||||||
|
|
||||||
|
private fun randomizeTiles(): WorldBuilder {
|
||||||
|
forAllPositions { pos ->
|
||||||
|
// In Kotlin if is not a statement but an expression. This means that it returns a value so we can assign it to our Map.
|
||||||
|
blocks[pos] = if (Math.random() < 0.5) {
|
||||||
|
GameBlockFactory.floor()
|
||||||
|
} else GameBlockFactory.wall()
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun smooth(iterations: Int): WorldBuilder {
|
||||||
|
// We are going to need a new Map of blocks for our smoothing because we can’t do it in place. Modifying the original Map would render our cellular automata algorithm useless because it needs to calculate the new state from the old state.
|
||||||
|
val newBlocks = mutableMapOf<Position3D, GameBlock>()
|
||||||
|
repeat(iterations) {
|
||||||
|
forAllPositions { pos ->
|
||||||
|
// We create a 3D world, so we need not only x and y, but also z. What you see here is called destructuring
|
||||||
|
val (x, y, z) = pos
|
||||||
|
var floors = 0
|
||||||
|
var rocks = 0
|
||||||
|
// Here we iterate over a list of the current position and all its neighbors
|
||||||
|
pos.sameLevelNeighborsShuffled().plus(pos).forEach { neighbor ->
|
||||||
|
// And we only care about the positions which have a corresponding block (when they are not outside of the game world)
|
||||||
|
blocks.whenPresent(neighbor) { block ->
|
||||||
|
if (block.isFloor) {
|
||||||
|
floors++
|
||||||
|
} else rocks++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newBlocks[Position3D.create(x, y, z)] =
|
||||||
|
if (floors >= rocks) GameBlockFactory.floor() else GameBlockFactory.wall()
|
||||||
|
}
|
||||||
|
// When we’re done with smoothing we replace the old Map with the new one.
|
||||||
|
blocks = newBlocks
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just a convenience function for iterating over all of the world’s positions which I added as a demonstration of how functions with lambdas work. Here you can pass any function which takes a Position3D and returns Unit (Unit is the equivalent of Java’s Void).
|
||||||
|
private fun forAllPositions(fn: (Position3D) -> Unit) {
|
||||||
|
worldSize.fetchPositions().forEach(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is an example of defining an extension function which takes a function as a parameter. What the header of the function means here is:
|
||||||
|
//
|
||||||
|
// Augment all MutableMaps which are holding Position3D to GameBlock mappings to have a function named “whenPresent” which takes a position and a function.
|
||||||
|
private fun MutableMap<Position3D, GameBlock>.whenPresent(pos: Position3D, fn: (GameBlock) -> Unit) {
|
||||||
|
this[pos]?.let(fn)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package group.ouroboros.potrogue.extensions
|
||||||
|
|
||||||
|
import org.hexworks.zircon.api.data.Position3D
|
||||||
|
|
||||||
|
// We add the extension function to Position3D. We do it by defining a function not with a simple name, but by the format: fun <target class>.<function name>: return type { // ....
|
||||||
|
fun Position3D.sameLevelNeighborsShuffled(): List<Position3D> {
|
||||||
|
return (-1..1).flatMap { x ->
|
||||||
|
// We use functional programming here. flatMap and map works in a similar way as you might've been used to it in Java 8’s Stream API.
|
||||||
|
(-1..1).map { y ->
|
||||||
|
// When you write extension functions this will be bound to the class being extended. So this here will point to the Position3D instance on which sameLevelNeighborsShuffled is called.
|
||||||
|
this.withRelativeX(x).withRelativeY(y)
|
||||||
|
}
|
||||||
|
// minus here will remove this position from the List and return a new List. shuffled will also return a new list which contains the same elements but shuffled.
|
||||||
|
}.minus(this).shuffled()
|
||||||
|
}
|
|
@ -4,15 +4,20 @@ import group.ouroboros.potrogue.GameConfig
|
||||||
import group.ouroboros.potrogue.GameConfig.LOG_AREA_HEIGHT
|
import group.ouroboros.potrogue.GameConfig.LOG_AREA_HEIGHT
|
||||||
import group.ouroboros.potrogue.GameConfig.SIDEBAR_WIDTH
|
import group.ouroboros.potrogue.GameConfig.SIDEBAR_WIDTH
|
||||||
import group.ouroboros.potrogue.GameConfig.WINDOW_WIDTH
|
import group.ouroboros.potrogue.GameConfig.WINDOW_WIDTH
|
||||||
|
import group.ouroboros.potrogue.builders.GameTileRepository
|
||||||
|
import group.ouroboros.potrogue.world.Game
|
||||||
|
import org.hexworks.cobalt.databinding.api.extension.toProperty
|
||||||
import org.hexworks.zircon.api.ComponentDecorations.box
|
import org.hexworks.zircon.api.ComponentDecorations.box
|
||||||
import org.hexworks.zircon.api.Components
|
import org.hexworks.zircon.api.Components
|
||||||
import org.hexworks.zircon.api.component.ColorTheme
|
import org.hexworks.zircon.api.component.ColorTheme
|
||||||
import org.hexworks.zircon.api.component.ComponentAlignment
|
import org.hexworks.zircon.api.component.ComponentAlignment
|
||||||
|
import org.hexworks.zircon.api.game.ProjectionMode
|
||||||
import org.hexworks.zircon.api.grid.TileGrid
|
import org.hexworks.zircon.api.grid.TileGrid
|
||||||
import org.hexworks.zircon.api.view.base.BaseView
|
import org.hexworks.zircon.api.view.base.BaseView
|
||||||
|
import org.hexworks.zircon.internal.game.impl.GameAreaComponentRenderer
|
||||||
|
|
||||||
|
|
||||||
class PlayView (private val grid: TileGrid, theme: ColorTheme = GameConfig.THEME) : BaseView(grid, theme) {
|
class PlayView (private val grid: TileGrid, private val game: Game = Game.create(), theme: ColorTheme = GameConfig.THEME) : BaseView(grid, theme) {
|
||||||
init {
|
init {
|
||||||
//Create Sidebar
|
//Create Sidebar
|
||||||
val sidebar = Components.panel()
|
val sidebar = Components.panel()
|
||||||
|
@ -22,11 +27,25 @@ class PlayView (private val grid: TileGrid, theme: ColorTheme = GameConfig.THEME
|
||||||
|
|
||||||
//Create area for logging
|
//Create area for logging
|
||||||
val logArea = Components.logArea()
|
val logArea = Components.logArea()
|
||||||
.withDecorations(box(title = "Log")) // 1
|
.withDecorations(box(title = "Log"))
|
||||||
.withSize(WINDOW_WIDTH - SIDEBAR_WIDTH, LOG_AREA_HEIGHT)
|
.withSize(WINDOW_WIDTH - SIDEBAR_WIDTH, LOG_AREA_HEIGHT)
|
||||||
.withAlignmentWithin(screen, ComponentAlignment.BOTTOM_RIGHT) // 2
|
.withAlignmentWithin(screen, ComponentAlignment.BOTTOM_RIGHT)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
screen.addComponents(sidebar, logArea)
|
//Create Game view
|
||||||
|
val gameComponent = Components.panel()
|
||||||
|
.withSize(game.world.visibleSize.to2DSize())
|
||||||
|
.withComponentRenderer(
|
||||||
|
GameAreaComponentRenderer(
|
||||||
|
gameArea = game.world,
|
||||||
|
projectionMode = ProjectionMode.TOP_DOWN.toProperty(),
|
||||||
|
fillerTile = GameTileRepository.FLOOR
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.withAlignmentWithin(screen, ComponentAlignment.TOP_RIGHT)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
screen.addComponents(sidebar, logArea, gameComponent)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
21
src/main/kotlin/group/ouroboros/potrogue/world/Game.kt
Normal file
21
src/main/kotlin/group/ouroboros/potrogue/world/Game.kt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package group.ouroboros.potrogue.world
|
||||||
|
|
||||||
|
import group.ouroboros.potrogue.GameConfig.GAME_AREA_SIZE
|
||||||
|
import group.ouroboros.potrogue.GameConfig.WORLD_SIZE
|
||||||
|
import group.ouroboros.potrogue.builders.WorldBuilder
|
||||||
|
import org.hexworks.zircon.api.data.Size3D
|
||||||
|
|
||||||
|
class Game (val world: World) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun create(
|
||||||
|
worldSize: Size3D = WORLD_SIZE,
|
||||||
|
visibleSize: Size3D = GAME_AREA_SIZE
|
||||||
|
) = Game(
|
||||||
|
WorldBuilder(worldSize)
|
||||||
|
.makeCaves()
|
||||||
|
.build(visibleSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue