Compare commits
6 Commits
1f2ff242e4
...
7d26dca752
Author | SHA1 | Date | |
---|---|---|---|
7d26dca752 | |||
58da85a5fd | |||
162e5c97dc | |||
a9ffd4b0b9 | |||
9cbeaceca9 | |||
9144683de3 |
@ -18,7 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
|
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||||
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
|
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
|
||||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
import djmil.cordacheckers.cordaclient.dao.Stone;
|
||||||
import djmil.cordacheckers.cordaclient.dao.Rank;
|
import djmil.cordacheckers.cordaclient.dao.Rank;
|
||||||
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
||||||
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
|
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
|
||||||
@ -95,7 +95,7 @@ public class CordaClient {
|
|||||||
.getResponce(requestBody);
|
.getResponce(requestBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameState gameProposalCreate(HoldingIdentity issuer, HoldingIdentity acquier, Piece.Color acquierColor,
|
public GameState gameProposalCreate(HoldingIdentity issuer, HoldingIdentity acquier, Stone.Color acquierColor,
|
||||||
String message
|
String message
|
||||||
) {
|
) {
|
||||||
final RequestBody requestBody = new RequestBody(
|
final RequestBody requestBody = new RequestBody(
|
||||||
|
@ -7,9 +7,9 @@ import java.util.UUID;
|
|||||||
public record GameState(
|
public record GameState(
|
||||||
Status status,
|
Status status,
|
||||||
String opponentName,
|
String opponentName,
|
||||||
Piece.Color opponentColor,
|
Stone.Color opponentColor,
|
||||||
|
|
||||||
Map<Integer, Piece> board,
|
Map<Integer, Stone> board,
|
||||||
Integer moveNumber,
|
Integer moveNumber,
|
||||||
List<Integer> previousMove,
|
List<Integer> previousMove,
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package djmil.cordacheckers.cordaclient.dao;
|
package djmil.cordacheckers.cordaclient.dao;
|
||||||
|
|
||||||
public class Piece {
|
public class Stone {
|
||||||
|
|
||||||
public enum Type {
|
public enum Type {
|
||||||
MAN,
|
MAN,
|
||||||
@ -19,12 +19,12 @@ public class Piece {
|
|||||||
Color color;
|
Color color;
|
||||||
Type type;
|
Type type;
|
||||||
|
|
||||||
public Piece() {
|
public Stone() {
|
||||||
this.color = null;
|
this.color = null;
|
||||||
this.type = null;
|
this.type = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Piece(Color color, Type type) {
|
public Stone(Color color, Type type) {
|
||||||
this.color = color;
|
this.color = color;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ public class Piece {
|
|||||||
return false;
|
return false;
|
||||||
if (getClass() != obj.getClass())
|
if (getClass() != obj.getClass())
|
||||||
return false;
|
return false;
|
||||||
Piece other = (Piece) obj;
|
Stone other = (Stone) obj;
|
||||||
if (color != other.color)
|
if (color != other.color)
|
||||||
return false;
|
return false;
|
||||||
if (type != other.type)
|
if (type != other.type)
|
@ -1,10 +1,10 @@
|
|||||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||||
|
|
||||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
import djmil.cordacheckers.cordaclient.dao.Stone;
|
||||||
|
|
||||||
public record ReqGameProposalCreate(
|
public record ReqGameProposalCreate(
|
||||||
String opponentName,
|
String opponentName,
|
||||||
Piece.Color opponentColor,
|
Stone.Color opponentColor,
|
||||||
String message
|
String message
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import com.fasterxml.jackson.databind.JsonMappingException;
|
|||||||
import djmil.cordacheckers.cordaclient.CordaClient;
|
import djmil.cordacheckers.cordaclient.CordaClient;
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||||
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
|
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
|
||||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
import djmil.cordacheckers.cordaclient.dao.Stone;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameProposalCreate;
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameProposalCreate;
|
||||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||||
import djmil.cordacheckers.user.User;
|
import djmil.cordacheckers.user.User;
|
||||||
@ -59,7 +59,7 @@ public class GameProposalController {
|
|||||||
final HoldingIdentity gpSender = sender.getHoldingIdentity();
|
final HoldingIdentity gpSender = sender.getHoldingIdentity();
|
||||||
// TODO: throw execption with custom type
|
// TODO: throw execption with custom type
|
||||||
final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName());
|
final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName());
|
||||||
final Piece.Color gpReceiverColor = gpRequest.opponentColor();
|
final Stone.Color gpReceiverColor = gpRequest.opponentColor();
|
||||||
|
|
||||||
// TODO handle expectionns here
|
// TODO handle expectionns here
|
||||||
GameState gameStateView = cordaClient.gameProposalCreate(
|
GameState gameStateView = cordaClient.gameProposalCreate(
|
||||||
|
215
backend/src/test/java/djmil/cordacheckers/CheckersMoveTest.java
Normal file
215
backend/src/test/java/djmil/cordacheckers/CheckersMoveTest.java
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -12,7 +12,7 @@ import org.springframework.boot.test.context.SpringBootTest;
|
|||||||
|
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameState.Status;
|
import djmil.cordacheckers.cordaclient.dao.GameState.Status;
|
||||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
import djmil.cordacheckers.cordaclient.dao.Stone;
|
||||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ -26,10 +26,10 @@ public class GameBoardTests {
|
|||||||
final String whitePlayerName = "alice";
|
final String whitePlayerName = "alice";
|
||||||
final String blackPlayerName = "bob";
|
final String blackPlayerName = "bob";
|
||||||
|
|
||||||
final static Piece WHITE_MAN = new Piece(Piece.Color.WHITE, Piece.Type.MAN);
|
final static Stone WHITE_MAN = new Stone(Stone.Color.WHITE, Stone.Type.MAN);
|
||||||
final static Piece WHITE_KING = new Piece(Piece.Color.WHITE, Piece.Type.KING);
|
final static Stone WHITE_KING = new Stone(Stone.Color.WHITE, Stone.Type.KING);
|
||||||
final static Piece BLACK_MAN = new Piece(Piece.Color.BLACK, Piece.Type.MAN);
|
final static Stone BLACK_MAN = new Stone(Stone.Color.BLACK, Stone.Type.MAN);
|
||||||
final static Piece BLACK_KING = new Piece(Piece.Color.BLACK, Piece.Type.KING);
|
final static Stone BLACK_KING = new Stone(Stone.Color.BLACK, Stone.Type.KING);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSurrender() {
|
void testSurrender() {
|
||||||
@ -38,14 +38,14 @@ public class GameBoardTests {
|
|||||||
final String message = "GameBoard SURRENDER test";
|
final String message = "GameBoard SURRENDER test";
|
||||||
|
|
||||||
final GameState game = cordaClient.gameProposalCreate(
|
final GameState game = cordaClient.gameProposalCreate(
|
||||||
hiWhite, hiBlack, Piece.Color.BLACK, message);
|
hiWhite, hiBlack, Stone.Color.BLACK, message);
|
||||||
|
|
||||||
System.out.println("Game UUID " +game.uuid());
|
System.out.println("Game UUID " +game.uuid());
|
||||||
|
|
||||||
final GameState acceptedGameBlackView = cordaClient.gameProposalAccept(
|
final GameState acceptedGameBlackView = cordaClient.gameProposalAccept(
|
||||||
hiBlack, game.uuid());
|
hiBlack, game.uuid());
|
||||||
|
|
||||||
assertThat(acceptedGameBlackView.opponentColor()).isEqualByComparingTo(Piece.Color.WHITE);
|
assertThat(acceptedGameBlackView.opponentColor()).isEqualByComparingTo(Stone.Color.WHITE);
|
||||||
assertThat(acceptedGameBlackView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
|
assertThat(acceptedGameBlackView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
|
||||||
|
|
||||||
assertThatThrownBy(() -> { // Black can not surrender, since it is opponent's turn
|
assertThatThrownBy(() -> { // Black can not surrender, since it is opponent's turn
|
||||||
@ -57,14 +57,14 @@ public class GameBoardTests {
|
|||||||
hiWhite, game.uuid());
|
hiWhite, game.uuid());
|
||||||
|
|
||||||
assertThat(surrendererGameView.opponentName()).isEqualToIgnoringCase(blackPlayerName);
|
assertThat(surrendererGameView.opponentName()).isEqualToIgnoringCase(blackPlayerName);
|
||||||
assertThat(surrendererGameView.opponentColor()).isEqualByComparingTo(Piece.Color.BLACK);
|
assertThat(surrendererGameView.opponentColor()).isEqualByComparingTo(Stone.Color.BLACK);
|
||||||
assertThat(surrendererGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_LOOSE);
|
assertThat(surrendererGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_LOOSE);
|
||||||
|
|
||||||
final GameState winnerGameView = cordaClient.gameStateGet(
|
final GameState winnerGameView = cordaClient.gameStateGet(
|
||||||
hiBlack, game.uuid());
|
hiBlack, game.uuid());
|
||||||
|
|
||||||
assertThat(winnerGameView.opponentName()).isEqualToIgnoringCase(whitePlayerName);
|
assertThat(winnerGameView.opponentName()).isEqualToIgnoringCase(whitePlayerName);
|
||||||
assertThat(winnerGameView.opponentColor()).isEqualByComparingTo(Piece.Color.WHITE);
|
assertThat(winnerGameView.opponentColor()).isEqualByComparingTo(Stone.Color.WHITE);
|
||||||
assertThat(winnerGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_WON);
|
assertThat(winnerGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_WON);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ public class GameBoardTests {
|
|||||||
final String message = "GameBoard MOVE test";
|
final String message = "GameBoard MOVE test";
|
||||||
|
|
||||||
final GameState game = cordaClient.gameProposalCreate(
|
final GameState game = cordaClient.gameProposalCreate(
|
||||||
hiWhite, hiBlack, Piece.Color.BLACK, message);
|
hiWhite, hiBlack, Stone.Color.BLACK, message);
|
||||||
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());
|
||||||
@ -95,7 +95,21 @@ public class GameBoardTests {
|
|||||||
assertThat(m0.board().get(18)).isNull();
|
assertThat(m0.board().get(18)).isNull();
|
||||||
assertThat(m1.board().get(22)).isNull();
|
assertThat(m1.board().get(22)).isNull();
|
||||||
assertThat(m1.board().get(18)).isEqualTo(WHITE_MAN);
|
assertThat(m1.board().get(18)).isEqualTo(WHITE_MAN);
|
||||||
|
assertThat(m1.moveNumber() == 1);
|
||||||
assertThat(m1.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
|
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) {
|
ArrayList<Integer> move(int from, int to) {
|
||||||
|
@ -10,7 +10,7 @@ import org.springframework.boot.test.context.SpringBootTest;
|
|||||||
|
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameState.Status;
|
import djmil.cordacheckers.cordaclient.dao.GameState.Status;
|
||||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
import djmil.cordacheckers.cordaclient.dao.Stone;
|
||||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ -23,7 +23,7 @@ public class GameProposalTests {
|
|||||||
|
|
||||||
final String issuer = "alice";
|
final String issuer = "alice";
|
||||||
final String acquier = "bob";
|
final String acquier = "bob";
|
||||||
final Piece.Color acquierColor = Piece.Color.WHITE;
|
final Stone.Color acquierColor = Stone.Color.WHITE;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCreate() {
|
void testCreate() {
|
||||||
|
@ -8,7 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
import djmil.cordacheckers.cordaclient.dao.Stone;
|
||||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ -21,7 +21,7 @@ public class GameStateTests {
|
|||||||
|
|
||||||
final String issuer = "alice";
|
final String issuer = "alice";
|
||||||
final String acquier = "bob";
|
final String acquier = "bob";
|
||||||
final Piece.Color acquierColor = Piece.Color.WHITE;
|
final Stone.Color acquierColor = Stone.Color.WHITE;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testList() {
|
void testList() {
|
||||||
|
@ -9,7 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
import djmil.cordacheckers.cordaclient.dao.Stone;
|
||||||
import djmil.cordacheckers.cordaclient.dao.Rank;
|
import djmil.cordacheckers.cordaclient.dao.Rank;
|
||||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ public class RankingTests {
|
|||||||
final var hiLooser = holdingIdentityResolver.getByUsername("Bob");
|
final var hiLooser = holdingIdentityResolver.getByUsername("Bob");
|
||||||
|
|
||||||
final GameState game = cordaClient.gameProposalCreate(
|
final GameState game = cordaClient.gameProposalCreate(
|
||||||
hiWinner, hiLooser, Piece.Color.WHITE, "GameBoard GLOBAL_RANKING test");
|
hiWinner, hiLooser, Stone.Color.WHITE, "GameBoard GLOBAL_RANKING test");
|
||||||
|
|
||||||
cordaClient.gameProposalAccept(hiLooser, game.uuid());
|
cordaClient.gameProposalAccept(hiLooser, game.uuid());
|
||||||
cordaClient.gameBoardSurrender(hiLooser, game.uuid());
|
cordaClient.gameBoardSurrender(hiLooser, game.uuid());
|
||||||
|
@ -0,0 +1,194 @@
|
|||||||
|
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 + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,35 +3,16 @@ package djmil.cordacheckers.contracts;
|
|||||||
import static djmil.cordacheckers.contracts.GameCommand.requireThat;
|
import static djmil.cordacheckers.contracts.GameCommand.requireThat;
|
||||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import djmil.cordacheckers.states.Piece;
|
import djmil.cordacheckers.checkers.Stone;
|
||||||
import djmil.cordacheckers.states.Piece.Color;
|
|
||||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||||
|
|
||||||
public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
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);
|
private final static Logger log = LoggerFactory.getLogger(GameBoardContract.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -59,60 +40,33 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MoveResult applyMove(List<Integer> move, Map<Integer, Piece> board, Piece.Color moveColor) {
|
public final static Map<Integer, Stone> initialBoard = Map.ofEntries(
|
||||||
final int mFrom = move.get(0);
|
|
||||||
final int mTo = move.get(1);
|
|
||||||
|
|
||||||
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
|
// Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php
|
||||||
Map.entry( 1, BLACK_MAN),
|
Map.entry( 1, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
|
||||||
Map.entry( 2, BLACK_MAN),
|
Map.entry( 2, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
|
||||||
Map.entry( 3, BLACK_MAN),
|
Map.entry( 3, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
|
||||||
Map.entry( 4, BLACK_MAN),
|
Map.entry( 4, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
|
||||||
Map.entry( 5, BLACK_MAN),
|
Map.entry( 5, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
|
||||||
Map.entry( 6, BLACK_MAN),
|
Map.entry( 6, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
|
||||||
Map.entry( 7, BLACK_MAN),
|
Map.entry( 7, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
|
||||||
Map.entry( 8, BLACK_MAN),
|
Map.entry( 8, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
|
||||||
Map.entry( 9, BLACK_MAN),
|
Map.entry( 9, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
|
||||||
Map.entry(10, BLACK_MAN),
|
Map.entry(10, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
|
||||||
Map.entry(11, BLACK_MAN),
|
Map.entry(11, new Stone(Stone.Color.BLACK, Stone.Type.MAN)),
|
||||||
Map.entry(12, BLACK_MAN),
|
Map.entry(12, new Stone(Stone.Color.BLACK, Stone.Type.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)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
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))
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,36 +6,35 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.checkers.Move;
|
||||||
|
import djmil.cordacheckers.checkers.Stone;
|
||||||
import djmil.cordacheckers.contracts.GameBoardContract;
|
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.annotations.ConstructorForDeserialization;
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
import net.corda.v5.ledger.utxo.BelongsToContract;
|
import net.corda.v5.ledger.utxo.BelongsToContract;
|
||||||
|
|
||||||
@BelongsToContract(GameBoardContract.class)
|
@BelongsToContract(GameBoardContract.class)
|
||||||
public class GameBoardState extends GameState {
|
public class GameBoardState extends GameState {
|
||||||
private final Piece.Color moveColor;
|
private final Stone.Color moveColor;
|
||||||
private final Integer moveNumber;
|
private final Integer moveNumber;
|
||||||
private final Map<Integer, Piece> board;
|
private final Map<Integer, Stone> board;
|
||||||
|
|
||||||
public GameBoardState(GameProposalState gameProposalState) {
|
public GameBoardState(GameProposalState gameProposalState) {
|
||||||
super(gameProposalState.whitePlayer, gameProposalState.blackPlayer,
|
super(gameProposalState.whitePlayer, gameProposalState.blackPlayer,
|
||||||
gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants);
|
gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants);
|
||||||
|
|
||||||
this.board = new LinkedHashMap<Integer, Piece>(GameBoardContract.initialBoard);
|
this.board = new LinkedHashMap<Integer, Stone>(GameBoardContract.initialBoard);
|
||||||
this.moveColor = Piece.Color.WHITE;
|
this.moveColor = Stone.Color.WHITE;
|
||||||
this.moveNumber = 0;
|
this.moveNumber = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameBoardState(GameBoardState currentGameBoardState, List<Integer> move, String message) {
|
public GameBoardState(GameBoardState currentGameBoardState, List<Integer> move, String message) {
|
||||||
super(currentGameBoardState.whitePlayer, currentGameBoardState.blackPlayer, currentGameBoardState.gameUuid,
|
super(currentGameBoardState.whitePlayer, currentGameBoardState.blackPlayer, currentGameBoardState.gameUuid,
|
||||||
message, currentGameBoardState.participants);
|
message, currentGameBoardState.participants);
|
||||||
|
|
||||||
final MoveResult moveResult = GameBoardContract.applyMove(move, currentGameBoardState.getBoard(), currentGameBoardState.getMoveColor());
|
this.board = new LinkedHashMap<Integer, Stone>(currentGameBoardState.getBoard());
|
||||||
this.moveColor = moveResult.moveColor;
|
this.moveColor = Move.apply(move, this.board, currentGameBoardState.getMoveColor());
|
||||||
this.board = moveResult.board;
|
|
||||||
|
|
||||||
this.moveNumber = (currentGameBoardState.moveColor == this.moveColor)
|
this.moveNumber = (currentGameBoardState.moveColor == this.moveColor)
|
||||||
? currentGameBoardState.getMoveNumber() // current player has not finished his move jet
|
? currentGameBoardState.getMoveNumber() // current player has not finished his move jet
|
||||||
: currentGameBoardState.getMoveNumber() +1;
|
: currentGameBoardState.getMoveNumber() +1;
|
||||||
@ -43,7 +42,7 @@ public class GameBoardState extends GameState {
|
|||||||
|
|
||||||
@ConstructorForDeserialization
|
@ConstructorForDeserialization
|
||||||
public GameBoardState(MemberX500Name whitePlayer, MemberX500Name blackPlayer,
|
public GameBoardState(MemberX500Name whitePlayer, MemberX500Name blackPlayer,
|
||||||
Color moveColor, Integer moveNumber, Map<Integer, Piece> board, String message,
|
Stone.Color moveColor, Integer moveNumber, Map<Integer, Stone> board, String message,
|
||||||
UUID gameUuid, List<PublicKey> participants) {
|
UUID gameUuid, List<PublicKey> participants) {
|
||||||
super(whitePlayer, blackPlayer, gameUuid, message, participants);
|
super(whitePlayer, blackPlayer, gameUuid, message, participants);
|
||||||
|
|
||||||
@ -52,7 +51,7 @@ public class GameBoardState extends GameState {
|
|||||||
this.board = board;
|
this.board = board;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Piece.Color getMoveColor() {
|
public Stone.Color getMoveColor() {
|
||||||
return moveColor;
|
return moveColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,10 +60,10 @@ public class GameBoardState extends GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MemberX500Name getActivePlayerName() {
|
public MemberX500Name getActivePlayerName() {
|
||||||
return moveColor == Piece.Color.WHITE ? whitePlayer : blackPlayer;
|
return moveColor == Stone.Color.WHITE ? whitePlayer : blackPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Integer, Piece> getBoard() {
|
public Map<Integer, Stone> getBoard() {
|
||||||
return board;
|
return board;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.checkers.Stone;
|
||||||
import net.corda.v5.base.annotations.CordaSerializable;
|
import net.corda.v5.base.annotations.CordaSerializable;
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
import net.corda.v5.ledger.utxo.ContractState;
|
import net.corda.v5.ledger.utxo.ContractState;
|
||||||
@ -67,11 +68,11 @@ public abstract class GameState implements ContractState {
|
|||||||
throw new NotInvolved(playerName, this.getClass(), gameUuid);
|
throw new NotInvolved(playerName, this.getClass(), gameUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Piece.Color getOpponentColor(MemberX500Name playerName) throws NotInvolved {
|
public Stone.Color getOpponentColor(MemberX500Name playerName) throws NotInvolved {
|
||||||
if (playerName.compareTo(whitePlayer) == 0)
|
if (playerName.compareTo(whitePlayer) == 0)
|
||||||
return Piece.Color.BLACK;
|
return Stone.Color.BLACK;
|
||||||
if (playerName.compareTo(blackPlayer) == 0)
|
if (playerName.compareTo(blackPlayer) == 0)
|
||||||
return Piece.Color.WHITE;
|
return Stone.Color.WHITE;
|
||||||
throw new NotInvolved(playerName, this.getClass(), gameUuid);
|
throw new NotInvolved(playerName, this.getClass(), gameUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -10,13 +10,13 @@ import java.util.UUID;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.checkers.Stone;
|
||||||
import djmil.cordacheckers.contracts.GameCommand;
|
import djmil.cordacheckers.contracts.GameCommand;
|
||||||
import djmil.cordacheckers.gamestate.CommitSubFlow;
|
import djmil.cordacheckers.gamestate.CommitSubFlow;
|
||||||
import djmil.cordacheckers.gamestate.FlowResponce;
|
import djmil.cordacheckers.gamestate.FlowResponce;
|
||||||
import djmil.cordacheckers.gamestate.View;
|
import djmil.cordacheckers.gamestate.View;
|
||||||
import djmil.cordacheckers.gamestate.ViewBuilder;
|
import djmil.cordacheckers.gamestate.ViewBuilder;
|
||||||
import djmil.cordacheckers.states.GameProposalState;
|
import djmil.cordacheckers.states.GameProposalState;
|
||||||
import djmil.cordacheckers.states.Piece;
|
|
||||||
import net.corda.v5.application.flows.ClientRequestBody;
|
import net.corda.v5.application.flows.ClientRequestBody;
|
||||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||||
import net.corda.v5.application.flows.CordaInject;
|
import net.corda.v5.application.flows.CordaInject;
|
||||||
@ -88,10 +88,10 @@ public class CreateFlow implements ClientStartableFlow{
|
|||||||
private GameProposalState buildGameProposalStateFrom(ClientRequestBody requestBody) {
|
private GameProposalState buildGameProposalStateFrom(ClientRequestBody requestBody) {
|
||||||
final CreateFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CreateFlowArgs.class);
|
final CreateFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CreateFlowArgs.class);
|
||||||
|
|
||||||
final Piece.Color opponentColor = Piece.Color.valueOf(args.opponentColor);
|
final Stone.Color opponentColor = Stone.Color.valueOf(args.opponentColor);
|
||||||
if (opponentColor == null) {
|
if (opponentColor == null) {
|
||||||
throw new RuntimeException("Allowed values for opponentColor are: "
|
throw new RuntimeException("Allowed values for opponentColor are: "
|
||||||
+ Piece.Color.WHITE.name() +", " + Piece.Color.BLACK.name()
|
+ Stone.Color.WHITE.name() +", " + Stone.Color.BLACK.name()
|
||||||
+ ". Actual value was: " + args.opponentColor);
|
+ ". 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
|
"MemberLookup can't find opponentName specified in flow arguments: " + args.opponentName
|
||||||
);
|
);
|
||||||
|
|
||||||
final MemberInfo whitePlayerInfo = opponentColor == Piece.Color.WHITE ? opponentInfo : myInfo;
|
final MemberInfo whitePlayerInfo = opponentColor == Stone.Color.WHITE ? opponentInfo : myInfo;
|
||||||
final MemberInfo blackPlayerInfo = opponentColor == Piece.Color.BLACK ? opponentInfo : myInfo;
|
final MemberInfo blackPlayerInfo = opponentColor == Stone.Color.BLACK ? opponentInfo : myInfo;
|
||||||
|
|
||||||
return new GameProposalState(
|
return new GameProposalState(
|
||||||
whitePlayerInfo.getName(),
|
whitePlayerInfo.getName(),
|
||||||
|
@ -4,10 +4,10 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.checkers.Stone;
|
||||||
import djmil.cordacheckers.states.GameBoardState;
|
import djmil.cordacheckers.states.GameBoardState;
|
||||||
import djmil.cordacheckers.states.GameProposalState;
|
import djmil.cordacheckers.states.GameProposalState;
|
||||||
import djmil.cordacheckers.states.GameResultState;
|
import djmil.cordacheckers.states.GameResultState;
|
||||||
import djmil.cordacheckers.states.Piece;
|
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
|
|
||||||
// GameBoard from the player's point of view
|
// GameBoard from the player's point of view
|
||||||
@ -27,9 +27,9 @@ public class View {
|
|||||||
public final Status status;
|
public final Status status;
|
||||||
|
|
||||||
public final String opponentName;
|
public final String opponentName;
|
||||||
public final Piece.Color opponentColor;
|
public final Stone.Color opponentColor;
|
||||||
|
|
||||||
public final Map<Integer, Piece> board;
|
public final Map<Integer, Stone> board;
|
||||||
public final Integer moveNumber;
|
public final Integer moveNumber;
|
||||||
public final List<Integer> previousMove;
|
public final List<Integer> previousMove;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user