Skip to content

Commit 65a0b0a

Browse files
author
Max
committed
feat(Board): generate random fish distribution on game board
This commit refactors the generateFields() method in the companion object to produce a better distribution of fish on the game board. Previously, the method randomly generated the fish placement with a maximum of 5 holes per side, and a minimum of 5 1-fish fields per side. The remainingFish variable was used to keep track of the number of fish left to place, and maxholes was used to ensure the maximum number of holes was not exceeded. The new implementation replaces the previous algorithm with a weighted probability distribution. The method now uses a list of Field objects and their corresponding probabilities to determine the number of fish that should be placed on each field. To ensure that the sum of the probabilities is 1, an IllegalArgumentException is thrown if the weighted sum does not equal 1. The method then determines the range of possible fish values that can be placed on the field, based on the number of fields remaining to be filled and the sum of fish already placed. The method then generates a random float between 0 and 1 and selects the corresponding fish value based on the probability distribution. The selected value is then assigned to the current field. Finally, the method shuffles the board to ensure that the placement of fish is random, and returns the resulting game board. Overall, this refactor results in a more evenly distributed placement of fish on the board, and eliminates the likelihood of having less than 8 1-fish fields. It also adds a constant distribution of (BoardSize*BoardSize*2) so 128 Fish on the Board.
1 parent 8fbd71f commit 65a0b0a

File tree

1 file changed

+96
-48
lines changed
  • plugin/src/main/kotlin/sc/plugin2023

1 file changed

+96
-48
lines changed

plugin/src/main/kotlin/sc/plugin2023/Board.kt

Lines changed: 96 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,93 +2,141 @@ package sc.plugin2023
22

33
import com.thoughtworks.xstream.annotations.XStreamAlias
44
import sc.api.plugins.*
5+
import kotlin.math.min
6+
import kotlin.math.roundToInt
57
import kotlin.random.Random
68
import sc.plugin2023.util.PluginConstants as Constants
79

810
/**
9-
* Klasse welche eine Spielbrett darstellt. Bestehend aus einem
11+
* Klasse welche ein Spielbrett darstellt. Bestehend aus einem
1012
* zweidimensionalen Array aus Feldern
1113
*
1214
* @author soed
1315
*/
1416
@XStreamAlias(value = "board")
15-
class Board(fields: TwoDBoard<Field> = generateFields()): RectangularBoard<Field>(fields) {
16-
17-
constructor(board: Board): this(board.gameField.clone())
18-
17+
class Board(fields: TwoDBoard<Field> = generateFields()) : RectangularBoard<Field>(fields) {
18+
19+
constructor(board: Board) : this(board.gameField.clone())
20+
1921
override fun isValid(coordinates: Coordinates) =
20-
(coordinates.x + coordinates.y) % 2 == 0 &&
21-
coordinates.x >= 0 &&
22-
super.isValid(coordinates.copy(coordinates.x / 2))
23-
22+
(coordinates.x + coordinates.y) % 2 == 0 &&
23+
coordinates.x >= 0 &&
24+
super.isValid(coordinates.copy(coordinates.x / 2))
25+
2426
/** Gibt das Feld an den gegebenen Koordinaten zurück. */
2527
override operator fun get(x: Int, y: Int) =
26-
super.get(x / 2, y)
27-
28+
super.get(x / 2, y)
29+
2830
/** Ersetzt die Fische des Feldes durch einen Pinguin.
2931
* @return Anzahl der ersetzten Fische. */
3032
operator fun set(position: Coordinates, team: Team?): Int {
31-
if(!isValid(position))
33+
if (!isValid(position))
3234
outOfBounds(position)
3335
val field = gameField[position.y][position.x / 2]
3436
gameField[position.y][position.x / 2] = Field(penguin = team)
3537
return field.fish
3638
}
37-
39+
3840
fun possibleMovesFrom(pos: Coordinates) =
39-
Vector.DoubledHex.directions.flatMap { vector ->
40-
(1 until Constants.BOARD_SIZE).map {
41-
Move.run(pos, vector * it)
42-
}.takeWhile { getOrEmpty(it.to).fish > 0 }
43-
}
44-
41+
Vector.DoubledHex.directions.flatMap { vector ->
42+
(1 until Constants.BOARD_SIZE).map {
43+
Move.run(pos, vector * it)
44+
}.takeWhile { getOrEmpty(it.to).fish > 0 }
45+
}
46+
4547
/** Returns a list of the non-null filter outputs */
4648
fun <T> filterFields(filter: (Field, Coordinates) -> T?): Collection<T> =
47-
gameField.flatMapIndexed { y, row ->
48-
row.mapIndexedNotNull { x, field ->
49-
filter(field, Coordinates.doubledHex(x, y))
50-
}
49+
gameField.flatMapIndexed { y, row ->
50+
row.mapIndexedNotNull { x, field ->
51+
filter(field, Coordinates.doubledHex(x, y))
5152
}
52-
53+
}
54+
5355
fun getPenguins() =
54-
filterFields { field, coordinates ->
55-
field.penguin?.let { Pair(coordinates, it) }
56-
}
57-
56+
filterFields { field, coordinates ->
57+
field.penguin?.let { Pair(coordinates, it) }
58+
}
59+
5860
fun getOrEmpty(key: Coordinates?) = key?.let { getOrNull(it) } ?: Field()
59-
61+
6062
override val entries: Set<Map.Entry<Coordinates, Field>>
6163
get() = filterFields { f, coordinates -> FieldPosition(coordinates, f) }.toSet()
62-
64+
6365
override fun clone(): Board = Board(this)
64-
66+
6567
companion object {
6668
/** Generiert ein neues Spielfeld mit zufällig auf dem Spielbrett verteilten Fischen. */
6769
private fun generateFields(seed: Int = Random.nextInt()): TwoDBoard<Field> {
68-
var remainingFish = Constants.BOARD_SIZE * Constants.BOARD_SIZE
6970
val random = Random(seed)
70-
println("Board Seed: $seed")
71-
var maxholes = 5
72-
// Pro Hälfte 32 Felder, mind. 27 Schollen
73-
// Maximal (64-20)/2 = 22 2-Fisch-Schollen,
74-
// also immer mindestens 5 1-Fisch-Schollen pro Seite
75-
return List(Constants.BOARD_SIZE / 2) {
76-
MutableList(Constants.BOARD_SIZE) {
77-
val rand = random.nextInt(remainingFish)
78-
if(rand < maxholes) {
79-
maxholes--
80-
return@MutableList Field()
71+
println("Board seed: $seed")
72+
val length = Constants.BOARD_SIZE
73+
val width = Constants.BOARD_SIZE
74+
val weightedInts =
75+
listOf(Field(0) to 0.1f, Field(1) to 0.2f, Field(2) to 0.4f, Field(3) to 0.2f, Field(4) to 0.1f)
76+
val totalSum = length * width
77+
val halfWidth = width / 2
78+
val halfEnforcedOnes = Constants.BOARD_SIZE / 2
79+
val arr: TwoDBoard<Field> = List(length) { MutableList(width) { Field(0) } }
80+
var countOne = 0
81+
82+
for (i in 0 until length) {
83+
for (j in 0 until halfWidth) {
84+
if (i * halfWidth + j < halfEnforcedOnes) {
85+
arr[i][j] = Field(1)
86+
countOne += 1
87+
continue
8188
}
82-
val fish = (rand - maxholes) / 20 + 1
83-
remainingFish -= fish
84-
Field(fish)
89+
90+
val currentSum = arr.sumOf { it -> it.sumOf { it.fish } }
91+
val notFilled = totalSum - (i * halfWidth + j)
92+
93+
val weightedSum = weightedInts.sumOf { it.second.toDouble() }
94+
if (weightedSum.roundToInt() != 1) {
95+
throw IllegalArgumentException("The sum of the probabilities must be 1. It is $weightedSum")
96+
}
97+
98+
val lowestPossible = weightedInts.filter { it.first.fish >= (totalSum - currentSum) / notFilled }
99+
.minOf { it.first.fish }
100+
val highestPossible = min(totalSum - currentSum, weightedInts.maxOf { it.first.fish })
101+
102+
val possibleValues =
103+
weightedInts.filter { it.first.fish in lowestPossible..highestPossible }.map { it.first.fish }
104+
val possibleWeights =
105+
weightedInts.filter { it.first.fish in lowestPossible..highestPossible }.map { it.second }
106+
107+
val value = random.nextFloat()
108+
var cumulativeWeight = 0f
109+
var index = 0
110+
while (index < possibleValues.size && cumulativeWeight + possibleWeights[index] < value) {
111+
cumulativeWeight += possibleWeights[index]
112+
index++
113+
}
114+
115+
arr[i][j] = Field(possibleValues[index])
116+
countOne += if (arr[i][j].fish == 1) 1 else 0
85117
}
86-
}.let {
118+
}
119+
120+
for (i in 0 until length) {
121+
for (j in 0 until halfWidth) {
122+
val x = random.nextInt(length)
123+
val y = random.nextInt(halfWidth)
124+
arr[i][j] = arr[x][y].also { arr[x][y] = arr[i][j] }
125+
}
126+
}
127+
128+
for (i in 0 until length) {
129+
for (j in 0 until halfWidth) {
130+
arr[i] += arr[length - i - 1][halfWidth - j - 1]
131+
}
132+
}
133+
134+
return arr.let {
87135
it + it.asReversed().map { list ->
88136
MutableList(Constants.BOARD_SIZE) { index -> list[Constants.BOARD_SIZE - index - 1].clone() }
89137
}
90138
}
139+
91140
}
92-
93141
}
94142
}

0 commit comments

Comments
 (0)