GameStates refactoring

This commit is contained in:
djmil 2023-09-21 22:59:21 +02:00
parent 959ea0051d
commit 9004e8a408
16 changed files with 215 additions and 287 deletions

View File

@ -21,15 +21,15 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
switch (command.action) { switch (command.action) {
case GAME_PROPOSAL_ACCEPT: case GAME_PROPOSAL_ACCEPT:
GameCommand.validateGameProposalAccept(trx); command.validateGameProposalAccept(trx);
break; break;
case GAME_BOARD_MOVE: case GAME_BOARD_MOVE:
GameCommand.validateGameBoardMove(trx); command.validateGameBoardMove(trx);
break; break;
case GAME_BOARD_SURRENDER: case GAME_BOARD_SURRENDER:
GameCommand.validateGameBoardSurrender(trx); command.validateGameBoardSurrender(trx);
break; break;
default: default:

View File

@ -1,5 +1,11 @@
package djmil.cordacheckers.contracts; package djmil.cordacheckers.contracts;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
import java.util.LinkedList;
import java.util.List;
import djmil.cordacheckers.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;
@ -11,13 +17,6 @@ import net.corda.v5.ledger.utxo.Command;
import net.corda.v5.ledger.utxo.StateAndRef; import net.corda.v5.ledger.utxo.StateAndRef;
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
import java.lang.reflect.Array;
import java.util.LinkedList;
import java.util.List;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
public class GameCommand implements Command { public class GameCommand implements Command {
@CordaSerializable @CordaSerializable
public static enum Action { public static enum Action {
@ -78,48 +77,49 @@ public class GameCommand implements Command {
/* /*
* Session initiator/respondent * Session initiator/respondent
*/ */
public MemberX500Name getCounterparty(StateAndRef<GameState> gameStateSar) {
public MemberX500Name getObserver(StateAndRef<GameState> gameStateSar) {
final GameState gameState = gameStateSar.getState().getContractState(); final GameState gameState = gameStateSar.getState().getContractState();
return gameState.getCounterpartyName(getActor(gameState)); return gameState.getOpponentName(getInitiator(gameState));
} }
public MemberX500Name getActor(StateAndRef<GameState> gameStateSar) { public MemberX500Name getCounterparty(GameState gameState) {
return getActor(gameStateSar.getState().getContractState()); return gameState.getOpponentName(getInitiator(gameState));
} }
public MemberX500Name getObserver(GameState gameState) { public MemberX500Name getInitiator(StateAndRef<GameState> gameStateSar) {
return gameState.getCounterpartyName(getActor(gameState)); return getInitiator(gameStateSar.getState().getContractState());
} }
public MemberX500Name getActor(GameState gameState) { public MemberX500Name getInitiator(GameState gameState) {
switch (action) { switch (action) {
case GAME_PROPOSAL_CREATE: case GAME_PROPOSAL_CREATE:
case GAME_PROPOSAL_CANCEL: case GAME_PROPOSAL_CANCEL:
if (gameState instanceof GameProposalState) if (gameState instanceof GameProposalState)
return ((GameProposalState)gameState).getIssuer(); return ((GameProposalState)gameState).getIssuerName();
break; break;
case GAME_PROPOSAL_REJECT: case GAME_PROPOSAL_REJECT:
if (gameState instanceof GameProposalState) if (gameState instanceof GameProposalState)
return ((GameProposalState)gameState).getAcquier(); return ((GameProposalState)gameState).getAcquierName();
break; break;
case GAME_PROPOSAL_ACCEPT: case GAME_PROPOSAL_ACCEPT:
if (gameState instanceof GameProposalState) // <<-- Session validation perspective if (gameState instanceof GameProposalState) // <<-- Session validation perspective
return ((GameProposalState)gameState).getAcquier(); return ((GameProposalState)gameState).getAcquierName();
if (gameState instanceof GameBoardState) // <<-- GameViewBuilder perspective if (gameState instanceof GameBoardState) // <<-- GameViewBuilder perspective
return ((GameBoardState)gameState).getMovePlayerName(); return ((GameBoardState)gameState).getActivePlayerName();
break;
case GAME_BOARD_SURRENDER:
if (gameState instanceof GameBoardState) // <<-- Session validation perspective
return ((GameBoardState)gameState).getActivePlayerName();
if (gameState instanceof GameResultState) // <<-- GameViewBuilder perspective
return ((GameResultState)gameState).getLooserName();
break; break;
case GAME_BOARD_MOVE: case GAME_BOARD_MOVE:
if (gameState instanceof GameBoardState) if (gameState instanceof GameBoardState)
return ((GameBoardState)gameState).getMovePlayerName(); return ((GameBoardState)gameState).getActivePlayerName();
break;
case GAME_BOARD_SURRENDER:
if (gameState instanceof GameResultState)
return ((GameResultState)gameState).getOpponentName();
break; break;
case GAME_RESULT_CREATE: case GAME_RESULT_CREATE:
@ -138,75 +138,79 @@ public class GameCommand implements Command {
* Transaction validation * Transaction validation
*/ */
public static 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);
GameProposalState outputState = getSingleOutputState(trx, GameProposalState.class); final GameProposalState gameProposal = getSingleOutputState(trx, GameProposalState.class);
requireThat(outputState.getAcquierColor() != null, NON_NULL_RECIPIENT_COLOR); requireThat(gameProposal.getOpponentName(gameProposal.getIssuerName()).compareTo(gameProposal.getAcquierName()) == 0,
"GameProposal.Issuer must be either Black or White player");
} }
public static void validateGameProposalAccept(UtxoLedgerTransaction trx) { public void validateGameProposalAccept(UtxoLedgerTransaction trx) {
requireThat(trx.getInputContractStates().size() == 1, ACCEPT_INPUT_STATE); requireThat(trx.getInputContractStates().size() == 1, ACCEPT_INPUT_STATE);
requireThat(trx.getOutputContractStates().size() == 1, ACCEPT_OUTPUT_STATE); requireThat(trx.getOutputContractStates().size() == 1, ACCEPT_OUTPUT_STATE);
GameProposalState inGameProposal = getSingleInputState(trx, GameProposalState.class); final GameProposalState inGameProposal = getSingleInputState(trx, GameProposalState.class);
GameBoardState outGameBoard = getSingleOutputState(trx, GameBoardState.class); final GameBoardState outGameBoard = getSingleOutputState(trx, GameBoardState.class);
requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), IN_OUT_PARTICIPANTS); requireThat(inGameProposal.getParticipants().containsAll(outGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
} }
public static void validateGameProposalReject(UtxoLedgerTransaction trx) { public void validateGameProposalReject(UtxoLedgerTransaction trx) {
requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE); requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE);
requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE); requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE);
getSingleInputState(trx, GameProposalState.class); getSingleInputState(trx, GameProposalState.class);
} }
public static void validateGameProposalCancel(UtxoLedgerTransaction trx) { public void validateGameProposalCancel(UtxoLedgerTransaction trx) {
requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE); requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE);
requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE); requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE);
getSingleInputState(trx, GameProposalState.class); getSingleInputState(trx, GameProposalState.class);
} }
public static void validateGameBoardSurrender(UtxoLedgerTransaction trx) { public void validateGameBoardSurrender(UtxoLedgerTransaction trx) {
requireThat(trx.getInputContractStates().size() == 1, SURRENDER_INPUT_STATE); requireThat(trx.getInputContractStates().size() == 1, SURRENDER_INPUT_STATE);
final var inGameBoardState = getSingleInputState(trx, GameBoardState.class); final var inGameBoardState = getSingleInputState(trx, GameBoardState.class);
requireThat(trx.getOutputContractStates().size() == 1, SURRENDER_OUTPUT_STATE); requireThat(trx.getOutputContractStates().size() == 1, SURRENDER_OUTPUT_STATE);
final var outGameResultState = getSingleOutputState(trx, GameResultState.class); final var outGameResultState = getSingleOutputState(trx, GameResultState.class);
List<MemberX500Name> inActors = new LinkedList<MemberX500Name>(List.of( List<MemberX500Name> inActors = new LinkedList<MemberX500Name>(List.of(
inGameBoardState.getWhitePlayerName(), inGameBoardState.getWhitePlayer(),
inGameBoardState.getBlackPlayerName()) inGameBoardState.getBlackPlayer())
); );
List<MemberX500Name> outActors = new LinkedList<MemberX500Name>(List.of( List<MemberX500Name> outActors = new LinkedList<MemberX500Name>(List.of(
outGameResultState.getWinnerName(), outGameResultState.getWhitePlayer(),
outGameResultState.getOpponentName()) outGameResultState.getBlackPlayer())
); );
requireThat(inActors.containsAll(outActors) && outActors.containsAll(inActors), IN_OUT_PARTICIPANTS); requireThat(inActors.containsAll(outActors) && outActors.containsAll(inActors), IN_OUT_PARTICIPANTS);
final var activePlayerName = outGameResultState.getCounterpartyName(outGameResultState.getWinnerName()); final var activePlayerName = getInitiator(inGameBoardState);
requireThat(inGameBoardState.getMovePlayerName().compareTo(activePlayerName) == 0, SURRENDER_ACTOR + "expected " +inGameBoardState.getMovePlayerName() +" actual " +activePlayerName); requireThat(inGameBoardState.getActivePlayerName().compareTo(activePlayerName) == 0, SURRENDER_ACTOR + "expected " +inGameBoardState.getActivePlayerName() +" actual " +activePlayerName);
final var expectedWinnerName = inGameBoardState.getOpponentName(activePlayerName);
requireThat(outGameResultState.getWinnerName().compareTo(expectedWinnerName) == 0,
"Expected winner "+expectedWinnerName.getCommonName() +", proposed winner " +outGameResultState.getWinnerName().getCommonName());
} }
public static void validateGameBoardMove(UtxoLedgerTransaction trx) { public void validateGameBoardMove(UtxoLedgerTransaction trx) {
requireThat(trx.getInputContractStates().size() == 1, MOVE_INPUT_STATE); requireThat(trx.getInputContractStates().size() == 1, MOVE_INPUT_STATE);
final var inGameBoardState = getSingleInputState(trx, GameBoardState.class); final var inGameBoardState = getSingleInputState(trx, GameBoardState.class);
requireThat(trx.getOutputContractStates().size() == 1, MOVE_OUTPUT_STATE); requireThat(trx.getOutputContractStates().size() == 1, MOVE_OUTPUT_STATE);
final var outGameBoardState = getSingleOutputState(trx, GameBoardState.class); final var outGameBoardState = getSingleOutputState(trx, GameBoardState.class);
requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameBoardState.getWhitePlayerName()) == 0, IN_OUT_PARTICIPANTS); requireThat(inGameBoardState.getWhitePlayer().compareTo(outGameBoardState.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameBoardState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS); requireThat(inGameBoardState.getBlackPlayer().compareTo(outGameBoardState.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
} }
public static void validateGameResultCreate(UtxoLedgerTransaction trx) { public void validateGameResultCreate(UtxoLedgerTransaction trx) {
throw new RuntimeException("Unimplemented"); throw new RuntimeException("Unimplemented");
} }
@ -221,7 +225,6 @@ public class GameCommand implements Command {
static final String CREATE_INPUT_STATE = "Create command should have no input states"; static final String CREATE_INPUT_STATE = "Create command should have no input states";
static final String CREATE_OUTPUT_STATE = "Create command should output exactly one GameProposal state"; static final String CREATE_OUTPUT_STATE = "Create command should output exactly one GameProposal state";
static final String NON_NULL_RECIPIENT_COLOR = "GameProposal.recipientColor field can not be null";
static final String REJECT_INPUT_STATE = "Reject command should have exactly one GameProposal input state"; static final String REJECT_INPUT_STATE = "Reject command should have exactly one GameProposal input state";
static final String REJECT_OUTPUT_STATE = "Reject command should have no output states"; static final String REJECT_OUTPUT_STATE = "Reject command should have no output states";

View File

@ -21,19 +21,19 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
switch (command.action) { switch (command.action) {
case GAME_PROPOSAL_CREATE: case GAME_PROPOSAL_CREATE:
GameCommand.validateGameProposalCreate(trx); command.validateGameProposalCreate(trx);
break; break;
case GAME_PROPOSAL_ACCEPT: case GAME_PROPOSAL_ACCEPT:
GameCommand.validateGameProposalAccept(trx); command.validateGameProposalAccept(trx);
break; break;
case GAME_PROPOSAL_REJECT: case GAME_PROPOSAL_REJECT:
GameCommand.validateGameProposalReject(trx); command.validateGameProposalReject(trx);
break; break;
case GAME_PROPOSAL_CANCEL: case GAME_PROPOSAL_CANCEL:
GameCommand.validateGameProposalCancel(trx); command.validateGameProposalCancel(trx);
break; break;
default: default:

View File

@ -21,11 +21,11 @@ public class GameResultContract implements net.corda.v5.ledger.utxo.Contract {
switch (command.action) { switch (command.action) {
case GAME_BOARD_SURRENDER: case GAME_BOARD_SURRENDER:
GameCommand.validateGameBoardSurrender(trx); command.validateGameBoardSurrender(trx);
break; break;
case GAME_RESULT_CREATE: case GAME_RESULT_CREATE:
GameCommand.validateGameResultCreate(trx); command.validateGameResultCreate(trx);
break; break;
default: default:

View File

@ -14,19 +14,13 @@ import net.corda.v5.ledger.utxo.BelongsToContract;
@BelongsToContract(GameBoardContract.class) @BelongsToContract(GameBoardContract.class)
public class GameBoardState extends GameState { public class GameBoardState extends GameState {
private final MemberX500Name whitePlayerName;
private final MemberX500Name blackPlayerName;
private final Piece.Color moveColor; private final Piece.Color moveColor;
private final Integer moveNumber; private final Integer moveNumber;
private final Map<Integer, Piece> board; private final Map<Integer, Piece> board;
public GameBoardState(GameProposalState gameProposalState) { public GameBoardState(GameProposalState gameProposalState) {
super(gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants); super(gameProposalState.whitePlayer, gameProposalState.blackPlayer,
gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants);
this.whitePlayerName = gameProposalState.getWhitePlayerName();
this.blackPlayerName = gameProposalState.getBlackPlayerName();
// Initial GameBoard state // Initial GameBoard state
this.moveColor = Piece.Color.WHITE; this.moveColor = Piece.Color.WHITE;
@ -34,43 +28,26 @@ public class GameBoardState extends GameState {
this.board = new LinkedHashMap<Integer, Piece>(initialBoard); this.board = new LinkedHashMap<Integer, Piece>(initialBoard);
} }
public GameBoardState( public GameBoardState(GameBoardState oldGameBoardState, Map<Integer, Piece> newBoard, Piece.Color moveColor) {
GameBoardState oldGameBoardState, Map<Integer, Piece> newBoard, Piece.Color moveColor) { super(oldGameBoardState.whitePlayer, oldGameBoardState.blackPlayer,
super(oldGameBoardState.gameUuid, oldGameBoardState.message, oldGameBoardState.participants); oldGameBoardState.gameUuid, oldGameBoardState.message, oldGameBoardState.participants);
this.whitePlayerName = oldGameBoardState.getWhitePlayerName();
this.blackPlayerName = oldGameBoardState.getBlackPlayerName();
// Initial GameBoard state
this.moveColor = moveColor; this.moveColor = moveColor;
this.moveNumber = oldGameBoardState.getMoveNumber() +1; this.moveNumber = oldGameBoardState.getMoveNumber() +1;
this.board = newBoard; this.board = newBoard;
} }
@ConstructorForDeserialization @ConstructorForDeserialization
public GameBoardState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName, public GameBoardState(MemberX500Name whitePlayer, MemberX500Name blackPlayer,
Color moveColor, Integer moveNumber, Map<Integer, Piece> board, String message, Color moveColor, Integer moveNumber, Map<Integer, Piece> board, String message,
UUID gameUuid, List<PublicKey> participants) { UUID gameUuid, List<PublicKey> participants) {
super(gameUuid, message, participants); super(whitePlayer, blackPlayer, gameUuid, message, participants);
this.whitePlayerName = whitePlayerName;
this.blackPlayerName = blackPlayerName;
this.moveColor = moveColor; this.moveColor = moveColor;
this.moveNumber = moveNumber; this.moveNumber = moveNumber;
this.board = board; this.board = board;
} }
public MemberX500Name getMovePlayerName() {
switch (moveColor) {
case WHITE:
return whitePlayerName;
case BLACK:
return blackPlayerName;
default:
throw new Piece.Color.UnknownException();
}
}
public Piece.Color getMoveColor() { public Piece.Color getMoveColor() {
return moveColor; return moveColor;
} }
@ -79,26 +56,14 @@ public class GameBoardState extends GameState {
return moveNumber; return moveNumber;
} }
public MemberX500Name getActivePlayerName() {
return moveColor == Piece.Color.WHITE ? whitePlayer : blackPlayer;
}
public Map<Integer, Piece> getBoard() { public Map<Integer, Piece> getBoard() {
return board; return board;
} }
@Override
public MemberX500Name getWhitePlayerName() {
return whitePlayerName;
}
@Override
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
if (whitePlayerName.compareTo(myName) == 0)
return blackPlayerName;
if (blackPlayerName.compareTo(myName) == 0)
return whitePlayerName;
throw new GameState.NotInvolved(myName, GameBoardState.class, this.gameUuid);
}
// TODO: move to contract // TODO: move to contract
public final static Map<Integer, Piece> initialBoard = Map.ofEntries( public final static Map<Integer, Piece> initialBoard = Map.ofEntries(
// Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php // Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php

View File

@ -12,50 +12,21 @@ import net.corda.v5.ledger.utxo.BelongsToContract;
@BelongsToContract(GameProposalContract.class) @BelongsToContract(GameProposalContract.class)
public class GameProposalState extends GameState { public class GameProposalState extends GameState {
private final MemberX500Name issuer; private final MemberX500Name issuerName;
private final MemberX500Name acquier;
private final Piece.Color acquierColor;
@ConstructorForDeserialization @ConstructorForDeserialization
public GameProposalState( public GameProposalState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, MemberX500Name issuerName,
MemberX500Name issuer, UUID gameUuid, String message, List<PublicKey> participants) {
MemberX500Name acquier, super(whitePlayer, blackPlayer, gameUuid, message, participants);
Piece.Color acquierColor, this.issuerName = issuerName;
String message,
UUID gameUuid,
List<PublicKey> participants
) {
super(gameUuid, message, participants);
this.issuer = issuer;
this.acquier = acquier;
this.acquierColor = acquierColor;
} }
public MemberX500Name getIssuer() { public MemberX500Name getIssuerName() {
return issuer; return issuerName;
} }
public MemberX500Name getAcquier() { public MemberX500Name getAcquierName() {
return acquier; return getOpponentName(issuerName);
}
public Piece.Color getAcquierColor() {
return acquierColor;
}
public MemberX500Name getWhitePlayerName() {
return acquierColor == Piece.Color.WHITE ? getAcquier() : getIssuer();
}
@Override
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
if (issuer.compareTo(myName) == 0)
return acquier;
if (acquier.compareTo(myName) == 0)
return issuer;
throw new GameState.NotInvolved(myName, GameProposalState.class, this.gameUuid);
} }
} }

View File

@ -13,52 +13,25 @@ import net.corda.v5.ledger.utxo.BelongsToContract;
public class GameResultState extends GameState { public class GameResultState extends GameState {
private final MemberX500Name winnerName; private final MemberX500Name winnerName;
private final MemberX500Name opponentName;
private final Piece.Color winnerColor;
@ConstructorForDeserialization @ConstructorForDeserialization
public GameResultState(MemberX500Name winnerName, MemberX500Name opponentName, Piece.Color winnerColor, public GameResultState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, MemberX500Name winnerName,
UUID gameUuid, String message, List<PublicKey> participants) { UUID gameUuid, String message, List<PublicKey> participants) {
super(gameUuid, message, participants); super(whitePlayer, blackPlayer, gameUuid, message, participants);
this.winnerName = winnerName; this.winnerName = winnerName;
this.opponentName = opponentName;
this.winnerColor = winnerColor;
} }
public GameResultState(GameBoardState gameBoardState, MemberX500Name winnerName) { public GameResultState(GameBoardState gameBoardState, MemberX500Name winnerName) {
super(gameBoardState.gameUuid, null, gameBoardState.participants); super(gameBoardState.whitePlayer, gameBoardState.blackPlayer, gameBoardState.gameUuid, null, gameBoardState.participants);
this.opponentName = gameBoardState.getCounterpartyName(winnerName);
this.winnerName = winnerName; this.winnerName = winnerName;
this.winnerColor = gameBoardState.getWhitePlayerName().compareTo(winnerName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK;
} }
public MemberX500Name getWinnerName() { public MemberX500Name getWinnerName() {
return winnerName; return winnerName;
} }
public MemberX500Name getOpponentName() { public MemberX500Name getLooserName() {
return opponentName; return getOpponentName(getWinnerName());
}
public Piece.Color getWinnerColor() {
return winnerColor;
}
@Override
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
if (winnerName.compareTo(myName) == 0)
return opponentName;
if (opponentName.compareTo(myName) == 0)
return winnerName;
throw new GameState.NotInvolved(myName, GameResultState.class, this.gameUuid);
}
@Override
public MemberX500Name getWhitePlayerName() {
return winnerColor == Piece.Color.WHITE ? winnerName : opponentName;
} }
} }

View File

@ -4,30 +4,35 @@ import java.security.PublicKey;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.jetbrains.annotations.NotNull;
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.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.ContractState; import net.corda.v5.ledger.utxo.ContractState;
@CordaSerializable @CordaSerializable
public abstract class GameState implements ContractState { public abstract class GameState implements ContractState {
final MemberX500Name whitePlayer;
final MemberX500Name blackPlayer;
final UUID gameUuid; final UUID gameUuid;
final String message; final String message;
final List<PublicKey> participants; final List<PublicKey> participants;
@NotNull GameState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, UUID gameUuid,
public abstract MemberX500Name getWhitePlayerName(); String message, List<PublicKey> participants) {
this.whitePlayer = whitePlayer;
@NotNull this.blackPlayer = blackPlayer;
public abstract MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved;
GameState(UUID gameUuid, String message, List<PublicKey> participants) {
this.gameUuid = gameUuid; this.gameUuid = gameUuid;
this.message = message; this.message = message;
this.participants = participants; this.participants = participants;
} }
public MemberX500Name getWhitePlayer() {
return whitePlayer;
}
public MemberX500Name getBlackPlayer() {
return blackPlayer;
}
public UUID getGameUuid() { public UUID getGameUuid() {
return gameUuid; return gameUuid;
} }
@ -36,18 +41,25 @@ public abstract class GameState implements ContractState {
return message; return message;
} }
@Override
public List<PublicKey> getParticipants() { public List<PublicKey> getParticipants() {
return participants; return participants;
} }
public MemberX500Name getBlackPlayerName() { public MemberX500Name getOpponentName(MemberX500Name playerName) throws NotInvolved {
return getCounterpartyName(getWhitePlayerName()); if (playerName.compareTo(whitePlayer) == 0)
return blackPlayer;
if (playerName.compareTo(blackPlayer) == 0)
return whitePlayer;
throw new NotInvolved(playerName, this.getClass(), gameUuid);
} }
public Piece.Color getCounterpartyColor(MemberX500Name myName) throws NotInvolved { public Piece.Color getOpponentColor(MemberX500Name playerName) throws NotInvolved {
final MemberX500Name opponentName = getCounterpartyName(myName); if (playerName.compareTo(whitePlayer) == 0)
return Piece.Color.BLACK;
return getWhitePlayerName().compareTo(opponentName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK; if (playerName.compareTo(blackPlayer) == 0)
return Piece.Color.WHITE;
throw new NotInvolved(playerName, this.getClass(), gameUuid);
} }
public static class NotInvolved extends RuntimeException { public static class NotInvolved extends RuntimeException {

View File

@ -52,7 +52,7 @@ public class SurrenderFlow implements ClientStartableFlow{
@Suspendable @Suspendable
@Override @Override
public String call(ClientRequestBody requestBody) { public String call(ClientRequestBody requestBody) {
SecureHash gameStateUtxoTrxId = null; SecureHash utxoTrxId = null;
try { try {
final GameCommand command = new GameCommand(GameCommand.Action.GAME_BOARD_SURRENDER); final GameCommand command = new GameCommand(GameCommand.Action.GAME_BOARD_SURRENDER);
@ -64,7 +64,7 @@ public class SurrenderFlow implements ClientStartableFlow{
final GameResultState outputState = prepareGameResultState(inputStateSar); final GameResultState outputState = prepareGameResultState(inputStateSar);
final UtxoSignedTransaction transactionCandidate = utxoLedgerService.createTransactionBuilder() final UtxoSignedTransaction gameBoardSurrenderTrx = utxoLedgerService.createTransactionBuilder()
.addCommand(command) .addCommand(command)
.addInputState(inputStateSar.getRef()) .addInputState(inputStateSar.getRef())
.addOutputState(outputState) .addOutputState(outputState)
@ -73,19 +73,19 @@ public class SurrenderFlow implements ClientStartableFlow{
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.toSignedTransaction(); .toSignedTransaction();
gameStateUtxoTrxId = this.flowEngine utxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(transactionCandidate, command.getObserver(outputState))); .subFlow(new CommitSubFlow(gameBoardSurrenderTrx, command.getCounterparty(inputStateSar)));
final View gameStateView = this.flowEngine final View gameStateView = this.flowEngine
.subFlow(new ViewBuilder(gameStateUtxoTrxId)); .subFlow(new ViewBuilder(utxoTrxId));
return new FlowResponce(gameStateView, gameStateUtxoTrxId) return new FlowResponce(gameStateView, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
catch (Exception e) { catch (Exception e) {
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
e.printStackTrace(System.out); e.printStackTrace(System.out);
return new FlowResponce(e, gameStateUtxoTrxId) return new FlowResponce(e, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
} }
@ -96,7 +96,7 @@ public class SurrenderFlow implements ClientStartableFlow{
final GameBoardState gameBoard = (GameBoardState) gameState; final GameBoardState gameBoard = (GameBoardState) gameState;
final MemberX500Name myName = memberLookup.myInfo().getName(); final MemberX500Name myName = memberLookup.myInfo().getName();
final MemberX500Name winnerName = gameBoard.getCounterpartyName(myName); final MemberX500Name winnerName = gameBoard.getOpponentName(myName);
return new GameResultState(gameBoard, winnerName); return new GameResultState(gameBoard, winnerName);
} }

View File

@ -43,40 +43,41 @@ public class AcceptFlow implements ClientStartableFlow{
@Suspendable @Suspendable
@Override @Override
public String call(ClientRequestBody requestBody) { public String call(ClientRequestBody requestBody) {
SecureHash gameStateUtxoTrxId = null; SecureHash utxoTrxId = null;
try { try {
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_ACCEPT); final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_ACCEPT);
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody()); final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
final StateAndRef<GameState> inputState = this.flowEngine final StateAndRef<GameState> gameProposalSar = this.flowEngine
.subFlow(new GetFlow(gameStateUuid)); .subFlow(new GetFlow(gameUuid));
final GameProposalState gameProposal = (GameProposalState)gameProposalSar.getState().getContractState();
final GameBoardState outputState = new GameBoardState((GameProposalState)inputState.getState().getContractState()); final GameBoardState gameBoard = new GameBoardState(gameProposal); // <<-- accepted
final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder() final UtxoSignedTransaction gameProposalAcceptTrx = utxoLedgerService.createTransactionBuilder()
.addCommand(command) .addCommand(command)
.addInputState(inputState.getRef()) .addInputState(gameProposalSar.getRef())
.addOutputState(outputState) .addOutputState(gameBoard)
.addSignatories(inputState.getState().getContractState().getParticipants()) .addSignatories(gameProposal.getParticipants())
.setNotary(inputState.getState().getNotaryName()) .setNotary(gameProposalSar.getState().getNotaryName())
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.toSignedTransaction(); .toSignedTransaction();
gameStateUtxoTrxId = this.flowEngine utxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState))); .subFlow(new CommitSubFlow(gameProposalAcceptTrx, command.getCounterparty(gameProposal)));
final View gameStateView = this.flowEngine final View gameView = this.flowEngine
.subFlow(new ViewBuilder(gameStateUtxoTrxId)); .subFlow(new ViewBuilder(utxoTrxId));
return new FlowResponce(gameStateView, gameStateUtxoTrxId) return new FlowResponce(gameView, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
catch (Exception e) { catch (Exception e) {
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
e.printStackTrace(System.out); e.printStackTrace(System.out);
return new FlowResponce(e, gameStateUtxoTrxId) return new FlowResponce(e, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
} }

View File

@ -13,6 +13,7 @@ import djmil.cordacheckers.gamestate.FlowResponce;
import djmil.cordacheckers.gamestate.GetFlow; import djmil.cordacheckers.gamestate.GetFlow;
import djmil.cordacheckers.gamestate.View; import djmil.cordacheckers.gamestate.View;
import djmil.cordacheckers.gamestate.ViewBuilder; import djmil.cordacheckers.gamestate.ViewBuilder;
import djmil.cordacheckers.states.GameProposalState;
import djmil.cordacheckers.states.GameState; import djmil.cordacheckers.states.GameState;
import net.corda.v5.application.flows.ClientRequestBody; import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow; import net.corda.v5.application.flows.ClientStartableFlow;
@ -41,36 +42,37 @@ public class CancelFlow implements ClientStartableFlow{
@Suspendable @Suspendable
@Override @Override
public String call(ClientRequestBody requestBody) { public String call(ClientRequestBody requestBody) {
SecureHash gameStateUtxoTrxId = null; SecureHash utxoTrxId = null;
try { try {
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_CANCEL); final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_CANCEL);
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody()); final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
final StateAndRef<GameState> inputState = this.flowEngine final StateAndRef<GameState> gameProposalSar = this.flowEngine
.subFlow(new GetFlow(gameStateUuid)); .subFlow(new GetFlow(gameUuid));
final GameProposalState gameProposal = (GameProposalState)gameProposalSar.getState().getContractState();
final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder() final UtxoSignedTransaction gameProposalCancelTrx = utxoLedgerService.createTransactionBuilder()
.addCommand(command) .addCommand(command)
.addInputState(inputState.getRef()) .addInputState(gameProposalSar.getRef())
.addSignatories(inputState.getState().getContractState().getParticipants()) .addSignatories(gameProposal.getParticipants())
.setNotary(inputState.getState().getNotaryName()) .setNotary(gameProposalSar.getState().getNotaryName())
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.toSignedTransaction(); .toSignedTransaction();
gameStateUtxoTrxId = this.flowEngine utxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState))); .subFlow(new CommitSubFlow(gameProposalCancelTrx, command.getCounterparty(gameProposal)));
final View gameStateView = this.flowEngine final View gameStateView = this.flowEngine
.subFlow(new ViewBuilder(gameStateUtxoTrxId)); .subFlow(new ViewBuilder(utxoTrxId));
return new FlowResponce(gameStateView, gameStateUtxoTrxId) return new FlowResponce(gameStateView, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
catch (Exception e) { catch (Exception e) {
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
return new FlowResponce(e, gameStateUtxoTrxId) return new FlowResponce(e, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
} }

View File

@ -53,60 +53,63 @@ public class CreateFlow implements ClientStartableFlow{
@Suspendable @Suspendable
@Override @Override
public String call(ClientRequestBody requestBody) { public String call(ClientRequestBody requestBody) {
SecureHash gameStateUtxoTrxId = null; SecureHash utxoTrxId = null;
try { try {
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_CREATE); final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_CREATE);
final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody); final GameProposalState gameProposal = buildGameProposalStateFrom(requestBody);
final UtxoSignedTransaction utxoCandidate = utxoLedgerService.createTransactionBuilder() final UtxoSignedTransaction gameProposalCreateTrx = utxoLedgerService.createTransactionBuilder()
.addCommand(command) .addCommand(command)
.addOutputState(newGameProposal) .addOutputState(gameProposal)
.addSignatories(newGameProposal.getParticipants()) .addSignatories(gameProposal.getParticipants())
.setNotary(notaryLookup.getNotaryServices().iterator().next().getName()) .setNotary(notaryLookup.getNotaryServices().iterator().next().getName())
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.toSignedTransaction(); .toSignedTransaction();
gameStateUtxoTrxId = this.flowEngine utxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(utxoCandidate, command.getObserver(newGameProposal))); .subFlow(new CommitSubFlow(gameProposalCreateTrx, command.getCounterparty(gameProposal)));
final View gameStateView = this.flowEngine final View gameView = this.flowEngine
.subFlow(new ViewBuilder(gameStateUtxoTrxId)); .subFlow(new ViewBuilder(utxoTrxId));
return new FlowResponce(gameStateView, gameStateUtxoTrxId) return new FlowResponce(gameView, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
catch (Exception e) { catch (Exception e) {
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
return new FlowResponce(e, gameStateUtxoTrxId) return new FlowResponce(e, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
} }
@Suspendable @Suspendable
private GameProposalState buildGameProposalStateFrom(ClientRequestBody requestBody) { private GameProposalState buildGameProposalStateFrom(ClientRequestBody requestBody) {
CreateFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CreateFlowArgs.class); final CreateFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CreateFlowArgs.class);
Piece.Color opponentColor = Piece.Color.valueOf(args.opponentColor); final Piece.Color opponentColor = Piece.Color.valueOf(args.opponentColor);
if (opponentColor == null) { if (opponentColor == null) {
throw new RuntimeException("Allowed values for opponentColor are: " throw new RuntimeException("Allowed values for opponentColor are: "
+ Piece.Color.WHITE.name() +", " + Piece.Color.BLACK.name() + Piece.Color.WHITE.name() +", " + Piece.Color.BLACK.name()
+ ". Actual value was: " + args.opponentColor); + ". Actual value was: " + args.opponentColor);
} }
MemberInfo myInfo = memberLookup.myInfo(); final MemberInfo myInfo = memberLookup.myInfo();
MemberInfo opponentInfo = requireNonNull( final MemberInfo opponentInfo = requireNonNull(
memberLookup.lookup(MemberX500Name.parse(args.opponentName)), memberLookup.lookup(MemberX500Name.parse(args.opponentName)),
"MemberLookup can't find opponentName specified in flow arguments: " + args.opponentName "MemberLookup can't find opponentName specified in flow arguments: " + args.opponentName
); );
final MemberInfo whitePlayerInfo = opponentColor == Piece.Color.WHITE ? opponentInfo : myInfo;
final MemberInfo blackPlayerInfo = opponentColor == Piece.Color.BLACK ? opponentInfo : myInfo;
return new GameProposalState( return new GameProposalState(
myInfo.getName(), whitePlayerInfo.getName(),
opponentInfo.getName(), blackPlayerInfo.getName(),
opponentColor, myInfo.getName(), // <<--- GameProposal issuer
args.message,
UUID.randomUUID(), UUID.randomUUID(),
args.message,
Arrays.asList(myInfo.getLedgerKeys().get(0), opponentInfo.getLedgerKeys().get(0)) Arrays.asList(myInfo.getLedgerKeys().get(0), opponentInfo.getLedgerKeys().get(0))
); );
} }

View File

@ -13,6 +13,7 @@ import djmil.cordacheckers.gamestate.FlowResponce;
import djmil.cordacheckers.gamestate.GetFlow; import djmil.cordacheckers.gamestate.GetFlow;
import djmil.cordacheckers.gamestate.View; import djmil.cordacheckers.gamestate.View;
import djmil.cordacheckers.gamestate.ViewBuilder; import djmil.cordacheckers.gamestate.ViewBuilder;
import djmil.cordacheckers.states.GameProposalState;
import djmil.cordacheckers.states.GameState; import djmil.cordacheckers.states.GameState;
import net.corda.v5.application.flows.ClientRequestBody; import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow; import net.corda.v5.application.flows.ClientStartableFlow;
@ -49,36 +50,37 @@ public class RejectFlow implements ClientStartableFlow{
@Suspendable @Suspendable
@Override @Override
public String call(ClientRequestBody requestBody) { public String call(ClientRequestBody requestBody) {
SecureHash gameStateUtxoTrxId = null; SecureHash utxoTrxId = null;
try { try {
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_REJECT); final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_REJECT);
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody()); final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
final StateAndRef<GameState> inputState = this.flowEngine final StateAndRef<GameState> gameProposalSar = this.flowEngine
.subFlow(new GetFlow(gameStateUuid)); .subFlow(new GetFlow(gameUuid));
final GameProposalState gameProposal = (GameProposalState)gameProposalSar.getState().getContractState();
final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder() final UtxoSignedTransaction gameProposalRejectTrx = utxoLedgerService.createTransactionBuilder()
.addCommand(command) .addCommand(command)
.addInputState(inputState.getRef()) .addInputState(gameProposalSar.getRef())
.addSignatories(inputState.getState().getContractState().getParticipants()) .addSignatories(gameProposal.getParticipants())
.setNotary(inputState.getState().getNotaryName()) .setNotary(gameProposalSar.getState().getNotaryName())
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.toSignedTransaction(); .toSignedTransaction();
gameStateUtxoTrxId = this.flowEngine utxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState))); .subFlow(new CommitSubFlow(gameProposalRejectTrx, command.getCounterparty(gameProposal)));
final View gameStateView = this.flowEngine final View gameStateView = this.flowEngine
.subFlow(new ViewBuilder(gameStateUtxoTrxId)); .subFlow(new ViewBuilder(utxoTrxId));
return new FlowResponce(gameStateView, gameStateUtxoTrxId) return new FlowResponce(gameStateView, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
catch (Exception e) { catch (Exception e) {
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
return new FlowResponce(e, gameStateUtxoTrxId) return new FlowResponce(e, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
} }

View File

@ -41,7 +41,7 @@ public class CommitSubFlowResponder implements ResponderFlow {
UtxoTransactionValidator txValidator = trxToValidate -> { UtxoTransactionValidator txValidator = trxToValidate -> {
try { try {
final GameCommand gameCommand = getSingleCommand(trxToValidate, GameCommand.class); final GameCommand gameCommand = getSingleCommand(trxToValidate, GameCommand.class);
final GameState gameState = getGameStateFromTransaction(trxToValidate, gameCommand); final GameState gameState = getActualGameStateFromTransaction(trxToValidate, gameCommand);
checkParticipants(session, gameCommand, gameState); checkParticipants(session, gameCommand, gameState);
@ -72,10 +72,10 @@ public class CommitSubFlowResponder implements ResponderFlow {
* @param command * @param command
* @return the most recent (from perspective of session participants validation) GameState for a given transaction * @return the most recent (from perspective of session participants validation) GameState for a given transaction
* *
* @see djmil.cordacheckers.gamestate.ViewBuilder#getGameStateFromTransaction(UtxoLedgerTransaction, GameCommand) * @see djmil.cordacheckers.gamestate.ViewBuilder#getLatestGameStateFromTransaction(UtxoLedgerTransaction, GameCommand)
*/ */
@Suspendable @Suspendable
GameState getGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) { GameState getActualGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) {
switch (command.action) { switch (command.action) {
case GAME_PROPOSAL_CREATE: case GAME_PROPOSAL_CREATE:
return getSingleOutputState(gameStateTransaction, GameProposalState.class); return getSingleOutputState(gameStateTransaction, GameProposalState.class);
@ -85,10 +85,12 @@ public class CommitSubFlowResponder implements ResponderFlow {
case GAME_PROPOSAL_CANCEL: case GAME_PROPOSAL_CANCEL:
return getSingleInputState(gameStateTransaction, GameProposalState.class); return getSingleInputState(gameStateTransaction, GameProposalState.class);
case GAME_BOARD_SURRENDER:
return getSingleInputState(gameStateTransaction, GameBoardState.class);
case GAME_BOARD_MOVE: case GAME_BOARD_MOVE:
return getSingleOutputState(gameStateTransaction, GameBoardState.class); return getSingleOutputState(gameStateTransaction, GameBoardState.class);
case GAME_BOARD_SURRENDER:
case GAME_RESULT_CREATE: case GAME_RESULT_CREATE:
return getSingleOutputState(gameStateTransaction, GameResultState.class); return getSingleOutputState(gameStateTransaction, GameResultState.class);
} }
@ -97,22 +99,17 @@ public class CommitSubFlowResponder implements ResponderFlow {
} }
@Suspendable @Suspendable
void checkParticipants(FlowSession session, GameCommand gameCommand, GameState outputGameState) throws ParticipantException { void checkParticipants(FlowSession session, GameCommand gameCommand, GameState gameState) throws ParticipantException {
final var myName = memberLookup.myInfo().getName(); final var myName = memberLookup.myInfo().getName();
final var outputStateConterparty = outputGameState.getCounterpartyName(myName); final var opponentName = gameState.getOpponentName(myName); // throws NotInvolved
final var sessionConterparty = session.getCounterparty(); final var conterpartyName = session.getCounterparty();
final var actorName = gameCommand.getInitiator(gameState);
final var actor = gameCommand.getActor(outputGameState);
final var observer = gameCommand.getObserver(outputGameState);
if (outputStateConterparty.compareTo(sessionConterparty) != 0) if (conterpartyName.compareTo(opponentName) != 0)
throw new ParticipantException("Counterparty", sessionConterparty, outputStateConterparty); throw new ParticipantException("Counterparty", conterpartyName, opponentName);
if (actor.compareTo(sessionConterparty) != 0) if (actorName.compareTo(conterpartyName) != 0)
throw new ParticipantException("Actor", sessionConterparty, actor); throw new ParticipantException("Actor", conterpartyName, actorName);
if (observer.compareTo(myName) != 0)
throw new ParticipantException("Observer", myName, observer);
} }
public static class ParticipantException extends Exception { public static class ParticipantException extends Exception {

View File

@ -10,7 +10,6 @@ import djmil.cordacheckers.states.GameResultState;
import djmil.cordacheckers.states.Piece; import djmil.cordacheckers.states.Piece;
import net.corda.v5.base.types.MemberX500Name; import net.corda.v5.base.types.MemberX500Name;
// GameBoard from the player's point of view // GameBoard from the player's point of view
public class View { public class View {
public static enum Status { public static enum Status {
@ -51,8 +50,8 @@ public class View {
public View(View.Status status, GameProposalState gameProposal, MemberX500Name myName) { public View(View.Status status, GameProposalState gameProposal, MemberX500Name myName) {
this.status = status; this.status = status;
this.opponentName = gameProposal.getCounterpartyName(myName).getCommonName(); this.opponentName = gameProposal.getOpponentName(myName).getCommonName();
this.opponentColor = gameProposal.getCounterpartyColor(myName); this.opponentColor = gameProposal.getOpponentColor(myName);
this.board = null; this.board = null;
this.moveNumber = null; this.moveNumber = null;
this.previousMove = null; this.previousMove = null;
@ -62,8 +61,8 @@ public class View {
public View(View.Status status, GameBoardState gameBoard, MemberX500Name myName) { public View(View.Status status, GameBoardState gameBoard, MemberX500Name myName) {
this.status = status; this.status = status;
this.opponentName = gameBoard.getCounterpartyName(myName).getCommonName(); this.opponentName = gameBoard.getOpponentName(myName).getCommonName();
this.opponentColor = gameBoard.getCounterpartyColor(myName); this.opponentColor = gameBoard.getOpponentColor(myName);
this.board = gameBoard.getBoard(); this.board = gameBoard.getBoard();
this.moveNumber = gameBoard.getMoveNumber(); this.moveNumber = gameBoard.getMoveNumber();
this.previousMove = null; this.previousMove = null;
@ -73,8 +72,8 @@ public class View {
public View(View.Status status, GameBoardState gameBoard, List<Integer> previousMove, MemberX500Name myName) { public View(View.Status status, GameBoardState gameBoard, List<Integer> previousMove, MemberX500Name myName) {
this.status = status; this.status = status;
this.opponentName = gameBoard.getCounterpartyName(myName).getCommonName(); this.opponentName = gameBoard.getOpponentName(myName).getCommonName();
this.opponentColor = gameBoard.getCounterpartyColor(myName); this.opponentColor = gameBoard.getOpponentColor(myName);
this.board = gameBoard.getBoard(); this.board = gameBoard.getBoard();
this.moveNumber = gameBoard.getMoveNumber(); this.moveNumber = gameBoard.getMoveNumber();
this.previousMove = previousMove; this.previousMove = previousMove;
@ -84,8 +83,8 @@ public class View {
public View(View.Status status, GameResultState gameResult, MemberX500Name myName) { public View(View.Status status, GameResultState gameResult, MemberX500Name myName) {
this.status = status; this.status = status;
this.opponentName = gameResult.getCounterpartyName(myName).getCommonName(); this.opponentName = gameResult.getOpponentName(myName).getCommonName();
this.opponentColor = gameResult.getCounterpartyColor(myName); this.opponentColor = gameResult.getOpponentColor(myName);
this.board = null; this.board = null;
this.moveNumber = null; this.moveNumber = null;
this.previousMove = null; this.previousMove = null;

View File

@ -48,7 +48,7 @@ public class ViewBuilder implements SubFlow<View> {
MemberX500Name myName = memberLookup.myInfo().getName(); MemberX500Name myName = memberLookup.myInfo().getName();
final GameCommand command = getSingleCommand(gameStateUtxo, GameCommand.class); final GameCommand command = getSingleCommand(gameStateUtxo, GameCommand.class);
final GameState state = getGameStateFromTransaction(gameStateUtxo, command); final GameState state = getLatestGameStateFromTransaction(gameStateUtxo, command);
final View.Status viewStatus = action2status(command, state, myName); final View.Status viewStatus = action2status(command, state, myName);
switch (command.action) { switch (command.action) {
@ -81,10 +81,10 @@ public class ViewBuilder implements SubFlow<View> {
* @param command * @param command
* @return the most recent (from perspective of building a GameView) GameState for a given transaction * @return the most recent (from perspective of building a GameView) GameState for a given transaction
* *
* @see djmil.cordacheckers.gamestate.CommitSubFlowResponder#getGameStateFromTransaction(UtxoLedgerTransaction, GameCommand) * @see djmil.cordacheckers.gamestate.CommitSubFlowResponder#getActualGameStateFromTransaction(UtxoLedgerTransaction, GameCommand)
*/ */
@Suspendable @Suspendable
GameState getGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) { GameState getLatestGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) {
switch (command.action) { switch (command.action) {
case GAME_PROPOSAL_CREATE: case GAME_PROPOSAL_CREATE:
return getSingleOutputState(gameStateTransaction, GameProposalState.class); return getSingleOutputState(gameStateTransaction, GameProposalState.class);
@ -107,7 +107,7 @@ public class ViewBuilder implements SubFlow<View> {
@Suspendable @Suspendable
View.Status action2status(GameCommand command, GameState state, MemberX500Name myName) { View.Status action2status(GameCommand command, GameState state, MemberX500Name myName) {
final boolean myAction = command.getActor(state).compareTo(myName) == 0; final boolean myAction = command.getInitiator(state).compareTo(myName) == 0;
switch (command.action) { switch (command.action) {
case GAME_PROPOSAL_CREATE: case GAME_PROPOSAL_CREATE: