mirror of
https://github.com/jlengrand/korge-samples.git
synced 2026-03-10 08:31:18 +00:00
Added tictactoe game sample using ktree
This commit is contained in:
1
game/tictactoe-ktree/.gitignore
vendored
Normal file
1
game/tictactoe-ktree/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
6
game/tictactoe-ktree/build.gradle
Normal file
6
game/tictactoe-ktree/build.gradle
Normal file
@@ -0,0 +1,6 @@
|
||||
apply plugin: com.soywiz.korge.gradle.KorgeGradlePlugin
|
||||
|
||||
korge {
|
||||
id = "org.korge.samples.game.tictactoe"
|
||||
targetDefault()
|
||||
}
|
||||
19
game/tictactoe-ktree/src/commonMain/kotlin/main.kt
Normal file
19
game/tictactoe-ktree/src/commonMain/kotlin/main.kt
Normal file
@@ -0,0 +1,19 @@
|
||||
import com.soywiz.korge.*
|
||||
import com.soywiz.korge.scene.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korinject.*
|
||||
import com.soywiz.korma.geom.*
|
||||
import scene.*
|
||||
|
||||
suspend fun main() = Korge(Korge.Config(module = TicTacToeModule))
|
||||
|
||||
object TicTacToeModule : Module() {
|
||||
override val mainScene = InGameScene::class
|
||||
override val bgcolor = Colors["#2b2b2b"]
|
||||
override val size = SizeInt(640, 480)
|
||||
|
||||
override suspend fun AsyncInjector.configure() {
|
||||
mapPrototype { InGameScene() }
|
||||
}
|
||||
}
|
||||
|
||||
88
game/tictactoe-ktree/src/commonMain/kotlin/model/model.kt
Normal file
88
game/tictactoe-ktree/src/commonMain/kotlin/model/model.kt
Normal file
@@ -0,0 +1,88 @@
|
||||
package model
|
||||
|
||||
import com.soywiz.kds.*
|
||||
import com.soywiz.korma.geom.*
|
||||
|
||||
data class TicTacToeModel(
|
||||
val board: Array2<CellKind> = Array2(3, 3) { CellKind.EMPTY }
|
||||
) {
|
||||
companion object {
|
||||
operator fun invoke(str: String) = TicTacToeModel(Array2(str) { char, x, y ->
|
||||
when (char) {
|
||||
'X' -> CellKind.CROSS
|
||||
'O' -> CellKind.CIRCLE
|
||||
else -> CellKind.EMPTY
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun toString(): String = board.toString(mapOf(
|
||||
CellKind.EMPTY to '.',
|
||||
CellKind.CROSS to 'X',
|
||||
CellKind.CIRCLE to 'O',
|
||||
))
|
||||
}
|
||||
|
||||
enum class CellKind { CIRCLE, CROSS, EMPTY }
|
||||
|
||||
sealed class GameResult {
|
||||
data class Winner(val player: CellKind, val winnerCells: List<PointInt>) : GameResult()
|
||||
object Tie : GameResult()
|
||||
object InProgress : GameResult()
|
||||
}
|
||||
|
||||
sealed class Command {
|
||||
data class PlaceChip(val x: Int, val y: Int, val kind: CellKind) : Command()
|
||||
}
|
||||
|
||||
data class TicTacToeTransition(
|
||||
val oldModel: TicTacToeModel,
|
||||
val newModel: TicTacToeModel,
|
||||
val commands: List<Command>
|
||||
)
|
||||
|
||||
fun TicTacToeModel.checkValidCell(x: Int, y: Int): Boolean = board[x, y] == CellKind.EMPTY
|
||||
|
||||
fun TicTacToeModel.place(x: Int, y: Int, kind: CellKind): TicTacToeTransition {
|
||||
return TicTacToeTransition(
|
||||
oldModel = this,
|
||||
newModel = TicTacToeModel(this.board.map2 { cx, cy, cv -> if (cx == x && cy == y) kind else cv }),
|
||||
commands = listOf(
|
||||
Command.PlaceChip(x, y, kind)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun TicTacToeModel.checkLine(x: Int, y: Int, dx: Int, dy: Int): GameResult.Winner? {
|
||||
val startCellValue = board[x, y]
|
||||
val points = arrayListOf<PointInt>()
|
||||
val lineWithAllSameChipsNonEmpty = (0 until 3).all { n ->
|
||||
val px = x + (dx * n)
|
||||
val py = y + (dy * n)
|
||||
val value = board[px, py]
|
||||
points.add(PointInt(px, py))
|
||||
value == startCellValue && value != CellKind.EMPTY
|
||||
}
|
||||
return if (lineWithAllSameChipsNonEmpty) GameResult.Winner(startCellValue, points) else null
|
||||
}
|
||||
|
||||
fun TicTacToeModel.checkResult(): GameResult {
|
||||
// . | . | .
|
||||
// ---------
|
||||
// . | . | .
|
||||
// ---------
|
||||
// . | . | .
|
||||
|
||||
for (n in 0 until 3) {
|
||||
checkLine(n, 0, 0, +1)?.let { return it } // Verticals
|
||||
checkLine(0, n, +1, 0)?.let { return it } // Horizontals
|
||||
}
|
||||
checkLine(0, 0, +1, +1)?.let { return it } // Diagonal1
|
||||
checkLine(2, 0, -1, +1)?.let { return it } // Diagonal2
|
||||
|
||||
if (board.all { it != CellKind.EMPTY }) {
|
||||
return GameResult.Tie
|
||||
}
|
||||
|
||||
return GameResult.InProgress
|
||||
}
|
||||
105
game/tictactoe-ktree/src/commonMain/kotlin/scene/InGameScene.kt
Normal file
105
game/tictactoe-ktree/src/commonMain/kotlin/scene/InGameScene.kt
Normal file
@@ -0,0 +1,105 @@
|
||||
package scene
|
||||
|
||||
import com.soywiz.korge.input.*
|
||||
import com.soywiz.korge.scene.*
|
||||
import com.soywiz.korge.view.*
|
||||
import com.soywiz.korge.view.ktree.*
|
||||
import com.soywiz.korio.file.std.*
|
||||
import model.*
|
||||
|
||||
class InGameScene(
|
||||
|
||||
) : Scene() {
|
||||
override suspend fun Container.sceneInit() {
|
||||
addChild(resourcesVfs["board.ktree"].readKTree(views))
|
||||
}
|
||||
|
||||
override suspend fun Container.sceneMain() {
|
||||
var model = TicTacToeModel()
|
||||
|
||||
setResultText("")
|
||||
|
||||
var turn = CellKind.CROSS
|
||||
|
||||
// Register the input events
|
||||
setModel(model)
|
||||
for (y in 0 until 3) {
|
||||
for (x in 0 until 3) {
|
||||
getCell(x, y).first.mouse {
|
||||
onOver { if (model.checkResult() is GameResult.InProgress) cellSetHighlight(x, y, true) }
|
||||
onOut { if (model.checkResult() is GameResult.InProgress) cellSetHighlight(x, y, false) }
|
||||
onUp {
|
||||
val result = model.checkResult()
|
||||
|
||||
if (result is GameResult.InProgress) {
|
||||
if (model.checkValidCell(x, y)) {
|
||||
val transition = model.place(x, y, turn)
|
||||
model = transition.newModel
|
||||
executeTransition(transition)
|
||||
turn = if (turn == CellKind.CROSS) CellKind.CIRCLE else CellKind.CROSS
|
||||
//cellSetKind(x, y, model.CellKind.CIRCLE)
|
||||
|
||||
val result = model.checkResult()
|
||||
when (result) {
|
||||
is GameResult.Winner -> {
|
||||
setResultText("${result.player} wins")
|
||||
for (cell in result.winnerCells) {
|
||||
cellSetHighlight(cell.x, cell.y, true)
|
||||
}
|
||||
}
|
||||
GameResult.Tie -> {
|
||||
setResultText("TIE!")
|
||||
}
|
||||
GameResult.InProgress -> Unit
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sceneContainer.changeTo<InGameScene>()
|
||||
//model = model.TicTacToeModel()
|
||||
//setModel(model)
|
||||
//setResultText("")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Container.setResultText(text: String) {
|
||||
val gameResultView = stage["gameresult"]
|
||||
(gameResultView.firstOrNull as? Text?)?.text = text
|
||||
}
|
||||
|
||||
fun Container.executeTransition(transition: TicTacToeTransition) {
|
||||
for (command in transition.commands) {
|
||||
when (command) {
|
||||
is Command.PlaceChip -> {
|
||||
cellSetKind(command.x, command.y, command.kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Container.setModel(model: TicTacToeModel) {
|
||||
for (y in 0 until 3) {
|
||||
for (x in 0 until 3) {
|
||||
cellSetKind(x, y, model.board[x, y])
|
||||
cellSetHighlight(x, y, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Container.getCell(row: Int, column: Int): QView = this["row$row"]["cell$column"]
|
||||
|
||||
fun Container.cellSetKind(row: Int, column: Int, kind: CellKind) {
|
||||
val cell = getCell(row, column)
|
||||
cell["cross"].alpha(if (kind == CellKind.CROSS) 1.0 else 0.0)
|
||||
cell["circle"].alpha(if (kind == CellKind.CIRCLE) 1.0 else 0.0)
|
||||
}
|
||||
|
||||
fun Container.cellSetHighlight(row: Int, column: Int, highlight: Boolean) {
|
||||
val cell = getCell(row, column)
|
||||
cell["highlight"].alpha(if (highlight) 0.2 else 0.0)
|
||||
}
|
||||
}
|
||||
10
game/tictactoe-ktree/src/commonMain/resources/board.ktree
Normal file
10
game/tictactoe-ktree/src/commonMain/resources/board.ktree
Normal file
@@ -0,0 +1,10 @@
|
||||
<ktree width="640.0" height="480.0" gridWidth="20" gridHeight="20">
|
||||
<treeviewref name="row0" x="220.0" y="140.0" sourceFile="row.ktree"/>
|
||||
<treeviewref name="row1" x="220.0" y="220.0" sourceFile="row.ktree"/>
|
||||
<treeviewref name="row2" x="220.0" y="300.0" sourceFile="row.ktree"/>
|
||||
<solidrect x="290.0" y="134.99999999999997" width="5.0" height="235.0"/>
|
||||
<solidrect x="370.0" y="134.99999999999997" width="5.0" height="235.0"/>
|
||||
<solidrect x="214.99999999999991" y="210.0" width="235.0" height="5.0"/>
|
||||
<solidrect x="214.99999999999994" y="290.0" width="235.0" height="5.0"/>
|
||||
<text name="gameresult" x="340.0" y="100.0" text="CROSS wins" fontSize="64.0" verticalAlign="MIDDLE" horizontalAlign="CENTER"/>
|
||||
</ktree>
|
||||
5
game/tictactoe-ktree/src/commonMain/resources/cell.ktree
Normal file
5
game/tictactoe-ktree/src/commonMain/resources/cell.ktree
Normal file
@@ -0,0 +1,5 @@
|
||||
<ktree width="1280.0" height="720.0" gridWidth="20" gridHeight="20">
|
||||
<image name="circle" width="64.0" height="64.0" sourceFile="circle.png"/>
|
||||
<image name="cross" width="64.0" height="64.0" sourceFile="cross.png"/>
|
||||
<solidrect name="highlight" colorMul="#ffe39543" alpha="0.2" width="64.0" height="64.0"/>
|
||||
</ktree>
|
||||
BIN
game/tictactoe-ktree/src/commonMain/resources/circle.png
Normal file
BIN
game/tictactoe-ktree/src/commonMain/resources/circle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
game/tictactoe-ktree/src/commonMain/resources/cross.png
Normal file
BIN
game/tictactoe-ktree/src/commonMain/resources/cross.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 396 B |
BIN
game/tictactoe-ktree/src/commonMain/resources/korge.png
Normal file
BIN
game/tictactoe-ktree/src/commonMain/resources/korge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
5
game/tictactoe-ktree/src/commonMain/resources/row.ktree
Normal file
5
game/tictactoe-ktree/src/commonMain/resources/row.ktree
Normal file
@@ -0,0 +1,5 @@
|
||||
<ktree width="640.0" height="480.0" gridWidth="20" gridHeight="20">
|
||||
<treeviewref name="cell0" sourceFile="cell.ktree"/>
|
||||
<treeviewref name="cell1" x="80.0" sourceFile="cell.ktree"/>
|
||||
<treeviewref name="cell2" x="160.0" sourceFile="cell.ktree"/>
|
||||
</ktree>
|
||||
@@ -0,0 +1,41 @@
|
||||
import com.soywiz.korma.geom.*
|
||||
import model.*
|
||||
import kotlin.test.*
|
||||
|
||||
class TicTacToeModelTest {
|
||||
@Test
|
||||
fun test() {
|
||||
assertEquals(
|
||||
"""
|
||||
...
|
||||
.X.
|
||||
...
|
||||
""".trimIndent(),
|
||||
TicTacToeModel().place(1, 1, CellKind.CROSS).newModel.toString()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testResult() {
|
||||
assertEquals(GameResult.InProgress, TicTacToeModel().checkResult())
|
||||
assertEquals(GameResult.InProgress, TicTacToeModel().place(1, 1, CellKind.CROSS).newModel.checkResult())
|
||||
|
||||
assertEquals(GameResult.Winner(CellKind.CROSS, listOf(PointInt(1, 0), PointInt(1, 1), PointInt(1, 2))), TicTacToeModel("""
|
||||
.X.
|
||||
.X.
|
||||
.X.
|
||||
""".trimIndent()).checkResult())
|
||||
|
||||
assertEquals(GameResult.Tie, TicTacToeModel("""
|
||||
OXX
|
||||
XOO
|
||||
OXX
|
||||
""".trimIndent()).checkResult())
|
||||
|
||||
assertEquals(GameResult.InProgress, TicTacToeModel("""
|
||||
OXX
|
||||
XO.
|
||||
OXX
|
||||
""".trimIndent()).checkResult())
|
||||
}
|
||||
}
|
||||
26
game/tictactoe-ktree/src/commonTest/kotlin/test.kt
Normal file
26
game/tictactoe-ktree/src/commonTest/kotlin/test.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
import com.soywiz.klock.*
|
||||
import com.soywiz.korge.input.*
|
||||
import com.soywiz.korge.tests.*
|
||||
import com.soywiz.korge.tween.*
|
||||
import com.soywiz.korge.view.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korma.geom.*
|
||||
import kotlin.test.*
|
||||
|
||||
class MyTest : ViewsForTesting() {
|
||||
@Test
|
||||
fun test() = viewsTest {
|
||||
val log = arrayListOf<String>()
|
||||
val rect = solidRect(100, 100, Colors.RED)
|
||||
rect.onClick {
|
||||
log += "clicked"
|
||||
}
|
||||
assertEquals(1, views.stage.numChildren)
|
||||
rect.simulateClick()
|
||||
assertEquals(true, rect.isVisibleToUser())
|
||||
tween(rect::x[-102], time = 10.seconds)
|
||||
assertEquals(Rectangle(x=-102, y=0, width=100, height=100), rect.globalBounds)
|
||||
assertEquals(false, rect.isVisibleToUser())
|
||||
assertEquals(listOf("clicked"), log)
|
||||
}
|
||||
}
|
||||
26
game/tictactoe-ktree/src/jvmMain/kotlin/GenerateResources.kt
Normal file
26
game/tictactoe-ktree/src/jvmMain/kotlin/GenerateResources.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korim.format.*
|
||||
import com.soywiz.korio.file.std.*
|
||||
import com.soywiz.korma.geom.vector.*
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
object GenerateResources {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
runBlocking {
|
||||
Bitmap32(64, 64).context2d {
|
||||
stroke(Colors.RED, lineWidth = 6.0) {
|
||||
line(0 + 4, 0 + 4, 64 - 4, 64 - 4)
|
||||
line(64 - 4, 0 + 4, 0 + 4, 64 - 4)
|
||||
}
|
||||
}.writeTo(localCurrentDirVfs["src/commonMain/resources/cross.png"], PNG)
|
||||
|
||||
Bitmap32(64, 64).context2d {
|
||||
stroke(Colors.BLUE, lineWidth = 6.0) {
|
||||
circle(32, 32, 32 - 4)
|
||||
}
|
||||
}.writeTo(localCurrentDirVfs["src/commonMain/resources/circle.png"], PNG)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user