Skip to content

Commit b21d30d

Browse files
authored
feat(plugin): refactor some and make some logic methods public again (#303)
* feat(plugin): make move validation methods' throw optional One can choose to have them throw as before, or return a `MoveMistake` enum which allows for switching of the specific issue in question.
1 parent 6704b57 commit b21d30d

File tree

3 files changed

+122
-77
lines changed

3 files changed

+122
-77
lines changed

plugin/src/shared/sc/plugin2021/Field.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ import sc.api.plugins.IField
66
/**
77
* Beschreibt die Farbe eines bestimmten Felds.
88
* @property coordinates die Position des Felds, als [Coordinates]
9-
* @property content Die Farbe des Felds, als [FieldContent]
9+
* @property content Die Farbe des Felds, als [FieldContent] oder [Color]
1010
*/
1111
@XStreamAlias(value = "field")
1212
class Field(val coordinates: Coordinates, val content: FieldContent): IField {
13+
14+
constructor(coordinates: Coordinates, content: Color): this(coordinates, +content) {}
15+
16+
val isEmpty = content == FieldContent.EMPTY
17+
1318
override fun toString(): String = "'$content $coordinates'"
1419

1520
override fun equals(other: Any?): Boolean {

plugin/src/shared/sc/plugin2021/util/GameRuleLogic.kt

Lines changed: 96 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ object GameRuleLogic {
4444
@JvmStatic
4545
fun performMove(gameState: GameState, move: Move) {
4646
if (Constants.VALIDATE_MOVE)
47-
validateMoveColor(gameState, move)
47+
validateMoveColor(gameState, move, true)
4848

4949
when (move) {
5050
is SkipMove -> performSkipMove(gameState)
@@ -53,31 +53,51 @@ object GameRuleLogic {
5353
gameState.lastMove = move
5454
}
5555

56-
/** Check if the given [move] has the right [Color]. */
56+
/** Prüfe, ob die Farbe des gegebenen [Move]s der aktiven Farbe des [GameState]s entspricht. */
5757
@JvmStatic
58-
private fun validateMoveColor(gameState: GameState, move: Move) {
58+
fun validateMoveColor(gameState: GameState, move: Move, throws: Boolean = false): MoveMistake? {
5959
if (move.color != gameState.currentColor)
60-
throw InvalidMoveException("Expected move from ${gameState.currentColor}", move)
60+
if (throws) {
61+
throw InvalidMoveException("Expected move from ${gameState.currentColor}", move)
62+
} else {
63+
return MoveMistake.WRONG_COLOR
64+
}
65+
return null
6166
}
6267

63-
/** Check if the given [move] is able to be performed for the given [gameState]. */
68+
/**
69+
* Prüfe, ob der gegebene [SetMove] gesetzt werden könnte.
70+
* @param throws wenn true, wirft die Methode bei invaliden Zügen einen Fehler.
71+
*
72+
* @return gibt einen [MoveMistake] zurück, wenn der Zug nicht valide wahr, ansonsten null
73+
*/
6474
@JvmStatic
65-
private fun validateSetMove(gameState: GameState, move: SetMove) {
75+
fun validateSetMove(gameState: GameState, move: SetMove, throws: Boolean = false): MoveMistake? {
6676
// Check whether the color's move is currently active
67-
validateMoveColor(gameState, move)
77+
return validateMoveColor(gameState, move, throws) ?:
6878
// Check whether the shape is valid
69-
validateShape(gameState, move.piece.kind, move.color)
79+
validateShape(gameState, move.piece.kind, move.color, throws) ?:
7080
// Check whether the piece can be placed
71-
validateSetMove(gameState.board, move)
81+
validateSetMove(gameState.board, move, throws) ?:
7282

7383
if (isFirstMove(gameState)) {
7484
// Check if it is placed correctly in a corner
7585
if (move.piece.coordinates.none { isOnCorner(it)})
76-
throw InvalidMoveException("The Piece isn't located in a corner", move)
86+
if (throws) {
87+
throw InvalidMoveException("The Piece isn't located in a corner", move)
88+
} else {
89+
MoveMistake.NOT_IN_CORNER
90+
}
91+
else null
7792
} else {
7893
// Check if the piece is connected to at least one tile of same color by corner
79-
if (move.piece.coordinates.none { cornersOnColor(gameState.board, it, move.color) })
80-
throw InvalidMoveException("${move.piece} shares no corner with another piece of same color", move)
94+
if (move.piece.coordinates.none { cornersOnColor(gameState.board, Field(it, move.color)) })
95+
if (throws) {
96+
throw InvalidMoveException("${move.piece} shares no corner with another piece of same color", move)
97+
} else {
98+
MoveMistake.NO_SHARED_CORNER
99+
}
100+
else null
81101
}
82102
}
83103

@@ -87,7 +107,7 @@ object GameRuleLogic {
87107
validateSetMove(gameState, move)
88108

89109
if (Constants.VALIDATE_MOVE)
90-
validateSetMove(gameState, move)
110+
validateSetMove(gameState, move, true)
91111

92112
performSetMove(gameState.board, move)
93113
gameState.undeployedPieceShapes(move.color).remove(move.piece.kind)
@@ -100,50 +120,75 @@ object GameRuleLogic {
100120
gameState.tryAdvance()
101121
}
102122

103-
/** Validate the [PieceShape] of a [SetMove] depending on the current [GameState]. */
123+
/**
124+
* Prüfe, ob der gegebene Spielstein auf dem Spielfeld platziert werden könnte.
125+
* Fehler treten auf, wenn
126+
* - im ersten Zug nicht der vorgegebene Stein
127+
* - in nachfolgenden Zügen bereits gesetzte Steine
128+
* gesetzt werden würde(n).
129+
*/
104130
@JvmStatic
105-
private fun validateShape(gameState: GameState, shape: PieceShape, color: Color = gameState.currentColor) {
131+
fun validateShape(gameState: GameState, shape: PieceShape, color: Color = gameState.currentColor, throws: Boolean = false): MoveMistake? {
106132
if (isFirstMove(gameState)) {
107133
if (shape != gameState.startPiece)
108-
throw InvalidMoveException("$shape is not the requested first shape, ${gameState.startPiece}")
134+
if (throws) {
135+
throw InvalidMoveException("$shape is not the requested first shape, ${gameState.startPiece}")
136+
} else {
137+
return MoveMistake.WRONG_SHAPE
138+
}
109139
} else {
110140
if (!gameState.undeployedPieceShapes(color).contains(shape))
111-
throw InvalidMoveException("Piece $shape has already been placed before")
141+
if (throws) {
142+
throw InvalidMoveException("Piece $shape has already been placed before")
143+
} else {
144+
return MoveMistake.DUPLICATE_SHAPE
145+
}
112146
}
147+
return null
113148
}
114149

115150
/**
116-
* Prüft, ob der gegebene [Move] zulässig ist.
151+
* Prüfe, ob der gegebene [Move] zulässig ist.
117152
* @param gameState der aktuelle Spielstand
118153
* @param move der zu überprüfende Zug
119154
*
120155
* @return ob der Zug zulässig ist
121156
*/
122157
@JvmStatic
123158
fun isValidSetMove(gameState: GameState, move: SetMove) =
124-
try {
125-
validateSetMove(gameState, move)
126-
true
127-
} catch (e: InvalidMoveException) {
128-
false
129-
}
159+
validateSetMove(gameState, move) == null
130160

131-
/** Validate a [SetMove] on a [board]. */
161+
/** Prüfe, ob der gegebene [SetMove] auf dem [Board] platziert werden kann. */
132162
@JvmStatic
133-
private fun validateSetMove(board: Board, move: SetMove) {
163+
fun validateSetMove(board: Board, move: SetMove, throws: Boolean = false): MoveMistake? {
164+
// throw IndexOutOfBounds if the initial position only is out of bounds
165+
board[move.piece.position]
134166
move.piece.coordinates.forEach {
135167
try {
136168
board[it]
137169
} catch (e: ArrayIndexOutOfBoundsException) {
138-
throw InvalidMoveException("Field $it is out of bounds", move)
170+
if (throws) {
171+
throw InvalidMoveException("Field $it is out of bounds", move)
172+
} else {
173+
return MoveMistake.OUT_OF_BOUNDS
174+
}
139175
}
140176
// Checks if a part of the piece is obstructed
141177
if (board.isObstructed(it))
142-
throw InvalidMoveException("Field $it already belongs to ${board[it].content}", move)
178+
if (throws) {
179+
throw InvalidMoveException("Field $it already belongs to ${board[it].content}", move)
180+
} else {
181+
return MoveMistake.OBSTRUCTED
182+
}
143183
// Checks if a part of the piece would border on another piece of same color
144-
if (bordersOnColor(board, it, move.color))
145-
throw InvalidMoveException("Field $it already borders on ${move.color}", move)
184+
if (bordersOnColor(board, Field(it, move.color)))
185+
if (throws) {
186+
throw InvalidMoveException("Field $it already borders on ${move.color}", move)
187+
} else {
188+
return MoveMistake.TOUCHES_SAME_COLOR
189+
}
146190
}
191+
return null
147192
}
148193

149194
/** Place a Piece on the given [board] according to [move]. */
@@ -154,42 +199,52 @@ object GameRuleLogic {
154199
}
155200
}
156201

202+
@JvmStatic
203+
fun validateSkipMove(gameState: GameState, throws: Boolean = false): MoveMistake? {
204+
if (isFirstMove(gameState))
205+
if (throws) {
206+
throw InvalidMoveException("Can't Skip on first round", SkipMove(gameState.currentColor))
207+
} else {
208+
return MoveMistake.SKIP_FIRST_TURN
209+
}
210+
return null
211+
}
212+
157213
/** Skip a turn. */
158214
@JvmStatic
159215
private fun performSkipMove(gameState: GameState) {
160216
if (!gameState.tryAdvance())
161217
logger.error("Couldn't proceed to next turn!")
162-
if (isFirstMove(gameState))
163-
throw InvalidMoveException("Can't Skip on first round", SkipMove(gameState.currentColor))
218+
validateSkipMove(gameState, true)
164219
}
165220

166-
/** Check if the given [position] already borders on another piece of same [color]. */
221+
/** Prüfe, ob das gegebene [Field] bereits an eins mit gleicher Farbe angrenzt. */
167222
@JvmStatic
168-
private fun bordersOnColor(board: Board, position: Coordinates, color: Color): Boolean = listOf(
223+
fun bordersOnColor(board: Board, field: Field): Boolean = listOf(
169224
Vector(1, 0),
170225
Vector(0, 1),
171226
Vector(-1, 0),
172227
Vector(0, -1)).any {
173228
try {
174-
board[position + it].content == +color
229+
board[field.coordinates + it].content == field.content && !field.isEmpty
175230
} catch (e: ArrayIndexOutOfBoundsException) { false }
176231
}
177232

178-
/** Return true if the given [Coordinates] touch a corner of a field of same color. */
233+
/** Prüfe, ob das gegebene Feld an die Ecke eines Feldes gleicher Farbe angrenzt. */
179234
@JvmStatic
180-
private fun cornersOnColor(board: Board, position: Coordinates, color: Color): Boolean = listOf(
235+
fun cornersOnColor(board: Board, field: Field): Boolean = listOf(
181236
Vector(1, 1),
182237
Vector(1, -1),
183238
Vector(-1, -1),
184239
Vector(-1, 1)).any {
185240
try {
186-
board[position + it].content == +color
241+
board[field.coordinates + it].content == field.content && !field.isEmpty
187242
} catch (e: ArrayIndexOutOfBoundsException) { false }
188243
}
189244

190-
/** Return true if the given [Coordinates] are a corner. */
245+
/** Prüfe, ob die gegebene Position eine Ecke des Spielfelds ist. */
191246
@JvmStatic
192-
private fun isOnCorner(position: Coordinates): Boolean =
247+
fun isOnCorner(position: Coordinates): Boolean =
193248
Corner.values().any { it.position == position }
194249

195250
/** Gib zurück, ob sich der [GameState] noch in der ersten Runde befindet. */
@@ -209,41 +264,6 @@ object GameRuleLogic {
209264
fun getPossibleMoves(gameState: GameState) =
210265
streamPossibleMoves(gameState).toSet()
211266

212-
/** Return a list of all possible SetMoves, regardless of whether it's the first round. */
213-
@JvmStatic
214-
private fun getAllPossibleMoves(gameState: GameState) =
215-
streamAllPossibleMoves(gameState).toSet()
216-
217-
/** Return a list of possible SetMoves if it's the first round. */
218-
@JvmStatic
219-
private fun getPossibleStartMoves(gameState: GameState) =
220-
streamPossibleStartMoves(gameState).toSet()
221-
222-
/**
223-
* Return a list of all moves, impossible or not.
224-
* There's no real usage, except maybe for cases where no Move validation happens
225-
* if `Constants.VALIDATE_MOVE` is false, then this function should return the same
226-
* Set as `::getPossibleMoves`
227-
*/
228-
@JvmStatic
229-
private fun getAllMoves(): Set<SetMove> {
230-
val moves = mutableSetOf<SetMove>()
231-
for (color in Color.values()) {
232-
for (shape in PieceShape.values()) {
233-
for (rotation in Rotation.values()) {
234-
for (flip in listOf(false, true)) {
235-
for (y in 0 until Constants.BOARD_SIZE) {
236-
for (x in 0 until Constants.BOARD_SIZE) {
237-
moves.add(SetMove(Piece(color, shape, rotation, flip, Coordinates(x, y))))
238-
}
239-
}
240-
}
241-
}
242-
}
243-
}
244-
return moves
245-
}
246-
247267
/** Entferne alle Farben, die keine Steine mehr auf dem Feld platzieren können. */
248268
@JvmStatic
249269
fun removeInvalidColors(gameState: GameState) {
@@ -286,4 +306,4 @@ object GameRuleLogic {
286306
}
287307
}
288308
}.filter { isValidSetMove(gameState, it) }
289-
}
309+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package sc.plugin2021.util
2+
3+
/**
4+
* Wird optional bei Validierung von Zügen zurückgegeben, falls ein Zug nicht valide ist.
5+
* MoveMistakes entstehen bei Zügen, die theoretisch möglich sein könnten,
6+
* es aber bei dem jeweiligen Spielstand nicht sind.
7+
*/
8+
enum class MoveMistake(val msg: String = "") {
9+
WRONG_COLOR("Die Farbe des Zuges ist nicht an der Reihe"),
10+
NOT_IN_CORNER("Der erste Zug muss auf eine freie Ecke gesetzt werden"),
11+
NO_SHARED_CORNER("Alle Teile müssen ein vorheriges Teil gleicher Farbe über mindestens eine Ecke berühren"),
12+
WRONG_SHAPE("Der erste Zug muss den festgelegten Spielstein setzen"),
13+
SKIP_FIRST_TURN("Der erste Zug muss einen Stein setzen"),
14+
DUPLICATE_SHAPE("Der gewählte Stein wurde bereits gesetzt"),
15+
OUT_OF_BOUNDS("Der Spielstein passt nicht vollständig auf das Spielfeld"),
16+
OBSTRUCTED("Der Spielstein würde eine andere Farbe überlagern"),
17+
TOUCHES_SAME_COLOR("Der Spielstein berührt ein Feld gleicher Farbe");
18+
19+
override fun toString(): String = msg
20+
}

0 commit comments

Comments
 (0)