Compare commits
	
		
			2 Commits
		
	
	
		
			bd6612f3e6
			...
			959ea0051d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 959ea0051d | |||
| dc702f7584 | 
| @ -14,31 +14,21 @@ import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.web.client.RestTemplate; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.JsonMappingException; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.GameState; | ||||
| import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; | ||||
| import djmil.cordacheckers.cordaclient.dao.Piece; | ||||
| import djmil.cordacheckers.cordaclient.dao.GameBoard; | ||||
| import djmil.cordacheckers.cordaclient.dao.GameBoardCommand; | ||||
| import djmil.cordacheckers.cordaclient.dao.GameProposal; | ||||
| import djmil.cordacheckers.cordaclient.dao.GameResult; | ||||
| import djmil.cordacheckers.cordaclient.dao.VirtualNode; | ||||
| import djmil.cordacheckers.cordaclient.dao.VirtualNodeList; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.RequestBody; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateRes; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalListRes; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardCommandReq; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardResGameResult; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardListRes; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardResGameBoard; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandAcceptRes; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandReq; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandRes; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.Req; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.Rsp; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameBoardMove; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameProposalCreate; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.RspGameState; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.RspGameStateList; | ||||
| 
 | ||||
| @Service | ||||
| public class CordaClient { | ||||
| @ -69,205 +59,110 @@ public class CordaClient { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Obtain list of unconsumed (active) GameProposals  | ||||
|      * @param myHoldingIdentity | ||||
|      * @return GameProposals list in JSON form | ||||
|      * @param holdingIdentity | ||||
|      * @return list of unconsumed (active) GameStateViews | ||||
|      */ | ||||
|     public List<GameProposal> gameProposalList(HoldingIdentity myHoldingIdentity) { | ||||
| 
 | ||||
|     public List<GameState> gameStateList(HoldingIdentity holdingIdentity) { | ||||
|         final RequestBody requestBody = new RequestBody( | ||||
|             "gp.list-" + UUID.randomUUID(), | ||||
|             "djmil.cordacheckers.gameproposal.ListFlow", | ||||
|             new Empty() | ||||
|             ); | ||||
|             "gs.list-" + UUID.randomUUID(), | ||||
|             "djmil.cordacheckers.gamestate.ListFlow", | ||||
|             new Req()); | ||||
| 
 | ||||
|         final GameProposalListRes listFlowResult = cordaFlowExecute( | ||||
|             myHoldingIdentity, | ||||
|             requestBody, | ||||
|             GameProposalListRes.class | ||||
|             ); | ||||
| 
 | ||||
|         if (listFlowResult.failureStatus() != null) { | ||||
|             System.out.println("GameProposalCreateFlow failed: " + listFlowResult.failureStatus()); | ||||
|             throw new RuntimeException("GameProsal: CreateFlow execution has failed"); | ||||
|         return cordaFlowExecute(holdingIdentity, requestBody, RspGameStateList.class) | ||||
|             .getResponce(requestBody); | ||||
|     } | ||||
| 
 | ||||
|         return listFlowResult.successStatus(); | ||||
|     public GameState gameStateGet(HoldingIdentity holdingIdentity, UUID gameStateUuid) { | ||||
|         final RequestBody requestBody = new RequestBody( | ||||
|             "gs.get-" + UUID.randomUUID(), | ||||
|             "djmil.cordacheckers.gamestate.GetFlow", | ||||
|             gameStateUuid); | ||||
| 
 | ||||
|         return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) | ||||
|             .getResponce(requestBody); | ||||
|     } | ||||
| 
 | ||||
|     public UUID gameProposalCreate( | ||||
|     public GameState gameProposalCreate( | ||||
|         HoldingIdentity issuer, | ||||
|         HoldingIdentity acquier, | ||||
|         Piece.Color acquierColor, | ||||
|         String message | ||||
|     ) throws JsonMappingException, JsonProcessingException { | ||||
|     ) { | ||||
|         final RequestBody requestBody = new RequestBody( | ||||
|             "gp.create-" + UUID.randomUUID(), | ||||
|             "djmil.cordacheckers.gameproposal.CreateFlow", | ||||
|             new GameProposalCreateReq( | ||||
|             new ReqGameProposalCreate( | ||||
|                 acquier.x500Name(), | ||||
|                 acquierColor, | ||||
|                 message | ||||
|                 ) | ||||
|             ); | ||||
|                 message)); | ||||
| 
 | ||||
|         final GameProposalCreateRes createResult = cordaFlowExecute( | ||||
|             issuer, | ||||
|             requestBody, | ||||
|             GameProposalCreateRes.class | ||||
|             ); | ||||
| 
 | ||||
|         if (createResult.failureStatus() != null) { | ||||
|             System.out.println("GameProposal.CreateFlow failed: " + createResult.failureStatus()); | ||||
|             throw new RuntimeException("GameProsal: CreateFlow execution has failed"); | ||||
|         return cordaFlowExecute(issuer, requestBody,RspGameState.class) | ||||
|             .getResponce(requestBody); | ||||
|     } | ||||
| 
 | ||||
|         return createResult.successStatus(); | ||||
|     } | ||||
| 
 | ||||
|     public String gameProposalReject( | ||||
|         HoldingIdentity myHoldingIdentity, | ||||
|         UUID gameProposalUuid | ||||
|     ) { | ||||
|     public GameState gameProposalReject(HoldingIdentity holdingIdentity, UUID gameProposalUuid) { | ||||
|         final RequestBody requestBody = new RequestBody( | ||||
|             "gp.reject-" +UUID.randomUUID(), | ||||
|             "djmil.cordacheckers.gameproposal.CommandFlow", | ||||
|             new GameProposalCommandReq( | ||||
|                 gameProposalUuid.toString(), | ||||
|                 GameProposalCommandReq.Command.REJECT | ||||
|                 ) | ||||
|             ); | ||||
|             "djmil.cordacheckers.gameproposal.RejectFlow", | ||||
|             gameProposalUuid); | ||||
| 
 | ||||
|         final GameProposalCommandRes actionResult = cordaFlowExecute( | ||||
|             myHoldingIdentity, | ||||
|             requestBody, | ||||
|             GameProposalCommandRes.class | ||||
|             ); | ||||
| 
 | ||||
|         if (actionResult.failureStatus() != null) { | ||||
|             System.out.println("GameProposal.CommandFlow failed: " + actionResult.failureStatus()); | ||||
|             throw new RuntimeException("GameProsal: CommandFlow execution has failed"); | ||||
|         return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) | ||||
|             .getResponce(requestBody); | ||||
|     } | ||||
| 
 | ||||
|         return actionResult.successStatus(); | ||||
|     public GameState gameProposalCancel(HoldingIdentity holdingIdentity, UUID gameProposalUuid) { | ||||
|         final RequestBody requestBody = new RequestBody( | ||||
|             "gp.reject-" +UUID.randomUUID(), | ||||
|             "djmil.cordacheckers.gameproposal.CancelFlow", | ||||
|             gameProposalUuid); | ||||
| 
 | ||||
|         return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) | ||||
|             .getResponce(requestBody); | ||||
|     } | ||||
| 
 | ||||
|     public GameBoard gameProposalAccept( | ||||
|         HoldingIdentity myHoldingIdentity, | ||||
|         UUID gameProposalUuid | ||||
|     ) { | ||||
|     public GameState gameProposalAccept(HoldingIdentity holdingIdentity, UUID gameProposalUuid) { | ||||
|         final RequestBody requestBody = new RequestBody( | ||||
|             "gp.accept-" +UUID.randomUUID(), | ||||
|             "djmil.cordacheckers.gameproposal.CommandFlow", | ||||
|             new GameProposalCommandReq( | ||||
|                 gameProposalUuid.toString(), | ||||
|                 GameProposalCommandReq.Command.ACCEPT | ||||
|                 ) | ||||
|             ); | ||||
|             "djmil.cordacheckers.gameproposal.AcceptFlow", | ||||
|             gameProposalUuid); | ||||
| 
 | ||||
|         final GameProposalCommandAcceptRes actionResult = cordaFlowExecute( | ||||
|             myHoldingIdentity, | ||||
|             requestBody, | ||||
|             GameProposalCommandAcceptRes.class | ||||
|             ); | ||||
| 
 | ||||
|         if (actionResult.failureStatus() != null) { | ||||
|             System.out.println("GameProposal.CommandFlow failed: " + actionResult.failureStatus()); | ||||
|             throw new RuntimeException("GameProsal: CommandFlow execution has failed"); | ||||
|         return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) | ||||
|             .getResponce(requestBody); | ||||
|     } | ||||
| 
 | ||||
|         return actionResult.successStatus(); | ||||
|     } | ||||
| 
 | ||||
|     public List<GameBoard> gameBoardList(HoldingIdentity holdingIdentity) { | ||||
| 
 | ||||
|         final RequestBody requestBody = new RequestBody( | ||||
|             "gb.list-" + UUID.randomUUID(), | ||||
|             "djmil.cordacheckers.gameboard.ListFlow", | ||||
|             new Empty() | ||||
|             ); | ||||
| 
 | ||||
|         final GameBoardListRes listFlowResult = cordaFlowExecute( | ||||
|             holdingIdentity, | ||||
|             requestBody, | ||||
|             GameBoardListRes.class | ||||
|             ); | ||||
| 
 | ||||
|         if (listFlowResult.failureStatus() != null) { | ||||
|             System.out.println("GameBoard.ListFlow failed: " + listFlowResult.failureStatus()); | ||||
|             throw new RuntimeException("GameBoard: ListFlow execution has failed"); | ||||
|         } | ||||
| 
 | ||||
|         return listFlowResult.successStatus(); | ||||
|     } | ||||
| 
 | ||||
|     public GameResult gameBoardSurrender( | ||||
|         HoldingIdentity myHoldingIdentity, | ||||
|         UUID gameBoardUuid | ||||
|     ) { | ||||
|     public GameState gameBoardSurrender(HoldingIdentity holdingIdentity, UUID gameBoardUuid) { | ||||
|         final RequestBody requestBody = new RequestBody( | ||||
|             "gb.surrender-" +UUID.randomUUID(), | ||||
|             "djmil.cordacheckers.gameboard.CommandFlow", | ||||
|             new GameBoardCommandReq( | ||||
|                 gameBoardUuid, | ||||
|                 new GameBoardCommand(GameBoardCommand.Type.SURRENDER) | ||||
|                 ) | ||||
|             ); | ||||
|             "djmil.cordacheckers.gameboard.SurrenderFlow", | ||||
|             gameBoardUuid); | ||||
| 
 | ||||
|         final GameBoardResGameResult moveResult = cordaFlowExecute( | ||||
|             myHoldingIdentity, | ||||
|             requestBody, | ||||
|             GameBoardResGameResult.class  | ||||
|             ); | ||||
| 
 | ||||
|         if (moveResult.failureStatus() != null) { | ||||
|             System.out.println("GameBoard.CommandFlow failed: " + moveResult.failureStatus()); | ||||
|             throw new RuntimeException("GameBoard: CommandFlow execution has failed"); | ||||
|         return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) | ||||
|             .getResponce(requestBody); | ||||
|     } | ||||
| 
 | ||||
|         return moveResult.successStatus(); | ||||
|     } | ||||
| 
 | ||||
|      public GameBoard gameBoardMove( | ||||
|         HoldingIdentity myHoldingIdentity, | ||||
|         UUID gameBoardUuid, | ||||
|         List<Integer> move | ||||
|     ) { | ||||
|     public GameState gameBoardMove(HoldingIdentity holdingIdentity, UUID gameBoardUuid, List<Integer> move) { | ||||
|         final RequestBody requestBody = new RequestBody( | ||||
|             "gb.move-" +UUID.randomUUID(), | ||||
|             "djmil.cordacheckers.gameboard.CommandFlow", | ||||
|             new GameBoardCommandReq( | ||||
|             new ReqGameBoardMove( | ||||
|                 gameBoardUuid, | ||||
|                 new GameBoardCommand(move)) | ||||
|             ); | ||||
|                 move)); | ||||
| 
 | ||||
|         final GameBoardResGameBoard moveResult = cordaFlowExecute( | ||||
|             myHoldingIdentity, | ||||
|             requestBody, | ||||
|             GameBoardResGameBoard.class  | ||||
|             ); | ||||
| 
 | ||||
|         if (moveResult.failureStatus() != null) { | ||||
|             System.out.println("GameBoard.CommandFlow failed: " + moveResult.failureStatus()); | ||||
|             throw new RuntimeException("GameBoard: CommandFlow execution has failed"); | ||||
|         return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) | ||||
|             .getResponce(requestBody); | ||||
|     } | ||||
| 
 | ||||
|         return moveResult.successStatus(); | ||||
|     } | ||||
| 
 | ||||
|     private <T> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowResultType) { | ||||
| 
 | ||||
|     private <T extends Rsp<?>> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowReponceType) { | ||||
|         try { | ||||
|             final String requestBodyJson = this.jsonMapper.writeValueAsString(requestBody); | ||||
|             final String requestBodyJson = this.jsonMapper | ||||
|                 .writeValueAsString(requestBody); | ||||
| 
 | ||||
|             final ResponseBody startedFlow = cordaFlowPost( | ||||
|                 holdingIdentity, | ||||
|                 requestBodyJson | ||||
|             ); | ||||
|             final ResponseBody startedFlow = cordaFlowPost(holdingIdentity, requestBodyJson); | ||||
| 
 | ||||
|             final String flowResult = cordaFlowPoll(startedFlow); | ||||
|             final String flowResponce = cordaFlowPoll(startedFlow); | ||||
| 
 | ||||
|             return this.jsonMapper.readValue(flowResult, flowResultType); | ||||
|             return this.jsonMapper | ||||
|                 .readValue(flowResponce, flowReponceType); | ||||
|         }  | ||||
|         catch (Exception e) { | ||||
|             throw new RuntimeException("Unable to perform "+requestBody.flowClassName() | ||||
| @ -335,7 +230,6 @@ public class CordaClient { | ||||
|             } | ||||
| 
 | ||||
|             if (responseBody.isFlowCompleted() && responseBody.flowResult() != null) { | ||||
|                 System.out.println("resp: "+ responseBody.flowResult()); | ||||
|                 return responseBody.flowResult(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -1,21 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import com.fasterxml.jackson.databind.annotation.JsonDeserialize; | ||||
| 
 | ||||
| @JsonDeserialize | ||||
| public record GameBoard( | ||||
|     String      opponentName, | ||||
|     Piece.Color opponentColor, | ||||
|     Boolean     opponentMove, | ||||
|     Integer     moveNumber, | ||||
|     Map<Integer, Piece> board, | ||||
|     GameBoardCommand previousCommand, | ||||
|     String message, | ||||
|     UUID gameUuid)  | ||||
| 
 | ||||
| implements GameState {  | ||||
| 
 | ||||
| } | ||||
| @ -1,46 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class GameBoardCommand { | ||||
|     public static enum Type { | ||||
|         MOVE, | ||||
|         SURRENDER, | ||||
|         VICTORY; | ||||
|     } | ||||
| 
 | ||||
|     private final Type type; | ||||
|     private final List<Integer> move; // [0] from, [1] to | ||||
|      | ||||
|     public GameBoardCommand() { | ||||
|         this.type = null; | ||||
|         this.move = null; | ||||
|     } | ||||
| 
 | ||||
|     public GameBoardCommand(Type type) { | ||||
|         this.type = type; | ||||
|         this.move = null; | ||||
|     } | ||||
| 
 | ||||
|     public GameBoardCommand(List<Integer> move) { | ||||
|         this.type = Type.MOVE; | ||||
|         this.move = move; | ||||
|     } | ||||
| 
 | ||||
|     public Type getType() { | ||||
|         return type; | ||||
|     } | ||||
| 
 | ||||
|     public List<Integer> getMove() { | ||||
|         return move; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         if (type == Type.MOVE)  | ||||
|             return move.get(0) +"->" +move.get(1); | ||||
|         else | ||||
|             return type.name(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,17 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import com.fasterxml.jackson.databind.annotation.JsonDeserialize; | ||||
| 
 | ||||
| @JsonDeserialize | ||||
| public record GameProposal( | ||||
|     String issuer, | ||||
|     String acquier, | ||||
|     Piece.Color acquierColor, | ||||
|     String message, | ||||
|     UUID gameUuid)  | ||||
|      | ||||
| implements GameState { | ||||
|      | ||||
| } | ||||
| @ -1,16 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import com.fasterxml.jackson.databind.annotation.JsonDeserialize; | ||||
| 
 | ||||
| @JsonDeserialize | ||||
| public record GameResult( | ||||
|     String whitePlayerName, | ||||
|     String blackPlayerName, | ||||
|     Piece.Color victoryColor, | ||||
|     UUID gameUuid) | ||||
| 
 | ||||
| implements GameState {  | ||||
| 
 | ||||
| } | ||||
| @ -1,7 +1,30 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| public interface GameState { | ||||
|     public UUID gameUuid(); | ||||
| public record GameState( | ||||
|     Status status, | ||||
|     String opponentName, | ||||
|     Piece.Color opponentColor, | ||||
|      | ||||
|     Map<Integer, Piece> board, | ||||
|     Integer moveNumber, | ||||
|     List<Integer> previousMove, | ||||
| 
 | ||||
|     String message, | ||||
|     UUID uuid | ||||
| ) { | ||||
|     public static enum Status { | ||||
|         GAME_PROPOSAL_WAIT_FOR_OPPONENT, | ||||
|         GAME_PROPOSAL_WAIT_FOR_YOU, | ||||
|         GAME_PROPOSAL_REJECTED, | ||||
|         GAME_PROPOSAL_CANCELED, | ||||
|         GAME_BOARD_WAIT_FOR_OPPONENT, | ||||
|         GAME_BOARD_WAIT_FOR_YOU, | ||||
|         GAME_RESULT_YOU_WON, | ||||
|         GAME_RESULT_YOU_LOOSE; | ||||
|     } | ||||
|      | ||||
| } | ||||
|  | ||||
| @ -4,12 +4,16 @@ public class Piece { | ||||
|      | ||||
|     public enum Type { | ||||
|         MAN, | ||||
|         KING, | ||||
|         KING; | ||||
|     } | ||||
| 
 | ||||
|     public enum Color { | ||||
|         WHITE, | ||||
|         BLACK, | ||||
|         BLACK; | ||||
| 
 | ||||
|         public Color opposite() { | ||||
|             return this == WHITE ? BLACK : WHITE; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     Color color; | ||||
|  | ||||
| @ -1,8 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| import djmil.cordacheckers.cordaclient.dao.GameBoardCommand; | ||||
| 
 | ||||
| public record GameBoardCommandReq(UUID gameBoardUuid, GameBoardCommand command) { | ||||
| 
 | ||||
| } | ||||
| @ -1,12 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.GameBoard; | ||||
| 
 | ||||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||||
| public record GameBoardListRes(List<GameBoard> successStatus, String failureStatus) { | ||||
|      | ||||
| } | ||||
| @ -1,7 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.GameBoard; | ||||
| 
 | ||||
| public record GameBoardResGameBoard(GameBoard successStatus, String failureStatus) { | ||||
| 
 | ||||
| } | ||||
| @ -1,7 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.GameResult; | ||||
| 
 | ||||
| public record GameBoardResGameResult(GameResult successStatus, String failureStatus) { | ||||
| 
 | ||||
| } | ||||
| @ -1,7 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.GameBoard; | ||||
| 
 | ||||
| public record GameProposalCommandAcceptRes(GameBoard successStatus, String transactionId, String failureStatus) { | ||||
|      | ||||
| } | ||||
| @ -1,9 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| public record GameProposalCommandReq(String gameProposalUuid, Command command) { | ||||
|     public enum Command { | ||||
|         ACCEPT, | ||||
|         REJECT, | ||||
|         CANCEL | ||||
|     } | ||||
| } | ||||
| @ -1,5 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| public record GameProposalCommandRes(String successStatus, String transactionId, String failureStatus) { | ||||
|      | ||||
| } | ||||
| @ -1,10 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||
| 
 | ||||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||||
| public record GameProposalCreateRes(UUID successStatus, String failureStatus) { | ||||
|      | ||||
| } | ||||
| @ -1,12 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.GameProposal; | ||||
| 
 | ||||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||||
| public record GameProposalListRes(List<GameProposal> successStatus, String failureStatus) { | ||||
|      | ||||
| } | ||||
| @ -3,6 +3,9 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| import com.fasterxml.jackson.databind.annotation.JsonSerialize; | ||||
| 
 | ||||
| @JsonSerialize | ||||
| public class Empty { | ||||
| /** | ||||
|  * Representation of a request without arguments | ||||
|  */ | ||||
| public class Req { | ||||
|      | ||||
| } | ||||
| @ -0,0 +1,11 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| public record ReqGameBoardMove( | ||||
|     UUID gameBoardUuid, | ||||
|     List<Integer> move | ||||
| ) { | ||||
|      | ||||
| } | ||||
| @ -2,6 +2,10 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.Piece; | ||||
| 
 | ||||
| public record GameProposalCreateReq(String opponentName, Piece.Color opponentColor, String message) { | ||||
| public record ReqGameProposalCreate( | ||||
|     String opponentName, | ||||
|     Piece.Color opponentColor, | ||||
|     String message | ||||
| ) { | ||||
|      | ||||
| } | ||||
| @ -0,0 +1,19 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.RequestBody; | ||||
| 
 | ||||
| public interface Rsp <T> { | ||||
|     public T successStatus(); | ||||
|     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 +". Reson " +failureStatus()); | ||||
|             throw new RuntimeException(msg); | ||||
|         } | ||||
| 
 | ||||
|         return successStatus(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| 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<GameState> { | ||||
|      | ||||
|     @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(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,11 @@ | ||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.GameState; | ||||
| 
 | ||||
| public record RspGameStateList( | ||||
|     List<GameState> successStatus, | ||||
|     String failureStatus) implements Rsp<List<GameState>> { | ||||
| 
 | ||||
| } | ||||
| @ -2,12 +2,14 @@ package djmil.cordacheckers.gameproposal; | ||||
| 
 | ||||
| import java.net.URI; | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestBody; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| import org.springframework.web.util.UriComponentsBuilder; | ||||
| 
 | ||||
| @ -15,17 +17,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.JsonMappingException; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.CordaClient; | ||||
| import djmil.cordacheckers.cordaclient.dao.GameState; | ||||
| import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq; | ||||
| import djmil.cordacheckers.cordaclient.dao.Piece; | ||||
| import djmil.cordacheckers.cordaclient.dao.GameProposal; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameProposalCreate; | ||||
| import djmil.cordacheckers.user.HoldingIdentityResolver; | ||||
| import djmil.cordacheckers.user.User; | ||||
| 
 | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestBody; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| 
 | ||||
| 
 | ||||
| @RestController | ||||
| @RequestMapping("gameproposal") | ||||
| @ -41,9 +39,9 @@ public class GameProposalController { | ||||
|     public ResponseEntity<String> findAllUnconsumed( | ||||
|         @AuthenticationPrincipal User player | ||||
|     ) { | ||||
|         List<GameProposal> gpList = cordaClient.gameProposalList(player.getHoldingIdentity()); | ||||
|         List<GameState> gsList = cordaClient.gameStateList(player.getHoldingIdentity()); | ||||
| 
 | ||||
|         return ResponseEntity.ok(gpList.toString()); | ||||
|         return ResponseEntity.ok(gsList.toString()); | ||||
|     } | ||||
| 
 | ||||
|     // @PostMapping() | ||||
| @ -55,7 +53,7 @@ public class GameProposalController { | ||||
|     @PostMapping() | ||||
|     public ResponseEntity<Void> createGameProposal( | ||||
|         @AuthenticationPrincipal User sender, | ||||
|         @RequestBody GameProposalCreateReq gpRequest, | ||||
|         @RequestBody ReqGameProposalCreate gpRequest, | ||||
|         UriComponentsBuilder ucb | ||||
|     ) throws JsonMappingException, JsonProcessingException { | ||||
|         final HoldingIdentity gpSender = sender.getHoldingIdentity(); | ||||
| @ -64,7 +62,7 @@ public class GameProposalController { | ||||
|         final Piece.Color gpReceiverColor = gpRequest.opponentColor(); | ||||
| 
 | ||||
|         // TODO handle expectionns here | ||||
|         UUID newGameProposalUuid = cordaClient.gameProposalCreate( | ||||
|         GameState gameStateView = cordaClient.gameProposalCreate( | ||||
|             gpSender, | ||||
|             gpReceiver, | ||||
|             gpReceiverColor, | ||||
| @ -73,7 +71,7 @@ public class GameProposalController { | ||||
| 
 | ||||
|         URI locationOfNewGameProposal = ucb | ||||
|             .path("gameproposal/{id}") | ||||
|             .buildAndExpand(newGameProposalUuid) | ||||
|             .buildAndExpand(gameStateView) | ||||
|             .toUri(); | ||||
| 
 | ||||
|         return ResponseEntity | ||||
|  | ||||
| @ -1,255 +0,0 @@ | ||||
| package djmil.cordacheckers.cordaclient; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import javax.naming.InvalidNameException; | ||||
| 
 | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.JsonMappingException; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.GameState; | ||||
| import djmil.cordacheckers.cordaclient.dao.GameBoard; | ||||
| import djmil.cordacheckers.cordaclient.dao.GameProposal; | ||||
| import djmil.cordacheckers.cordaclient.dao.GameResult; | ||||
| import djmil.cordacheckers.cordaclient.dao.Piece; | ||||
| import djmil.cordacheckers.cordaclient.dao.VirtualNode; | ||||
| import djmil.cordacheckers.user.HoldingIdentityResolver; | ||||
| 
 | ||||
| @SpringBootTest | ||||
| public class CordaClientTest { | ||||
|     @Autowired | ||||
|     CordaClient cordaClient; | ||||
| 
 | ||||
|     @Autowired | ||||
|     HoldingIdentityResolver holdingIdentityResolver; | ||||
| 
 | ||||
|     @Test | ||||
|     void testGetVirtualNodeList() throws InvalidNameException { | ||||
|         List<VirtualNode> vNodes = cordaClient.getVirtualNodeList(); | ||||
| 
 | ||||
| 		assertThat(vNodes.size()).isEqualTo(5); // default vNode config for CSDE | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testGameProposalList() throws JsonProcessingException { | ||||
|         List<GameProposal> gpList = cordaClient.gameProposalList( | ||||
|             holdingIdentityResolver.getByUsername("Alice")); | ||||
| 
 | ||||
|         System.out.println("testListGameProposals\n"+ gpList); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testGameProposalCreate() throws JsonMappingException, JsonProcessingException { | ||||
|         final String gpIssuer = "alice"; | ||||
|         final String gpAcquier = "bob"; | ||||
|         final Piece.Color gpAcquierColor = Piece.Color.WHITE; | ||||
|         final String gpMessage = "GameProposal create test"; | ||||
| 
 | ||||
|         final UUID createdGpUuid = cordaClient.gameProposalCreate( | ||||
|             holdingIdentityResolver.getByUsername(gpIssuer), | ||||
|             holdingIdentityResolver.getByUsername(gpAcquier), | ||||
|             gpAcquierColor, | ||||
|             gpMessage); | ||||
| 
 | ||||
|         List<GameProposal> gpListSender = cordaClient.gameProposalList( | ||||
|             holdingIdentityResolver.getByUsername(gpIssuer)); | ||||
| 
 | ||||
|         assertThat(findByUuid(gpListSender, createdGpUuid)).isNotNull(); | ||||
| 
 | ||||
|         List<GameProposal> gpListReceiver = cordaClient.gameProposalList( | ||||
|             holdingIdentityResolver.getByUsername(gpAcquier)); | ||||
| 
 | ||||
|         GameProposal gp; | ||||
|         assertThat(gp = findByUuid(gpListReceiver, createdGpUuid)).isNotNull(); | ||||
| 
 | ||||
|         assertThat(gp.acquier()).isEqualToIgnoringCase(gpAcquier); | ||||
|         assertThat(gp.issuer()).isEqualToIgnoringCase(gpIssuer); | ||||
|         assertThat(gp.acquierColor()).isEqualByComparingTo(gpAcquierColor); | ||||
|         assertThat(gp.message()).isEqualTo(gpMessage); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testGameProposalReject() throws JsonMappingException, JsonProcessingException { | ||||
|         final String gpIssuer = "alice"; | ||||
|         final String gpAcquier = "bob"; | ||||
|         final Piece.Color gpReceiverColor = Piece.Color.WHITE; | ||||
|         final String gpMessage = "GameProposal REJECT test"; | ||||
| 
 | ||||
|         final UUID gpUuid = cordaClient.gameProposalCreate( | ||||
|             holdingIdentityResolver.getByUsername(gpIssuer), | ||||
|             holdingIdentityResolver.getByUsername(gpAcquier), | ||||
|             gpReceiverColor, | ||||
|             gpMessage); | ||||
| 
 | ||||
|         System.out.println("Create GP UUID "+ gpUuid); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> {  // Issuer can not reject | ||||
|             cordaClient.gameProposalReject( | ||||
|                 holdingIdentityResolver.getByUsername(gpIssuer), | ||||
|                 gpUuid); | ||||
|         }); | ||||
| 
 | ||||
|         final String rejectRes = cordaClient.gameProposalReject( | ||||
|             holdingIdentityResolver.getByUsername(gpAcquier), | ||||
|             gpUuid); | ||||
| 
 | ||||
|         assertThat(rejectRes).isEqualToIgnoringCase("REJECTED"); | ||||
| 
 | ||||
|         List<GameProposal> gpListSender = cordaClient.gameProposalList( | ||||
|             holdingIdentityResolver.getByUsername(gpIssuer)); | ||||
| 
 | ||||
|         assertThat(findByUuid(gpListSender, gpUuid)).isNull(); | ||||
| 
 | ||||
|         List<GameProposal> gpListReceiver = cordaClient.gameProposalList( | ||||
|             holdingIdentityResolver.getByUsername(gpAcquier)); | ||||
| 
 | ||||
|         assertThat(findByUuid(gpListReceiver, gpUuid)).isNull(); | ||||
| 
 | ||||
|         // GameProposal can not be rejected twice  | ||||
|         assertThatThrownBy(() -> { | ||||
|             cordaClient.gameProposalReject( | ||||
|                 holdingIdentityResolver.getByUsername(gpIssuer), | ||||
|                 gpUuid); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testGameProposalAccept() throws JsonMappingException, JsonProcessingException { | ||||
|         final String gpIssuer = "alice"; | ||||
|         final String gpAcquier = "bob"; | ||||
|         final Piece.Color gpAcquierColor = Piece.Color.WHITE; | ||||
|         final String gpMessage = "GameProposal ACCEPT test"; | ||||
| 
 | ||||
|         final UUID gpUuid = cordaClient.gameProposalCreate( | ||||
|             holdingIdentityResolver.getByUsername(gpIssuer), | ||||
|             holdingIdentityResolver.getByUsername(gpAcquier), | ||||
|             gpAcquierColor, | ||||
|             gpMessage | ||||
|         ); | ||||
| 
 | ||||
|         System.out.println("New GameProposal UUID "+ gpUuid); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> { // Issuer can not accept | ||||
|             cordaClient.gameProposalAccept( | ||||
|                 holdingIdentityResolver.getByUsername(gpIssuer), | ||||
|                 gpUuid); | ||||
|         }); | ||||
| 
 | ||||
|         final GameBoard gbState = cordaClient.gameProposalAccept( | ||||
|             holdingIdentityResolver.getByUsername(gpAcquier), | ||||
|             gpUuid); | ||||
| 
 | ||||
|         System.out.println("New GameBoard UUID "+gbState); | ||||
| 
 | ||||
|         List<GameBoard> gbListIssuer = cordaClient.gameBoardList( | ||||
|             holdingIdentityResolver.getByUsername(gpIssuer)); | ||||
| 
 | ||||
|         GameBoard gbAlice = findByUuid(gbListIssuer, gbState.gameUuid()); | ||||
|         assertThat(gbAlice).isNotNull(); | ||||
|         assertThat(gbAlice.opponentName()).isEqualToIgnoringCase(gpAcquier); | ||||
|         assertThat(gbAlice.opponentColor()).isEqualByComparingTo(gpAcquierColor); | ||||
|         assertThat(gbAlice.opponentMove()).isEqualTo(true); | ||||
| 
 | ||||
| 
 | ||||
|         List<GameBoard> gbListAcquier = cordaClient.gameBoardList( | ||||
|         holdingIdentityResolver.getByUsername(gpAcquier)); | ||||
| 
 | ||||
|         GameBoard bgBob = findByUuid(gbListAcquier, gbState.gameUuid()); | ||||
|         assertThat(bgBob).isNotNull(); | ||||
|         assertThat(bgBob.opponentName()).isEqualToIgnoringCase(gpIssuer); | ||||
|         assertThat(bgBob.opponentColor()).isEqualByComparingTo(Piece.Color.BLACK); | ||||
|         assertThat(bgBob.opponentMove()).isEqualTo(false); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> {   // GameProposal can not be accepted twice  | ||||
|             cordaClient.gameProposalAccept( | ||||
|                 holdingIdentityResolver.getByUsername(gpAcquier), | ||||
|                 gpUuid); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testGameBoardList() throws JsonMappingException, JsonProcessingException { | ||||
|         List <GameBoard> gbList = cordaClient.gameBoardList( | ||||
|             holdingIdentityResolver.getByUsername("bob")); | ||||
| 
 | ||||
|         System.out.println("GB list: " +gbList); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testGameBoardSurrender() throws JsonMappingException, JsonProcessingException, InvalidNameException { | ||||
|         final var hiAlice = holdingIdentityResolver.getByUsername("alice"); | ||||
|         final var hiBob   = holdingIdentityResolver.getByUsername("bob"); | ||||
|         final var bobColor = Piece.Color.WHITE; | ||||
| 
 | ||||
|         final UUID gpUuid = cordaClient.gameProposalCreate( | ||||
|             hiAlice, hiBob, | ||||
|             bobColor, "GameBoard SURRENDER test" | ||||
|         ); | ||||
| 
 | ||||
|         System.out.println("New GameProposal UUID "+ gpUuid); | ||||
| 
 | ||||
|         final GameBoard gbState = cordaClient.gameProposalAccept( | ||||
|             hiBob, gpUuid | ||||
|         ); | ||||
| 
 | ||||
|         System.out.println("New GameBoard UUID "+ gbState.gameUuid()); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> { // Alice can not surrender, since it is Bob's turn | ||||
|             cordaClient.gameBoardSurrender( | ||||
|                 hiAlice, gbState.gameUuid()); | ||||
|         }); | ||||
| 
 | ||||
|         final GameResult gameResult = cordaClient.gameBoardSurrender( | ||||
|             hiBob, gbState.gameUuid()); | ||||
| 
 | ||||
|         assertThat(gameResult.whitePlayerName()).isEqualTo(hiBob.getName()); | ||||
|         assertThat(gameResult.blackPlayerName()).isEqualTo(hiAlice.getName()); | ||||
|         assertThat(gameResult.victoryColor()).isEqualByComparingTo(Piece.Color.BLACK); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testGameBoardMove() throws JsonMappingException, JsonProcessingException, InvalidNameException { | ||||
|         final var hiAlice = holdingIdentityResolver.getByUsername("alice"); | ||||
|         final var hiBob   = holdingIdentityResolver.getByUsername("bob"); | ||||
|         final var bobColor = Piece.Color.WHITE; | ||||
| 
 | ||||
|         final UUID gpUuid = cordaClient.gameProposalCreate( | ||||
|             hiAlice, hiBob, | ||||
|             bobColor, "GameBoard MOVE test" | ||||
|         ); | ||||
| 
 | ||||
|         System.out.println("New GameProposal UUID "+ gpUuid); | ||||
| 
 | ||||
|         final GameBoard gbState = cordaClient.gameProposalAccept( | ||||
|             hiBob, gpUuid | ||||
|         ); | ||||
| 
 | ||||
|         System.out.println("New GameBoard UUID "+ gbState.gameUuid()); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> { // Alice can not move, since it is Bob's turn | ||||
|             cordaClient.gameBoardMove( | ||||
|                 hiAlice, gbState.gameUuid(), | ||||
|                 Arrays.asList(1, 2)); | ||||
|         }); | ||||
| 
 | ||||
|         final GameBoard gameBoard = cordaClient.gameBoardMove( | ||||
|             hiBob, gbState.gameUuid(), Arrays.asList(1, 2)); | ||||
|     } | ||||
| 
 | ||||
|     private <T extends GameState> T findByUuid(List<T> statesList, UUID uuid) { | ||||
|         for (T state : statesList) { | ||||
|             if (state.gameUuid().compareTo(uuid) == 0) | ||||
|                 return state; | ||||
|         }; | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,99 @@ | ||||
| package djmil.cordacheckers.cordaclient; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||||
| 
 | ||||
| import org.junit.jupiter.api.Test; | ||||
| 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.user.HoldingIdentityResolver; | ||||
| 
 | ||||
| @SpringBootTest | ||||
| public class GameBoardTests { | ||||
|     @Autowired | ||||
|     CordaClient cordaClient; | ||||
| 
 | ||||
|     @Autowired | ||||
|     HoldingIdentityResolver holdingIdentityResolver; | ||||
| 
 | ||||
|     final String whitePlayerName = "alice"; | ||||
|     final String blackPlayerName = "bob"; | ||||
| 
 | ||||
|     @Test | ||||
|     void testSurrender() { | ||||
|         final var hiWhite  = holdingIdentityResolver.getByUsername(whitePlayerName); | ||||
|         final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName); | ||||
|         final String message = "GameBoard SURRENDER test"; | ||||
| 
 | ||||
|         final GameState game = cordaClient.gameProposalCreate( | ||||
|             hiWhite, hiBlack, Piece.Color.BLACK, message); | ||||
| 
 | ||||
|         System.out.println("Game UUID " +game.uuid()); | ||||
| 
 | ||||
|         final GameState acceptedGameBlackView = cordaClient.gameProposalAccept( | ||||
|             hiBlack, game.uuid()); | ||||
| 
 | ||||
|         assertThat(acceptedGameBlackView.opponentColor()).isEqualByComparingTo(Piece.Color.WHITE); | ||||
|         assertThat(acceptedGameBlackView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> { // Black can not surrender, since it is opponent's turn | ||||
|             cordaClient.gameBoardSurrender( | ||||
|                 hiBlack, game.uuid()); | ||||
|         }); | ||||
| 
 | ||||
|         final GameState surrendererGameView = cordaClient.gameBoardSurrender( | ||||
|             hiWhite, game.uuid()); | ||||
| 
 | ||||
|         assertThat(surrendererGameView.opponentName()).isEqualToIgnoringCase(blackPlayerName); | ||||
|         assertThat(surrendererGameView.opponentColor()).isEqualByComparingTo(Piece.Color.BLACK); | ||||
|         assertThat(surrendererGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_LOOSE); | ||||
| 
 | ||||
|         final GameState winnerGameView = cordaClient.gameStateGet( | ||||
|             hiBlack, game.uuid()); | ||||
| 
 | ||||
|         assertThat(winnerGameView.opponentName()).isEqualToIgnoringCase(whitePlayerName); | ||||
|         assertThat(winnerGameView.opponentColor()).isEqualByComparingTo(Piece.Color.WHITE); | ||||
|         assertThat(winnerGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_WON); | ||||
|     } | ||||
| 
 | ||||
|     // @Test | ||||
|     // void testGameBoardMove() throws JsonMappingException, JsonProcessingException, InvalidNameException { | ||||
|     //     final var hiAlice = holdingIdentityResolver.getByUsername("alice"); | ||||
|     //     final var hiBob   = holdingIdentityResolver.getByUsername("bob"); | ||||
|     //     final var bobColor = Piece.Color.WHITE; | ||||
| 
 | ||||
|     //     final UUID gpUuid = cordaClient.gameProposalCreate( | ||||
|     //         hiAlice, hiBob, | ||||
|     //         bobColor, "GameBoard MOVE test" | ||||
|     //     ); | ||||
| 
 | ||||
|     //     System.out.println("New GameProposal UUID "+ gpUuid); | ||||
| 
 | ||||
|     //     final GameBoard gbState = cordaClient.gameProposalAccept( | ||||
|     //         hiBob, gpUuid | ||||
|     //     ); | ||||
| 
 | ||||
|     //     System.out.println("New GameBoard UUID "+ gbState.gameUuid()); | ||||
| 
 | ||||
|     //     assertThatThrownBy(() -> { // Alice can not move, since it is Bob's turn | ||||
|     //         cordaClient.gameBoardMove( | ||||
|     //             hiAlice, gbState.gameUuid(), | ||||
|     //             Arrays.asList(1, 2)); | ||||
|     //     }); | ||||
| 
 | ||||
|     //     final GameBoard gameBoard = cordaClient.gameBoardMove( | ||||
|     //         hiBob, gbState.gameUuid(), Arrays.asList(1, 2)); | ||||
|     // } | ||||
| 
 | ||||
|     // private <T extends GameState> T findByUuid(List<T> statesList, UUID uuid) { | ||||
|     //     for (T state : statesList) { | ||||
|     //         if (state.uuid().compareTo(uuid) == 0) | ||||
|     //             return state; | ||||
|     //     }; | ||||
|     //     return null; | ||||
|     // } | ||||
| } | ||||
| @ -0,0 +1,171 @@ | ||||
| package djmil.cordacheckers.cordaclient; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||||
| import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||||
| 
 | ||||
| import org.junit.jupiter.api.Test; | ||||
| 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.user.HoldingIdentityResolver; | ||||
| 
 | ||||
| @SpringBootTest | ||||
| public class GameProposalTests { | ||||
|     @Autowired | ||||
|     CordaClient cordaClient; | ||||
| 
 | ||||
|     @Autowired | ||||
|     HoldingIdentityResolver holdingIdentityResolver; | ||||
| 
 | ||||
|     final String issuer = "alice"; | ||||
|     final String acquier = "bob"; | ||||
|     final Piece.Color acquierColor = Piece.Color.WHITE; | ||||
| 
 | ||||
|     @Test | ||||
|     void testCreate() { | ||||
|         final var hiIssuer  = holdingIdentityResolver.getByUsername(issuer); | ||||
|         final var hiAcquier = holdingIdentityResolver.getByUsername(acquier); | ||||
|         final String message = "GameProposal CREATE test"; | ||||
| 
 | ||||
|         final GameState issuerGameView = cordaClient.gameProposalCreate( | ||||
|             hiIssuer, | ||||
|             hiAcquier, | ||||
|             acquierColor, | ||||
|             message); | ||||
| 
 | ||||
|         System.out.println("Game UUID " +issuerGameView.uuid()); | ||||
| 
 | ||||
|         /* | ||||
|          * Issuers perspective on a newly created GameProposal | ||||
|          */ | ||||
|         assertThat(issuerGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_WAIT_FOR_OPPONENT); | ||||
|         assertThat(issuerGameView.opponentName()).isEqualToIgnoringCase(acquier); | ||||
|         assertThat(issuerGameView.opponentColor()).isEqualByComparingTo(acquierColor); | ||||
|         assertThat(issuerGameView.message()).isEqualToIgnoringCase(message); | ||||
|         assertThat(issuerGameView.uuid()).isNotNull(); | ||||
|        | ||||
|         /* | ||||
|          * Acquier shall be able to find newly created GameProposal | ||||
|          */ | ||||
|         GameState acquierGameView = assertDoesNotThrow( () -> { | ||||
|             return cordaClient.gameStateList(hiAcquier) | ||||
|                 .stream() | ||||
|                 .filter(gameView -> gameView.uuid().equals(issuerGameView.uuid())) | ||||
|                 .reduce((a, b) -> {throw new IllegalStateException("Duplicate");}) | ||||
|                 .get(); | ||||
|         }); | ||||
| 
 | ||||
|         assertThat(acquierGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_WAIT_FOR_YOU); | ||||
|         assertThat(acquierGameView.opponentName()).isEqualToIgnoringCase(issuer); | ||||
|         assertThat(acquierGameView.opponentColor()).isEqualByComparingTo(acquierColor.opposite()); | ||||
|         assertThat(acquierGameView.message()).isEqualToIgnoringCase(message); | ||||
|         assertThat(acquierGameView.uuid()).isEqualByComparingTo(issuerGameView.uuid()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     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()); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> {  // Issuer can not reject | ||||
|             cordaClient.gameProposalReject( | ||||
|                 hiIssuer, game.uuid()); | ||||
|         }); | ||||
| 
 | ||||
|         final GameState pendingGameView = cordaClient.gameStateGet( | ||||
|             hiAcquier, game.uuid()); | ||||
|         assertThat(pendingGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_WAIT_FOR_YOU); | ||||
|         assertThat(pendingGameView.uuid()).isEqualByComparingTo(game.uuid()); | ||||
| 
 | ||||
|         final GameState rejectedGameView = cordaClient.gameProposalReject( | ||||
|             hiAcquier, game.uuid()); | ||||
|         assertThat(rejectedGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_REJECTED); | ||||
|         assertThat(rejectedGameView.uuid()).isEqualByComparingTo(game.uuid()); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> {  // Issuer has no pending GameProposal | ||||
|             cordaClient.gameStateGet( | ||||
|                 hiIssuer, game.uuid()); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     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()); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> {  // Acquier can not cancel | ||||
|             cordaClient.gameProposalCancel( | ||||
|                 hiAcquier, game.uuid()); | ||||
|         }); | ||||
| 
 | ||||
|         final GameState pendingGameView = cordaClient.gameStateGet( | ||||
|             hiIssuer, game.uuid()); | ||||
|         assertThat(pendingGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_WAIT_FOR_OPPONENT); | ||||
|         assertThat(pendingGameView.uuid()).isEqualByComparingTo(game.uuid()); | ||||
| 
 | ||||
|         final GameState canceledGameView = cordaClient.gameProposalCancel( | ||||
|             hiIssuer, game.uuid()); | ||||
|         assertThat(canceledGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_CANCELED); | ||||
|         assertThat(canceledGameView.uuid()).isEqualByComparingTo(game.uuid()); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> {  // Acquier has no pending GameProposal | ||||
|             cordaClient.gameStateGet( | ||||
|             hiAcquier, game.uuid()); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     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()); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> { // Issuer can not accept | ||||
|             cordaClient.gameProposalAccept( | ||||
|                 hiIssuer, game.uuid()); | ||||
|         }); | ||||
| 
 | ||||
|         final GameState acceptedGameAcquierView = cordaClient.gameProposalAccept( | ||||
|             hiAcquier, game.uuid()); | ||||
| 
 | ||||
|         assertThat(acceptedGameAcquierView.opponentName()).isEqualToIgnoringCase(issuer); | ||||
|         assertThat(acceptedGameAcquierView.opponentColor()).isEqualByComparingTo(acquierColor.opposite()); | ||||
|         assertThat(acceptedGameAcquierView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_YOU); | ||||
|         assertThat(acceptedGameAcquierView.uuid()).isEqualByComparingTo(game.uuid()); | ||||
| 
 | ||||
|         assertThatThrownBy(() -> {   // same GameProposal can not be accepted twice  | ||||
|             cordaClient.gameProposalAccept( | ||||
|                 hiIssuer, game.uuid()); | ||||
|         }); | ||||
| 
 | ||||
|         final GameState acceptedGameIssuerView = cordaClient.gameStateGet( | ||||
|             hiIssuer, game.uuid()); | ||||
| 
 | ||||
|         assertThat(acceptedGameIssuerView.opponentName()).isEqualToIgnoringCase(acquier); | ||||
|         assertThat(acceptedGameIssuerView.opponentColor()).isEqualByComparingTo(acquierColor); | ||||
|         assertThat(acceptedGameIssuerView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); | ||||
|         assertThat(acceptedGameIssuerView.uuid()).isEqualByComparingTo(game.uuid()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,53 @@ | ||||
| package djmil.cordacheckers.cordaclient; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import javax.naming.InvalidNameException; | ||||
| 
 | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.GameState; | ||||
| import djmil.cordacheckers.cordaclient.dao.VirtualNode; | ||||
| import djmil.cordacheckers.user.HoldingIdentityResolver; | ||||
| 
 | ||||
| @SpringBootTest | ||||
| public class GameStateTests { | ||||
|     @Autowired | ||||
|     CordaClient cordaClient; | ||||
| 
 | ||||
|     @Autowired | ||||
|     HoldingIdentityResolver holdingIdentityResolver; | ||||
| 
 | ||||
|     @Test | ||||
|     void testGetVirtualNodeList() throws InvalidNameException { | ||||
|         List<VirtualNode> vNodes = cordaClient.getVirtualNodeList(); | ||||
| 
 | ||||
| 		assertThat(vNodes.size()).isEqualTo(5); // default vNode config for CSDE | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testList() throws JsonProcessingException { | ||||
|         List<GameState> gsList = cordaClient.gameStateList( | ||||
|             holdingIdentityResolver.getByUsername("bob")); | ||||
| 
 | ||||
|         System.out.println(gsList); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testGet() throws JsonProcessingException { | ||||
|         GameState gameStateView = cordaClient.gameStateGet( | ||||
|             holdingIdentityResolver.getByUsername("bob"), | ||||
|             UUID.fromString("cf357d0a-8f64-4599-b9b5-d263163812d4") | ||||
|             ); | ||||
| 
 | ||||
|         System.out.println(gameStateView); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -5,7 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.autoconfigure.json.JsonTest; | ||||
| import org.springframework.boot.test.json.JacksonTester; | ||||
| 
 | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty; | ||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.Req; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| 
 | ||||
| @ -23,7 +23,7 @@ public class RequestBodyTest { | ||||
|         RequestBody requestBody = new RequestBody( | ||||
|             "list-2", | ||||
|             "djmil.cordacheckers.gameproposal.ListFlow", | ||||
|             new Empty() | ||||
|             new Req() | ||||
|         ); | ||||
| 
 | ||||
|         assertThat(jTester.write(requestBody)).isEqualToJson("requestBody/ListFlow.json"); | ||||
|  | ||||
| @ -1,128 +0,0 @@ | ||||
| package djmil.cordacheckers.contracts; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.requireThat; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import djmil.cordacheckers.states.GameBoardState; | ||||
| import djmil.cordacheckers.states.GameResultState; | ||||
| import net.corda.v5.base.annotations.ConstructorForDeserialization; | ||||
| import net.corda.v5.base.annotations.CordaSerializable; | ||||
| import net.corda.v5.base.exceptions.CordaRuntimeException; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| import net.corda.v5.ledger.utxo.Command; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| 
 | ||||
| public class GameBoardCommand implements Command { | ||||
| 
 | ||||
|     @CordaSerializable | ||||
|     public static enum Type { | ||||
|         MOVE, | ||||
|         SURRENDER, | ||||
|         FINISH; | ||||
|     } | ||||
| 
 | ||||
|     private final Type type; | ||||
|     private final List<Integer> move; // [0] from, [1] to | ||||
| 
 | ||||
|     // Serialisation service requires a default constructor | ||||
|     public GameBoardCommand() { | ||||
|         this.type = null; | ||||
|         this.move = null; | ||||
|     } | ||||
| 
 | ||||
|     @ConstructorForDeserialization | ||||
|     public GameBoardCommand(Type type, List<Integer> move) { | ||||
|         this.type = type; | ||||
|         this.move = move; | ||||
|     } | ||||
| 
 | ||||
|     public GameBoardCommand(Type type) { | ||||
|         if (type == Type.MOVE) | ||||
|             throw new CordaRuntimeException (BAD_ACTIONMOVE_CONSTRUCTOR); | ||||
| 
 | ||||
|         this.type = type; | ||||
|         this.move = null; | ||||
|     } | ||||
| 
 | ||||
|     public GameBoardCommand(List<Integer> move) { | ||||
|         this.type = Type.MOVE; | ||||
|         this.move = move; | ||||
|     } | ||||
| 
 | ||||
|     public Type getType() { | ||||
|         return this.type; | ||||
|     } | ||||
| 
 | ||||
|     public List<Integer> getMove() { | ||||
|         return this.move; | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getInitiator(GameBoardState gameBoardState) { | ||||
|         return gameBoardState.getMovePlayerName(); | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getRespondent(GameBoardState gameBoardState) { | ||||
|         return gameBoardState.getCounterpartyName(gameBoardState.getMovePlayerName()); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateSurrenderTrx(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); | ||||
| 
 | ||||
|         requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameResultState.getWhitePlayerName()) == 0, IN_OUT_PARTICIPANTS); | ||||
|         requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateMoveTrx(UtxoLedgerTransaction trx) { | ||||
|         requireThat(trx.getInputContractStates().size() == 1, MOVE_INPUT_STATE); | ||||
|         final var inGameBoardState = getSingleInputState(trx, GameBoardState.class); | ||||
| 
 | ||||
|         requireThat(trx.getOutputContractStates().size() == 1, MOVE_OUTPUT_STATE); | ||||
|         final var outGameBoardState = getSingleOutputState(trx, GameBoardState.class); | ||||
| 
 | ||||
|         requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameBoardState.getWhitePlayerName()) == 0, IN_OUT_PARTICIPANTS); | ||||
|         requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameBoardState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateFinishTrx(UtxoLedgerTransaction trx) { | ||||
|         requireThat(trx.getInputContractStates().size() == 1, FINAL_MOVE_INPUT_STATE); | ||||
|         final var inGameBoardState = getSingleInputState(trx, GameBoardState.class); | ||||
| 
 | ||||
|         requireThat(trx.getOutputContractStates().size() == 1, FINAL_MOVE_OUTPUT_STATE); | ||||
|         final var outGameResultState = getSingleOutputState(trx, GameResultState.class); | ||||
| 
 | ||||
|         requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameResultState.getWhitePlayerName()) == 0, IN_OUT_PARTICIPANTS); | ||||
|         requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS); | ||||
|     } | ||||
| 
 | ||||
|     static final String BAD_ACTIONMOVE_CONSTRUCTOR = "Bad constructor for Action.MOVE"; | ||||
| 
 | ||||
|     static final String SURRENDER_INPUT_STATE = "SURRENDER command should have exactly one GameBoardState input state"; | ||||
|     static final String SURRENDER_OUTPUT_STATE = "SURRENDER command should have exactly one GameResultState output state"; | ||||
| 
 | ||||
|     static final String MOVE_INPUT_STATE = "MOVE command should have exactly one GameBoardState input state"; | ||||
|     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 FINAL_MOVE_INPUT_STATE = "FINAL_MOVE command should have exactly one GameBoardState input state"; | ||||
|     static final String FINAL_MOVE_OUTPUT_STATE = "FINAL_MOVE command should have exactly one GameResultState output state"; | ||||
| 
 | ||||
|     static final String IN_OUT_PARTICIPANTS = "InputState and OutputState participants do not match"; | ||||
| 
 | ||||
|     public static class CommandTypeException extends RuntimeException { | ||||
|         public CommandTypeException() { | ||||
|             super("Bad GameBoardCommand type"); | ||||
|         } | ||||
|         public CommandTypeException(String message) { | ||||
|             super(message); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,12 +1,11 @@ | ||||
| package djmil.cordacheckers.contracts; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.GameCommand.requireThat; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import net.corda.v5.base.exceptions.CordaRuntimeException; | ||||
| import net.corda.v5.ledger.utxo.Command; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| 
 | ||||
| public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract { | ||||
| @ -17,52 +16,25 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract { | ||||
|     public void verify(UtxoLedgerTransaction trx) { | ||||
|         log.info("GameBoardContract.verify() called"); | ||||
| 
 | ||||
|         requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); | ||||
|         final Command command = getSingleCommand(trx, Command.class); | ||||
|         requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND); | ||||
|         final GameCommand command = getSingleCommand(trx, GameCommand.class); | ||||
| 
 | ||||
|         if (command instanceof GameProposalCommand) { | ||||
|             log.info("GameBoardContract.verify() as GameProposalCommand "+(GameProposalCommand)command); | ||||
|             switch ((GameProposalCommand)command) { | ||||
|                 case ACCEPT: | ||||
|                     GameProposalCommand.validateAcceptTrx(trx); | ||||
|         switch (command.action) { | ||||
|             case GAME_PROPOSAL_ACCEPT: | ||||
|                 GameCommand.validateGameProposalAccept(trx); | ||||
|                 break; | ||||
|          | ||||
|             case GAME_BOARD_MOVE: | ||||
|                 GameCommand.validateGameBoardMove(trx); | ||||
|                 break; | ||||
| 
 | ||||
|             case GAME_BOARD_SURRENDER: | ||||
|                 GameCommand.validateGameBoardSurrender(trx); | ||||
|                 break; | ||||
| 
 | ||||
|             default: | ||||
|                     throw new CordaRuntimeException("UNKNOWN_COMMAND"); | ||||
|             } | ||||
|         } else  | ||||
|         if (command instanceof GameBoardCommand) { | ||||
|             log.info("GameBoardContract.verify() as GameBoardCommand "+((GameBoardCommand)command).getType()); | ||||
|             switch (((GameBoardCommand)command).getType()) { | ||||
|                 case MOVE: | ||||
|                     GameBoardCommand.validateMoveTrx(trx); | ||||
|                     break; | ||||
|                  | ||||
|                 case SURRENDER: | ||||
|                     GameBoardCommand.validateSurrenderTrx(trx); | ||||
|                     break; | ||||
| 
 | ||||
|                 case FINISH: | ||||
|                     GameBoardCommand.validateFinishTrx(trx); | ||||
|                     break; | ||||
| 
 | ||||
|                 default: | ||||
|                     throw new GameBoardCommand.CommandTypeException(); | ||||
|             } | ||||
|         } else { | ||||
|             throw new RuntimeException("Bad utxo command type"); | ||||
|                 throw new GameCommand.ActionException(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     private static void requireThat(boolean asserted, String errorMessage) { | ||||
|         if(!asserted) { | ||||
|             throw new CordaRuntimeException("Failed requirement: " + errorMessage); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static final String REQUIRE_SINGLE_COMMAND = "Require a single command"; | ||||
| 
 | ||||
|     static final String SINGLE_STATE_EXPECTED = "Single state expected"; | ||||
|     static final String NO_REFERANCE_GAMEPROPOSAL_STATE_FOR_TRXID = "No reference GamePropsal state found for trx.id "; | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,243 @@ | ||||
| package djmil.cordacheckers.contracts; | ||||
| 
 | ||||
| import djmil.cordacheckers.states.GameBoardState; | ||||
| import djmil.cordacheckers.states.GameProposalState; | ||||
| import djmil.cordacheckers.states.GameResultState; | ||||
| import djmil.cordacheckers.states.GameState; | ||||
| import net.corda.v5.base.annotations.ConstructorForDeserialization; | ||||
| import net.corda.v5.base.annotations.CordaSerializable; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| import net.corda.v5.ledger.utxo.Command; | ||||
| import net.corda.v5.ledger.utxo.StateAndRef; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| 
 | ||||
| import java.lang.reflect.Array; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState; | ||||
| 
 | ||||
| public class GameCommand implements Command { | ||||
|     @CordaSerializable | ||||
|     public static enum Action { | ||||
|         GAME_PROPOSAL_CREATE, | ||||
|         GAME_PROPOSAL_CANCEL, | ||||
|         GAME_PROPOSAL_ACCEPT, | ||||
|         GAME_PROPOSAL_REJECT, | ||||
| 
 | ||||
|         GAME_BOARD_MOVE, | ||||
|         GAME_BOARD_SURRENDER, | ||||
| 
 | ||||
|         GAME_RESULT_CREATE; | ||||
|     } | ||||
| 
 | ||||
|     public final Action action; | ||||
|     public final List<Integer> move; // [0] from, [1] to | ||||
| 
 | ||||
|     public static class ActionException extends RuntimeException { | ||||
|         public ActionException() { | ||||
|             super("Bad GameCommand.Action value"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Serialisation service requires a default constructor | ||||
|     public GameCommand() { | ||||
|         this.action = null; | ||||
|         this.move = null; | ||||
|     } | ||||
| 
 | ||||
|     public GameCommand(Action action) { | ||||
|         if (action == Action.GAME_BOARD_MOVE) | ||||
|             throw new ActionException(); | ||||
| 
 | ||||
|         this.action = action; | ||||
|         this.move = null; | ||||
|     } | ||||
| 
 | ||||
|     public GameCommand(List<Integer> move) { | ||||
|         this.action = Action.GAME_BOARD_MOVE; | ||||
|         this.move = move; | ||||
|     } | ||||
| 
 | ||||
|     @ConstructorForDeserialization | ||||
|     public GameCommand(Action action, List<Integer> move) { | ||||
|         this.action = action; | ||||
|         this.move = move; | ||||
|     } | ||||
| 
 | ||||
|     public Action getAction() { | ||||
|         return action; | ||||
|     } | ||||
| 
 | ||||
|     public List<Integer> getMove() { | ||||
|         return move; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     /* | ||||
|      * Session initiator/respondent | ||||
|      */ | ||||
| 
 | ||||
|     public MemberX500Name getObserver(StateAndRef<GameState> gameStateSar) { | ||||
|         final GameState gameState = gameStateSar.getState().getContractState(); | ||||
|         return gameState.getCounterpartyName(getActor(gameState)); | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getActor(StateAndRef<GameState> gameStateSar) { | ||||
|         return getActor(gameStateSar.getState().getContractState()); | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getObserver(GameState gameState) { | ||||
|         return gameState.getCounterpartyName(getActor(gameState)); | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getActor(GameState gameState) { | ||||
|         switch (action) { | ||||
|             case GAME_PROPOSAL_CREATE: | ||||
|             case GAME_PROPOSAL_CANCEL: | ||||
|                 if (gameState instanceof GameProposalState) | ||||
|                     return ((GameProposalState)gameState).getIssuer(); | ||||
|                 break; | ||||
| 
 | ||||
|             case GAME_PROPOSAL_REJECT: | ||||
|                 if (gameState instanceof GameProposalState) | ||||
|                     return ((GameProposalState)gameState).getAcquier(); | ||||
|                 break; | ||||
|              | ||||
|             case GAME_PROPOSAL_ACCEPT: | ||||
|                 if (gameState instanceof GameProposalState) // <<-- Session validation perspective | ||||
|                     return ((GameProposalState)gameState).getAcquier(); | ||||
|                 if (gameState instanceof GameBoardState)    // <<-- GameViewBuilder perspective | ||||
|                     return ((GameBoardState)gameState).getMovePlayerName(); | ||||
|                 break; | ||||
| 
 | ||||
|             case GAME_BOARD_MOVE: | ||||
|                 if (gameState instanceof GameBoardState) | ||||
|                     return ((GameBoardState)gameState).getMovePlayerName(); | ||||
|                 break; | ||||
| 
 | ||||
|             case GAME_BOARD_SURRENDER: | ||||
|                 if (gameState instanceof GameResultState) | ||||
|                     return ((GameResultState)gameState).getOpponentName(); | ||||
|                 break; | ||||
|              | ||||
|             case GAME_RESULT_CREATE: | ||||
|                 if (gameState instanceof GameResultState) | ||||
|                     return ((GameResultState)gameState).getWinnerName(); | ||||
|                 break; | ||||
|              | ||||
|             default: | ||||
|                 throw new ActionException(); | ||||
|         } | ||||
| 
 | ||||
|         throw new RuntimeException(action +": unexpected GameState type " +gameState.getClass()); | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|      * Transaction validation | ||||
|      */ | ||||
| 
 | ||||
|     public static void validateGameProposalCreate(UtxoLedgerTransaction trx) { | ||||
|         requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE); | ||||
|         requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE); | ||||
| 
 | ||||
|         GameProposalState outputState = getSingleOutputState(trx, GameProposalState.class); | ||||
| 
 | ||||
|         requireThat(outputState.getAcquierColor() != null, NON_NULL_RECIPIENT_COLOR); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateGameProposalAccept(UtxoLedgerTransaction trx) { | ||||
|         requireThat(trx.getInputContractStates().size() == 1, ACCEPT_INPUT_STATE); | ||||
|         requireThat(trx.getOutputContractStates().size() == 1, ACCEPT_OUTPUT_STATE); | ||||
| 
 | ||||
|         GameProposalState inGameProposal = getSingleInputState(trx, GameProposalState.class); | ||||
|         GameBoardState outGameBoard      = getSingleOutputState(trx, GameBoardState.class); | ||||
| 
 | ||||
|         requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), IN_OUT_PARTICIPANTS); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateGameProposalReject(UtxoLedgerTransaction trx) { | ||||
|         requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE); | ||||
|         requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE); | ||||
| 
 | ||||
|         getSingleInputState(trx, GameProposalState.class); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateGameProposalCancel(UtxoLedgerTransaction trx) { | ||||
|         requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE); | ||||
|         requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE); | ||||
|          | ||||
|         getSingleInputState(trx, GameProposalState.class); | ||||
|     } | ||||
| 
 | ||||
|     public static 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<MemberX500Name> inActors = new LinkedList<MemberX500Name>(List.of( | ||||
|             inGameBoardState.getWhitePlayerName(), | ||||
|             inGameBoardState.getBlackPlayerName()) | ||||
|         ); | ||||
| 
 | ||||
|         List<MemberX500Name> outActors = new LinkedList<MemberX500Name>(List.of( | ||||
|             outGameResultState.getWinnerName(), | ||||
|             outGameResultState.getOpponentName()) | ||||
|         ); | ||||
| 
 | ||||
|         requireThat(inActors.containsAll(outActors) && outActors.containsAll(inActors), IN_OUT_PARTICIPANTS); | ||||
| 
 | ||||
|         final var activePlayerName = outGameResultState.getCounterpartyName(outGameResultState.getWinnerName()); | ||||
|         requireThat(inGameBoardState.getMovePlayerName().compareTo(activePlayerName) == 0, SURRENDER_ACTOR + "expected " +inGameBoardState.getMovePlayerName() +" actual " +activePlayerName); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateGameBoardMove(UtxoLedgerTransaction trx) { | ||||
|         requireThat(trx.getInputContractStates().size() == 1, MOVE_INPUT_STATE); | ||||
|         final var inGameBoardState = getSingleInputState(trx, GameBoardState.class); | ||||
| 
 | ||||
|         requireThat(trx.getOutputContractStates().size() == 1, MOVE_OUTPUT_STATE); | ||||
|         final var outGameBoardState = getSingleOutputState(trx, GameBoardState.class); | ||||
| 
 | ||||
|         requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameBoardState.getWhitePlayerName()) == 0, IN_OUT_PARTICIPANTS); | ||||
|         requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameBoardState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateGameResultCreate(UtxoLedgerTransaction trx) { | ||||
|         throw new RuntimeException("Unimplemented"); | ||||
|     } | ||||
| 
 | ||||
|     public static void requireThat(boolean asserted, String errorMessage) { | ||||
|         if (!asserted) { | ||||
|             throw new IllegalStateException("Failed requirement: " + errorMessage); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     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 CREATE_INPUT_STATE  = "Create command should have no input states"; | ||||
|     static final String CREATE_OUTPUT_STATE = "Create command should output exactly one GameProposal state"; | ||||
|     static final String NON_NULL_RECIPIENT_COLOR = "GameProposal.recipientColor field can not be null"; | ||||
| 
 | ||||
|     static final String REJECT_INPUT_STATE  = "Reject command should have exactly one GameProposal input state"; | ||||
|     static final String REJECT_OUTPUT_STATE = "Reject command should have no output states"; | ||||
| 
 | ||||
|     static final String CANCEL_INPUT_STATE  = "Cancel command should have exactly one GameProposal input state"; | ||||
|     static final String CANCEL_OUTPUT_STATE = "Cancel command should have no output states"; | ||||
| 
 | ||||
|     static final String ACCEPT_INPUT_STATE  = "Accept command should have exactly one GameProposal input state"; | ||||
|     static final String ACCEPT_OUTPUT_STATE = "Accept command should have exactly one GameBoard output state"; | ||||
| 
 | ||||
|     static final String SURRENDER_INPUT_STATE = "SURRENDER command should have exactly one GameBoardState input state"; | ||||
|     static final String SURRENDER_OUTPUT_STATE = "SURRENDER command should have exactly one GameResultState output state"; | ||||
|     static final String SURRENDER_ACTOR = "Only active player can SURRENDER"; | ||||
| 
 | ||||
|     static final String MOVE_INPUT_STATE = "MOVE command should have exactly one GameBoardState input state"; | ||||
|     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"; | ||||
| } | ||||
| @ -1,102 +0,0 @@ | ||||
| package djmil.cordacheckers.contracts; | ||||
| 
 | ||||
| import djmil.cordacheckers.states.GameBoardState; | ||||
| import djmil.cordacheckers.states.GameProposalState; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| import net.corda.v5.ledger.utxo.Command; | ||||
| import net.corda.v5.ledger.utxo.StateAndRef; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.requireThat; | ||||
| 
 | ||||
| public enum GameProposalCommand implements Command {  | ||||
|     CREATE, | ||||
|     ACCEPT, | ||||
|     REJECT, | ||||
|     CANCEL; | ||||
| 
 | ||||
|     public MemberX500Name getInitiator(GameProposalState gameProposalState) { | ||||
|         switch (this) { | ||||
|             case CREATE: | ||||
|             case CANCEL: | ||||
|                 return gameProposalState.getIssuer(); | ||||
|             case ACCEPT: | ||||
|             case REJECT: | ||||
|                 return gameProposalState.getAcquier(); | ||||
|             default: | ||||
|                 throw new RuntimeException(UNSUPPORTED_VALUE_OF + this.name()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getRespondent(GameProposalState gameProposalState) { | ||||
|         switch (this) { | ||||
|             case CREATE: | ||||
|             case CANCEL: | ||||
|                 return gameProposalState.getAcquier(); | ||||
|             case ACCEPT: | ||||
|             case REJECT: | ||||
|                 return gameProposalState.getIssuer(); | ||||
|             default: | ||||
|                 throw new RuntimeException(UNSUPPORTED_VALUE_OF + this.name()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getInitiator(StateAndRef<GameProposalState> gameProposalSar) { | ||||
|         return getInitiator(gameProposalSar.getState().getContractState()); | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getRespondent(StateAndRef<GameProposalState> gameProposalSar) { | ||||
|         return getRespondent(gameProposalSar.getState().getContractState()); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateCreateTrx(UtxoLedgerTransaction trx) { | ||||
|         requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE); | ||||
|         requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE); | ||||
| 
 | ||||
|         GameProposalState outputState = getSingleOutputState(trx, GameProposalState.class); | ||||
| 
 | ||||
|         requireThat(outputState.getAcquierColor() != null, NON_NULL_RECIPIENT_COLOR); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateAcceptTrx(UtxoLedgerTransaction trx) { | ||||
|         requireThat(trx.getInputContractStates().size() == 1, ACCEPT_INPUT_STATE); | ||||
|         requireThat(trx.getOutputContractStates().size() == 1, ACCEPT_OUTPUT_STATE); | ||||
| 
 | ||||
|         GameProposalState inGameProposal = getSingleInputState(trx, GameProposalState.class); | ||||
|         GameBoardState outGameBoard      = getSingleOutputState(trx, GameBoardState.class); | ||||
| 
 | ||||
|         requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), ACCEPT_PARTICIPANTS); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateRejectTrx(UtxoLedgerTransaction trx) { | ||||
|         requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE); | ||||
|         requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE); | ||||
| 
 | ||||
|         getSingleInputState(trx, GameProposalState.class); | ||||
|     } | ||||
| 
 | ||||
|     public static void validateCancelTrx(UtxoLedgerTransaction trx) { | ||||
|         requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE); | ||||
|         requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE); | ||||
|          | ||||
|         getSingleInputState(trx, GameProposalState.class); | ||||
|     } | ||||
| 
 | ||||
|     public static final String UNSUPPORTED_VALUE_OF = "Unsupported GameProposalCommand value: "; | ||||
| 
 | ||||
|     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"; | ||||
|     static final String NON_NULL_RECIPIENT_COLOR = "GameProposal.recipientColor field can not be null"; | ||||
| 
 | ||||
|     static final String REJECT_INPUT_STATE  = "Reject command should have exactly one GameProposal input state"; | ||||
|     static final String REJECT_OUTPUT_STATE = "Reject command should have no output states"; | ||||
| 
 | ||||
|     static final String CANCEL_INPUT_STATE  = "Cancel command should have exactly one GameProposal input state"; | ||||
|     static final String CANCEL_OUTPUT_STATE = "Cancel command should have no output states"; | ||||
| 
 | ||||
|     static final String ACCEPT_INPUT_STATE  = "Accept command should have exactly one GameProposal input state"; | ||||
|     static final String ACCEPT_OUTPUT_STATE = "Accept command should have exactly one GameBoard output state"; | ||||
|     static final String ACCEPT_PARTICIPANTS = "Accept command: GameBoard participants should math GameProposal participants"; | ||||
| } | ||||
| @ -1,14 +1,13 @@ | ||||
| package djmil.cordacheckers.contracts; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.GameCommand.requireThat; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import net.corda.v5.base.exceptions.CordaRuntimeException; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.requireThat; | ||||
| 
 | ||||
| public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { | ||||
| 
 | ||||
|     private final static Logger log = LoggerFactory.getLogger(GameProposalContract.class); | ||||
| @ -17,31 +16,29 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { | ||||
|     public void verify(UtxoLedgerTransaction trx) { | ||||
|         log.info("GameProposalContract.verify() called"); | ||||
| 
 | ||||
|         requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); | ||||
|         final GameProposalCommand command = getSingleCommand(trx, GameProposalCommand.class); | ||||
|         requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND); | ||||
|         final GameCommand command = getSingleCommand(trx, GameCommand.class); | ||||
| 
 | ||||
|         switch (command) { | ||||
|             case CREATE: | ||||
|                 GameProposalCommand.validateCreateTrx(trx); | ||||
|         switch (command.action) { | ||||
|             case GAME_PROPOSAL_CREATE: | ||||
|                 GameCommand.validateGameProposalCreate(trx); | ||||
|                 break; | ||||
| 
 | ||||
|             case ACCEPT: | ||||
|                 GameProposalCommand.validateAcceptTrx(trx); | ||||
|             case GAME_PROPOSAL_ACCEPT: | ||||
|                 GameCommand.validateGameProposalAccept(trx); | ||||
|                 break; | ||||
| 
 | ||||
|             case REJECT: | ||||
|                 GameProposalCommand.validateRejectTrx(trx); | ||||
|             case GAME_PROPOSAL_REJECT: | ||||
|                 GameCommand.validateGameProposalReject(trx); | ||||
|                 break; | ||||
| 
 | ||||
|             case CANCEL: | ||||
|                 GameProposalCommand.validateCancelTrx(trx); | ||||
|             case GAME_PROPOSAL_CANCEL: | ||||
|                 GameCommand.validateGameProposalCancel(trx); | ||||
|                 break; | ||||
| 
 | ||||
|             default: | ||||
|                 throw new CordaRuntimeException(UNKNOWN_COMMAND); | ||||
|                 throw new GameCommand.ActionException(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static final String REQUIRE_SINGLE_COMMAND = "Require a single command"; | ||||
|     static final String UNKNOWN_COMMAND = "Unsupported command"; | ||||
| } | ||||
|  | ||||
| @ -1,15 +1,13 @@ | ||||
| package djmil.cordacheckers.contracts; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.GameCommand.requireThat; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import net.corda.v5.base.exceptions.CordaRuntimeException; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.requireThat; | ||||
| 
 | ||||
| public class GameResultContract implements net.corda.v5.ledger.utxo.Contract { | ||||
| 
 | ||||
|     private final static Logger log = LoggerFactory.getLogger(GameResultContract.class); | ||||
| @ -18,23 +16,21 @@ public class GameResultContract implements net.corda.v5.ledger.utxo.Contract { | ||||
|     public void verify(UtxoLedgerTransaction trx) { | ||||
|         log.info("GameResultContract.verify() called"); | ||||
| 
 | ||||
|         requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); | ||||
|         final GameBoardCommand command = getSingleCommand(trx, GameBoardCommand.class); | ||||
|         requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND); | ||||
|         final GameCommand command = getSingleCommand(trx, GameCommand.class); | ||||
| 
 | ||||
|         switch (command.getType()) { | ||||
|             case SURRENDER:  | ||||
|                 GameBoardCommand.validateSurrenderTrx(trx); | ||||
|         switch (command.action) { | ||||
|             case GAME_BOARD_SURRENDER:  | ||||
|                 GameCommand.validateGameBoardSurrender(trx); | ||||
|                 break; | ||||
| 
 | ||||
|             case FINISH: | ||||
|                 GameBoardCommand.validateFinishTrx(trx); | ||||
|             case GAME_RESULT_CREATE: | ||||
|                 GameCommand.validateGameResultCreate(trx); | ||||
|                 break; | ||||
| 
 | ||||
|             default: | ||||
|                 throw new CordaRuntimeException(UNKNOWN_COMMAND); | ||||
|                 throw new GameCommand.ActionException(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static final String REQUIRE_SINGLE_COMMAND = "Require a single command"; | ||||
|     static final String UNKNOWN_COMMAND = "Unsupported command"; | ||||
| } | ||||
|  | ||||
| @ -3,53 +3,45 @@ package djmil.cordacheckers.contracts; | ||||
| import java.util.List; | ||||
| import java.util.Optional; | ||||
| 
 | ||||
| import net.corda.v5.ledger.utxo.Command; | ||||
| import net.corda.v5.ledger.utxo.ContractState; | ||||
| import net.corda.v5.ledger.utxo.StateAndRef; | ||||
| import djmil.cordacheckers.states.GameState; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| 
 | ||||
| public class UtxoLedgerTransactionUtil { | ||||
|     public static <T extends Command> T getSingleCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|     public static <T extends GameCommand> T getSingleCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|         return single(utxoTrx.getCommands(clazz), clazz); | ||||
|     } | ||||
| 
 | ||||
|     public static <T extends ContractState> StateAndRef<T> getSingleReferenceSar(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|         return singleSar(utxoTrx.getReferenceStateAndRefs(clazz), clazz); | ||||
|     } | ||||
|     // public static <T extends ContractState> StateAndRef<T> getSingleReferenceSar(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|     //     return singleSar(utxoTrx.getReferenceStateAndRefs(clazz), clazz); | ||||
|     // } | ||||
| 
 | ||||
|     public static <T extends ContractState> T getSingleInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|     public static <T extends GameState> T getSingleInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|         return single(utxoTrx.getInputStates(clazz), clazz); | ||||
|     } | ||||
| 
 | ||||
|     public static <T extends ContractState> StateAndRef<T> getSingleInputSar(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|         return singleSar(utxoTrx.getInputStateAndRefs(clazz), clazz); | ||||
|     } | ||||
|     // public static <T extends ContractState> StateAndRef<T> getSingleInputSar(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|     //     return singleSar(utxoTrx.getInputStateAndRefs(clazz), clazz); | ||||
|     // } | ||||
| 
 | ||||
|     public static <T extends ContractState> T getSingleOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|     public static <T extends GameState> T getSingleOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|         return single(utxoTrx.getOutputStates(clazz), clazz); | ||||
|     } | ||||
| 
 | ||||
|     public static <T extends Command> Optional<T> getOptionalCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|         return optional(utxoTrx.getCommands(clazz), clazz); | ||||
|     } | ||||
|     // public static <T extends Command> Optional<T> getOptionalCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|     //     return optional(utxoTrx.getCommands(clazz), clazz); | ||||
|     // } | ||||
| 
 | ||||
|     public static <T extends ContractState> Optional<T> getOptionalReferenceState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|         return optional(utxoTrx.getReferenceStates(clazz), clazz); | ||||
|     } | ||||
|     // public static <T extends ContractState> Optional<T> getOptionalReferenceState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|     //     return optional(utxoTrx.getReferenceStates(clazz), clazz); | ||||
|     // } | ||||
| 
 | ||||
|     public static <T extends ContractState> Optional<T> getOptionalInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|         return optional(utxoTrx.getInputStates(clazz), clazz); | ||||
|     } | ||||
|     // public static <T extends ContractState> Optional<T> getOptionalInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|     //     return optional(utxoTrx.getInputStates(clazz), clazz); | ||||
|     // } | ||||
| 
 | ||||
|     public static <T extends ContractState> Optional<T> getOptionalOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|         return optional(utxoTrx.getOutputStates(clazz), clazz); | ||||
|     } | ||||
| 
 | ||||
|     public static void requireThat(boolean asserted, String errorMessage) { | ||||
|         if (!asserted) { | ||||
|             throw new IllegalStateException("Failed requirement: " + errorMessage); | ||||
|         } | ||||
|     } | ||||
|     // public static <T extends ContractState> Optional<T> getOptionalOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) { | ||||
|     //     return optional(utxoTrx.getOutputStates(clazz), clazz); | ||||
|     // } | ||||
| 
 | ||||
|     private static <T> Optional<T> optional(List<T> list, Class<T> clazz) { | ||||
|         return list | ||||
| @ -62,13 +54,6 @@ public class UtxoLedgerTransactionUtil { | ||||
|             .orElseThrow( () -> new IllegalStateException(NO_INSTANCES_OF +clazz.getName()) ); | ||||
|     } | ||||
| 
 | ||||
|     private static <T extends ContractState> StateAndRef<T> singleSar(List<StateAndRef<T>> list, Class<T> clazz) { | ||||
|         return list | ||||
|             .stream() | ||||
|             .reduce((a, b) -> {throw new IllegalStateException(MULTIPLE_INSTANCES_OF +clazz.getName());}) | ||||
|             .orElseThrow( () -> new IllegalStateException(NO_INSTANCES_OF +clazz.getName()) ); | ||||
|     } | ||||
| 
 | ||||
|     private static String MULTIPLE_INSTANCES_OF = "Multiple instances of "; | ||||
|     private static String NO_INSTANCES_OF = "No instances of "; | ||||
| } | ||||
|  | ||||
| @ -1,24 +0,0 @@ | ||||
| package djmil.cordacheckers.states; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import org.jetbrains.annotations.NotNull; | ||||
| 
 | ||||
| import net.corda.v5.base.annotations.CordaSerializable; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| 
 | ||||
| @CordaSerializable | ||||
| public interface Game { | ||||
| 
 | ||||
|     @NotNull | ||||
|     MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved; | ||||
| 
 | ||||
|     @NotNull | ||||
|     UUID getGameUuid(); | ||||
|      | ||||
|     public static class NotInvolved extends RuntimeException { | ||||
|         public <T> NotInvolved(MemberX500Name myName, Class<T> clazz, UUID uuid) { | ||||
|             super(myName +" not involved in " +clazz.getSimpleName() +" UUID " +uuid); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -11,10 +11,9 @@ import djmil.cordacheckers.states.Piece.Color; | ||||
| import net.corda.v5.base.annotations.ConstructorForDeserialization; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| import net.corda.v5.ledger.utxo.BelongsToContract; | ||||
| import net.corda.v5.ledger.utxo.ContractState; | ||||
| 
 | ||||
| @BelongsToContract(GameBoardContract.class) | ||||
| public class GameBoardState implements ContractState, Game { | ||||
| public class GameBoardState extends GameState { | ||||
|      | ||||
|     private final MemberX500Name whitePlayerName; | ||||
|     private final MemberX500Name blackPlayerName; | ||||
| @ -22,14 +21,10 @@ public class GameBoardState implements ContractState, Game { | ||||
|     private final Piece.Color moveColor; | ||||
|     private final Integer moveNumber; | ||||
|     private final Map<Integer, Piece> board; | ||||
|     private final String message; | ||||
| 
 | ||||
|     private final UUID gameUuid; | ||||
|     private final List<PublicKey> participants; | ||||
|     public GameBoardState(GameProposalState gameProposalState) { | ||||
|         super(gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants); | ||||
| 
 | ||||
|     public GameBoardState( | ||||
|         GameProposalState gameProposalState | ||||
|     ) { | ||||
|         this.whitePlayerName = gameProposalState.getWhitePlayerName(); | ||||
|         this.blackPlayerName = gameProposalState.getBlackPlayerName(); | ||||
|          | ||||
| @ -37,15 +32,12 @@ public class GameBoardState implements ContractState, Game { | ||||
|         this.moveColor = Piece.Color.WHITE; | ||||
|         this.moveNumber = 0; | ||||
|         this.board = new LinkedHashMap<Integer, Piece>(initialBoard); | ||||
|         this.message = null; | ||||
| 
 | ||||
|         this.gameUuid = gameProposalState.getGameUuid(); | ||||
|         this.participants = gameProposalState.getParticipants(); | ||||
|     } | ||||
| 
 | ||||
|     public GameBoardState( | ||||
|         GameBoardState oldGameBoardState, Map<Integer, Piece> newBoard, Piece.Color moveColor | ||||
|     ) { | ||||
|         GameBoardState oldGameBoardState, Map<Integer, Piece> newBoard, Piece.Color moveColor) { | ||||
|         super(oldGameBoardState.gameUuid, oldGameBoardState.message, oldGameBoardState.participants); | ||||
| 
 | ||||
|         this.whitePlayerName = oldGameBoardState.getWhitePlayerName(); | ||||
|         this.blackPlayerName = oldGameBoardState.getBlackPlayerName(); | ||||
|          | ||||
| @ -53,32 +45,30 @@ public class GameBoardState implements ContractState, Game { | ||||
|         this.moveColor = moveColor; | ||||
|         this.moveNumber = oldGameBoardState.getMoveNumber() +1; | ||||
|         this.board = newBoard; | ||||
|         this.message = null; | ||||
| 
 | ||||
|         this.gameUuid = oldGameBoardState.getGameUuid(); | ||||
|         this.participants = oldGameBoardState.getParticipants(); | ||||
|     } | ||||
|   | ||||
|     @ConstructorForDeserialization | ||||
|     public GameBoardState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName,  | ||||
|             Color moveColor, Integer moveNumber, Map<Integer, Piece> board, String message,  | ||||
|             UUID gameUuid, List<PublicKey> participants) { | ||||
|         super(gameUuid, message, participants); | ||||
| 
 | ||||
|         this.whitePlayerName = whitePlayerName; | ||||
|         this.blackPlayerName = blackPlayerName; | ||||
|         this.moveColor = moveColor; | ||||
|         this.moveNumber = moveNumber; | ||||
|         this.board = board; | ||||
|         this.message = message; | ||||
|         this.gameUuid = gameUuid; | ||||
|         this.participants = participants; | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getWhitePlayerName() { | ||||
|     public MemberX500Name getMovePlayerName() { | ||||
|         switch (moveColor) { | ||||
|             case WHITE: | ||||
|                 return whitePlayerName; | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getBlackPlayerName() { | ||||
|             case BLACK: | ||||
|                 return blackPlayerName; | ||||
|             default: | ||||
|                 throw new Piece.Color.UnknownException(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public Piece.Color getMoveColor() { | ||||
| @ -93,16 +83,9 @@ public class GameBoardState implements ContractState, Game { | ||||
|         return board; | ||||
|     } | ||||
| 
 | ||||
|     public String getMessage() { | ||||
|         return message; | ||||
|     } | ||||
| 
 | ||||
|     public UUID getGameUuid() { | ||||
|         return gameUuid; | ||||
|     } | ||||
| 
 | ||||
|     public List<PublicKey> getParticipants() { | ||||
|         return participants; | ||||
|     @Override | ||||
|     public MemberX500Name getWhitePlayerName() { | ||||
|         return whitePlayerName; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @ -113,26 +96,10 @@ public class GameBoardState implements ContractState, Game { | ||||
|         if (blackPlayerName.compareTo(myName) == 0) | ||||
|             return whitePlayerName; | ||||
|          | ||||
|         throw new Game.NotInvolved(myName, GameBoardState.class, this.gameUuid); | ||||
|     } | ||||
| 
 | ||||
|     public Piece.Color getCounterpartyColor(MemberX500Name myName) throws NotInvolved { | ||||
|         final MemberX500Name opponentName = getCounterpartyName(myName); | ||||
| 
 | ||||
|         return getWhitePlayerName().compareTo(opponentName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK; | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getMovePlayerName() { | ||||
|         switch (moveColor) { | ||||
|             case WHITE: | ||||
|                 return whitePlayerName; | ||||
|             case BLACK: | ||||
|                 return blackPlayerName; | ||||
|             default: | ||||
|                 throw new Piece.Color.UnknownException(); | ||||
|         } | ||||
|         throw new GameState.NotInvolved(myName, GameBoardState.class, this.gameUuid); | ||||
|     } | ||||
| 
 | ||||
|     // TODO: move to contract | ||||
|     public final static Map<Integer, Piece> 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)), | ||||
|  | ||||
| @ -8,17 +8,13 @@ import djmil.cordacheckers.contracts.GameProposalContract; | ||||
| import net.corda.v5.base.annotations.ConstructorForDeserialization; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| import net.corda.v5.ledger.utxo.BelongsToContract; | ||||
| import net.corda.v5.ledger.utxo.ContractState; | ||||
| 
 | ||||
| @BelongsToContract(GameProposalContract.class) | ||||
| public class GameProposalState implements ContractState, Game { | ||||
| public class GameProposalState extends GameState { | ||||
| 
 | ||||
|     private final MemberX500Name issuer; | ||||
|     private final MemberX500Name acquier; | ||||
|     private final Piece.Color acquierColor; | ||||
|     private final String message; | ||||
|     private final UUID gameUuid; | ||||
|     private final List<PublicKey> participants; | ||||
| 
 | ||||
|     @ConstructorForDeserialization | ||||
|     public GameProposalState( | ||||
| @ -29,12 +25,10 @@ public class GameProposalState implements ContractState, Game { | ||||
|         UUID gameUuid, | ||||
|         List<PublicKey> participants | ||||
|     ) { | ||||
|         super(gameUuid, message, participants); | ||||
|         this.issuer = issuer; | ||||
|         this.acquier = acquier; | ||||
|         this.acquierColor = acquierColor; | ||||
|         this.message = message; | ||||
|         this.gameUuid = gameUuid; | ||||
|         this.participants = participants; | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getIssuer() { | ||||
| @ -49,26 +43,10 @@ public class GameProposalState implements ContractState, Game { | ||||
|         return acquierColor; | ||||
|     } | ||||
| 
 | ||||
|     public String getMessage() { | ||||
|         return message; | ||||
|     } | ||||
| 
 | ||||
|     public UUID getGameUuid() { | ||||
|         return gameUuid; | ||||
|     } | ||||
| 
 | ||||
|     public List<PublicKey> getParticipants() { | ||||
|         return participants; | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getWhitePlayerName() { | ||||
|         return acquierColor == Piece.Color.WHITE ? getAcquier() : getIssuer(); | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getBlackPlayerName() { | ||||
|         return acquierColor == Piece.Color.BLACK ? getAcquier() : getIssuer(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved { | ||||
|         if (issuer.compareTo(myName) == 0) | ||||
| @ -77,7 +55,7 @@ public class GameProposalState implements ContractState, Game { | ||||
|         if (acquier.compareTo(myName) == 0) | ||||
|             return issuer; | ||||
|          | ||||
|         throw new Game.NotInvolved(myName, GameProposalState.class, this.gameUuid); | ||||
|         throw new GameState.NotInvolved(myName, GameProposalState.class, this.gameUuid); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -8,68 +8,57 @@ import djmil.cordacheckers.contracts.GameResultContract; | ||||
| import net.corda.v5.base.annotations.ConstructorForDeserialization; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| import net.corda.v5.ledger.utxo.BelongsToContract; | ||||
| import net.corda.v5.ledger.utxo.ContractState; | ||||
| 
 | ||||
| @BelongsToContract(GameResultContract.class) | ||||
| public class GameResultState implements ContractState, Game { | ||||
| public class GameResultState extends GameState { | ||||
| 
 | ||||
|     private final MemberX500Name whitePlayerName; | ||||
|     private final MemberX500Name blackPlayerName; | ||||
|     private final MemberX500Name winnerName; | ||||
|     private final MemberX500Name opponentName; | ||||
| 
 | ||||
|     private final Piece.Color victoryColor; | ||||
| 
 | ||||
|     private final UUID gameUuid; | ||||
|     private final List<PublicKey> participants; | ||||
|     private final Piece.Color winnerColor; | ||||
|   | ||||
|     @ConstructorForDeserialization | ||||
|     public GameResultState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName, Piece.Color victoryColor,  | ||||
|             UUID gameUuid, List<PublicKey> participants) { | ||||
|         this.whitePlayerName = whitePlayerName; | ||||
|         this.blackPlayerName = blackPlayerName; | ||||
|         this.victoryColor = victoryColor; | ||||
|         this.gameUuid = gameUuid; | ||||
|         this.participants = participants; | ||||
|     public GameResultState(MemberX500Name winnerName, MemberX500Name opponentName, Piece.Color winnerColor,  | ||||
|             UUID gameUuid, String message, List<PublicKey> participants) { | ||||
|         super(gameUuid, message, participants); | ||||
|         this.winnerName = winnerName; | ||||
|         this.opponentName = opponentName; | ||||
|         this.winnerColor = winnerColor; | ||||
|     } | ||||
| 
 | ||||
|     public GameResultState(GameBoardState stateGameBoard, Piece.Color victoryColor) { | ||||
|         this.whitePlayerName = stateGameBoard.getWhitePlayerName(); | ||||
|         this.blackPlayerName = stateGameBoard.getBlackPlayerName(); | ||||
|         this.victoryColor = victoryColor; | ||||
| 
 | ||||
|         this.gameUuid = stateGameBoard.getGameUuid(); | ||||
|         this.participants = stateGameBoard.getParticipants(); | ||||
|     public GameResultState(GameBoardState gameBoardState, MemberX500Name winnerName) { | ||||
|         super(gameBoardState.gameUuid, null, gameBoardState.participants); | ||||
|         this.opponentName = gameBoardState.getCounterpartyName(winnerName); | ||||
|         this.winnerName = winnerName; | ||||
|         this.winnerColor = gameBoardState.getWhitePlayerName().compareTo(winnerName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK; | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getWhitePlayerName() { | ||||
|         return whitePlayerName; | ||||
|     public MemberX500Name getWinnerName() { | ||||
|         return winnerName; | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getBlackPlayerName() { | ||||
|         return blackPlayerName; | ||||
|     public MemberX500Name getOpponentName() { | ||||
|         return opponentName; | ||||
|     } | ||||
| 
 | ||||
|     public Piece.Color getVictoryColor() { | ||||
|         return victoryColor; | ||||
|     } | ||||
| 
 | ||||
|     public UUID getGameUuid() { | ||||
|         return gameUuid; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<PublicKey> getParticipants() { | ||||
|         return participants; | ||||
|     public Piece.Color getWinnerColor() { | ||||
|         return winnerColor; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved { | ||||
|         if (whitePlayerName.compareTo(myName) == 0) | ||||
|             return blackPlayerName; | ||||
|         if (winnerName.compareTo(myName) == 0) | ||||
|             return opponentName; | ||||
|      | ||||
|         if (blackPlayerName.compareTo(myName) == 0) | ||||
|             return whitePlayerName; | ||||
|         if (opponentName.compareTo(myName) == 0) | ||||
|             return winnerName; | ||||
|          | ||||
|         throw new Game.NotInvolved(myName, GameResultState.class, this.gameUuid); | ||||
|         throw new GameState.NotInvolved(myName, GameResultState.class, this.gameUuid); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MemberX500Name getWhitePlayerName() { | ||||
|         return winnerColor == Piece.Color.WHITE ? winnerName : opponentName; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,59 @@ | ||||
| package djmil.cordacheckers.states; | ||||
| 
 | ||||
| import java.security.PublicKey; | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import org.jetbrains.annotations.NotNull; | ||||
| 
 | ||||
| import net.corda.v5.base.annotations.CordaSerializable; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| import net.corda.v5.ledger.utxo.ContractState; | ||||
| 
 | ||||
| @CordaSerializable | ||||
| public abstract class GameState implements ContractState { | ||||
|     final UUID gameUuid; | ||||
|     final String message; | ||||
|     final List<PublicKey> participants; | ||||
| 
 | ||||
|     @NotNull | ||||
|     public abstract MemberX500Name getWhitePlayerName(); | ||||
| 
 | ||||
|     @NotNull | ||||
|     public abstract MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved; | ||||
| 
 | ||||
|     GameState(UUID gameUuid, String message, List<PublicKey> participants) { | ||||
|         this.gameUuid = gameUuid; | ||||
|         this.message = message; | ||||
|         this.participants = participants; | ||||
|     } | ||||
| 
 | ||||
|     public UUID getGameUuid() { | ||||
|         return gameUuid; | ||||
|     } | ||||
| 
 | ||||
|     public String getMessage() { | ||||
|         return message; | ||||
|     } | ||||
| 
 | ||||
|     public List<PublicKey> getParticipants() { | ||||
|         return participants; | ||||
|     } | ||||
| 
 | ||||
|     public MemberX500Name getBlackPlayerName() { | ||||
|         return getCounterpartyName(getWhitePlayerName()); | ||||
|     } | ||||
| 
 | ||||
|     public Piece.Color getCounterpartyColor(MemberX500Name myName) throws NotInvolved { | ||||
|         final MemberX500Name opponentName = getCounterpartyName(myName); | ||||
| 
 | ||||
|         return getWhitePlayerName().compareTo(opponentName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK; | ||||
|     } | ||||
| 
 | ||||
|     public static class NotInvolved extends RuntimeException { | ||||
|         public <T> NotInvolved(MemberX500Name name, Class<T> clazz, UUID uuid) { | ||||
|             super(name +" is not involved in " +clazz.getSimpleName() +" UUID " +uuid); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,198 +0,0 @@ | ||||
| package djmil.cordacheckers.gameboard; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState; | ||||
| 
 | ||||
| import java.time.Duration; | ||||
| import java.time.Instant; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import djmil.cordacheckers.FlowResult; | ||||
| import djmil.cordacheckers.contracts.GameBoardCommand; | ||||
| import djmil.cordacheckers.contracts.GameBoardCommand.CommandTypeException; | ||||
| import djmil.cordacheckers.gameresult.GameResultView; | ||||
| import djmil.cordacheckers.states.GameBoardState; | ||||
| import djmil.cordacheckers.states.GameResultState; | ||||
| import djmil.cordacheckers.states.Piece; | ||||
| import net.corda.v5.application.flows.ClientRequestBody; | ||||
| import net.corda.v5.application.flows.ClientStartableFlow; | ||||
| import net.corda.v5.application.flows.CordaInject; | ||||
| import net.corda.v5.application.flows.InitiatingFlow; | ||||
| import net.corda.v5.application.marshalling.JsonMarshallingService; | ||||
| import net.corda.v5.application.membership.MemberLookup; | ||||
| import net.corda.v5.application.messaging.FlowMessaging; | ||||
| import net.corda.v5.application.messaging.FlowSession; | ||||
| 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.UtxoLedgerTransaction; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder; | ||||
| 
 | ||||
| @InitiatingFlow(protocol = "game-board") | ||||
| public class CommandFlow implements ClientStartableFlow { | ||||
| 
 | ||||
|     private final static Logger log = LoggerFactory.getLogger(CommandFlow.class); | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public JsonMarshallingService jsonMarshallingService; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public MemberLookup memberLookup; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public FlowMessaging flowMessaging; | ||||
| 
 | ||||
|     @Override | ||||
|     @Suspendable | ||||
|     public String call(ClientRequestBody requestBody) { | ||||
|         log.info("GameBoardCommandFlow started"); | ||||
|         try { | ||||
|             final CommandFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CommandFlowArgs.class); | ||||
| 
 | ||||
|             final StateAndRef<GameBoardState> utxoSarGameBoard = findUtxoGameBoard(args.getGameBoardUuid()); | ||||
|          | ||||
|             final SecureHash trxId = doTransaction(args.getCommand(), utxoSarGameBoard); | ||||
|             final Object trxResult = getTrxResult(trxId); | ||||
| 
 | ||||
|             return new FlowResult(trxResult, trxId) | ||||
|                 .toJsonEncodedString(jsonMarshallingService); | ||||
|         } | ||||
|         catch (Exception e) { | ||||
|             log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +  | ||||
|                         " because: " + e.getMessage()); | ||||
|             return new FlowResult(e).toJsonEncodedString(jsonMarshallingService); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     StateAndRef<GameBoardState> findUtxoGameBoard (UUID gameBoardUuid) { | ||||
|         /* | ||||
|          * Get list of all unconsumed aka 'active' GameProposalStates, then filter by UUID. | ||||
|          * Note, this is an inefficient way to perform this operation if there are a large | ||||
|          * number of 'active' GameProposals exists in storage. | ||||
|          */ | ||||
|         return this.utxoLedgerService | ||||
|             .findUnconsumedStatesByType(GameBoardState.class) | ||||
|             .stream() | ||||
|             .filter(sar -> sar.getState().getContractState().getGameUuid().equals(gameBoardUuid)) | ||||
|             .reduce((a, b) -> {throw new IllegalStateException("Multiple states: " +a +", " +b);}) | ||||
|             .get(); | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     SecureHash doTransaction( | ||||
|         GameBoardCommand command, | ||||
|         StateAndRef<GameBoardState> utxoSarGameBoard | ||||
|     ) { | ||||
|         final GameBoardState stateGameBoard = utxoSarGameBoard.getState().getContractState(); | ||||
|         final MemberX500Name myName = memberLookup.myInfo().getName(); | ||||
|         final MemberX500Name opponentName = stateGameBoard.getCounterpartyName(myName); | ||||
| 
 | ||||
|         UtxoTransactionBuilder trxBuilder = utxoLedgerService.createTransactionBuilder() | ||||
|             .setNotary(utxoSarGameBoard.getState().getNotaryName()) | ||||
|             .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) | ||||
|             .addCommand(command) | ||||
|             .addInputState(utxoSarGameBoard.getRef()) | ||||
|             .addSignatories(stateGameBoard.getParticipants()); | ||||
| 
 | ||||
|         switch (command.getType()) { | ||||
|             case SURRENDER: | ||||
|                 trxBuilder = trxBuilder | ||||
|                     .addOutputState( new GameResultState( | ||||
|                         stateGameBoard,  | ||||
|                         stateGameBoard.getCounterpartyColor(myName)) // winner color | ||||
|                         ); | ||||
|                 break; | ||||
|              | ||||
|             case MOVE: | ||||
|                 trxBuilder = trxBuilder | ||||
|                     .addOutputState( new GameBoardState(stateGameBoard, stateGameBoard.getBoard(), stateGameBoard.getMoveColor())); // TODO: advance color  | ||||
|                     break; | ||||
| 
 | ||||
|             case FINISH: | ||||
|                 throw new CommandTypeException("GameBoard.CommandFlow can not process externaly issued FINISH commnd"); | ||||
| 
 | ||||
|             default: | ||||
|                 throw new CommandTypeException("GameBoard.CommandFlow doTransaction(): Unknown command"); | ||||
|         } | ||||
| 
 | ||||
|         return commit( | ||||
|             trxBuilder.toSignedTransaction(), | ||||
|             opponentName); | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     SecureHash commit(UtxoSignedTransaction candidateTrx, MemberX500Name counterpartyName) { | ||||
|         log.info("About to commit " +candidateTrx.getId()); | ||||
| 
 | ||||
|         final FlowSession session = flowMessaging.initiateFlow(counterpartyName); | ||||
| 
 | ||||
|         /* | ||||
|          * Calls the Corda provided finalise() function which gather signatures from the counterparty, | ||||
|          * notarises the transaction and persists the transaction to each party's vault. | ||||
|          */ | ||||
| 
 | ||||
|         final List<FlowSession> sessionsList = Arrays.asList(session); | ||||
| 
 | ||||
|         final SecureHash trxId = utxoLedgerService | ||||
|             .finalize(candidateTrx, sessionsList) | ||||
|             .getTransaction() | ||||
|             .getId(); | ||||
| 
 | ||||
|         log.info("GameBoard utxo trx id " +trxId); | ||||
|         return trxId; | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     Object getTrxResult(SecureHash trxId) { | ||||
|         final UtxoLedgerTransaction utxoTrx = utxoLedgerService | ||||
|             .findLedgerTransaction(trxId); | ||||
| 
 | ||||
|         final var command = getSingleCommand(utxoTrx, GameBoardCommand.class); | ||||
| 
 | ||||
|         switch (command.getType()) { | ||||
|             case SURRENDER: | ||||
|                 return viewGameResult(utxoTrx); | ||||
| 
 | ||||
|             case MOVE: | ||||
|                 // 1. create initial GameBoardView | ||||
|                 // 2. check for winning conditions | ||||
|                 //    - run subcommnd FINISH that will consume current gbUtxo and will produce GameResult state | ||||
|                 //    - update GameBoardView to have FINISH command | ||||
|                 // 3. return GameBoardView | ||||
|                 return viewGameBoard(utxoTrx); | ||||
| 
 | ||||
|             case FINISH: | ||||
|                 return viewGameBoard(utxoTrx); | ||||
| 
 | ||||
|             default: | ||||
|                 throw new CommandTypeException(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     GameResultView viewGameResult(UtxoLedgerTransaction utxoGameResult) { | ||||
|         final GameResultState grState = getSingleOutputState(utxoGameResult, GameResultState.class); | ||||
| 
 | ||||
|         return new GameResultView(grState); | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     GameBoardView viewGameBoard(UtxoLedgerTransaction utxoGameBoard) { | ||||
|         final MemberX500Name myName = memberLookup.myInfo().getName(); | ||||
| 
 | ||||
|         return new GameBoardView(utxoGameBoard, myName); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,24 +0,0 @@ | ||||
| package djmil.cordacheckers.gameboard; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import djmil.cordacheckers.contracts.GameBoardCommand; | ||||
| 
 | ||||
| public class CommandFlowArgs { | ||||
|     private UUID gameBoardUuid; | ||||
|     private GameBoardCommand command; | ||||
| 
 | ||||
|     // Serialisation service requires a default constructor | ||||
|     public CommandFlowArgs() { | ||||
|         this.gameBoardUuid = null; | ||||
|         this.command = null; | ||||
|     } | ||||
|      | ||||
|     public GameBoardCommand getCommand() { | ||||
|         return this.command; | ||||
|     } | ||||
| 
 | ||||
|     public UUID getGameBoardUuid() { | ||||
|         return this.gameBoardUuid; | ||||
|     } | ||||
| } | ||||
| @ -1,91 +0,0 @@ | ||||
| package djmil.cordacheckers.gameboard; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import djmil.cordacheckers.contracts.GameBoardCommand; | ||||
| import djmil.cordacheckers.contracts.GameBoardCommand.CommandTypeException; | ||||
| import djmil.cordacheckers.states.GameBoardState; | ||||
| import net.corda.v5.application.flows.CordaInject; | ||||
| import net.corda.v5.application.flows.InitiatedBy; | ||||
| import net.corda.v5.application.flows.ResponderFlow; | ||||
| import net.corda.v5.application.membership.MemberLookup; | ||||
| import net.corda.v5.application.messaging.FlowSession; | ||||
| import net.corda.v5.base.annotations.Suspendable; | ||||
| import net.corda.v5.base.exceptions.CordaRuntimeException; | ||||
| import net.corda.v5.ledger.utxo.UtxoLedgerService; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; | ||||
| 
 | ||||
| @InitiatedBy(protocol = "game-board") | ||||
| public class CommandResponderFlow implements ResponderFlow { | ||||
|      | ||||
|     private final static Logger log = LoggerFactory.getLogger(CommandResponderFlow.class); | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public MemberLookup memberLookup; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public UtxoLedgerService utxoLedgerService; | ||||
|      | ||||
|     @Suspendable | ||||
|     @Override | ||||
|     public void call(FlowSession session) { | ||||
|         try { | ||||
|             UtxoTransactionValidator txValidator = utxoGameBoard -> { | ||||
|                 final var command = getSingleCommand(utxoGameBoard, GameBoardCommand.class); | ||||
|                 final var stateGameBoard = getGameBoardState(utxoGameBoard, command); | ||||
| 
 | ||||
|                 checkParticipants(session, stateGameBoard, command); | ||||
| 
 | ||||
|                 /* | ||||
|                  * Other checks / actions ? | ||||
|                  */ | ||||
| 
 | ||||
|                 log.info("Verified the transaction - " + utxoGameBoard.getId()); | ||||
|             }; | ||||
| 
 | ||||
|             UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService | ||||
|                     .receiveFinality(session, txValidator) | ||||
|                     .getTransaction(); | ||||
| 
 | ||||
|             log.info("Finished responder flow - " + finalizedSignedTransaction.getId()); | ||||
|         } | ||||
|         catch(Exception e) { | ||||
|             log.warn("Responder flow failed: ", e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     GameBoardState getGameBoardState(UtxoLedgerTransaction utxoGameBoard, GameBoardCommand command) { | ||||
|         switch (command.getType()) { | ||||
|             case SURRENDER: | ||||
|             case FINISH: | ||||
|                 return getSingleInputState(utxoGameBoard, GameBoardState.class); | ||||
| 
 | ||||
|             case MOVE: | ||||
|                 return getSingleOutputState(utxoGameBoard, GameBoardState.class);  | ||||
| 
 | ||||
|             default: | ||||
|                 throw new CommandTypeException(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     void checkParticipants(FlowSession session, GameBoardState stateGameBoard, GameBoardCommand command) {         | ||||
|         final var myName = memberLookup.myInfo().getName(); | ||||
|         final var counterpartyName = session.getCounterparty(); | ||||
| 
 | ||||
|         if (command.getRespondent(stateGameBoard).compareTo(myName) != 0) | ||||
|             throw new CordaRuntimeException("Bad respondent"); | ||||
| 
 | ||||
|         if (command.getInitiator(stateGameBoard).compareTo(counterpartyName) !=0) | ||||
|             throw new CordaRuntimeException("Bad initiator"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,68 +0,0 @@ | ||||
| package djmil.cordacheckers.gameboard; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import djmil.cordacheckers.contracts.GameBoardCommand; | ||||
| import djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil; | ||||
| import djmil.cordacheckers.states.Game.NotInvolved; | ||||
| import djmil.cordacheckers.states.GameBoardState; | ||||
| import djmil.cordacheckers.states.Piece; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| 
 | ||||
| 
 | ||||
| // GameBoard from the player's point of view | ||||
| public class GameBoardView { | ||||
|     public final String opponentName; | ||||
|     public final Piece.Color opponentColor; | ||||
|     public final Boolean opponentMove; | ||||
|     public final Integer moveNumber; | ||||
|     public final Map<Integer, Piece> board; | ||||
|     public final GameBoardCommand previousCommand; | ||||
|     public final String message; | ||||
|     public final UUID gameUuid; | ||||
| 
 | ||||
|     /* | ||||
|      * GameStatus enum: | ||||
|      * - YOUR_TURN | ||||
|      * - WAIT_FOR_OPPONENT | ||||
|      * - VICTORY | ||||
|      * - YOU_LOOSE | ||||
|      */ | ||||
| 
 | ||||
|     // Serialisation service requires a default constructor | ||||
|     public GameBoardView() { | ||||
|         this.opponentName = null; | ||||
|         this.opponentColor = null; | ||||
|         this.opponentMove = null; | ||||
|         this.moveNumber = null; | ||||
|         this.board = null; | ||||
|         this.previousCommand = null; | ||||
|         this.message = null; | ||||
|         this.gameUuid = null; | ||||
|     } | ||||
| 
 | ||||
|     // A view from a perspective of a concrete player, on a ledger transaction that has  | ||||
|     // produced new GameBoardState | ||||
|     public GameBoardView(UtxoLedgerTransaction utxoGameBoard, MemberX500Name myName) throws NotInvolved { | ||||
|         // TODO check command type: MOVE vs FINNISH | SURRENDER | ||||
|         //                  SingleOutPut vs SingleInput | ||||
|         this.previousCommand = UtxoLedgerTransactionUtil | ||||
|             .getOptionalCommand(utxoGameBoard, GameBoardCommand.class) | ||||
|             .orElseGet(() -> null); // there is no previous command for GameProposal.Accept case | ||||
| 
 | ||||
|         final GameBoardState stateGameBoard = UtxoLedgerTransactionUtil | ||||
|             .getSingleOutputState(utxoGameBoard, GameBoardState.class); | ||||
| 
 | ||||
|         this.opponentName  = stateGameBoard.getCounterpartyName(myName).getCommonName(); | ||||
|         this.opponentColor = stateGameBoard.getCounterpartyColor(myName); | ||||
| 
 | ||||
|         this.opponentMove  = this.opponentColor == stateGameBoard.getMoveColor(); | ||||
|         this.moveNumber    = stateGameBoard.getMoveNumber(); | ||||
|         this.board   = stateGameBoard.getBoard(); | ||||
|         this.message = stateGameBoard.getMessage(); | ||||
|         this.gameUuid      = stateGameBoard.getGameUuid(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,83 +0,0 @@ | ||||
| package djmil.cordacheckers.gameboard; | ||||
| 
 | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import djmil.cordacheckers.FlowResult; | ||||
| import djmil.cordacheckers.states.GameBoardState; | ||||
| import djmil.cordacheckers.states.Game.NotInvolved; | ||||
| 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.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; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| 
 | ||||
| public class ListFlow implements ClientStartableFlow { | ||||
| 
 | ||||
|     private final static Logger log = LoggerFactory.getLogger(ListFlow.class); | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public MemberLookup memberLookup; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public JsonMarshallingService jsonMarshallingService; | ||||
| 
 | ||||
|     @Suspendable | ||||
|     @Override | ||||
|     public String call(ClientRequestBody requestBody) { | ||||
| 
 | ||||
|         try { | ||||
|             final var utxoGameBoardList = utxoLedgerService | ||||
|                 .findUnconsumedStatesByType(GameBoardState.class); | ||||
|                 // NOTE: lambda operations (aka .map) can not be used with UtxoLedgerService instances | ||||
| 
 | ||||
|             final List<GameBoardView> res = prepareGameBoardViewList(utxoGameBoardList); | ||||
| 
 | ||||
|             return new FlowResult(res) | ||||
|                 .toJsonEncodedString(jsonMarshallingService); | ||||
|         } | ||||
|         catch (Exception e) { | ||||
|             log.warn("GameBoard.ListFlow failed to process utxo request body " + requestBody + " because: " + e.getMessage()); | ||||
|             return new FlowResult(e).toJsonEncodedString(jsonMarshallingService); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     private List<GameBoardView> prepareGameBoardViewList(List<StateAndRef<GameBoardState>> utxoGamaBoardList) { | ||||
|         final MemberX500Name myName = memberLookup.myInfo().getName(); | ||||
|         List<GameBoardView> res = new LinkedList<GameBoardView>(); | ||||
| 
 | ||||
|         for (StateAndRef<GameBoardState> sarGameBoard :utxoGamaBoardList) { | ||||
|             try { | ||||
|                 res.add(prepareGameBoardView(sarGameBoard, myName)); | ||||
|             } catch (NotInvolved e) { | ||||
|                log.warn(e.getMessage()); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return res; | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     private GameBoardView prepareGameBoardView(StateAndRef<GameBoardState> sarGameBoard, MemberX500Name myName) throws NotInvolved { | ||||
|         final SecureHash trxId = sarGameBoard.getRef().getTransactionId(); | ||||
| 
 | ||||
|         final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService | ||||
|             .findLedgerTransaction(trxId); | ||||
| 
 | ||||
|         return new GameBoardView(utxoGameBoard, myName); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @ -0,0 +1,104 @@ | ||||
| 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.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.GameResultState; | ||||
| 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.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.common.NotaryLookup; | ||||
| 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{ | ||||
| 
 | ||||
|      private final static Logger log = LoggerFactory.getLogger(SurrenderFlow.class); | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public JsonMarshallingService jsonMarshallingService; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public MemberLookup memberLookup; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public NotaryLookup notaryLookup; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public FlowEngine flowEngine; | ||||
| 
 | ||||
|      @Suspendable | ||||
|      @Override | ||||
|      public String call(ClientRequestBody requestBody) { | ||||
|           SecureHash gameStateUtxoTrxId = null; | ||||
| 
 | ||||
|           try { | ||||
|                final GameCommand command = new GameCommand(GameCommand.Action.GAME_BOARD_SURRENDER); | ||||
| 
 | ||||
|                final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody()); | ||||
| 
 | ||||
|                final StateAndRef<GameState> inputStateSar = this.flowEngine | ||||
|                     .subFlow(new GetFlow(gameStateUuid)); | ||||
| 
 | ||||
|                final GameResultState outputState = prepareGameResultState(inputStateSar); | ||||
| 
 | ||||
|                final UtxoSignedTransaction transactionCandidate = utxoLedgerService.createTransactionBuilder() | ||||
|                     .addCommand(command) | ||||
|                     .addInputState(inputStateSar.getRef()) | ||||
|                     .addOutputState(outputState) | ||||
|                     .addSignatories(outputState.getParticipants()) | ||||
|                     .setNotary(inputStateSar.getState().getNotaryName()) | ||||
|                     .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) | ||||
|                     .toSignedTransaction(); | ||||
| 
 | ||||
|                gameStateUtxoTrxId = this.flowEngine | ||||
|                     .subFlow(new CommitSubFlow(transactionCandidate, command.getObserver(outputState))); | ||||
| 
 | ||||
|                final View gameStateView = this.flowEngine | ||||
|                     .subFlow(new ViewBuilder(gameStateUtxoTrxId)); | ||||
| 
 | ||||
|                return new FlowResponce(gameStateView, gameStateUtxoTrxId) | ||||
|                     .toJsonEncodedString(jsonMarshallingService); | ||||
|           }  | ||||
|           catch (Exception e) { | ||||
|                log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); | ||||
|                e.printStackTrace(System.out); | ||||
|                return new FlowResponce(e, gameStateUtxoTrxId) | ||||
|                     .toJsonEncodedString(jsonMarshallingService); | ||||
|           } | ||||
|      } | ||||
| 
 | ||||
|      @Suspendable | ||||
|      GameResultState prepareGameResultState(StateAndRef<GameState> gameStateSar) { | ||||
|           final GameState gameState = gameStateSar.getState().getContractState(); | ||||
|           final GameBoardState gameBoard = (GameBoardState) gameState; | ||||
| 
 | ||||
|           final MemberX500Name myName = memberLookup.myInfo().getName(); | ||||
|           final MemberX500Name winnerName = gameBoard.getCounterpartyName(myName); | ||||
|            | ||||
|           return new GameResultState(gameBoard, winnerName); | ||||
|      } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,84 @@ | ||||
| package djmil.cordacheckers.gameproposal; | ||||
| 
 | ||||
| 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.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.GameProposalState; | ||||
| 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 AcceptFlow implements ClientStartableFlow{ | ||||
| 
 | ||||
|      private final static Logger log = LoggerFactory.getLogger(AcceptFlow.class); | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public JsonMarshallingService jsonMarshallingService; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public FlowEngine flowEngine; | ||||
| 
 | ||||
|      @Suspendable | ||||
|      @Override | ||||
|      public String call(ClientRequestBody requestBody) { | ||||
|           SecureHash gameStateUtxoTrxId = null; | ||||
| 
 | ||||
|           try { | ||||
|                final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_ACCEPT); | ||||
| 
 | ||||
|                final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody()); | ||||
| 
 | ||||
|                final StateAndRef<GameState> inputState = this.flowEngine | ||||
|                     .subFlow(new GetFlow(gameStateUuid)); | ||||
| 
 | ||||
|                final GameBoardState outputState = new GameBoardState((GameProposalState)inputState.getState().getContractState()); | ||||
| 
 | ||||
|                final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder() | ||||
|                     .addCommand(command) | ||||
|                     .addInputState(inputState.getRef()) | ||||
|                     .addOutputState(outputState) | ||||
|                     .addSignatories(inputState.getState().getContractState().getParticipants()) | ||||
|                     .setNotary(inputState.getState().getNotaryName()) | ||||
|                     .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) | ||||
|                     .toSignedTransaction(); | ||||
| 
 | ||||
|                gameStateUtxoTrxId = this.flowEngine | ||||
|                     .subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState))); | ||||
| 
 | ||||
|                final View gameStateView = this.flowEngine | ||||
|                     .subFlow(new ViewBuilder(gameStateUtxoTrxId)); | ||||
| 
 | ||||
|                return new FlowResponce(gameStateView, gameStateUtxoTrxId) | ||||
|                     .toJsonEncodedString(jsonMarshallingService); | ||||
|           }  | ||||
|           catch (Exception e) { | ||||
|                log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); | ||||
|                e.printStackTrace(System.out); | ||||
|                return new FlowResponce(e, gameStateUtxoTrxId) | ||||
|                     .toJsonEncodedString(jsonMarshallingService); | ||||
|           } | ||||
|      } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,78 @@ | ||||
| package djmil.cordacheckers.gameproposal; | ||||
| 
 | ||||
| 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.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.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 CancelFlow implements ClientStartableFlow{ | ||||
| 
 | ||||
|      private final static Logger log = LoggerFactory.getLogger(CancelFlow.class); | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public JsonMarshallingService jsonMarshallingService; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public FlowEngine flowEngine; | ||||
| 
 | ||||
|      @Suspendable | ||||
|      @Override | ||||
|      public String call(ClientRequestBody requestBody) { | ||||
|           SecureHash gameStateUtxoTrxId = null; | ||||
| 
 | ||||
|           try { | ||||
|                final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_CANCEL); | ||||
| 
 | ||||
|                final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody()); | ||||
| 
 | ||||
|                final StateAndRef<GameState> inputState = this.flowEngine | ||||
|                     .subFlow(new GetFlow(gameStateUuid)); | ||||
| 
 | ||||
|                final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder() | ||||
|                     .addCommand(command) | ||||
|                     .addInputState(inputState.getRef()) | ||||
|                     .addSignatories(inputState.getState().getContractState().getParticipants()) | ||||
|                     .setNotary(inputState.getState().getNotaryName()) | ||||
|                     .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) | ||||
|                     .toSignedTransaction(); | ||||
| 
 | ||||
|                gameStateUtxoTrxId = this.flowEngine | ||||
|                     .subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState))); | ||||
| 
 | ||||
|                final View gameStateView = this.flowEngine | ||||
|                     .subFlow(new ViewBuilder(gameStateUtxoTrxId)); | ||||
| 
 | ||||
|                return new FlowResponce(gameStateView, gameStateUtxoTrxId) | ||||
|                     .toJsonEncodedString(jsonMarshallingService); | ||||
|           }  | ||||
|           catch (Exception e) { | ||||
|                log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); | ||||
|                return new FlowResponce(e, gameStateUtxoTrxId) | ||||
|                     .toJsonEncodedString(jsonMarshallingService); | ||||
|           } | ||||
|      } | ||||
| 
 | ||||
| } | ||||
| @ -1,124 +0,0 @@ | ||||
| package djmil.cordacheckers.gameproposal; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import djmil.cordacheckers.FlowResult; | ||||
| import djmil.cordacheckers.contracts.GameProposalCommand; | ||||
| import djmil.cordacheckers.gameboard.GameBoardView; | ||||
| import djmil.cordacheckers.states.GameBoardState; | ||||
| import djmil.cordacheckers.states.GameProposalState; | ||||
| import djmil.cordacheckers.states.Game.NotInvolved; | ||||
| 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.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.UtxoLedgerTransaction; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder; | ||||
| 
 | ||||
| import java.time.Duration; | ||||
| import java.time.Instant; | ||||
| 
 | ||||
| public class CommandFlow implements ClientStartableFlow { | ||||
| 
 | ||||
|     private final static Logger log = LoggerFactory.getLogger(CommandFlow.class); | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public JsonMarshallingService jsonMarshallingService; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public FlowEngine flowEngine; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public MemberLookup memberLookup;     | ||||
| 
 | ||||
|     @Override | ||||
|     @Suspendable | ||||
|     public String call(ClientRequestBody requestBody) { | ||||
|         try { | ||||
|             final CommandFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CommandFlowArgs.class); | ||||
|             final GameProposalCommand command = args.getCommand(); | ||||
| 
 | ||||
|             final StateAndRef<GameProposalState> utxoSarGameProposal = findUtxoGameProposal(args.getGameProposalUuid()); | ||||
| 
 | ||||
|             final UtxoSignedTransaction utxoDraft = prepareSignedTransaction(command, utxoSarGameProposal); | ||||
| 
 | ||||
|             final SecureHash utxoTrxId = this.flowEngine | ||||
|                 .subFlow( new CommitSubFlow(utxoDraft, command.getRespondent(utxoSarGameProposal)) ); | ||||
| 
 | ||||
|             if (command == GameProposalCommand.ACCEPT) { | ||||
|                 final GameBoardView viewGameBoard = prepareGameBoardView(utxoTrxId); | ||||
| 
 | ||||
|                 return new FlowResult(viewGameBoard, utxoTrxId)  | ||||
|                     .toJsonEncodedString(jsonMarshallingService); | ||||
|             } | ||||
| 
 | ||||
|             return new FlowResult(command+"ED", utxoTrxId)  // REJECT+ED | ||||
|                 .toJsonEncodedString(jsonMarshallingService); | ||||
|         }  | ||||
|         catch (Exception e) { | ||||
|             log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +  | ||||
|                         " because: " + e.getMessage()); | ||||
|             return new FlowResult(e).toJsonEncodedString(jsonMarshallingService); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     private StateAndRef<GameProposalState> findUtxoGameProposal (UUID uuidGameProposal) { | ||||
|         /* | ||||
|          * Get list of all unconsumed aka 'active' GameProposalStates, then filter by UUID. | ||||
|          * Note, this is an inefficient way to perform this operation if there are a large | ||||
|          * number of 'active' GameProposals exists in storage. | ||||
|          */ | ||||
|         return this.utxoLedgerService | ||||
|             .findUnconsumedStatesByType(GameProposalState.class) | ||||
|             .stream() | ||||
|             .filter(sar -> sar.getState().getContractState().getGameUuid().equals(uuidGameProposal)) | ||||
|             .reduce((a, b) -> {throw new IllegalStateException("Multiple states: " +a +", " +b);}) | ||||
|             .get(); | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     private UtxoSignedTransaction prepareSignedTransaction( | ||||
|         GameProposalCommand command, | ||||
|         StateAndRef<GameProposalState> sarGameProposal | ||||
|     ) { | ||||
|         UtxoTransactionBuilder trxBuilder = utxoLedgerService.createTransactionBuilder() | ||||
|             .setNotary(sarGameProposal.getState().getNotaryName()) | ||||
|             .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) | ||||
|             .addInputState(sarGameProposal.getRef()) | ||||
|             .addCommand(command) | ||||
|             .addSignatories(sarGameProposal.getState().getContractState().getParticipants()); | ||||
| 
 | ||||
|         if (command == GameProposalCommand.ACCEPT) { | ||||
|             trxBuilder = trxBuilder | ||||
|                 .addOutputState(new GameBoardState(sarGameProposal.getState().getContractState())); | ||||
|         } | ||||
| 
 | ||||
|         return trxBuilder.toSignedTransaction(); | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     private GameBoardView prepareGameBoardView(SecureHash utxoTrxId) throws NotInvolved { | ||||
|         final MemberX500Name myName = memberLookup.myInfo().getName(); | ||||
| 
 | ||||
|         final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService | ||||
|             .findLedgerTransaction(utxoTrxId); | ||||
| 
 | ||||
|         return new GameBoardView(utxoGameBoard, myName); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,24 +0,0 @@ | ||||
| package djmil.cordacheckers.gameproposal; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import djmil.cordacheckers.contracts.GameProposalCommand; | ||||
| 
 | ||||
| public class CommandFlowArgs { | ||||
|     private UUID gameProposalUuid; | ||||
|     private String command; | ||||
| 
 | ||||
|     // Serialisation service requires a default constructor | ||||
|     public CommandFlowArgs() { | ||||
|         this.gameProposalUuid = null; | ||||
|         this.command = null; | ||||
|     } | ||||
|      | ||||
|     public GameProposalCommand getCommand() { | ||||
|         return GameProposalCommand.valueOf(this.command); | ||||
|     } | ||||
| 
 | ||||
|     public UUID getGameProposalUuid() { | ||||
|         return this.gameProposalUuid; | ||||
|     } | ||||
| } | ||||
| @ -1,98 +0,0 @@ | ||||
| package djmil.cordacheckers.gameproposal; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import djmil.cordacheckers.contracts.GameProposalCommand; | ||||
| import djmil.cordacheckers.states.GameProposalState; | ||||
| import net.corda.v5.application.flows.CordaInject; | ||||
| import net.corda.v5.application.flows.InitiatedBy; | ||||
| import net.corda.v5.application.flows.ResponderFlow; | ||||
| import net.corda.v5.application.membership.MemberLookup; | ||||
| import net.corda.v5.application.messaging.FlowSession; | ||||
| import net.corda.v5.base.annotations.Suspendable; | ||||
| import net.corda.v5.base.exceptions.CordaRuntimeException; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| import net.corda.v5.ledger.utxo.UtxoLedgerService; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand; | ||||
| 
 | ||||
| @InitiatedBy(protocol = "game-proposal") | ||||
| public class CommitResponderFlow implements ResponderFlow { | ||||
|      | ||||
|     private final static Logger log = LoggerFactory.getLogger(CommitResponderFlow.class); | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public MemberLookup memberLookup; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public UtxoLedgerService utxoLedgerService; | ||||
|      | ||||
|     @Suspendable | ||||
|     @Override | ||||
|     public void call(FlowSession session) { | ||||
|         log.info("GameProposal: Commit responder flow"); | ||||
|         try { | ||||
|             UtxoTransactionValidator txValidator = ledgerTransaction -> { | ||||
|                 final GameProposalCommand command = ledgerTransaction.getCommands(GameProposalCommand.class).get(0); | ||||
| 
 | ||||
|                 final GameProposalState gameProposal = getGameProposal(ledgerTransaction);  | ||||
| 
 | ||||
|                 checkParticipants(session, gameProposal, command); | ||||
| 
 | ||||
|                 /* | ||||
|                  * Other checks / actions ? | ||||
|                  */ | ||||
| 
 | ||||
|                 log.info("Verified the transaction - " + ledgerTransaction.getId()); | ||||
|             }; | ||||
| 
 | ||||
|             UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService | ||||
|                     .receiveFinality(session, txValidator) | ||||
|                     .getTransaction(); | ||||
| 
 | ||||
|             log.info("Finished responder flow - " + finalizedSignedTransaction.getId()); | ||||
|         } | ||||
|         catch(Exception e) { | ||||
|             log.warn("Responder flow failed: ", e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     void checkParticipants( | ||||
|         FlowSession session, | ||||
|         GameProposalState gameProposal, | ||||
|         GameProposalCommand command | ||||
|     ) { | ||||
|         final MemberX500Name myName = memberLookup.myInfo().getName(); | ||||
|         final MemberX500Name otherName = session.getCounterparty(); | ||||
| 
 | ||||
|         if (command.getRespondent(gameProposal).compareTo(myName) != 0) | ||||
|             throw new CordaRuntimeException("Bad GameProposal acquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'"); | ||||
|          | ||||
|         if (command.getInitiator(gameProposal).compareTo(otherName) != 0) | ||||
|             throw new CordaRuntimeException("Bad GameProposal issuer: expected '"+otherName+"', actual '"+gameProposal.getIssuer()+"'"); | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     GameProposalState getGameProposal(UtxoLedgerTransaction trx) { | ||||
|         final GameProposalCommand command = trx.getCommands(GameProposalCommand.class).get(0); | ||||
|          | ||||
|         switch (command) { | ||||
|             case CREATE: | ||||
|                 return (GameProposalState)trx.getOutputContractStates().get(0); | ||||
| 
 | ||||
|             case ACCEPT: | ||||
|             case REJECT: | ||||
|             case CANCEL: | ||||
|                 return (GameProposalState)trx.getInputContractStates().get(0); | ||||
| 
 | ||||
|             default: | ||||
|                 throw new RuntimeException(GameProposalCommand.UNSUPPORTED_VALUE_OF +command); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,10 +1,20 @@ | ||||
| package djmil.cordacheckers.gameproposal; | ||||
| 
 | ||||
| import static java.util.Objects.requireNonNull; | ||||
| 
 | ||||
| import java.time.Duration; | ||||
| import java.time.Instant; | ||||
| import java.util.Arrays; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import djmil.cordacheckers.FlowResult; | ||||
| import djmil.cordacheckers.contracts.GameProposalCommand; | ||||
| import djmil.cordacheckers.contracts.GameCommand; | ||||
| import djmil.cordacheckers.gamestate.CommitSubFlow; | ||||
| import djmil.cordacheckers.gamestate.FlowResponce; | ||||
| import djmil.cordacheckers.gamestate.View; | ||||
| import djmil.cordacheckers.gamestate.ViewBuilder; | ||||
| import djmil.cordacheckers.states.GameProposalState; | ||||
| import djmil.cordacheckers.states.Piece; | ||||
| import net.corda.v5.application.flows.ClientRequestBody; | ||||
| @ -20,14 +30,6 @@ import net.corda.v5.ledger.common.NotaryLookup; | ||||
| import net.corda.v5.ledger.utxo.UtxoLedgerService; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | ||||
| import net.corda.v5.membership.MemberInfo; | ||||
| import net.corda.v5.membership.NotaryInfo; | ||||
| 
 | ||||
| import static java.util.Objects.requireNonNull; | ||||
| 
 | ||||
| import java.time.Duration; | ||||
| import java.time.Instant; | ||||
| import java.util.Arrays; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| public class CreateFlow implements ClientStartableFlow{ | ||||
| 
 | ||||
| @ -43,7 +45,7 @@ public class CreateFlow implements ClientStartableFlow{ | ||||
|      public NotaryLookup notaryLookup; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public UtxoLedgerService ledgerService; | ||||
|      public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public FlowEngine flowEngine; | ||||
| @ -51,24 +53,34 @@ public class CreateFlow implements ClientStartableFlow{ | ||||
|      @Suspendable | ||||
|      @Override | ||||
|      public String call(ClientRequestBody requestBody) { | ||||
|           SecureHash gameStateUtxoTrxId = null; | ||||
| 
 | ||||
|           try { | ||||
|                log.info("flow: Create Game Proposal"); | ||||
|                final GameProposalCommand command = GameProposalCommand.CREATE; | ||||
|                final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_CREATE); | ||||
| 
 | ||||
|                final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody); | ||||
|                 | ||||
|                final UtxoSignedTransaction trx = prepareSignedTransaction(newGameProposal); | ||||
|                final UtxoSignedTransaction utxoCandidate = utxoLedgerService.createTransactionBuilder() | ||||
|                     .addCommand(command) | ||||
|                     .addOutputState(newGameProposal) | ||||
|                     .addSignatories(newGameProposal.getParticipants()) | ||||
|                     .setNotary(notaryLookup.getNotaryServices().iterator().next().getName()) | ||||
|                     .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) | ||||
|                     .toSignedTransaction(); | ||||
| 
 | ||||
|                final SecureHash trxId = this.flowEngine | ||||
|                     .subFlow(new CommitSubFlow(trx, command.getRespondent(newGameProposal))); | ||||
|                gameStateUtxoTrxId = this.flowEngine | ||||
|                     .subFlow(new CommitSubFlow(utxoCandidate, command.getObserver(newGameProposal))); | ||||
| 
 | ||||
|                return new FlowResult(newGameProposal.getGameUuid(), trxId) | ||||
|                final View gameStateView = this.flowEngine | ||||
|                     .subFlow(new ViewBuilder(gameStateUtxoTrxId)); | ||||
| 
 | ||||
|                return new FlowResponce(gameStateView, gameStateUtxoTrxId) | ||||
|                     .toJsonEncodedString(jsonMarshallingService); | ||||
|           }  | ||||
|           catch (Exception e) { | ||||
|                log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody +  | ||||
|                          " because: " + e.getMessage()); | ||||
|                return new FlowResult(e).toJsonEncodedString(jsonMarshallingService); | ||||
|                log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); | ||||
|                return new FlowResponce(e, gameStateUtxoTrxId) | ||||
|                     .toJsonEncodedString(jsonMarshallingService); | ||||
|           } | ||||
|      } | ||||
| 
 | ||||
| @ -99,16 +111,4 @@ public class CreateFlow implements ClientStartableFlow{ | ||||
|           ); | ||||
|      } | ||||
| 
 | ||||
|      @Suspendable | ||||
|      private UtxoSignedTransaction prepareSignedTransaction(GameProposalState outputGameProposalState) {      | ||||
|           final NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next(); | ||||
| 
 | ||||
|           return ledgerService.createTransactionBuilder() | ||||
|                .setNotary(notary.getName()) | ||||
|                .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) | ||||
|                .addOutputState(outputGameProposalState) | ||||
|                .addCommand(GameProposalCommand.CREATE) | ||||
|                .addSignatories(outputGameProposalState.getParticipants()) | ||||
|                .toSignedTransaction(); | ||||
|      } | ||||
| } | ||||
|  | ||||
| @ -1,49 +0,0 @@ | ||||
| package djmil.cordacheckers.gameproposal; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import djmil.cordacheckers.FlowResult; | ||||
| import djmil.cordacheckers.states.GameProposalState; | ||||
| 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.marshalling.JsonMarshallingService; | ||||
| import net.corda.v5.base.annotations.Suspendable; | ||||
| import net.corda.v5.ledger.utxo.UtxoLedgerService; | ||||
| 
 | ||||
| public class ListFlow implements ClientStartableFlow { | ||||
| 
 | ||||
|     private final static Logger log = LoggerFactory.getLogger(ListFlow.class); | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public JsonMarshallingService jsonMarshallingService; | ||||
| 
 | ||||
|     @Suspendable | ||||
|     @Override | ||||
|     public String call(ClientRequestBody requestBody) { | ||||
|         try { | ||||
|             log.info("ListFlow.call() called"); | ||||
| 
 | ||||
|             // Queries the VNode's vault for unconsumed states and converts the resulting  | ||||
|             // List<StateAndRef<GameProposalState>> to a _serializable_ List<ListItem> DTO  | ||||
|             List<ListItem> unconsumedGameProposaList = utxoLedgerService | ||||
|                 .findUnconsumedStatesByType(GameProposalState.class) | ||||
|                 .stream() | ||||
|                 .map( stateAndRef -> new ListItem(stateAndRef.getState().getContractState()) ) | ||||
|                 .collect(Collectors.toList()); | ||||
| 
 | ||||
|             return new FlowResult(unconsumedGameProposaList).toJsonEncodedString(jsonMarshallingService); | ||||
|         } catch (Exception e) { | ||||
|             log.warn("ListFlow failed to process utxo request body " + requestBody + " because: " + e.getMessage()); | ||||
|             return new FlowResult(e).toJsonEncodedString(jsonMarshallingService); | ||||
|         } | ||||
|     } | ||||
|      | ||||
| } | ||||
| @ -1,36 +0,0 @@ | ||||
| package djmil.cordacheckers.gameproposal; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import djmil.cordacheckers.states.GameProposalState; | ||||
| import djmil.cordacheckers.states.Piece; | ||||
| 
 | ||||
| // Class to hold results of the List flow. | ||||
| // JsonMarshallingService can only serialize simple classes that the underlying Jackson serializer recognises,  | ||||
| // hence creating a DTO style object which consists only of Strings and a UUIDs. It is possible to create custom | ||||
| // serializers for the JsonMarshallingService in the future. | ||||
| 
 | ||||
| public class ListItem { | ||||
|     public final String issuer; | ||||
|     public final String acquier; | ||||
|     public final Piece.Color acquierColor; | ||||
|     public final String message; | ||||
|     public final UUID id; | ||||
| 
 | ||||
|     // Serialisation service requires a default constructor | ||||
|     public ListItem() {  | ||||
|         this.issuer = null; | ||||
|         this.acquier = null; | ||||
|         this.acquierColor = null; | ||||
|         this.message = null; | ||||
|         this.id = null; | ||||
|     } | ||||
| 
 | ||||
|     public ListItem(GameProposalState state) { | ||||
|         this.issuer  = state.getIssuer().getCommonName(); | ||||
|         this.acquier = state.getAcquier().getCommonName(); | ||||
|         this.acquierColor = state.getAcquierColor(); | ||||
|         this.message = state.getMessage(); | ||||
|         this.id      = state.getGameUuid(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,86 @@ | ||||
| package djmil.cordacheckers.gameproposal; | ||||
| 
 | ||||
| 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.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.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.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; | ||||
| 
 | ||||
| public class RejectFlow implements ClientStartableFlow{ | ||||
| 
 | ||||
|      private final static Logger log = LoggerFactory.getLogger(RejectFlow.class); | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public JsonMarshallingService jsonMarshallingService; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public MemberLookup memberLookup; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public NotaryLookup notaryLookup; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|      @CordaInject | ||||
|      public FlowEngine flowEngine; | ||||
| 
 | ||||
|      @Suspendable | ||||
|      @Override | ||||
|      public String call(ClientRequestBody requestBody) { | ||||
|           SecureHash gameStateUtxoTrxId = null; | ||||
| 
 | ||||
|           try { | ||||
|                final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_REJECT); | ||||
| 
 | ||||
|                final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody()); | ||||
| 
 | ||||
|                final StateAndRef<GameState> inputState = this.flowEngine | ||||
|                     .subFlow(new GetFlow(gameStateUuid)); | ||||
| 
 | ||||
|                final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder() | ||||
|                     .addCommand(command) | ||||
|                     .addInputState(inputState.getRef()) | ||||
|                     .addSignatories(inputState.getState().getContractState().getParticipants()) | ||||
|                     .setNotary(inputState.getState().getNotaryName()) | ||||
|                     .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) | ||||
|                     .toSignedTransaction(); | ||||
| 
 | ||||
|                gameStateUtxoTrxId = this.flowEngine | ||||
|                     .subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState))); | ||||
| 
 | ||||
|                final View gameStateView = this.flowEngine | ||||
|                     .subFlow(new ViewBuilder(gameStateUtxoTrxId)); | ||||
| 
 | ||||
|                return new FlowResponce(gameStateView, gameStateUtxoTrxId) | ||||
|                     .toJsonEncodedString(jsonMarshallingService); | ||||
|           }  | ||||
|           catch (Exception e) { | ||||
|                log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); | ||||
|                return new FlowResponce(e, gameStateUtxoTrxId) | ||||
|                     .toJsonEncodedString(jsonMarshallingService); | ||||
|           } | ||||
|      } | ||||
| 
 | ||||
| } | ||||
| @ -1,29 +0,0 @@ | ||||
| package djmil.cordacheckers.gameresult; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import djmil.cordacheckers.states.GameResultState; | ||||
| import djmil.cordacheckers.states.Piece; | ||||
| 
 | ||||
| public class GameResultView { | ||||
|     public final String whitePlayerName; | ||||
|     public final String blackPlayerName; | ||||
|     public final Piece.Color victoryColor; | ||||
|     public final UUID id; | ||||
| 
 | ||||
|     // Serialisation service requires a default constructor | ||||
|     public GameResultView() { | ||||
|         this.whitePlayerName = null; | ||||
|         this.blackPlayerName = null; | ||||
|         this.victoryColor = null; | ||||
|         this.id = null; | ||||
|     } | ||||
| 
 | ||||
|     public GameResultView(GameResultState gameResultState) { | ||||
|         this.whitePlayerName = gameResultState.getWhitePlayerName().getCommonName(); | ||||
|         this.blackPlayerName = gameResultState.getBlackPlayerName().getCommonName(); | ||||
|         this.victoryColor = gameResultState.getVictoryColor(); | ||||
|         this.id = gameResultState.getGameUuid(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package djmil.cordacheckers.gameproposal; | ||||
| package djmil.cordacheckers.gamestate; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| @ -17,16 +17,16 @@ import net.corda.v5.crypto.SecureHash; | ||||
| import net.corda.v5.ledger.utxo.UtxoLedgerService; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | ||||
| 
 | ||||
| @InitiatingFlow(protocol = "game-proposal") | ||||
| @InitiatingFlow(protocol = "gamestate-commit") | ||||
| public class CommitSubFlow implements SubFlow<SecureHash> { | ||||
| 
 | ||||
|     private final static Logger log = LoggerFactory.getLogger(CommitSubFlow.class); | ||||
|     private final UtxoSignedTransaction signedTransaction; | ||||
|     private final MemberX500Name respondenName; | ||||
|     private final UtxoSignedTransaction utxTrxCandidate; | ||||
|     private final MemberX500Name counterpartyName; | ||||
| 
 | ||||
|     public CommitSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name respondenName) { | ||||
|         this.signedTransaction = signedTransaction; | ||||
|         this.respondenName = respondenName; | ||||
|     public CommitSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name counterpartyName) { | ||||
|         this.utxTrxCandidate = signedTransaction; | ||||
|         this.counterpartyName = counterpartyName; | ||||
|     } | ||||
| 
 | ||||
|     @CordaInject | ||||
| @ -38,9 +38,9 @@ public class CommitSubFlow implements SubFlow<SecureHash> { | ||||
|     @Override | ||||
|     @Suspendable | ||||
|     public SecureHash call() { | ||||
|         log.info("GamePropsal commit started"); | ||||
|         log.info("GameState commit started"); | ||||
| 
 | ||||
|         final FlowSession session = flowMessaging.initiateFlow(this.respondenName); | ||||
|         final FlowSession session = flowMessaging.initiateFlow(this.counterpartyName); | ||||
| 
 | ||||
|         /* | ||||
|          * Calls the Corda provided finalise() function which gather signatures from the counterparty, | ||||
| @ -50,11 +50,11 @@ public class CommitSubFlow implements SubFlow<SecureHash> { | ||||
|         final List<FlowSession> sessionsList = Arrays.asList(session); | ||||
| 
 | ||||
|         final SecureHash trxId = ledgerService | ||||
|             .finalize(this.signedTransaction, sessionsList) | ||||
|             .finalize(this.utxTrxCandidate, sessionsList) | ||||
|             .getTransaction() | ||||
|             .getId(); | ||||
| 
 | ||||
|         log.info("GamePropsal commit " +trxId); | ||||
|         log.info("GameState commit " +trxId); | ||||
|         return trxId; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,124 @@ | ||||
| package djmil.cordacheckers.gamestate; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import djmil.cordacheckers.contracts.GameCommand; | ||||
| import djmil.cordacheckers.states.GameBoardState; | ||||
| import djmil.cordacheckers.states.GameProposalState; | ||||
| import djmil.cordacheckers.states.GameResultState; | ||||
| import djmil.cordacheckers.states.GameState; | ||||
| import net.corda.v5.application.flows.CordaInject; | ||||
| import net.corda.v5.application.flows.InitiatedBy; | ||||
| import net.corda.v5.application.flows.ResponderFlow; | ||||
| import net.corda.v5.application.membership.MemberLookup; | ||||
| import net.corda.v5.application.messaging.FlowSession; | ||||
| import net.corda.v5.base.annotations.Suspendable; | ||||
| import net.corda.v5.base.exceptions.CordaRuntimeException; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| import net.corda.v5.ledger.utxo.UtxoLedgerService; | ||||
| 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 { | ||||
|      | ||||
|     private final static Logger log = LoggerFactory.getLogger(CommitSubFlowResponder.class); | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public MemberLookup memberLookup; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public UtxoLedgerService utxoLedgerService; | ||||
|      | ||||
|     @Suspendable | ||||
|     @Override | ||||
|     public void call(FlowSession session) { | ||||
|         UtxoTransactionValidator txValidator = trxToValidate -> { | ||||
|             try { | ||||
|                 final GameCommand gameCommand = getSingleCommand(trxToValidate, GameCommand.class); | ||||
|                 final GameState gameState = getGameStateFromTransaction(trxToValidate, gameCommand); | ||||
| 
 | ||||
|                 checkParticipants(session, gameCommand, gameState); | ||||
| 
 | ||||
|                 /* | ||||
|                  * Other checks / actions ? | ||||
|                  */ | ||||
| 
 | ||||
|                 log.info("Approval for " + trxToValidate.getId()); | ||||
|             } catch (Exception e) { | ||||
|                 log.warn("Validation has failed. " +e.getMessage()); | ||||
|                 // We have to re-trow an exception to indicate validation failure | ||||
|                 throw new CordaRuntimeException(e.getClass().getCanonicalName(), e.getMessage(), e.getCause()); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         try { | ||||
|             this.utxoLedgerService | ||||
|                 .receiveFinality(session, txValidator); | ||||
|         } catch (Exception e) { | ||||
|             // Log Corda's specific message about finality validation failure | ||||
|             log.warn(e.getMessage());  | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @param gameStateTransaction an utxo ledger transaction, that involves GameState | ||||
|      * @param command | ||||
|      * @return the most recent (from perspective of session participants validation) GameState for a given transaction | ||||
|      *  | ||||
|      * @see djmil.cordacheckers.gamestate.ViewBuilder#getGameStateFromTransaction(UtxoLedgerTransaction, GameCommand) | ||||
|      */ | ||||
|     @Suspendable | ||||
|     GameState getGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) { | ||||
|         switch (command.action) { | ||||
|             case GAME_PROPOSAL_CREATE: | ||||
|                 return getSingleOutputState(gameStateTransaction, GameProposalState.class); | ||||
|              | ||||
|             case GAME_PROPOSAL_ACCEPT: | ||||
|             case GAME_PROPOSAL_REJECT: | ||||
|             case GAME_PROPOSAL_CANCEL: | ||||
|                 return getSingleInputState(gameStateTransaction, GameProposalState.class); | ||||
| 
 | ||||
|             case GAME_BOARD_MOVE: | ||||
|                 return getSingleOutputState(gameStateTransaction, GameBoardState.class); | ||||
| 
 | ||||
|             case GAME_BOARD_SURRENDER: | ||||
|             case GAME_RESULT_CREATE: | ||||
|                 return getSingleOutputState(gameStateTransaction, GameResultState.class); | ||||
|         } | ||||
| 
 | ||||
|         throw new GameCommand.ActionException(); | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     void checkParticipants(FlowSession session, GameCommand gameCommand, GameState outputGameState) throws ParticipantException { | ||||
|         final var myName = memberLookup.myInfo().getName(); | ||||
|         final var outputStateConterparty = outputGameState.getCounterpartyName(myName); | ||||
|         final var sessionConterparty = session.getCounterparty(); | ||||
|          | ||||
|         final var actor  = gameCommand.getActor(outputGameState); | ||||
|         final var observer = gameCommand.getObserver(outputGameState); | ||||
| 
 | ||||
|         if (outputStateConterparty.compareTo(sessionConterparty) != 0) | ||||
|             throw new ParticipantException("Counterparty", sessionConterparty, outputStateConterparty); | ||||
| 
 | ||||
|         if (actor.compareTo(sessionConterparty) != 0) | ||||
|            throw new ParticipantException("Actor", sessionConterparty, actor); | ||||
| 
 | ||||
|         if (observer.compareTo(myName) != 0) | ||||
|             throw new ParticipantException("Observer", myName, observer); | ||||
|     } | ||||
| 
 | ||||
|     public static class ParticipantException extends Exception { | ||||
|         public ParticipantException(String role, MemberX500Name expected, MemberX500Name actual) { | ||||
|             super("Bad participants. " +role +" role: expected '" +expected +"', actual '" +actual +"'"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,28 +1,22 @@ | ||||
| package djmil.cordacheckers; | ||||
| package djmil.cordacheckers.gamestate; | ||||
| 
 | ||||
| import net.corda.v5.application.marshalling.JsonMarshallingService; | ||||
| import net.corda.v5.crypto.SecureHash; | ||||
| 
 | ||||
| public class FlowResult { | ||||
|     public final Object successStatus; | ||||
| public class FlowResponce { | ||||
|     public final View successStatus; | ||||
|     public final SecureHash transactionId; | ||||
|     public final String failureStatus; | ||||
| 
 | ||||
|     public FlowResult(Object success) { | ||||
|         this.successStatus = success; | ||||
|         this.transactionId = null; | ||||
|         this.failureStatus = null; | ||||
|     } | ||||
| 
 | ||||
|     public FlowResult(Object success, SecureHash transactionId) { | ||||
|     public FlowResponce(View success, SecureHash transactionId) { | ||||
|         this.successStatus = success; | ||||
|         this.transactionId = transactionId; | ||||
|         this.failureStatus = null; | ||||
|     } | ||||
| 
 | ||||
|     public FlowResult(Exception exception) { | ||||
|     public FlowResponce(Exception exception, SecureHash transactionId) { | ||||
|         this.successStatus = null; | ||||
|         this.transactionId = null; | ||||
|         this.transactionId = transactionId; | ||||
|         this.failureStatus = exception.getMessage(); | ||||
|     } | ||||
| 
 | ||||
| @ -0,0 +1,86 @@ | ||||
| package djmil.cordacheckers.gamestate; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| 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.flows.SubFlow; | ||||
| 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; | ||||
| 
 | ||||
| public class GetFlow implements ClientStartableFlow, SubFlow<StateAndRef<GameState>> { | ||||
| 
 | ||||
|     private final static Logger log = LoggerFactory.getLogger(GetFlow.class); | ||||
|     private UUID subflowGameStateUuid = null; | ||||
| 
 | ||||
|     public GetFlow() { | ||||
|         this.subflowGameStateUuid = null; | ||||
|     } | ||||
| 
 | ||||
|     public GetFlow(UUID gameStateUuid) { | ||||
|         this.subflowGameStateUuid = gameStateUuid; | ||||
|     } | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public JsonMarshallingService jsonMarshallingService; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public FlowEngine flowEngine; | ||||
| 
 | ||||
|     @Override | ||||
|     @Suspendable | ||||
|     public StateAndRef<GameState> call() {              // <<-- SubFlow<T> entry | ||||
|         return findGameStateUtxoSar(this.subflowGameStateUuid); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Suspendable | ||||
|     public String call(ClientRequestBody requestBody) { // <<-- ClientStartableFlow entry | ||||
|         SecureHash gameStateUtxoTrxId = null; | ||||
| 
 | ||||
|         try { | ||||
|             final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody()); | ||||
| 
 | ||||
|             gameStateUtxoTrxId = findGameStateUtxoSar(gameStateUuid) | ||||
|                 .getRef().getTransactionId(); | ||||
| 
 | ||||
|             final View gameStateView = this.flowEngine | ||||
|                 .subFlow(new ViewBuilder(gameStateUtxoTrxId)); | ||||
| 
 | ||||
|             return new FlowResponce(gameStateView, gameStateUtxoTrxId) | ||||
|                 .toJsonEncodedString(jsonMarshallingService); | ||||
|         } | ||||
|         catch (Exception e) { | ||||
|             log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); | ||||
|             return new FlowResponce(e, gameStateUtxoTrxId).toJsonEncodedString(jsonMarshallingService); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     StateAndRef<GameState> findGameStateUtxoSar(UUID gameStateUuid) { | ||||
|         /* | ||||
|          * Get list of all unconsumed aka 'active' GameStates, then filter by UUID. | ||||
|          * Note, this is an inefficient way to perform this operation if there are a large | ||||
|          * number of 'active' GameProposals exists in storage. | ||||
|          */ | ||||
|         return this.utxoLedgerService | ||||
|             .findUnconsumedStatesByType(GameState.class) | ||||
|             .stream() | ||||
|             .filter(sar -> sar.getState().getContractState().getGameUuid().equals(gameStateUuid)) | ||||
|             .reduce((a, b) -> {throw new IllegalStateException("Multiple GameStates found: " +a +", " +b);}) | ||||
|             .get(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,67 @@ | ||||
| package djmil.cordacheckers.gamestate; | ||||
| 
 | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| 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.UtxoLedgerService; | ||||
| 
 | ||||
| public class ListFlow implements ClientStartableFlow { | ||||
| 
 | ||||
|     private final static Logger log = LoggerFactory.getLogger(ListFlow.class); | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public JsonMarshallingService jsonMarshallingService; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public FlowEngine flowEngine; | ||||
| 
 | ||||
|     @Suspendable | ||||
|     @Override | ||||
|     public String call(ClientRequestBody requestBody) { | ||||
|         try { | ||||
|             List<SecureHash> gameStateUtxoTrxIdList = getGameStateUtxoTrxIdList(); | ||||
| 
 | ||||
|             // TODO: try in one single list  | ||||
|             List<View> gameStateViewList = new LinkedList<View>(); | ||||
| 
 | ||||
|             for (SecureHash gameStateUtxoTrxId: gameStateUtxoTrxIdList) {                 | ||||
|                 final View gameStateView = this.flowEngine | ||||
|                     .subFlow(new ViewBuilder(gameStateUtxoTrxId)); | ||||
|                  | ||||
|                 gameStateViewList.add(gameStateView); | ||||
|             } | ||||
| 
 | ||||
|             return new ListFlowResponce(gameStateViewList) | ||||
|                 .toJsonEncodedString(jsonMarshallingService); | ||||
|         } catch (Exception e) { | ||||
|             log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); | ||||
|             return new ListFlowResponce(e) | ||||
|                 .toJsonEncodedString(jsonMarshallingService); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     List<SecureHash> getGameStateUtxoTrxIdList() { | ||||
|         return utxoLedgerService | ||||
|             .findUnconsumedStatesByType(GameState.class) | ||||
|             .stream() | ||||
|             .map( stateAndRef -> stateAndRef.getRef().getTransactionId() ) | ||||
|             .collect(Collectors.toList()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,25 @@ | ||||
| package djmil.cordacheckers.gamestate; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import net.corda.v5.application.marshalling.JsonMarshallingService; | ||||
| 
 | ||||
| public class ListFlowResponce { | ||||
|     public final List<View> successStatus; | ||||
|     public final String failureStatus; | ||||
| 
 | ||||
|     public ListFlowResponce(List<View> success) { | ||||
|         this.successStatus = success; | ||||
|         this.failureStatus = null; | ||||
|     } | ||||
| 
 | ||||
|     public ListFlowResponce(Exception exception) { | ||||
|         this.successStatus = null; | ||||
|         this.failureStatus = exception.getMessage(); | ||||
|     } | ||||
| 
 | ||||
|     public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) { | ||||
|         return jsonMarshallingService.format(this); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @ -0,0 +1,96 @@ | ||||
| package djmil.cordacheckers.gamestate; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import djmil.cordacheckers.states.GameBoardState; | ||||
| import djmil.cordacheckers.states.GameProposalState; | ||||
| import djmil.cordacheckers.states.GameResultState; | ||||
| import djmil.cordacheckers.states.Piece; | ||||
| import net.corda.v5.base.types.MemberX500Name; | ||||
| 
 | ||||
| 
 | ||||
| // GameBoard from the player's point of view | ||||
| public class View { | ||||
|     public static enum Status { | ||||
|         GAME_PROPOSAL_WAIT_FOR_OPPONENT, | ||||
|         GAME_PROPOSAL_WAIT_FOR_YOU, | ||||
|         GAME_PROPOSAL_REJECTED, | ||||
|         GAME_PROPOSAL_CANCELED, | ||||
|          | ||||
|         GAME_BOARD_WAIT_FOR_OPPONENT, | ||||
|         GAME_BOARD_WAIT_FOR_YOU, | ||||
| 
 | ||||
|         GAME_RESULT_YOU_WON, | ||||
|         GAME_RESULT_YOU_LOOSE; | ||||
|     } | ||||
|     public final Status status; | ||||
| 
 | ||||
|     public final String opponentName; | ||||
|     public final Piece.Color opponentColor; | ||||
| 
 | ||||
|     public final Map<Integer, Piece> board; | ||||
|     public final Integer moveNumber; | ||||
|     public final List<Integer> previousMove; | ||||
| 
 | ||||
|     public final String message; | ||||
|     public final UUID uuid; | ||||
| 
 | ||||
|     // Serialisation service requires a default constructor | ||||
|     public View() { | ||||
|         this.status = null; | ||||
|         this.opponentName = null; | ||||
|         this.opponentColor = null; | ||||
|         this.board = null; | ||||
|         this.moveNumber = null; | ||||
|         this.previousMove = null; | ||||
|         this.message = null; | ||||
|         this.uuid = null; | ||||
|     } | ||||
| 
 | ||||
|     public View(View.Status status, GameProposalState gameProposal, MemberX500Name myName) { | ||||
|         this.status = status; | ||||
|         this.opponentName = gameProposal.getCounterpartyName(myName).getCommonName(); | ||||
|         this.opponentColor = gameProposal.getCounterpartyColor(myName); | ||||
|         this.board = null; | ||||
|         this.moveNumber = null; | ||||
|         this.previousMove = null; | ||||
|         this.message = gameProposal.getMessage(); | ||||
|         this.uuid = gameProposal.getGameUuid(); | ||||
|     } | ||||
| 
 | ||||
|     public View(View.Status status, GameBoardState gameBoard, MemberX500Name myName) { | ||||
|         this.status = status; | ||||
|         this.opponentName = gameBoard.getCounterpartyName(myName).getCommonName(); | ||||
|         this.opponentColor = gameBoard.getCounterpartyColor(myName); | ||||
|         this.board = gameBoard.getBoard(); | ||||
|         this.moveNumber = gameBoard.getMoveNumber(); | ||||
|         this.previousMove = null; | ||||
|         this.message = gameBoard.getMessage(); | ||||
|         this.uuid = gameBoard.getGameUuid(); | ||||
|     } | ||||
| 
 | ||||
|     public View(View.Status status, GameBoardState gameBoard, List<Integer> previousMove, MemberX500Name myName) { | ||||
|         this.status = status; | ||||
|         this.opponentName = gameBoard.getCounterpartyName(myName).getCommonName(); | ||||
|         this.opponentColor = gameBoard.getCounterpartyColor(myName); | ||||
|         this.board = gameBoard.getBoard(); | ||||
|         this.moveNumber = gameBoard.getMoveNumber(); | ||||
|         this.previousMove = previousMove; | ||||
|         this.message = gameBoard.getMessage(); | ||||
|         this.uuid = gameBoard.getGameUuid(); | ||||
|     } | ||||
| 
 | ||||
|     public View(View.Status status, GameResultState gameResult, MemberX500Name myName) { | ||||
|         this.status = status; | ||||
|         this.opponentName = gameResult.getCounterpartyName(myName).getCommonName(); | ||||
|         this.opponentColor = gameResult.getCounterpartyColor(myName); | ||||
|         this.board = null; | ||||
|         this.moveNumber = null; | ||||
|         this.previousMove = null; | ||||
|         this.message = gameResult.getMessage(); | ||||
|         this.uuid = gameResult.getGameUuid(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,147 @@ | ||||
| package djmil.cordacheckers.gamestate; | ||||
| 
 | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState; | ||||
| import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState; | ||||
| 
 | ||||
| import djmil.cordacheckers.contracts.GameCommand; | ||||
| import djmil.cordacheckers.states.GameBoardState; | ||||
| import djmil.cordacheckers.states.GameProposalState; | ||||
| 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.crypto.SecureHash; | ||||
| import net.corda.v5.ledger.utxo.UtxoLedgerService; | ||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||
| 
 | ||||
| /* | ||||
|  * A view on a ledger transaction that has GameState involved in it, from perspective of a concrete player | ||||
|  */  | ||||
| public class ViewBuilder implements SubFlow<View> { | ||||
|     private final SecureHash gameStateUtxoTrxId; | ||||
| 
 | ||||
|     public ViewBuilder (SecureHash gameStateUtxoTrxId) { | ||||
|         this.gameStateUtxoTrxId = gameStateUtxoTrxId; | ||||
|     } | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public UtxoLedgerService utxoLedgerService; | ||||
| 
 | ||||
|     @CordaInject | ||||
|     public MemberLookup memberLookup; | ||||
| 
 | ||||
|     @Override | ||||
|     @Suspendable | ||||
|     public View call() { | ||||
|         final UtxoLedgerTransaction gameStateUtxo = this.utxoLedgerService | ||||
|             .findLedgerTransaction(gameStateUtxoTrxId); | ||||
| 
 | ||||
|         return buildGameStateView(gameStateUtxo); | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     View buildGameStateView(UtxoLedgerTransaction gameStateUtxo) { | ||||
|         MemberX500Name myName = memberLookup.myInfo().getName(); | ||||
| 
 | ||||
|         final GameCommand command = getSingleCommand(gameStateUtxo, GameCommand.class); | ||||
|         final GameState state = getGameStateFromTransaction(gameStateUtxo, command); | ||||
|         final View.Status viewStatus = action2status(command, state, myName); | ||||
| 
 | ||||
|         switch (command.action) { | ||||
|             case GAME_PROPOSAL_CREATE: | ||||
|             case GAME_PROPOSAL_CANCEL: | ||||
|             case GAME_PROPOSAL_REJECT: | ||||
|                 if (state instanceof GameProposalState) | ||||
|                     return new View(viewStatus, (GameProposalState)state, myName); | ||||
|                 break; | ||||
| 
 | ||||
|             case GAME_PROPOSAL_ACCEPT: | ||||
|             case GAME_BOARD_MOVE: | ||||
|                 if (state instanceof GameBoardState) | ||||
|                     return new View(viewStatus, (GameBoardState)state, command.move, myName); | ||||
|                 break; | ||||
|              | ||||
|             case GAME_BOARD_SURRENDER: | ||||
|             case GAME_RESULT_CREATE: | ||||
|                 if (state instanceof GameResultState) | ||||
|                     return new View(viewStatus, (GameResultState)state, myName); | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         throw new GameCommand.ActionException(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @param gameStateTransaction an utxo ledger transaction, that involves GameState | ||||
|      * @param command | ||||
|      * @return the most recent (from perspective of building a GameView) GameState for a given transaction | ||||
|      *  | ||||
|      * @see djmil.cordacheckers.gamestate.CommitSubFlowResponder#getGameStateFromTransaction(UtxoLedgerTransaction, GameCommand) | ||||
|      */ | ||||
|     @Suspendable | ||||
|     GameState getGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) { | ||||
|         switch (command.action) { | ||||
|             case GAME_PROPOSAL_CREATE: | ||||
|                 return getSingleOutputState(gameStateTransaction, GameProposalState.class); | ||||
|              | ||||
|             case GAME_PROPOSAL_REJECT: | ||||
|             case GAME_PROPOSAL_CANCEL: | ||||
|                 return getSingleInputState(gameStateTransaction, GameProposalState.class); | ||||
| 
 | ||||
|             case GAME_PROPOSAL_ACCEPT: | ||||
|             case GAME_BOARD_MOVE: | ||||
|                 return getSingleOutputState(gameStateTransaction, GameBoardState.class); | ||||
|              | ||||
|             case GAME_BOARD_SURRENDER: | ||||
|             case GAME_RESULT_CREATE: | ||||
|                 return getSingleOutputState(gameStateTransaction, GameResultState.class); | ||||
|         } | ||||
| 
 | ||||
|         throw new GameCommand.ActionException(); | ||||
|     } | ||||
| 
 | ||||
|     @Suspendable | ||||
|     View.Status action2status(GameCommand command, GameState state, MemberX500Name myName) { | ||||
|         final boolean myAction = command.getActor(state).compareTo(myName) == 0; | ||||
| 
 | ||||
|         switch (command.action) { | ||||
|             case GAME_PROPOSAL_CREATE: | ||||
|                 if (myAction) | ||||
|                     return View.Status.GAME_PROPOSAL_WAIT_FOR_OPPONENT; | ||||
|                 else | ||||
|                     return View.Status.GAME_PROPOSAL_WAIT_FOR_YOU; | ||||
|                      | ||||
|             case GAME_PROPOSAL_REJECT: | ||||
|                 return View.Status.GAME_PROPOSAL_REJECTED; | ||||
| 
 | ||||
|             case GAME_PROPOSAL_CANCEL: | ||||
|                 return View.Status.GAME_PROPOSAL_CANCELED; | ||||
|              | ||||
|             case GAME_PROPOSAL_ACCEPT: | ||||
|             case GAME_BOARD_MOVE: | ||||
|                 if (myAction) | ||||
|                     return View.Status.GAME_BOARD_WAIT_FOR_YOU; | ||||
|                 else | ||||
|                     return View.Status.GAME_BOARD_WAIT_FOR_OPPONENT; | ||||
| 
 | ||||
|             case GAME_BOARD_SURRENDER: | ||||
|                 if (myAction) | ||||
|                     return View.Status.GAME_RESULT_YOU_LOOSE; | ||||
|                 else | ||||
|                     return View.Status.GAME_RESULT_YOU_WON;  | ||||
|              | ||||
|             case GAME_RESULT_CREATE: | ||||
|                 if (myAction) | ||||
|                     return View.Status.GAME_RESULT_YOU_WON; | ||||
|                 else | ||||
|                     return View.Status.GAME_RESULT_YOU_LOOSE;  | ||||
|         } | ||||
| 
 | ||||
|         throw new GameCommand.ActionException(); | ||||
|     } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user