diff --git a/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java b/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java index c5753c2..e131684 100644 --- a/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java +++ b/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.ArrayList; import java.util.Arrays; +import java.util.UUID; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -33,12 +34,11 @@ public class GameBoardTests { @Test void testSurrender() { - final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); + final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName); - final String message = "GameBoard SURRENDER test"; final GameState game = cordaClient.gameProposalCreate( - hiWhite, hiBlack, Stone.Color.BLACK, message); + hiWhite, hiBlack, Stone.Color.BLACK, "GameBoard SURRENDER test"); System.out.println("Game UUID " +game.uuid()); @@ -70,12 +70,11 @@ public class GameBoardTests { @Test void testMove() { - final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); + final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName); - final String message = "GameBoard MOVE test"; final GameState game = cordaClient.gameProposalCreate( - hiWhite, hiBlack, Stone.Color.BLACK, message); + hiWhite, hiBlack, Stone.Color.BLACK, "GameBoard MOVE test"); System.out.println("Game UUID " +game.uuid()); final var m0 = cordaClient.gameProposalAccept(hiBlack, game.uuid()); @@ -126,6 +125,44 @@ public class GameBoardTests { assertThat(m3.moveNumber() == 3); assertThat(m3.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); + final var m4 = cordaClient.gameBoardMove(hiBlack, game.uuid(), move(8, 15), + "Choose one of two possible mandatory jumps"); + assertThat(m4.moveNumber() == 4); + assertThat(m4.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); + + final var m5 = cordaClient.gameBoardMove(hiWhite, game.uuid(), move(26, 22), null); + assertThat(m5.moveNumber() == 5); + assertThat(m5.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); + + final var m6 = cordaClient.gameBoardMove(hiBlack, game.uuid(), move(4, 8), null); + assertThat(m6.moveNumber() == 6); + assertThat(m6.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); + + final var m7 = cordaClient.gameBoardMove(hiWhite, game.uuid(), move(23, 18), + "A move that ends in mandatory capture on a next turn for me"); + assertThat(m7.moveNumber() == 7); + assertThat(m7.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); + + final var m8 = cordaClient.gameBoardMove(hiBlack, game.uuid(), move(9, 14), + "Moard mandatory capture on a next turn for opponent"); + assertThat(m8.moveNumber() == 8); + assertThat(m8.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); + + final var m9 = cordaClient.gameBoardMove(hiWhite, game.uuid(), move(18, 11), + "First part of a mandatory jump"); + assertThat(m9.moveNumber() == 9); + assertThat(m9.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_YOU); + + assertThatThrownBy(() -> { + cordaClient.gameBoardMove(hiWhite, game.uuid(), move(22, 17), + "An attempt to ignore mandatory capture"); + }); + + final var m10 = cordaClient.gameBoardMove(hiWhite, game.uuid(), move(11, 4), + "Second part of a mandatory jump into a king row"); + assertThat(m10.board().get(4)).isEqualTo(WHITE_KING); + assertThat(m10.moveNumber() == 9); + assertThat(m10.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); } ArrayList move(int from, int to) { diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Move.java b/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Move.java index 91bafb5..5f65e06 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Move.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Move.java @@ -31,6 +31,10 @@ public class Move { } private Stone.Color apply(Map board, Stone.Color expectedMoveColor) { + final Set mandatoryJumps = getMandatoryJumps(board, expectedMoveColor); + if (mandatoryJumps.size() != 0 && !mandatoryJumps.contains(this)) + throw new Exception("A mandatory opponent's stone capture must be performed"); + Stone movingStone = board.remove(from); if (movingStone == null) throw new Exception("An empty starting tile"); @@ -42,10 +46,6 @@ public class Move { if (!allMoves.contains(this)) throw new Exception("Prohibited move"); - final Set mandatoryJumps = getMandatoryJumps(board, expectedMoveColor); - if (mandatoryJumps.size() != 0 && !mandatoryJumps.contains(this)) - throw new Exception("A mandatory opponent's stone capture must be performed"); - movingStone = movingStone.promoteIfPossible(to); if (board.put(to, movingStone) != null) throw new Exception("An occupied finishing tile"); @@ -54,15 +54,35 @@ public class Move { final Stone jumpOver = board.remove(this.jumpOver); if (jumpOver == null || jumpOver.getColor() != expectedMoveColor.opposite()) throw new Exception("Must jump over an opponent's stone"); + + final Set chainJumps = movingStone.getJumps(this.to); + chainJumps.removeIf(move -> !move.canJump(board, expectedMoveColor)); + if (chainJumps.size() != 0) + return expectedMoveColor; // player must continue his turn } - return expectedMoveColor.opposite(); + return expectedMoveColor.opposite(); // player has finished his turn } - public boolean isJump() { + boolean isJump() { return this.jumpOver != null; } + boolean canJump(Map board, Stone.Color color) { + final Stone jumpStone = board.get(this.from); + if (jumpStone == null || jumpStone.getColor() != color) + return false; + + final Stone jumpOverStone = board.get(this.jumpOver); + if (jumpOverStone == null || jumpOverStone.getColor() != color.opposite()) + return false; + + if (board.get(this.to) != null) + return false; + + return true; + } + static Integer intersection(Set a, Set b) { Set intersection = new HashSet(a); @@ -151,26 +171,20 @@ public class Move { } } - public static Set getMandatoryJumps(Map board, Stone.Color color) { + static Set getMandatoryJumps(Map board, Stone.Color color) { Set res = new HashSet(); for (Map.Entry entry : board.entrySet()) { final Integer from = entry.getKey(); final Stone stone = entry.getValue(); + if (stone.getColor() != color) continue; - final Stone.Color opponentsColor = color.opposite(); - final Set allJumps = stone.getJumps(from); + final Set jumps = stone.getJumps(from); + jumps.removeIf( move -> !move.canJump(board, color)); - allJumps.removeIf( j -> { - if (board.get(j.to) != null) - return true; // must be an empty jumpTo tile - final Stone jumpOverStone = board.get(j.jumpOver); - return jumpOverStone == null || jumpOverStone.getColor() != opponentsColor; - }); - - res.addAll(allJumps); + res.addAll(jumps); } return res;