diff --git a/.github/workflows/deploy-js.yml b/.github/workflows/deploy-js.yml index 945a488..ab2d5c3 100644 --- a/.github/workflows/deploy-js.yml +++ b/.github/workflows/deploy-js.yml @@ -30,7 +30,7 @@ jobs: - { uses: actions/checkout@v4 } - { name: Set up JDK, uses: actions/setup-java@v3, with: { distribution: "${{ env.JAVA_DISTRIBUTION }}", java-version: "${{ env.JAVA_VERSION }}" } } - { name: Prepare Gradle, uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 } # v3.1.0 - - { name: Buid JS bundle, run: ./gradlew browserReleaseEsbuild } - #- { name: Buid JS bundle, run: ./gradlew browserReleaseWebpack } # available after 6.0.0-beta3 + #- { name: Buid JS bundle, run: ./gradlew browserReleaseEsbuild } + - { name: Buid JS bundle, run: ./gradlew browserReleaseWebpack } - { name: Upload artifact, uses: actions/upload-pages-artifact@v3, with: { path: 'build/www' } } - { name: Deploy 🚀 to GitHub Pages, id: deployment, uses: actions/deploy-pages@v4} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eeaeecb..cfa4e26 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,3 +1,3 @@ [plugins] -korge = { id = "com.soywiz.korge", version = "6.0.0-alpha2" } +korge = { id = "com.soywiz.korge", version = "6.0.0-alpha3" } #korge = { id = "com.soywiz.korge", version = "999.0.0.999" } diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 8f8a233..58f434a 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -2543,7 +2543,7 @@ source-map-loader@5.0.0: iconv-lite "^0.6.3" source-map-js "^1.0.2" -source-map-support@0.5.21, source-map-support@~0.5.20: +source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== diff --git a/resources/tiles.ase b/resources/tiles.ase index 2974520..a9d0f8c 100644 Binary files a/resources/tiles.ase and b/resources/tiles.ase differ diff --git a/src/TileRules.kt b/src/TileRules.kt new file mode 100644 index 0000000..1d3a6c8 --- /dev/null +++ b/src/TileRules.kt @@ -0,0 +1,142 @@ +import korlibs.datastructure.* +import korlibs.image.tiles.* +import korlibs.memory.* + +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) + } + } +} + +fun IntArray2.getOr(x: Int, y: Int, default: Int = -1): Int { + if (!inside(x, y)) return default + return this[x, y] +} + +fun IntArray2.setOr(x: Int, y: Int, value: Int) { + if (!inside(x, y)) return + this[x, y] = value +} + +data class SimpleTileSpec( + val left: Boolean = false, + val up: Boolean = false, + val right: Boolean = false, + val down: Boolean = false, +) { + val bits: Int = bits(left, up, right, down) + + companion object { + fun bits(left: Boolean, up: Boolean, right: Boolean, down: Boolean): Int = 0 + .insert(left, 0).insert(up, 1).insert(right, 2).insert(down, 3) + } +} + +fun Tile.flippedX(): Tile = Tile(tile, orientation.flippedX(), offsetX, offsetY) +fun Tile.flippedY(): Tile = Tile(tile, orientation.flippedY(), offsetX, offsetY) +fun Tile.rotated(): Tile = Tile(tile, orientation.rotatedRight(), offsetX, offsetY) + +data class SimpleRule( + val tile: Tile, + val spec: SimpleTileSpec, +) { + val left get() = spec.left + val right get() = spec.right + val up get() = spec.up + val down get() = spec.down + + constructor(tile: Tile, left: Boolean = false, up: Boolean = false, right: Boolean = false, down: Boolean = false) : this(tile, SimpleTileSpec(left, up, right, down)) + + fun flippedX(): SimpleRule = SimpleRule(tile.flippedX(), right, up, left, down) + fun flippedY(): SimpleRule = SimpleRule(tile.flippedY(), left, down, right, up) + fun rotated(): SimpleRule = SimpleRule(tile.rotated(), up = left, right = up, down = right, left = down) + //fun rotated(): SimpleRule = SimpleRule(tile.rotated(), up = right, right = down, down = left, left = up) + + fun match(spec: SimpleTileSpec): Boolean { + return this.spec == spec + } +} + +interface ISimpleTileProvider : IRuleMatcher { + fun get(spec: SimpleTileSpec): Tile +} + +interface IRuleMatcher { + val maxDist: Int + fun get(ints: IntArray2, x: Int, y: Int): Tile +} + +class CombinedRuleMatcher(val rules: List) : IRuleMatcher { + constructor(vararg rules: IRuleMatcher) : this(rules.toList()) + + override val maxDist: Int by lazy { rules.maxOf { it.maxDist } } + + override fun get(ints: IntArray2, x: Int, y: Int): Tile { + for (rule in rules) { + val tile = rule.get(ints, x, y) + if (tile.isValid) return tile + } + return Tile.INVALID + } + +} + +open class SimpleTileProvider(val value: Int) : ISimpleTileProvider, IRuleMatcher { + override val maxDist: Int = 1 + + //val rules = mutableSetOf() + val ruleTable = arrayOfNulls(16) + + companion object { + val FALSE = listOf(false) + val BOOLS = listOf(false, true) + } + + fun rule( + rule: SimpleRule, + 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() + val bits = r.spec.bits + if (ruleTable[bits] == null) ruleTable[bits] = r + //rules += r + } + } + } + } + + override fun get(spec: SimpleTileSpec): Tile { + ruleTable[spec.bits]?.let { return it.tile } + //for (rule in rules) { + // if (rule.match(spec)) { + // return rule.tile + // } + //} + return Tile.INVALID + } + + override fun get(ints: IntArray2, x: Int, y: Int): Tile { + if (ints.getOr(x, y) != value) return Tile.INVALID + val left = ints.getOr(x - 1, y) == value + val right = ints.getOr(x + 1, y) == value + val up = ints.getOr(x, y - 1) == value + val down = ints.getOr(x, y + 1) == value + return get(SimpleTileSpec(left, up, right, down)) + } +} + +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 fea0b28..b25d0fc 100644 --- a/src/main.kt +++ b/src/main.kt @@ -8,11 +8,12 @@ import korlibs.io.file.std.* import korlibs.korge.* import korlibs.korge.input.* import korlibs.korge.scene.* +import korlibs.korge.time.* import korlibs.korge.view.* import korlibs.korge.view.tiles.* import korlibs.math.geom.* import korlibs.math.geom.slice.* -import kotlin.time.Duration.Companion.milliseconds +import korlibs.time.* suspend fun main() = Korge(windowSize = Size(512, 512), backgroundColor = Colors["#2b2b2b"]) { val sceneContainer = sceneContainer() @@ -20,67 +21,95 @@ suspend fun main() = Korge(windowSize = Size(512, 512), backgroundColor = Colors sceneContainer.changeTo { MyScene() } } -class MyScene : PixelatedScene(256, 196) { +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, 32, tileSet = tileSet)) + 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 - for (n in 0 until 64) { - tileMap.map[n, 0] = Tile(n, SliceOrientation.NORMAL) - } + IntGridToTileGrid(ints, CombinedRuleMatcher(WallsProvider, AppleProvider), tileMap.map) + + tileMap.map[10, 10] = WallsProvider.get(SimpleTileSpec(down = true, right = true, left = true, up = true)) + + //for (n in 0 until 64) tileMap.map[n, 0] = Tile(n, SliceOrientation.NORMAL) //var tile = 1 //var tile = 11 //var tile = 4 - var tile = 5 - var offset = Point(0, 0) - var orientation = SliceOrientation.NORMAL - tileMap.map.push(4, 4, Tile(7)) - tileMap.map.push(4, 4, Tile(tile, orientation)) + //var tile = 5 + //var offset = Point(0, 0) + //var orientation = SliceOrientation.NORMAL + //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) .withExtraMove(SnakeMove.RIGHT) - .withExtraMove(SnakeMove.DOWN) - .withExtraMove(SnakeMove.DOWN) - .withExtraMove(SnakeMove.RIGHT) - .withExtraMove(SnakeMove.UP) - snake.render(tileMap.map) - - fun updateOrientation( - updateOffset: (Point) -> Point = { it }, - update: (SliceOrientation) -> SliceOrientation = { it } - ) { - offset = updateOffset(offset) - orientation = update(orientation) - println("orientation=$orientation") - tileMap.map[4, 4] = Tile(tile, orientation, offset.x.toInt(), offset.y.toInt()) - } + //.withExtraMove(SnakeMove.DOWN) + //.withExtraMove(SnakeMove.DOWN) + //.withExtraMove(SnakeMove.RIGHT) + //.withExtraMove(SnakeMove.UP) + snake.render(snakeMap.map) + + //fun updateOrientation( + // updateOffset: (Point) -> Point = { it }, + // update: (SliceOrientation) -> SliceOrientation = { it } + //) { + // offset = updateOffset(offset) + // orientation = update(orientation) + // println("orientation=$orientation") + // tileMap.map[4, 4] = Tile(tile, orientation, offset.x.toInt(), offset.y.toInt()) + //} + + var direction: SnakeMove = SnakeMove.RIGHT fun snakeMove(move: SnakeMove) { - snake.clear(tileMap.map) + snake.clear(snakeMap.map) snake = snake.withExtraMove(move) - snake.render(tileMap.map) + snake.render(snakeMap.map) + } + + interval(0.1.fastSeconds) { + snakeMove(direction) } keys { - down(Key.LEFT) { snakeMove(SnakeMove.LEFT) } - down(Key.RIGHT) { snakeMove(SnakeMove.RIGHT) } - down(Key.UP) { snakeMove(SnakeMove.UP) } - down(Key.DOWN) { snakeMove(SnakeMove.DOWN) } - - - down(Key.LEFT) { updateOrientation { it.rotatedLeft() } } - down(Key.RIGHT) { updateOrientation { it.rotatedRight() } } - down(Key.Y) { updateOrientation { it.flippedY() } } - down(Key.X) { updateOrientation { it.flippedX() } } - downFrame(Key.W, dt = 16.milliseconds) { updateOrientation(updateOffset = { it + Point(0, -1) }) } - downFrame(Key.A, dt = 16.milliseconds) { updateOrientation(updateOffset = { it + Point(-1, 0) }) } - downFrame(Key.S, dt = 16.milliseconds) { updateOrientation(updateOffset = { it + Point(0, +1) }) } - downFrame(Key.D, dt = 16.milliseconds) { updateOrientation(updateOffset = { it + Point(+1, 0) }) } + down(Key.LEFT) { direction = SnakeMove.LEFT } + down(Key.RIGHT) { direction = SnakeMove.RIGHT } + down(Key.UP) { direction = SnakeMove.UP } + down(Key.DOWN) { direction = SnakeMove.DOWN } + + //down(Key.LEFT) { updateOrientation { it.rotatedLeft() } } + //down(Key.RIGHT) { updateOrientation { it.rotatedRight() } } + //down(Key.Y) { updateOrientation { it.flippedY() } } + //down(Key.X) { updateOrientation { it.flippedX() } } + //downFrame(Key.W, dt = 16.milliseconds) { updateOrientation(updateOffset = { it + Point(0, -1) }) } + //downFrame(Key.A, dt = 16.milliseconds) { updateOrientation(updateOffset = { it + Point(-1, 0) }) } + //downFrame(Key.S, dt = 16.milliseconds) { updateOrientation(updateOffset = { it + Point(0, +1) }) } + //downFrame(Key.D, dt = 16.milliseconds) { updateOrientation(updateOffset = { it + Point(+1, 0) }) } + } + } +} + +operator fun IntArray2.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) { + this.setOr(x, y, value) } } } @@ -90,9 +119,22 @@ enum class SnakeMove(val dir: PointInt) { RIGHT(PointInt(+1, 0)), UP(PointInt(0, -1)), DOWN(PointInt(0, +1)), - ; + NONE(PointInt(0, 0)), +; + + val comingFromLeft get() = this == RIGHT + val comingFromRight get() = this == LEFT + val comingFromUp get() = this == DOWN + val comingFromDown get() = this == UP + + val left get() = this == LEFT + val right get() = this == RIGHT + val up get() = this == UP + val down get() = this == DOWN + val isHorizontal get() = dir.x != 0 val isVertical get() = dir.y != 0 + val isNone get() = !isHorizontal && !isVertical } data class Snake(val pos: List, val maxLen: Int = 10) { @@ -109,7 +151,7 @@ data class Snake(val pos: List, val maxLen: Int = 10) { p1.x < p0.x -> SnakeMove.LEFT p1.y > p0.y -> SnakeMove.DOWN p1.y < p0.y -> SnakeMove.UP - else -> SnakeMove.RIGHT + else -> SnakeMove.NONE } } @@ -135,57 +177,44 @@ data class Snake(val pos: List, val maxLen: Int = 10) { val isLast = i == pos.size - 1 val p0 = dir(i - 1) val p1 = dir(i) + val tile = when { - isFirst -> { - Tile(1, - when (p1) { - SnakeMove.RIGHT -> SliceOrientation.NORMAL - SnakeMove.LEFT -> SliceOrientation.ROTATE_180 - SnakeMove.DOWN -> SliceOrientation.ROTATE_90 - SnakeMove.UP -> SliceOrientation.ROTATE_270 - } - , 0, 0) - } - isLast -> Tile(4, - when (p0) { - SnakeMove.RIGHT -> SliceOrientation.NORMAL - SnakeMove.LEFT -> SliceOrientation.ROTATE_180 - SnakeMove.DOWN -> SliceOrientation.ROTATE_90 - SnakeMove.UP -> SliceOrientation.ROTATE_270 - } - , 0, 0) - else -> { - if (p0 == p1) { - Tile(2, when (p0) { - SnakeMove.RIGHT -> SliceOrientation.NORMAL - SnakeMove.LEFT -> SliceOrientation.ROTATE_180 - SnakeMove.DOWN -> SliceOrientation.ROTATE_90 - SnakeMove.UP -> SliceOrientation.ROTATE_270 - }, 0, 0) - } else { - Tile(3, when (p0 to p1) { - SnakeMove.RIGHT to SnakeMove.DOWN -> SliceOrientation.NORMAL - SnakeMove.UP to SnakeMove.LEFT -> SliceOrientation.NORMAL - - SnakeMove.RIGHT to SnakeMove.UP -> SliceOrientation.ROTATE_90 - SnakeMove.DOWN to SnakeMove.LEFT -> SliceOrientation.ROTATE_90 - - SnakeMove.LEFT to SnakeMove.UP -> SliceOrientation.ROTATE_180 - SnakeMove.DOWN to SnakeMove.RIGHT -> SliceOrientation.ROTATE_180 - - SnakeMove.LEFT to SnakeMove.DOWN -> SliceOrientation.ROTATE_270 - SnakeMove.UP to SnakeMove.RIGHT -> SliceOrientation.ROTATE_270 - - - else -> SliceOrientation.ROTATE_270 - }, 0, 0) - } - //println("$p0, $p1") + isFirst || isLast -> { + val p = if (isLast) p0 else p1 + (if (isLast) SnakeHeadProvider else SnakeProvider).get(SimpleTileSpec(p.left, p.up, p.right, p.down)) } + else -> SnakeProvider.get(SimpleTileSpec( + left = p0.comingFromLeft || p1.left, + up = p0.comingFromUp || p1.up, + right = p0.comingFromRight || p1.right, + down = p0.comingFromDown || p1.down + )) } - if (map.inside(p.x, p.y)) { - map.push(p.x, p.y, tile) - } + map.pushInside(p.x, p.y, tile) } } } + +object SnakeProvider : ISimpleTileProvider by (SimpleTileProvider(value = 3).also { + it.rule(SimpleRule(Tile(1), right = true)) + it.rule(SimpleRule(Tile(2), left = true, right = true)) + it.rule(SimpleRule(Tile(3), left = true, down = true)) +}) + +object SnakeHeadProvider : ISimpleTileProvider by (SimpleTileProvider(value = 3).also { + it.rule(SimpleRule(Tile(0))) + it.rule(SimpleRule(Tile(4), right = true)) +}) + +object AppleProvider : ISimpleTileProvider by (SimpleTileProvider(value = 2).also { + it.rule(SimpleRule(Tile(12))) +}) + +object WallsProvider : ISimpleTileProvider by (SimpleTileProvider(value = 1).also { + it.rule(SimpleRule(Tile(16))) + it.rule(SimpleRule(Tile(17), right = true)) + it.rule(SimpleRule(Tile(18), left = true, right = true)) + it.rule(SimpleRule(Tile(19), left = true, down = true)) + it.rule(SimpleRule(Tile(20), up = true, left = true, down = true)) + it.rule(SimpleRule(Tile(21), up = true, left = true, right = true, down = true)) +}) diff --git a/test/SimpleTileProviderTest.kt b/test/SimpleTileProviderTest.kt new file mode 100644 index 0000000..e3c3fd6 --- /dev/null +++ b/test/SimpleTileProviderTest.kt @@ -0,0 +1,11 @@ +import kotlin.test.* + +class SimpleTileProviderTest { + @Test + fun test() { + println(WallsProvider.get(SimpleTileSpec(right = true)).toStringInfo()) + println(WallsProvider.get(SimpleTileSpec(left = true)).toStringInfo()) + println(WallsProvider.get(SimpleTileSpec(up = true)).toStringInfo()) + println(WallsProvider.get(SimpleTileSpec(down = true)).toStringInfo()) + } +}