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/build.gradle.kts b/build.gradle.kts index be11c79d..afbecf41 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,30 +5,30 @@ 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.36.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 { - 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( @@ -39,67 +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.4") 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("run").apply { 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") } - } - } + 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") } + } + } } 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 diff --git a/src/main/kotlin/sc/gui/controller/GameController.kt b/src/main/kotlin/sc/gui/controller/GameController.kt index 8c11c74b..9d33590c 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,20 +149,17 @@ 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 + // 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) @@ -180,24 +177,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)) @@ -205,25 +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) - - 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.getValue(state.currentColor) + .set(moves.filterValues { it.isNotEmpty() }.keys) } subscribe { event -> gameResult.set(event.result) @@ -238,10 +224,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/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 { 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..968eba20 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) @@ -108,18 +111,19 @@ class UndeployedPiecesFragment(private val color: Color, undeployedPieces: Prope 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) } } }