Skip to content

Commit 3c3a0bc

Browse files
committed
feat(plugin): add tests for Piece & Board
1 parent 70693a6 commit 3c3a0bc

File tree

6 files changed

+153
-4
lines changed

6 files changed

+153
-4
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ class Game(override val currentState: GameState = GameState()): AbstractGame<Pla
7373
}
7474

7575
override fun getScoreFor(player: Player): PlayerScore {
76+
logger.debug("Calculating score for $player")
7677
val team = player.team as Team
77-
logger.debug("Get score for player $team (violated: ${if (player.hasViolated()) "yes" else "no"})")
7878
val opponent = players[team.opponent().index]
7979
val winCondition = checkWinCondition()
8080

@@ -85,6 +85,7 @@ class Game(override val currentState: GameState = GameState()): AbstractGame<Pla
8585

8686
// Is the game already finished?
8787
if (winCondition?.reason == WinReason.EQUAL_SCORE)
88+
// TODO something is going wrong on draw scores
8889
score = Constants.DRAW_SCORE
8990
if (winCondition?.reason == WinReason.DIFFERING_SCORES)
9091
if (winCondition.winner == team)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package sc.plugin2022
33
import sc.api.plugins.IMove
44

55
/** Ein Spielzug. */
6-
class Move(
6+
data class Move(
77
/** Ursprungsposition des Zugs. */
88
val start: Coordinates,
99
/** Zielposition des Zugs. */

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@ package sc.plugin2022
33
import com.thoughtworks.xstream.annotations.XStreamAlias
44
import com.thoughtworks.xstream.annotations.XStreamAsAttribute
55
import sc.api.plugins.ITeam
6+
import sc.api.plugins.Team
67

78
enum class PieceType(val char: Char, vararg val possibleMoves: Vector) {
9+
/** Bewegt sich nur diagonal vorwärts. */
810
Herzmuschel('H', Vector(1, 1), Vector(-1, 1)),
11+
/** Bewegt sich nur auf Nachbarfelder. */
912
Moewe('M', *Vector.cardinals),
13+
/** Bewegt sich diagonal oder vorwärts. */
1014
Seestern('S', *Vector.diagonals, Vector(0, 1)),
15+
/** Wie ein Springer im Schach. Einzige nicht-Leichtfigur */
1116
Robbe('R', *Vector.diagonals.flatMap { listOf(it.copy(dx = it.dx * 2), it.copy(dy = it.dy * 2)) }.toTypedArray());
17+
1218
val isLight
1319
get() = this != Robbe
20+
21+
fun teamPieces() = Team.values().map { Piece(this, it) }
1422
}
1523

1624
/** Ein Spielstein. */
@@ -33,8 +41,20 @@ data class Piece(
3341
count += other.count
3442
}
3543

36-
fun shortString() =
37-
type.char.toString() + if (count == 1) type.char else count
44+
fun shortString(): String {
45+
val char = type.char.run { if(team.index > 0) toLowerCase() else this }.toString()
46+
return if (count == 1) char + char else char + count
47+
}
48+
49+
companion object {
50+
@OptIn(ExperimentalStdlibApi::class)
51+
fun fromString(string: String): Piece {
52+
val type = string.first()
53+
return Piece(PieceType.values().first { it.char == type },
54+
if(type.isLowerCase()) Team.TWO else Team.ONE,
55+
string.last().digitToIntOrNull() ?: 0)
56+
}
57+
}
3858
}
3959

4060
val ITeam.direction
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package sc.plugin2022
2+
3+
import io.kotest.assertions.throwables.shouldThrow
4+
import io.kotest.core.spec.style.FunSpec
5+
import io.kotest.inspectors.forAll
6+
import io.kotest.matchers.collections.shouldBeOneOf
7+
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
8+
import io.kotest.matchers.maps.shouldBeEmpty
9+
import io.kotest.matchers.maps.shouldHaveSize
10+
import io.kotest.matchers.maps.shouldNotBeEmpty
11+
import io.kotest.matchers.shouldBe
12+
import io.kotest.matchers.string.shouldHaveLineCount
13+
import io.kotest.matchers.string.shouldNotContain
14+
import sc.api.plugins.Team
15+
import sc.plugin2022.PieceType.*
16+
import sc.plugin2022.util.Constants
17+
import sc.plugin2022.util.MoveMistake
18+
import sc.shared.InvalidMoveException
19+
20+
class BoardTest: FunSpec({
21+
context("Board generation") {
22+
val board = Board()
23+
test("does not misplace pieces") {
24+
board shouldHaveSize Constants.BOARD_SIZE * 2
25+
board.keys.forAll {
26+
it.y shouldBeOneOf listOf(0, Constants.BOARD_SIZE - 1)
27+
}
28+
board.values shouldContainExactlyInAnyOrder values().flatMap { type ->
29+
Team.values().map { team ->
30+
Piece(type, team)
31+
}
32+
}.let { it + it }
33+
}
34+
test("is stringified apropriately") {
35+
val string = board.toString()
36+
println(string)
37+
string shouldHaveLineCount 8
38+
val lines = string.lines()
39+
lines.first() shouldNotContain "-"
40+
lines.last() shouldNotContain "-"
41+
lines.first().reversed().toLowerCase() shouldBe lines.last()
42+
lines.subList(1, 7).forAll {
43+
it shouldBe "----------------"
44+
}
45+
}
46+
}
47+
context("Board performs Moves") {
48+
context("refuses invalid moves") {
49+
test("can't move backwards or off the fields") {
50+
val board = Board(arrayOf(Seestern, Herzmuschel).flatMap { it.teamPieces() }.mapIndexed { index, piece -> Coordinates(index, 4) to piece }.toMap(HashMap()))
51+
board.entries.forAll {
52+
shouldThrow<InvalidMoveException> {
53+
board.movePiece(Move(it.key, it.key.copy(y = if(it.value.team == Team.ONE) 3 else 5)))
54+
}.mistake shouldBe MoveMistake.INVALID_MOVEMENT
55+
}
56+
}
57+
test("can't move onto own piece") {
58+
val board = makeBoard(0 y 0 to "R", 1 y 2 to "R")
59+
shouldThrow<InvalidMoveException> {
60+
board.movePiece(Move(0 y 0, 1 y 2))
61+
}.mistake shouldBe MoveMistake.DESTINATION_BLOCKED
62+
}
63+
}
64+
context("amber") {
65+
val coords = Coordinates(0, 6)
66+
test("not for other team") {
67+
val moewe = Piece(Moewe, Team.TWO)
68+
val board = Board(mutableMapOf(coords to moewe))
69+
board.movePiece(Move(coords, coords.copy(y = 7))) shouldBe moewe
70+
board.shouldNotBeEmpty()
71+
}
72+
test("from position") {
73+
val moewe = Piece(Moewe, Team.ONE)
74+
val board = Board(mutableMapOf(coords to moewe))
75+
board.movePiece(Move(coords, coords.copy(y = 7))) shouldBe null
76+
board.shouldBeEmpty()
77+
}
78+
test("not from Robbe in position") {
79+
val robbe = Piece(Robbe, Team.ONE)
80+
val board = Board(mutableMapOf(coords to robbe))
81+
board.movePiece(Move(coords, Coordinates(2, 7))) shouldBe robbe
82+
board.shouldNotBeEmpty()
83+
}
84+
test("from tower") {
85+
val tower = Piece(Herzmuschel, Team.ONE, 2)
86+
val board = Board(mutableMapOf())
87+
}
88+
}
89+
}
90+
})
91+
92+
infix fun String.at(pos: Coordinates) = Pair(pos, Piece.fromString(this))
93+
94+
infix fun Int.y(other: Int) = Coordinates(this, other)
95+
96+
fun makeBoard(vararg list: Pair<Coordinates, String>) =
97+
Board(list.associateTo(HashMap()) { it.first to Piece.fromString(it.second) })

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import java.math.BigDecimal
1616
import kotlin.time.ExperimentalTime
1717
import kotlin.time.milliseconds
1818

19+
/** This test verifies that the Game implementation works correctly.
20+
* It is the only test that should stay between seasons. */
1921
@OptIn(ExperimentalTime::class)
2022
class GameTest: WordSpec({
2123
isolationMode = IsolationMode.SingleInstance
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package sc.plugin2022
2+
3+
import io.kotest.core.datatest.forAll
4+
import io.kotest.core.spec.style.FunSpec
5+
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
6+
import io.kotest.matchers.shouldBe
7+
import sc.api.plugins.Team
8+
import sc.plugin2022.PieceType.*
9+
10+
class PieceTest: FunSpec({
11+
test("to and from String") {
12+
forAll(ts = PieceType.values().toList()) {
13+
Piece.fromString(it.char.toString()) shouldBe Piece(it, Team.ONE)
14+
}
15+
forAll(
16+
"MM" to Piece(Moewe, Team.ONE, 1),
17+
"R2" to Piece(Robbe, Team.ONE, 2),
18+
"s2" to Piece(Seestern, Team.TWO, 2),
19+
"hh" to Piece(Herzmuschel, Team.TWO),
20+
) { pair: Pair<String, Piece> ->
21+
pair.second.toString() shouldBe pair.first
22+
Piece.fromString(pair.first) shouldBe pair.second
23+
}
24+
}
25+
test("can't move backwards") {
26+
Piece(Herzmuschel, Team.ONE).possibleMoves shouldContainExactlyInAnyOrder listOf(Vector(1, 1), Vector(-1, 1))
27+
Piece(Seestern, Team.ONE).possibleMoves shouldContainExactlyInAnyOrder listOf(*Vector.diagonals, Vector(0, 1))
28+
}
29+
})

0 commit comments

Comments
 (0)