Skip to content

Commit 450a1be

Browse files
committed
fix(plugin24): merge consecutive advances
1 parent c2ce9a8 commit 450a1be

File tree

3 files changed

+107
-82
lines changed

3 files changed

+107
-82
lines changed

plugin/src/main/kotlin/sc/plugin2024/GameState.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,16 @@ data class GameState @JvmOverloads constructor(
8484
override fun performMoveDirectly(move: Move) {
8585
if(move.actions.isEmpty()) throw InvalidMoveException(MoveMistake.NO_ACTIONS)
8686

87-
move.actions.forEachIndexed { index, action ->
87+
val actions = move.actions.fold(ArrayList<Action>()) { acc, act ->
88+
val last = acc.lastOrNull()
89+
if(last is Advance && act is Advance) {
90+
acc[acc.lastIndex] = last + act
91+
} else {
92+
acc.add(act)
93+
}
94+
acc
95+
}
96+
actions.forEachIndexed { index, action ->
8897
when {
8998
board[currentShip.position] == Field.SANDBANK && index != 0 -> throw InvalidMoveException(MoveMistake.SAND_BANK_END, move)
9099
currentShip.position == otherShip.position && action !is Push -> throw InvalidMoveException(MoveMistake.PUSH_ACTION_REQUIRED, move)
@@ -100,7 +109,7 @@ data class GameState @JvmOverloads constructor(
100109

101110
board.pickupPassenger(currentShip)
102111
currentShip.points = calculatePoints(currentShip)
103-
if(move.actions.any { it is Push }) {
112+
if(actions.any { it is Push }) {
104113
if(otherShip.speed == 1)
105114
board.pickupPassenger(otherShip)
106115
otherShip.points = calculatePoints(otherShip)

plugin/src/main/kotlin/sc/plugin2024/actions/Advance.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ data class Advance(
4545
}
4646

4747
override fun toString(): String = if(distance >= 0) "Gehe $distance Felder vor" else "Gehe $distance Felder zurück"
48+
49+
operator fun plus(other: Advance) = Advance(distance + other.distance)
4850
}

plugin/src/test/kotlin/sc/plugin2024/GameStateTest.kt

Lines changed: 94 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,6 @@ class GameStateTest: FunSpec({
4747
gameState.currentTeam shouldBe gameState.startTeam.opponent()
4848
}
4949

50-
test("reveals segment after move") {
51-
val state = GameState(Board(listOf(Segment.empty(), Segment.empty(CubeCoordinates(4, 0)), Segment.empty(CubeCoordinates(8, 0)))))
52-
val move = Move(Accelerate(5), Advance(6))
53-
var found = false
54-
state.getAllMoves().forEachRemaining { if(move == it) found = true }
55-
found shouldBe true
56-
57-
state.performMoveDirectly(move)
58-
state.board.segmentIndex(state.otherShip.position) shouldBe 1
59-
state.board.visibleSegments shouldBe 3
60-
}
61-
6250
context("points calculation") {
6351
test("at start") {
6452
gameState.ships.forAll {
@@ -81,21 +69,35 @@ class GameStateTest: FunSpec({
8169
}
8270
}
8371

84-
test("getPossiblePushs") {
85-
gameState.getPossiblePushs().shouldBeEmpty()
86-
gameState.currentShip.position = gameState.otherShip.position
87-
gameState.getPossiblePushs() shouldHaveSize 4
88-
gameState.currentShip.movement = 0
89-
gameState.getPossiblePushs().shouldBeEmpty()
90-
}
91-
92-
test("getPossibleTurns") {
93-
gameState.getPossibleTurns(0).shouldHaveSize(2)
94-
gameState.getPossibleTurns(1).shouldHaveSize(4)
95-
gameState.getPossibleTurns(2).shouldHaveSize(5)
96-
}
97-
9872
context("getPossibleAdvances") {
73+
context("advanceLimit") {
74+
val ship = gameState.currentShip
75+
test("from start") {
76+
gameState.checkAdvanceLimit(ship.position, CubeDirection.DOWN_RIGHT, 1).distance shouldBe 0
77+
gameState.checkAdvanceLimit(ship.position, CubeDirection.DOWN_RIGHT, 2).distance shouldBe 1
78+
val furtherInfo = gameState.checkAdvanceLimit(ship.position, CubeDirection.DOWN_RIGHT, 3)
79+
furtherInfo.costUntil(1) shouldBe 2
80+
furtherInfo.distance shouldBe 2
81+
furtherInfo.costUntil(2) shouldBe 3
82+
}
83+
test("considers pushing and current") {
84+
ship.direction = CubeDirection.DOWN_RIGHT
85+
gameState.otherShip.position = CubeCoordinates.ORIGIN + CubeDirection.LEFT.vector
86+
87+
gameState.checkAdvanceLimit(ship).run {
88+
distance shouldBe 0
89+
problem shouldBe AdvanceProblem.NO_MOVEMENT_POINTS
90+
}
91+
92+
ship.speed = 3
93+
ship.movement = 3
94+
gameState.checkAdvanceLimit(ship).run {
95+
distance shouldBe 1
96+
costUntil(1) shouldBe 2
97+
problem shouldBe AdvanceProblem.SHIP_ALREADY_IN_TARGET
98+
}
99+
}
100+
}
99101
test("from starting position") {
100102
gameState.getPossibleAdvances() shouldHaveSingleElement Advance(1)
101103
}
@@ -111,40 +113,24 @@ class GameStateTest: FunSpec({
111113
}
112114
}
113115

114-
test("getPossibleAccelerations") {
115-
gameState.getPossibleAccelerations(0).size shouldBe 1
116-
gameState.getPossibleAccelerations(1).size shouldBe 2
117-
}
118-
119-
context("advanceLimit") {
120-
val ship = gameState.currentShip
121-
test("from start") {
122-
gameState.checkAdvanceLimit(ship.position, CubeDirection.DOWN_RIGHT, 1).distance shouldBe 0
123-
gameState.checkAdvanceLimit(ship.position, CubeDirection.DOWN_RIGHT, 2).distance shouldBe 1
124-
val furtherInfo = gameState.checkAdvanceLimit(ship.position, CubeDirection.DOWN_RIGHT, 3)
125-
furtherInfo.costUntil(1) shouldBe 2
126-
furtherInfo.distance shouldBe 2
127-
furtherInfo.costUntil(2) shouldBe 3
116+
context("getPossibleActions") {
117+
test("getPossibleAccelerations") {
118+
gameState.getPossibleAccelerations(0).size shouldBe 1
119+
gameState.getPossibleAccelerations(1).size shouldBe 2
128120
}
129-
test("considers pushing and current") {
130-
ship.direction = CubeDirection.DOWN_RIGHT
131-
gameState.otherShip.position = CubeCoordinates.ORIGIN + CubeDirection.LEFT.vector
132-
133-
gameState.checkAdvanceLimit(ship).run {
134-
distance shouldBe 0
135-
problem shouldBe AdvanceProblem.NO_MOVEMENT_POINTS
136-
}
137-
138-
ship.speed = 3
139-
ship.movement = 3
140-
gameState.checkAdvanceLimit(ship).run {
141-
distance shouldBe 1
142-
costUntil(1) shouldBe 2
143-
problem shouldBe AdvanceProblem.SHIP_ALREADY_IN_TARGET
144-
}
121+
test("getPossibleTurns") {
122+
gameState.getPossibleTurns(0).shouldHaveSize(2)
123+
gameState.getPossibleTurns(1).shouldHaveSize(4)
124+
gameState.getPossibleTurns(2).shouldHaveSize(5)
145125
}
146-
}
147-
context("getPossibleActions") {
126+
test("getPossiblePushs") {
127+
gameState.getPossiblePushs().shouldBeEmpty()
128+
gameState.currentShip.position = gameState.otherShip.position
129+
gameState.getPossiblePushs() shouldHaveSize 4
130+
gameState.currentShip.movement = 0
131+
gameState.getPossiblePushs().shouldBeEmpty()
132+
}
133+
148134
test("from starting position") {
149135
gameState.getPossibleActions(0) shouldHaveSize 11
150136
}
@@ -199,7 +185,7 @@ class GameStateTest: FunSpec({
199185
}
200186
test("unpushable opponent") {
201187
val state = GameState(
202-
Board(listOf(Segment (CubeDirection.RIGHT, CubeCoordinates.ORIGIN, arrayOf(arrayOf(Field.WATER, Field.WATER, Field.WATER))))),
188+
Board(listOf(Segment(CubeDirection.RIGHT, CubeCoordinates.ORIGIN, arrayOf(arrayOf(Field.WATER, Field.WATER, Field.WATER))))),
203189
ships = listOf(
204190
Ship(CubeCoordinates(-1, -2), Team.ONE),
205191
Ship(CubeCoordinates(-1, 0), Team.TWO),
@@ -211,29 +197,57 @@ class GameStateTest: FunSpec({
211197
}
212198
}
213199

214-
context("current works when board is truncated") {
215-
val commonBoard = Board(listOf(Segment.empty(),
216-
Segment.empty(CubeCoordinates(4, 0)),
217-
Segment(CubeDirection.UP_RIGHT, CubeCoordinates(8, -4), generateSegment(true, arrayOf()))))
218-
val state = GameState(commonBoard)
219-
val start = CubeCoordinates(1, -1)
220-
state.currentShip.run {
221-
position = start
222-
speed = 2
223-
movement = 2
200+
context("performing move") {
201+
val straightState = GameState(Board(listOf(Segment.empty(), Segment.empty(CubeCoordinates(4, 0)), Segment.empty(CubeCoordinates(8, 0)))))
202+
test("reveals next segment") {
203+
val move = Move(Accelerate(5), Advance(6))
204+
var found = false
205+
straightState.getAllMoves().forEachRemaining { if(move == it) found = true }
206+
found shouldBe true
207+
208+
straightState.performMoveDirectly(move)
209+
straightState.board.segmentIndex(straightState.otherShip.position) shouldBe 1
210+
straightState.board.visibleSegments shouldBe 3
224211
}
225212

226-
val moves = state.getPossibleMoves(1)
227-
val truncState = state.copy(Board(commonBoard.segments.subList(0, 2), nextDirection = CubeDirection.UP_RIGHT))
228-
moves shouldContainAll truncState.getPossibleMoves(1)
229-
forAll<GameState>("full" to state, "truncated" to truncState) { state ->
230-
state.checkAdvanceLimit(start, CubeDirection.RIGHT, 5).costUntil(4) shouldBe 5
231-
state.clone().checkAdvanceLimit(start, CubeDirection.RIGHT, 5).costUntil(4) shouldBe 5
232-
state.performMove(Move(Accelerate(1), Advance(3)))
233-
state.performMove(Move(Accelerate(3), Advance(4)))
234-
state.performMove(Move(Accelerate(4), Advance(5)))
235-
shouldThrow<InvalidMoveException> { state.performMove(Move(Accelerate(2), Advance(3))) }.mistake shouldBe MoveMistake.MOVEMENT_POINTS_LEFT
236-
shouldThrow<InvalidMoveException> { state.performMove(Move(Accelerate(2), Advance(4))) }.mistake shouldBe AdvanceProblem.NO_MOVEMENT_POINTS
213+
test("merges duplicate advances") {
214+
straightState.currentShip.position = CubeCoordinates.ORIGIN
215+
val actions = arrayOf(Turn(CubeDirection.RIGHT), Turn(CubeDirection.UP_RIGHT), Turn(CubeDirection.RIGHT), Advance(1))
216+
shouldThrow<InvalidMoveException> { straightState.performMove(Move(*actions)) }.mistake shouldBe AdvanceProblem.NO_MOVEMENT_POINTS
217+
straightState.performMove(Move(Accelerate(1), *actions))
218+
shouldThrow<InvalidMoveException> { straightState.performMove(Move(*actions)) }.mistake shouldBe AdvanceProblem.NO_MOVEMENT_POINTS
219+
straightState.performMove(Move(Accelerate(2), *actions, Advance(1)))
220+
straightState.performMoveDirectly(Move(Accelerate(2), *actions, Advance(1), Turn(CubeDirection.DOWN_RIGHT)))
221+
straightState.otherShip.run {
222+
position shouldBe CubeCoordinates(2, 0)
223+
coal shouldBe 3
224+
}
225+
}
226+
227+
context("current works when board is truncated") {
228+
val commonBoard = Board(listOf(Segment.empty(),
229+
Segment.empty(CubeCoordinates(4, 0)),
230+
Segment(CubeDirection.UP_RIGHT, CubeCoordinates(8, -4), generateSegment(true, arrayOf()))))
231+
val state = GameState(commonBoard)
232+
val start = CubeCoordinates(1, -1)
233+
state.currentShip.run {
234+
position = start
235+
speed = 2
236+
movement = 2
237+
}
238+
239+
val moves = state.getPossibleMoves(1)
240+
val truncState = state.copy(Board(commonBoard.segments.subList(0, 2), nextDirection = CubeDirection.UP_RIGHT))
241+
moves shouldContainAll truncState.getPossibleMoves(1)
242+
forAll<GameState>("full" to state, "truncated" to truncState) { state ->
243+
state.checkAdvanceLimit(start, CubeDirection.RIGHT, 5).costUntil(4) shouldBe 5
244+
state.clone().checkAdvanceLimit(start, CubeDirection.RIGHT, 5).costUntil(4) shouldBe 5
245+
state.performMove(Move(Accelerate(1), Advance(3)))
246+
state.performMove(Move(Accelerate(3), Advance(4)))
247+
state.performMove(Move(Accelerate(4), Advance(5)))
248+
shouldThrow<InvalidMoveException> { state.performMove(Move(Accelerate(2), Advance(3))) }.mistake shouldBe MoveMistake.MOVEMENT_POINTS_LEFT
249+
shouldThrow<InvalidMoveException> { state.performMove(Move(Accelerate(2), Advance(4))) }.mistake shouldBe AdvanceProblem.NO_MOVEMENT_POINTS
250+
}
237251
}
238252
}
239253

0 commit comments

Comments
 (0)