Compare commits

...

2 Commits

Author SHA1 Message Date
729384fb62 chain jump 2023-09-29 19:07:54 +02:00
8affa353da move: mandatory capture 2023-09-29 13:59:34 +02:00
3 changed files with 107 additions and 13 deletions

View File

@ -21,7 +21,7 @@ public class CheckersMoveTest {
Map.entry(2, Arrays.asList(6, 7, 9, 11)), Map.entry(2, Arrays.asList(6, 7, 9, 11)),
Map.entry(3, Arrays.asList(7, 8, 10, 12)), Map.entry(3, Arrays.asList(7, 8, 10, 12)),
Map.entry(4, Arrays.asList(8, 11)), Map.entry(4, Arrays.asList(8, 11)),
Map.entry(5, Arrays.asList(1, 9, 14, 9, 11)), Map.entry(5, Arrays.asList(1, 9, 14)),
Map.entry(6, Arrays.asList(1, 2, 9, 10, 13, 15)), Map.entry(6, Arrays.asList(1, 2, 9, 10, 13, 15)),
Map.entry(7, Arrays.asList(2, 3, 10, 11, 14, 16)), Map.entry(7, Arrays.asList(2, 3, 10, 11, 14, 16)),
Map.entry(8, Arrays.asList(3, 4, 11, 12, 15)), Map.entry(8, Arrays.asList(3, 4, 11, 12, 15)),

View File

@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.UUID;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -33,12 +34,11 @@ public class GameBoardTests {
@Test @Test
void testSurrender() { void testSurrender() {
final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName); final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
final String message = "GameBoard SURRENDER test";
final GameState game = cordaClient.gameProposalCreate( 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()); System.out.println("Game UUID " +game.uuid());
@ -70,12 +70,11 @@ public class GameBoardTests {
@Test @Test
void testMove() { void testMove() {
final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName); final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
final String message = "GameBoard MOVE test";
final GameState game = cordaClient.gameProposalCreate( 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()); System.out.println("Game UUID " +game.uuid());
final var m0 = cordaClient.gameProposalAccept(hiBlack, game.uuid()); final var m0 = cordaClient.gameProposalAccept(hiBlack, game.uuid());
@ -110,6 +109,60 @@ public class GameBoardTests {
assertThat(m2.board().get(15)).isEqualTo(BLACK_MAN); assertThat(m2.board().get(15)).isEqualTo(BLACK_MAN);
assertThat(m2.moveNumber() == 2); assertThat(m2.moveNumber() == 2);
assertThat(m2.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); assertThat(m2.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
assertThatThrownBy(() -> {
cordaClient.gameBoardMove(hiWhite, game.uuid(), move(24, 19),
"An attempt to ignore mandatory capture");
});
final var m3 = cordaClient.gameBoardMove(hiWhite, game.uuid(), move(18, 11), null);
assertThat(m2.board().get(18)).isEqualTo(WHITE_MAN);
assertThat(m2.board().get(11)).isNull();
assertThat(m2.board().get(15)).isEqualTo(BLACK_MAN);
assertThat(m3.board().get(11)).isEqualTo(WHITE_MAN);
assertThat(m3.board().get(18)).isNull();
assertThat(m3.board().get(15)).isNull();
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<Integer> move(int from, int to) { ArrayList<Integer> move(int from, int to) {

View File

@ -31,6 +31,10 @@ public class Move {
} }
private Stone.Color apply(Map<Integer, Stone> board, Stone.Color expectedMoveColor) { private Stone.Color apply(Map<Integer, Stone> board, Stone.Color expectedMoveColor) {
final Set<Move> 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); Stone movingStone = board.remove(from);
if (movingStone == null) if (movingStone == null)
throw new Exception("An empty starting tile"); throw new Exception("An empty starting tile");
@ -42,8 +46,6 @@ public class Move {
if (!allMoves.contains(this)) if (!allMoves.contains(this))
throw new Exception("Prohibited move"); throw new Exception("Prohibited move");
// TODO: check for mandatory captures
movingStone = movingStone.promoteIfPossible(to); movingStone = movingStone.promoteIfPossible(to);
if (board.put(to, movingStone) != null) if (board.put(to, movingStone) != null)
throw new Exception("An occupied finishing tile"); throw new Exception("An occupied finishing tile");
@ -52,21 +54,41 @@ public class Move {
final Stone jumpOver = board.remove(this.jumpOver); final Stone jumpOver = board.remove(this.jumpOver);
if (jumpOver == null || jumpOver.getColor() != expectedMoveColor.opposite()) if (jumpOver == null || jumpOver.getColor() != expectedMoveColor.opposite())
throw new Exception("Must jump over an opponent's stone"); throw new Exception("Must jump over an opponent's stone");
final Set<Move> 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; return this.jumpOver != null;
} }
boolean canJump(Map<Integer, Stone> 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<Integer> a, Set<Integer> b) { static Integer intersection(Set<Integer> a, Set<Integer> b) {
Set<Integer> intersection = new HashSet<Integer>(a); Set<Integer> intersection = new HashSet<Integer>(a);
intersection.retainAll(b); intersection.retainAll(b);
if (intersection.size() != 1) if (intersection.size() != 1)
// A legit move is characterized by single intersection point // A legit move is characterized by a single intersection point
throw new Exception("Prohibited move"); throw new Exception("Prohibited move");
return intersection.iterator().next(); return intersection.iterator().next();
@ -113,7 +135,7 @@ public class Move {
Map.entry(2, Arrays.asList(6, 7, 9, 11)), Map.entry(2, Arrays.asList(6, 7, 9, 11)),
Map.entry(3, Arrays.asList(7, 8, 10, 12)), Map.entry(3, Arrays.asList(7, 8, 10, 12)),
Map.entry(4, Arrays.asList(8, 11)), Map.entry(4, Arrays.asList(8, 11)),
Map.entry(5, Arrays.asList(1, 9, 14, 9, 11)), Map.entry(5, Arrays.asList(1, 9, 14)),
Map.entry(6, Arrays.asList(1, 2, 9, 10, 13, 15)), Map.entry(6, Arrays.asList(1, 2, 9, 10, 13, 15)),
Map.entry(7, Arrays.asList(2, 3, 10, 11, 14, 16)), Map.entry(7, Arrays.asList(2, 3, 10, 11, 14, 16)),
Map.entry(8, Arrays.asList(3, 4, 11, 12, 15)), Map.entry(8, Arrays.asList(3, 4, 11, 12, 15)),
@ -149,6 +171,25 @@ public class Move {
} }
} }
static Set<Move> getMandatoryJumps(Map<Integer, Stone> board, Stone.Color color) {
Set<Move> res = new HashSet<Move>();
for (Map.Entry<Integer, Stone> entry : board.entrySet()) {
final Integer from = entry.getKey();
final Stone stone = entry.getValue();
if (stone.getColor() != color)
continue;
final Set<Move> jumps = stone.getJumps(from);
jumps.removeIf( move -> !move.canJump(board, color));
res.addAll(jumps);
}
return res;
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;