Skip to content

Instantly share code, notes, and snippets.

@RezMike
Last active March 29, 2021 23:05
Show Gist options
  • Save RezMike/6adce5df5f2a3108eb648ac59cdda924 to your computer and use it in GitHub Desktop.
Save RezMike/6adce5df5f2a3108eb648ac59cdda924 to your computer and use it in GitHub Desktop.
Code for "KorGE Tutorial - Writing 2048 game. Step 2 - State and interaction"
import Number.*
import com.soywiz.korge.view.*
import com.soywiz.korim.color.*
fun Container.block(number: Number) = Block(number).addTo(this)
class Block(val number: Number) : Container() {
init {
roundRect(cellSize, cellSize, 5.0, fill = number.color)
val textColor = when (number) {
ZERO, ONE -> Colors.BLACK
else -> Colors.WHITE
}
text(number.value.toString(), textSizeFor(number), textColor, font).apply {
centerBetween(0.0, 0.0, cellSize, cellSize)
}
}
}
private fun textSizeFor(number: Number) = when (number) {
ZERO, ONE, TWO, THREE, FOUR, FIVE -> cellSize / 2
SIX, SEVEN, EIGHT -> cellSize * 4 / 9
NINE, TEN, ELEVEN, TWELVE -> cellSize * 2 / 5
THIRTEEN, FOURTEEN, FIFTEEN -> cellSize * 7 / 20
SIXTEEN -> cellSize * 3 / 10
}
import com.soywiz.korev.*
import com.soywiz.korge.*
import com.soywiz.korge.html.*
import com.soywiz.korge.input.*
import com.soywiz.korge.view.*
import com.soywiz.korim.color.*
import com.soywiz.korim.font.*
import com.soywiz.korim.format.*
import com.soywiz.korio.file.std.*
import com.soywiz.korma.geom.*
import com.soywiz.korma.geom.vector.*
import kotlin.properties.*
import kotlin.random.*
var cellSize: Double = 0.0
var fieldSize: Double = 0.0
var leftIndent: Double = 0.0
var topIndent: Double = 0.0
var font: BitmapFont by Delegates.notNull()
fun columnX(number: Int) = leftIndent + 10 + (cellSize + 10) * number
fun rowY(number: Int) = topIndent + 10 + (cellSize + 10) * number
var map = PositionMap()
val blocks = mutableMapOf<Int, Block>()
var freeId = 0
suspend fun main() = Korge(width = 480, height = 640, title = "2048", bgcolor = RGBA(253, 247, 240)) {
font = resourcesVfs["clear_sans.fnt"].readBitmapFont()
cellSize = views.virtualWidth / 5.0
fieldSize = 50 + 4 * cellSize
leftIndent = (views.virtualWidth - fieldSize) / 2
topIndent = 150.0
val bgField = roundRect(fieldSize, fieldSize, 5.0, fill = Colors["#b9aea0"]) {
position(leftIndent, topIndent)
}
graphics {
position(leftIndent, topIndent)
fill(Colors["#cec0b2"]) {
for (i in 0..3) {
for (j in 0..3) {
roundRect(10 + (10 + cellSize) * i, 10 + (10 + cellSize) * j, cellSize, cellSize, 5.0)
}
}
}
}
val bgLogo = roundRect(cellSize, cellSize, 5.0, fill = Colors["#edc403"]) {
position(leftIndent, 30.0)
}
text("2048", cellSize * 0.5, Colors.WHITE, font).centerOn(bgLogo)
val bgBest = roundRect(cellSize * 1.5, cellSize * 0.8, 5.0, fill = Colors["#bbae9e"]) {
alignRightToRightOf(bgField)
alignTopToTopOf(bgLogo)
}
text("BEST", cellSize * 0.25, RGBA(239, 226, 210), font) {
centerXOn(bgBest)
alignTopToTopOf(bgBest, 5.0)
}
text("0", cellSize * 0.5, Colors.WHITE, font) {
setTextBounds(Rectangle(0.0, 0.0, bgBest.width, cellSize - 24.0))
alignment = TextAlignment.MIDDLE_CENTER
alignTopToTopOf(bgBest, 12.0)
centerXOn(bgBest)
}
val bgScore = roundRect(cellSize * 1.5, cellSize * 0.8, 5.0, fill = Colors["#bbae9e"]) {
alignRightToLeftOf(bgBest, 24.0)
alignTopToTopOf(bgBest)
}
text("SCORE", cellSize * 0.25, RGBA(239, 226, 210), font) {
centerXOn(bgScore)
alignTopToTopOf(bgScore, 5.0)
}
text("0", cellSize * 0.5, Colors.WHITE, font) {
setTextBounds(Rectangle(0.0, 0.0, bgScore.width, cellSize - 24.0))
alignment = TextAlignment.MIDDLE_CENTER
centerXOn(bgScore)
alignTopToTopOf(bgScore, 12.0)
}
val btnSize = cellSize * 0.3
val restartImg = resourcesVfs["restart.png"].readBitmap()
val undoImg = resourcesVfs["undo.png"].readBitmap()
val restartBlock = container {
val background = roundRect(btnSize, btnSize, 5.0, fill = RGBA(185, 174, 160))
image(restartImg) {
size(btnSize * 0.8, btnSize * 0.8)
centerOn(background)
}
alignTopToBottomOf(bgBest, 5.0)
alignRightToRightOf(bgField)
}
val undoBlock = container {
val background = roundRect(btnSize, btnSize, 5.0, fill = RGBA(185, 174, 160))
image(undoImg) {
size(btnSize * 0.6, btnSize * 0.6)
centerOn(background)
}
alignTopToTopOf(restartBlock)
alignRightToLeftOf(restartBlock, 5.0)
}
generateBlock()
keys.down {
when (it.key) {
Key.LEFT -> moveBlocksTo(Direction.LEFT)
Key.RIGHT -> moveBlocksTo(Direction.RIGHT)
Key.UP -> moveBlocksTo(Direction.TOP)
Key.DOWN -> moveBlocksTo(Direction.BOTTOM)
else -> Unit
}
}
onSwipe(20.0) {
when (it.direction) {
SwipeDirection.LEFT -> moveBlocksTo(Direction.LEFT)
SwipeDirection.RIGHT -> moveBlocksTo(Direction.RIGHT)
SwipeDirection.TOP -> moveBlocksTo(Direction.TOP)
SwipeDirection.BOTTOM -> moveBlocksTo(Direction.BOTTOM)
}
}
}
fun Stage.moveBlocksTo(direction: Direction) {
println(direction)
//TODO, implement this and calculate changes
}
fun Container.generateBlock() {
val position = map.getRandomFreePosition() ?: return
val number = if (Random.nextDouble() < 0.9) Number.ZERO else Number.ONE
val newId = createNewBlock(number, position)
map[position.x, position.y] = newId
}
fun Container.createNewBlock(number: Number, position: Position): Int {
val id = freeId++
createNewBlockWithId(id, number, position)
return id
}
fun Container.createNewBlockWithId(id: Int, number: Number, position: Position) {
blocks[id] = block(number).position(columnX(position.x), rowY(position.y))
}
import com.soywiz.korim.color.RGBA
enum class Number(val value: Int, val color: RGBA) {
ZERO(2, RGBA(240, 228, 218)),
ONE(4, RGBA(236, 224, 201)),
TWO(8, RGBA(255, 178, 120)),
THREE(16, RGBA(254, 150, 92)),
FOUR(32, RGBA(247, 123, 97)),
FIVE(64, RGBA(235, 88, 55)),
SIX(128, RGBA(236, 220, 146)),
SEVEN(256, RGBA(240, 212, 121)),
EIGHT(512, RGBA(244, 206, 96)),
NINE(1024, RGBA(248, 200, 71)),
TEN(2048, RGBA(256, 194, 46)),
ELEVEN(4096, RGBA(104, 130, 249)),
TWELVE(8192, RGBA(51, 85, 247)),
THIRTEEN(16384, RGBA(10, 47, 222)),
FOURTEEN(32768, RGBA(9, 43, 202)),
FIFTEEN(65536, RGBA(181, 37, 188)),
SIXTEEN(131072, RGBA(166, 34, 172));
fun next() = values()[(ordinal + 1) % values().size]
}
import com.soywiz.kds.*
import kotlin.random.*
class Position(val x: Int, val y: Int)
enum class Direction {
LEFT, RIGHT, TOP, BOTTOM
}
class PositionMap(private val array: IntArray2 = IntArray2(4, 4, -1)) {
private fun getOrNull(x: Int, y: Int) = if (array.get(x, y) != -1) Position(x, y) else null
private fun getNumber(x: Int, y: Int) = array.tryGet(x, y)?.let { blocks[it]?.number?.ordinal ?: -1 } ?: -1
fun getRandomFreePosition(): Position? {
val quantity = array.count { it == -1 }
if (quantity == 0) return null
val chosen = Random.nextInt(quantity)
var current = -1
array.each { x, y, value ->
if (value == -1) {
current++
if (current == chosen) {
return Position(x, y)
}
}
}
return null
}
operator fun get(x: Int, y: Int) = array[x, y]
operator fun set(x: Int, y: Int, value: Int) {
array[x, y] = value
}
fun forEach(action: (Int) -> Unit) { array.forEach(action) }
override fun equals(other: Any?): Boolean {
return (other is PositionMap) && this.array.data.contentEquals(other.array.data)
}
override fun hashCode() = array.hashCode()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment