diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java index 371ccb4..df420a9 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java @@ -3,6 +3,7 @@ package djmil.cordacheckers.cordaclient; import static java.util.Objects.requireNonNull; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -101,14 +102,20 @@ public class CordaClient { } public GameState gameProposalCreate(HoldingIdentity issuer, HoldingIdentity acquier, Stone.Color acquierColor, - String message - ) { + String message) { + + return gameProposalCreate(issuer, acquier, acquierColor, GameState.defaultGameBoard, message); + } + + public GameState gameProposalCreate(HoldingIdentity issuer, HoldingIdentity acquier, Stone.Color acquierColor, + Map board, String message) { final RequestBody requestBody = new RequestBody( "gp.create-" + UUID.randomUUID(), "djmil.cordacheckers.gameproposal.CreateFlow", new ReqGameProposalCreate( acquier.x500Name(), acquierColor, + board, message)); return cordaFlowExecute(issuer, requestBody,RspGameState.class) diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameState.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameState.java index a68ae73..48a344c 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameState.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameState.java @@ -27,4 +27,32 @@ public record GameState( GAME_RESULT_YOU_LOOSE; } + public final static Map defaultGameBoard = 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)), + + 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)) + ); } diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/ReqGameProposalCreate.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/ReqGameProposalCreate.java index 358619f..a0f5ed1 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/ReqGameProposalCreate.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/ReqGameProposalCreate.java @@ -1,10 +1,13 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; +import java.util.Map; + import djmil.cordacheckers.cordaclient.dao.Stone; public record ReqGameProposalCreate( String opponentName, Stone.Color opponentColor, + Map board, String message ) { diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/Rsp.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/Rsp.java index 9bdbaaf..35b718c 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/Rsp.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/Rsp.java @@ -1,5 +1,6 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; +import djmil.cordacheckers.cordaclient.dao.GameState; import djmil.cordacheckers.cordaclient.dao.flow.RequestBody; public interface Rsp { @@ -8,8 +9,14 @@ public interface Rsp { public default T getResponce(RequestBody requestBody) { if (failureStatus() == null) { - System.out.println(requestBody.clientRequestId() +" [OK]"); - return successStatus(); + final var responce = successStatus(); + final String gameUuid = (responce instanceof GameState) + ? "GameUUID " +((GameState)responce).uuid().toString() + : ""; + + System.out.println(requestBody.clientRequestId() +" [OK] " +gameUuid); + + return responce; } System.err.println(requestBody.clientRequestId() +" has failed: " +failureStatus()); diff --git a/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java b/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java index b7152fb..c907f9b 100644 --- a/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java +++ b/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -31,6 +32,17 @@ public class GameBoardTests { 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 Map victoryTestBoard1 = Map.ofEntries( + Map.entry(15, BLACK_MAN), + Map.entry(23, WHITE_MAN) + ); + + final static Map victoryTestBoard2 = Map.ofEntries( + Map.entry(21, BLACK_MAN), + Map.entry(29, WHITE_MAN), + Map.entry(30, WHITE_MAN) + ); + @Test void testSurrender() { final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); @@ -39,8 +51,6 @@ public class GameBoardTests { final GameState game = cordaClient.gameProposalCreate( hiWhite, hiBlack, Stone.Color.BLACK, "GameBoard SURRENDER test"); - System.out.println("Game UUID " +game.uuid()); - final GameState acceptedGameBlackView = cordaClient.gameProposalAccept( hiBlack, game.uuid()); @@ -54,6 +64,7 @@ public class GameBoardTests { assertThat(surrendererGameView.opponentName()).isEqualToIgnoringCase(whitePlayerName); assertThat(surrendererGameView.opponentColor()).isEqualByComparingTo(Stone.Color.WHITE); assertThat(surrendererGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_LOOSE); + assertThat(surrendererGameView.moveNumber() == 0); assertThatThrownBy(() -> { // White can not surrender, since Black already did cordaClient.gameBoardSurrender( @@ -66,6 +77,7 @@ public class GameBoardTests { assertThat(winnerGameView.opponentName()).isEqualToIgnoringCase(blackPlayerName); assertThat(winnerGameView.opponentColor()).isEqualByComparingTo(Stone.Color.BLACK); assertThat(winnerGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_WON); + assertThat(winnerGameView.moveNumber() == 0); } @Test @@ -75,7 +87,6 @@ public class GameBoardTests { final GameState game = cordaClient.gameProposalCreate( hiWhite, hiBlack, Stone.Color.BLACK, "GameBoard MOVE test"); - System.out.println("Game UUID " +game.uuid()); final var m0 = cordaClient.gameProposalAccept(hiBlack, game.uuid()); assertThat(m0.previousMove()).isNull(); @@ -186,27 +197,23 @@ public class GameBoardTests { final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName); - final GameState game = cordaClient.gameProposalCreate( - hiWhite, hiBlack, Stone.Color.BLACK, "GameBoard Vitcory test 1"); + final GameState game = cordaClient.gameProposalCreate(hiBlack, hiWhite, Stone.Color.WHITE, + victoryTestBoard1, "GameBoard Vitcory test 1"); - System.out.println("Game UUID " +game.uuid()); - - final GameState m0 = cordaClient.gameProposalAccept(hiBlack, game.uuid()); - - // For this test to work, uncoment proper initialBoard state configuration in - // cordacheckers/contracts/GameBoardContract.java - // i'm too lazy to implement this test properly =^.^= - assertThat(m0.board().size() == 2); - assertThat(m0.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); + final GameState m0 = cordaClient.gameProposalAccept(hiWhite, game.uuid()); + assertThat(m0.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_YOU); + assertThat(m0.board()).containsAllEntriesOf(victoryTestBoard1); final var m1 = cordaClient.gameBoardMove(hiWhite, game.uuid(), move(23, 18), null); assertThat(m1.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); final var m2 = cordaClient.gameBoardMove(hiBlack, game.uuid(), move(15, 22), null); assertThat(m2.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_WON); + assertThat(m2.moveNumber() == 2); final var m2WhiteView = cordaClient.gameStateGet(hiWhite, game.uuid()); assertThat(m2WhiteView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_LOOSE); + assertThat(m2WhiteView.moveNumber() == 2); } @Test @@ -214,27 +221,23 @@ public class GameBoardTests { final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName); - final GameState game = cordaClient.gameProposalCreate( - hiWhite, hiBlack, Stone.Color.BLACK, "GameBoard Vitcory test 2"); - - System.out.println("Game UUID " +game.uuid()); + final GameState game = cordaClient.gameProposalCreate(hiWhite, hiBlack, Stone.Color.BLACK, + victoryTestBoard2, "GameBoard Vitcory test 2"); final GameState m0 = cordaClient.gameProposalAccept(hiBlack, game.uuid()); - - // For this test to work, uncoment proper initialBoard state configuration in - // cordacheckers/contracts/GameBoardContract.java - // i'm too lazy to implement this test properly =^.^= - assertThat(m0.board().size() == 3); assertThat(m0.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); + assertThat(m0.board()).containsAllEntriesOf(victoryTestBoard2); final var m0WhiteView = cordaClient.gameStateGet(hiWhite, game.uuid()); assertThat(m0WhiteView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_YOU); final var m1 = cordaClient.gameBoardMove(hiWhite, game.uuid(), move(29, 25), null); assertThat(m1.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_WON); + assertThat(m1.moveNumber() == 1); final var m1BlackView = cordaClient.gameStateGet(hiBlack, game.uuid()); assertThat(m1BlackView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_LOOSE); + assertThat(m1BlackView.moveNumber() == 1); } ArrayList move(int from, int to) { diff --git a/backend/src/test/java/djmil/cordacheckers/cordaclient/GameProposalTests.java b/backend/src/test/java/djmil/cordacheckers/cordaclient/GameProposalTests.java index 87b3665..cc2096f 100644 --- a/backend/src/test/java/djmil/cordacheckers/cordaclient/GameProposalTests.java +++ b/backend/src/test/java/djmil/cordacheckers/cordaclient/GameProposalTests.java @@ -32,12 +32,7 @@ public class GameProposalTests { final String message = "GameProposal CREATE test"; final GameState issuerGameView = cordaClient.gameProposalCreate( - hiIssuer, - hiAcquier, - acquierColor, - message); - - System.out.println("Game UUID " +issuerGameView.uuid()); + hiIssuer, hiAcquier, acquierColor, message); /* * Issuers perspective on a newly created GameProposal @@ -45,6 +40,9 @@ public class GameProposalTests { assertThat(issuerGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_WAIT_FOR_OPPONENT); assertThat(issuerGameView.opponentName()).isEqualToIgnoringCase(acquier); assertThat(issuerGameView.opponentColor()).isEqualByComparingTo(acquierColor); + assertThat(issuerGameView.board()).containsAllEntriesOf(GameState.defaultGameBoard); + assertThat(issuerGameView.previousMove()).isEmpty(); + assertThat(issuerGameView.moveNumber() == 0); assertThat(issuerGameView.message()).isEqualToIgnoringCase(message); assertThat(issuerGameView.uuid()).isNotNull(); @@ -62,6 +60,9 @@ public class GameProposalTests { assertThat(acquierGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_WAIT_FOR_YOU); assertThat(acquierGameView.opponentName()).isEqualToIgnoringCase(issuer); assertThat(acquierGameView.opponentColor()).isEqualByComparingTo(acquierColor.opposite()); + assertThat(acquierGameView.board()).containsAllEntriesOf(GameState.defaultGameBoard); + assertThat(acquierGameView.previousMove()).isEmpty(); + assertThat(acquierGameView.moveNumber() == 0); assertThat(acquierGameView.message()).isEqualToIgnoringCase(message); assertThat(acquierGameView.uuid()).isEqualByComparingTo(issuerGameView.uuid()); } @@ -70,12 +71,9 @@ public class GameProposalTests { void testReject() { final var hiIssuer = holdingIdentityResolver.getByUsername(issuer); final var hiAcquier = holdingIdentityResolver.getByUsername(acquier); - final String message = "GameProposal REJECT test"; final GameState game = cordaClient.gameProposalCreate( - hiIssuer, hiAcquier, acquierColor, message); - - System.out.println("Game UUID " +game.uuid()); + hiIssuer, hiAcquier, acquierColor, "GameProposal REJECT test"); assertThatThrownBy(() -> { // Issuer can not reject cordaClient.gameProposalReject( @@ -102,12 +100,9 @@ public class GameProposalTests { void testCancel() { final var hiIssuer = holdingIdentityResolver.getByUsername(issuer); final var hiAcquier = holdingIdentityResolver.getByUsername(acquier); - final String message = "GameProposal CANCEL test"; final GameState game = cordaClient.gameProposalCreate( - hiIssuer, hiAcquier, acquierColor, message); - - System.out.println("Game UUID " +game.uuid()); + hiIssuer, hiAcquier, acquierColor, "GameProposal CANCEL test"); assertThatThrownBy(() -> { // Acquier can not cancel cordaClient.gameProposalCancel( @@ -134,12 +129,9 @@ public class GameProposalTests { void testAccept() { final var hiIssuer = holdingIdentityResolver.getByUsername(issuer); final var hiAcquier = holdingIdentityResolver.getByUsername(acquier); - final String message = "GameProposal ACCEPT test"; final GameState game = cordaClient.gameProposalCreate( - hiIssuer, hiAcquier, acquierColor, message); - - System.out.println("Game UUID " +game.uuid()); + hiIssuer, hiAcquier, acquierColor, "GameProposal ACCEPT test"); assertThatThrownBy(() -> { // Issuer can not accept cordaClient.gameProposalAccept( @@ -151,6 +143,9 @@ public class GameProposalTests { assertThat(acceptedGameAcquierView.opponentName()).isEqualToIgnoringCase(issuer); assertThat(acceptedGameAcquierView.opponentColor()).isEqualByComparingTo(acquierColor.opposite()); + assertThat(acceptedGameAcquierView.board()).containsAllEntriesOf(GameState.defaultGameBoard); + assertThat(acceptedGameAcquierView.previousMove()).isEmpty(); + assertThat(acceptedGameAcquierView.moveNumber() == 0); assertThat(acceptedGameAcquierView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_YOU); assertThat(acceptedGameAcquierView.uuid()).isEqualByComparingTo(game.uuid()); @@ -164,6 +159,9 @@ public class GameProposalTests { assertThat(acceptedGameIssuerView.opponentName()).isEqualToIgnoringCase(acquier); assertThat(acceptedGameIssuerView.opponentColor()).isEqualByComparingTo(acquierColor); + assertThat(acceptedGameIssuerView.board()).containsAllEntriesOf(GameState.defaultGameBoard); + assertThat(acceptedGameIssuerView.previousMove()).isEmpty(); + assertThat(acceptedGameIssuerView.moveNumber() == 0); assertThat(acceptedGameIssuerView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); assertThat(acceptedGameIssuerView.uuid()).isEqualByComparingTo(game.uuid()); } diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Stone.java b/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Stone.java index 0c82ba8..6ef9113 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Stone.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Stone.java @@ -34,6 +34,12 @@ public class Stone { private final Color color; private final Type type; + // Serialisation service requires a default constructor + public Stone() { + this.color = null; + this.type = null; + } + @ConstructorForDeserialization public Stone(Color color, Type type) { this.color = color; diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardContract.java b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardContract.java index 9a9107c..34392d2 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardContract.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardContract.java @@ -3,12 +3,9 @@ package djmil.cordacheckers.contracts; import static djmil.cordacheckers.contracts.GameCommand.requireThat; import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand; -import java.util.Map; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import djmil.cordacheckers.checkers.Stone; import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract { @@ -17,7 +14,7 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract { @Override public void verify(UtxoLedgerTransaction trx) { - log.info("GameBoardContract.verify() called"); + log.info("GameBoardContract verify"); requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND); final GameCommand command = getSingleCommand(trx, GameCommand.class); @@ -44,44 +41,4 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract { } } - public final static Map 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)), - - // 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)) - - - /* VICTORY TEST 1 */ - // Map.entry(15, new Stone(Stone.Color.BLACK, Stone.Type.MAN)), - // Map.entry(23, new Stone(Stone.Color.WHITE, Stone.Type.MAN)) - - - /* VICTORY TEST 2 */ - Map.entry(21, new Stone(Stone.Color.BLACK, 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)) - ); - } diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameCommand.java b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameCommand.java index 97062d2..dc66a6a 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameCommand.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameCommand.java @@ -140,7 +140,7 @@ public class GameCommand implements Command { /* * Major command logick check */ - requireThat(outGameBoard.getBoard().equals(GameBoardContract.initialBoard), "Bad GameBoard initial state"); + requireThat(outGameBoard.getBoard().size() > 0, "GameBoard initial state was not found"); } public void validateGameProposalReject(UtxoLedgerTransaction trx) { @@ -192,6 +192,8 @@ public class GameCommand implements Command { requireThat(outGameResult.getLooserName().compareTo(outGameResult.getBlackPlayer()) == 0 || outGameResult.getLooserName().compareTo(outGameResult.getWhitePlayer()) == 0, "Surenderer must be either Black or White player"); + + requireThat(outGameResult.getTotalMoves() == inGameBoard.getMoveNumber(), GAME_RESULT_MOVES); } public void validateGameBoardVictory(UtxoLedgerTransaction trx) { @@ -212,6 +214,7 @@ public class GameCommand implements Command { requireThat(possibleMoves.isEmpty(), "Victory condition violation"); requireThat(outGameResult.getWinnerName().compareTo(inGameBoard.getIdelPlayerName()) == 0, "Bad winner name"); + requireThat(outGameResult.getTotalMoves() == inGameBoard.getMoveNumber(), GAME_RESULT_MOVES); } public static void requireThat(boolean asserted, String errorMessage) { @@ -245,4 +248,6 @@ public class GameCommand implements Command { static final String VICTORY_INPUT_STATE = "VICTORY command should have exactly one GameBoardState input state"; static final String VICTORY_OUTPUT_STATE = "VICTORY command should have exactly one GameResultState output state"; + + static final String GAME_RESULT_MOVES = "Wrong number of total moves"; } diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameBoardState.java b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameBoardState.java index 5ff77b5..478a9b0 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameBoardState.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameBoardState.java @@ -1,6 +1,7 @@ package djmil.cordacheckers.states; import java.security.PublicKey; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -23,7 +24,7 @@ public class GameBoardState extends GameState { super(gameProposalState.whitePlayer, gameProposalState.blackPlayer, gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants); - this.board = new LinkedHashMap(GameBoardContract.initialBoard); + this.board = gameProposalState.getBoard(); this.activeColor = Stone.Color.WHITE; this.moveNumber = 0; } @@ -68,7 +69,7 @@ public class GameBoardState extends GameState { } public Map getBoard() { - return board; + return Collections.unmodifiableMap(board); } @Override diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameProposalState.java b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameProposalState.java index 27a4bdf..24062d8 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameProposalState.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameProposalState.java @@ -1,9 +1,12 @@ package djmil.cordacheckers.states; import java.security.PublicKey; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.UUID; +import djmil.cordacheckers.checkers.Stone; import djmil.cordacheckers.contracts.GameProposalContract; import net.corda.v5.base.annotations.ConstructorForDeserialization; import net.corda.v5.base.types.MemberX500Name; @@ -13,12 +16,15 @@ import net.corda.v5.ledger.utxo.BelongsToContract; public class GameProposalState extends GameState { private final MemberX500Name issuerName; + private final Map board; @ConstructorForDeserialization public GameProposalState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, MemberX500Name issuerName, - UUID gameUuid, String message, List participants) { + UUID gameUuid, Map board, String message, List participants) { super(whitePlayer, blackPlayer, gameUuid, message, participants); + this.issuerName = issuerName; + this.board = board; } public MemberX500Name getIssuerName() { @@ -29,4 +35,8 @@ public class GameProposalState extends GameState { return getOpponentName(issuerName); } + public Map getBoard() { + return Collections.unmodifiableMap(board); + } + } diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameResultState.java b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameResultState.java index c5b6c1f..b196896 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameResultState.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameResultState.java @@ -13,17 +13,20 @@ import net.corda.v5.ledger.utxo.BelongsToContract; public class GameResultState extends GameState { private final MemberX500Name winnerName; + private final Integer totalMoves; @ConstructorForDeserialization public GameResultState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, MemberX500Name winnerName, - UUID gameUuid, String message, List participants) { + Integer totalMoves, UUID gameUuid, String message, List participants) { super(whitePlayer, blackPlayer, gameUuid, message, participants); this.winnerName = winnerName; + this.totalMoves = totalMoves; } public GameResultState(MemberX500Name winnerName, GameBoardState gameBoardState, PublicKey custodianPubicKey) { super(gameBoardState.whitePlayer, gameBoardState.blackPlayer, gameBoardState.gameUuid, null, gameBoardState.participants, custodianPubicKey); this.winnerName = winnerName; + this.totalMoves = gameBoardState.getMoveNumber(); } public MemberX500Name getWinnerName() { @@ -34,4 +37,8 @@ public class GameResultState extends GameState { return getOpponentName(getWinnerName()); } + public Integer getTotalMoves() { + return totalMoves; + } + } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java index 00594a3..568fddf 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java @@ -90,29 +90,25 @@ public class CreateFlow implements ClientStartableFlow{ GameProposalState buildGameProposalStateFrom(ClientRequestBody requestBody) { final CreateFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CreateFlowArgs.class); - final Stone.Color opponentColor = Stone.Color.valueOf(args.opponentColor); - if (opponentColor == null) { - throw new RuntimeException("Allowed values for opponentColor are: " - + Stone.Color.WHITE.name() +", " + Stone.Color.BLACK.name() - + ". Actual value was: " + args.opponentColor); - } - final MemberInfo myInfo = memberLookup.myInfo(); final MemberInfo opponentInfo = requireNonNull( memberLookup.lookup(MemberX500Name.parse(args.opponentName)), "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 = args.opponentColor == Stone.Color.WHITE ? opponentInfo : myInfo; + final MemberInfo blackPlayerInfo = args.opponentColor == Stone.Color.BLACK ? opponentInfo : myInfo; return new GameProposalState( whitePlayerInfo.getName(), blackPlayerInfo.getName(), myInfo.getName(), // <<--- GameProposal issuer UUID.randomUUID(), + args.board, args.message, - Arrays.asList(myInfo.getLedgerKeys().get(0), opponentInfo.getLedgerKeys().get(0)) + Arrays.asList( + myInfo.getLedgerKeys().get(0), + opponentInfo.getLedgerKeys().get(0)) ); } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlowArgs.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlowArgs.java index 17149b1..40771f4 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlowArgs.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlowArgs.java @@ -1,14 +1,20 @@ package djmil.cordacheckers.gameproposal; +import java.util.Map; + +import djmil.cordacheckers.checkers.Stone; + public class CreateFlowArgs { public final String opponentName; - public final String opponentColor; + public final Stone.Color opponentColor; + public final Map board; public final String message; // Serialisation service requires a default constructor public CreateFlowArgs() { opponentName = null; opponentColor = null; + board = null; message = null; } } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/View.java b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/View.java index dbc3b48..07340de 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/View.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/View.java @@ -52,9 +52,9 @@ public class View { this.status = status; this.opponentName = gameProposal.getOpponentName(myName).getCommonName(); this.opponentColor = gameProposal.getOpponentColor(myName); - this.board = null; - this.moveNumber = null; + this.board = gameProposal.getBoard(); this.previousMove = null; + this.moveNumber = 0; this.message = gameProposal.getMessage(); this.uuid = gameProposal.getGameUuid(); } @@ -64,8 +64,8 @@ public class View { this.opponentName = gameBoard.getOpponentName(myName).getCommonName(); this.opponentColor = gameBoard.getOpponentColor(myName); this.board = gameBoard.getBoard(); - this.moveNumber = gameBoard.getMoveNumber(); this.previousMove = previousMove; + this.moveNumber = gameBoard.getMoveNumber(); this.message = gameBoard.getMessage(); this.uuid = gameBoard.getGameUuid(); } @@ -75,8 +75,8 @@ public class View { this.opponentName = gameResult.getOpponentName(myName).getCommonName(); this.opponentColor = gameResult.getOpponentColor(myName); this.board = null; - this.moveNumber = null; this.previousMove = null; + this.moveNumber = gameResult.getTotalMoves(); this.message = gameResult.getMessage(); this.uuid = gameResult.getGameUuid(); }