diff --git a/build.gradle b/build.gradle index 48b347f..ea703d8 100644 --- a/build.gradle +++ b/build.gradle @@ -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 + } + } + } } diff --git a/korge-tic-tac-toe/common/.gitignore b/korge-tic-tac-toe/common/.gitignore new file mode 100644 index 0000000..d88dafb --- /dev/null +++ b/korge-tic-tac-toe/common/.gitignore @@ -0,0 +1,7 @@ +/.idea +/.gradle +/build +/classes +/out +/genresources +/web diff --git a/korge-tic-tac-toe/build.gradle b/korge-tic-tac-toe/common/build.gradle similarity index 79% rename from korge-tic-tac-toe/build.gradle rename to korge-tic-tac-toe/common/build.gradle index 40bc0df..489f77c 100644 --- a/korge-tic-tac-toe/build.gradle +++ b/korge-tic-tac-toe/common/build.gradle @@ -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 diff --git a/korge-tic-tac-toe/common/settings.gradle b/korge-tic-tac-toe/common/settings.gradle new file mode 100644 index 0000000..6fae12c --- /dev/null +++ b/korge-tic-tac-toe/common/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'korge-tic-tac-toe-common' diff --git a/korge-tic-tac-toe/common/src/generated/resources/main.ani b/korge-tic-tac-toe/common/src/generated/resources/main.ani new file mode 100644 index 0000000..9064af1 Binary files /dev/null and b/korge-tic-tac-toe/common/src/generated/resources/main.ani differ diff --git a/korge-tic-tac-toe/common/src/generated/resources/main.ani.0.png b/korge-tic-tac-toe/common/src/generated/resources/main.ani.0.png new file mode 100644 index 0000000..5b24934 Binary files /dev/null and b/korge-tic-tac-toe/common/src/generated/resources/main.ani.0.png differ diff --git a/korge-tic-tac-toe/common/src/generated/resources/main.ani.meta b/korge-tic-tac-toe/common/src/generated/resources/main.ani.meta new file mode 100644 index 0000000..7f774be --- /dev/null +++ b/korge-tic-tac-toe/common/src/generated/resources/main.ani.meta @@ -0,0 +1 @@ +{"name":"main.swf","loaderVersion":16,"sha1":"516c0fac4fbcac08b7740d2cabcc718d08c6fab4","configSha1":"3c38f2d222663a800a900cf37498c8ff3b43e9fb"} \ No newline at end of file diff --git a/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/Board.kt b/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/Board.kt new file mode 100644 index 0000000..b749c38 --- /dev/null +++ b/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/Board.kt @@ -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? { + 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>() + + init { + fun addLine(line: List?) { + 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.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? + get() { + val out = kotlin.collections.ArrayList() + 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 + } +} diff --git a/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/BoardMediator.kt b/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/BoardMediator.kt new file mode 100644 index 0000000..5f52d53 --- /dev/null +++ b/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/BoardMediator.kt @@ -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 { null } +val Board.Cell.vview: View get() = this.view!! +val Board.Cell.onPress by Extra.Property { Signal() } + +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) + } +} diff --git a/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/InjectExt.kt b/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/InjectExt.kt new file mode 100644 index 0000000..e7ec889 --- /dev/null +++ b/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/InjectExt.kt @@ -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")) } diff --git a/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/TicTacToe.kt b/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/TicTacToe.kt new file mode 100644 index 0000000..cad2b08 --- /dev/null +++ b/korge-tic-tac-toe/common/src/main/kotlin/com/soywiz/korge/tictactoe/TicTacToe.kt @@ -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) = 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 = super.plugins + listOf( + AnLibraryPlugin + ) + + suspend override fun init(injector: AsyncInjector) { + //injector.get().mapExtensions("swf" to "ani") + //injector.get().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) { + interface Result { + object DRAW : Result + class WIN(val player: Player?, val cells: List) : 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() + + init { + for (cell in board.cells) { + cell.onPress { + clicked(cell.pos) + } + } + } + + suspend override fun move(): PointInt { + return clicked.waitOne() + } + +} diff --git a/korge-tic-tac-toe/resources/icon.png b/korge-tic-tac-toe/common/src/main/resources/icon.png similarity index 100% rename from korge-tic-tac-toe/resources/icon.png rename to korge-tic-tac-toe/common/src/main/resources/icon.png diff --git a/korge-tic-tac-toe/resources/index.html b/korge-tic-tac-toe/common/src/main/resources/index.html similarity index 100% rename from korge-tic-tac-toe/resources/index.html rename to korge-tic-tac-toe/common/src/main/resources/index.html diff --git a/korge-tic-tac-toe/resources/main.fla b/korge-tic-tac-toe/common/src/main/resources/main.fla similarity index 100% rename from korge-tic-tac-toe/resources/main.fla rename to korge-tic-tac-toe/common/src/main/resources/main.fla diff --git a/korge-tic-tac-toe/resources/main.swf b/korge-tic-tac-toe/common/src/main/resources/main.swf similarity index 100% rename from korge-tic-tac-toe/resources/main.swf rename to korge-tic-tac-toe/common/src/main/resources/main.swf diff --git a/korge-tic-tac-toe/resources/main.swf.config b/korge-tic-tac-toe/common/src/main/resources/main.swf.config similarity index 95% rename from korge-tic-tac-toe/resources/main.swf.config rename to korge-tic-tac-toe/common/src/main/resources/main.swf.config index 1a86a20..5821822 100644 --- a/korge-tic-tac-toe/resources/main.swf.config +++ b/korge-tic-tac-toe/common/src/main/resources/main.swf.config @@ -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 \ No newline at end of file diff --git a/korge-tic-tac-toe/test/com/soywiz/korge/tictactoe/BoardTest.kt b/korge-tic-tac-toe/common/src/test/kotlin/com/soywiz/korge/tictactoe/BoardTest.kt similarity index 100% rename from korge-tic-tac-toe/test/com/soywiz/korge/tictactoe/BoardTest.kt rename to korge-tic-tac-toe/common/src/test/kotlin/com/soywiz/korge/tictactoe/BoardTest.kt diff --git a/korge-tic-tac-toe/js/.gitignore b/korge-tic-tac-toe/js/.gitignore new file mode 100644 index 0000000..d88dafb --- /dev/null +++ b/korge-tic-tac-toe/js/.gitignore @@ -0,0 +1,7 @@ +/.idea +/.gradle +/build +/classes +/out +/genresources +/web diff --git a/korge-tic-tac-toe/js/build.gradle b/korge-tic-tac-toe/js/build.gradle new file mode 100644 index 0000000..aaa1510 --- /dev/null +++ b/korge-tic-tac-toe/js/build.gradle @@ -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 diff --git a/korge-tic-tac-toe/js/settings.gradle b/korge-tic-tac-toe/js/settings.gradle new file mode 100644 index 0000000..1578f51 --- /dev/null +++ b/korge-tic-tac-toe/js/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'korge-tic-tac-toe-js' diff --git a/korge-tic-tac-toe/jvm/.gitignore b/korge-tic-tac-toe/jvm/.gitignore new file mode 100644 index 0000000..d88dafb --- /dev/null +++ b/korge-tic-tac-toe/jvm/.gitignore @@ -0,0 +1,7 @@ +/.idea +/.gradle +/build +/classes +/out +/genresources +/web diff --git a/korge-tic-tac-toe/jvm/build.gradle b/korge-tic-tac-toe/jvm/build.gradle new file mode 100644 index 0000000..cc74a08 --- /dev/null +++ b/korge-tic-tac-toe/jvm/build.gradle @@ -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" +} diff --git a/korge-tic-tac-toe/jvm/settings.gradle b/korge-tic-tac-toe/jvm/settings.gradle new file mode 100644 index 0000000..57aa17e --- /dev/null +++ b/korge-tic-tac-toe/jvm/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'korge-tic-tac-toe' diff --git a/settings.gradle b/settings.gradle index be7b5ca..095004b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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', )