GameID
a universal ID shared between GameProposal, GameBoard and a GameResult
This commit is contained in:
parent
a34ea39dfb
commit
e26cfe0d91
@ -35,6 +35,7 @@ import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardCommandReq;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardResGameResult;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardListRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardResGameBoard;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandAcceptRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandReq;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandRes;
|
||||
@ -227,6 +228,33 @@ public class CordaClient {
|
||||
return moveResult.successStatus();
|
||||
}
|
||||
|
||||
public GameBoard gameBoardMove(
|
||||
HoldingIdentity myHoldingIdentity,
|
||||
UUID gameBoardUuid,
|
||||
List<Integer> move
|
||||
) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gb.move-" +UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameboard.CommandFlow",
|
||||
new GameBoardCommandReq(
|
||||
gameBoardUuid,
|
||||
new GameBoardCommand(move))
|
||||
);
|
||||
|
||||
final GameBoardResGameBoard moveResult = cordaFlowExecute(
|
||||
myHoldingIdentity,
|
||||
requestBody,
|
||||
GameBoardResGameBoard.class
|
||||
);
|
||||
|
||||
if (moveResult.failureStatus() != null) {
|
||||
System.out.println("GameBoard.CommandFlow failed: " + moveResult.failureStatus());
|
||||
throw new RuntimeException("GameBoard: CommandFlow execution has failed");
|
||||
}
|
||||
|
||||
return moveResult.successStatus();
|
||||
}
|
||||
|
||||
private <T> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowResultType) {
|
||||
|
||||
try {
|
||||
|
@ -10,6 +10,7 @@ public record GameBoard(
|
||||
String opponentName,
|
||||
Piece.Color opponentColor,
|
||||
Boolean opponentMove,
|
||||
Integer moveNumber,
|
||||
Map<Integer, Piece> board,
|
||||
GameBoardCommand previousCommand,
|
||||
String message,
|
||||
|
@ -6,9 +6,7 @@ public class GameBoardCommand {
|
||||
public static enum Type {
|
||||
MOVE,
|
||||
SURRENDER,
|
||||
DRAW,
|
||||
VICTORY,
|
||||
ACCEPT;
|
||||
VICTORY;
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
|
@ -0,0 +1,7 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
||||
|
||||
public record GameBoardResGameBoard(GameBoard successStatus, String failureStatus) {
|
||||
|
||||
}
|
@ -3,6 +3,7 @@ package djmil.cordacheckers.cordaclient;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -215,6 +216,35 @@ public class CordaClientTest {
|
||||
assertThat(gameResult.victoryColor()).isEqualByComparingTo(Piece.Color.BLACK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGameBoardMove() throws JsonMappingException, JsonProcessingException, InvalidNameException {
|
||||
final var hiAlice = holdingIdentityResolver.getByUsername("alice");
|
||||
final var hiBob = holdingIdentityResolver.getByUsername("bob");
|
||||
final var bobColor = Piece.Color.WHITE;
|
||||
|
||||
final UUID gpUuid = cordaClient.gameProposalCreate(
|
||||
hiAlice, hiBob,
|
||||
bobColor, "GameBoard MOVE test"
|
||||
);
|
||||
|
||||
System.out.println("New GameProposal UUID "+ gpUuid);
|
||||
|
||||
final GameBoard gbState = cordaClient.gameProposalAccept(
|
||||
hiBob, gpUuid
|
||||
);
|
||||
|
||||
System.out.println("New GameBoard UUID "+ gbState.id());
|
||||
|
||||
assertThatThrownBy(() -> { // Alice can not move, since it is Bob's turn
|
||||
cordaClient.gameBoardMove(
|
||||
hiAlice, gbState.id(),
|
||||
Arrays.asList(1, 2));
|
||||
});
|
||||
|
||||
final GameBoard gameBoard = cordaClient.gameBoardMove(
|
||||
hiBob, gbState.id(), Arrays.asList(1, 2));
|
||||
}
|
||||
|
||||
private <T extends CordaState> T findByUuid(List<T> statesList, UUID uuid) {
|
||||
for (T state : statesList) {
|
||||
if (state.id().compareTo(uuid) == 0)
|
||||
|
@ -21,9 +21,7 @@ public class GameBoardCommand implements Command {
|
||||
public static enum Type {
|
||||
MOVE,
|
||||
SURRENDER,
|
||||
DRAW,
|
||||
VICTORY,
|
||||
ACCEPT; // aka accept opponents DRAW or VICTORY request
|
||||
FINISH;
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
@ -81,11 +79,41 @@ public class GameBoardCommand implements Command {
|
||||
requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS);
|
||||
}
|
||||
|
||||
public static void validateMoveTrx(UtxoLedgerTransaction trx) {
|
||||
requireThat(trx.getInputContractStates().size() == 1, MOVE_INPUT_STATE);
|
||||
final var inGameBoardState = getSingleInputState(trx, GameBoardState.class);
|
||||
|
||||
requireThat(trx.getOutputContractStates().size() == 1, MOVE_OUTPUT_STATE);
|
||||
final var outGameBoardState = getSingleOutputState(trx, GameBoardState.class);
|
||||
|
||||
requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameBoardState.getWhitePlayerName()) == 0, IN_OUT_PARTICIPANTS);
|
||||
requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameBoardState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS);
|
||||
}
|
||||
|
||||
public static void validateFinishTrx(UtxoLedgerTransaction trx) {
|
||||
requireThat(trx.getInputContractStates().size() == 1, FINAL_MOVE_INPUT_STATE);
|
||||
final var inGameBoardState = getSingleInputState(trx, GameBoardState.class);
|
||||
|
||||
requireThat(trx.getOutputContractStates().size() == 1, FINAL_MOVE_OUTPUT_STATE);
|
||||
final var outGameResultState = getSingleOutputState(trx, GameResultState.class);
|
||||
|
||||
requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameResultState.getWhitePlayerName()) == 0, IN_OUT_PARTICIPANTS);
|
||||
requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS);
|
||||
}
|
||||
|
||||
static final String BAD_ACTIONMOVE_CONSTRUCTOR = "Bad constructor for Action.MOVE";
|
||||
|
||||
static final String SURRENDER_INPUT_STATE = "SURRENDER command should have exactly one GameBoardState input state";
|
||||
static final String SURRENDER_OUTPUT_STATE = "SURRENDER command should have exactly one GameResultState output state";
|
||||
|
||||
static final String MOVE_INPUT_STATE = "MOVE command should have exactly one GameBoardState input state";
|
||||
static final String MOVE_OUTPUT_STATE = "MOVE command should have exactly one GameBoardState output state";
|
||||
static final String MOVE_OUT_BOARD = "MOVE command: move checkers rules violation";
|
||||
static final String MOVE_OUT_COLOR = "MOVE command: moveColor checkers rules violation";
|
||||
|
||||
static final String FINAL_MOVE_INPUT_STATE = "FINAL_MOVE command should have exactly one GameBoardState input state";
|
||||
static final String FINAL_MOVE_OUTPUT_STATE = "FINAL_MOVE command should have exactly one GameResultState output state";
|
||||
|
||||
static final String IN_OUT_PARTICIPANTS = "InputState and OutputState participants do not match";
|
||||
|
||||
public static class CommandTypeException extends RuntimeException {
|
||||
|
@ -1,17 +1,12 @@
|
||||
package djmil.cordacheckers.contracts;
|
||||
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputSar;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleReferenceSar;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
@ -40,18 +35,17 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
log.info("GameBoardContract.verify() as GameBoardCommand "+((GameBoardCommand)command).getType());
|
||||
switch (((GameBoardCommand)command).getType()) {
|
||||
case MOVE:
|
||||
GameBoardCommand.validateMoveTrx(trx);
|
||||
break;
|
||||
|
||||
case SURRENDER:
|
||||
GameBoardCommand.validateSurrenderTrx(trx);
|
||||
break;
|
||||
|
||||
case DRAW:
|
||||
break;
|
||||
case VICTORY:
|
||||
break;
|
||||
case ACCEPT:
|
||||
case FINISH:
|
||||
GameBoardCommand.validateFinishTrx(trx);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new GameBoardCommand.CommandTypeException();
|
||||
}
|
||||
|
@ -26,8 +26,9 @@ public class GameResultContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
GameBoardCommand.validateSurrenderTrx(trx);
|
||||
break;
|
||||
|
||||
// case ACCEPT:
|
||||
// break;
|
||||
case FINISH:
|
||||
GameBoardCommand.validateFinishTrx(trx);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new CordaRuntimeException(UNKNOWN_COMMAND);
|
||||
|
@ -20,6 +20,7 @@ public class GameBoardState implements ContractState, Counterparty {
|
||||
private final MemberX500Name blackPlayerName;
|
||||
|
||||
private final Piece.Color moveColor;
|
||||
private final Integer moveNumber;
|
||||
private final Map<Integer, Piece> board;
|
||||
private final String message;
|
||||
|
||||
@ -27,27 +28,45 @@ public class GameBoardState implements ContractState, Counterparty {
|
||||
private final List<PublicKey> participants;
|
||||
|
||||
public GameBoardState(
|
||||
GameProposalState gameBoard
|
||||
GameProposalState gameProposalState
|
||||
) {
|
||||
this.whitePlayerName = gameBoard.getWhitePlayerName();
|
||||
this.blackPlayerName = gameBoard.getBlackPlayerName();
|
||||
this.whitePlayerName = gameProposalState.getWhitePlayerName();
|
||||
this.blackPlayerName = gameProposalState.getBlackPlayerName();
|
||||
|
||||
// Initial GameBoard state
|
||||
this.moveColor = Piece.Color.WHITE;
|
||||
this.moveNumber = 0;
|
||||
this.board = new LinkedHashMap<Integer, Piece>(initialBoard);
|
||||
this.message = null;
|
||||
|
||||
this.id = UUID.randomUUID();
|
||||
this.participants = gameBoard.getParticipants();
|
||||
this.id = gameProposalState.getId();
|
||||
this.participants = gameProposalState.getParticipants();
|
||||
}
|
||||
|
||||
public GameBoardState(
|
||||
GameBoardState oldGameBoardState, Map<Integer, Piece> newBoard, Piece.Color moveColor
|
||||
) {
|
||||
this.whitePlayerName = oldGameBoardState.getWhitePlayerName();
|
||||
this.blackPlayerName = oldGameBoardState.getBlackPlayerName();
|
||||
|
||||
// Initial GameBoard state
|
||||
this.moveColor = moveColor;
|
||||
this.moveNumber = oldGameBoardState.getMoveNumber() +1;
|
||||
this.board = newBoard;
|
||||
this.message = null;
|
||||
|
||||
this.id = oldGameBoardState.getId();
|
||||
this.participants = oldGameBoardState.getParticipants();
|
||||
}
|
||||
|
||||
@ConstructorForDeserialization
|
||||
public GameBoardState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName,
|
||||
Color moveColor, Map<Integer, Piece> board, String message,
|
||||
Color moveColor, Integer moveNumber, Map<Integer, Piece> board, String message,
|
||||
UUID id, List<PublicKey> participants) {
|
||||
this.whitePlayerName = whitePlayerName;
|
||||
this.blackPlayerName = blackPlayerName;
|
||||
this.moveColor = moveColor;
|
||||
this.moveNumber = moveNumber;
|
||||
this.board = board;
|
||||
this.message = message;
|
||||
this.id = id;
|
||||
@ -66,6 +85,10 @@ public class GameBoardState implements ContractState, Counterparty {
|
||||
return moveColor;
|
||||
}
|
||||
|
||||
public Integer getMoveNumber() {
|
||||
return moveNumber;
|
||||
}
|
||||
|
||||
public Map<Integer, Piece> getBoard() {
|
||||
return board;
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ public class GameResultState implements ContractState, Counterparty {
|
||||
this.blackPlayerName = stateGameBoard.getBlackPlayerName();
|
||||
this.victoryColor = victoryColor;
|
||||
|
||||
this.id = UUID.randomUUID();
|
||||
this.id = stateGameBoard.getId();
|
||||
this.participants = stateGameBoard.getParticipants();
|
||||
}
|
||||
|
||||
|
@ -102,20 +102,29 @@ public class CommandFlow implements ClientStartableFlow {
|
||||
UtxoTransactionBuilder trxBuilder = utxoLedgerService.createTransactionBuilder()
|
||||
.setNotary(utxoSarGameBoard.getState().getNotaryName())
|
||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.addInputState(utxoSarGameBoard.getRef())
|
||||
.addCommand(command)
|
||||
.addInputState(utxoSarGameBoard.getRef())
|
||||
.addSignatories(stateGameBoard.getParticipants());
|
||||
|
||||
switch (command.getType()) {
|
||||
case SURRENDER:
|
||||
case ACCEPT:
|
||||
final Piece.Color winnerColor = winnerColor(command, stateGameBoard, myName);
|
||||
trxBuilder = trxBuilder
|
||||
.addOutputState( new GameResultState(stateGameBoard, winnerColor) );
|
||||
.addOutputState( new GameResultState(
|
||||
stateGameBoard,
|
||||
stateGameBoard.getCounterpartyColor(myName)) // winner color
|
||||
);
|
||||
break;
|
||||
|
||||
case MOVE:
|
||||
trxBuilder = trxBuilder
|
||||
.addOutputState( new GameBoardState(stateGameBoard, stateGameBoard.getBoard(), stateGameBoard.getMoveColor())); // TODO: advance color
|
||||
break;
|
||||
|
||||
case FINISH:
|
||||
throw new CommandTypeException("GameBoard.CommandFlow can not process externaly issued FINISH commnd");
|
||||
|
||||
default:
|
||||
throw new CommandTypeException("GameBoard.CommandFlow doTransaction()");
|
||||
throw new CommandTypeException("GameBoard.CommandFlow doTransaction(): Unknown command");
|
||||
}
|
||||
|
||||
return commit(
|
||||
@ -123,22 +132,6 @@ public class CommandFlow implements ClientStartableFlow {
|
||||
opponentName);
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
Piece.Color winnerColor(GameBoardCommand command, GameBoardState stateGameBoard, MemberX500Name myName) {
|
||||
|
||||
switch (command.getType()) {
|
||||
case SURRENDER:
|
||||
return stateGameBoard.getCounterpartyColor(myName);
|
||||
|
||||
case ACCEPT:
|
||||
//stateGameBoard.getMoveColor();
|
||||
throw new RuntimeException("Unimplemented");
|
||||
|
||||
default:
|
||||
throw new CommandTypeException("GameBoard.CommandFlow winnerColor()");
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
SecureHash commit(UtxoSignedTransaction candidateTrx, MemberX500Name counterpartyName) {
|
||||
log.info("About to commit " +candidateTrx.getId());
|
||||
@ -170,12 +163,17 @@ public class CommandFlow implements ClientStartableFlow {
|
||||
|
||||
switch (command.getType()) {
|
||||
case SURRENDER:
|
||||
case ACCEPT:
|
||||
return viewGameResult(utxoTrx);
|
||||
|
||||
case MOVE:
|
||||
case VICTORY: // request, shall be accepted by opponent
|
||||
case DRAW: // request, shall be accepted by opponent
|
||||
// 1. create initial GameBoardView
|
||||
// 2. check for winning conditions
|
||||
// - run subcommnd FINISH that will consume current gbUtxo and will produce GameResult state
|
||||
// - update GameBoardView to have FINISH command
|
||||
// 3. return GameBoardView
|
||||
return viewGameBoard(utxoTrx);
|
||||
|
||||
case FINISH:
|
||||
return viewGameBoard(utxoTrx);
|
||||
|
||||
default:
|
||||
|
@ -65,12 +65,10 @@ public class CommandResponderFlow implements ResponderFlow {
|
||||
GameBoardState getGameBoardState(UtxoLedgerTransaction utxoGameBoard, GameBoardCommand command) {
|
||||
switch (command.getType()) {
|
||||
case SURRENDER:
|
||||
case ACCEPT:
|
||||
case FINISH:
|
||||
return getSingleInputState(utxoGameBoard, GameBoardState.class);
|
||||
|
||||
case MOVE:
|
||||
case VICTORY:
|
||||
case DRAW:
|
||||
return getSingleOutputState(utxoGameBoard, GameBoardState.class);
|
||||
|
||||
default:
|
||||
|
@ -17,16 +17,26 @@ public class GameBoardView {
|
||||
public final String opponentName;
|
||||
public final Piece.Color opponentColor;
|
||||
public final Boolean opponentMove;
|
||||
public final Integer moveNumber;
|
||||
public final Map<Integer, Piece> board;
|
||||
public final GameBoardCommand previousCommand;
|
||||
public final String message;
|
||||
public final UUID id;
|
||||
|
||||
/*
|
||||
* GameStatus enum:
|
||||
* - YOUR_TURN
|
||||
* - WAIT_FOR_OPPONENT
|
||||
* - VICTORY
|
||||
* - YOU_LOOSE
|
||||
*/
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public GameBoardView() {
|
||||
this.opponentName = null;
|
||||
this.opponentColor = null;
|
||||
this.opponentMove = null;
|
||||
this.moveNumber = null;
|
||||
this.board = null;
|
||||
this.previousCommand = null;
|
||||
this.message = null;
|
||||
@ -36,6 +46,11 @@ public class GameBoardView {
|
||||
// A view from a perspective of a concrete player, on a ledger transaction that has
|
||||
// produced new GameBoardState
|
||||
public GameBoardView(UtxoLedgerTransaction utxoGameBoard, MemberX500Name myName) throws NotInvolved {
|
||||
// TODO check command type: MOVE vs FINNISH | SURRENDER
|
||||
// SingleOutPut vs SingleInput
|
||||
this.previousCommand = UtxoLedgerTransactionUtil
|
||||
.getOptionalCommand(utxoGameBoard, GameBoardCommand.class)
|
||||
.orElseGet(() -> null); // there is no previous command for GameProposal.Accept case
|
||||
|
||||
final GameBoardState stateGameBoard = UtxoLedgerTransactionUtil
|
||||
.getSingleOutputState(utxoGameBoard, GameBoardState.class);
|
||||
@ -44,13 +59,10 @@ public class GameBoardView {
|
||||
this.opponentColor = stateGameBoard.getCounterpartyColor(myName);
|
||||
|
||||
this.opponentMove = this.opponentColor == stateGameBoard.getMoveColor();
|
||||
this.moveNumber = stateGameBoard.getMoveNumber();
|
||||
this.board = stateGameBoard.getBoard();
|
||||
this.message = stateGameBoard.getMessage();
|
||||
this.id = stateGameBoard.getId();
|
||||
|
||||
this.previousCommand = UtxoLedgerTransactionUtil
|
||||
.getOptionalCommand(utxoGameBoard, GameBoardCommand.class)
|
||||
.orElseGet(() -> null); // there is no previous command for GameProposal.Accept case
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ public class CommitResponderFlow implements ResponderFlow {
|
||||
@Suspendable
|
||||
@Override
|
||||
public void call(FlowSession session) {
|
||||
log.info("GameProposal: Commit responder flow");
|
||||
try {
|
||||
UtxoTransactionValidator txValidator = ledgerTransaction -> {
|
||||
final GameProposalCommand command = ledgerTransaction.getCommands(GameProposalCommand.class).get(0);
|
||||
|
Loading…
Reference in New Issue
Block a user