Skip to content

Commit 2706c6c

Browse files
committed
feat(plugin26): swarm calculation with fish size
1 parent 6afa5b0 commit 2706c6c

File tree

3 files changed

+62
-43
lines changed

3 files changed

+62
-43
lines changed

plugin2026/src/main/kotlin/sc/plugin2026/Board.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ class Board(gameField: MutableTwoDBoard<FieldState> = randomFields()):
3232
fun getTeam(pos: Coordinates): Team? =
3333
this[pos].team
3434

35-
fun fieldsForTeam(team: ITeam): Collection<Coordinates> =
36-
filterValues { field -> field.team == team }.map { it.key }
35+
fun fieldsForTeam(team: ITeam): Map<Coordinates, Int> =
36+
filterValues { field -> field.team == team }
37+
.mapValues { (_, field) -> field.size }
3738

3839
companion object {
3940
/** Erstellt ein zufälliges Spielbrett. */

plugin2026/src/main/kotlin/sc/plugin2026/util/GameRuleLogic.kt

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -102,68 +102,52 @@ object GameRuleLogic {
102102
return returnSet
103103
}
104104

105-
private fun getSwarm(found: MutableSet<Coordinates>, swarm: MutableSet<Coordinates>): MutableSet<Coordinates> {
106-
if(swarm.isEmpty() && !found.isEmpty()) {
107-
val field = found.iterator().next()
108-
swarm.add(field)
109-
found.remove(field)
110-
}
105+
/** Called with a single fish in [swarm] and the [looseFishes] left,
106+
* recursively calling with neighbors added to [swarm] to find the whole swarm. */
107+
private fun getSwarm(looseFishes: Set<Coordinates>, swarm: List<Coordinates>): List<Coordinates> {
108+
val swarmNeighbours =
109+
swarm.flatMap { getDirectNeighbour(it, looseFishes) }
111110

112-
var tmpSwarm: MutableSet<Coordinates> = HashSet(swarm)
113-
// O(swarm.size()) time
114-
for(field in swarm) {
115-
// Constant time for both calls (max of 8 neighbors)
116-
val neighbours = getDirectNeighbour(field, found)
117-
tmpSwarm.addAll(neighbours)
111+
// only search on if any neighbors were added
112+
if(swarmNeighbours.isNotEmpty()) {
113+
return getSwarm(looseFishes - swarmNeighbours, swarm + swarmNeighbours)
118114
}
119-
120-
// O(found.size()*swarm.size()) time
121-
// FIXME: Might be improved O(swarm.size()) should be possible
122-
if(swarm.size != tmpSwarm.size) tmpSwarm = getSwarm(found, tmpSwarm)
123-
124-
swarm.addAll(tmpSwarm)
125-
126-
found.removeAll(swarm)
127115
return swarm
128116
}
129117

130118
@JvmStatic
131-
fun greatestSwarm(fieldsToCheck: Set<Coordinates>): Set<Coordinates> {
119+
fun greatestSwarm(fieldsToCheck: Map<Coordinates, Int>): Map<Coordinates, Int>? {
132120
// Make a copy, so there will be no conflict with direct calls.
133-
val occupiedFields: MutableSet<Coordinates> = HashSet(fieldsToCheck)
134-
var greatestSwarm: Set<Coordinates> = HashSet()
121+
val fieldsLeft = fieldsToCheck.keys.toMutableList()
135122
var maxSize = -1
123+
var maxSwarm: Map<Coordinates, Int>? = null
136124

137125
// this is a maximum of MAX_FISH iterations, so it is a linear iteration altogether
138-
while(!occupiedFields.isEmpty() && occupiedFields.size > maxSize) {
139-
val swarm: Set<Coordinates> = getSwarm(occupiedFields, HashSet())
140-
// TODO consider fish weights
141-
if(maxSize < swarm.size) {
142-
maxSize = swarm.size
143-
greatestSwarm = swarm
126+
while(!fieldsLeft.isEmpty() && fieldsLeft.size > maxSize) {
127+
val swarmCoords = getSwarm(fieldsToCheck.keys, listOf(fieldsLeft.removeLast()))
128+
fieldsLeft.removeAll(swarmCoords)
129+
val swarm = fieldsToCheck.filterKeys { swarmCoords.contains(it) }
130+
val swarmSize = swarm.values.sum()
131+
if(maxSize < swarmSize) {
132+
maxSwarm = swarm
133+
maxSize = swarmSize
144134
}
145135
}
146-
return greatestSwarm
136+
return maxSwarm
147137
}
148138

149139
@JvmStatic
150-
fun greatestSwarm(board: Board, team: ITeam): Set<Coordinates> {
151-
val occupiedFields = board.fieldsForTeam(team)
152-
return greatestSwarm(occupiedFields.toHashSet())
153-
}
140+
fun greatestSwarmSize(fields: Map<Coordinates, Int>): Int =
141+
greatestSwarm(fields)?.values?.sum() ?: -1
154142

155143
@JvmStatic
156144
fun greatestSwarmSize(board: Board, team: ITeam): Int =
157-
greatestSwarm(board, team).size
158-
159-
@JvmStatic
160-
fun greatestSwarmSize(set: Set<Coordinates>): Int =
161-
greatestSwarm(set).size
145+
greatestSwarmSize(board.fieldsForTeam(team))
162146

163147
@JvmStatic
164148
fun isSwarmConnected(board: Board, team: ITeam): Boolean {
165149
val fieldsWithFish = board.fieldsForTeam(team)
166-
val numGreatestSwarm: Int = greatestSwarmSize(fieldsWithFish.toHashSet())
167-
return numGreatestSwarm == fieldsWithFish.size
150+
val greatestSwarm = greatestSwarm(fieldsWithFish)
151+
return greatestSwarm?.size == fieldsWithFish.size
168152
}
169153
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package sc.plugin2026
2+
3+
import io.kotest.core.spec.style.FunSpec
4+
import io.kotest.inspectors.forAll
5+
import io.kotest.matchers.*
6+
import io.kotest.matchers.ints.*
7+
import sc.api.plugins.Team
8+
import sc.plugin2026.util.GameRuleLogic
9+
10+
class GameRuleLogicTest: FunSpec({
11+
context("swarm size") {
12+
test("generated board") {
13+
val newBoard = Board()
14+
Team.values().forAll { team ->
15+
GameRuleLogic.greatestSwarmSize(newBoard, team) shouldBeGreaterThanOrEqual 8
16+
GameRuleLogic.isSwarmConnected(newBoard, team) shouldBe false
17+
}
18+
}
19+
test("board of only red fishes") {
20+
val board = Board(arrayOf(arrayOf(FieldState.ONE_S, FieldState.ONE_M, FieldState.ONE_L)))
21+
GameRuleLogic.isSwarmConnected(board, Team.ONE) shouldBe true
22+
GameRuleLogic.isSwarmConnected(board, Team.TWO) shouldBe false
23+
GameRuleLogic.greatestSwarmSize(board, Team.ONE) shouldBe 6
24+
GameRuleLogic.greatestSwarmSize(board, Team.TWO) shouldBe -1
25+
}
26+
test("board with red and blue fishes") {
27+
val board = Board(arrayOf(arrayOf(FieldState.ONE_S, FieldState.ONE_L, FieldState.TWO_M, FieldState.ONE_L)))
28+
GameRuleLogic.isSwarmConnected(board, Team.ONE) shouldBe false
29+
GameRuleLogic.isSwarmConnected(board, Team.TWO) shouldBe true
30+
GameRuleLogic.greatestSwarmSize(board, Team.ONE) shouldBe 4
31+
GameRuleLogic.greatestSwarmSize(board, Team.TWO) shouldBe 2
32+
}
33+
}
34+
})

0 commit comments

Comments
 (0)