From 076672269c68dbeeae0fb737b1b7ce48ffb6a5ad Mon Sep 17 00:00:00 2001 From: korge Date: Mon, 17 Jun 2024 14:31:17 +0200 Subject: [PATCH] More work --- src/TileRules.kt | 149 +++++++++++++++++++++++++++++++++++++++++++++-- src/main.kt | 73 ++++++++++++++++------- 2 files changed, 195 insertions(+), 27 deletions(-) diff --git a/src/TileRules.kt b/src/TileRules.kt index 1d3a6c8..6dd8808 100644 --- a/src/TileRules.kt +++ b/src/TileRules.kt @@ -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() + + open fun updated(rect: RectangleInt) { + } + + private var locked = atomic(0) + private var bb = BoundsBuilder() + + inline fun 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) } } } @@ -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 @@ -80,10 +153,9 @@ class CombinedRuleMatcher(val rules: List) : 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() @@ -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) : 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() + + 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) diff --git a/src/main.kt b/src/main.kt index b25d0fc..6df1b60 100644 --- a/src/main.kt +++ b/src/main.kt @@ -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) @@ -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 }, @@ -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) { @@ -163,15 +183,16 @@ data class Snake(val pos: List, 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 @@ -191,6 +212,7 @@ data class Snake(val pos: List, val maxLen: Int = 10) { )) } map.pushInside(p.x, p.y, tile) + ints[p] = 3 } } } @@ -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