DRAW: request, accept, reject
- tests for draw - ranking updates, to suuport draw results - replaca GameCommand.getCounterparty() with GameState.getOpponent(myName) makes code much readable and maintainable - uuid check in a contract - better exception handling in corda flows
This commit is contained in:
		
							parent
							
								
									5589c8ebe6
								
							
						
					
					
						commit
						5168e38710
					
				@ -32,7 +32,7 @@ import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameBoardMove;
 | 
				
			|||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameProposalCreate;
 | 
					import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameProposalCreate;
 | 
				
			||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.RspGameState;
 | 
					import djmil.cordacheckers.cordaclient.dao.flow.arguments.RspGameState;
 | 
				
			||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.RspGameStateList;
 | 
					import djmil.cordacheckers.cordaclient.dao.flow.arguments.RspGameStateList;
 | 
				
			||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.RspRankList;
 | 
					import djmil.cordacheckers.cordaclient.dao.flow.arguments.RspRankMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Service
 | 
					@Service
 | 
				
			||||||
public class CordaClient {
 | 
					public class CordaClient {
 | 
				
			||||||
@ -73,7 +73,7 @@ public class CordaClient {
 | 
				
			|||||||
            "djmil.cordacheckers.gameresult.RankingFlow",
 | 
					            "djmil.cordacheckers.gameresult.RankingFlow",
 | 
				
			||||||
            new Req());
 | 
					            new Req());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return cordaFlowExecute(holdingIdentity, requestBody, RspRankList.class)
 | 
					        return cordaFlowExecute(holdingIdentity, requestBody, RspRankMap.class)
 | 
				
			||||||
            .getResponce(requestBody);
 | 
					            .getResponce(requestBody);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -174,6 +174,36 @@ public class CordaClient {
 | 
				
			|||||||
            .getResponce(requestBody);
 | 
					            .getResponce(requestBody);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public GameState gameDrawRequest(HoldingIdentity holdingIdentity, UUID gameUuid) {
 | 
				
			||||||
 | 
					        final RequestBody requestBody = new RequestBody(
 | 
				
			||||||
 | 
					            "gd.request-" +UUID.randomUUID(),
 | 
				
			||||||
 | 
					            "djmil.cordacheckers.gameboard.DrawRequestFlow",
 | 
				
			||||||
 | 
					            gameUuid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
 | 
				
			||||||
 | 
					            .getResponce(requestBody);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public GameState gameDrawAccept(HoldingIdentity holdingIdentity, UUID gameUuid) {
 | 
				
			||||||
 | 
					        final RequestBody requestBody = new RequestBody(
 | 
				
			||||||
 | 
					            "gd.accept-" +UUID.randomUUID(),
 | 
				
			||||||
 | 
					            "djmil.cordacheckers.gameboard.DrawAcceptFlow",
 | 
				
			||||||
 | 
					            gameUuid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
 | 
				
			||||||
 | 
					            .getResponce(requestBody);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public GameState gameDrawReject(HoldingIdentity holdingIdentity, UUID gameUuid) {
 | 
				
			||||||
 | 
					        final RequestBody requestBody = new RequestBody(
 | 
				
			||||||
 | 
					            "gd.reject-" +UUID.randomUUID(),
 | 
				
			||||||
 | 
					            "djmil.cordacheckers.gameboard.DrawRejectFlow",
 | 
				
			||||||
 | 
					            gameUuid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
 | 
				
			||||||
 | 
					            .getResponce(requestBody);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private <T extends Rsp<?>> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowReponceType) {
 | 
					    private <T extends Rsp<?>> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowReponceType) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            final String requestBodyJson = this.jsonMapper
 | 
					            final String requestBodyJson = this.jsonMapper
 | 
				
			||||||
 | 
				
			|||||||
@ -21,10 +21,16 @@ public record GameState(
 | 
				
			|||||||
        GAME_PROPOSAL_WAIT_FOR_YOU,
 | 
					        GAME_PROPOSAL_WAIT_FOR_YOU,
 | 
				
			||||||
        GAME_PROPOSAL_REJECTED,
 | 
					        GAME_PROPOSAL_REJECTED,
 | 
				
			||||||
        GAME_PROPOSAL_CANCELED,
 | 
					        GAME_PROPOSAL_CANCELED,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GAME_BOARD_WAIT_FOR_OPPONENT,
 | 
					        GAME_BOARD_WAIT_FOR_OPPONENT,
 | 
				
			||||||
        GAME_BOARD_WAIT_FOR_YOU,
 | 
					        GAME_BOARD_WAIT_FOR_YOU,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DRAW_REQUEST_WAIT_FOR_OPPONENT,
 | 
				
			||||||
 | 
					        DRAW_REQUEST_WAIT_FOR_YOU,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GAME_RESULT_YOU_WON,
 | 
					        GAME_RESULT_YOU_WON,
 | 
				
			||||||
        GAME_RESULT_YOU_LOOSE;
 | 
					        GAME_RESULT_YOU_LOOSE,
 | 
				
			||||||
 | 
					        GAME_RESULT_DRAW;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    public final static Map<Integer, Stone> defaultGameBoard =  Map.ofEntries(
 | 
					    public final static Map<Integer, Stone> defaultGameBoard =  Map.ofEntries(
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,8 @@ package djmil.cordacheckers.cordaclient.dao;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
public record Rank(
 | 
					public record Rank(
 | 
				
			||||||
    Integer gamesPlayed,
 | 
					    Integer gamesPlayed,
 | 
				
			||||||
    Integer gamesWon
 | 
					    Integer gamesWon,
 | 
				
			||||||
 | 
					    Integer gamesDraw
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@ import java.util.Map;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import djmil.cordacheckers.cordaclient.dao.Rank;
 | 
					import djmil.cordacheckers.cordaclient.dao.Rank;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public record RspRankList(
 | 
					public record RspRankMap(
 | 
				
			||||||
    Map<String, Rank> successStatus,
 | 
					    Map<String, Rank> successStatus,
 | 
				
			||||||
    String failureStatus) implements Rsp<Map<String, Rank>> {
 | 
					    String failureStatus) implements Rsp<Map<String, Rank>> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -193,7 +193,7 @@ public class GameBoardTests {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    void testVictoryTest1() {
 | 
					    void testVictory_NoStones() {
 | 
				
			||||||
        final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
 | 
					        final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
 | 
				
			||||||
        final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
 | 
					        final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -217,7 +217,7 @@ public class GameBoardTests {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    void testVictoryTest2() {
 | 
					    void testVictory_StonesBlocked() {
 | 
				
			||||||
        final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
 | 
					        final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
 | 
				
			||||||
        final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
 | 
					        final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -240,6 +240,108 @@ public class GameBoardTests {
 | 
				
			|||||||
        assertThat(m1BlackView.moveNumber() == 1);
 | 
					        assertThat(m1BlackView.moveNumber() == 1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    void testDrawRequest() {
 | 
				
			||||||
 | 
					        final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
 | 
				
			||||||
 | 
					        final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState game = cordaClient.gameProposalCreate(hiWhite, hiBlack, Stone.Color.BLACK, 
 | 
				
			||||||
 | 
					            "Test draw request");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThat(cordaClient.gameProposalAccept(hiBlack, game.uuid())
 | 
				
			||||||
 | 
					            .status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThatThrownBy(() -> {  // Black can not request draw, since it is not his turn
 | 
				
			||||||
 | 
					            cordaClient.gameDrawRequest(hiBlack, game.uuid());
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState drawReqIssuerView = cordaClient.gameDrawRequest(hiWhite, game.uuid());
 | 
				
			||||||
 | 
					        assertThat(drawReqIssuerView.status()).isEqualByComparingTo(Status.DRAW_REQUEST_WAIT_FOR_OPPONENT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState drawReqAcquierView = cordaClient.gameStateGet(hiBlack, game.uuid());
 | 
				
			||||||
 | 
					        assertThat(drawReqAcquierView.status()).isEqualByComparingTo(Status.DRAW_REQUEST_WAIT_FOR_YOU);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThat(drawReqAcquierView.board()).containsAllEntriesOf(drawReqIssuerView.board());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThatThrownBy(() -> {
 | 
				
			||||||
 | 
					            cordaClient.gameBoardMove(hiBlack, game.uuid(), move(10, 15), 
 | 
				
			||||||
 | 
					            "Black can not move since it is not his turn and draw request is pending");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThatThrownBy(() -> {
 | 
				
			||||||
 | 
					            cordaClient.gameBoardMove(hiWhite, game.uuid(), move(22, 17), 
 | 
				
			||||||
 | 
					            "White can not move since draw request is pending");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    void testDrawAccept() {
 | 
				
			||||||
 | 
					        final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
 | 
				
			||||||
 | 
					        final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState game = cordaClient.gameProposalCreate(hiBlack, hiWhite, Stone.Color.WHITE, 
 | 
				
			||||||
 | 
					            "Draw ACCEPT test");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThat(cordaClient.gameProposalAccept(hiWhite, game.uuid())
 | 
				
			||||||
 | 
					            .status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_YOU);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThatThrownBy(() -> {  // Black can not request draw, since it is not his turn
 | 
				
			||||||
 | 
					            cordaClient.gameDrawRequest(hiBlack, game.uuid());
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState drawReqIssuerView = cordaClient.gameDrawRequest(hiWhite, game.uuid());
 | 
				
			||||||
 | 
					        assertThat(drawReqIssuerView.status()).isEqualByComparingTo(Status.DRAW_REQUEST_WAIT_FOR_OPPONENT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThatThrownBy(() -> {  // White shall not be able to accept own Draw Request
 | 
				
			||||||
 | 
					            cordaClient.gameDrawAccept(hiWhite, game.uuid());
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState drawReqAcquierView = cordaClient.gameStateGet(hiBlack, game.uuid());
 | 
				
			||||||
 | 
					        assertThat(drawReqAcquierView.status()).isEqualByComparingTo(Status.DRAW_REQUEST_WAIT_FOR_YOU);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThatThrownBy(() -> {  // Black shall not be able to send counter Draw Request
 | 
				
			||||||
 | 
					            cordaClient.gameDrawRequest(hiBlack, game.uuid());
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState drawAcceptBlackView = cordaClient.gameDrawAccept(hiBlack, game.uuid());
 | 
				
			||||||
 | 
					        assertThat(drawAcceptBlackView.status()).isEqualByComparingTo(Status.GAME_RESULT_DRAW);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState drawAcceptWhiteView = cordaClient.gameStateGet(hiWhite, game.uuid());
 | 
				
			||||||
 | 
					        assertThat(drawAcceptWhiteView.status()).isEqualByComparingTo(Status.GAME_RESULT_DRAW);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    void testDrawReject() {
 | 
				
			||||||
 | 
					        final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
 | 
				
			||||||
 | 
					        final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState game = cordaClient.gameProposalCreate(hiWhite, hiBlack, Stone.Color.BLACK, 
 | 
				
			||||||
 | 
					            "Draw REJECT test");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThat(cordaClient.gameProposalAccept(hiBlack, game.uuid())
 | 
				
			||||||
 | 
					            .status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState drawReqIssuerView = cordaClient.gameDrawRequest(hiWhite, game.uuid());
 | 
				
			||||||
 | 
					        assertThat(drawReqIssuerView.status()).isEqualByComparingTo(Status.DRAW_REQUEST_WAIT_FOR_OPPONENT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThatThrownBy(() -> {  // White shall not be able to reject own Draw Request
 | 
				
			||||||
 | 
					            cordaClient.gameDrawReject(hiWhite, game.uuid());
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState drawReqAcquierView = cordaClient.gameStateGet(hiBlack, game.uuid());
 | 
				
			||||||
 | 
					        assertThat(drawReqAcquierView.status()).isEqualByComparingTo(Status.DRAW_REQUEST_WAIT_FOR_YOU);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThatThrownBy(() -> {  // Black shall not be able to send counter Draw Request
 | 
				
			||||||
 | 
					            cordaClient.gameDrawRequest(hiBlack, game.uuid());
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState drawRejectBlackView = cordaClient.gameDrawReject(hiBlack, game.uuid());
 | 
				
			||||||
 | 
					        assertThat(drawRejectBlackView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState drawRejectWhiteView = cordaClient.gameStateGet(hiWhite, game.uuid());
 | 
				
			||||||
 | 
					        assertThat(drawRejectWhiteView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_YOU);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ArrayList<Integer> move(int from, int to) {
 | 
					    ArrayList<Integer> move(int from, int to) {
 | 
				
			||||||
        return new ArrayList<Integer>(Arrays.asList(from, to));
 | 
					        return new ArrayList<Integer>(Arrays.asList(from, to));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -61,7 +61,7 @@ public class GameProposalTests {
 | 
				
			|||||||
        assertThat(acquierGameView.opponentName()).isEqualToIgnoringCase(issuer);
 | 
					        assertThat(acquierGameView.opponentName()).isEqualToIgnoringCase(issuer);
 | 
				
			||||||
        assertThat(acquierGameView.opponentColor()).isEqualByComparingTo(acquierColor.opposite());
 | 
					        assertThat(acquierGameView.opponentColor()).isEqualByComparingTo(acquierColor.opposite());
 | 
				
			||||||
        assertThat(acquierGameView.board()).containsAllEntriesOf(GameState.defaultGameBoard);
 | 
					        assertThat(acquierGameView.board()).containsAllEntriesOf(GameState.defaultGameBoard);
 | 
				
			||||||
        assertThat(acquierGameView.previousMove()).isEmpty();
 | 
					        assertThat(acquierGameView.previousMove()).isNull();
 | 
				
			||||||
        assertThat(acquierGameView.moveNumber() == 0);
 | 
					        assertThat(acquierGameView.moveNumber() == 0);
 | 
				
			||||||
        assertThat(acquierGameView.message()).isEqualToIgnoringCase(message);
 | 
					        assertThat(acquierGameView.message()).isEqualToIgnoringCase(message);
 | 
				
			||||||
        assertThat(acquierGameView.uuid()).isEqualByComparingTo(issuerGameView.uuid());
 | 
					        assertThat(acquierGameView.uuid()).isEqualByComparingTo(issuerGameView.uuid());
 | 
				
			||||||
@ -134,8 +134,7 @@ public class GameProposalTests {
 | 
				
			|||||||
            hiIssuer, hiAcquier, acquierColor, "GameProposal ACCEPT test");
 | 
					            hiIssuer, hiAcquier, acquierColor, "GameProposal ACCEPT test");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertThatThrownBy(() -> { // Issuer can not accept
 | 
					        assertThatThrownBy(() -> { // Issuer can not accept
 | 
				
			||||||
            cordaClient.gameProposalAccept(
 | 
					            cordaClient.gameProposalAccept(hiIssuer, game.uuid());
 | 
				
			||||||
                hiIssuer, game.uuid());
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final GameState acceptedGameAcquierView = cordaClient.gameProposalAccept(
 | 
					        final GameState acceptedGameAcquierView = cordaClient.gameProposalAccept(
 | 
				
			||||||
 | 
				
			|||||||
@ -36,7 +36,7 @@ public class GameStateTests {
 | 
				
			|||||||
        final UUID gameUuid = p1GameView.uuid();
 | 
					        final UUID gameUuid = p1GameView.uuid();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /*
 | 
					        /*
 | 
				
			||||||
         * Both players shall be able to find newly created GameProposal aka GameList
 | 
					         * Both players shall be able to find newly created GameProposal within GameList
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        final List<GameState> p1GameList = cordaClient.gameStateList(hiPlayer1);
 | 
					        final List<GameState> p1GameList = cordaClient.gameStateList(hiPlayer1);
 | 
				
			||||||
        final List<GameState> p2GameList = cordaClient.gameStateList(hiPlayer2);
 | 
					        final List<GameState> p2GameList = cordaClient.gameStateList(hiPlayer2);
 | 
				
			||||||
 | 
				
			|||||||
@ -13,8 +13,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			|||||||
import org.springframework.boot.test.context.SpringBootTest;
 | 
					import org.springframework.boot.test.context.SpringBootTest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import djmil.cordacheckers.cordaclient.dao.GameState;
 | 
					import djmil.cordacheckers.cordaclient.dao.GameState;
 | 
				
			||||||
import djmil.cordacheckers.cordaclient.dao.Stone;
 | 
					 | 
				
			||||||
import djmil.cordacheckers.cordaclient.dao.Rank;
 | 
					import djmil.cordacheckers.cordaclient.dao.Rank;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.cordaclient.dao.Stone;
 | 
				
			||||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
 | 
					import djmil.cordacheckers.user.HoldingIdentityResolver;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@SpringBootTest
 | 
					@SpringBootTest
 | 
				
			||||||
@ -25,8 +25,8 @@ public class RankingTests {
 | 
				
			|||||||
    @Autowired
 | 
					    @Autowired
 | 
				
			||||||
    HoldingIdentityResolver holdingIdentityResolver;
 | 
					    HoldingIdentityResolver holdingIdentityResolver;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final String player1 = "kumar";
 | 
					    final String player1 = "Kumar";
 | 
				
			||||||
    final String player2 = "bobik";
 | 
					    final String player2 = "Bobik";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final static Stone WHITE_MAN  = new Stone(Stone.Color.WHITE, Stone.Type.MAN);
 | 
					    final static Stone WHITE_MAN  = new Stone(Stone.Color.WHITE, Stone.Type.MAN);
 | 
				
			||||||
    final static Stone WHITE_KING = new Stone(Stone.Color.WHITE, Stone.Type.KING);
 | 
					    final static Stone WHITE_KING = new Stone(Stone.Color.WHITE, Stone.Type.KING);
 | 
				
			||||||
@ -60,9 +60,11 @@ public class RankingTests {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        assertThat(liderboard1.get(winnerName).gamesWon()    +1 == liderboard2.get(winnerName).gamesWon()   );
 | 
					        assertThat(liderboard1.get(winnerName).gamesWon()    +1 == liderboard2.get(winnerName).gamesWon()   );
 | 
				
			||||||
        assertThat(liderboard1.get(winnerName).gamesPlayed() +1 == liderboard2.get(winnerName).gamesPlayed());
 | 
					        assertThat(liderboard1.get(winnerName).gamesPlayed() +1 == liderboard2.get(winnerName).gamesPlayed());
 | 
				
			||||||
 | 
					        assertThat(liderboard1.get(winnerName).gamesDraw()      == liderboard2.get(winnerName).gamesDraw()  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertThat(liderboard1.get(losserName).gamesWon()       == liderboard2.get(losserName).gamesWon()   );
 | 
					        assertThat(liderboard1.get(losserName).gamesWon()       == liderboard2.get(losserName).gamesWon()   );
 | 
				
			||||||
        assertThat(liderboard1.get(losserName).gamesPlayed() +1 == liderboard2.get(losserName).gamesPlayed());
 | 
					        assertThat(liderboard1.get(losserName).gamesPlayed() +1 == liderboard2.get(losserName).gamesPlayed());
 | 
				
			||||||
 | 
					        assertThat(liderboard1.get(losserName).gamesDraw()      == liderboard2.get(losserName).gamesDraw()  );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
@ -87,9 +89,38 @@ public class RankingTests {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        assertThat(liderboard1.get(winnerName).gamesWon()    +1 == liderboard2.get(winnerName).gamesWon()   );
 | 
					        assertThat(liderboard1.get(winnerName).gamesWon()    +1 == liderboard2.get(winnerName).gamesWon()   );
 | 
				
			||||||
        assertThat(liderboard1.get(winnerName).gamesPlayed() +1 == liderboard2.get(winnerName).gamesPlayed());
 | 
					        assertThat(liderboard1.get(winnerName).gamesPlayed() +1 == liderboard2.get(winnerName).gamesPlayed());
 | 
				
			||||||
 | 
					        assertThat(liderboard1.get(winnerName).gamesDraw()      == liderboard2.get(winnerName).gamesDraw()  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertThat(liderboard1.get(losserName).gamesWon()       == liderboard2.get(losserName).gamesWon()   );
 | 
					        assertThat(liderboard1.get(losserName).gamesWon()       == liderboard2.get(losserName).gamesWon()   );
 | 
				
			||||||
        assertThat(liderboard1.get(losserName).gamesPlayed() +1 == liderboard2.get(losserName).gamesPlayed());
 | 
					        assertThat(liderboard1.get(losserName).gamesPlayed() +1 == liderboard2.get(losserName).gamesPlayed());
 | 
				
			||||||
 | 
					        assertThat(liderboard1.get(losserName).gamesDraw()      == liderboard2.get(losserName).gamesDraw()  );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    void testDraw() throws InvalidNameException {
 | 
				
			||||||
 | 
					        final var hiCustodian  = holdingIdentityResolver.getCustodian();
 | 
				
			||||||
 | 
					        assertThat(hiCustodian).isNotNull();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final var hiPlayer1 = holdingIdentityResolver.getByUsername(player1);
 | 
				
			||||||
 | 
					        final var hiPlayer2 = holdingIdentityResolver.getByUsername(player2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final GameState game = cordaClient.gameProposalCreate(hiPlayer1, hiPlayer2, Stone.Color.WHITE, 
 | 
				
			||||||
 | 
					            "GameBoard GLOBAL_RANKING draw test");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cordaClient.gameProposalAccept(hiPlayer2, game.uuid());
 | 
				
			||||||
 | 
					        cordaClient.gameDrawRequest(hiPlayer2, game.uuid());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final Map<String, Rank> liderboard1 = cordaClient.fetchRanking(hiCustodian);
 | 
				
			||||||
 | 
					        cordaClient.gameDrawAccept(hiPlayer1, game.uuid());
 | 
				
			||||||
 | 
					        final Map<String, Rank> liderboard2 = cordaClient.fetchRanking(hiCustodian);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThat(liderboard1.get(player1).gamesWon()       == liderboard2.get(player1).gamesWon()   );
 | 
				
			||||||
 | 
					        assertThat(liderboard1.get(player1).gamesPlayed() +1 == liderboard2.get(player1).gamesPlayed());
 | 
				
			||||||
 | 
					        assertThat(liderboard1.get(player1).gamesDraw()   +1 == liderboard2.get(player1).gamesDraw()  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThat(liderboard1.get(player2).gamesWon()       == liderboard2.get(player2).gamesWon()   );
 | 
				
			||||||
 | 
					        assertThat(liderboard1.get(player2).gamesPlayed() +1 == liderboard2.get(player2).gamesPlayed());
 | 
				
			||||||
 | 
					        assertThat(liderboard1.get(player2).gamesDraw()   +1 == liderboard2.get(player2).gamesDraw()  );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
 | 
				
			|||||||
@ -24,16 +24,28 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
 | 
				
			|||||||
                command.validateGameProposalAccept(trx);
 | 
					                command.validateGameProposalAccept(trx);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
            case GAME_BOARD_MOVE:
 | 
					            case MOVE:
 | 
				
			||||||
                command.validateGameBoardMove(trx, command.getMove());
 | 
					                command.validateMove(trx, command.getMove());
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case GAME_BOARD_SURRENDER:
 | 
					            case SURRENDER:
 | 
				
			||||||
                command.validateGameBoardSurrender(trx);
 | 
					                command.validateSurrender(trx);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case GAME_BOARD_VICTORY:
 | 
					            case CLAIM_VICTORY:
 | 
				
			||||||
                command.validateGameBoardVictory(trx);
 | 
					                command.validateClaimVictory(trx);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case DRAW_REQUEST:
 | 
				
			||||||
 | 
					                command.validateDrawRequest(trx);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            case DRAW_ACCEPT:
 | 
				
			||||||
 | 
					                command.validateDrawAcquire(trx);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case DRAW_REJECT:
 | 
				
			||||||
 | 
					                command.validateDrawDecline(trx);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
 | 
				
			|||||||
@ -9,12 +9,9 @@ import djmil.cordacheckers.checkers.Move;
 | 
				
			|||||||
import djmil.cordacheckers.states.GameBoardState;
 | 
					import djmil.cordacheckers.states.GameBoardState;
 | 
				
			||||||
import djmil.cordacheckers.states.GameProposalState;
 | 
					import djmil.cordacheckers.states.GameProposalState;
 | 
				
			||||||
import djmil.cordacheckers.states.GameResultState;
 | 
					import djmil.cordacheckers.states.GameResultState;
 | 
				
			||||||
import djmil.cordacheckers.states.GameState;
 | 
					 | 
				
			||||||
import net.corda.v5.base.annotations.ConstructorForDeserialization;
 | 
					import net.corda.v5.base.annotations.ConstructorForDeserialization;
 | 
				
			||||||
import net.corda.v5.base.annotations.CordaSerializable;
 | 
					import net.corda.v5.base.annotations.CordaSerializable;
 | 
				
			||||||
import net.corda.v5.base.types.MemberX500Name;
 | 
					 | 
				
			||||||
import net.corda.v5.ledger.utxo.Command;
 | 
					import net.corda.v5.ledger.utxo.Command;
 | 
				
			||||||
import net.corda.v5.ledger.utxo.StateAndRef;
 | 
					 | 
				
			||||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
 | 
					import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class GameCommand implements Command {
 | 
					public class GameCommand implements Command {
 | 
				
			||||||
@ -25,9 +22,12 @@ public class GameCommand implements Command {
 | 
				
			|||||||
        GAME_PROPOSAL_ACCEPT,
 | 
					        GAME_PROPOSAL_ACCEPT,
 | 
				
			||||||
        GAME_PROPOSAL_REJECT,
 | 
					        GAME_PROPOSAL_REJECT,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GAME_BOARD_MOVE,
 | 
					        MOVE,
 | 
				
			||||||
        GAME_BOARD_SURRENDER,
 | 
					        DRAW_REQUEST,
 | 
				
			||||||
        GAME_BOARD_VICTORY;
 | 
					        DRAW_ACCEPT,
 | 
				
			||||||
 | 
					        DRAW_REJECT,
 | 
				
			||||||
 | 
					        SURRENDER,
 | 
				
			||||||
 | 
					        CLAIM_VICTORY;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final Action action;
 | 
					    private final Action action;
 | 
				
			||||||
@ -46,7 +46,7 @@ public class GameCommand implements Command {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public GameCommand(Action action) {
 | 
					    public GameCommand(Action action) {
 | 
				
			||||||
        if (action == Action.GAME_BOARD_MOVE)
 | 
					        if (action == Action.MOVE)
 | 
				
			||||||
            throw new ActionException();
 | 
					            throw new ActionException();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.action = action;
 | 
					        this.action = action;
 | 
				
			||||||
@ -54,7 +54,7 @@ public class GameCommand implements Command {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public GameCommand(List<Integer> move) {
 | 
					    public GameCommand(List<Integer> move) {
 | 
				
			||||||
        this.action = Action.GAME_BOARD_MOVE;
 | 
					        this.action = Action.MOVE;
 | 
				
			||||||
        this.move = move;
 | 
					        this.move = move;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -72,46 +72,6 @@ public class GameCommand implements Command {
 | 
				
			|||||||
        return move;
 | 
					        return move;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public MemberX500Name getCounterparty(StateAndRef<GameState> gameStateSar) {
 | 
					 | 
				
			||||||
        return getCounterparty(gameStateSar.getState().getContractState());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public MemberX500Name getCounterparty(GameState gameState) {
 | 
					 | 
				
			||||||
        switch (this.action) {
 | 
					 | 
				
			||||||
            case GAME_PROPOSAL_CREATE:
 | 
					 | 
				
			||||||
            case GAME_PROPOSAL_CANCEL:
 | 
					 | 
				
			||||||
                if (gameState instanceof GameProposalState)
 | 
					 | 
				
			||||||
                    return ((GameProposalState)gameState).getAcquierName();
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            case GAME_PROPOSAL_ACCEPT:
 | 
					 | 
				
			||||||
            case GAME_PROPOSAL_REJECT:
 | 
					 | 
				
			||||||
                if (gameState instanceof GameProposalState)
 | 
					 | 
				
			||||||
                    return ((GameProposalState)gameState).getIssuerName();
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            case GAME_BOARD_MOVE:
 | 
					 | 
				
			||||||
                if (gameState instanceof GameBoardState)
 | 
					 | 
				
			||||||
                    return ((GameBoardState)gameState).getIdelPlayerName();
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
            case GAME_BOARD_SURRENDER:
 | 
					 | 
				
			||||||
                if (gameState instanceof GameResultState)
 | 
					 | 
				
			||||||
                    return ((GameResultState)gameState).getWinnerName();
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            case GAME_BOARD_VICTORY:
 | 
					 | 
				
			||||||
                if (gameState instanceof GameResultState)
 | 
					 | 
				
			||||||
                    return ((GameResultState)gameState).getLooserName();
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            default:
 | 
					 | 
				
			||||||
                throw new ActionException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        throw new RuntimeException(action +": unexpected GameState type " +gameState.getClass());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void validateGameProposalCreate(UtxoLedgerTransaction trx) {
 | 
					    public void validateGameProposalCreate(UtxoLedgerTransaction trx) {
 | 
				
			||||||
        requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE);
 | 
					        requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE);
 | 
				
			||||||
        requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE);
 | 
					        requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE);
 | 
				
			||||||
@ -119,7 +79,7 @@ public class GameCommand implements Command {
 | 
				
			|||||||
        final GameProposalState gameProposal = getSingleOutputState(trx, GameProposalState.class);
 | 
					        final GameProposalState gameProposal = getSingleOutputState(trx, GameProposalState.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /*
 | 
					        /*
 | 
				
			||||||
         * Major command logick check 
 | 
					         * Major command logic check 
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        requireThat(gameProposal.getIssuerName().compareTo(gameProposal.getBlackPlayer()) == 0 ||
 | 
					        requireThat(gameProposal.getIssuerName().compareTo(gameProposal.getBlackPlayer()) == 0 ||
 | 
				
			||||||
                    gameProposal.getIssuerName().compareTo(gameProposal.getWhitePlayer()) == 0,
 | 
					                    gameProposal.getIssuerName().compareTo(gameProposal.getWhitePlayer()) == 0,
 | 
				
			||||||
@ -136,9 +96,10 @@ public class GameCommand implements Command {
 | 
				
			|||||||
        requireThat(outGameBoard.getWhitePlayer().compareTo(inGameProposal.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameBoard.getWhitePlayer().compareTo(inGameProposal.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
        requireThat(outGameBoard.getBlackPlayer().compareTo(inGameProposal.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameBoard.getBlackPlayer().compareTo(inGameProposal.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
        requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.getGameUuid().compareTo(inGameProposal.getGameUuid()) == 0, UUID_MUST_NOT_CHANGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /*
 | 
					        /*
 | 
				
			||||||
         * Major command logick check 
 | 
					         * Major command logic check 
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        requireThat(outGameBoard.getBoard().size() > 0, "GameBoard initial state was not found");
 | 
					        requireThat(outGameBoard.getBoard().size() > 0, "GameBoard initial state was not found");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -157,7 +118,7 @@ public class GameCommand implements Command {
 | 
				
			|||||||
        getSingleInputState(trx, GameProposalState.class);
 | 
					        getSingleInputState(trx, GameProposalState.class);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void validateGameBoardMove(UtxoLedgerTransaction trx, List<Integer> move) {
 | 
					    public void validateMove(UtxoLedgerTransaction trx, List<Integer> move) {
 | 
				
			||||||
        requireThat(trx.getInputContractStates().size()  == 1, MOVE_INPUT_STATE);
 | 
					        requireThat(trx.getInputContractStates().size()  == 1, MOVE_INPUT_STATE);
 | 
				
			||||||
        requireThat(trx.getOutputContractStates().size() == 1, MOVE_OUTPUT_STATE);
 | 
					        requireThat(trx.getOutputContractStates().size() == 1, MOVE_OUTPUT_STATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -167,15 +128,18 @@ public class GameCommand implements Command {
 | 
				
			|||||||
        requireThat(outGameBoard.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameBoard.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
        requireThat(outGameBoard.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameBoard.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
        requireThat(outGameBoard.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameBoard.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.getGameUuid().compareTo(inGameBoard.getGameUuid()) == 0, UUID_MUST_NOT_CHANGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /*
 | 
					        /*
 | 
				
			||||||
         * Major command logick check 
 | 
					         * Major command logic check 
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        final var newGameBoard = new GameBoardState(inGameBoard, move, outGameBoard.getMessage());
 | 
					        final var newGameBoard = new GameBoardState(inGameBoard, move, outGameBoard.getMessage());
 | 
				
			||||||
        requireThat(outGameBoard.equals(newGameBoard), "Unexpected output state");
 | 
					        requireThat(outGameBoard.equals(newGameBoard), "Unexpected output state");
 | 
				
			||||||
 | 
					        requireThat(inGameBoard.isDrawRequested() == false, "Draw was requested");
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.isDrawRequested() == false, "Draw can not be requested during move");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void validateGameBoardSurrender(UtxoLedgerTransaction trx) {
 | 
					    public void validateSurrender(UtxoLedgerTransaction trx) {
 | 
				
			||||||
        requireThat(trx.getInputContractStates().size()  == 1, SURRENDER_INPUT_STATE);
 | 
					        requireThat(trx.getInputContractStates().size()  == 1, SURRENDER_INPUT_STATE);
 | 
				
			||||||
        requireThat(trx.getOutputContractStates().size() == 1, SURRENDER_OUTPUT_STATE);
 | 
					        requireThat(trx.getOutputContractStates().size() == 1, SURRENDER_OUTPUT_STATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -185,18 +149,19 @@ public class GameCommand implements Command {
 | 
				
			|||||||
        requireThat(outGameResult.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameResult.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
        requireThat(outGameResult.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameResult.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
        requireThat(outGameResult.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameResult.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameResult.getGameUuid().compareTo(inGameBoard.getGameUuid()) == 0, UUID_MUST_NOT_CHANGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /*
 | 
					        /*
 | 
				
			||||||
         * Major command logick check 
 | 
					         * Major command logic check 
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        requireThat(outGameResult.getLooserName().compareTo(outGameResult.getBlackPlayer()) == 0 ||
 | 
					        requireThat(outGameResult.getLooserName().compareTo(outGameResult.getBlackPlayer()) == 0 ||
 | 
				
			||||||
                    outGameResult.getLooserName().compareTo(outGameResult.getWhitePlayer()) == 0,
 | 
					                    outGameResult.getLooserName().compareTo(outGameResult.getWhitePlayer()) == 0,
 | 
				
			||||||
            "Surenderer must be either Black or White player");
 | 
					            "Surenderer must be either Black or White player");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        requireThat(outGameResult.getTotalMoves() == inGameBoard.getMoveNumber(), GAME_RESULT_MOVES);
 | 
					        requireThat(outGameResult.getTotalMoves() == inGameBoard.getMoveNumber(), BAD_TOTAL_MOVES);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void validateGameBoardVictory(UtxoLedgerTransaction trx) {
 | 
					    public void validateClaimVictory(UtxoLedgerTransaction trx) {
 | 
				
			||||||
        requireThat(trx.getInputContractStates().size()  == 1, VICTORY_INPUT_STATE);
 | 
					        requireThat(trx.getInputContractStates().size()  == 1, VICTORY_INPUT_STATE);
 | 
				
			||||||
        requireThat(trx.getOutputContractStates().size() == 1, VICTORY_OUTPUT_STATE);
 | 
					        requireThat(trx.getOutputContractStates().size() == 1, VICTORY_OUTPUT_STATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -206,15 +171,75 @@ public class GameCommand implements Command {
 | 
				
			|||||||
        requireThat(outGameResult.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameResult.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
        requireThat(outGameResult.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameResult.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
        requireThat(outGameResult.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
 | 
					        requireThat(outGameResult.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameResult.getGameUuid().compareTo(inGameBoard.getGameUuid()) == 0, UUID_MUST_NOT_CHANGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /*
 | 
					        /*
 | 
				
			||||||
         * Major command logick check 
 | 
					         * Major command logic check 
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        final var possibleMoves = Move.getPossibleMoves(inGameBoard.getBoard(), inGameBoard.getActiveColor());
 | 
					        final var possibleMoves = Move.getPossibleMoves(inGameBoard.getBoard(), inGameBoard.getActiveColor());
 | 
				
			||||||
        requireThat(possibleMoves.isEmpty(), "Victory condition violation");
 | 
					        requireThat(possibleMoves.isEmpty(), "Victory condition violation");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        requireThat(outGameResult.getWinnerName().compareTo(inGameBoard.getIdelPlayerName()) == 0, "Bad winner name");
 | 
					        requireThat(outGameResult.getWinnerName().compareTo(inGameBoard.getIdelPlayerName()) == 0, "Bad winner name");
 | 
				
			||||||
        requireThat(outGameResult.getTotalMoves() == inGameBoard.getMoveNumber(), GAME_RESULT_MOVES);
 | 
					        requireThat(outGameResult.getTotalMoves() == inGameBoard.getMoveNumber(), BAD_TOTAL_MOVES);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void validateDrawRequest(UtxoLedgerTransaction trx) {
 | 
				
			||||||
 | 
					        requireThat(trx.getInputContractStates().size()  == 1, Action.DRAW_REQUEST + BAD_IN_STATE_SIZE);
 | 
				
			||||||
 | 
					        requireThat(trx.getOutputContractStates().size() == 1, Action.DRAW_REQUEST + BAD_OUT_STATE_SIZE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final var inGameBoard  = getSingleInputState (trx, GameBoardState.class);
 | 
				
			||||||
 | 
					        final var outGameBoard = getSingleOutputState(trx, GameBoardState.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.getGameUuid().compareTo(inGameBoard.getGameUuid()) == 0, UUID_MUST_NOT_CHANGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /*
 | 
				
			||||||
 | 
					         * Major command logic check 
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        requireThat(isGameBoardEqual(inGameBoard, outGameBoard) == true, GAME_BOARD_STATE_MUST_NOT_CHANGE);
 | 
				
			||||||
 | 
					        requireThat(inGameBoard.getDrawRequest() == null, "Draw can be requested only once per move");
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.isDrawRequested() == true, DRAW_REQ_EXPECTED);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void validateDrawDecline(UtxoLedgerTransaction trx) {
 | 
				
			||||||
 | 
					        requireThat(trx.getInputContractStates().size()  == 1, Action.DRAW_REJECT + BAD_IN_STATE_SIZE);
 | 
				
			||||||
 | 
					        requireThat(trx.getOutputContractStates().size() == 1, Action.DRAW_REJECT + BAD_OUT_STATE_SIZE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final var inGameBoard  = getSingleInputState (trx, GameBoardState.class);
 | 
				
			||||||
 | 
					        final var outGameBoard = getSingleOutputState(trx, GameBoardState.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.getGameUuid().compareTo(inGameBoard.getGameUuid()) == 0, UUID_MUST_NOT_CHANGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /*
 | 
				
			||||||
 | 
					         * Major command logic check 
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        requireThat(isGameBoardEqual(inGameBoard, outGameBoard) == true, GAME_BOARD_STATE_MUST_NOT_CHANGE);
 | 
				
			||||||
 | 
					        requireThat(inGameBoard.isDrawRequested() == true, DRAW_REQ_EXPECTED);
 | 
				
			||||||
 | 
					        requireThat(outGameBoard.isDrawRequested() == false, "Draw decline expected");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void validateDrawAcquire(UtxoLedgerTransaction trx) {
 | 
				
			||||||
 | 
					        requireThat(trx.getInputContractStates().size()  == 1, Action.DRAW_ACCEPT + BAD_IN_STATE_SIZE);
 | 
				
			||||||
 | 
					        requireThat(trx.getOutputContractStates().size() == 1, Action.DRAW_ACCEPT + BAD_OUT_STATE_SIZE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final var inGameBoard   = getSingleInputState (trx, GameBoardState.class);
 | 
				
			||||||
 | 
					        final var outGameResult = getSingleOutputState(trx, GameResultState.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        requireThat(outGameResult.getWhitePlayer().compareTo(inGameBoard.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameResult.getBlackPlayer().compareTo(inGameBoard.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameResult.getParticipants().containsAll(inGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
 | 
				
			||||||
 | 
					        requireThat(outGameResult.getGameUuid().compareTo(inGameBoard.getGameUuid()) == 0, UUID_MUST_NOT_CHANGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /*
 | 
				
			||||||
 | 
					         * Major command logic check 
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        requireThat(inGameBoard.isDrawRequested() == true, DRAW_REQ_EXPECTED);
 | 
				
			||||||
 | 
					        requireThat(outGameResult.getWinnerName() == null, "Draw can not have winner");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static void requireThat(boolean asserted, String errorMessage) {
 | 
					    public static void requireThat(boolean asserted, String errorMessage) {
 | 
				
			||||||
@ -223,6 +248,12 @@ public class GameCommand implements Command {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private boolean isGameBoardEqual(GameBoardState a, GameBoardState b) {
 | 
				
			||||||
 | 
					        return a.getBoard().equals(b.getBoard()) &&
 | 
				
			||||||
 | 
					                a.getActiveColor() == b.getActiveColor() &&
 | 
				
			||||||
 | 
					                a.getMoveNumber() == b.getMoveNumber();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static final String REQUIRE_SINGLE_COMMAND = "Require a single command";
 | 
					    static final String REQUIRE_SINGLE_COMMAND = "Require a single command";
 | 
				
			||||||
    static final String IN_OUT_PARTICIPANTS = "Output participants should include all input participants";
 | 
					    static final String IN_OUT_PARTICIPANTS = "Output participants should include all input participants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -249,5 +280,12 @@ public class GameCommand implements Command {
 | 
				
			|||||||
    static final String VICTORY_INPUT_STATE  = "VICTORY command should have exactly one GameBoardState input state";
 | 
					    static final String VICTORY_INPUT_STATE  = "VICTORY command should have exactly one GameBoardState input state";
 | 
				
			||||||
    static final String VICTORY_OUTPUT_STATE = "VICTORY command should have exactly one GameResultState output state";
 | 
					    static final String VICTORY_OUTPUT_STATE = "VICTORY command should have exactly one GameResultState output state";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static final String GAME_RESULT_MOVES = "Wrong number of total moves";
 | 
					    static final String BAD_TOTAL_MOVES = "Wrong number of total moves";
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    static final String BAD_IN_STATE_SIZE  = " - wrong input state size";
 | 
				
			||||||
 | 
					    static final String BAD_OUT_STATE_SIZE = " - wrong output state size";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static final String DRAW_REQ_EXPECTED = "Draw request expected";
 | 
				
			||||||
 | 
					    static final String GAME_BOARD_STATE_MUST_NOT_CHANGE = "GameBoard state must not be changed";
 | 
				
			||||||
 | 
					    static final String UUID_MUST_NOT_CHANGE = "UUID must not be changed";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -47,19 +47,37 @@ public class GameInfo {
 | 
				
			|||||||
                this.actor  = ((GameBoardState)this.state).getActivePlayerName();
 | 
					                this.actor  = ((GameBoardState)this.state).getActivePlayerName();
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case GAME_BOARD_MOVE:
 | 
					            case MOVE:
 | 
				
			||||||
                this.state  = getSingleOutputState(utxoTrx, GameBoardState.class);
 | 
					                this.state  = getSingleOutputState(utxoTrx, GameBoardState.class);
 | 
				
			||||||
                this.issuer = getSingleInputState(utxoTrx, GameBoardState.class).getActivePlayerName();
 | 
					                this.issuer = getSingleInputState(utxoTrx, GameBoardState.class).getActivePlayerName();
 | 
				
			||||||
                this.actor  = ((GameBoardState)this.state).getActivePlayerName();
 | 
					                this.actor  = ((GameBoardState)this.state).getActivePlayerName();
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case GAME_BOARD_VICTORY:
 | 
					            case DRAW_REQUEST:
 | 
				
			||||||
 | 
					                this.state  = getSingleOutputState(utxoTrx, GameBoardState.class);
 | 
				
			||||||
 | 
					                this.issuer = ((GameBoardState)this.state).getActivePlayerName();
 | 
				
			||||||
 | 
					                this.actor  = ((GameBoardState)this.state).getIdelPlayerName();
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case DRAW_REJECT:
 | 
				
			||||||
 | 
					                this.state  = getSingleOutputState(utxoTrx, GameBoardState.class);
 | 
				
			||||||
 | 
					                this.issuer = ((GameBoardState)this.state).getIdelPlayerName();
 | 
				
			||||||
 | 
					                this.actor  = ((GameBoardState)this.state).getActivePlayerName();
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case DRAW_ACCEPT:        
 | 
				
			||||||
 | 
					                this.state  = getSingleOutputState(utxoTrx, GameResultState.class);
 | 
				
			||||||
 | 
					                this.issuer = getSingleInputState(utxoTrx, GameBoardState.class).getIdelPlayerName();
 | 
				
			||||||
 | 
					                this.actor  = null;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case CLAIM_VICTORY:
 | 
				
			||||||
                this.state  = getSingleOutputState(utxoTrx, GameResultState.class);
 | 
					                this.state  = getSingleOutputState(utxoTrx, GameResultState.class);
 | 
				
			||||||
                this.issuer = ((GameResultState)this.state).getWinnerName();
 | 
					                this.issuer = ((GameResultState)this.state).getWinnerName();
 | 
				
			||||||
                this.actor  = null;
 | 
					                this.actor  = null;
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case GAME_BOARD_SURRENDER:
 | 
					            case SURRENDER:
 | 
				
			||||||
                this.state  = getSingleOutputState(utxoTrx, GameResultState.class);
 | 
					                this.state  = getSingleOutputState(utxoTrx, GameResultState.class);
 | 
				
			||||||
                this.issuer = ((GameResultState)this.state).getLooserName();
 | 
					                this.issuer = ((GameResultState)this.state).getLooserName();
 | 
				
			||||||
                this.actor  = null;
 | 
					                this.actor  = null;
 | 
				
			||||||
 | 
				
			|||||||
@ -20,12 +20,16 @@ public class GameResultContract implements net.corda.v5.ledger.utxo.Contract {
 | 
				
			|||||||
        final GameCommand command = getSingleCommand(trx, GameCommand.class);
 | 
					        final GameCommand command = getSingleCommand(trx, GameCommand.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        switch (command.getAction()) {
 | 
					        switch (command.getAction()) {
 | 
				
			||||||
            case GAME_BOARD_SURRENDER: 
 | 
					            case SURRENDER: 
 | 
				
			||||||
                command.validateGameBoardSurrender(trx);
 | 
					                command.validateSurrender(trx);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case GAME_BOARD_VICTORY:
 | 
					            case CLAIM_VICTORY:
 | 
				
			||||||
                command.validateGameBoardVictory(trx);
 | 
					                command.validateClaimVictory(trx);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case DRAW_ACCEPT:
 | 
				
			||||||
 | 
					                command.validateDrawAcquire(trx);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,8 @@ import java.util.List;
 | 
				
			|||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.jetbrains.annotations.Nullable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import djmil.cordacheckers.checkers.Move;
 | 
					import djmil.cordacheckers.checkers.Move;
 | 
				
			||||||
import djmil.cordacheckers.checkers.Stone;
 | 
					import djmil.cordacheckers.checkers.Stone;
 | 
				
			||||||
import djmil.cordacheckers.contracts.GameBoardContract;
 | 
					import djmil.cordacheckers.contracts.GameBoardContract;
 | 
				
			||||||
@ -18,6 +20,7 @@ import net.corda.v5.ledger.utxo.BelongsToContract;
 | 
				
			|||||||
public class GameBoardState extends GameState {
 | 
					public class GameBoardState extends GameState {
 | 
				
			||||||
    private final Stone.Color activeColor;
 | 
					    private final Stone.Color activeColor;
 | 
				
			||||||
    private final Integer moveNumber;
 | 
					    private final Integer moveNumber;
 | 
				
			||||||
 | 
					    private final Boolean drawRequest;
 | 
				
			||||||
    private final Map<Integer, Stone> board;
 | 
					    private final Map<Integer, Stone> board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public GameBoardState(GameProposalState gameProposalState) {
 | 
					    public GameBoardState(GameProposalState gameProposalState) {
 | 
				
			||||||
@ -27,6 +30,7 @@ public class GameBoardState extends GameState {
 | 
				
			|||||||
        this.board = gameProposalState.getBoard();
 | 
					        this.board = gameProposalState.getBoard();
 | 
				
			||||||
        this.activeColor = Stone.Color.WHITE;
 | 
					        this.activeColor = Stone.Color.WHITE;
 | 
				
			||||||
        this.moveNumber = 0;
 | 
					        this.moveNumber = 0;
 | 
				
			||||||
 | 
					        this.drawRequest = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public GameBoardState(GameBoardState currentGameBoardState, List<Integer> move, String message) {
 | 
					    public GameBoardState(GameBoardState currentGameBoardState, List<Integer> move, String message) {
 | 
				
			||||||
@ -39,10 +43,21 @@ public class GameBoardState extends GameState {
 | 
				
			|||||||
        this.moveNumber = (currentGameBoardState.activeColor == this.activeColor)
 | 
					        this.moveNumber = (currentGameBoardState.activeColor == this.activeColor)
 | 
				
			||||||
            ? currentGameBoardState.getMoveNumber() // current player has not finished his move jet
 | 
					            ? currentGameBoardState.getMoveNumber() // current player has not finished his move jet
 | 
				
			||||||
            : currentGameBoardState.getMoveNumber() +1;
 | 
					            : currentGameBoardState.getMoveNumber() +1;
 | 
				
			||||||
 | 
					        this.drawRequest = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public GameBoardState(GameBoardState currentGameBoardState, Boolean drawRequest) {
 | 
				
			||||||
 | 
					        super(currentGameBoardState.whitePlayer, currentGameBoardState.blackPlayer, currentGameBoardState.gameUuid, 
 | 
				
			||||||
 | 
					                    null, currentGameBoardState.participants);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.board = currentGameBoardState.getBoard();
 | 
				
			||||||
 | 
					        this.activeColor = currentGameBoardState.activeColor;
 | 
				
			||||||
 | 
					        this.moveNumber = currentGameBoardState.moveNumber;
 | 
				
			||||||
 | 
					        this.drawRequest = drawRequest;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
    @ConstructorForDeserialization
 | 
					    @ConstructorForDeserialization
 | 
				
			||||||
    public GameBoardState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, 
 | 
					    public GameBoardState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, Boolean drawRequest, 
 | 
				
			||||||
            Stone.Color activeColor, Integer moveNumber, Map<Integer, Stone> board, String message, 
 | 
					            Stone.Color activeColor, Integer moveNumber, Map<Integer, Stone> board, String message, 
 | 
				
			||||||
            UUID gameUuid, List<PublicKey> participants) {
 | 
					            UUID gameUuid, List<PublicKey> participants) {
 | 
				
			||||||
        super(whitePlayer, blackPlayer, gameUuid, message, participants);
 | 
					        super(whitePlayer, blackPlayer, gameUuid, message, participants);
 | 
				
			||||||
@ -50,6 +65,7 @@ public class GameBoardState extends GameState {
 | 
				
			|||||||
        this.activeColor = activeColor;
 | 
					        this.activeColor = activeColor;
 | 
				
			||||||
        this.moveNumber = moveNumber;
 | 
					        this.moveNumber = moveNumber;
 | 
				
			||||||
        this.board = board;
 | 
					        this.board = board;
 | 
				
			||||||
 | 
					        this.drawRequest = drawRequest;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public Stone.Color getActiveColor() {
 | 
					    public Stone.Color getActiveColor() {
 | 
				
			||||||
@ -72,6 +88,15 @@ public class GameBoardState extends GameState {
 | 
				
			|||||||
        return Collections.unmodifiableMap(board);
 | 
					        return Collections.unmodifiableMap(board);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable
 | 
				
			||||||
 | 
					    public Boolean getDrawRequest() {
 | 
				
			||||||
 | 
					        return drawRequest;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Boolean isDrawRequested() {
 | 
				
			||||||
 | 
					        return drawRequest != null && drawRequest == true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public boolean equals(Object obj) {
 | 
					    public boolean equals(Object obj) {
 | 
				
			||||||
        if (this == obj)
 | 
					        if (this == obj)
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,8 @@ import java.security.PublicKey;
 | 
				
			|||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.jetbrains.annotations.Nullable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import djmil.cordacheckers.contracts.GameResultContract;
 | 
					import djmil.cordacheckers.contracts.GameResultContract;
 | 
				
			||||||
import net.corda.v5.base.annotations.ConstructorForDeserialization;
 | 
					import net.corda.v5.base.annotations.ConstructorForDeserialization;
 | 
				
			||||||
import net.corda.v5.base.types.MemberX500Name;
 | 
					import net.corda.v5.base.types.MemberX500Name;
 | 
				
			||||||
@ -12,7 +14,7 @@ import net.corda.v5.ledger.utxo.BelongsToContract;
 | 
				
			|||||||
@BelongsToContract(GameResultContract.class)
 | 
					@BelongsToContract(GameResultContract.class)
 | 
				
			||||||
public class GameResultState extends GameState {
 | 
					public class GameResultState extends GameState {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final MemberX500Name winnerName;
 | 
					    private final MemberX500Name winnerName; // NULL if it is a draw
 | 
				
			||||||
    private final Integer totalMoves;
 | 
					    private final Integer totalMoves;
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
    @ConstructorForDeserialization
 | 
					    @ConstructorForDeserialization
 | 
				
			||||||
@ -29,6 +31,7 @@ public class GameResultState extends GameState {
 | 
				
			|||||||
        this.totalMoves = gameBoardState.getMoveNumber();
 | 
					        this.totalMoves = gameBoardState.getMoveNumber();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable
 | 
				
			||||||
    public MemberX500Name getWinnerName() {
 | 
					    public MemberX500Name getWinnerName() {
 | 
				
			||||||
        return winnerName;
 | 
					        return winnerName;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					package djmil.cordacheckers.gameboard;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import djmil.cordacheckers.contracts.GameCommand;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gameresult.GameResultCommiter;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gamestate.FlowResponce;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gamestate.View;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gamestate.ViewBuilder;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.ClientRequestBody;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.ClientStartableFlow;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.CordaInject;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.FlowEngine;
 | 
				
			||||||
 | 
					import net.corda.v5.application.marshalling.JsonMarshallingService;
 | 
				
			||||||
 | 
					import net.corda.v5.base.annotations.Suspendable;
 | 
				
			||||||
 | 
					import net.corda.v5.crypto.SecureHash;
 | 
				
			||||||
 | 
					import net.corda.v5.ledger.utxo.UtxoLedgerService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class DrawAcceptFlow implements ClientStartableFlow{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     private final static Logger log = LoggerFactory.getLogger(DrawAcceptFlow.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @CordaInject
 | 
				
			||||||
 | 
					     public JsonMarshallingService jsonMarshallingService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @CordaInject
 | 
				
			||||||
 | 
					     public UtxoLedgerService utxoLedgerService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @CordaInject
 | 
				
			||||||
 | 
					     public FlowEngine flowEngine;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @Suspendable
 | 
				
			||||||
 | 
					     @Override
 | 
				
			||||||
 | 
					     public String call(ClientRequestBody requestBody) {
 | 
				
			||||||
 | 
					          SecureHash utxoTrxId = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					               final GameCommand command = new GameCommand(GameCommand.Action.DRAW_ACCEPT);
 | 
				
			||||||
 | 
					               final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               utxoTrxId = this.flowEngine
 | 
				
			||||||
 | 
					                    .subFlow(new GameResultCommiter(gameUuid, command));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               final View gameStateView = this.flowEngine
 | 
				
			||||||
 | 
					                    .subFlow(new ViewBuilder(utxoTrxId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               return new FlowResponce(gameStateView, utxoTrxId)
 | 
				
			||||||
 | 
					                    .toJsonEncodedString(jsonMarshallingService);
 | 
				
			||||||
 | 
					          } 
 | 
				
			||||||
 | 
					          catch (Exception e) {
 | 
				
			||||||
 | 
					               log.warn(requestBody + " [ERROR] " +e.toString());
 | 
				
			||||||
 | 
					               e.printStackTrace();
 | 
				
			||||||
 | 
					               return new FlowResponce(e, utxoTrxId)
 | 
				
			||||||
 | 
					                    .toJsonEncodedString(jsonMarshallingService);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					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.CommitTrx;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gamestate.FlowResponce;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gamestate.GetFlow;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gamestate.View;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gamestate.ViewBuilder;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.states.GameBoardState;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.states.GameState;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.ClientRequestBody;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.ClientStartableFlow;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.CordaInject;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.FlowEngine;
 | 
				
			||||||
 | 
					import net.corda.v5.application.marshalling.JsonMarshallingService;
 | 
				
			||||||
 | 
					import net.corda.v5.base.annotations.Suspendable;
 | 
				
			||||||
 | 
					import net.corda.v5.crypto.SecureHash;
 | 
				
			||||||
 | 
					import net.corda.v5.ledger.utxo.StateAndRef;
 | 
				
			||||||
 | 
					import net.corda.v5.ledger.utxo.UtxoLedgerService;
 | 
				
			||||||
 | 
					import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class DrawRejectFlow implements ClientStartableFlow{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     private final static Logger log = LoggerFactory.getLogger(DrawRejectFlow.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @CordaInject
 | 
				
			||||||
 | 
					     public JsonMarshallingService jsonMarshallingService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @CordaInject
 | 
				
			||||||
 | 
					     public UtxoLedgerService utxoLedgerService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @CordaInject
 | 
				
			||||||
 | 
					     public FlowEngine flowEngine;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @Suspendable
 | 
				
			||||||
 | 
					     @Override
 | 
				
			||||||
 | 
					     public String call(ClientRequestBody requestBody) {
 | 
				
			||||||
 | 
					          SecureHash utxoTrxId = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					               final GameCommand command = new GameCommand(GameCommand.Action.DRAW_REJECT);
 | 
				
			||||||
 | 
					               final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               final StateAndRef<GameState> currentGameSar = this.flowEngine
 | 
				
			||||||
 | 
					                    .subFlow(new GetFlow(gameUuid));
 | 
				
			||||||
 | 
					               final GameBoardState currenGameBoardState = (GameBoardState)currentGameSar.getState().getContractState();
 | 
				
			||||||
 | 
					               
 | 
				
			||||||
 | 
					               final GameBoardState newGameBoard = new GameBoardState(
 | 
				
			||||||
 | 
					                    currenGameBoardState,
 | 
				
			||||||
 | 
					                    false); // <<-- Decline draw request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               final UtxoSignedTransaction drawDeclineTrx = utxoLedgerService.createTransactionBuilder()
 | 
				
			||||||
 | 
					                    .addCommand(command)
 | 
				
			||||||
 | 
					                    .addInputState(currentGameSar.getRef())
 | 
				
			||||||
 | 
					                    .addOutputState(newGameBoard)
 | 
				
			||||||
 | 
					                    .addSignatories(newGameBoard.getParticipants())
 | 
				
			||||||
 | 
					                    .setNotary(currentGameSar.getState().getNotaryName())
 | 
				
			||||||
 | 
					                    .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
 | 
				
			||||||
 | 
					                    .toSignedTransaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               utxoTrxId = this.flowEngine
 | 
				
			||||||
 | 
					                    .subFlow(new CommitTrx(drawDeclineTrx, currenGameBoardState.getActivePlayerName()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               final View gameStateView = this.flowEngine
 | 
				
			||||||
 | 
					                    .subFlow(new ViewBuilder(utxoTrxId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               return new FlowResponce(gameStateView, utxoTrxId)
 | 
				
			||||||
 | 
					                    .toJsonEncodedString(jsonMarshallingService);
 | 
				
			||||||
 | 
					          } 
 | 
				
			||||||
 | 
					          catch (Exception e) {
 | 
				
			||||||
 | 
					               log.warn(requestBody + " [ERROR] " +e.toString());
 | 
				
			||||||
 | 
					               return new FlowResponce(e, utxoTrxId)
 | 
				
			||||||
 | 
					                    .toJsonEncodedString(jsonMarshallingService);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					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.CommitTrx;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gamestate.FlowResponce;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gamestate.GetFlow;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gamestate.View;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.gamestate.ViewBuilder;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.states.GameBoardState;
 | 
				
			||||||
 | 
					import djmil.cordacheckers.states.GameState;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.ClientRequestBody;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.ClientStartableFlow;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.CordaInject;
 | 
				
			||||||
 | 
					import net.corda.v5.application.flows.FlowEngine;
 | 
				
			||||||
 | 
					import net.corda.v5.application.marshalling.JsonMarshallingService;
 | 
				
			||||||
 | 
					import net.corda.v5.application.membership.MemberLookup;
 | 
				
			||||||
 | 
					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 DrawRequestFlow implements ClientStartableFlow{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     private final static Logger log = LoggerFactory.getLogger(DrawRequestFlow.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @CordaInject
 | 
				
			||||||
 | 
					     public JsonMarshallingService jsonMarshallingService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @CordaInject
 | 
				
			||||||
 | 
					     public MemberLookup memberLookup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @CordaInject
 | 
				
			||||||
 | 
					     public UtxoLedgerService utxoLedgerService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @CordaInject
 | 
				
			||||||
 | 
					     public FlowEngine flowEngine;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @Suspendable
 | 
				
			||||||
 | 
					     @Override
 | 
				
			||||||
 | 
					     public String call(ClientRequestBody requestBody) {
 | 
				
			||||||
 | 
					          SecureHash utxoTrxId = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					               final GameCommand command = new GameCommand(GameCommand.Action.DRAW_REQUEST);
 | 
				
			||||||
 | 
					               final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               final StateAndRef<GameState> currentGameSar = this.flowEngine
 | 
				
			||||||
 | 
					                    .subFlow(new GetFlow(gameUuid));
 | 
				
			||||||
 | 
					               final GameBoardState currenGameBoardState = (GameBoardState)currentGameSar.getState().getContractState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               final GameBoardState newGameBoard = new GameBoardState(
 | 
				
			||||||
 | 
					                    currenGameBoardState,
 | 
				
			||||||
 | 
					                    true); // <<-- draw request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               final UtxoSignedTransaction drawReqTrx = utxoLedgerService.createTransactionBuilder()
 | 
				
			||||||
 | 
					                    .addCommand(command)
 | 
				
			||||||
 | 
					                    .addInputState(currentGameSar.getRef())
 | 
				
			||||||
 | 
					                    .addOutputState(newGameBoard)
 | 
				
			||||||
 | 
					                    .addSignatories(newGameBoard.getParticipants())
 | 
				
			||||||
 | 
					                    .setNotary(currentGameSar.getState().getNotaryName())
 | 
				
			||||||
 | 
					                    .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
 | 
				
			||||||
 | 
					                    .toSignedTransaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               utxoTrxId = this.flowEngine
 | 
				
			||||||
 | 
					                    .subFlow(new CommitTrx(drawReqTrx, currenGameBoardState.getIdelPlayerName()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               final View gameStateView = this.flowEngine
 | 
				
			||||||
 | 
					                    .subFlow(new ViewBuilder(utxoTrxId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               return new FlowResponce(gameStateView, utxoTrxId)
 | 
				
			||||||
 | 
					                    .toJsonEncodedString(jsonMarshallingService);
 | 
				
			||||||
 | 
					          } 
 | 
				
			||||||
 | 
					          catch (Exception e) {
 | 
				
			||||||
 | 
					               log.warn(requestBody + " [ERROR] " +e.toString());
 | 
				
			||||||
 | 
					               return new FlowResponce(e, utxoTrxId)
 | 
				
			||||||
 | 
					                    .toJsonEncodedString(jsonMarshallingService);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -55,32 +55,33 @@ public class MoveFlow implements ClientStartableFlow{
 | 
				
			|||||||
               
 | 
					               
 | 
				
			||||||
               final GameCommand command = new GameCommand(args.move);
 | 
					               final GameCommand command = new GameCommand(args.move);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               final StateAndRef<GameState> currenrGameSar = this.flowEngine
 | 
					               final StateAndRef<GameState> currentGameSar = this.flowEngine
 | 
				
			||||||
                    .subFlow(new GetFlow(args.gameUuid));
 | 
					                    .subFlow(new GetFlow(args.gameUuid));
 | 
				
			||||||
 | 
					               final GameBoardState currenGameBoardState = (GameBoardState)currentGameSar.getState().getContractState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               final GameBoardState newGameBoard = new GameBoardState(
 | 
					               final GameBoardState newGameBoard = new GameBoardState(
 | 
				
			||||||
                    (GameBoardState)currenrGameSar.getState().getContractState(), 
 | 
					                    currenGameBoardState, 
 | 
				
			||||||
                    args.move,
 | 
					                    args.move,
 | 
				
			||||||
                    args.message);
 | 
					                    args.message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               final UtxoSignedTransaction moveTrx = utxoLedgerService.createTransactionBuilder()
 | 
					               final UtxoSignedTransaction moveTrx = utxoLedgerService.createTransactionBuilder()
 | 
				
			||||||
                    .addCommand(command)
 | 
					                    .addCommand(command)
 | 
				
			||||||
                    .addInputState(currenrGameSar.getRef())
 | 
					                    .addInputState(currentGameSar.getRef())
 | 
				
			||||||
                    .addOutputState(newGameBoard)
 | 
					                    .addOutputState(newGameBoard)
 | 
				
			||||||
                    .addSignatories(newGameBoard.getParticipants())
 | 
					                    .addSignatories(newGameBoard.getParticipants())
 | 
				
			||||||
                    .setNotary(currenrGameSar.getState().getNotaryName())
 | 
					                    .setNotary(currentGameSar.getState().getNotaryName())
 | 
				
			||||||
                    .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
 | 
					                    .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
 | 
				
			||||||
                    .toSignedTransaction();
 | 
					                    .toSignedTransaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               utxoTrxId = this.flowEngine
 | 
					               utxoTrxId = this.flowEngine
 | 
				
			||||||
                    .subFlow(new CommitTrx(moveTrx, command.getCounterparty(currenrGameSar)));
 | 
					                    .subFlow(new CommitTrx(moveTrx, currenGameBoardState.getIdelPlayerName()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               if (amIwon(newGameBoard)) {
 | 
					               if (amIwon(newGameBoard)) {
 | 
				
			||||||
                    log.info("Opponent has no possible moves. Claim victory!");
 | 
					                    log.info("Opponent has no possible moves. Claim victory!");
 | 
				
			||||||
                    utxoTrxId = this.flowEngine
 | 
					                    utxoTrxId = this.flowEngine
 | 
				
			||||||
                         .subFlow(new GameResultCommiter(
 | 
					                         .subFlow(new GameResultCommiter(
 | 
				
			||||||
                                   newGameBoard.getGameUuid(), 
 | 
					                                   newGameBoard.getGameUuid(), 
 | 
				
			||||||
                                   new GameCommand(GameCommand.Action.GAME_BOARD_VICTORY)));
 | 
					                                   new GameCommand(GameCommand.Action.CLAIM_VICTORY)));
 | 
				
			||||||
               }
 | 
					               }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               final View gameStateView = this.flowEngine
 | 
					               final View gameStateView = this.flowEngine
 | 
				
			||||||
 | 
				
			|||||||
@ -38,7 +38,7 @@ public class SurrenderFlow implements ClientStartableFlow{
 | 
				
			|||||||
          SecureHash utxoTrxId = null;
 | 
					          SecureHash utxoTrxId = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          try {
 | 
					          try {
 | 
				
			||||||
               final GameCommand command = new GameCommand(GameCommand.Action.GAME_BOARD_SURRENDER);
 | 
					               final GameCommand command = new GameCommand(GameCommand.Action.SURRENDER);
 | 
				
			||||||
               final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
 | 
					               final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               utxoTrxId = this.flowEngine
 | 
					               utxoTrxId = this.flowEngine
 | 
				
			||||||
 | 
				
			|||||||
@ -39,7 +39,7 @@ public class VictoryFlow implements ClientStartableFlow {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
 | 
					            final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
 | 
				
			||||||
            final GameCommand command = new GameCommand(GameCommand.Action.GAME_BOARD_VICTORY);
 | 
					            final GameCommand command = new GameCommand(GameCommand.Action.CLAIM_VICTORY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            utxoTrxId = this.flowEngine
 | 
					            utxoTrxId = this.flowEngine
 | 
				
			||||||
                .subFlow(new GameResultCommiter(gameUuid, command));
 | 
					                .subFlow(new GameResultCommiter(gameUuid, command));
 | 
				
			||||||
 | 
				
			|||||||
@ -56,7 +56,7 @@ public class AcceptFlow implements ClientStartableFlow{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
               final GameBoardState gameBoard = new GameBoardState(gameProposal); // <<-- accepted 
 | 
					               final GameBoardState gameBoard = new GameBoardState(gameProposal); // <<-- accepted 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               final UtxoSignedTransaction acceptTrx = utxoLedgerService.createTransactionBuilder()
 | 
					               final UtxoSignedTransaction gameProposalAcceptTrx = utxoLedgerService.createTransactionBuilder()
 | 
				
			||||||
                    .addCommand(command)
 | 
					                    .addCommand(command)
 | 
				
			||||||
                    .addInputState(gameProposalSar.getRef())
 | 
					                    .addInputState(gameProposalSar.getRef())
 | 
				
			||||||
                    .addOutputState(gameBoard)
 | 
					                    .addOutputState(gameBoard)
 | 
				
			||||||
@ -66,7 +66,7 @@ public class AcceptFlow implements ClientStartableFlow{
 | 
				
			|||||||
                    .toSignedTransaction();
 | 
					                    .toSignedTransaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               utxoTrxId = this.flowEngine
 | 
					               utxoTrxId = this.flowEngine
 | 
				
			||||||
                    .subFlow(new CommitTrx(acceptTrx, command.getCounterparty(gameProposal)));
 | 
					                    .subFlow(new CommitTrx(gameProposalAcceptTrx, gameProposal.getIssuerName()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               final View gameView = this.flowEngine
 | 
					               final View gameView = this.flowEngine
 | 
				
			||||||
                    .subFlow(new ViewBuilder(utxoTrxId));
 | 
					                    .subFlow(new ViewBuilder(utxoTrxId));
 | 
				
			||||||
 | 
				
			|||||||
@ -62,7 +62,7 @@ public class CancelFlow implements ClientStartableFlow{
 | 
				
			|||||||
                    .toSignedTransaction();
 | 
					                    .toSignedTransaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               utxoTrxId = this.flowEngine
 | 
					               utxoTrxId = this.flowEngine
 | 
				
			||||||
                    .subFlow(new CommitTrx(gameProposalCancelTrx, command.getCounterparty(gameProposal)));
 | 
					                    .subFlow(new CommitTrx(gameProposalCancelTrx, gameProposal.getAcquierName()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               final View gameStateView = this.flowEngine
 | 
					               final View gameStateView = this.flowEngine
 | 
				
			||||||
                    .subFlow(new ViewBuilder(utxoTrxId));
 | 
					                    .subFlow(new ViewBuilder(utxoTrxId));
 | 
				
			||||||
 | 
				
			|||||||
@ -71,7 +71,7 @@ public class CreateFlow implements ClientStartableFlow{
 | 
				
			|||||||
                    .toSignedTransaction();
 | 
					                    .toSignedTransaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               utxoTrxId = this.flowEngine
 | 
					               utxoTrxId = this.flowEngine
 | 
				
			||||||
                    .subFlow(new CommitTrx(gameProposalCreateTrx, command.getCounterparty(gameProposal)));
 | 
					                    .subFlow(new CommitTrx(gameProposalCreateTrx, gameProposal.getAcquierName()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               final View gameView = this.flowEngine
 | 
					               final View gameView = this.flowEngine
 | 
				
			||||||
                    .subFlow(new ViewBuilder(utxoTrxId));
 | 
					                    .subFlow(new ViewBuilder(utxoTrxId));
 | 
				
			||||||
 | 
				
			|||||||
@ -70,7 +70,7 @@ public class RejectFlow implements ClientStartableFlow{
 | 
				
			|||||||
                    .toSignedTransaction();
 | 
					                    .toSignedTransaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               utxoTrxId = this.flowEngine
 | 
					               utxoTrxId = this.flowEngine
 | 
				
			||||||
                    .subFlow(new CommitTrx(gameProposalRejectTrx, command.getCounterparty(gameProposal)));
 | 
					                    .subFlow(new CommitTrx(gameProposalRejectTrx, gameProposal.getIssuerName()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
               final View gameStateView = this.flowEngine
 | 
					               final View gameStateView = this.flowEngine
 | 
				
			||||||
                    .subFlow(new ViewBuilder(utxoTrxId));
 | 
					                    .subFlow(new ViewBuilder(utxoTrxId));
 | 
				
			||||||
 | 
				
			|||||||
@ -65,7 +65,7 @@ public class GameResultCommiter implements SubFlow<SecureHash> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return this.flowEngine.subFlow( 
 | 
					        return this.flowEngine.subFlow( 
 | 
				
			||||||
            new CommitTrx(gameResultTrx, 
 | 
					            new CommitTrx(gameResultTrx, 
 | 
				
			||||||
                    command.getCounterparty(gameResult), 
 | 
					                    getCounterparty(gameResult),
 | 
				
			||||||
                    custodianInfo.getName()) );
 | 
					                    custodianInfo.getName()) );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -84,12 +84,16 @@ public class GameResultCommiter implements SubFlow<SecureHash> {
 | 
				
			|||||||
        final MemberX500Name myName = memberLookup.myInfo().getName();
 | 
					        final MemberX500Name myName = memberLookup.myInfo().getName();
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        switch(this.command.getAction()) {
 | 
					        switch(this.command.getAction()) {
 | 
				
			||||||
            case GAME_BOARD_VICTORY:
 | 
					            case CLAIM_VICTORY:
 | 
				
			||||||
                return new GameResultState(myName, // i'm a winner
 | 
					                return new GameResultState(myName, // i'm a winner
 | 
				
			||||||
                            gameBoard, custodianPublicKey);
 | 
					                            gameBoard, custodianPublicKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case GAME_BOARD_SURRENDER:
 | 
					            case SURRENDER:
 | 
				
			||||||
                return new GameResultState(gameBoard.getOpponentName(myName), // me surrender to
 | 
					                return new GameResultState(gameBoard.getOpponentName(myName), // me surrender to opponent
 | 
				
			||||||
 | 
					                            gameBoard, custodianPublicKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case DRAW_ACCEPT:
 | 
				
			||||||
 | 
					                return new GameResultState(null, // there is no winner, it's a draw
 | 
				
			||||||
                            gameBoard, custodianPublicKey);
 | 
					                            gameBoard, custodianPublicKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
@ -97,4 +101,10 @@ public class GameResultCommiter implements SubFlow<SecureHash> {
 | 
				
			|||||||
        } 
 | 
					        } 
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Suspendable
 | 
				
			||||||
 | 
					    MemberX500Name getCounterparty(GameState gameState) {
 | 
				
			||||||
 | 
					        final MemberX500Name myName = this.memberLookup.myInfo().getName();
 | 
				
			||||||
 | 
					        return gameState.getOpponentName(myName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,16 +3,19 @@ package djmil.cordacheckers.gameresult;
 | 
				
			|||||||
public class Rank {
 | 
					public class Rank {
 | 
				
			||||||
    private Integer gamesPlayed;
 | 
					    private Integer gamesPlayed;
 | 
				
			||||||
    private Integer gamesWon;
 | 
					    private Integer gamesWon;
 | 
				
			||||||
 | 
					    private Integer gamesDraw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Serialisation service requires a default constructor
 | 
					    // Serialisation service requires a default constructor
 | 
				
			||||||
    public Rank() {
 | 
					    public Rank() {
 | 
				
			||||||
        gamesPlayed = 0;
 | 
					        gamesPlayed = 0;
 | 
				
			||||||
        gamesWon = 0;
 | 
					        gamesWon = 0;
 | 
				
			||||||
 | 
					        gamesDraw = 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public Rank(Integer gamesPlayed, Integer gamesWon) {
 | 
					    public Rank(Integer gamesPlayed, Integer gamesWon, Integer gamesDraw) {
 | 
				
			||||||
        this.gamesPlayed = gamesPlayed;
 | 
					        this.gamesPlayed = gamesPlayed;
 | 
				
			||||||
        this.gamesWon = gamesWon;
 | 
					        this.gamesWon = gamesWon;
 | 
				
			||||||
 | 
					        this.gamesDraw = gamesDraw;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Rank gamePlayed() {
 | 
					    Rank gamePlayed() {
 | 
				
			||||||
@ -25,6 +28,13 @@ public class Rank {
 | 
				
			|||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Rank gameDraw(boolean isDraw) {
 | 
				
			||||||
 | 
					        if (isDraw)
 | 
				
			||||||
 | 
					            gamesDraw++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public Integer getGamesPlayed() {
 | 
					    public Integer getGamesPlayed() {
 | 
				
			||||||
        return gamesPlayed;
 | 
					        return gamesPlayed;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -33,4 +43,8 @@ public class Rank {
 | 
				
			|||||||
        return gamesWon;
 | 
					        return gamesWon;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Integer getGamesDraw() {
 | 
				
			||||||
 | 
					        return gamesDraw;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -39,15 +39,19 @@ public class RankingFlow implements ClientStartableFlow {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            Map<String, Rank> leaderboard = newLeaderboard();
 | 
					            Map<String, Rank> leaderboard = newLeaderboard();
 | 
				
			||||||
            for (GameResultState gs : gameStateResutList) {
 | 
					            for (GameResultState gs : gameStateResutList) {
 | 
				
			||||||
                final var winner = gs.getWinnerName().getCommonName();
 | 
					             
 | 
				
			||||||
                if (winner != null)
 | 
					                boolean isDraw = true;
 | 
				
			||||||
                    leaderboard.put(winner, leaderboard.get(winner).gameWon() );
 | 
					                if (gs.getWinnerName() != null) {
 | 
				
			||||||
 | 
					                    final var winnerCommonName =  gs.getWinnerName().getCommonName();
 | 
				
			||||||
 | 
					                    leaderboard.put(winnerCommonName, leaderboard.get(winnerCommonName).gameWon() );
 | 
				
			||||||
 | 
					                    isDraw = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                final var blackPlayer = gs.getBlackPlayer().getCommonName();
 | 
					                final var blackPlayer = gs.getBlackPlayer().getCommonName();
 | 
				
			||||||
                leaderboard.put(blackPlayer, leaderboard.get(blackPlayer).gamePlayed() );
 | 
					                leaderboard.put(blackPlayer, leaderboard.get(blackPlayer).gamePlayed().gameDraw(isDraw) );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                final var whitePlayer = gs.getWhitePlayer().getCommonName();
 | 
					                final var whitePlayer = gs.getWhitePlayer().getCommonName();
 | 
				
			||||||
                leaderboard.put(whitePlayer, leaderboard.get(whitePlayer).gamePlayed() );
 | 
					                leaderboard.put(whitePlayer, leaderboard.get(whitePlayer).gamePlayed().gameDraw(isDraw) );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return new RankingFlowResponce(leaderboard)
 | 
					            return new RankingFlowResponce(leaderboard)
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ public class RankingFlowResponce {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public RankingFlowResponce(Exception exception) {
 | 
					    public RankingFlowResponce(Exception exception) {
 | 
				
			||||||
        this.successStatus = null;
 | 
					        this.successStatus = null;
 | 
				
			||||||
        this.failureStatus = exception.getMessage();
 | 
					        this.failureStatus = exception.toString();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) {
 | 
					    public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) {
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ public class ListFlowResponce {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public ListFlowResponce(Exception exception) {
 | 
					    public ListFlowResponce(Exception exception) {
 | 
				
			||||||
        this.successStatus = null;
 | 
					        this.successStatus = null;
 | 
				
			||||||
        this.failureStatus = exception.getMessage();
 | 
					        this.failureStatus = exception.toString();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) {
 | 
					    public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) {
 | 
				
			||||||
 | 
				
			|||||||
@ -21,8 +21,12 @@ public class View {
 | 
				
			|||||||
        GAME_BOARD_WAIT_FOR_OPPONENT,
 | 
					        GAME_BOARD_WAIT_FOR_OPPONENT,
 | 
				
			||||||
        GAME_BOARD_WAIT_FOR_YOU,
 | 
					        GAME_BOARD_WAIT_FOR_YOU,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DRAW_REQUEST_WAIT_FOR_OPPONENT,
 | 
				
			||||||
 | 
					        DRAW_REQUEST_WAIT_FOR_YOU,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GAME_RESULT_YOU_WON,
 | 
					        GAME_RESULT_YOU_WON,
 | 
				
			||||||
        GAME_RESULT_YOU_LOOSE;
 | 
					        GAME_RESULT_YOU_LOOSE,
 | 
				
			||||||
 | 
					        GAME_RESULT_DRAW;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    public final Status status;
 | 
					    public final Status status;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -76,19 +76,29 @@ public class ViewBuilder implements SubFlow<View> {
 | 
				
			|||||||
                return View.Status.GAME_PROPOSAL_CANCELED;
 | 
					                return View.Status.GAME_PROPOSAL_CANCELED;
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            case GAME_PROPOSAL_ACCEPT:
 | 
					            case GAME_PROPOSAL_ACCEPT:
 | 
				
			||||||
            case GAME_BOARD_MOVE:
 | 
					            case DRAW_REJECT:
 | 
				
			||||||
 | 
					            case MOVE:
 | 
				
			||||||
                if (game.isMyAction(myName))
 | 
					                if (game.isMyAction(myName))
 | 
				
			||||||
                    return View.Status.GAME_BOARD_WAIT_FOR_YOU;
 | 
					                    return View.Status.GAME_BOARD_WAIT_FOR_YOU;
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                    return View.Status.GAME_BOARD_WAIT_FOR_OPPONENT;
 | 
					                    return View.Status.GAME_BOARD_WAIT_FOR_OPPONENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case GAME_BOARD_SURRENDER:
 | 
					            case DRAW_REQUEST:
 | 
				
			||||||
 | 
					                if (game.isMyAction(myName))
 | 
				
			||||||
 | 
					                    return View.Status.DRAW_REQUEST_WAIT_FOR_YOU;
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                    return View.Status.DRAW_REQUEST_WAIT_FOR_OPPONENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case DRAW_ACCEPT:
 | 
				
			||||||
 | 
					                return View.Status.GAME_RESULT_DRAW;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case SURRENDER:
 | 
				
			||||||
                if (game.issuer.compareTo(myName) == 0)
 | 
					                if (game.issuer.compareTo(myName) == 0)
 | 
				
			||||||
                    return View.Status.GAME_RESULT_YOU_LOOSE;
 | 
					                    return View.Status.GAME_RESULT_YOU_LOOSE;
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                    return View.Status.GAME_RESULT_YOU_WON; 
 | 
					                    return View.Status.GAME_RESULT_YOU_WON; 
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            case GAME_BOARD_VICTORY:
 | 
					            case CLAIM_VICTORY:
 | 
				
			||||||
                if (game.issuer.compareTo(myName) == 0)
 | 
					                if (game.issuer.compareTo(myName) == 0)
 | 
				
			||||||
                    return View.Status.GAME_RESULT_YOU_WON;
 | 
					                    return View.Status.GAME_RESULT_YOU_WON;
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user