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.RspGameState;
|
||||
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
|
||||
public class CordaClient {
|
||||
@ -73,7 +73,7 @@ public class CordaClient {
|
||||
"djmil.cordacheckers.gameresult.RankingFlow",
|
||||
new Req());
|
||||
|
||||
return cordaFlowExecute(holdingIdentity, requestBody, RspRankList.class)
|
||||
return cordaFlowExecute(holdingIdentity, requestBody, RspRankMap.class)
|
||||
.getResponce(requestBody);
|
||||
}
|
||||
|
||||
@ -174,6 +174,36 @@ public class CordaClient {
|
||||
.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) {
|
||||
try {
|
||||
final String requestBodyJson = this.jsonMapper
|
||||
|
@ -21,10 +21,16 @@ public record GameState(
|
||||
GAME_PROPOSAL_WAIT_FOR_YOU,
|
||||
GAME_PROPOSAL_REJECTED,
|
||||
GAME_PROPOSAL_CANCELED,
|
||||
|
||||
GAME_BOARD_WAIT_FOR_OPPONENT,
|
||||
GAME_BOARD_WAIT_FOR_YOU,
|
||||
|
||||
DRAW_REQUEST_WAIT_FOR_OPPONENT,
|
||||
DRAW_REQUEST_WAIT_FOR_YOU,
|
||||
|
||||
GAME_RESULT_YOU_WON,
|
||||
GAME_RESULT_YOU_LOOSE;
|
||||
GAME_RESULT_YOU_LOOSE,
|
||||
GAME_RESULT_DRAW;
|
||||
}
|
||||
|
||||
public final static Map<Integer, Stone> defaultGameBoard = Map.ofEntries(
|
||||
|
@ -2,7 +2,8 @@ package djmil.cordacheckers.cordaclient.dao;
|
||||
|
||||
public record Rank(
|
||||
Integer gamesPlayed,
|
||||
Integer gamesWon
|
||||
Integer gamesWon,
|
||||
Integer gamesDraw
|
||||
) {
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import java.util.Map;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.Rank;
|
||||
|
||||
public record RspRankList(
|
||||
public record RspRankMap(
|
||||
Map<String, Rank> successStatus,
|
||||
String failureStatus) implements Rsp<Map<String, Rank>> {
|
||||
|
@ -193,7 +193,7 @@ public class GameBoardTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVictoryTest1() {
|
||||
void testVictory_NoStones() {
|
||||
final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
|
||||
final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
|
||||
|
||||
@ -217,7 +217,7 @@ public class GameBoardTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVictoryTest2() {
|
||||
void testVictory_StonesBlocked() {
|
||||
final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
|
||||
final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
|
||||
|
||||
@ -240,6 +240,108 @@ public class GameBoardTests {
|
||||
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) {
|
||||
return new ArrayList<Integer>(Arrays.asList(from, to));
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ public class GameProposalTests {
|
||||
assertThat(acquierGameView.opponentName()).isEqualToIgnoringCase(issuer);
|
||||
assertThat(acquierGameView.opponentColor()).isEqualByComparingTo(acquierColor.opposite());
|
||||
assertThat(acquierGameView.board()).containsAllEntriesOf(GameState.defaultGameBoard);
|
||||
assertThat(acquierGameView.previousMove()).isEmpty();
|
||||
assertThat(acquierGameView.previousMove()).isNull();
|
||||
assertThat(acquierGameView.moveNumber() == 0);
|
||||
assertThat(acquierGameView.message()).isEqualToIgnoringCase(message);
|
||||
assertThat(acquierGameView.uuid()).isEqualByComparingTo(issuerGameView.uuid());
|
||||
@ -134,8 +134,7 @@ public class GameProposalTests {
|
||||
hiIssuer, hiAcquier, acquierColor, "GameProposal ACCEPT test");
|
||||
|
||||
assertThatThrownBy(() -> { // Issuer can not accept
|
||||
cordaClient.gameProposalAccept(
|
||||
hiIssuer, game.uuid());
|
||||
cordaClient.gameProposalAccept(hiIssuer, game.uuid());
|
||||
});
|
||||
|
||||
final GameState acceptedGameAcquierView = cordaClient.gameProposalAccept(
|
||||
|
@ -36,7 +36,7 @@ public class GameStateTests {
|
||||
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> p2GameList = cordaClient.gameStateList(hiPlayer2);
|
||||
|
@ -13,8 +13,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||
import djmil.cordacheckers.cordaclient.dao.Stone;
|
||||
import djmil.cordacheckers.cordaclient.dao.Rank;
|
||||
import djmil.cordacheckers.cordaclient.dao.Stone;
|
||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||
|
||||
@SpringBootTest
|
||||
@ -25,8 +25,8 @@ public class RankingTests {
|
||||
@Autowired
|
||||
HoldingIdentityResolver holdingIdentityResolver;
|
||||
|
||||
final String player1 = "kumar";
|
||||
final String player2 = "bobik";
|
||||
final String player1 = "Kumar";
|
||||
final String player2 = "Bobik";
|
||||
|
||||
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);
|
||||
@ -60,9 +60,11 @@ public class RankingTests {
|
||||
|
||||
assertThat(liderboard1.get(winnerName).gamesWon() +1 == liderboard2.get(winnerName).gamesWon() );
|
||||
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).gamesPlayed() +1 == liderboard2.get(losserName).gamesPlayed());
|
||||
assertThat(liderboard1.get(losserName).gamesDraw() == liderboard2.get(losserName).gamesDraw() );
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -87,9 +89,38 @@ public class RankingTests {
|
||||
|
||||
assertThat(liderboard1.get(winnerName).gamesWon() +1 == liderboard2.get(winnerName).gamesWon() );
|
||||
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).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
|
||||
|
@ -24,16 +24,28 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
command.validateGameProposalAccept(trx);
|
||||
break;
|
||||
|
||||
case GAME_BOARD_MOVE:
|
||||
command.validateGameBoardMove(trx, command.getMove());
|
||||
case MOVE:
|
||||
command.validateMove(trx, command.getMove());
|
||||
break;
|
||||
|
||||
case GAME_BOARD_SURRENDER:
|
||||
command.validateGameBoardSurrender(trx);
|
||||
case SURRENDER:
|
||||
command.validateSurrender(trx);
|
||||
break;
|
||||
|
||||
case GAME_BOARD_VICTORY:
|
||||
command.validateGameBoardVictory(trx);
|
||||
case CLAIM_VICTORY:
|
||||
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;
|
||||
|
||||
default:
|
||||
|
@ -9,12 +9,9 @@ import djmil.cordacheckers.checkers.Move;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import djmil.cordacheckers.states.GameResultState;
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
||||
import net.corda.v5.base.annotations.CordaSerializable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
public class GameCommand implements Command {
|
||||
@ -25,9 +22,12 @@ public class GameCommand implements Command {
|
||||
GAME_PROPOSAL_ACCEPT,
|
||||
GAME_PROPOSAL_REJECT,
|
||||
|
||||
GAME_BOARD_MOVE,
|
||||
GAME_BOARD_SURRENDER,
|
||||
GAME_BOARD_VICTORY;
|
||||
MOVE,
|
||||
DRAW_REQUEST,
|
||||
DRAW_ACCEPT,
|
||||
DRAW_REJECT,
|
||||
SURRENDER,
|
||||
CLAIM_VICTORY;
|
||||
}
|
||||
|
||||
private final Action action;
|
||||
@ -46,7 +46,7 @@ public class GameCommand implements Command {
|
||||
}
|
||||
|
||||
public GameCommand(Action action) {
|
||||
if (action == Action.GAME_BOARD_MOVE)
|
||||
if (action == Action.MOVE)
|
||||
throw new ActionException();
|
||||
|
||||
this.action = action;
|
||||
@ -54,7 +54,7 @@ public class GameCommand implements Command {
|
||||
}
|
||||
|
||||
public GameCommand(List<Integer> move) {
|
||||
this.action = Action.GAME_BOARD_MOVE;
|
||||
this.action = Action.MOVE;
|
||||
this.move = move;
|
||||
}
|
||||
|
||||
@ -72,46 +72,6 @@ public class GameCommand implements Command {
|
||||
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) {
|
||||
requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_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);
|
||||
|
||||
/*
|
||||
* Major command logick check
|
||||
* Major command logic check
|
||||
*/
|
||||
requireThat(gameProposal.getIssuerName().compareTo(gameProposal.getBlackPlayer()) == 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.getBlackPlayer().compareTo(inGameProposal.getBlackPlayer()) == 0, 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");
|
||||
}
|
||||
@ -157,7 +118,7 @@ public class GameCommand implements Command {
|
||||
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.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.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 logick check
|
||||
* Major command logic check
|
||||
*/
|
||||
final var newGameBoard = new GameBoardState(inGameBoard, move, outGameBoard.getMessage());
|
||||
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.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.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 logick check
|
||||
* Major command logic check
|
||||
*/
|
||||
requireThat(outGameResult.getLooserName().compareTo(outGameResult.getBlackPlayer()) == 0 ||
|
||||
outGameResult.getLooserName().compareTo(outGameResult.getWhitePlayer()) == 0,
|
||||
"Surenderer must be either Black or White player");
|
||||
|
||||
requireThat(outGameResult.getTotalMoves() == inGameBoard.getMoveNumber(), GAME_RESULT_MOVES);
|
||||
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.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.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 logick check
|
||||
* Major command logic check
|
||||
*/
|
||||
final var possibleMoves = Move.getPossibleMoves(inGameBoard.getBoard(), inGameBoard.getActiveColor());
|
||||
requireThat(possibleMoves.isEmpty(), "Victory condition violation");
|
||||
|
||||
requireThat(outGameResult.getWinnerName().compareTo(inGameBoard.getIdelPlayerName()) == 0, "Bad winner name");
|
||||
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) {
|
||||
@ -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 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_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();
|
||||
return;
|
||||
|
||||
case GAME_BOARD_MOVE:
|
||||
case MOVE:
|
||||
this.state = getSingleOutputState(utxoTrx, GameBoardState.class);
|
||||
this.issuer = getSingleInputState(utxoTrx, GameBoardState.class).getActivePlayerName();
|
||||
this.actor = ((GameBoardState)this.state).getActivePlayerName();
|
||||
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.issuer = ((GameResultState)this.state).getWinnerName();
|
||||
this.actor = null;
|
||||
return;
|
||||
|
||||
case GAME_BOARD_SURRENDER:
|
||||
case SURRENDER:
|
||||
this.state = getSingleOutputState(utxoTrx, GameResultState.class);
|
||||
this.issuer = ((GameResultState)this.state).getLooserName();
|
||||
this.actor = null;
|
||||
|
@ -20,12 +20,16 @@ public class GameResultContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
final GameCommand command = getSingleCommand(trx, GameCommand.class);
|
||||
|
||||
switch (command.getAction()) {
|
||||
case GAME_BOARD_SURRENDER:
|
||||
command.validateGameBoardSurrender(trx);
|
||||
case SURRENDER:
|
||||
command.validateSurrender(trx);
|
||||
break;
|
||||
|
||||
case GAME_BOARD_VICTORY:
|
||||
command.validateGameBoardVictory(trx);
|
||||
case CLAIM_VICTORY:
|
||||
command.validateClaimVictory(trx);
|
||||
break;
|
||||
|
||||
case DRAW_ACCEPT:
|
||||
command.validateDrawAcquire(trx);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -7,6 +7,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import djmil.cordacheckers.checkers.Move;
|
||||
import djmil.cordacheckers.checkers.Stone;
|
||||
import djmil.cordacheckers.contracts.GameBoardContract;
|
||||
@ -18,6 +20,7 @@ import net.corda.v5.ledger.utxo.BelongsToContract;
|
||||
public class GameBoardState extends GameState {
|
||||
private final Stone.Color activeColor;
|
||||
private final Integer moveNumber;
|
||||
private final Boolean drawRequest;
|
||||
private final Map<Integer, Stone> board;
|
||||
|
||||
public GameBoardState(GameProposalState gameProposalState) {
|
||||
@ -27,6 +30,7 @@ public class GameBoardState extends GameState {
|
||||
this.board = gameProposalState.getBoard();
|
||||
this.activeColor = Stone.Color.WHITE;
|
||||
this.moveNumber = 0;
|
||||
this.drawRequest = null;
|
||||
}
|
||||
|
||||
public GameBoardState(GameBoardState currentGameBoardState, List<Integer> move, String message) {
|
||||
@ -39,10 +43,21 @@ public class GameBoardState extends GameState {
|
||||
this.moveNumber = (currentGameBoardState.activeColor == this.activeColor)
|
||||
? currentGameBoardState.getMoveNumber() // current player has not finished his move jet
|
||||
: 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
|
||||
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,
|
||||
UUID gameUuid, List<PublicKey> participants) {
|
||||
super(whitePlayer, blackPlayer, gameUuid, message, participants);
|
||||
@ -50,6 +65,7 @@ public class GameBoardState extends GameState {
|
||||
this.activeColor = activeColor;
|
||||
this.moveNumber = moveNumber;
|
||||
this.board = board;
|
||||
this.drawRequest = drawRequest;
|
||||
}
|
||||
|
||||
public Stone.Color getActiveColor() {
|
||||
@ -72,6 +88,15 @@ public class GameBoardState extends GameState {
|
||||
return Collections.unmodifiableMap(board);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean getDrawRequest() {
|
||||
return drawRequest;
|
||||
}
|
||||
|
||||
public Boolean isDrawRequested() {
|
||||
return drawRequest != null && drawRequest == true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
|
@ -4,6 +4,8 @@ import java.security.PublicKey;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameResultContract;
|
||||
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
@ -12,7 +14,7 @@ import net.corda.v5.ledger.utxo.BelongsToContract;
|
||||
@BelongsToContract(GameResultContract.class)
|
||||
public class GameResultState extends GameState {
|
||||
|
||||
private final MemberX500Name winnerName;
|
||||
private final MemberX500Name winnerName; // NULL if it is a draw
|
||||
private final Integer totalMoves;
|
||||
|
||||
@ConstructorForDeserialization
|
||||
@ -29,6 +31,7 @@ public class GameResultState extends GameState {
|
||||
this.totalMoves = gameBoardState.getMoveNumber();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MemberX500Name getWinnerName() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -43,7 +43,7 @@ public class MoveFlow implements ClientStartableFlow{
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
@ -55,32 +55,33 @@ public class MoveFlow implements ClientStartableFlow{
|
||||
|
||||
final GameCommand command = new GameCommand(args.move);
|
||||
|
||||
final StateAndRef<GameState> currenrGameSar = this.flowEngine
|
||||
final StateAndRef<GameState> currentGameSar = this.flowEngine
|
||||
.subFlow(new GetFlow(args.gameUuid));
|
||||
final GameBoardState currenGameBoardState = (GameBoardState)currentGameSar.getState().getContractState();
|
||||
|
||||
final GameBoardState newGameBoard = new GameBoardState(
|
||||
(GameBoardState)currenrGameSar.getState().getContractState(),
|
||||
currenGameBoardState,
|
||||
args.move,
|
||||
args.message);
|
||||
|
||||
final UtxoSignedTransaction moveTrx = utxoLedgerService.createTransactionBuilder()
|
||||
.addCommand(command)
|
||||
.addInputState(currenrGameSar.getRef())
|
||||
.addInputState(currentGameSar.getRef())
|
||||
.addOutputState(newGameBoard)
|
||||
.addSignatories(newGameBoard.getParticipants())
|
||||
.setNotary(currenrGameSar.getState().getNotaryName())
|
||||
.setNotary(currentGameSar.getState().getNotaryName())
|
||||
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.toSignedTransaction();
|
||||
|
||||
utxoTrxId = this.flowEngine
|
||||
.subFlow(new CommitTrx(moveTrx, command.getCounterparty(currenrGameSar)));
|
||||
.subFlow(new CommitTrx(moveTrx, currenGameBoardState.getIdelPlayerName()));
|
||||
|
||||
if (amIwon(newGameBoard)) {
|
||||
log.info("Opponent has no possible moves. Claim victory!");
|
||||
utxoTrxId = this.flowEngine
|
||||
.subFlow(new GameResultCommiter(
|
||||
newGameBoard.getGameUuid(),
|
||||
new GameCommand(GameCommand.Action.GAME_BOARD_VICTORY)));
|
||||
new GameCommand(GameCommand.Action.CLAIM_VICTORY)));
|
||||
}
|
||||
|
||||
final View gameStateView = this.flowEngine
|
||||
|
@ -38,7 +38,7 @@ public class SurrenderFlow implements ClientStartableFlow{
|
||||
SecureHash utxoTrxId = null;
|
||||
|
||||
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());
|
||||
|
||||
utxoTrxId = this.flowEngine
|
||||
|
@ -39,7 +39,7 @@ public class VictoryFlow implements ClientStartableFlow {
|
||||
|
||||
try {
|
||||
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
|
||||
.subFlow(new GameResultCommiter(gameUuid, command));
|
||||
|
@ -56,7 +56,7 @@ public class AcceptFlow implements ClientStartableFlow{
|
||||
|
||||
final GameBoardState gameBoard = new GameBoardState(gameProposal); // <<-- accepted
|
||||
|
||||
final UtxoSignedTransaction acceptTrx = utxoLedgerService.createTransactionBuilder()
|
||||
final UtxoSignedTransaction gameProposalAcceptTrx = utxoLedgerService.createTransactionBuilder()
|
||||
.addCommand(command)
|
||||
.addInputState(gameProposalSar.getRef())
|
||||
.addOutputState(gameBoard)
|
||||
@ -66,7 +66,7 @@ public class AcceptFlow implements ClientStartableFlow{
|
||||
.toSignedTransaction();
|
||||
|
||||
utxoTrxId = this.flowEngine
|
||||
.subFlow(new CommitTrx(acceptTrx, command.getCounterparty(gameProposal)));
|
||||
.subFlow(new CommitTrx(gameProposalAcceptTrx, gameProposal.getIssuerName()));
|
||||
|
||||
final View gameView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(utxoTrxId));
|
||||
|
@ -62,7 +62,7 @@ public class CancelFlow implements ClientStartableFlow{
|
||||
.toSignedTransaction();
|
||||
|
||||
utxoTrxId = this.flowEngine
|
||||
.subFlow(new CommitTrx(gameProposalCancelTrx, command.getCounterparty(gameProposal)));
|
||||
.subFlow(new CommitTrx(gameProposalCancelTrx, gameProposal.getAcquierName()));
|
||||
|
||||
final View gameStateView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(utxoTrxId));
|
||||
|
@ -71,7 +71,7 @@ public class CreateFlow implements ClientStartableFlow{
|
||||
.toSignedTransaction();
|
||||
|
||||
utxoTrxId = this.flowEngine
|
||||
.subFlow(new CommitTrx(gameProposalCreateTrx, command.getCounterparty(gameProposal)));
|
||||
.subFlow(new CommitTrx(gameProposalCreateTrx, gameProposal.getAcquierName()));
|
||||
|
||||
final View gameView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(utxoTrxId));
|
||||
|
@ -70,7 +70,7 @@ public class RejectFlow implements ClientStartableFlow{
|
||||
.toSignedTransaction();
|
||||
|
||||
utxoTrxId = this.flowEngine
|
||||
.subFlow(new CommitTrx(gameProposalRejectTrx, command.getCounterparty(gameProposal)));
|
||||
.subFlow(new CommitTrx(gameProposalRejectTrx, gameProposal.getIssuerName()));
|
||||
|
||||
final View gameStateView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(utxoTrxId));
|
||||
|
@ -65,7 +65,7 @@ public class GameResultCommiter implements SubFlow<SecureHash> {
|
||||
|
||||
return this.flowEngine.subFlow(
|
||||
new CommitTrx(gameResultTrx,
|
||||
command.getCounterparty(gameResult),
|
||||
getCounterparty(gameResult),
|
||||
custodianInfo.getName()) );
|
||||
}
|
||||
|
||||
@ -84,12 +84,16 @@ public class GameResultCommiter implements SubFlow<SecureHash> {
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
|
||||
switch(this.command.getAction()) {
|
||||
case GAME_BOARD_VICTORY:
|
||||
case CLAIM_VICTORY:
|
||||
return new GameResultState(myName, // i'm a winner
|
||||
gameBoard, custodianPublicKey);
|
||||
|
||||
case GAME_BOARD_SURRENDER:
|
||||
return new GameResultState(gameBoard.getOpponentName(myName), // me surrender to
|
||||
case SURRENDER:
|
||||
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);
|
||||
|
||||
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 {
|
||||
private Integer gamesPlayed;
|
||||
private Integer gamesWon;
|
||||
private Integer gamesDraw;
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public Rank() {
|
||||
gamesPlayed = 0;
|
||||
gamesWon = 0;
|
||||
gamesDraw = 0;
|
||||
}
|
||||
|
||||
public Rank(Integer gamesPlayed, Integer gamesWon) {
|
||||
public Rank(Integer gamesPlayed, Integer gamesWon, Integer gamesDraw) {
|
||||
this.gamesPlayed = gamesPlayed;
|
||||
this.gamesWon = gamesWon;
|
||||
this.gamesDraw = gamesDraw;
|
||||
}
|
||||
|
||||
Rank gamePlayed() {
|
||||
@ -25,6 +28,13 @@ public class Rank {
|
||||
return this;
|
||||
}
|
||||
|
||||
Rank gameDraw(boolean isDraw) {
|
||||
if (isDraw)
|
||||
gamesDraw++;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getGamesPlayed() {
|
||||
return gamesPlayed;
|
||||
}
|
||||
@ -33,4 +43,8 @@ public class Rank {
|
||||
return gamesWon;
|
||||
}
|
||||
|
||||
public Integer getGamesDraw() {
|
||||
return gamesDraw;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -39,15 +39,19 @@ public class RankingFlow implements ClientStartableFlow {
|
||||
|
||||
Map<String, Rank> leaderboard = newLeaderboard();
|
||||
for (GameResultState gs : gameStateResutList) {
|
||||
final var winner = gs.getWinnerName().getCommonName();
|
||||
if (winner != null)
|
||||
leaderboard.put(winner, leaderboard.get(winner).gameWon() );
|
||||
|
||||
boolean isDraw = true;
|
||||
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();
|
||||
leaderboard.put(blackPlayer, leaderboard.get(blackPlayer).gamePlayed() );
|
||||
leaderboard.put(blackPlayer, leaderboard.get(blackPlayer).gamePlayed().gameDraw(isDraw) );
|
||||
|
||||
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)
|
||||
|
@ -15,7 +15,7 @@ public class RankingFlowResponce {
|
||||
|
||||
public RankingFlowResponce(Exception exception) {
|
||||
this.successStatus = null;
|
||||
this.failureStatus = exception.getMessage();
|
||||
this.failureStatus = exception.toString();
|
||||
}
|
||||
|
||||
public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) {
|
||||
|
@ -15,7 +15,7 @@ public class ListFlowResponce {
|
||||
|
||||
public ListFlowResponce(Exception exception) {
|
||||
this.successStatus = null;
|
||||
this.failureStatus = exception.getMessage();
|
||||
this.failureStatus = exception.toString();
|
||||
}
|
||||
|
||||
public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) {
|
||||
|
@ -21,8 +21,12 @@ public class View {
|
||||
GAME_BOARD_WAIT_FOR_OPPONENT,
|
||||
GAME_BOARD_WAIT_FOR_YOU,
|
||||
|
||||
DRAW_REQUEST_WAIT_FOR_OPPONENT,
|
||||
DRAW_REQUEST_WAIT_FOR_YOU,
|
||||
|
||||
GAME_RESULT_YOU_WON,
|
||||
GAME_RESULT_YOU_LOOSE;
|
||||
GAME_RESULT_YOU_LOOSE,
|
||||
GAME_RESULT_DRAW;
|
||||
}
|
||||
public final Status status;
|
||||
|
||||
|
@ -76,19 +76,29 @@ public class ViewBuilder implements SubFlow<View> {
|
||||
return View.Status.GAME_PROPOSAL_CANCELED;
|
||||
|
||||
case GAME_PROPOSAL_ACCEPT:
|
||||
case GAME_BOARD_MOVE:
|
||||
case DRAW_REJECT:
|
||||
case MOVE:
|
||||
if (game.isMyAction(myName))
|
||||
return View.Status.GAME_BOARD_WAIT_FOR_YOU;
|
||||
else
|
||||
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)
|
||||
return View.Status.GAME_RESULT_YOU_LOOSE;
|
||||
else
|
||||
return View.Status.GAME_RESULT_YOU_WON;
|
||||
|
||||
case GAME_BOARD_VICTORY:
|
||||
case CLAIM_VICTORY:
|
||||
if (game.issuer.compareTo(myName) == 0)
|
||||
return View.Status.GAME_RESULT_YOU_WON;
|
||||
else
|
||||
|
Loading…
Reference in New Issue
Block a user