From 19742e67798131383b3d2012324e002bd4c12676 Mon Sep 17 00:00:00 2001 From: xerus2000 <27jf@pm.me> Date: Fri, 22 Jan 2021 14:24:50 +0100 Subject: [PATCH 1/6] refactor: store undeployed and valid pieces in EnumMap --- .../sc/gui/controller/GameController.kt | 62 +++++++------------ src/main/kotlin/sc/gui/view/GameView.kt | 28 ++++----- .../sc/gui/view/UndeployedPiecesFragment.kt | 7 ++- 3 files changed, 40 insertions(+), 57 deletions(-) diff --git a/src/main/kotlin/sc/gui/controller/GameController.kt b/src/main/kotlin/sc/gui/controller/GameController.kt index 8c11c74b..4415e1e4 100644 --- a/src/main/kotlin/sc/gui/controller/GameController.kt +++ b/src/main/kotlin/sc/gui/controller/GameController.kt @@ -12,6 +12,7 @@ import sc.plugin2021.util.GameRuleLogic import sc.shared.GameResult import tornadofx.Controller import tornadofx.objectProperty +import java.util.* import kotlin.math.max // The following *Binding-classes are necessary to automatically unbind and rebind to a new piece (when switched) @@ -34,7 +35,6 @@ class ColorBinding(piece: Property) : ObjectBinding() { } override fun computeValue(): Color { - logger.debug("Color: {}", model.value.colorProperty().get()) return model.value.colorProperty().get() } @@ -149,18 +149,13 @@ class GameController : Controller() { val teamTwoScore = objectProperty(0) val playerNames = objectProperty>() - val gameResult: ObjectProperty = objectProperty(null) + val gameResult = objectProperty() - // we need to have them split separately otherwise we cannot listen to a specific color alone - val undeployedRedPieces = objectProperty(PieceShape.shapes.values) - val undeployedBluePieces = objectProperty(PieceShape.shapes.values) - val undeployedGreenPieces = objectProperty(PieceShape.shapes.values) - val undeployedYellowPieces = objectProperty(PieceShape.shapes.values) + val undeployedPieces: Map>> = EnumMap( + Color.values().associateWith { objectProperty(PieceShape.shapes.values) }) - val validRedPieces = objectProperty(ArrayList()) - val validBluePieces = objectProperty(ArrayList()) - val validGreenPieces = objectProperty(ArrayList()) - val validYellowPieces = objectProperty(ArrayList()) + val validPieces: Map>> = EnumMap( + Color.values().associateWith { objectProperty(emptyList()) }) // use selected* to access the property of currentPiece in order to always correctly be automatically rebind val currentPiece = objectProperty(PiecesModel(Color.RED, PieceShape.MONO)) @@ -180,24 +175,19 @@ class GameController : Controller() { gameState.set(state) canSkip.set(false) - // I don't know why orderedColors becomes an empty array and results in CurrentColor being inaccessible (throwing error) when the game ended, - // but this is how we can avoid it for now TODO("fix this in the plugin") - if (state.orderedColors.isNotEmpty()) { - previousColor.set(currentColor.get()) - currentColor.set(state.currentColor) - currentTeam.set(state.currentTeam) - } - playerNames.set(state.playerNames) - undeployedRedPieces.set(state.undeployedPieceShapes(Color.RED)) - undeployedBluePieces.set(state.undeployedPieceShapes(Color.BLUE)) - undeployedGreenPieces.set(state.undeployedPieceShapes(Color.GREEN)) - undeployedYellowPieces.set(state.undeployedPieceShapes(Color.YELLOW)) + previousColor.set(currentColor.get()) + currentColor.set(state.currentColor) + currentTeam.set(state.currentTeam) boardController.board.boardProperty().set(state.board) - validRedPieces.set(ArrayList()) - validBluePieces.set(ArrayList()) - validGreenPieces.set(ArrayList()) - validYellowPieces.set(ArrayList()) + undeployedPieces.forEach { (color, pieces) -> + pieces.set(state.undeployedPieceShapes(color)) + } + validPieces.forEach { (_, pieces) -> + pieces.set(emptyList()) + } + availableTurns.set(max(availableTurns.get(), state.turn)) + playerNames.set(state.playerNames) currentTurn.set(state.turn) currentRound.set(state.round) teamOneScore.set(state.getPointsForPlayer(Team.ONE)) @@ -215,15 +205,10 @@ class GameController : Controller() { isHumanTurn.set(true) canSkip.set(!gameEnded() && isHumanTurn.get() && !GameRuleLogic.isFirstMove(state)) boardController.calculateIsPlaceableBoard(state.board, state.currentColor) - - when (state.currentColor) { - Color.RED -> validRedPieces - Color.BLUE -> validBluePieces - Color.GREEN -> validGreenPieces - Color.YELLOW -> validYellowPieces - }.set(state.undeployedPieceShapes(state.currentColor).filter { shape -> - moves[shape]!!.isNotEmpty() - } as ArrayList?) + + validPieces[state.currentColor]!!.set( + state.undeployedPieceShapes(state.currentColor) + .filter { shape -> moves[shape]!!.isNotEmpty() }) } subscribe { event -> gameResult.set(event.result) @@ -238,10 +223,7 @@ class GameController : Controller() { availableTurns.set(0) currentTurn.set(0) currentRound.set(0) - undeployedRedPieces.set(PieceShape.values().toList()) - undeployedBluePieces.set(PieceShape.values().toList()) - undeployedGreenPieces.set(PieceShape.values().toList()) - undeployedYellowPieces.set(PieceShape.values().toList()) + undeployedPieces.forEach { (_, pieces) -> pieces.set(PieceShape.values().toList()) } } fun selectPiece(piece: PiecesModel) { diff --git a/src/main/kotlin/sc/gui/view/GameView.kt b/src/main/kotlin/sc/gui/view/GameView.kt index 946e88a3..7a31dad1 100644 --- a/src/main/kotlin/sc/gui/view/GameView.kt +++ b/src/main/kotlin/sc/gui/view/GameView.kt @@ -4,25 +4,26 @@ import javafx.geometry.Insets import javafx.scene.input.KeyCode import javafx.scene.input.MouseButton import org.slf4j.LoggerFactory -import sc.gui.controller.* -import sc.plugin2021.* +import sc.gui.controller.GameController +import sc.plugin2021.Color +import sc.plugin2021.Rotation +import sc.plugin2021.Team import sc.plugin2021.util.Constants import tornadofx.* +import java.util.* class GameView : View() { private val gameController: GameController by inject() - private val redUndeployedPieces = UndeployedPiecesFragment(Color.RED, gameController.undeployedRedPieces, gameController.validRedPieces) - private val blueUndeployedPieces = UndeployedPiecesFragment(Color.BLUE, gameController.undeployedBluePieces, gameController.validBluePieces) - private val greenUndeployedPieces = UndeployedPiecesFragment(Color.GREEN, gameController.undeployedGreenPieces, gameController.validGreenPieces) - private val yellowUndeployedPieces = UndeployedPiecesFragment(Color.YELLOW, gameController.undeployedYellowPieces, gameController.validYellowPieces) - + private val undeployedPieces = EnumMap( + Color.values().associateWith { color -> + UndeployedPiecesFragment(color, gameController.undeployedPieces.getValue(color), gameController.validPieces.getValue(color)) + }) + private val leftPane = vbox { - this += blueUndeployedPieces - this += redUndeployedPieces + replaceChildren(*undeployedPieces.filterKeys { it.team == Team.ONE }.values.toTypedArray()) } private val rightPane = vbox { - this += yellowUndeployedPieces - this += greenUndeployedPieces + replaceChildren(*undeployedPieces.filterKeys { it.team == Team.TWO }.values.toTypedArray()) } val game = borderpane { top(StatusView::class) @@ -105,10 +106,7 @@ class GameView : View() { } init { - redUndeployedPieces.root.prefHeightProperty().bind(root.heightProperty()) - blueUndeployedPieces.root.prefHeightProperty().bind(root.heightProperty()) - yellowUndeployedPieces.root.prefHeightProperty().bind(root.heightProperty()) - greenUndeployedPieces.root.prefHeightProperty().bind(root.heightProperty()) + undeployedPieces.forEach { (_, pieces) -> pieces.root.prefHeightProperty().bind(root.heightProperty()) } val resizer = ChangeListener { _, _, _ -> resize() } // responsive scaling diff --git a/src/main/kotlin/sc/gui/view/UndeployedPiecesFragment.kt b/src/main/kotlin/sc/gui/view/UndeployedPiecesFragment.kt index 7d6a478d..7cb92765 100644 --- a/src/main/kotlin/sc/gui/view/UndeployedPiecesFragment.kt +++ b/src/main/kotlin/sc/gui/view/UndeployedPiecesFragment.kt @@ -1,7 +1,6 @@ package sc.gui.view import javafx.beans.property.ObjectProperty -import javafx.beans.property.Property import javafx.collections.FXCollections import javafx.collections.ObservableList import javafx.geometry.Pos @@ -16,7 +15,11 @@ import sc.plugin2021.Color import sc.plugin2021.PieceShape import tornadofx.* -class UndeployedPiecesFragment(private val color: Color, undeployedPieces: Property>, validPieces: ObjectProperty>) : Fragment() { +class UndeployedPiecesFragment( + private val color: Color, + undeployedPieces: ObjectProperty>, + validPieces: ObjectProperty> +) : Fragment() { val controller: GameController by inject() private val boardController: BoardController by inject() private val shapes: ObservableList = FXCollections.observableArrayList(undeployedPieces.value) From 609ac1eb1443e8db71cf415664ad4ad0c2190d86 Mon Sep 17 00:00:00 2001 From: xerus2000 <27jf@pm.me> Date: Fri, 22 Jan 2021 14:26:46 +0100 Subject: [PATCH 2/6] fix(view): don't disable entire socha menubar item before starting game enableWhen applied to the whole "sochaIcon"-menu due to its scope --- backend | 2 +- src/main/kotlin/sc/gui/view/AppView.kt | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/backend b/backend index 7457426b..44bc82d8 160000 --- a/backend +++ b/backend @@ -1 +1 @@ -Subproject commit 7457426b06f8a1007c763e8b383b291d0efd6667 +Subproject commit 44bc82d83892898cd35dab67e3664641b2e2b3d7 diff --git a/src/main/kotlin/sc/gui/view/AppView.kt b/src/main/kotlin/sc/gui/view/AppView.kt index 77d6be57..502aff81 100644 --- a/src/main/kotlin/sc/gui/view/AppView.kt +++ b/src/main/kotlin/sc/gui/view/AppView.kt @@ -28,11 +28,12 @@ class AppView : View("Software-Challenge Germany") { logger.debug("Quitting!") Platform.exit() } - item("Neues Spiel", "Shortcut+N").action { + item("Neues Spiel", "Shortcut+N") { enableWhen(controller.model.currentView.isNotEqualTo(ViewType.GAME_CREATION)) - logger.debug("New Game!") - if (controller.model.currentView.get() == ViewType.GAME) { - alert( + action { + logger.debug("New Game!") + if (controller.model.currentView.get() == ViewType.GAME) { + alert( type = Alert.AlertType.CONFIRMATION, header = "Neues Spiel anfangen", content = "Willst du wirklich dein aktuelles Spiel verwerfen und ein neues anfangen?", @@ -42,9 +43,10 @@ class AppView : View("Software-Challenge Germany") { gameController.clearGame() } } - ) - } else if (controller.model.currentView.get() != ViewType.GAME_CREATION) { - controller.changeViewTo(ViewType.GAME_CREATION) + ) + } else if (controller.model.currentView.get() != ViewType.GAME_CREATION) { + controller.changeViewTo(ViewType.GAME_CREATION) + } } } item("Toggle Darkmode").action { From 5dd6695a78607132342c8ee523efcd64b9d2c684 Mon Sep 17 00:00:00 2001 From: xerus2000 <27jf@pm.me> Date: Fri, 22 Jan 2021 14:27:13 +0100 Subject: [PATCH 3/6] chore(gradle): fix run task working directory --- build.gradle.kts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index be11c79d..14612064 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -89,7 +89,10 @@ tasks { } run.configure { - workingDir(buildDir.resolve("run").apply { mkdirs() }) + workingDir(buildDir.resolve("tmp")) + doFirst { + workingDir.mkdirs() + } } val release by creating { From 080a46b8c87d575ca3f68d32f9832b8a89b0ed41 Mon Sep 17 00:00:00 2001 From: xerus2000 <27jf@pm.me> Date: Fri, 22 Jan 2021 18:56:44 +0100 Subject: [PATCH 4/6] fix: properly calculate valid pieces for human player Should fix #43 Since we do not have sufficient automated testing yet, I can only assume it to be fixed through experimentation and understanding of the code. --- .../kotlin/sc/gui/controller/GameController.kt | 15 ++++++++------- .../sc/gui/view/UndeployedPiecesFragment.kt | 17 +++++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/sc/gui/controller/GameController.kt b/src/main/kotlin/sc/gui/controller/GameController.kt index 4415e1e4..9d33590c 100644 --- a/src/main/kotlin/sc/gui/controller/GameController.kt +++ b/src/main/kotlin/sc/gui/controller/GameController.kt @@ -158,6 +158,8 @@ class GameController : Controller() { Color.values().associateWith { objectProperty(emptyList()) }) // use selected* to access the property of currentPiece in order to always correctly be automatically rebind + // TODO maybe this should be nullable rather than having a random default - + // then it could also be unset to prevent spurious errors like https://github.com/CAU-Kiel-Tech-Inf/gui/issues/43 val currentPiece = objectProperty(PiecesModel(Color.RED, PieceShape.MONO)) val selectedColor: ColorBinding = ColorBinding(currentPiece) val selectedShape: ShapeBinding = ShapeBinding(currentPiece) @@ -195,20 +197,19 @@ class GameController : Controller() { } subscribe { event -> val state = event.gameState - logger.debug("Human move request for ${state.currentColor}") - val moves = state.undeployedPieceShapes().map { it to GameRuleLogic.getPossibleMovesForShape(state, it) }.toMap() - logger.debug("Number of possible moves: ${moves.toList().flatMap { it.second }.size}") - + logger.debug("Human move request for {} - {} possible moves", + state.currentColor, + moves.values.sumBy { it.size }) + isHumanTurn.set(true) canSkip.set(!gameEnded() && isHumanTurn.get() && !GameRuleLogic.isFirstMove(state)) boardController.calculateIsPlaceableBoard(state.board, state.currentColor) - validPieces[state.currentColor]!!.set( - state.undeployedPieceShapes(state.currentColor) - .filter { shape -> moves[shape]!!.isNotEmpty() }) + validPieces.getValue(state.currentColor) + .set(moves.filterValues { it.isNotEmpty() }.keys) } subscribe { event -> gameResult.set(event.result) diff --git a/src/main/kotlin/sc/gui/view/UndeployedPiecesFragment.kt b/src/main/kotlin/sc/gui/view/UndeployedPiecesFragment.kt index 7cb92765..968eba20 100644 --- a/src/main/kotlin/sc/gui/view/UndeployedPiecesFragment.kt +++ b/src/main/kotlin/sc/gui/view/UndeployedPiecesFragment.kt @@ -111,18 +111,19 @@ class UndeployedPiecesFragment( unplayableNotice.isVisible = turn != 0 && !controller.isValidColor(color) } - validPieces.addListener { _, _, new -> - piecesList.forEach { - if(new.contains(it.key)) { - it.value.removeClass(AppStyle.pieceUnselectable) - } else if (!it.value.hasClass(AppStyle.pieceUnselectable)) { - it.value.addClass(AppStyle.pieceUnselectable) + validPieces.addListener { _, _, value -> + piecesList.forEach { (piece, box) -> + if(value.contains(piece)) { + box.removeClass(AppStyle.pieceUnselectable) + } else if (!box.hasClass(AppStyle.pieceUnselectable)) { + box.addClass(AppStyle.pieceUnselectable) } } if (controller.currentColor.get() == color) { - if (new.isNotEmpty()) { - pieces[new.last()]?.model?.let { controller.selectPiece(it) } + logger.debug("Current color ${color.name} can place $value") + if (value.isNotEmpty()) { + controller.selectPiece(pieces.filterKeys { it in value }.values.last().model) } } } From 42fa6c4e758b2887001c2a68acc0d049f9b8feb0 Mon Sep 17 00:00:00 2001 From: xerus2000 <27jf@pm.me> Date: Mon, 25 Jan 2021 12:31:06 +0100 Subject: [PATCH 5/6] style(gradle): reformat buildscript --- build.gradle.kts | 127 +++++++++++++++++++++++------------------------ 1 file changed, 62 insertions(+), 65 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 14612064..3231d3df 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,26 +5,26 @@ val versionNumber = "21.2.1" val minJavaVersion = JavaVersion.VERSION_11 plugins { - val minJavaVersion = JavaVersion.VERSION_11 // Declared twice because plugins block has its own scope - require(JavaVersion.current() >= minJavaVersion) { - "Building requires at least JDK $minJavaVersion - please look into the README." - } - - application - kotlin("jvm") version "1.4.20" - id("org.openjfx.javafxplugin") version "0.0.9" - id("com.github.johnrengelman.shadow") version "6.1.0" - - id("com.github.ben-manes.versions") version "0.33.0" - id("se.patrikerdes.use-latest-versions") version "0.2.15" + val minJavaVersion = JavaVersion.VERSION_11 // Declared twice because plugins block has its own scope + require(JavaVersion.current() >= minJavaVersion) { + "Building requires at least JDK $minJavaVersion - please look into the README" + } + + application + kotlin("jvm") version "1.4.20" + id("org.openjfx.javafxplugin") version "0.0.9" + id("com.github.johnrengelman.shadow") version "6.1.0" + + id("com.github.ben-manes.versions") version "0.33.0" + id("se.patrikerdes.use-latest-versions") version "0.2.15" } group = "sc.gui" version = versionNumber try { - // Add hash suffix if git is available - version = version.toString() + "-" + Runtime.getRuntime().exec(arrayOf("git", "rev-parse", "--short", "--verify", "HEAD")).inputStream.reader().readText().trim() -} catch(_: java.io.IOException) { + // Add hash suffix if git is available + version = "$version-" + Runtime.getRuntime().exec(arrayOf("git", "rev-parse", "--short", "--verify", "HEAD")).inputStream.reader().readText().trim() +} catch (_: java.io.IOException) { } application { @@ -39,70 +39,67 @@ application { // For accessing InputMap used in RangeSliderBehavior "--add-exports=javafx.controls/com.sun.javafx.scene.control.inputmap=ALL-UNNAMED" ) - } repositories { mavenCentral() - maven { - url = uri("https://oss.sonatype.org/content/repositories/snapshots") - } + maven("https://oss.sonatype.org/content/repositories/snapshots") } val backend = gradle.includedBuilds.last() dependencies { - implementation(kotlin("stdlib-jdk8")) - - implementation(kotlin("reflect")) + implementation(kotlin("stdlib-jdk8")) + + implementation(kotlin("reflect")) implementation("no.tornado", "tornadofx", "2.0.0-SNAPSHOT") { exclude("org.jetbrains.kotlin", "kotlin-reflect") } - implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.3") + implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.3") implementation(fileTree(backend.name + "/server/build/runnable") { include("**/*.jar") }) } tasks { - compileJava { - options.release.set(minJavaVersion.majorVersion.toInt()) - } - processResources { - doFirst{ - sourceSets.main.get().resources.srcDirs.first().resolve("version.txt").writeText(version.toString()) - } - } - withType { - dependsOn(backend.task(":server:deploy")) - kotlinOptions{ - jvmTarget = minJavaVersion.toString() - freeCompilerArgs = listOf("-Xjvm-default=all") - } - } - - javafx { - version = "13" - modules("javafx.controls", "javafx.fxml", "javafx.base", "javafx.graphics") - } - - shadowJar { - destinationDirectory.set(buildDir) - archiveClassifier.set(OperatingSystem.current().familyName) - } - - run.configure { - workingDir(buildDir.resolve("tmp")) + compileJava { + options.release.set(minJavaVersion.majorVersion.toInt()) + } + processResources { + doFirst { + sourceSets.main.get().resources.srcDirs.single().resolve("version.txt").writeText(version.toString()) + } + } + withType { + dependsOn(backend.task(":server:deploy")) + kotlinOptions { + jvmTarget = minJavaVersion.toString() + freeCompilerArgs = listOf("-Xjvm-default=all") + } + } + + javafx { + version = "13" + modules("javafx.controls", "javafx.fxml", "javafx.base", "javafx.graphics") + } + + shadowJar { + destinationDirectory.set(buildDir) + archiveClassifier.set(OperatingSystem.current().familyName) + } + + run.configure { + workingDir(buildDir.resolve("tmp")) doFirst { - workingDir.mkdirs() - } - } - - val release by creating { - dependsOn(check) - group = "distribution" - description = "Creates and pushes a tagged commit with the current version" - doLast { - exec { commandLine("git", "commit", "-a", "-m", "release: $versionNumber") } - exec { commandLine("git", "tag", versionNumber) } - exec { commandLine("git", "push", "origin", versionNumber, "master") } - } - } + workingDir.mkdirs() + } + } + + val release by creating { + dependsOn(check) + group = "distribution" + description = "Creates and pushes a tagged commit with the current version" + doLast { + exec { commandLine("git", "commit", "-a", "-m", "release: $versionNumber") } + exec { commandLine("git", "tag", versionNumber) } + exec { commandLine("git", "push", "origin", versionNumber, "master") } + } + } } From 19934155821f96f80462092984689cfce24f5308 Mon Sep 17 00:00:00 2001 From: xerus2000 <27jf@pm.me> Date: Mon, 25 Jan 2021 12:40:11 +0100 Subject: [PATCH 6/6] chore(gradle): update gradle --- build.gradle.kts | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3231d3df..afbecf41 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,7 @@ plugins { id("org.openjfx.javafxplugin") version "0.0.9" id("com.github.johnrengelman.shadow") version "6.1.0" - id("com.github.ben-manes.versions") version "0.33.0" + id("com.github.ben-manes.versions") version "0.36.0" id("se.patrikerdes.use-latest-versions") version "0.2.15" } @@ -28,7 +28,7 @@ try { } application { - mainClassName = "sc.gui.GuiAppKt" + mainClassName = "sc.gui.GuiAppKt" // not migrating from legacy because of https://github.com/johnrengelman/shadow/issues/609 // these are required because of using JDK >8, // see https://github.com/controlsfx/controlsfx/wiki/Using-ControlsFX-with-JDK-9-and-above applicationDefaultJvmArgs = listOf( @@ -53,7 +53,7 @@ dependencies { implementation(kotlin("reflect")) implementation("no.tornado", "tornadofx", "2.0.0-SNAPSHOT") { exclude("org.jetbrains.kotlin", "kotlin-reflect") } - implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.3") + implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.4") implementation(fileTree(backend.name + "/server/build/runnable") { include("**/*.jar") }) } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 12d38de6..80cf08e7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists