diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java index 1d9952a..2bc9eee 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java @@ -85,21 +85,18 @@ public class CordaClient { .getResponce(requestBody); } - public GameState gameStateGet(HoldingIdentity holdingIdentity, UUID gameStateUuid) { + public GameState gameStateGet(HoldingIdentity holdingIdentity, UUID gameUuid) { final RequestBody requestBody = new RequestBody( "gs.get-" + UUID.randomUUID(), "djmil.cordacheckers.gamestate.GetFlow", - gameStateUuid); + gameUuid); return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) .getResponce(requestBody); } - public GameState gameProposalCreate( - HoldingIdentity issuer, - HoldingIdentity acquier, - Piece.Color acquierColor, - String message + public GameState gameProposalCreate(HoldingIdentity issuer, HoldingIdentity acquier, Piece.Color acquierColor, + String message ) { final RequestBody requestBody = new RequestBody( "gp.create-" + UUID.randomUUID(), @@ -113,53 +110,53 @@ public class CordaClient { .getResponce(requestBody); } - public GameState gameProposalReject(HoldingIdentity holdingIdentity, UUID gameProposalUuid) { + public GameState gameProposalReject(HoldingIdentity holdingIdentity, UUID gameUuid) { final RequestBody requestBody = new RequestBody( "gp.reject-" +UUID.randomUUID(), "djmil.cordacheckers.gameproposal.RejectFlow", - gameProposalUuid); + gameUuid); return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) .getResponce(requestBody); } - public GameState gameProposalCancel(HoldingIdentity holdingIdentity, UUID gameProposalUuid) { + public GameState gameProposalCancel(HoldingIdentity holdingIdentity, UUID gameUuid) { final RequestBody requestBody = new RequestBody( "gp.reject-" +UUID.randomUUID(), "djmil.cordacheckers.gameproposal.CancelFlow", - gameProposalUuid); + gameUuid); return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) .getResponce(requestBody); } - public GameState gameProposalAccept(HoldingIdentity holdingIdentity, UUID gameProposalUuid) { + public GameState gameProposalAccept(HoldingIdentity holdingIdentity, UUID gameUuid) { final RequestBody requestBody = new RequestBody( "gp.accept-" +UUID.randomUUID(), "djmil.cordacheckers.gameproposal.AcceptFlow", - gameProposalUuid); + gameUuid); return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) .getResponce(requestBody); } - public GameState gameBoardSurrender(HoldingIdentity holdingIdentity, UUID gameBoardUuid) { + public GameState gameBoardSurrender(HoldingIdentity holdingIdentity, UUID gameUuid) { final RequestBody requestBody = new RequestBody( "gb.surrender-" +UUID.randomUUID(), "djmil.cordacheckers.gameboard.SurrenderFlow", - gameBoardUuid); + gameUuid); return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) .getResponce(requestBody); } - public GameState gameBoardMove(HoldingIdentity holdingIdentity, UUID gameBoardUuid, List move) { + public GameState gameBoardMove(HoldingIdentity holdingIdentity, UUID gameUuid, List move, + String message) { final RequestBody requestBody = new RequestBody( "gb.move-" +UUID.randomUUID(), - "djmil.cordacheckers.gameboard.CommandFlow", + "djmil.cordacheckers.gameboard.MoveFlow", new ReqGameBoardMove( - gameBoardUuid, - move)); + move, gameUuid, message)); return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) .getResponce(requestBody); diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/Piece.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/Piece.java index 84cbeac..c283f0f 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/Piece.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/Piece.java @@ -42,4 +42,20 @@ public class Piece { return color +"." +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; + } + } diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/ReqGameBoardMove.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/ReqGameBoardMove.java index 3069173..5fa29ab 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/ReqGameBoardMove.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/ReqGameBoardMove.java @@ -4,8 +4,9 @@ import java.util.List; import java.util.UUID; public record ReqGameBoardMove( - UUID gameBoardUuid, - List move + List move, + UUID gameUuid, + String message ) { } diff --git a/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java b/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java index 6a8437c..741a008 100644 --- a/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java +++ b/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java @@ -3,6 +3,9 @@ package djmil.cordacheckers.cordaclient; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.ArrayList; +import java.util.Arrays; + import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -23,6 +26,11 @@ public class GameBoardTests { final String whitePlayerName = "alice"; final String blackPlayerName = "bob"; + 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() { final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); @@ -60,4 +68,37 @@ public class GameBoardTests { assertThat(winnerGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_WON); } + @Test + void testMove() { + final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); + final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName); + final String message = "GameBoard MOVE test"; + + final GameState game = cordaClient.gameProposalCreate( + hiWhite, hiBlack, Piece.Color.BLACK, message); + System.out.println("Game UUID " +game.uuid()); + + final var m0 = cordaClient.gameProposalAccept(hiBlack, game.uuid()); + + assertThatThrownBy(() -> { + cordaClient.gameBoardMove(hiBlack, game.uuid(), move(12, 16), + "Black can not move, since it is opponent's turn"); + }); + + assertThatThrownBy(() -> { + cordaClient.gameBoardMove(hiWhite, game.uuid(), move(17, 14), + "Trying to move an empty tile"); + }); + + final var m1 = cordaClient.gameBoardMove(hiWhite, game.uuid(), move(22, 18), null); + assertThat(m0.board().get(22)).isEqualTo(WHITE_MAN); + assertThat(m0.board().get(18)).isNull(); + assertThat(m1.board().get(22)).isNull(); + assertThat(m1.board().get(18)).isEqualTo(WHITE_MAN); + assertThat(m1.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); + } + + ArrayList move(int from, int to) { + return new ArrayList(Arrays.asList(from, to)); + } } diff --git a/backend/src/test/java/djmil/cordacheckers/cordaclient/RankingTests.java b/backend/src/test/java/djmil/cordacheckers/cordaclient/RankingTests.java index 43acced..249473c 100644 --- a/backend/src/test/java/djmil/cordacheckers/cordaclient/RankingTests.java +++ b/backend/src/test/java/djmil/cordacheckers/cordaclient/RankingTests.java @@ -9,7 +9,6 @@ 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.GameState.Status; import djmil.cordacheckers.cordaclient.dao.Piece; import djmil.cordacheckers.cordaclient.dao.Rank; import djmil.cordacheckers.user.HoldingIdentityResolver; @@ -25,6 +24,8 @@ public class RankingTests { @Test void testGlobalRanking() { final var hiCustodian = holdingIdentityResolver.getCustodian(); + assertThat(hiCustodian).isNotNull(); + final List liderboard1 = cordaClient.fetchRanking(hiCustodian); final var hiWinner = holdingIdentityResolver.getByUsername("Charlie"); 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 85d30d3..b9fe620 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,34 @@ 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.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 board; + final public Piece.Color moveColor; + + public MoveResult(Map 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); @@ -19,13 +41,13 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract { requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND); final GameCommand command = getSingleCommand(trx, GameCommand.class); - switch (command.action) { + switch (command.getAction()) { case GAME_PROPOSAL_ACCEPT: command.validateGameProposalAccept(trx); break; case GAME_BOARD_MOVE: - command.validateGameBoardMove(trx); + command.validateGameBoardMove(trx, command.getMove()); break; case GAME_BOARD_SURRENDER: @@ -37,4 +59,60 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract { } } + public static MoveResult applyMove(List move, Map board, Piece.Color moveColor) { + 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 newBoard = new LinkedHashMap(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 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) + ); + + } 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 53d9442..0c9bd79 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameCommand.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameCommand.java @@ -31,8 +31,8 @@ public class GameCommand implements Command { GAME_RESULT_CREATE; } - public final Action action; - public final List move; // [0] from, [1] to + private final Action action; + private final List move; public static class ActionException extends RuntimeException { public ActionException() { @@ -156,6 +156,7 @@ public class GameCommand implements Command { final GameBoardState outGameBoard = getSingleOutputState(trx, GameBoardState.class); requireThat(inGameProposal.getParticipants().containsAll(outGameBoard.getParticipants()), IN_OUT_PARTICIPANTS); + requireThat(outGameBoard.getBoard().equals(GameBoardContract.initialBoard), "Bad GameBoard initial state"); } public void validateGameProposalReject(UtxoLedgerTransaction trx) { @@ -197,7 +198,7 @@ public class GameCommand implements Command { "Expected winner "+expectedWinnerName.getCommonName() +", proposed winner " +outGameResultState.getWinnerName().getCommonName()); } - public void validateGameBoardMove(UtxoLedgerTransaction trx) { + public void validateGameBoardMove(UtxoLedgerTransaction trx, List move) { requireThat(trx.getInputContractStates().size() == 1, MOVE_INPUT_STATE); final var inGameBoardState = getSingleInputState(trx, GameBoardState.class); @@ -206,6 +207,9 @@ public class GameCommand implements Command { requireThat(inGameBoardState.getWhitePlayer().compareTo(outGameBoardState.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS); requireThat(inGameBoardState.getBlackPlayer().compareTo(outGameBoardState.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS); + + final var newGameBoard = new GameBoardState(inGameBoardState, move, outGameBoardState.getMessage()); + requireThat(outGameBoardState.equals(newGameBoard), "Unexpected output state"); } public void validateGameResultCreate(UtxoLedgerTransaction trx) { diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java index 59e9d86..34c3b9b 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java @@ -19,7 +19,7 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND); final GameCommand command = getSingleCommand(trx, GameCommand.class); - switch (command.action) { + switch (command.getAction()) { case GAME_PROPOSAL_CREATE: command.validateGameProposalCreate(trx); break; diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameResultContract.java b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameResultContract.java index 0807616..bf2d960 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameResultContract.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameResultContract.java @@ -19,7 +19,7 @@ public class GameResultContract implements net.corda.v5.ledger.utxo.Contract { requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND); final GameCommand command = getSingleCommand(trx, GameCommand.class); - switch (command.action) { + switch (command.getAction()) { case GAME_BOARD_SURRENDER: command.validateGameBoardSurrender(trx); break; 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 f2d951b..c8a8d0f 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameBoardState.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameBoardState.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.UUID; 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; @@ -22,19 +23,22 @@ public class GameBoardState extends GameState { super(gameProposalState.whitePlayer, gameProposalState.blackPlayer, gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants); - // Initial GameBoard state + this.board = new LinkedHashMap(GameBoardContract.initialBoard); this.moveColor = Piece.Color.WHITE; this.moveNumber = 0; - this.board = new LinkedHashMap(initialBoard); } - public GameBoardState(GameBoardState oldGameBoardState, Map newBoard, Piece.Color moveColor) { - super(oldGameBoardState.whitePlayer, oldGameBoardState.blackPlayer, - oldGameBoardState.gameUuid, oldGameBoardState.message, oldGameBoardState.participants); + public GameBoardState(GameBoardState currentGameBoardState, List move, String message) { + super(currentGameBoardState.whitePlayer, currentGameBoardState.blackPlayer, currentGameBoardState.gameUuid, + message, currentGameBoardState.participants); - this.moveColor = moveColor; - this.moveNumber = oldGameBoardState.getMoveNumber() +1; - this.board = newBoard; + 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 + : currentGameBoardState.getMoveNumber() +1; } @ConstructorForDeserialization @@ -64,33 +68,29 @@ public class GameBoardState extends GameState { return board; } - // TODO: move to contract - public final static Map initialBoard = Map.ofEntries( - // Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php - Map.entry( 1, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), - Map.entry( 2, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), - Map.entry( 3, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), - Map.entry( 4, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), - Map.entry( 5, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), - Map.entry( 6, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), - Map.entry( 7, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), - Map.entry( 8, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), - Map.entry( 9, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), - Map.entry(10, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), - Map.entry(11, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), - Map.entry(12, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + GameBoardState other = (GameBoardState) obj; + if (moveColor != other.moveColor) + return false; + if (moveNumber == null) { + if (other.moveNumber != null) + return false; + } else if (!moveNumber.equals(other.moveNumber)) + return false; + if (board == null) { + if (other.board != null) + return false; + } else if (!board.equals(other.board)) + return false; + return true; + } - Map.entry(21, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), - Map.entry(22, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), - Map.entry(23, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), - Map.entry(24, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), - Map.entry(25, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), - Map.entry(26, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), - Map.entry(27, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), - Map.entry(28, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), - Map.entry(29, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), - Map.entry(30, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), - Map.entry(31, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), - Map.entry(32, new Piece(Piece.Color.WHITE, Piece.Type.MAN)) - ); + } diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/states/Piece.java b/corda/contracts/src/main/java/djmil/cordacheckers/states/Piece.java index f766f0f..c3e6def 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/states/Piece.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/states/Piece.java @@ -17,21 +17,14 @@ public class Piece { WHITE, BLACK; - public static Color oppositOf(Color color) { - switch (color) { + public Color opposite() { + switch (this) { case WHITE: return BLACK; case BLACK: return WHITE; - default: - throw new UnknownException(); - } - } - - public static class UnknownException extends RuntimeException { - public UnknownException() { - super("Unknown Color value"); } + throw new RuntimeException("Unknown Color"); } } @@ -52,4 +45,20 @@ public class Piece { 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; + } + } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlow.java new file mode 100644 index 0000000..0ba00ca --- /dev/null +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlow.java @@ -0,0 +1,85 @@ +package djmil.cordacheckers.gameboard; + +import java.time.Duration; +import java.time.Instant; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import djmil.cordacheckers.contracts.GameCommand; +import djmil.cordacheckers.gamestate.CommitSubFlow; +import djmil.cordacheckers.gamestate.FlowResponce; +import djmil.cordacheckers.gamestate.GetFlow; +import djmil.cordacheckers.gamestate.View; +import djmil.cordacheckers.gamestate.ViewBuilder; +import djmil.cordacheckers.states.GameBoardState; +import djmil.cordacheckers.states.GameState; +import net.corda.v5.application.flows.ClientRequestBody; +import net.corda.v5.application.flows.ClientStartableFlow; +import net.corda.v5.application.flows.CordaInject; +import net.corda.v5.application.flows.FlowEngine; +import net.corda.v5.application.marshalling.JsonMarshallingService; +import net.corda.v5.base.annotations.Suspendable; +import net.corda.v5.crypto.SecureHash; +import net.corda.v5.ledger.utxo.StateAndRef; +import net.corda.v5.ledger.utxo.UtxoLedgerService; +import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; + +public class MoveFlow implements ClientStartableFlow{ + + private final static Logger log = LoggerFactory.getLogger(MoveFlow.class); + + @CordaInject + public JsonMarshallingService jsonMarshallingService; + + @CordaInject + public UtxoLedgerService utxoLedgerService; + + @CordaInject + public FlowEngine flowEngine; + + @Suspendable + @Override + public String call(ClientRequestBody requestBody) { + SecureHash utxoTrxId = null; + + try { + final MoveFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, MoveFlowArgs.class); + + final GameCommand command = new GameCommand(args.move); + + final StateAndRef currntGameBoardSar = this.flowEngine + .subFlow(new GetFlow(args.gameUuid)); + + final GameBoardState newGameBoard = new GameBoardState( + (GameBoardState)currntGameBoardSar.getState().getContractState(), + args.move, + args.message); + + final UtxoSignedTransaction gameBoardMoveTrx = utxoLedgerService.createTransactionBuilder() + .addCommand(command) + .addInputState(currntGameBoardSar.getRef()) + .addOutputState(newGameBoard) + .addSignatories(newGameBoard.getParticipants()) + .setNotary(currntGameBoardSar.getState().getNotaryName()) + .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) + .toSignedTransaction(); + + utxoTrxId = this.flowEngine + .subFlow(new CommitSubFlow(gameBoardMoveTrx, command.getCounterparty(currntGameBoardSar))); + + final View gameStateView = this.flowEngine + .subFlow(new ViewBuilder(utxoTrxId)); + + return new FlowResponce(gameStateView, utxoTrxId) + .toJsonEncodedString(jsonMarshallingService); + } + catch (Exception e) { + log.warn(requestBody + " " +e.getClass() +": " +e.getMessage()); + e.printStackTrace(System.out); + return new FlowResponce(e, utxoTrxId) + .toJsonEncodedString(jsonMarshallingService); + } + } + +} diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlowArgs.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlowArgs.java new file mode 100644 index 0000000..d05ec71 --- /dev/null +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlowArgs.java @@ -0,0 +1,17 @@ +package djmil.cordacheckers.gameboard; + +import java.util.List; +import java.util.UUID; + +public class MoveFlowArgs { + public final List move; + public final UUID gameUuid; + public final String message; + + // Serialisation service requires a default constructor + public MoveFlowArgs() { + this.move = null; + this.gameUuid = null; + this.message = null; + } +} diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/SurrenderFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/SurrenderFlow.java index ac1bd04..558db02 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/SurrenderFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/SurrenderFlow.java @@ -21,10 +21,8 @@ import net.corda.v5.application.flows.ClientStartableFlow; import net.corda.v5.application.flows.CordaInject; import net.corda.v5.application.flows.FlowEngine; import net.corda.v5.application.marshalling.JsonMarshallingService; -import net.corda.v5.application.membership.MemberLookup; import net.corda.v5.base.annotations.Suspendable; import net.corda.v5.crypto.SecureHash; -import net.corda.v5.ledger.common.NotaryLookup; import net.corda.v5.ledger.utxo.StateAndRef; import net.corda.v5.ledger.utxo.UtxoLedgerService; import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; @@ -36,12 +34,6 @@ public class SurrenderFlow implements ClientStartableFlow{ @CordaInject public JsonMarshallingService jsonMarshallingService; - @CordaInject - public MemberLookup memberLookup; - - @CordaInject - public NotaryLookup notaryLookup; - @CordaInject public UtxoLedgerService utxoLedgerService; diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitSubFlowResponder.java b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitSubFlowResponder.java index 392c02f..9f5ee0d 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitSubFlowResponder.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitSubFlowResponder.java @@ -76,7 +76,7 @@ public class CommitSubFlowResponder implements ResponderFlow { */ @Suspendable GameState getActualGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) { - switch (command.action) { + switch (command.getAction()) { case GAME_PROPOSAL_CREATE: return getSingleOutputState(gameStateTransaction, GameProposalState.class); @@ -89,7 +89,7 @@ public class CommitSubFlowResponder implements ResponderFlow { return getSingleInputState(gameStateTransaction, GameBoardState.class); case GAME_BOARD_MOVE: - return getSingleOutputState(gameStateTransaction, GameBoardState.class); + return getSingleInputState(gameStateTransaction, GameBoardState.class); case GAME_RESULT_CREATE: return getSingleOutputState(gameStateTransaction, GameResultState.class); diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/FlowResponce.java b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/FlowResponce.java index 68fffb6..c7813af 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/FlowResponce.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/FlowResponce.java @@ -17,7 +17,7 @@ public class FlowResponce { public FlowResponce(Exception exception, SecureHash transactionId) { this.successStatus = null; this.transactionId = transactionId; - this.failureStatus = exception.getMessage(); + this.failureStatus = exception.toString(); } public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) { diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/ViewBuilder.java b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/ViewBuilder.java index 989fb62..257934d 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/ViewBuilder.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/ViewBuilder.java @@ -51,7 +51,7 @@ public class ViewBuilder implements SubFlow { final GameState state = getLatestGameStateFromTransaction(gameStateUtxo, command); final View.Status viewStatus = action2status(command, state, myName); - switch (command.action) { + switch (command.getAction()) { case GAME_PROPOSAL_CREATE: case GAME_PROPOSAL_CANCEL: case GAME_PROPOSAL_REJECT: @@ -62,7 +62,7 @@ public class ViewBuilder implements SubFlow { case GAME_PROPOSAL_ACCEPT: case GAME_BOARD_MOVE: if (state instanceof GameBoardState) - return new View(viewStatus, (GameBoardState)state, command.move, myName); + return new View(viewStatus, (GameBoardState)state, command.getMove(), myName); break; case GAME_BOARD_SURRENDER: @@ -85,7 +85,7 @@ public class ViewBuilder implements SubFlow { */ @Suspendable GameState getLatestGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) { - switch (command.action) { + switch (command.getAction()) { case GAME_PROPOSAL_CREATE: return getSingleOutputState(gameStateTransaction, GameProposalState.class); @@ -109,7 +109,7 @@ public class ViewBuilder implements SubFlow { View.Status action2status(GameCommand command, GameState state, MemberX500Name myName) { final boolean myAction = command.getInitiator(state).compareTo(myName) == 0; - switch (command.action) { + switch (command.getAction()) { case GAME_PROPOSAL_CREATE: if (myAction) return View.Status.GAME_PROPOSAL_WAIT_FOR_OPPONENT;