Compare commits

..

No commits in common. "7d26dca7522a449f7add1c608d14a29ccc29c821" and "1f2ff242e4f11a7aecc6e763263f6748323cc2b8" have entirely different histories.

18 changed files with 192 additions and 617 deletions

View File

@ -18,7 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import djmil.cordacheckers.cordaclient.dao.GameState;
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
import djmil.cordacheckers.cordaclient.dao.Stone;
import djmil.cordacheckers.cordaclient.dao.Piece;
import djmil.cordacheckers.cordaclient.dao.Rank;
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
@ -95,7 +95,7 @@ public class CordaClient {
.getResponce(requestBody);
}
public GameState gameProposalCreate(HoldingIdentity issuer, HoldingIdentity acquier, Stone.Color acquierColor,
public GameState gameProposalCreate(HoldingIdentity issuer, HoldingIdentity acquier, Piece.Color acquierColor,
String message
) {
final RequestBody requestBody = new RequestBody(

View File

@ -7,9 +7,9 @@ import java.util.UUID;
public record GameState(
Status status,
String opponentName,
Stone.Color opponentColor,
Piece.Color opponentColor,
Map<Integer, Stone> board,
Map<Integer, Piece> board,
Integer moveNumber,
List<Integer> previousMove,

View File

@ -1,6 +1,6 @@
package djmil.cordacheckers.cordaclient.dao;
public class Stone {
public class Piece {
public enum Type {
MAN,
@ -19,12 +19,12 @@ public class Stone {
Color color;
Type type;
public Stone() {
public Piece() {
this.color = null;
this.type = null;
}
public Stone(Color color, Type type) {
public Piece(Color color, Type type) {
this.color = color;
this.type = type;
}
@ -50,7 +50,7 @@ public class Stone {
return false;
if (getClass() != obj.getClass())
return false;
Stone other = (Stone) obj;
Piece other = (Piece) obj;
if (color != other.color)
return false;
if (type != other.type)

View File

@ -1,10 +1,10 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import djmil.cordacheckers.cordaclient.dao.Stone;
import djmil.cordacheckers.cordaclient.dao.Piece;
public record ReqGameProposalCreate(
String opponentName,
Stone.Color opponentColor,
Piece.Color opponentColor,
String message
) {

View File

@ -19,7 +19,7 @@ import com.fasterxml.jackson.databind.JsonMappingException;
import djmil.cordacheckers.cordaclient.CordaClient;
import djmil.cordacheckers.cordaclient.dao.GameState;
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
import djmil.cordacheckers.cordaclient.dao.Stone;
import djmil.cordacheckers.cordaclient.dao.Piece;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameProposalCreate;
import djmil.cordacheckers.user.HoldingIdentityResolver;
import djmil.cordacheckers.user.User;
@ -59,7 +59,7 @@ public class GameProposalController {
final HoldingIdentity gpSender = sender.getHoldingIdentity();
// TODO: throw execption with custom type
final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName());
final Stone.Color gpReceiverColor = gpRequest.opponentColor();
final Piece.Color gpReceiverColor = gpRequest.opponentColor();
// TODO handle expectionns here
GameState gameStateView = cordaClient.gameProposalCreate(

View File

@ -1,215 +0,0 @@
package djmil.cordacheckers;
import static java.lang.Math.abs;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.annotation.Testable;
@Testable
public class CheckersMoveTest {
public final static Map<Integer, List<Integer>> adjacentCell = Map.ofEntries(
Map.entry(1, Arrays.asList(5, 6, 10)),
Map.entry(2, Arrays.asList(6, 7, 9, 11)),
Map.entry(3, Arrays.asList(7, 8, 10, 12)),
Map.entry(4, Arrays.asList(8, 11)),
Map.entry(5, Arrays.asList(1, 9, 14, 9, 11)),
Map.entry(6, Arrays.asList(1, 2, 9, 10, 13, 15)),
Map.entry(7, Arrays.asList(2, 3, 10, 11, 14, 16)),
Map.entry(8, Arrays.asList(3, 4, 11, 12, 15)),
Map.entry(9, Arrays.asList(2, 5, 6, 13, 14, 18)),
Map.entry(10, Arrays.asList(1, 3, 6, 7, 14, 15, 17, 19)),
Map.entry(11, Arrays.asList(2, 4, 7, 8, 15, 16, 18, 20)),
Map.entry(12, Arrays.asList(3, 8, 16, 19)),
Map.entry(13, Arrays.asList(6, 9, 17, 22)),
Map.entry(14, Arrays.asList(5, 7, 9, 10, 17, 18, 21, 23)),
Map.entry(15, Arrays.asList(6, 8, 10, 11, 18, 19, 22, 24)),
Map.entry(16, Arrays.asList(7, 11, 12, 19, 20, 23)),
Map.entry(17, Arrays.asList(10, 13, 14, 21, 22, 26)),
Map.entry(18, Arrays.asList(9, 11, 14, 15, 22, 23, 25, 27)),
Map.entry(19, Arrays.asList(10, 12, 15, 16, 23, 24)),
Map.entry(20, Arrays.asList(11, 16, 24, 27)),
Map.entry(21, Arrays.asList(14, 17, 25, 30)),
Map.entry(22, Arrays.asList(13, 15, 17, 18, 25, 26, 29, 31)),
Map.entry(23, Arrays.asList(14, 16, 18, 19, 26, 27, 30, 32)),
Map.entry(24, Arrays.asList(15, 19, 20, 27, 28, 31)),
Map.entry(25, Arrays.asList(18, 21, 22, 29, 30)),
Map.entry(26, Arrays.asList(17, 19, 22, 23, 30, 31)),
Map.entry(27, Arrays.asList(18, 20, 23, 24, 31, 32)),
Map.entry(28, Arrays.asList(19, 24, 32)),
Map.entry(29, Arrays.asList(22, 25)),
Map.entry(30, Arrays.asList(21, 23, 25, 26)),
Map.entry(31, Arrays.asList(22, 24, 26, 27)),
Map.entry(32, Arrays.asList(23, 27, 28))
);
public static class Jump {
final public int jump;
final public int step;
Jump(int jump, int step) {
this.jump = jump;
this.step = step;
}
static Set<Jump> intersect(Set<Integer> jumps, Set<Integer> steps) {
Set<Jump> res = new HashSet<Jump>();
for (Integer jump :jumps) {
var jumpSteps = getKingSteps(jump);
jumpSteps.retainAll(steps);
if (jumpSteps.size() == 1)
res.add(new Jump(jump, jumpSteps.iterator().next()));
}
return res;
}
@Override
public String toString() {
return "[jump=" + jump + ", step=" + step + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + jump;
result = prime * result + step;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Jump other = (Jump) obj;
if (jump != other.jump)
return false;
if (step != other.step)
return false;
return true;
}
}
public static Set<Integer> getBlackSteps(Integer idx) {
return adjacentCell.get(idx).stream()
.filter(cur -> cur > idx && cur - idx <= 5)
.collect(Collectors.toSet());
}
public static Set<Integer> getWhiteSteps(Integer idx) {
return adjacentCell.get(idx).stream()
.filter(cur -> cur < idx && idx - cur <= 5)
.collect(Collectors.toSet());
}
public static Set<Integer> getKingSteps(Integer idx) {
return adjacentCell.get(idx).stream()
.filter(cur -> abs(idx - cur) <= 5)
.collect(Collectors.toSet());
}
public static Set<Jump> getBlackJumps(Integer idx) {
final var allSteps = getBlackSteps(idx);
final var allJumps = adjacentCell.get(idx).stream()
.filter(cur -> cur > idx && cur - idx > 5)
.collect(Collectors.toSet());
return Jump.intersect(allJumps, allSteps);
}
public static Set<Jump> getWhiteJumps(Integer idx) {
final var allSteps = getWhiteSteps(idx);
final var allJumps = adjacentCell.get(idx).stream()
.filter(cur -> idx > cur && idx - cur > 5)
.collect(Collectors.toSet());
return Jump.intersect(allJumps, allSteps);
}
public static Set<Jump> getKingJumps(Integer idx) {
final var allSteps = getKingSteps(idx);
final var allJumps = adjacentCell.get(idx).stream()
.filter(cur -> abs(idx - cur) > 5)
.collect(Collectors.toSet());
return Jump.intersect(allJumps, allSteps);
}
@Test
void blackStepTest() {
assertThat(getBlackSteps(1)).containsAll(Arrays.asList(5, 6));
assertThat(getBlackSteps(4)).containsAll(Arrays.asList(8));
assertThat(getBlackSteps(18)).containsAll(Arrays.asList(22, 23));
assertThat(getBlackSteps(21)).containsAll(Arrays.asList(25));
}
@Test
void whiteStepTest() {
assertThat(getWhiteSteps(29)).containsAll(Arrays.asList(25));
assertThat(getWhiteSteps(28)).containsAll(Arrays.asList(24));
assertThat(getWhiteSteps(14)).containsAll(Arrays.asList(9, 10));
assertThat(getWhiteSteps(8)).containsAll(Arrays.asList(3, 4));
}
@Test
void kingStepTest() {
assertThat(getKingSteps(29)).containsAll(Arrays.asList(25));
assertThat(getKingSteps(31)).containsAll(Arrays.asList(26, 27));
assertThat(getKingSteps(20)).containsAll(Arrays.asList(16, 24));
assertThat(getKingSteps(13)).containsAll(Arrays.asList(9, 17));
assertThat(getKingSteps(2)).containsAll(Arrays.asList(6, 7));
assertThat(getKingSteps(15)).containsAll(Arrays.asList(10, 11, 18, 19));
}
@Test
void blackJumpTest() {
assertThat(getBlackJumps(4)).containsAll(
Arrays.asList(new Jump(11, 8)));
assertThat(getBlackJumps(16)).containsAll(
Arrays.asList(new Jump(23, 19)));
assertThat(getBlackJumps(15)).containsAll(
Arrays.asList(new Jump(22, 18), new Jump(24, 19)));
assertThat(getBlackJumps(28)).isEmpty();
}
@Test
void whiteJumpTest() {
assertThat(getWhiteJumps(30)).containsAll(
Arrays.asList(new Jump(21, 25), new Jump(23, 26)));
assertThat(getWhiteJumps(17)).containsAll(
Arrays.asList(new Jump(10, 14)));
assertThat(getWhiteJumps(7)).isEmpty();
assertThat(getWhiteJumps(9)).containsAll(
Arrays.asList(new Jump(2, 6)));
}
@Test
void kingJumpTest() {
assertThat(getKingJumps(11)).containsAll(
Arrays.asList(new Jump(2, 7), new Jump(4, 8), new Jump(18, 15),new Jump(20, 16)));
assertThat(getKingJumps(17)).containsAll(
Arrays.asList(new Jump(10, 14), new Jump(26, 22)));
assertThat(getKingJumps(32)).containsAll(
Arrays.asList(new Jump(23, 27)));
}
}

View File

@ -12,7 +12,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import djmil.cordacheckers.cordaclient.dao.GameState;
import djmil.cordacheckers.cordaclient.dao.GameState.Status;
import djmil.cordacheckers.cordaclient.dao.Stone;
import djmil.cordacheckers.cordaclient.dao.Piece;
import djmil.cordacheckers.user.HoldingIdentityResolver;
@SpringBootTest
@ -26,10 +26,10 @@ public class GameBoardTests {
final String whitePlayerName = "alice";
final String blackPlayerName = "bob";
final static Stone WHITE_MAN = new Stone(Stone.Color.WHITE, Stone.Type.MAN);
final static Stone WHITE_KING = new Stone(Stone.Color.WHITE, Stone.Type.KING);
final static Stone BLACK_MAN = new Stone(Stone.Color.BLACK, Stone.Type.MAN);
final static Stone BLACK_KING = new Stone(Stone.Color.BLACK, Stone.Type.KING);
final static Piece WHITE_MAN = new Piece(Piece.Color.WHITE, Piece.Type.MAN);
final static Piece WHITE_KING = new Piece(Piece.Color.WHITE, Piece.Type.KING);
final static Piece BLACK_MAN = new Piece(Piece.Color.BLACK, Piece.Type.MAN);
final static Piece BLACK_KING = new Piece(Piece.Color.BLACK, Piece.Type.KING);
@Test
void testSurrender() {
@ -38,14 +38,14 @@ public class GameBoardTests {
final String message = "GameBoard SURRENDER test";
final GameState game = cordaClient.gameProposalCreate(
hiWhite, hiBlack, Stone.Color.BLACK, message);
hiWhite, hiBlack, Piece.Color.BLACK, message);
System.out.println("Game UUID " +game.uuid());
final GameState acceptedGameBlackView = cordaClient.gameProposalAccept(
hiBlack, game.uuid());
assertThat(acceptedGameBlackView.opponentColor()).isEqualByComparingTo(Stone.Color.WHITE);
assertThat(acceptedGameBlackView.opponentColor()).isEqualByComparingTo(Piece.Color.WHITE);
assertThat(acceptedGameBlackView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
assertThatThrownBy(() -> { // Black can not surrender, since it is opponent's turn
@ -57,14 +57,14 @@ public class GameBoardTests {
hiWhite, game.uuid());
assertThat(surrendererGameView.opponentName()).isEqualToIgnoringCase(blackPlayerName);
assertThat(surrendererGameView.opponentColor()).isEqualByComparingTo(Stone.Color.BLACK);
assertThat(surrendererGameView.opponentColor()).isEqualByComparingTo(Piece.Color.BLACK);
assertThat(surrendererGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_LOOSE);
final GameState winnerGameView = cordaClient.gameStateGet(
hiBlack, game.uuid());
assertThat(winnerGameView.opponentName()).isEqualToIgnoringCase(whitePlayerName);
assertThat(winnerGameView.opponentColor()).isEqualByComparingTo(Stone.Color.WHITE);
assertThat(winnerGameView.opponentColor()).isEqualByComparingTo(Piece.Color.WHITE);
assertThat(winnerGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_WON);
}
@ -75,7 +75,7 @@ public class GameBoardTests {
final String message = "GameBoard MOVE test";
final GameState game = cordaClient.gameProposalCreate(
hiWhite, hiBlack, Stone.Color.BLACK, message);
hiWhite, hiBlack, Piece.Color.BLACK, message);
System.out.println("Game UUID " +game.uuid());
final var m0 = cordaClient.gameProposalAccept(hiBlack, game.uuid());
@ -95,21 +95,7 @@ public class GameBoardTests {
assertThat(m0.board().get(18)).isNull();
assertThat(m1.board().get(22)).isNull();
assertThat(m1.board().get(18)).isEqualTo(WHITE_MAN);
assertThat(m1.moveNumber() == 1);
assertThat(m1.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
assertThatThrownBy(() -> {
cordaClient.gameBoardMove(hiBlack, game.uuid(), move(12, 13),
"Prohibitted move shall be rejected");
});
final var m2 = cordaClient.gameBoardMove(hiBlack, game.uuid(), move(11, 15), null);
assertThat(m1.board().get(11)).isEqualTo(BLACK_MAN);
assertThat(m1.board().get(15)).isNull();
assertThat(m2.board().get(11)).isNull();
assertThat(m2.board().get(15)).isEqualTo(BLACK_MAN);
assertThat(m2.moveNumber() == 2);
assertThat(m2.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
}
ArrayList<Integer> move(int from, int to) {

View File

@ -10,7 +10,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import djmil.cordacheckers.cordaclient.dao.GameState;
import djmil.cordacheckers.cordaclient.dao.GameState.Status;
import djmil.cordacheckers.cordaclient.dao.Stone;
import djmil.cordacheckers.cordaclient.dao.Piece;
import djmil.cordacheckers.user.HoldingIdentityResolver;
@SpringBootTest
@ -23,7 +23,7 @@ public class GameProposalTests {
final String issuer = "alice";
final String acquier = "bob";
final Stone.Color acquierColor = Stone.Color.WHITE;
final Piece.Color acquierColor = Piece.Color.WHITE;
@Test
void testCreate() {

View File

@ -8,7 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import djmil.cordacheckers.cordaclient.dao.GameState;
import djmil.cordacheckers.cordaclient.dao.Stone;
import djmil.cordacheckers.cordaclient.dao.Piece;
import djmil.cordacheckers.user.HoldingIdentityResolver;
@SpringBootTest
@ -21,7 +21,7 @@ public class GameStateTests {
final String issuer = "alice";
final String acquier = "bob";
final Stone.Color acquierColor = Stone.Color.WHITE;
final Piece.Color acquierColor = Piece.Color.WHITE;
@Test
void testList() {

View File

@ -9,7 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import djmil.cordacheckers.cordaclient.dao.GameState;
import djmil.cordacheckers.cordaclient.dao.Stone;
import djmil.cordacheckers.cordaclient.dao.Piece;
import djmil.cordacheckers.cordaclient.dao.Rank;
import djmil.cordacheckers.user.HoldingIdentityResolver;
@ -32,7 +32,7 @@ public class RankingTests {
final var hiLooser = holdingIdentityResolver.getByUsername("Bob");
final GameState game = cordaClient.gameProposalCreate(
hiWinner, hiLooser, Stone.Color.WHITE, "GameBoard GLOBAL_RANKING test");
hiWinner, hiLooser, Piece.Color.WHITE, "GameBoard GLOBAL_RANKING test");
cordaClient.gameProposalAccept(hiLooser, game.uuid());
cordaClient.gameBoardSurrender(hiLooser, game.uuid());

View File

@ -1,194 +0,0 @@
package djmil.cordacheckers.checkers;
import static java.lang.Math.abs;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class Move {
final public Integer from;
final public Integer to;
final public Integer jumpOver;
public Move(int from, int to) {
this.from = from;
this.to = to;
Set<Integer> fromSteps = getSteps(from);
if (fromSteps.contains(to))
this.jumpOver = null;
else
this.jumpOver = intersection(fromSteps, getSteps(to));
}
public static Stone.Color apply(List<Integer> moves, Map<Integer, Stone> board, Stone.Color moveColor) {
Move move = new Move(moves.get(0), moves.get(1));
return move.apply(board, moveColor);
}
private Stone.Color apply(Map<Integer, Stone> board, Stone.Color expectedMoveColor) {
Stone movingStone = board.remove(from);
if (movingStone == null)
throw new Exception("An empty starting tile");
if (movingStone.getColor() != expectedMoveColor)
throw new Exception("Only " +expectedMoveColor.name() +" color is expected to move");
final Set<Move> allMoves = movingStone.getMoves(from);
if (!allMoves.contains(this))
throw new Exception("Prohibited move");
// TODO: check for mandatory captures
movingStone = movingStone.promoteIfPossible(to);
if (board.put(to, movingStone) != null)
throw new Exception("An occupied finishing tile");
if (isJump()) {
final Stone jumpOver = board.remove(this.jumpOver);
if (jumpOver == null || jumpOver.getColor() != expectedMoveColor.opposite())
throw new Exception("Must jump over an opponent's stone");
}
return expectedMoveColor.opposite();
}
public boolean isJump() {
return this.jumpOver != null;
}
static Integer intersection(Set<Integer> a, Set<Integer> b) {
Set<Integer> intersection = new HashSet<Integer>(a);
intersection.retainAll(b);
if (intersection.size() != 1)
// A legit move is characterized by single intersection point
throw new Exception("Prohibited move");
return intersection.iterator().next();
}
static Set<Integer> getSteps(Integer idx) {
return adjacentCell.get(idx).stream()
.filter(cur -> abs(idx - cur) <= 5)
.collect(Collectors.toSet());
}
static Set<Integer> getJumps(Integer idx) {
return adjacentCell.get(idx).stream()
.filter(cur -> abs(idx - cur) > 5)
.collect(Collectors.toSet());
}
static Set<Integer> getBlackSteps(Integer idx) {
return adjacentCell.get(idx).stream()
.filter(cur -> cur > idx && cur - idx <= 5)
.collect(Collectors.toSet());
}
static Set<Integer> getWhiteSteps(Integer idx) {
return adjacentCell.get(idx).stream()
.filter(cur -> cur < idx && idx - cur <= 5)
.collect(Collectors.toSet());
}
static Set<Integer> getBlackJumps(Integer idx) {
return adjacentCell.get(idx).stream()
.filter(cur -> cur > idx && cur - idx > 5)
.collect(Collectors.toSet());
}
static Set<Integer> getWhiteJumps(Integer idx) {
return adjacentCell.get(idx).stream()
.filter(cur -> cur < idx && idx - cur > 5)
.collect(Collectors.toSet());
}
final static Map<Integer, List<Integer>> adjacentCell = Map.ofEntries(
Map.entry(1, Arrays.asList(5, 6, 10)),
Map.entry(2, Arrays.asList(6, 7, 9, 11)),
Map.entry(3, Arrays.asList(7, 8, 10, 12)),
Map.entry(4, Arrays.asList(8, 11)),
Map.entry(5, Arrays.asList(1, 9, 14, 9, 11)),
Map.entry(6, Arrays.asList(1, 2, 9, 10, 13, 15)),
Map.entry(7, Arrays.asList(2, 3, 10, 11, 14, 16)),
Map.entry(8, Arrays.asList(3, 4, 11, 12, 15)),
Map.entry(9, Arrays.asList(2, 5, 6, 13, 14, 18)),
Map.entry(10, Arrays.asList(1, 3, 6, 7, 14, 15, 17, 19)),
Map.entry(11, Arrays.asList(2, 4, 7, 8, 15, 16, 18, 20)),
Map.entry(12, Arrays.asList(3, 8, 16, 19)),
Map.entry(13, Arrays.asList(6, 9, 17, 22)),
Map.entry(14, Arrays.asList(5, 7, 9, 10, 17, 18, 21, 23)),
Map.entry(15, Arrays.asList(6, 8, 10, 11, 18, 19, 22, 24)),
Map.entry(16, Arrays.asList(7, 11, 12, 19, 20, 23)),
Map.entry(17, Arrays.asList(10, 13, 14, 21, 22, 26)),
Map.entry(18, Arrays.asList(9, 11, 14, 15, 22, 23, 25, 27)),
Map.entry(19, Arrays.asList(10, 12, 15, 16, 23, 24)),
Map.entry(20, Arrays.asList(11, 16, 24, 27)),
Map.entry(21, Arrays.asList(14, 17, 25, 30)),
Map.entry(22, Arrays.asList(13, 15, 17, 18, 25, 26, 29, 31)),
Map.entry(23, Arrays.asList(14, 16, 18, 19, 26, 27, 30, 32)),
Map.entry(24, Arrays.asList(15, 19, 20, 27, 28, 31)),
Map.entry(25, Arrays.asList(18, 21, 22, 29, 30)),
Map.entry(26, Arrays.asList(17, 19, 22, 23, 30, 31)),
Map.entry(27, Arrays.asList(18, 20, 23, 24, 31, 32)),
Map.entry(28, Arrays.asList(19, 24, 32)),
Map.entry(29, Arrays.asList(22, 25)),
Map.entry(30, Arrays.asList(21, 23, 25, 26)),
Map.entry(31, Arrays.asList(22, 24, 26, 27)),
Map.entry(32, Arrays.asList(23, 27, 28))
);
public static class Exception extends RuntimeException {
public Exception(String message) {
super(message);
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((from == null) ? 0 : from.hashCode());
result = prime * result + ((to == null) ? 0 : to.hashCode());
result = prime * result + ((jumpOver == null) ? 0 : jumpOver.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Move other = (Move) obj;
if (from == null) {
if (other.from != null)
return false;
} else if (!from.equals(other.from))
return false;
if (to == null) {
if (other.to != null)
return false;
} else if (!to.equals(other.to))
return false;
if (jumpOver == null) {
if (other.jumpOver != null)
return false;
} else if (!jumpOver.equals(other.jumpOver))
return false;
return true;
}
@Override
public String toString() {
return "[from=" + from + ", to=" + to + ", jumpOver=" + jumpOver + "]";
}
}

View File

@ -1,112 +0,0 @@
package djmil.cordacheckers.checkers;
import java.util.HashSet;
import java.util.Set;
import net.corda.v5.base.annotations.ConstructorForDeserialization;
import net.corda.v5.base.annotations.CordaSerializable;
@CordaSerializable
public class Stone {
@CordaSerializable
public enum Type {
MAN,
KING;
}
@CordaSerializable
public enum Color {
WHITE,
BLACK;
public Color opposite() {
switch (this) {
case WHITE:
return BLACK;
case BLACK:
return WHITE;
}
throw new RuntimeException("Unknown Color");
}
}
private final Color color;
private final Type type;
@ConstructorForDeserialization
public Stone(Color color, Type type) {
this.color = color;
this.type = type;
}
public Color getColor() {
return color;
}
public Type getType() {
return type;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Stone other = (Stone) obj;
if (color != other.color)
return false;
if (type != other.type)
return false;
return true;
}
public Set<Move> getJumps(Integer from) {
Set<Integer> jumps;
if (type == Type.KING)
jumps = Move.getJumps(from);
else if (color == Color.BLACK)
jumps = Move.getBlackJumps(from);
else
jumps = Move.getWhiteJumps(from);
Set<Move> res = new HashSet<>();
for (Integer jump : jumps)
res.add(new Move(from, jump));
return res;
}
public Set<Move> getMoves(Integer from) {
Set<Integer> steps;
if (type == Type.KING)
steps = Move.getSteps(from);
else if (color == Color.BLACK)
steps = Move.getBlackSteps(from);
else
steps = Move.getWhiteSteps(from);
Set<Move> moves = getJumps(from); // <<--- Steps + Jumps
for (Integer step : steps) {
moves.add(new Move(from, step));
}
return moves;
}
public Stone promoteIfPossible(Integer to) {
if ((color == Color.WHITE && to >= 1 && to <= 4) ||
(color == Color.BLACK && to >= 29 && to <= 32))
return new Stone(color, Type.KING);
else
return this;
}
}

View File

@ -3,16 +3,35 @@ package djmil.cordacheckers.contracts;
import static djmil.cordacheckers.contracts.GameCommand.requireThat;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import djmil.cordacheckers.checkers.Stone;
import djmil.cordacheckers.states.Piece;
import djmil.cordacheckers.states.Piece.Color;
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
public static class MoveResult {
final public Map<Integer, Piece> board;
final public Piece.Color moveColor;
public MoveResult(Map<Integer, Piece> board, Color moveColor) {
this.board = board;
this.moveColor = moveColor;
}
public static class Exception extends RuntimeException {
public Exception(String message) {
super(message);
}
}
}
private final static Logger log = LoggerFactory.getLogger(GameBoardContract.class);
@Override
@ -40,33 +59,60 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
}
}
public final static Map<Integer, Stone> initialBoard = Map.ofEntries(
// Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php
Map.entry( 1, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
Map.entry( 2, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
Map.entry( 3, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
Map.entry( 4, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
Map.entry( 5, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
Map.entry( 6, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
Map.entry( 7, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
Map.entry( 8, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
Map.entry( 9, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
Map.entry(10, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
Map.entry(11, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
Map.entry(12, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
public static MoveResult applyMove(List<Integer> move, Map<Integer, Piece> board, Piece.Color moveColor) {
final int mFrom = move.get(0);
final int mTo = move.get(1);
Map.entry(21, new Stone(Stone.Color.WHITE, Stone.Type.MAN)),
Map.entry(22, new Stone(Stone.Color.WHITE, Stone.Type.MAN)),
Map.entry(23, new Stone(Stone.Color.WHITE, Stone.Type.MAN)),
Map.entry(24, new Stone(Stone.Color.WHITE, Stone.Type.MAN)),
Map.entry(25, new Stone(Stone.Color.WHITE, Stone.Type.MAN)),
Map.entry(26, new Stone(Stone.Color.WHITE, Stone.Type.MAN)),
Map.entry(27, new Stone(Stone.Color.WHITE, Stone.Type.MAN)),
Map.entry(28, new Stone(Stone.Color.WHITE, Stone.Type.MAN)),
Map.entry(29, new Stone(Stone.Color.WHITE, Stone.Type.MAN)),
Map.entry(30, new Stone(Stone.Color.WHITE, Stone.Type.MAN)),
Map.entry(31, new Stone(Stone.Color.WHITE, Stone.Type.MAN)),
Map.entry(32, new Stone(Stone.Color.WHITE, Stone.Type.MAN))
final Piece piece = board.get(mFrom);
if (piece == null)
throw new MoveResult.Exception("An empty starting tile");
if (piece.getColor() != moveColor)
throw new MoveResult.Exception("Can not move opponent's piece");
if (board.get(mTo) != null)
throw new MoveResult.Exception("An occupied finishing tile");
final Map<Integer, Piece> newBoard = new LinkedHashMap<Integer, Piece>(board);
newBoard.remove(mFrom);
newBoard.put(mTo, piece);
return new GameBoardContract.MoveResult(newBoard, moveColor.opposite());
}
final static Piece WHITE_MAN = new Piece(Piece.Color.WHITE, Piece.Type.MAN);
final static Piece WHITE_KING = new Piece(Piece.Color.WHITE, Piece.Type.KING);
final static Piece BLACK_MAN = new Piece(Piece.Color.BLACK, Piece.Type.MAN);
final static Piece BLACK_KING = new Piece(Piece.Color.BLACK, Piece.Type.KING);
public final static Map<Integer, Piece> initialBoard = Map.ofEntries(
// Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php
Map.entry( 1, BLACK_MAN),
Map.entry( 2, BLACK_MAN),
Map.entry( 3, BLACK_MAN),
Map.entry( 4, BLACK_MAN),
Map.entry( 5, BLACK_MAN),
Map.entry( 6, BLACK_MAN),
Map.entry( 7, BLACK_MAN),
Map.entry( 8, BLACK_MAN),
Map.entry( 9, BLACK_MAN),
Map.entry(10, BLACK_MAN),
Map.entry(11, BLACK_MAN),
Map.entry(12, BLACK_MAN),
Map.entry(21, WHITE_MAN),
Map.entry(22, WHITE_MAN),
Map.entry(23, WHITE_MAN),
Map.entry(24, WHITE_MAN),
Map.entry(25, WHITE_MAN),
Map.entry(26, WHITE_MAN),
Map.entry(27, WHITE_MAN),
Map.entry(28, WHITE_MAN),
Map.entry(29, WHITE_MAN),
Map.entry(30, WHITE_MAN),
Map.entry(31, WHITE_MAN),
Map.entry(32, WHITE_MAN)
);
}

View File

@ -6,25 +6,25 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import djmil.cordacheckers.checkers.Move;
import djmil.cordacheckers.checkers.Stone;
import djmil.cordacheckers.contracts.GameBoardContract;
import djmil.cordacheckers.contracts.GameBoardContract.MoveResult;
import djmil.cordacheckers.states.Piece.Color;
import net.corda.v5.base.annotations.ConstructorForDeserialization;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.BelongsToContract;
@BelongsToContract(GameBoardContract.class)
public class GameBoardState extends GameState {
private final Stone.Color moveColor;
private final Piece.Color moveColor;
private final Integer moveNumber;
private final Map<Integer, Stone> board;
private final Map<Integer, Piece> board;
public GameBoardState(GameProposalState gameProposalState) {
super(gameProposalState.whitePlayer, gameProposalState.blackPlayer,
gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants);
this.board = new LinkedHashMap<Integer, Stone>(GameBoardContract.initialBoard);
this.moveColor = Stone.Color.WHITE;
this.board = new LinkedHashMap<Integer, Piece>(GameBoardContract.initialBoard);
this.moveColor = Piece.Color.WHITE;
this.moveNumber = 0;
}
@ -32,8 +32,9 @@ public class GameBoardState extends GameState {
super(currentGameBoardState.whitePlayer, currentGameBoardState.blackPlayer, currentGameBoardState.gameUuid,
message, currentGameBoardState.participants);
this.board = new LinkedHashMap<Integer, Stone>(currentGameBoardState.getBoard());
this.moveColor = Move.apply(move, this.board, currentGameBoardState.getMoveColor());
final MoveResult moveResult = GameBoardContract.applyMove(move, currentGameBoardState.getBoard(), currentGameBoardState.getMoveColor());
this.moveColor = moveResult.moveColor;
this.board = moveResult.board;
this.moveNumber = (currentGameBoardState.moveColor == this.moveColor)
? currentGameBoardState.getMoveNumber() // current player has not finished his move jet
@ -42,7 +43,7 @@ public class GameBoardState extends GameState {
@ConstructorForDeserialization
public GameBoardState(MemberX500Name whitePlayer, MemberX500Name blackPlayer,
Stone.Color moveColor, Integer moveNumber, Map<Integer, Stone> board, String message,
Color moveColor, Integer moveNumber, Map<Integer, Piece> board, String message,
UUID gameUuid, List<PublicKey> participants) {
super(whitePlayer, blackPlayer, gameUuid, message, participants);
@ -51,7 +52,7 @@ public class GameBoardState extends GameState {
this.board = board;
}
public Stone.Color getMoveColor() {
public Piece.Color getMoveColor() {
return moveColor;
}
@ -60,10 +61,10 @@ public class GameBoardState extends GameState {
}
public MemberX500Name getActivePlayerName() {
return moveColor == Stone.Color.WHITE ? whitePlayer : blackPlayer;
return moveColor == Piece.Color.WHITE ? whitePlayer : blackPlayer;
}
public Map<Integer, Stone> getBoard() {
public Map<Integer, Piece> getBoard() {
return board;
}

View File

@ -5,7 +5,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import djmil.cordacheckers.checkers.Stone;
import net.corda.v5.base.annotations.CordaSerializable;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.ContractState;
@ -68,11 +67,11 @@ public abstract class GameState implements ContractState {
throw new NotInvolved(playerName, this.getClass(), gameUuid);
}
public Stone.Color getOpponentColor(MemberX500Name playerName) throws NotInvolved {
public Piece.Color getOpponentColor(MemberX500Name playerName) throws NotInvolved {
if (playerName.compareTo(whitePlayer) == 0)
return Stone.Color.BLACK;
return Piece.Color.BLACK;
if (playerName.compareTo(blackPlayer) == 0)
return Stone.Color.WHITE;
return Piece.Color.WHITE;
throw new NotInvolved(playerName, this.getClass(), gameUuid);
}

View File

@ -0,0 +1,64 @@
package djmil.cordacheckers.states;
import net.corda.v5.base.annotations.ConstructorForDeserialization;
import net.corda.v5.base.annotations.CordaSerializable;
@CordaSerializable
public class Piece {
@CordaSerializable
public enum Type {
MAN,
KING;
}
@CordaSerializable
public enum Color {
WHITE,
BLACK;
public Color opposite() {
switch (this) {
case WHITE:
return BLACK;
case BLACK:
return WHITE;
}
throw new RuntimeException("Unknown Color");
}
}
private final Color color;
private final Type type;
@ConstructorForDeserialization
public Piece(Color color, Type type) {
this.color = color;
this.type = type;
}
public Color getColor() {
return color;
}
public Type getType() {
return type;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Piece other = (Piece) obj;
if (color != other.color)
return false;
if (type != other.type)
return false;
return true;
}
}

View File

@ -10,13 +10,13 @@ import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import djmil.cordacheckers.checkers.Stone;
import djmil.cordacheckers.contracts.GameCommand;
import djmil.cordacheckers.gamestate.CommitSubFlow;
import djmil.cordacheckers.gamestate.FlowResponce;
import djmil.cordacheckers.gamestate.View;
import djmil.cordacheckers.gamestate.ViewBuilder;
import djmil.cordacheckers.states.GameProposalState;
import djmil.cordacheckers.states.Piece;
import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow;
import net.corda.v5.application.flows.CordaInject;
@ -88,10 +88,10 @@ public class CreateFlow implements ClientStartableFlow{
private GameProposalState buildGameProposalStateFrom(ClientRequestBody requestBody) {
final CreateFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CreateFlowArgs.class);
final Stone.Color opponentColor = Stone.Color.valueOf(args.opponentColor);
final Piece.Color opponentColor = Piece.Color.valueOf(args.opponentColor);
if (opponentColor == null) {
throw new RuntimeException("Allowed values for opponentColor are: "
+ Stone.Color.WHITE.name() +", " + Stone.Color.BLACK.name()
+ Piece.Color.WHITE.name() +", " + Piece.Color.BLACK.name()
+ ". Actual value was: " + args.opponentColor);
}
@ -101,8 +101,8 @@ public class CreateFlow implements ClientStartableFlow{
"MemberLookup can't find opponentName specified in flow arguments: " + args.opponentName
);
final MemberInfo whitePlayerInfo = opponentColor == Stone.Color.WHITE ? opponentInfo : myInfo;
final MemberInfo blackPlayerInfo = opponentColor == Stone.Color.BLACK ? opponentInfo : myInfo;
final MemberInfo whitePlayerInfo = opponentColor == Piece.Color.WHITE ? opponentInfo : myInfo;
final MemberInfo blackPlayerInfo = opponentColor == Piece.Color.BLACK ? opponentInfo : myInfo;
return new GameProposalState(
whitePlayerInfo.getName(),

View File

@ -4,10 +4,10 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import djmil.cordacheckers.checkers.Stone;
import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.GameProposalState;
import djmil.cordacheckers.states.GameResultState;
import djmil.cordacheckers.states.Piece;
import net.corda.v5.base.types.MemberX500Name;
// GameBoard from the player's point of view
@ -27,9 +27,9 @@ public class View {
public final Status status;
public final String opponentName;
public final Stone.Color opponentColor;
public final Piece.Color opponentColor;
public final Map<Integer, Stone> board;
public final Map<Integer, Piece> board;
public final Integer moveNumber;
public final List<Integer> previousMove;