Skip to content

Commit

Permalink
More work
Browse files Browse the repository at this point in the history
  • Loading branch information
korge-game-engine committed Jun 17, 2024
1 parent 1942800 commit 0766722
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 27 deletions.
149 changes: 143 additions & 6 deletions src/TileRules.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,79 @@
import korlibs.datastructure.*
import korlibs.image.tiles.*
import korlibs.io.async.*
import korlibs.math.geom.*
import korlibs.memory.*
import kotlinx.atomicfu.*
import kotlin.math.*

fun IntGridToTileGrid(grid: IntArray2, rules: IRuleMatcher, tiles: TileMapData) {
for (y in 0 until grid.height) {
for (x in 0 until grid.width) {
tiles[x, y] = rules.get(grid, x, y)
open class ObservableIntArray2(val width: Int, val height: Int, val default: Int = 0) {
val data = IntArray2(width, height, default)
//private val listeners = Signal2<ObservableIntArray2, RectangleInt>()

open fun updated(rect: RectangleInt) {
}

private var locked = atomic(0)
private var bb = BoundsBuilder()

inline fun <T> lock(block: () -> T): T {
lock()
try {
return block()
} finally {
unlock()
}
}

@PublishedApi internal fun lock() {
locked.incrementAndGet()
}
@PublishedApi internal fun unlock() {
if (locked.decrementAndGet() <= 0) {
flush()
}
}

private fun flush() {
if (locked.value == 0 && bb.isNotEmpty) {
updated(bb.bounds.toInt())
bb = BoundsBuilder.EMPTY
}
}

operator fun set(rect: RectangleInt, value: Int) {
val l = rect.left.coerceIn(0, width)
val r = rect.right.coerceIn(0, width)
val u = rect.top.coerceIn(0, height)
val d = rect.bottom.coerceIn(0, height)
for (x in l until r) {
for (y in u until d) {
data.setOr(x, y, value)
}
}
bb += rect.toFloat()
flush()
}

operator fun get(p: PointInt): Int = this[p.x, p.y]
operator fun get(x: Int, y: Int): Int = if (data.inside(x, y)) data[x, y] else default
operator fun set(p: PointInt, value: Int) { this[p.x, p.y] = value }

operator fun set(x: Int, y: Int, value: Int) {
if (data.inside(x, y)) data[x, y] = value
bb += Rectangle(x, y, 1, 1)
flush()
}
}

fun IntGridToTileGrid(ints: IntArray2, rules: IRuleMatcher, tiles: TileMapData, updated: RectangleInt = RectangleInt(0, 0, ints.width, ints.height)) {
val l = (updated.left - rules.maxDist).coerceIn(0, ints.width)
val r = (updated.right + rules.maxDist).coerceIn(0, ints.width)
val t = (updated.top - rules.maxDist).coerceIn(0, ints.height)
val b = (updated.bottom + rules.maxDist).coerceIn(0, ints.height)
for (y in t until b) {
for (x in l until r) {
tiles[x, y] = rules.get(ints, x, y)
}
}
}
Expand Down Expand Up @@ -63,6 +131,11 @@ interface ISimpleTileProvider : IRuleMatcher {
fun get(spec: SimpleTileSpec): Tile
}

interface IRuleMatcherMatch {
val maxDist: Int
fun match(ints: IntArray2, x: Int, y: Int): Boolean
}

interface IRuleMatcher {
val maxDist: Int
fun get(ints: IntArray2, x: Int, y: Int): Tile
Expand All @@ -80,10 +153,9 @@ class CombinedRuleMatcher(val rules: List<IRuleMatcher>) : IRuleMatcher {
}
return Tile.INVALID
}

}

open class SimpleTileProvider(val value: Int) : ISimpleTileProvider, IRuleMatcher {
class SimpleTileProvider(val value: Int) : ISimpleTileProvider, IRuleMatcher {
override val maxDist: Int = 1

//val rules = mutableSetOf<SimpleRule>()
Expand Down Expand Up @@ -135,6 +207,71 @@ open class SimpleTileProvider(val value: Int) : ISimpleTileProvider, IRuleMatche
}
}

data class TileMatch(val id: Int, val offset: PointInt, val eq: Boolean = true) : IRuleMatcherMatch {
override val maxDist: Int = maxOf(offset.x.absoluteValue, offset.y.absoluteValue)

val offsetX: Int = offset.x
val offsetY: Int = offset.y

fun flippedX(): TileMatch = TileMatch(id, PointInt(-offset.x, offset.y), eq)
fun flippedY(): TileMatch = TileMatch(id, PointInt(offset.x, -offset.y), eq)
fun rotated(): TileMatch = TileMatch(id, PointInt(offset.y, offset.x), eq)

private fun comp(value: Int): Boolean = if (eq) value == id else value != id

override fun match(ints: IntArray2, x: Int, y: Int): Boolean {
return comp(ints[x + offset.x, y + offset.y])
}
}

data class TileMatchGroup(val tile: Tile, val matches: List<TileMatch>) : IRuleMatcherMatch {
constructor(tile: Tile, vararg matches: TileMatch) : this(tile, matches.toList())

override val maxDist: Int by lazy { matches.maxOf { it.maxDist } }
fun flippedX(): TileMatchGroup = TileMatchGroup(tile.flippedY(), matches.map { it.flippedX() })
fun flippedY(): TileMatchGroup = TileMatchGroup(tile.flippedY(), matches.map { it.flippedY() })
fun rotated(): TileMatchGroup = TileMatchGroup(tile.rotated(), matches.map { it.rotated() })

override fun match(ints: IntArray2, x: Int, y: Int): Boolean {
return matches.all { it.match(ints, x, y) }
}
}

class GenericTileProvider : IRuleMatcher {
override val maxDist: Int = 1

val rules = mutableSetOf<TileMatchGroup>()

companion object {
val FALSE = listOf(false)
val BOOLS = listOf(false, true)
}

fun rule(
rule: TileMatchGroup,
registerFlipX: Boolean = true,
registerFlipY: Boolean = true,
registerRotated: Boolean = true,
) {
for (fx in if (registerFlipX) BOOLS else FALSE) {
for (fy in if (registerFlipY) BOOLS else FALSE) {
for (rot in if (registerRotated) BOOLS else FALSE) {
var r = rule
if (rot) r = r.rotated()
if (fx) r = r.flippedX()
if (fy) r = r.flippedY()
rules += r
}
}
}
}

override fun get(ints: IntArray2, x: Int, y: Int): Tile {
for (rule in rules) if (rule.match(ints, x, y)) return rule.tile
return Tile.INVALID
}
}

fun TileMapData.pushInside(x: Int, y: Int, value: Tile) {
if (inside(x, y)) {
this.data.push(x, y, value.raw)
Expand Down
73 changes: 52 additions & 21 deletions src/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,51 @@ import korlibs.korge.view.*
import korlibs.korge.view.tiles.*
import korlibs.math.geom.*
import korlibs.math.geom.slice.*
import korlibs.math.random.*
import korlibs.time.*
import kotlin.random.*

suspend fun main() = Korge(windowSize = Size(512, 512), backgroundColor = Colors["#2b2b2b"]) {
suspend fun main() = Korge(windowSize = Size(256 * 2, 196 * 2), backgroundColor = Colors.DIMGRAY) {
val sceneContainer = sceneContainer()

sceneContainer.changeTo { MyScene() }
}

//class MirroredInt

class MyScene : PixelatedScene(256 * 2, 196 * 2) {
override suspend fun SContainer.sceneMain() {
solidRect(500, 500, Colors.DIMGRAY)
val tilesIDC = resourcesVfs["tiles.ase"].readImageDataContainer(ASE)
val tiles = tilesIDC.mainBitmap.slice()

val tileSet = TileSet(tiles.splitInRows(16, 16).mapIndexed { index, slice -> TileSetTileInfo(index, slice) })
val tileMap = tileMap(TileMapData(32, 24, tileSet = tileSet))
val snakeMap = tileMap(TileMapData(32, 24, tileSet = tileSet))
val ints = IntArray2(tileMap.map.width, tileMap.map.height, 0)
ints[RectangleInt(0, 0, ints.width, 1)] = 1
ints[RectangleInt(0, 0, 1, ints.height)] = 1
ints[RectangleInt(0, ints.height - 1, ints.width, 1)] = 1
ints[RectangleInt(ints.width - 1, 0, 1, ints.height)] = 1
ints[1, 1] = 1
ints[15, 15] = 2
val ints = object : ObservableIntArray2(tileMap.map.width, tileMap.map.height, GROUND) {
val rules = CombinedRuleMatcher(WallsProvider, AppleProvider)
override fun updated(rect: RectangleInt) {
IntGridToTileGrid(this.data, rules, tileMap.map, rect)
}
}
ints.lock {
ints[RectangleInt(0, 0, ints.width, 1)] = WALL
ints[RectangleInt(0, 0, 1, ints.height)] = WALL
ints[RectangleInt(0, ints.height - 1, ints.width, 1)] = WALL
ints[RectangleInt(ints.width - 1, 0, 1, ints.height)] = WALL
ints[RectangleInt(4, 4, ints.width / 2, 1)] = WALL
}

IntGridToTileGrid(ints, CombinedRuleMatcher(WallsProvider, AppleProvider), tileMap.map)
fun putRandomApple() {
while (true) {
val p: PointInt = Random[Rectangle(0, 0, ints.width, ints.height)].toInt()
if (ints[p] == GROUND) {
ints[p] = APPLE
break
}
}
}

tileMap.map[10, 10] = WallsProvider.get(SimpleTileSpec(down = true, right = true, left = true, up = true))
putRandomApple()

//for (n in 0 until 64) tileMap.map[n, 0] = Tile(n, SliceOrientation.NORMAL)

Expand All @@ -53,14 +70,10 @@ class MyScene : PixelatedScene(256 * 2, 196 * 2) {
//tileMap.map.push(4, 4, Tile(7))
//tileMap.map.push(4, 4, Tile(tile, orientation))

var snake = Snake(listOf(PointInt(5, 5)))
.withExtraMove(SnakeMove.RIGHT)
var snake = Snake(listOf(PointInt(5, 5)), maxLen = 2)
.withExtraMove(SnakeMove.RIGHT)
//.withExtraMove(SnakeMove.DOWN)
//.withExtraMove(SnakeMove.DOWN)
//.withExtraMove(SnakeMove.RIGHT)
//.withExtraMove(SnakeMove.UP)
snake.render(snakeMap.map)
snake.render(ints, snakeMap.map)

//fun updateOrientation(
// updateOffset: (Point) -> Point = { it },
Expand All @@ -75,9 +88,16 @@ class MyScene : PixelatedScene(256 * 2, 196 * 2) {
var direction: SnakeMove = SnakeMove.RIGHT

fun snakeMove(move: SnakeMove) {
snake.clear(snakeMap.map)
val oldSnake = snake
snake = snake.withExtraMove(move)
snake.render(snakeMap.map)
val headPos = snake.pos.last()
if (ints[headPos] == APPLE) {
ints[headPos] = GROUND
snake = snake.copy(maxLen = snake.maxLen + 1)
putRandomApple()
}
oldSnake.clear(ints, snakeMap.map)
snake.render(ints, snakeMap.map)
}

interval(0.1.fastSeconds) {
Expand Down Expand Up @@ -163,15 +183,16 @@ data class Snake(val pos: List<PointInt>, val maxLen: Int = 10) {
}
}

fun clear(map: TileMapData) {
fun clear(ints: ObservableIntArray2, map: TileMapData) {
for (p in pos) {
ints[p] = GROUND
map.clearAt(p.x, p.y)
//map.push()
//map[p.x, p.y] = Tile(0)
}
}

fun render(map: TileMapData) {
fun render(ints: ObservableIntArray2, map: TileMapData) {
for ((i, p) in pos.withIndex()) {
val isFirst = i == 0
val isLast = i == pos.size - 1
Expand All @@ -191,6 +212,7 @@ data class Snake(val pos: List<PointInt>, val maxLen: Int = 10) {
))
}
map.pushInside(p.x, p.y, tile)
ints[p] = 3
}
}
}
Expand Down Expand Up @@ -218,3 +240,12 @@ object WallsProvider : ISimpleTileProvider by (SimpleTileProvider(value = 1).als
it.rule(SimpleRule(Tile(20), up = true, left = true, down = true))
it.rule(SimpleRule(Tile(21), up = true, left = true, right = true, down = true))
})

operator fun IntArray2.get(p: PointInt): Int = this.getOr(p.x, p.y)
operator fun IntArray2.set(p: PointInt, value: Int) { if (inside(p.x, p.y)) this[p.x, p.y] = value }
//fun IStackedIntArray2.get(p: PointInt): Int = this[p.x, p.y]
//fun IStackedLongArray2.get(p: PointInt): Long = this[p.x, p.y]

val GROUND = 0
val WALL = 1
val APPLE = 2

0 comments on commit 0766722

Please sign in to comment.