Skip to content

Commit 1f01202

Browse files
Merge pull request #123 from software-challenge/feat/capperReady
fix(blokus): special character support and long status labels
2 parents f06bb2b + 50241b8 commit 1f01202

4 files changed

Lines changed: 141 additions & 20 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package sc.gui.view
2+
3+
import javafx.beans.binding.DoubleBinding
4+
import javafx.geometry.Insets
5+
import javafx.geometry.Pos
6+
import sc.api.plugins.Team
7+
import sc.gui.controller.BlokusController
8+
import sc.gui.model.GameModel
9+
import sc.plugin2027.Color
10+
import tornadofx.*
11+
12+
class PlayerOneView(gameController: BlokusController, gridSize: DoubleBinding): View() {
13+
private val game: GameModel by inject()
14+
15+
// This is a vbox since all elements are below each other.
16+
override val root = vbox {
17+
useMaxWidth = true
18+
alignment = Pos.TOP_CENTER
19+
add(playerLabel(game, Team.ONE))
20+
add(UndeployedPiecesFragment(
21+
Color.BLUE,
22+
gameController.undeployedPieces.getValue(Color.BLUE),
23+
gameController.validPieces.getValue(Color.BLUE),
24+
gridSize
25+
))
26+
add(UndeployedPiecesFragment(
27+
Color.RED,
28+
gameController.undeployedPieces.getValue(Color.RED),
29+
gameController.validPieces.getValue(Color.RED),
30+
gridSize
31+
))
32+
// Run this later since scene is not ready yet
33+
runLater {
34+
this.padding = Insets(0.0, 0.0, 0.0, 0.0)
35+
}
36+
}
37+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package sc.gui.view
2+
3+
import javafx.beans.binding.DoubleBinding
4+
import javafx.geometry.Insets
5+
import javafx.geometry.Pos
6+
import sc.api.plugins.Team
7+
import sc.gui.controller.BlokusController
8+
import sc.gui.model.GameModel
9+
import sc.plugin2027.Color
10+
import tornadofx.*
11+
12+
class PlayerTwoView(gameController: BlokusController, gridSize: DoubleBinding): View() {
13+
private val game: GameModel by inject()
14+
15+
// This is a vbox since all elements are below each other.
16+
override val root = vbox {
17+
useMaxWidth = true
18+
alignment = Pos.TOP_CENTER
19+
add(playerLabel(game, Team.TWO))
20+
add(UndeployedPiecesFragment(
21+
Color.YELLOW,
22+
gameController.undeployedPieces.getValue(Color.YELLOW),
23+
gameController.validPieces.getValue(Color.YELLOW),
24+
gridSize
25+
))
26+
add(UndeployedPiecesFragment(
27+
Color.GREEN,
28+
gameController.undeployedPieces.getValue(Color.GREEN),
29+
gameController.validPieces.getValue(Color.GREEN),
30+
gridSize
31+
))
32+
// Run this later since scene is not ready yet
33+
runLater {
34+
this.padding = Insets(0.0, 0.0, 0.0, 0.0)
35+
}
36+
}
37+
}

src/main/kotlin/sc/gui/view/StatusView.kt

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import javafx.geometry.Pos
55
import javafx.scene.control.Label
66
import javafx.scene.layout.Priority
77
import javafx.scene.paint.Color
8+
import javafx.scene.text.Font
89
import javafx.scene.text.TextAlignment
910
import sc.api.plugins.ITeam
1011
import sc.api.plugins.Team
@@ -14,6 +15,41 @@ import sc.gui.model.GameModel
1415
import sc.gui.strings
1516
import tornadofx.*
1617

18+
fun decodeXmlEntities(toDecode: String): String {
19+
// if ('&' !in toDecode) return toDecode
20+
// Replace potentially bad characters
21+
return toDecode.replace("&lt;", "<")
22+
.replace("&gt;", ">")
23+
.replace("&#38;", "&")
24+
.replace("&amp;", "&")
25+
.replace("&quot;", "\"")
26+
}
27+
28+
fun playerLabel(game: GameModel, team: Team) =
29+
Label().apply {
30+
// Add string to display for team including teamStats.
31+
textProperty().bind(
32+
game.gameState.stringBinding { state ->
33+
state?.teamStats(team)?.takeUnless { it.isEmpty() }?.let { stats ->
34+
stats.joinToString(
35+
"\n",
36+
"${decodeXmlEntities(game.playerNames[team.index])} (${strings["color.${team.color}"]})\n"
37+
) { stat ->
38+
"${stat.label} ${stat.icon?.let { if(stat.value > 0) it.repeat(stat.value) else "-" } ?: stat.value}"
39+
}
40+
}
41+
})
42+
// Handle theming
43+
textFillProperty().bind(AppModel.darkMode.objectBinding {
44+
if(it == true)
45+
Color.hsb(Color.valueOf(team.color).hue, .4, 1.0)
46+
else
47+
Color.hsb(Color.valueOf(team.color).hue, .8, .6)
48+
})
49+
font = Font(AppStyle.fontSizeBig.value)
50+
51+
}
52+
1753
class StatusBinding(private val game: GameModel): StringBinding() {
1854
init {
1955
bind(game.gameStarted, game.currentTeam, game.gameResult, game.playerNames, game.atLatestTurn)
@@ -23,11 +59,11 @@ class StatusBinding(private val game: GameModel): StringBinding() {
2359
if(game.gameStarted.value && game.atLatestTurn.value || game.gameResult.value != null)
2460
game.gameResult.takeIf { game.atLatestTurn.value }?.get()?.let { gameResult ->
2561
"""
26-
${gameResult.win?.winner?.let { "${it.displayName} hat gewonnen!" } ?: "Unentschieden"}
27-
${gameResult.win?.reason?.message?.replace(" brig", " übrig").orEmpty()}
62+
${gameResult.win?.winner?.let { "${decodeXmlEntities(it.displayName)} hat gewonnen!" } ?: "Unentschieden"}
63+
${decodeXmlEntities(gameResult.win?.reason?.message?.replace(" brig", " übrig").orEmpty())}
2864
""".trimIndent().trim('\n')
29-
} ?: "${game.currentTeam.value.displayName} am Zug"
30-
else game.playerNames.joinToString(" vs ")
65+
} ?: "${decodeXmlEntities(game.currentTeam.value.displayName)} am Zug"
66+
else game.playerNames.map { decodeXmlEntities(it) }.joinToString(" vs ")
3167

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

41-
override fun computeValue(): String =
42-
if(game.gameStarted.value)
43-
"Runde ${game.getCurrentRound()} - " +
44-
game.gameState.value?.run {
45-
Team.values().sortedBy { it != startTeam }.joinToString(" : ") {
46-
getPointsForTeam(it).first().toString()
47-
}
48-
}
49-
else "Drücke auf Start".takeUnless { game.gameOver.value && game.atLatestTurn.value }.orEmpty()
77+
/**
78+
* A ScoreBinding should have the following computed String value:
79+
* Runde X - Punkte Team 1 : Punkte Team 2
80+
* This point order is inverted if the startTeam is Team.TWO.
81+
* This is only used for the finals.
82+
*/
83+
override fun computeValue(): String {
84+
if(game.gameStarted.value) {
85+
return "Runde ${(game.currentTurn.get() + 1) / 2} - " +
86+
game.gameState.value?.getPointsForTeam(Team.ONE)?.first().toString() +
87+
" : " +
88+
game.gameState.value?.getPointsForTeam(Team.TWO)?.first().toString()
89+
} else {
90+
return "Drücke auf Start".takeUnless { game.gameOver.value && game.atLatestTurn.value }.orEmpty()
91+
}
92+
}
5093
}
5194

5295
class StatusView: View() {
@@ -55,13 +98,15 @@ class StatusView: View() {
5598
override val root = hbox {
5699
useMaxWidth = true
57100
alignment = Pos.CENTER
58-
add(playerLabel(Team.ONE))
101+
// Moved to PlayerOne/TwoView
102+
// add(playerLabel(Team.ONE))
59103
vbox(alignment = Pos.CENTER) {
60104
this.spacing = AppStyle.fontSizeUnscaled.value
61105
runLater {
62106
prefWidthProperty().bind(scene.widthProperty().divide(2))
63107
hgrow = Priority.ALWAYS
64-
maxWidth = AppStyle.fontSizeRegular.value * 60
108+
maxWidth = AppStyle.fontSizeRegular.value * 90
109+
prefHeightProperty().bind(scene.heightProperty().divide(6))
65110
}
66111
addClass(AppStyle.statusLabel)
67112
label(StatusBinding(game)) {
@@ -70,7 +115,8 @@ class StatusView: View() {
70115
}
71116
label(ScoreBinding(game))
72117
}
73-
add(playerLabel(Team.TWO))
118+
// Moved to PlayerOne/TwoView
119+
// add(playerLabel(Team.TWO))
74120

75121
//runLater {
76122
// scene.root.apply {

src/main/kotlin/sc/gui/view/game/BlokusBoard.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ val Color.borderStyle
4141
}
4242

4343
class BlokusBoard: GameBoard<GameState>() {
44-
private val gameController: BlokusController by inject()
44+
val gameController: BlokusController by inject()
4545
val controller: BlokusBoardController by inject()
4646

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

5757
private val leftPane = vbox {
5858
alignment = Pos.TOP_LEFT
59-
replaceChildren(*undeployedPieces.filterKeys { it.team == Team.ONE }.values.toTypedArray())
59+
add(PlayerOneView(gameController, gridSize))
6060
}
6161
private val rightPane = vbox {
6262
alignment = Pos.TOP_RIGHT
63-
replaceChildren(*undeployedPieces.filterKeys { it.team == Team.TWO }.values.toTypedArray())
63+
add(PlayerTwoView(gameController, gridSize))
6464
}
6565

6666
/**
@@ -78,7 +78,8 @@ class BlokusBoard: GameBoard<GameState>() {
7878
*/
7979
val grid: GridPane = GridPane().addClass("grid").apply {
8080
squareSize.listenImmediately { size ->
81-
val cellSize = (size.toDouble() / Constants.BOARD_LENGTH).toInt().toDouble()
81+
// +2 is the magic number that makes everything fit.
82+
val cellSize = (size.toDouble() / (Constants.BOARD_LENGTH + 2)).toInt().toDouble()
8283
columnConstraints.clear()
8384
rowConstraints.clear()
8485
repeat(Constants.BOARD_LENGTH) {

0 commit comments

Comments
 (0)