Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/main/kotlin/sc/gui/controller/BlokusController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import javafx.beans.property.Property
import javafx.beans.value.ObservableValue
import org.slf4j.LoggerFactory
import sc.api.plugins.Coordinates
import sc.api.plugins.IMove
import sc.api.plugins.Team
import sc.gui.GameOverEvent
import sc.gui.GameReadyEvent
Expand Down Expand Up @@ -254,6 +255,12 @@ class BlokusController : Controller() {
fun scroll(deltaY: Double) {
currentPiece.get().scroll(deltaY)
}

fun sendHumanMove(move: IMove) {
fire(HumanMoveAction(move.also {
logger.debug("Human Move: {}", it)
}))
}

companion object {
private val logger = LoggerFactory.getLogger(BlokusController::class.java)
Expand Down
39 changes: 39 additions & 0 deletions src/main/kotlin/sc/gui/view/PlayerOneView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package sc.gui.view

import javafx.beans.binding.DoubleBinding
import javafx.geometry.Insets
import javafx.geometry.Pos
import sc.api.plugins.Team
import sc.gui.controller.BlokusController
import sc.gui.model.GameModel
import sc.gui.view.game.SkipMoveButton
import sc.plugin2027.Color
import tornadofx.*

class PlayerOneView(gameController: BlokusController, gridSize: DoubleBinding): View() {
private val game: GameModel by inject()

// This is a vbox since all elements are below each other.
override val root = vbox {
useMaxWidth = true
alignment = Pos.TOP_CENTER
add(playerLabel(game, Team.ONE))
add(UndeployedPiecesFragment(
Color.BLUE,
gameController.undeployedPieces.getValue(Color.BLUE),
gameController.validPieces.getValue(Color.BLUE),
gridSize
))
add(UndeployedPiecesFragment(
Color.RED,
gameController.undeployedPieces.getValue(Color.RED),
gameController.validPieces.getValue(Color.RED),
gridSize
))
add(SkipMoveButton(gameController, gridSize, Team.ONE))
// Run this later since scene is not ready yet
runLater {
this.padding = Insets(0.0, 0.0, 0.0, 0.0)
}
}
}
39 changes: 39 additions & 0 deletions src/main/kotlin/sc/gui/view/PlayerTwoView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package sc.gui.view

import javafx.beans.binding.DoubleBinding
import javafx.geometry.Insets
import javafx.geometry.Pos
import sc.api.plugins.Team
import sc.gui.controller.BlokusController
import sc.gui.model.GameModel
import sc.gui.view.game.SkipMoveButton
import sc.plugin2027.Color
import tornadofx.*

class PlayerTwoView(gameController: BlokusController, gridSize: DoubleBinding): View() {
private val game: GameModel by inject()

// This is a vbox since all elements are below each other.
override val root = vbox {
useMaxWidth = true
alignment = Pos.TOP_CENTER
add(playerLabel(game, Team.TWO))
add(UndeployedPiecesFragment(
Color.YELLOW,
gameController.undeployedPieces.getValue(Color.YELLOW),
gameController.validPieces.getValue(Color.YELLOW),
gridSize
))
add(UndeployedPiecesFragment(
Color.GREEN,
gameController.undeployedPieces.getValue(Color.GREEN),
gameController.validPieces.getValue(Color.GREEN),
gridSize
))
add(SkipMoveButton(gameController, gridSize, Team.TWO))
// Run this later since scene is not ready yet
runLater {
this.padding = Insets(0.0, 0.0, 0.0, 0.0)
}
}
}
78 changes: 62 additions & 16 deletions src/main/kotlin/sc/gui/view/StatusView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import javafx.geometry.Pos
import javafx.scene.control.Label
import javafx.scene.layout.Priority
import javafx.scene.paint.Color
import javafx.scene.text.Font
import javafx.scene.text.TextAlignment
import sc.api.plugins.ITeam
import sc.api.plugins.Team
Expand All @@ -14,6 +15,41 @@ import sc.gui.model.GameModel
import sc.gui.strings
import tornadofx.*

fun decodeXmlEntities(toDecode: String): String {
// if ('&' !in toDecode) return toDecode
// Replace potentially bad characters
return toDecode.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&#38;", "&")
.replace("&amp;", "&")
.replace("&quot;", "\"")
}

fun playerLabel(game: GameModel, team: Team) =
Label().apply {
// Add string to display for team including teamStats.
textProperty().bind(
game.gameState.stringBinding { state ->
state?.teamStats(team)?.takeUnless { it.isEmpty() }?.let { stats ->
stats.joinToString(
"\n",
"${decodeXmlEntities(game.playerNames[team.index])} (${strings["color.${team.color}"]})\n"
) { stat ->
"${stat.label} ${stat.icon?.let { if(stat.value > 0) it.repeat(stat.value) else "-" } ?: stat.value}"
}
}
})
// Handle theming
textFillProperty().bind(AppModel.darkMode.objectBinding {
if(it == true)
Color.hsb(Color.valueOf(team.color).hue, .4, 1.0)
else
Color.hsb(Color.valueOf(team.color).hue, .8, .6)
})
font = Font(AppStyle.fontSizeBig.value)

}

class StatusBinding(private val game: GameModel): StringBinding() {
init {
bind(game.gameStarted, game.currentTeam, game.gameResult, game.playerNames, game.atLatestTurn)
Expand All @@ -23,11 +59,11 @@ class StatusBinding(private val game: GameModel): StringBinding() {
if(game.gameStarted.value && game.atLatestTurn.value || game.gameResult.value != null)
game.gameResult.takeIf { game.atLatestTurn.value }?.get()?.let { gameResult ->
"""
${gameResult.win?.winner?.let { "${it.displayName} hat gewonnen!" } ?: "Unentschieden"}
${gameResult.win?.reason?.message?.replace(" brig", " übrig").orEmpty()}
${gameResult.win?.winner?.let { "${decodeXmlEntities(it.displayName)} hat gewonnen!" } ?: "Unentschieden"}
${decodeXmlEntities(gameResult.win?.reason?.message?.replace(" brig", " übrig").orEmpty())}
""".trimIndent().trim('\n')
} ?: "${game.currentTeam.value.displayName} am Zug"
else game.playerNames.joinToString(" vs ")
} ?: "${decodeXmlEntities(game.currentTeam.value.displayName)} am Zug"
else game.playerNames.map { decodeXmlEntities(it) }.joinToString(" vs ")

val ITeam.displayName
get() = index.let { game.playerNames.getOrNull(it) ?: "Spieler ${it + 1}" }
Expand All @@ -38,15 +74,22 @@ class ScoreBinding(private val game: GameModel): StringBinding() {
bind(game.gameStarted, game.gameState)
}

override fun computeValue(): String =
if(game.gameStarted.value)
"Runde ${game.getCurrentRound()} - " +
game.gameState.value?.run {
Team.values().sortedBy { it != startTeam }.joinToString(" : ") {
getPointsForTeam(it).first().toString()
}
}
else "Drücke auf Start".takeUnless { game.gameOver.value && game.atLatestTurn.value }.orEmpty()
/**
* A ScoreBinding should have the following computed String value:
* Runde X - Punkte Team 1 : Punkte Team 2
* This point order is inverted if the startTeam is Team.TWO.
* This is only used for the finals.
*/
override fun computeValue(): String {
if(game.gameStarted.value) {
return "Runde ${(game.currentTurn.get() + 1) / 2} - " +
game.gameState.value?.getPointsForTeam(Team.ONE)?.first().toString() +
" : " +
game.gameState.value?.getPointsForTeam(Team.TWO)?.first().toString()
} else {
return "Drücke auf Start".takeUnless { game.gameOver.value && game.atLatestTurn.value }.orEmpty()
}
}
}

class StatusView: View() {
Expand All @@ -55,13 +98,15 @@ class StatusView: View() {
override val root = hbox {
useMaxWidth = true
alignment = Pos.CENTER
add(playerLabel(Team.ONE))
// Moved to PlayerOne/TwoView
// add(playerLabel(Team.ONE))
vbox(alignment = Pos.CENTER) {
this.spacing = AppStyle.fontSizeUnscaled.value
runLater {
prefWidthProperty().bind(scene.widthProperty().divide(2))
hgrow = Priority.ALWAYS
maxWidth = AppStyle.fontSizeRegular.value * 60
maxWidth = AppStyle.fontSizeRegular.value * 90
prefHeightProperty().bind(scene.heightProperty().divide(6))
}
addClass(AppStyle.statusLabel)
label(StatusBinding(game)) {
Expand All @@ -70,7 +115,8 @@ class StatusView: View() {
}
label(ScoreBinding(game))
}
add(playerLabel(Team.TWO))
// Moved to PlayerOne/TwoView
// add(playerLabel(Team.TWO))

//runLater {
// scene.root.apply {
Expand Down
9 changes: 5 additions & 4 deletions src/main/kotlin/sc/gui/view/game/BlokusBoard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ val Color.borderStyle
}

class BlokusBoard: GameBoard<GameState>() {
private val gameController: BlokusController by inject()
val gameController: BlokusController by inject()
val controller: BlokusBoardController by inject()

private val undeployedPieces = EnumMap(
Expand All @@ -56,11 +56,11 @@ class BlokusBoard: GameBoard<GameState>() {

private val leftPane = vbox {
alignment = Pos.TOP_LEFT
replaceChildren(*undeployedPieces.filterKeys { it.team == Team.ONE }.values.toTypedArray())
add(PlayerOneView(gameController, gridSize))
}
private val rightPane = vbox {
alignment = Pos.TOP_RIGHT
replaceChildren(*undeployedPieces.filterKeys { it.team == Team.TWO }.values.toTypedArray())
add(PlayerTwoView(gameController, gridSize))
}

/**
Expand All @@ -78,7 +78,8 @@ class BlokusBoard: GameBoard<GameState>() {
*/
val grid: GridPane = GridPane().addClass("grid").apply {
squareSize.listenImmediately { size ->
val cellSize = (size.toDouble() / Constants.BOARD_LENGTH).toInt().toDouble()
// +2 is the magic number that makes everything fit.
val cellSize = (size.toDouble() / (Constants.BOARD_LENGTH + 2)).toInt().toDouble()
columnConstraints.clear()
rowConstraints.clear()
repeat(Constants.BOARD_LENGTH) {
Expand Down
32 changes: 32 additions & 0 deletions src/main/kotlin/sc/gui/view/game/SkipMoveButton.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package sc.gui.view.game

import javafx.beans.binding.Bindings
import javafx.beans.binding.DoubleBinding
import javafx.scene.control.Button
import sc.api.plugins.Team
import sc.gui.controller.BlokusController
import sc.plugin2027.SkipMove
import tornadofx.onLeftClick

class SkipMoveButton(
private val gameController: BlokusController,
gridSize: DoubleBinding,
team: Team
) : Button("Aussetzen") {

init {
prefWidthProperty().bind(gridSize.multiply(5))
// Disable button if the current turn is not from a human player,
// we cannot skip (i.e. the first round),
// or if it is not our turn.
disableProperty().bind(Bindings.createBooleanBinding(
{ !(gameController.isHumanTurn.value && gameController.canSkip.value && gameController.currentColor.value.team == team) },
gameController.isHumanTurn,
gameController.canSkip,
gameController.currentColor
))
onLeftClick {
gameController.sendHumanMove(SkipMove(gameController.currentColor.value))
}
}
}
Loading