From e3ca1e0fc02bda4fefd784f5769515fabcbf61fc Mon Sep 17 00:00:00 2001 From: djmil Date: Sun, 1 Oct 2023 14:13:52 +0200 Subject: [PATCH] GameBoard VicrotyFlow - better error messages - moar GameMove tests - a vicotry condition check - GameContract + participnats vaidation + more accure Major Action checks - automaic Vicotry condition check for Move flow - GameResultCommiter as a sub flow --- .../cordaclient/dao/flow/arguments/Rsp.java | 10 +- .../dao/flow/arguments/RspGameState.java | 12 -- .../cordaclient/GameBoardTests.java | 18 ++- .../djmil/cordacheckers/checkers/Move.java | 54 ++++++-- .../cordacheckers/contracts/GameCommand.java | 119 ++++++++++-------- .../cordacheckers/contracts/GameInfo.java | 2 +- .../contracts/GameResultContract.java | 2 +- .../cordacheckers/states/GameBoardState.java | 24 ++-- .../cordacheckers/states/GameResultState.java | 2 +- .../cordacheckers/gameboard/MoveFlow.java | 51 ++++++-- .../gameboard/SurrenderFlow.java | 33 +---- .../cordacheckers/gameboard/VictoryFlow.java | 60 +++++++++ .../gameproposal/AcceptFlow.java | 9 +- .../gameproposal/CancelFlow.java | 6 +- .../gameproposal/CreateFlow.java | 6 +- .../gameproposal/RejectFlow.java | 6 +- .../gameresult/GameResultBuilder.java | 74 ----------- .../gameresult/GameResultCommiter.java | 99 +++++++++++++++ .../{CommitSubFlow.java => CommitTrx.java} | 8 +- ...Responder.java => CommitTrxResponder.java} | 4 +- .../cordacheckers/gamestate/FlowResponce.java | 6 + .../cordacheckers/gamestate/GetFlow.java | 5 +- .../cordacheckers/gamestate/ListFlow.java | 2 +- 23 files changed, 385 insertions(+), 227 deletions(-) create mode 100644 corda/workflows/src/main/java/djmil/cordacheckers/gameboard/VictoryFlow.java delete mode 100644 corda/workflows/src/main/java/djmil/cordacheckers/gameresult/GameResultBuilder.java create mode 100644 corda/workflows/src/main/java/djmil/cordacheckers/gameresult/GameResultCommiter.java rename corda/workflows/src/main/java/djmil/cordacheckers/gamestate/{CommitSubFlow.java => CommitTrx.java} (88%) rename corda/workflows/src/main/java/djmil/cordacheckers/gamestate/{CommitSubFlowResponder.java => CommitTrxResponder.java} (97%) 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 d2ff720..9bdbaaf 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 @@ -7,13 +7,13 @@ public interface Rsp { public String failureStatus(); public default T getResponce(RequestBody requestBody) { - if (failureStatus() != null) { - final String msg = requestBody.flowClassName() +" requestId " +requestBody.clientRequestId() +" has failed: " +failureStatus(); - System.err.println(msg); - throw new RuntimeException(msg); + if (failureStatus() == null) { + System.out.println(requestBody.clientRequestId() +" [OK]"); + return successStatus(); } - return successStatus(); + System.err.println(requestBody.clientRequestId() +" has failed: " +failureStatus()); + throw new RuntimeException(failureStatus()); } } diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/RspGameState.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/RspGameState.java index 758597c..f712911 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/RspGameState.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/RspGameState.java @@ -1,22 +1,10 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; import djmil.cordacheckers.cordaclient.dao.GameState; -import djmil.cordacheckers.cordaclient.dao.flow.RequestBody; public record RspGameState( GameState successStatus, String transactionId, String failureStatus) implements Rsp { - @Override - public GameState getResponce(RequestBody requestBody) { - if (failureStatus() != null) { - final String msg = requestBody.flowClassName() +" requestId " +requestBody.clientRequestId() +" has failed"; - System.err.println(msg +". Reson " +failureStatus +". TransactionId " +transactionId); - throw new RuntimeException(msg); - } - - System.out.println("[OK] "+transactionId); - return successStatus(); - } } diff --git a/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java b/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java index 5108986..5591f07 100644 --- a/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java +++ b/backend/src/test/java/djmil/cordacheckers/cordaclient/GameBoardTests.java @@ -79,12 +79,24 @@ public class GameBoardTests { System.out.println("Game UUID " +game.uuid()); final var m0 = cordaClient.gameProposalAccept(hiBlack, game.uuid()); + assertThat(m0.previousMove()).isNull(); + assertThat(m0.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); assertThatThrownBy(() -> { cordaClient.gameBoardMove(hiBlack, game.uuid(), move(12, 16), "Black can not move, since it is opponent's turn"); }); + assertThatThrownBy(() -> { + cordaClient.gameBoardMove(hiBlack, game.uuid(), move(24, 20), + "Black can not move opponent's stone as well"); + }); + + assertThatThrownBy(() -> { + cordaClient.gameBoardMove(hiWhite, game.uuid(), move(9, 13), + "White can not move opponent's stone"); + }); + assertThatThrownBy(() -> { cordaClient.gameBoardMove(hiWhite, game.uuid(), move(17, 14), "Trying to move an empty tile"); @@ -96,11 +108,13 @@ public class GameBoardTests { assertThat(m1.board().get(22)).isNull(); assertThat(m1.board().get(18)).isEqualTo(WHITE_MAN); assertThat(m1.moveNumber() == 1); + assertThat(m1.previousMove().get(0) == 22); + assertThat(m1.previousMove().get(1) == 18); assertThat(m1.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); assertThatThrownBy(() -> { cordaClient.gameBoardMove(hiBlack, game.uuid(), move(12, 13), - "Prohibitted move shall be rejected"); + "Prohibited move shall be rejected"); }); final var m2 = cordaClient.gameBoardMove(hiBlack, game.uuid(), move(11, 15), null); @@ -109,6 +123,8 @@ public class GameBoardTests { assertThat(m2.board().get(11)).isNull(); assertThat(m2.board().get(15)).isEqualTo(BLACK_MAN); assertThat(m2.moveNumber() == 2); + assertThat(m2.previousMove().get(0) == 11); + assertThat(m2.previousMove().get(1) == 15); assertThat(m2.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); assertThatThrownBy(() -> { diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Move.java b/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Move.java index 5f65e06..8ea542b 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Move.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/checkers/Move.java @@ -30,8 +30,8 @@ public class Move { return move.apply(board, moveColor); } - private Stone.Color apply(Map board, Stone.Color expectedMoveColor) { - final Set mandatoryJumps = getMandatoryJumps(board, expectedMoveColor); + private Stone.Color apply(Map board, Stone.Color moveColor) { + final Set mandatoryJumps = getPossibleJumps(board, moveColor); if (mandatoryJumps.size() != 0 && !mandatoryJumps.contains(this)) throw new Exception("A mandatory opponent's stone capture must be performed"); @@ -39,8 +39,8 @@ public class Move { 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"); + if (movingStone.getColor() != moveColor) + throw new Exception("Only " +moveColor.name() +" color is expected to move"); final Set allMoves = movingStone.getMoves(from); if (!allMoves.contains(this)) @@ -52,16 +52,16 @@ public class Move { if (isJump()) { final Stone jumpOver = board.remove(this.jumpOver); - if (jumpOver == null || jumpOver.getColor() != expectedMoveColor.opposite()) + if (jumpOver == null || jumpOver.getColor() != moveColor.opposite()) throw new Exception("Must jump over an opponent's stone"); final Set chainJumps = movingStone.getJumps(this.to); - chainJumps.removeIf(move -> !move.canJump(board, expectedMoveColor)); + chainJumps.removeIf(move -> !move.canJump(board, moveColor)); if (chainJumps.size() != 0) - return expectedMoveColor; // player must continue his turn + return moveColor; // player must continue his turn } - return expectedMoveColor.opposite(); // player has finished his turn + return moveColor.opposite(); // player has finished his turn } boolean isJump() { @@ -69,6 +69,9 @@ public class Move { } boolean canJump(Map board, Stone.Color color) { + if (!isJump()) + return false; + final Stone jumpStone = board.get(this.from); if (jumpStone == null || jumpStone.getColor() != color) return false; @@ -83,6 +86,20 @@ public class Move { return true; } + boolean canMove(Map board, Stone.Color color) { + if (isJump()) + return canJump(board, color); + + final Stone stepStone = board.get(this.from); + if (stepStone == null || stepStone.getColor() != color) + return false; + + if (board.get(this.to) == null) + return false; + + return true; + } + static Integer intersection(Set a, Set b) { Set intersection = new HashSet(a); @@ -171,7 +188,7 @@ public class Move { } } - static Set getMandatoryJumps(Map board, Stone.Color color) { + static Set getPossibleJumps(Map board, Stone.Color color) { Set res = new HashSet(); for (Map.Entry entry : board.entrySet()) { @@ -183,13 +200,30 @@ public class Move { final Set jumps = stone.getJumps(from); jumps.removeIf( move -> !move.canJump(board, color)); - res.addAll(jumps); } return res; } + public static Set getPossibleMoves(Map board, Stone.Color color) { + Set res = new HashSet(); + + for (Map.Entry entry : board.entrySet()) { + final Integer from = entry.getKey(); + final Stone stone = entry.getValue(); + + if (stone.getColor() != color) + continue; + + final Set moves = stone.getMoves(from); + moves.removeIf( move -> !move.canMove(board, color)); + res.addAll(moves); + } + + return res; + } + @Override public int hashCode() { final int prime = 31; 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 d6648cd..97062d2 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameCommand.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameCommand.java @@ -3,9 +3,9 @@ package djmil.cordacheckers.contracts; import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState; import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState; -import java.util.LinkedList; import java.util.List; +import djmil.cordacheckers.checkers.Move; import djmil.cordacheckers.states.GameBoardState; import djmil.cordacheckers.states.GameProposalState; import djmil.cordacheckers.states.GameResultState; @@ -91,10 +91,8 @@ public class GameCommand implements Command { break; case GAME_BOARD_MOVE: - if (gameState instanceof GameBoardState) { - final var activePlayer = ((GameBoardState)gameState).getActivePlayerName(); - return gameState.getOpponentName(activePlayer); - } + if (gameState instanceof GameBoardState) + return ((GameBoardState)gameState).getIdelPlayerName(); break; case GAME_BOARD_SURRENDER: @@ -114,28 +112,34 @@ public class GameCommand implements Command { throw new RuntimeException(action +": unexpected GameState type " +gameState.getClass()); } - /* - * Transaction validation - */ - public void validateGameProposalCreate(UtxoLedgerTransaction trx) { requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE); requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE); final GameProposalState gameProposal = getSingleOutputState(trx, GameProposalState.class); - requireThat(gameProposal.getOpponentName(gameProposal.getIssuerName()).compareTo(gameProposal.getAcquierName()) == 0, + /* + * Major command logick check + */ + requireThat(gameProposal.getIssuerName().compareTo(gameProposal.getBlackPlayer()) == 0 || + gameProposal.getIssuerName().compareTo(gameProposal.getWhitePlayer()) == 0, "GameProposal.Issuer must be either Black or White player"); } public void validateGameProposalAccept(UtxoLedgerTransaction trx) { - requireThat(trx.getInputContractStates().size() == 1, ACCEPT_INPUT_STATE); + requireThat(trx.getInputContractStates().size() == 1, ACCEPT_INPUT_STATE); requireThat(trx.getOutputContractStates().size() == 1, ACCEPT_OUTPUT_STATE); - final GameProposalState inGameProposal = getSingleInputState(trx, GameProposalState.class); + final GameProposalState inGameProposal = getSingleInputState (trx, GameProposalState.class); final GameBoardState outGameBoard = getSingleOutputState(trx, GameBoardState.class); - requireThat(inGameProposal.getParticipants().containsAll(outGameBoard.getParticipants()), IN_OUT_PARTICIPANTS); + requireThat(outGameBoard.getWhitePlayer().compareTo(inGameProposal.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS); + requireThat(outGameBoard.getBlackPlayer().compareTo(inGameProposal.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS); + requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), IN_OUT_PARTICIPANTS); + + /* + * Major command logick check + */ requireThat(outGameBoard.getBoard().equals(GameBoardContract.initialBoard), "Bad GameBoard initial state"); } @@ -153,47 +157,61 @@ public class GameCommand implements Command { getSingleInputState(trx, GameProposalState.class); } - public void validateGameBoardSurrender(UtxoLedgerTransaction trx) { - requireThat(trx.getInputContractStates().size() == 1, SURRENDER_INPUT_STATE); - final var inGameBoardState = getSingleInputState(trx, GameBoardState.class); - - requireThat(trx.getOutputContractStates().size() == 1, SURRENDER_OUTPUT_STATE); - final var outGameResultState = getSingleOutputState(trx, GameResultState.class); - - List inActors = new LinkedList(List.of( - inGameBoardState.getWhitePlayer(), - inGameBoardState.getBlackPlayer()) - ); - - List outActors = new LinkedList(List.of( - outGameResultState.getWhitePlayer(), - outGameResultState.getBlackPlayer()) - ); - - requireThat(inActors.containsAll(outActors) && outActors.containsAll(inActors), IN_OUT_PARTICIPANTS); - - final GameInfo gameInfo = new GameInfo(trx); - final var winnerName = inGameBoardState.getOpponentName(gameInfo.issuer); - requireThat(outGameResultState.getWinnerName().compareTo(winnerName) == 0, - "Expected winner "+winnerName.getCommonName() +", proposed winner " +outGameResultState.getWinnerName().getCommonName()); - } - public void validateGameBoardMove(UtxoLedgerTransaction trx, List move) { - requireThat(trx.getInputContractStates().size() == 1, MOVE_INPUT_STATE); - final var inGameBoardState = getSingleInputState(trx, GameBoardState.class); - + requireThat(trx.getInputContractStates().size() == 1, MOVE_INPUT_STATE); requireThat(trx.getOutputContractStates().size() == 1, MOVE_OUTPUT_STATE); - final var outGameBoardState = getSingleOutputState(trx, GameBoardState.class); - requireThat(inGameBoardState.getWhitePlayer().compareTo(outGameBoardState.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS); - requireThat(inGameBoardState.getBlackPlayer().compareTo(outGameBoardState.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS); + final var inGameBoard = getSingleInputState (trx, GameBoardState.class); + final var outGameBoard = getSingleOutputState(trx, GameBoardState.class); - final var newGameBoard = new GameBoardState(inGameBoardState, move, outGameBoardState.getMessage()); - requireThat(outGameBoardState.equals(newGameBoard), "Unexpected output state"); + requireThat(outGameBoard.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS); + requireThat(outGameBoard.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS); + requireThat(outGameBoard.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS); + + /* + * Major command logick check + */ + final var newGameBoard = new GameBoardState(inGameBoard, move, outGameBoard.getMessage()); + requireThat(outGameBoard.equals(newGameBoard), "Unexpected output state"); } - public void validateGameResultCreate(UtxoLedgerTransaction trx) { - throw new RuntimeException("Unimplemented"); + public void validateGameBoardSurrender(UtxoLedgerTransaction trx) { + requireThat(trx.getInputContractStates().size() == 1, SURRENDER_INPUT_STATE); + requireThat(trx.getOutputContractStates().size() == 1, SURRENDER_OUTPUT_STATE); + + final var inGameBoard = getSingleInputState (trx, GameBoardState.class); + final var outGameResult = getSingleOutputState(trx, GameResultState.class); + + requireThat(outGameResult.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS); + requireThat(outGameResult.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS); + requireThat(outGameResult.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS); + + /* + * Major command logick check + */ + requireThat(outGameResult.getLooserName().compareTo(outGameResult.getBlackPlayer()) == 0 || + outGameResult.getLooserName().compareTo(outGameResult.getWhitePlayer()) == 0, + "Surenderer must be either Black or White player"); + } + + public void validateGameBoardVictory(UtxoLedgerTransaction trx) { + requireThat(trx.getInputContractStates().size() == 1, VICTORY_INPUT_STATE); + requireThat(trx.getOutputContractStates().size() == 1, VICTORY_OUTPUT_STATE); + + final var inGameBoard = getSingleInputState (trx, GameBoardState.class); + final var outGameResult = getSingleOutputState(trx, GameResultState.class); + + requireThat(outGameResult.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS); + requireThat(outGameResult.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS); + requireThat(outGameResult.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS); + + /* + * Major command logick check + */ + final var possibleMoves = Move.getPossibleMoves(inGameBoard.getBoard(), inGameBoard.getActiveColor()); + requireThat(possibleMoves.isEmpty(), "Victory condition violation"); + + requireThat(outGameResult.getWinnerName().compareTo(inGameBoard.getIdelPlayerName()) == 0, "Bad winner name"); } public static void requireThat(boolean asserted, String errorMessage) { @@ -203,7 +221,7 @@ public class GameCommand implements Command { } static final String REQUIRE_SINGLE_COMMAND = "Require a single command"; - static final String IN_OUT_PARTICIPANTS = "Input participants should represent output participants"; + static final String IN_OUT_PARTICIPANTS = "Output participants should include all input participants"; static final String CREATE_INPUT_STATE = "Create command should have no input states"; static final String CREATE_OUTPUT_STATE = "Create command should output exactly one GameProposal state"; @@ -224,4 +242,7 @@ public class GameCommand implements Command { static final String MOVE_OUTPUT_STATE = "MOVE command should have exactly one GameBoardState output state"; static final String MOVE_OUT_BOARD = "MOVE command: move checkers rules violation"; static final String MOVE_OUT_COLOR = "MOVE command: moveColor checkers rules violation"; + + 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"; } diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameInfo.java b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameInfo.java index 075a403..506132a 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameInfo.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameInfo.java @@ -60,7 +60,7 @@ public class GameInfo { return; case GAME_BOARD_SURRENDER: - this.state = getSingleOutputState(utxoTrx, GameResultState.class); + this.state = getSingleOutputState(utxoTrx, GameResultState.class); this.issuer = ((GameResultState)this.state).getLooserName(); this.actor = null; return; 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 d41ba4e..2b27a75 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameResultContract.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameResultContract.java @@ -25,7 +25,7 @@ public class GameResultContract implements net.corda.v5.ledger.utxo.Contract { break; case GAME_BOARD_VICTORY: - command.validateGameResultCreate(trx); + command.validateGameBoardVictory(trx); break; default: 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 938a283..5ff77b5 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameBoardState.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameBoardState.java @@ -15,7 +15,7 @@ import net.corda.v5.ledger.utxo.BelongsToContract; @BelongsToContract(GameBoardContract.class) public class GameBoardState extends GameState { - private final Stone.Color moveColor; + private final Stone.Color activeColor; private final Integer moveNumber; private final Map board; @@ -24,7 +24,7 @@ public class GameBoardState extends GameState { gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants); this.board = new LinkedHashMap(GameBoardContract.initialBoard); - this.moveColor = Stone.Color.WHITE; + this.activeColor = Stone.Color.WHITE; this.moveNumber = 0; } @@ -33,26 +33,26 @@ public class GameBoardState extends GameState { message, currentGameBoardState.participants); this.board = new LinkedHashMap(currentGameBoardState.getBoard()); - this.moveColor = Move.apply(move, this.board, currentGameBoardState.getMoveColor()); + this.activeColor = Move.apply(move, this.board, currentGameBoardState.getActiveColor()); - this.moveNumber = (currentGameBoardState.moveColor == this.moveColor) + this.moveNumber = (currentGameBoardState.activeColor == this.activeColor) ? currentGameBoardState.getMoveNumber() // current player has not finished his move jet : currentGameBoardState.getMoveNumber() +1; } @ConstructorForDeserialization public GameBoardState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, - Stone.Color moveColor, Integer moveNumber, Map board, String message, + Stone.Color activeColor, Integer moveNumber, Map board, String message, UUID gameUuid, List participants) { super(whitePlayer, blackPlayer, gameUuid, message, participants); - this.moveColor = moveColor; + this.activeColor = activeColor; this.moveNumber = moveNumber; this.board = board; } - public Stone.Color getMoveColor() { - return moveColor; + public Stone.Color getActiveColor() { + return activeColor; } public Integer getMoveNumber() { @@ -60,7 +60,11 @@ public class GameBoardState extends GameState { } public MemberX500Name getActivePlayerName() { - return moveColor == Stone.Color.WHITE ? whitePlayer : blackPlayer; + return activeColor == Stone.Color.WHITE ? whitePlayer : blackPlayer; + } + + public MemberX500Name getIdelPlayerName() { + return activeColor == Stone.Color.WHITE ? blackPlayer : whitePlayer; } public Map getBoard() { @@ -76,7 +80,7 @@ public class GameBoardState extends GameState { if (getClass() != obj.getClass()) return false; GameBoardState other = (GameBoardState) obj; - if (moveColor != other.moveColor) + if (activeColor != other.activeColor) return false; if (moveNumber == null) { if (other.moveNumber != null) 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 d8c02ce..c5b6c1f 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameResultState.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameResultState.java @@ -21,7 +21,7 @@ public class GameResultState extends GameState { this.winnerName = winnerName; } - public GameResultState(GameBoardState gameBoardState, MemberX500Name winnerName, PublicKey custodianPubicKey) { + public GameResultState(MemberX500Name winnerName, GameBoardState gameBoardState, PublicKey custodianPubicKey) { super(gameBoardState.whitePlayer, gameBoardState.blackPlayer, gameBoardState.gameUuid, null, gameBoardState.participants, custodianPubicKey); this.winnerName = winnerName; } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlow.java index 0ba00ca..6a18ae3 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlow.java @@ -6,8 +6,10 @@ import java.time.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import djmil.cordacheckers.checkers.Move; import djmil.cordacheckers.contracts.GameCommand; -import djmil.cordacheckers.gamestate.CommitSubFlow; +import djmil.cordacheckers.gameresult.GameResultCommiter; +import djmil.cordacheckers.gamestate.CommitTrx; import djmil.cordacheckers.gamestate.FlowResponce; import djmil.cordacheckers.gamestate.GetFlow; import djmil.cordacheckers.gamestate.View; @@ -19,7 +21,9 @@ 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.base.types.MemberX500Name; import net.corda.v5.crypto.SecureHash; import net.corda.v5.ledger.utxo.StateAndRef; import net.corda.v5.ledger.utxo.UtxoLedgerService; @@ -38,6 +42,9 @@ public class MoveFlow implements ClientStartableFlow{ @CordaInject public FlowEngine flowEngine; + @CordaInject + public MemberLookup memberLookup; + @Suspendable @Override public String call(ClientRequestBody requestBody) { @@ -48,38 +55,62 @@ public class MoveFlow implements ClientStartableFlow{ final GameCommand command = new GameCommand(args.move); - final StateAndRef currntGameBoardSar = this.flowEngine + final StateAndRef currenrGameSar = this.flowEngine .subFlow(new GetFlow(args.gameUuid)); final GameBoardState newGameBoard = new GameBoardState( - (GameBoardState)currntGameBoardSar.getState().getContractState(), + (GameBoardState)currenrGameSar.getState().getContractState(), args.move, args.message); - final UtxoSignedTransaction gameBoardMoveTrx = utxoLedgerService.createTransactionBuilder() + final UtxoSignedTransaction moveTrx = utxoLedgerService.createTransactionBuilder() .addCommand(command) - .addInputState(currntGameBoardSar.getRef()) + .addInputState(currenrGameSar.getRef()) .addOutputState(newGameBoard) .addSignatories(newGameBoard.getParticipants()) - .setNotary(currntGameBoardSar.getState().getNotaryName()) + .setNotary(currenrGameSar.getState().getNotaryName()) .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .toSignedTransaction(); utxoTrxId = this.flowEngine - .subFlow(new CommitSubFlow(gameBoardMoveTrx, command.getCounterparty(currntGameBoardSar))); + .subFlow(new CommitTrx(moveTrx, command.getCounterparty(currenrGameSar))); + + if (amIwon(newGameBoard)) { + log.info("Opponent has no possible moves. Claim victory!"); + utxoTrxId = this.flowEngine + .subFlow(new GameResultCommiter( + newGameBoard.getGameUuid(), + new GameCommand(GameCommand.Action.GAME_BOARD_VICTORY))); + } final View gameStateView = this.flowEngine .subFlow(new ViewBuilder(utxoTrxId)); return new FlowResponce(gameStateView, utxoTrxId) .toJsonEncodedString(jsonMarshallingService); - } + } + catch (Move.Exception e) { + return new FlowResponce(e.getMessage(), utxoTrxId) + .toJsonEncodedString(jsonMarshallingService); + } catch (Exception e) { - log.warn(requestBody + " " +e.getClass() +": " +e.getMessage()); - e.printStackTrace(System.out); + log.warn(requestBody + " [ERROR] " +e.toString()); return new FlowResponce(e, utxoTrxId) .toJsonEncodedString(jsonMarshallingService); } } + @Suspendable + boolean amIwon(GameBoardState gameBoard) { + final MemberX500Name myName = memberLookup.myInfo().getName(); + + if (gameBoard.getActivePlayerName().compareTo(myName) == 0 || + gameBoard.getIdelPlayerName().compareTo(myName) != 0) + return false; // a bit of overkill check, but you have to finish your move first + + final var possibleOponnentMoves = Move.getPossibleMoves(gameBoard.getBoard(), gameBoard.getActiveColor()); + + return possibleOponnentMoves.isEmpty(); // true, if my opponent has no possible moves + } + } 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 ccce6de..2412003 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/SurrenderFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/SurrenderFlow.java @@ -1,21 +1,15 @@ package djmil.cordacheckers.gameboard; -import java.time.Duration; -import java.time.Instant; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import djmil.cordacheckers.contracts.GameCommand; -import djmil.cordacheckers.gameresult.GameResultBuilder; -import djmil.cordacheckers.gameresult.GameResultBuilder.Reason; -import djmil.cordacheckers.gamestate.CommitSubFlow; +import djmil.cordacheckers.gameresult.GameResultCommiter; import djmil.cordacheckers.gamestate.FlowResponce; -import djmil.cordacheckers.gamestate.GetFlow; import djmil.cordacheckers.gamestate.View; import djmil.cordacheckers.gamestate.ViewBuilder; -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; @@ -23,9 +17,7 @@ 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 SurrenderFlow implements ClientStartableFlow{ @@ -47,28 +39,10 @@ public class SurrenderFlow implements ClientStartableFlow{ try { final GameCommand command = new GameCommand(GameCommand.Action.GAME_BOARD_SURRENDER); - final UUID gameUuid = UUID.fromString(requestBody.getRequestBody()); - final StateAndRef gameBoardSar = this.flowEngine - .subFlow(new GetFlow(gameUuid)); - - final GameResultBuilder.Result out = this.flowEngine - .subFlow(new GameResultBuilder(gameBoardSar, Reason.SURRENDER)); - - final UtxoSignedTransaction gameBoardSurrenderTrx = utxoLedgerService.createTransactionBuilder() - .addCommand(command) - .addInputState(gameBoardSar.getRef()) - .addOutputState(out.gameResult) - .addSignatories(out.gameResult.getParticipants()) - .setNotary(gameBoardSar.getState().getNotaryName()) - .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) - .toSignedTransaction(); - utxoTrxId = this.flowEngine - .subFlow(new CommitSubFlow(gameBoardSurrenderTrx, - command.getCounterparty(out.gameResult), - out.custodyName)); + .subFlow(new GameResultCommiter(gameUuid, command)); final View gameStateView = this.flowEngine .subFlow(new ViewBuilder(utxoTrxId)); @@ -77,8 +51,7 @@ public class SurrenderFlow implements ClientStartableFlow{ .toJsonEncodedString(jsonMarshallingService); } catch (Exception e) { - log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); - e.printStackTrace(System.out); + log.warn(requestBody + " [ERROR] " +e.toString()); return new FlowResponce(e, utxoTrxId) .toJsonEncodedString(jsonMarshallingService); } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/VictoryFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/VictoryFlow.java new file mode 100644 index 0000000..0100591 --- /dev/null +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/VictoryFlow.java @@ -0,0 +1,60 @@ +package djmil.cordacheckers.gameboard; + +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import djmil.cordacheckers.contracts.GameCommand; +import djmil.cordacheckers.gameresult.GameResultCommiter; +import djmil.cordacheckers.gamestate.FlowResponce; +import djmil.cordacheckers.gamestate.View; +import djmil.cordacheckers.gamestate.ViewBuilder; +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.UtxoLedgerService; + +public class VictoryFlow implements ClientStartableFlow { + + private final static Logger log = LoggerFactory.getLogger(VictoryFlow.class); + + @CordaInject + public JsonMarshallingService jsonMarshallingService; + + @CordaInject + public UtxoLedgerService utxoLedgerService; + + @CordaInject + public FlowEngine flowEngine; + + @Override + @Suspendable + public String call(ClientRequestBody requestBody) { + SecureHash utxoTrxId = null; + + try { + final UUID gameUuid = UUID.fromString(requestBody.getRequestBody()); + final GameCommand command = new GameCommand(GameCommand.Action.GAME_BOARD_VICTORY); + + utxoTrxId = this.flowEngine + .subFlow(new GameResultCommiter(gameUuid, command)); + + final View gameStateView = this.flowEngine + .subFlow(new ViewBuilder(utxoTrxId)); + + return new FlowResponce(gameStateView, utxoTrxId) + .toJsonEncodedString(jsonMarshallingService); + } + catch (Exception e) { + log.warn(requestBody + " [ERROR] " +e.toString()); + return new FlowResponce(e, utxoTrxId) + .toJsonEncodedString(jsonMarshallingService); + } + } + +} diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/AcceptFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/AcceptFlow.java index 2941c54..63cc45c 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/AcceptFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/AcceptFlow.java @@ -8,7 +8,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import djmil.cordacheckers.contracts.GameCommand; -import djmil.cordacheckers.gamestate.CommitSubFlow; +import djmil.cordacheckers.gamestate.CommitTrx; import djmil.cordacheckers.gamestate.FlowResponce; import djmil.cordacheckers.gamestate.GetFlow; import djmil.cordacheckers.gamestate.View; @@ -56,7 +56,7 @@ public class AcceptFlow implements ClientStartableFlow{ final GameBoardState gameBoard = new GameBoardState(gameProposal); // <<-- accepted - final UtxoSignedTransaction gameProposalAcceptTrx = utxoLedgerService.createTransactionBuilder() + final UtxoSignedTransaction acceptTrx = utxoLedgerService.createTransactionBuilder() .addCommand(command) .addInputState(gameProposalSar.getRef()) .addOutputState(gameBoard) @@ -66,7 +66,7 @@ public class AcceptFlow implements ClientStartableFlow{ .toSignedTransaction(); utxoTrxId = this.flowEngine - .subFlow(new CommitSubFlow(gameProposalAcceptTrx, command.getCounterparty(gameProposal))); + .subFlow(new CommitTrx(acceptTrx, command.getCounterparty(gameProposal))); final View gameView = this.flowEngine .subFlow(new ViewBuilder(utxoTrxId)); @@ -75,8 +75,7 @@ public class AcceptFlow implements ClientStartableFlow{ .toJsonEncodedString(jsonMarshallingService); } catch (Exception e) { - log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); - e.printStackTrace(System.out); + log.warn(requestBody + " [ERROR] " +e.toString()); return new FlowResponce(e, utxoTrxId) .toJsonEncodedString(jsonMarshallingService); } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CancelFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CancelFlow.java index a2ba205..ff7fdc2 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CancelFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CancelFlow.java @@ -8,7 +8,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import djmil.cordacheckers.contracts.GameCommand; -import djmil.cordacheckers.gamestate.CommitSubFlow; +import djmil.cordacheckers.gamestate.CommitTrx; import djmil.cordacheckers.gamestate.FlowResponce; import djmil.cordacheckers.gamestate.GetFlow; import djmil.cordacheckers.gamestate.View; @@ -62,7 +62,7 @@ public class CancelFlow implements ClientStartableFlow{ .toSignedTransaction(); utxoTrxId = this.flowEngine - .subFlow(new CommitSubFlow(gameProposalCancelTrx, command.getCounterparty(gameProposal))); + .subFlow(new CommitTrx(gameProposalCancelTrx, command.getCounterparty(gameProposal))); final View gameStateView = this.flowEngine .subFlow(new ViewBuilder(utxoTrxId)); @@ -71,7 +71,7 @@ public class CancelFlow implements ClientStartableFlow{ .toJsonEncodedString(jsonMarshallingService); } catch (Exception e) { - log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); + log.warn(requestBody + " [ERROR] " +e.toString()); return new FlowResponce(e, utxoTrxId) .toJsonEncodedString(jsonMarshallingService); } 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 fc59632..bb4601d 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java @@ -12,7 +12,7 @@ import org.slf4j.LoggerFactory; import djmil.cordacheckers.checkers.Stone; import djmil.cordacheckers.contracts.GameCommand; -import djmil.cordacheckers.gamestate.CommitSubFlow; +import djmil.cordacheckers.gamestate.CommitTrx; import djmil.cordacheckers.gamestate.FlowResponce; import djmil.cordacheckers.gamestate.View; import djmil.cordacheckers.gamestate.ViewBuilder; @@ -69,7 +69,7 @@ public class CreateFlow implements ClientStartableFlow{ .toSignedTransaction(); utxoTrxId = this.flowEngine - .subFlow(new CommitSubFlow(gameProposalCreateTrx, command.getCounterparty(gameProposal))); + .subFlow(new CommitTrx(gameProposalCreateTrx, command.getCounterparty(gameProposal))); final View gameView = this.flowEngine .subFlow(new ViewBuilder(utxoTrxId)); @@ -78,7 +78,7 @@ public class CreateFlow implements ClientStartableFlow{ .toJsonEncodedString(jsonMarshallingService); } catch (Exception e) { - log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); + log.warn(requestBody + " [ERROR] " +e.toString()); return new FlowResponce(e, utxoTrxId) .toJsonEncodedString(jsonMarshallingService); } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/RejectFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/RejectFlow.java index 009f97a..0d8fabe 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/RejectFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/RejectFlow.java @@ -8,7 +8,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import djmil.cordacheckers.contracts.GameCommand; -import djmil.cordacheckers.gamestate.CommitSubFlow; +import djmil.cordacheckers.gamestate.CommitTrx; import djmil.cordacheckers.gamestate.FlowResponce; import djmil.cordacheckers.gamestate.GetFlow; import djmil.cordacheckers.gamestate.View; @@ -70,7 +70,7 @@ public class RejectFlow implements ClientStartableFlow{ .toSignedTransaction(); utxoTrxId = this.flowEngine - .subFlow(new CommitSubFlow(gameProposalRejectTrx, command.getCounterparty(gameProposal))); + .subFlow(new CommitTrx(gameProposalRejectTrx, command.getCounterparty(gameProposal))); final View gameStateView = this.flowEngine .subFlow(new ViewBuilder(utxoTrxId)); @@ -79,7 +79,7 @@ public class RejectFlow implements ClientStartableFlow{ .toJsonEncodedString(jsonMarshallingService); } catch (Exception e) { - log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); + log.warn(requestBody + " [ERROR] " +e.toString()); return new FlowResponce(e, utxoTrxId) .toJsonEncodedString(jsonMarshallingService); } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameresult/GameResultBuilder.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameresult/GameResultBuilder.java deleted file mode 100644 index 94e726c..0000000 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameresult/GameResultBuilder.java +++ /dev/null @@ -1,74 +0,0 @@ -package djmil.cordacheckers.gameresult; - -import djmil.cordacheckers.states.GameBoardState; -import djmil.cordacheckers.states.GameResultState; -import djmil.cordacheckers.states.GameState; -import net.corda.v5.application.flows.CordaInject; -import net.corda.v5.application.flows.SubFlow; -import net.corda.v5.application.membership.MemberLookup; -import net.corda.v5.base.annotations.Suspendable; -import net.corda.v5.base.types.MemberX500Name; -import net.corda.v5.ledger.utxo.StateAndRef; -import net.corda.v5.membership.MemberInfo; - -public class GameResultBuilder implements SubFlow { - public class Result { - final public GameResultState gameResult; - final public MemberX500Name custodyName; - - public Result(GameResultState gameResult, MemberX500Name custodyName){ - this.gameResult = gameResult; - this.custodyName = custodyName; - } - } - - public static enum Reason { - VICTORY, - SURRENDER - } - - private final GameBoardState gameBoard; - private final Reason reason; - - public GameResultBuilder(StateAndRef gameBoardSar, Reason reason) { - this.gameBoard = (GameBoardState)gameBoardSar.getState().getContractState(); - this.reason = reason; - } - - @CordaInject - public MemberLookup memberLookup; - - @Override - @Suspendable - public Result call() { - final MemberInfo custodiaInfo = findCustodian(); - - return new Result( - new GameResultState(gameBoard, winnerName(), custodiaInfo.getLedgerKeys().get(0)), - custodiaInfo.getName()); - } - - @Suspendable - MemberInfo findCustodian() { - return memberLookup.lookup() - .stream() - .filter(m -> m.getName().getOrganizationUnit().equals("Custodian") ) - .reduce((a,b) -> {throw new IllegalStateException("Multiple Custodian VNodes");}) - .orElseThrow( () -> new IllegalStateException("No Custodian VNode found")); - } - - @Suspendable - MemberX500Name winnerName() { - final MemberX500Name myName = memberLookup.myInfo().getName(); - - switch(reason) { - case VICTORY: - return myName; - case SURRENDER: - return gameBoard.getOpponentName(myName); - } - - throw new IllegalStateException("Bad reason"); - } - -} diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameresult/GameResultCommiter.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameresult/GameResultCommiter.java new file mode 100644 index 0000000..fe35a53 --- /dev/null +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameresult/GameResultCommiter.java @@ -0,0 +1,99 @@ +package djmil.cordacheckers.gameresult; + +import java.security.PublicKey; +import java.time.Duration; +import java.time.Instant; +import java.util.UUID; + +import djmil.cordacheckers.contracts.GameCommand; +import djmil.cordacheckers.gamestate.CommitTrx; +import djmil.cordacheckers.gamestate.GetFlow; +import djmil.cordacheckers.states.GameBoardState; +import djmil.cordacheckers.states.GameResultState; +import djmil.cordacheckers.states.GameState; +import net.corda.v5.application.flows.CordaInject; +import net.corda.v5.application.flows.FlowEngine; +import net.corda.v5.application.flows.SubFlow; +import net.corda.v5.application.membership.MemberLookup; +import net.corda.v5.base.annotations.Suspendable; +import net.corda.v5.base.types.MemberX500Name; +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; +import net.corda.v5.membership.MemberInfo; + +public class GameResultCommiter implements SubFlow { + + private final UUID gameUuid; + private final GameCommand command; + + public GameResultCommiter(UUID gameUuid, GameCommand command) { + this.gameUuid = gameUuid; + this.command = command; + } + + @CordaInject + public FlowEngine flowEngine; + + @CordaInject + public MemberLookup memberLookup; + + @CordaInject + public UtxoLedgerService utxoLedgerService; + + @Override + @Suspendable + public SecureHash call() { + final MemberInfo custodianInfo = findCustodian(); + + final StateAndRef gameBoardSar = this.flowEngine + .subFlow(new GetFlow(this.gameUuid)); + final GameBoardState gameBoard = (GameBoardState)gameBoardSar.getState().getContractState(); + + final GameResultState gameResult = gameResultBuilder(gameBoard, custodianInfo); + + final UtxoSignedTransaction gameResultTrx = utxoLedgerService.createTransactionBuilder() + .addCommand(this.command) + .addInputState(gameBoardSar.getRef()) + .addOutputState(gameResult) + .addSignatories(gameResult.getParticipants()) + .setNotary(gameBoardSar.getState().getNotaryName()) + .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) + .toSignedTransaction(); + + return this.flowEngine.subFlow( + new CommitTrx(gameResultTrx, + command.getCounterparty(gameResult), + custodianInfo.getName()) ); + } + + @Suspendable + MemberInfo findCustodian() { + return memberLookup.lookup() + .stream() + .filter(m -> m.getName().getOrganizationUnit().equals("Custodian") ) + .reduce((a,b) -> {throw new IllegalStateException("Multiple Custodian VNodes");}) + .orElseThrow( () -> new IllegalStateException("No Custodian VNode found")); + } + + @Suspendable + GameResultState gameResultBuilder(GameBoardState gameBoard, MemberInfo custodiaInfo) { + final PublicKey custodianPublicKey = custodiaInfo.getLedgerKeys().get(0); + final MemberX500Name myName = memberLookup.myInfo().getName(); + + switch(this.command.getAction()) { + case GAME_BOARD_VICTORY: + return new GameResultState(myName, // i'm a winner + gameBoard, custodianPublicKey); + + case GAME_BOARD_SURRENDER: + return new GameResultState(gameBoard.getOpponentName(myName), // me surrender to + gameBoard, custodianPublicKey); + + default: + throw new IllegalStateException("GameResult: bad reason"); + } + } + +} diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitSubFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitTrx.java similarity index 88% rename from corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitSubFlow.java rename to corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitTrx.java index 72ffc11..7141c5e 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitSubFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitTrx.java @@ -20,20 +20,20 @@ import net.corda.v5.ledger.utxo.UtxoLedgerService; import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; @InitiatingFlow(protocol = "gamestate-commit") -public class CommitSubFlow implements SubFlow { +public class CommitTrx implements SubFlow { - private final static Logger log = LoggerFactory.getLogger(CommitSubFlow.class); + private final static Logger log = LoggerFactory.getLogger(CommitTrx.class); private final UtxoSignedTransaction utxTrxCandidate; private final MemberX500Name counterpartyName; private final MemberX500Name custodyName; - public CommitSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name counterpartyName) { + public CommitTrx(UtxoSignedTransaction signedTransaction, MemberX500Name counterpartyName) { this.utxTrxCandidate = signedTransaction; this.counterpartyName = counterpartyName; this.custodyName = null; } - public CommitSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name counterpartyName, MemberX500Name custodyName) { + public CommitTrx(UtxoSignedTransaction signedTransaction, MemberX500Name counterpartyName, MemberX500Name custodyName) { this.utxTrxCandidate = signedTransaction; this.counterpartyName = counterpartyName; this.custodyName = custodyName; diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitSubFlowResponder.java b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitTrxResponder.java similarity index 97% rename from corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitSubFlowResponder.java rename to corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitTrxResponder.java index 346c1e9..f6b37be 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitSubFlowResponder.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/CommitTrxResponder.java @@ -17,9 +17,9 @@ import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; @InitiatedBy(protocol = "gamestate-commit") -public class CommitSubFlowResponder implements ResponderFlow { +public class CommitTrxResponder implements ResponderFlow { - private final static Logger log = LoggerFactory.getLogger(CommitSubFlowResponder.class); + private final static Logger log = LoggerFactory.getLogger(CommitTrxResponder.class); @CordaInject public MemberLookup memberLookup; 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 c7813af..7359a34 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/FlowResponce.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/FlowResponce.java @@ -20,6 +20,12 @@ public class FlowResponce { this.failureStatus = exception.toString(); } + public FlowResponce(String exceptionMessage, SecureHash transactionId) { + this.successStatus = null; + this.transactionId = transactionId; + this.failureStatus = exceptionMessage; + } + public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) { return jsonMarshallingService.format(this); } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/GetFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/GetFlow.java index b9bc3b9..de1abbc 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/GetFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gamestate/GetFlow.java @@ -63,8 +63,9 @@ public class GetFlow implements ClientStartableFlow, SubFlow