Refactor tic-tac-toe

This commit is contained in:
soywiz
2017-10-07 17:35:15 +02:00
parent 16e491b34a
commit a4e847e6a1
24 changed files with 481 additions and 15 deletions

View File

@@ -8,6 +8,20 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.soywiz:korge-gradle-plugin:$korVersion"
}
ext {
libraries = [
kotlin_stdlib_common: "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion",
kotlin_stdlib_jvm : "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion",
kotlin_stdlib_js : "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion",
kotlin_test_common : "org.jetbrains.kotlin:kotlin-test-common:$kotlinVersion",
kotlin_test_jvm : "org.jetbrains.kotlin:kotlin-test:$kotlinVersion",
kotlin_test_js : "org.jetbrains.kotlin:kotlin-test-js:$kotlinVersion",
kotlin_reflect : "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion",
]
}
}
configurations {
@@ -25,5 +39,44 @@ allprojects {
maven { url 'http://dl.bintray.com/kotlin/kotlin-eap-1.2' }
mavenCentral()
}
it.afterEvaluate {
if (it.plugins.hasPlugin("kotlin-platform-common")) {
dependencies {
compile libraries.kotlin_stdlib_common
testCompile libraries.kotlin_test_common
}
kotlin {
experimental { coroutines 'enable' }
}
}
if (it.plugins.hasPlugin("kotlin-platform-jvm") || it.plugins.hasPlugin("kotlin")) {
dependencies {
compile libraries.kotlin_stdlib_jvm
testCompile libraries.kotlin_test_jvm
testCompile "junit:junit:4.12"
}
kotlin {
experimental { coroutines 'enable' }
}
}
if (it.plugins.hasPlugin("kotlin-platform-js") || it.plugins.hasPlugin("kotlin2js")) {
dependencies {
compile libraries.kotlin_stdlib_js
testCompile libraries.kotlin_test_js
}
kotlin {
experimental { coroutines 'enable' }
}
compileKotlin2Js {
kotlinOptions.moduleKind = "umd"
kotlinOptions.sourceMap = true
}
}
}
}

7
korge-tic-tac-toe/common/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
/.idea
/.gradle
/build
/classes
/out
/genresources
/web

View File

@@ -1,10 +1,15 @@
apply from: "../include_js.gradle"
apply plugin: 'kotlin2js'
apply plugin: 'kotlin-platform-common'
//apply plugin: 'application'
//mainClassName = "com.soywiz.korge.tictactoe.TicTacToe"
sourceSets {
//generated.resources.srcDirs = ['src/generated/resources']
//main.resources.srcDirs = [ 'src/main/resources' ]
main.resources.srcDirs = [ 'src/main/resources', 'src/generated/resources' ]
}
/*
sourceSets {
generated.resources.srcDirs = [ 'genresources' ]
@@ -12,17 +17,20 @@ sourceSets {
main.resources.srcDirs = [ 'resources', 'genresources' ]
test.kotlin.srcDirs = [ 'test' ]
}
*/
dependencies {
compile "com.soywiz:korge-js:$korVersion"
compile "com.soywiz:korge-ext-swf-js:$korVersion"
compile "com.soywiz:korau-mp3-js:$korVersion"
compile "com.soywiz:korge-common:$korVersion"
compile "com.soywiz:korge-ext-swf-common:$korVersion"
compile "com.soywiz:korau-mp3-common:$korVersion"
testCompile "com.soywiz:korge-tests-common:$korVersion"
//compile "com.soywiz:korge:$korVersion"
//compile "com.soywiz:korge-ext-swf:$korVersion"
//compile "com.soywiz:korau-mp3:$korVersion"
}
/*
compileKotlin2Js {
kotlinOptions.outputFile = "${projectDir}/web/output.js"
//kotlinOptions.moduleKind = "amd"
@@ -58,6 +66,7 @@ compileKotlin2Js.doLast {
}
jar.enabled = false
*/
//distTar.enabled = false
//distZip.enabled = false

View File

@@ -0,0 +1 @@
rootProject.name = 'korge-tic-tac-toe-common'

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1 @@
{"name":"main.swf","loaderVersion":16,"sha1":"516c0fac4fbcac08b7740d2cabcc718d08c6fab4","configSha1":"3c38f2d222663a800a900cf37498c8ff3b43e9fb"}

View File

@@ -0,0 +1,64 @@
package com.soywiz.korge.tictactoe
import com.soywiz.korio.util.Extra
import com.soywiz.korma.ds.Array2
import com.soywiz.korma.geom.PointInt
import kotlin.collections.ArrayList
enum class Chip { EMPTY, CROSS, CIRCLE }
class Board(val width: Int = 3, val height: Int = width, val lineSize: Int = width) {
class Cell(val x: Int, val y: Int) : Extra by Extra.Mixin() {
val pos = PointInt(x, y)
var value = Chip.EMPTY
}
val cells = Array2(width, height) { Cell(it % width, it / width) }
fun inside(x: Int, y: Int) = cells.inside(x, y)
fun select(x: Int, y: Int, dx: Int, dy: Int, size: Int): List<Cell>? {
if (!inside(x, y)) return null
if (!inside(x + dx * (size - 1), y + dy * (size - 1))) return null
return (0 until size).map { cells[x + dx * it, y + dy * it] }
}
val lines = kotlin.collections.ArrayList<List<Cell>>()
init {
fun addLine(line: List<Cell>?) {
if (line != null) lines += line
}
for (y in 0..height) {
for (x in 0..width) {
addLine(select(x, y, 1, 0, lineSize))
addLine(select(x, y, 0, 1, lineSize))
addLine(select(x, y, 1, 1, lineSize))
addLine(select(width - x - 1, y, -1, 1, lineSize))
}
}
}
operator fun get(x: Int, y: Int) = cells[x, y]
operator fun set(x: Int, y: Int, value: Chip) = run { cells[x, y].value = value }
val Iterable<Cell>.chipLine: Chip?
get() {
val expected = this.first().value
return if (expected == Chip.EMPTY) null else if (this.all { it.value == expected }) expected else null
}
val moreMovements: Boolean get() = cells.any { it.value == Chip.EMPTY }
val winnerLine: List<Cell>?
get() {
val out = kotlin.collections.ArrayList<Cell>()
for (line in lines) if (line.chipLine != null) out += line
return if (out.isEmpty()) null else out.toSet().toList()
}
val winner: Chip?
get() {
return winnerLine?.firstOrNull()?.value
}
}

View File

@@ -0,0 +1,92 @@
package com.soywiz.korge.tictactoe
import com.soywiz.korge.animate.play
import com.soywiz.korge.input.onClick
import com.soywiz.korge.time.milliseconds
import com.soywiz.korge.tween.*
import com.soywiz.korge.view.View
import com.soywiz.korge.view.get
import com.soywiz.korio.async.Signal
import com.soywiz.korio.async.async
import com.soywiz.korio.util.Extra
var Board.Cell.view by Extra.Property<View?> { null }
val Board.Cell.vview: View get() = this.view!!
val Board.Cell.onPress by Extra.Property { Signal<Unit>() }
fun Board.Cell.set(type: Chip) {
this.value = type
view["chip"].play(when (type) {
Chip.EMPTY -> "empty"
Chip.CIRCLE -> "circle"
Chip.CROSS -> "cross"
})
}
suspend fun Board.Cell.setAnimate(type: Chip) {
set(type)
async {
view.tween(
(view["chip"]!!::alpha[0.7, 1.0]).linear(),
(view["chip"]!!::scale[0.8, 1.0]).easeOutElastic(),
time = 300.milliseconds
)
}
}
var Board.Cell.highlighting by Extra.Property { false }
suspend fun Board.Cell.highlight(highlight: Boolean) {
view["highlight"].play(if (highlight) "highlight" else "none")
this.highlighting = highlight
if (highlight) {
async {
val hl = view["highlight"]!!
while (highlighting) {
hl.tween((hl::alpha[0.7]).easeInOutQuad(), time = 300.milliseconds)
hl.tween((hl::alpha[1.0]).easeInOutQuad(), time = 200.milliseconds)
}
}
async {
val ch = view["chip"]!!
ch.tween((ch::scale[0.4]).easeOutQuad(), time = 100.milliseconds)
ch.tween((ch::scale[1.2]).easeOutElastic(), time = 300.milliseconds)
while (highlighting) {
ch.tween((ch::scale[1.0]).easeOutQuad(), time = 300.milliseconds)
ch.tween((ch::scale[1.2]).easeOutElastic(), time = 300.milliseconds)
}
}
}
}
suspend fun Board.Cell.lowlight(lowlight: Boolean) {
async {
view.tween(
view!!::scale[1.0, 0.7],
view!!::alpha[0.3],
time = 300.milliseconds, easing = Easings.EASE_OUT_QUAD
)
}
}
suspend fun Board.reset() {
for (cell in cells) {
//cell.view?.removeAllComponents()
cell.set(Chip.EMPTY)
cell.highlight(false)
cell.view?.scale = 1.0
cell.view?.alpha = 1.0
cell.view["chip"]?.scale = 1.0
cell.view["chip"]?.alpha = 1.0
}
}
fun Board.Cell.init(view: View) {
this.view = view
set(this.value)
view["hit"].onClick {
onPress(Unit)
}
}

View File

@@ -0,0 +1,10 @@
package com.soywiz.korge.tictactoe
import com.soywiz.korge.animate.AnLibrary
import com.soywiz.korge.resources.getPath
import com.soywiz.korio.inject.AsyncInjector
// AUTOGENERATED:
fun AsyncInjector.generatedInject() = this
//.mapPrototype { TicTacToeMainScene(getPath("main.ani")) } // @TODO: kotlin.js bug
.mapPrototype { TicTacToeMainScene(getPath(AnLibrary::class, "main.ani")) }

View File

@@ -0,0 +1,146 @@
package com.soywiz.korge.tictactoe
import com.soywiz.korge.Korge
import com.soywiz.korge.animate.AnLibrary
import com.soywiz.korge.animate.AnLibraryPlugin
import com.soywiz.korge.input.mouse
import com.soywiz.korge.plugin.KorgePlugin
import com.soywiz.korge.resources.Path
import com.soywiz.korge.scene.Module
import com.soywiz.korge.scene.Scene
import com.soywiz.korge.view.Container
import com.soywiz.korge.view.descendantsWithPropInt
import com.soywiz.korge.view.get
import com.soywiz.korge.view.setText
import com.soywiz.korio.async.Signal
import com.soywiz.korio.async.go
import com.soywiz.korio.async.waitOne
import com.soywiz.korio.error.invalidOp
import com.soywiz.korio.inject.AsyncInjector
import com.soywiz.korio.lang.JvmStatic
import com.soywiz.korma.geom.PointInt
object TicTacToe {
@JvmStatic
fun main(args: Array<String>) = Korge(TicTacToeModule, injector = AsyncInjector().generatedInject())
}
object TicTacToeModule : Module() {
override val mainScene = TicTacToeMainScene::class
override val title: String = "tic-tac-toe"
override val icon: String = "icon.png"
override val plugins: List<KorgePlugin> = super.plugins + listOf(
AnLibraryPlugin
)
suspend override fun init(injector: AsyncInjector) {
//injector.get<ResourcesRoot>().mapExtensions("swf" to "ani")
//injector.get<ResourcesRoot>().mapExtensionsJustInJTransc("swf" to "ani")
}
}
// Controller
class TicTacToeMainScene(
@Path("main.ani") val mainLibrary: AnLibrary
) : Scene() {
val board = Board(3, 3)
lateinit var game: Game
suspend override fun sceneInit(sceneView: Container) {
sceneView += mainLibrary.createMainTimeLine()
for ((rowView, row) in sceneView.descendantsWithPropInt("row")) {
for ((cellView, cell) in rowView.descendantsWithPropInt("cell")) {
board.cells[row, cell].init(cellView)
}
}
val p1 = InteractivePlayer(board, Chip.CROSS)
val p2 = BotPlayer(board, Chip.CIRCLE)
//val p2 = InteractivePlayer(board, Chip.CIRCLE)
game = Game(board, listOf(p1, p2))
cancellables += go {
while (true) {
game.board.reset()
val result = game.game()
println(result)
val results = mainLibrary.createMovieClip("Results")
//(results["result"] as AnTextField).format?.face = Html.FontFace.Bitmap(font)
when (result) {
is Game.Result.DRAW -> results["result"].setText("DRAW")
is Game.Result.WIN -> {
results["result"].setText("WIN")
for (cell in result.cells) cell.highlight(true)
for (cell in game.board.cells.toList() - result.cells) cell.lowlight(true)
}
}
sceneView += results
results["hit"]?.mouse?.onClick?.waitOne()
//sceneView -= results
results.removeFromParent()
}
}
}
}
interface Player {
val chip: Chip
suspend fun move(): PointInt
}
class Game(val board: Board, val players: List<Player>) {
interface Result {
object DRAW : Result
class WIN(val player: Player?, val cells: List<Board.Cell>) : Result
}
suspend fun game(): Result {
var turn = 0
while (board.moreMovements) {
val currentPlayer = players[turn % players.size]
while (true) {
val pos = currentPlayer.move()
println(pos)
if (board.cells[pos].value == Chip.EMPTY) {
board.cells[pos].setAnimate(currentPlayer.chip)
break
}
}
if (board.winner != null) return Result.WIN(currentPlayer, board.winnerLine ?: listOf())
turn++
}
return Result.DRAW
}
}
class BotPlayer(val board: Board, override val chip: Chip) : Player {
suspend override fun move(): PointInt {
for (cell in board.cells) {
if (cell.value == Chip.EMPTY) {
return cell.pos
}
}
invalidOp("No more movements")
}
}
class InteractivePlayer(val board: Board, override val chip: Chip) : Player {
val clicked = Signal<PointInt>()
init {
for (cell in board.cells) {
cell.onPress {
clicked(cell.pos)
}
}
}
suspend override fun move(): PointInt {
return clicked.waitOne()
}
}

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,9 +1,9 @@
mipmaps: true
antialiasing: true
rasterizerMethod: X4
exportScale: 2.0
minShapeSide: 64
maxShapeSide: 512
minMorphShapeSide: 16
maxMorphShapeSide: 128
mipmaps: true
antialiasing: true
rasterizerMethod: X4
exportScale: 2.0
minShapeSide: 64
maxShapeSide: 512
minMorphShapeSide: 16
maxMorphShapeSide: 128
exportPaths: false

7
korge-tic-tac-toe/js/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
/.idea
/.gradle
/build
/classes
/out
/genresources
/web

View File

@@ -0,0 +1,46 @@
apply plugin: 'kotlin-platform-js'
sourceSets {
main.resources.srcDirs = [ '../common/src/main/resources', '../common/src/generated/resources' ]
}
dependencies {
implement project(":korge-tic-tac-toe:common")
compile "com.soywiz:korge-js:$korVersion"
compile "com.soywiz:korge-ext-swf-js:$korVersion"
compile "com.soywiz:korau-mp3-js:$korVersion"
testCompile "com.soywiz:korge-tests-js:$korVersion"
}
compileKotlin2Js {
kotlinOptions.outputFile = "${projectDir}/web/output.js"
kotlinOptions.sourceMap = false
}
clean {
delete new File("${projectDir}/web")
}
compileKotlin2Js.doLast {
configurations.compile.each { File file ->
copy {
includeEmptyDirs = false
from zipTree(file.absolutePath)
into "${projectDir}/web"
include { fileTreeElement ->
def path = fileTreeElement.path
(path.endsWith(".js") || path.endsWith(".js.map")) && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
}
}
}
copy {
from sourceSets.main.resources.srcDirs
into "${projectDir}/web"
}
}
jar.enabled = false

View File

@@ -0,0 +1 @@
rootProject.name = 'korge-tic-tac-toe-js'

7
korge-tic-tac-toe/jvm/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
/.idea
/.gradle
/build
/classes
/out
/genresources
/web

View File

@@ -0,0 +1,18 @@
apply plugin: 'kotlin-platform-jvm'
apply plugin: 'application'
mainClassName = 'com.soywiz.korge.tictactoe.TicTacToe'
sourceSets {
main.resources.srcDirs = [ '../common/src/main/resources', '../common/src/generated/resources' ]
}
dependencies {
implement project(":korge-tic-tac-toe:common")
compile "com.soywiz:korge:$korVersion"
compile "com.soywiz:korge-ext-swf:$korVersion"
compile "com.soywiz:korau-mp3:$korVersion"
testCompile "com.soywiz:korge-tests:$korVersion"
}

View File

@@ -0,0 +1 @@
rootProject.name = 'korge-tic-tac-toe'

View File

@@ -1,7 +1,10 @@
rootProject.name = 'korge-samples'
include(
'korge-tic-tac-toe',
'korge-tic-tac-toe:common',
'korge-tic-tac-toe:js',
'korge-tic-tac-toe:jvm',
//'korge-coffee',
//'korge-simon',
)