Skip to content
Merged
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
5 changes: 4 additions & 1 deletion plugin2026/src/main/kotlin/sc/plugin2026/GameState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ data class GameState @JvmOverloads constructor(
override fun getPointsForTeam(team: ITeam): IntArray =
intArrayOf(GameRuleLogic.greatestSwarmSize(board, team))

// TODO test if one player is unable to move he loses e.g. in corner
override val isOver: Boolean
get() = (Team.values().any { GameRuleLogic.isSwarmConnected(board, it) } && turn.mod(2) == 0) ||
this.getSensibleMoves().isEmpty() ||
turn / 2 >= PiranhaConstants.ROUND_LIMIT

override val winCondition: WinCondition?
Expand All @@ -48,6 +48,9 @@ data class GameState @JvmOverloads constructor(
Team.values().toList().maxByNoEqual { team -> GameRuleLogic.greatestSwarmSize(board, team) }
?.let { WinCondition(it, PiranhasWinReason.BIGGER_SWARM) }
?: WinCondition(null, WinReasonTie)
} else if (this.getSensibleMoves().isEmpty()) {
val team = this.currentTeam.opponent()
WinCondition(team, PiranhasWinReason.BLOCKED)
} else {
null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import sc.shared.*
enum class PiranhasWinReason(override val message: String, override val isRegular: Boolean = true): IWinReason {
BIGGER_SWARM("%s hat den größeren zusammenhängenden Schwarm"),
FIRST_UNION("%s hat zuerst alle Fische einer Farbe vereinigt"),
BLOCKED("%s hat den Gegner blockiert, sodass er keinen Zug mehr machen kann"),
}

class GamePlugin: IGamePlugin<Move> {
Expand Down
33 changes: 32 additions & 1 deletion plugin2026/src/test/kotlin/sc/plugin2026/GameRuleLogicTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import io.kotest.matchers.maps.*
import sc.api.plugins.Coordinates
import sc.api.plugins.Direction
import sc.api.plugins.Team
import sc.plugin2026.util.GameRuleLogic
import sc.plugin2026.util.*
import sc.shared.MoveMistake
import sc.shared.WinCondition

class GameRuleLogicTest: FunSpec({
context("swarm size") {
Expand Down Expand Up @@ -53,4 +54,34 @@ class GameRuleLogicTest: FunSpec({
GameRuleLogic.possibleMovesFor(board, fish) shouldHaveSize 3
}
}
/**
* Check if a player loses when no move is possible.
*/
context("losing by no moves") {
test("cornered") {
val board = Board()
// Remove all piranhas from the board
for(x in 0 until PiranhaConstants.BOARD_LENGTH) {
for(y in 0 until PiranhaConstants.BOARD_LENGTH) {
board[x, y] = FieldState.EMPTY
}
}

// Readd piranhas for the test
// Piranha at (0, 0) is blocked
board[0, 0] = FieldState.ONE_S
board[1, 0] = FieldState.TWO_S
board[1, 1] = FieldState.TWO_S
board[0, 1] = FieldState.TWO_S

// Piranha at (9, 9) is also blocked
board[9, 9] = FieldState.ONE_S
board[8, 9] = FieldState.TWO_S
board[8, 8] = FieldState.TWO_S
board[9, 8] = FieldState.TWO_S
val gameState = GameState(board = board, turn = 0)
gameState.isOver shouldBe true
gameState.winCondition shouldBe WinCondition(Team.TWO, PiranhasWinReason.BLOCKED)
}
}
})