Skip to content

Commit 01a62e9

Browse files
committed
feat(plugin): enable diffing Boards
1 parent 59c4e60 commit 01a62e9

File tree

2 files changed

+78
-17
lines changed

2 files changed

+78
-17
lines changed

plugin/src/main/sc/plugin2022/Board.kt

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,51 @@ data class Board(
4444
* and if yes, removes it.
4545
* @return number of ambers */
4646
fun checkAmber(position: Coordinates): Int =
47-
piecePositions[position]?.let { piece ->
48-
arrayOf(piece.isAmber, piece.type.isLight && position.y == piece.team.opponent().startLine)
49-
.sumBy { if(it) 1 else 0 }
50-
.also { if(it > 0) piecePositions.remove(position) }
51-
} ?: 0
47+
piecePositions[position]?.let { piece ->
48+
arrayOf(piece.isAmber, piece.type.isLight && position.y == piece.team.opponent().startLine)
49+
.sumBy { if (it) 1 else 0 }
50+
.also { if (it > 0) piecePositions.remove(position) }
51+
} ?: 0
52+
53+
/** Berechnet die Züge, die die beiden Spielbretter unterscheiden.
54+
*
55+
* - Berücksichtigt nicht die Turmhöhen, kann daher in Ausnahmefällen inkorrekt sein.
56+
* - Die Züge müssen nicht valide Züge der entsprechenden Figuren sein.
57+
* - Wenn ein Stein verschwindet (z.B. weil er zu einem Bernstein wird),
58+
* ist der Zielpunkt des Zuges eine invalide Koordinate.
59+
* - Extra Steine in [other] werden nicht berücksichtigt.
60+
*
61+
* @return List an Zügen, die nötig wären, damit dieses Board äquivalent zu [other] wird.*/
62+
fun diff(other: Board): Collection<Move> {
63+
val both = arrayOf(this, other)
64+
val teams = both.flatMapTo(HashSet()) { b -> b.values.map { it.team } }
65+
val moves = teams.flatMapTo(ArrayList()) { team ->
66+
PieceType.values().flatMap { type ->
67+
both.map { it.filterValues { it.team == team && it.type == type }.keys }
68+
.zipWithNext { b1, b2 ->
69+
val combinations = b1.flatMapTo(ArrayDeque()) { e1 ->
70+
b2.map { e2 -> Move(e1, e2) }
71+
}
72+
val moves = mutableListOf<Move>()
73+
while (combinations.isNotEmpty()) {
74+
val move = combinations.minOrNull() ?: break
75+
moves.add(move)
76+
combinations.removeIf {
77+
it.start == move.start ||
78+
it.destination == move.destination
79+
}
80+
}
81+
moves
82+
}.single()
83+
}
84+
}
85+
val startPoints = moves.map { it.start }
86+
moves.addAll(keys.filter {
87+
it !in startPoints
88+
}.map { Move(it, Coordinates(-1, -1)) })
89+
moves.removeIf { it.start == it.destination }
90+
return moves
91+
}
5292

5393
override fun toString() =
5494
boardrange.joinToString("\n") { y ->

plugin/src/test/sc/plugin2022/BoardTest.kt

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package sc.plugin2022
33
import io.kotest.assertions.throwables.shouldThrow
44
import io.kotest.core.spec.style.FunSpec
55
import io.kotest.inspectors.forAll
6-
import io.kotest.matchers.collections.shouldBeOneOf
7-
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
6+
import io.kotest.matchers.booleans.shouldBeFalse
7+
import io.kotest.matchers.collections.*
88
import io.kotest.matchers.maps.shouldBeEmpty
99
import io.kotest.matchers.maps.shouldHaveSize
1010
import io.kotest.matchers.shouldBe
@@ -46,10 +46,11 @@ class BoardTest: FunSpec({
4646
context("Board performs Moves") {
4747
context("refuses invalid moves") {
4848
test("can't move backwards or off the fields") {
49-
val board = Board(arrayOf(Seestern, Herzmuschel).flatMap { it.teamPieces() }.mapIndexed { index, piece -> Coordinates(index, 4) to piece }.toMap(HashMap()))
49+
val board = Board(arrayOf(Seestern, Herzmuschel).flatMap { it.teamPieces() }
50+
.mapIndexed { index, piece -> Coordinates(index, 4) to piece }.toMap(HashMap()))
5051
board.entries.forAll {
5152
shouldThrow<InvalidMoveException> {
52-
board.movePiece(Move(it.key, it.key.copy(y = if(it.value.team == Team.ONE) 3 else 5)))
53+
board.movePiece(Move(it.key, it.key.copy(y = if (it.value.team == Team.ONE) 3 else 5)))
5354
}.mistake shouldBe MoveMistake.INVALID_MOVEMENT
5455
}
5556
}
@@ -61,22 +62,18 @@ class BoardTest: FunSpec({
6162
}
6263
}
6364
context("amber") {
64-
val coords = Coordinates(0, 6)
6565
test("not for other team") {
66-
val moewe = Piece(Moewe, Team.TWO)
67-
val board = Board(mutableMapOf(coords to moewe))
66+
val board = makeBoard(0 y 6 to "m")
6867
board.movePiece(Move(0 y 6, 0 y 7)) shouldBe 0
6968
board shouldHaveSize 1
7069
}
71-
test("from position") {
72-
val moewe = Piece(Moewe, Team.ONE)
73-
val board = Board(mutableMapOf(coords to moewe))
70+
test("from position") {
71+
val board = makeBoard(0 y 6 to "M")
7472
board.movePiece(Move(0 y 6, 0 y 7)) shouldBe 1
7573
board.shouldBeEmpty()
7674
}
7775
test("not from Robbe in position") {
78-
val robbe = Piece(Robbe, Team.ONE)
79-
val board = Board(mutableMapOf(coords to robbe))
76+
val board = makeBoard(0 y 6 to "R")
8077
board.movePiece(Move(0 y 6, 2 y 7)) shouldBe 0
8178
board shouldHaveSize 1
8279
}
@@ -96,6 +93,30 @@ class BoardTest: FunSpec({
9693
}
9794
}
9895
}
96+
context("Board calculates diffs") {
97+
val board = makeBoard(0 y 0 to "r", 2 y 0 to "r")
98+
test("empty for itself") {
99+
board.diff(board).shouldBeEmpty()
100+
board.diff(board.clone()).shouldBeEmpty()
101+
board.clone().diff(board).shouldBeEmpty()
102+
}
103+
test("one moved and one unmoved piece") {
104+
val move = Move(0 y 0, 2 y 1)
105+
val newBoard = board.clone()
106+
newBoard.movePiece(move)
107+
board.diff(newBoard) shouldContainExactly listOf(move)
108+
}
109+
test("both pieces moved") {
110+
val newBoard = makeBoard(2 y 1 to "r", 1 y 2 to "r")
111+
board.diff(newBoard) shouldHaveSize 2
112+
}
113+
test("one piece vanished") {
114+
val newBoard = makeBoard(2 y 0 to "r")
115+
val move = board.diff(newBoard).single()
116+
move.start shouldBe (0 y 0)
117+
move.destination.isValid.shouldBeFalse()
118+
}
119+
}
99120
})
100121

101122
infix fun String.at(pos: Coordinates) = Pair(pos, Piece.fromString(this))

0 commit comments

Comments
 (0)