GameState

a base state class for GameProposal, GameBoard and GameResult

also will be used as a base building block for GameBoardView
This commit is contained in:
djmil 2023-09-18 13:33:49 +02:00
parent bd6612f3e6
commit dc702f7584
10 changed files with 125 additions and 156 deletions

View File

@ -75,8 +75,8 @@ public class GameBoardCommand implements Command {
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);
requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameResultState.getWhitePlayerName()) == 0, IN_OUT_PARTICIPANTS); requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameResultState.getWinnerName()) == 0, IN_OUT_PARTICIPANTS);
requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS); requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getOpponentName()) == 0, IN_OUT_PARTICIPANTS);
} }
public static void validateMoveTrx(UtxoLedgerTransaction trx) { public static void validateMoveTrx(UtxoLedgerTransaction trx) {
@ -97,8 +97,8 @@ public class GameBoardCommand implements Command {
requireThat(trx.getOutputContractStates().size() == 1, FINAL_MOVE_OUTPUT_STATE); requireThat(trx.getOutputContractStates().size() == 1, FINAL_MOVE_OUTPUT_STATE);
final var outGameResultState = getSingleOutputState(trx, GameResultState.class); final var outGameResultState = getSingleOutputState(trx, GameResultState.class);
requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameResultState.getWhitePlayerName()) == 0, IN_OUT_PARTICIPANTS); requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameResultState.getWinnerName()) == 0, IN_OUT_PARTICIPANTS);
requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS); requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getOpponentName()) == 0, IN_OUT_PARTICIPANTS);
} }
static final String BAD_ACTIONMOVE_CONSTRUCTOR = "Bad constructor for Action.MOVE"; static final String BAD_ACTIONMOVE_CONSTRUCTOR = "Bad constructor for Action.MOVE";

View File

@ -1,24 +0,0 @@
package djmil.cordacheckers.states;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import net.corda.v5.base.annotations.CordaSerializable;
import net.corda.v5.base.types.MemberX500Name;
@CordaSerializable
public interface Game {
@NotNull
MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved;
@NotNull
UUID getGameUuid();
public static class NotInvolved extends RuntimeException {
public <T> NotInvolved(MemberX500Name myName, Class<T> clazz, UUID uuid) {
super(myName +" not involved in " +clazz.getSimpleName() +" UUID " +uuid);
}
}
}

View File

@ -11,10 +11,9 @@ import djmil.cordacheckers.states.Piece.Color;
import net.corda.v5.base.annotations.ConstructorForDeserialization; import net.corda.v5.base.annotations.ConstructorForDeserialization;
import net.corda.v5.base.types.MemberX500Name; import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.BelongsToContract; import net.corda.v5.ledger.utxo.BelongsToContract;
import net.corda.v5.ledger.utxo.ContractState;
@BelongsToContract(GameBoardContract.class) @BelongsToContract(GameBoardContract.class)
public class GameBoardState implements ContractState, Game { public class GameBoardState extends GameState {
private final MemberX500Name whitePlayerName; private final MemberX500Name whitePlayerName;
private final MemberX500Name blackPlayerName; private final MemberX500Name blackPlayerName;
@ -22,14 +21,10 @@ public class GameBoardState implements ContractState, Game {
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;
private final String message;
private final UUID gameUuid; public GameBoardState(GameProposalState gameProposalState) {
private final List<PublicKey> participants; super(gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants);
public GameBoardState(
GameProposalState gameProposalState
) {
this.whitePlayerName = gameProposalState.getWhitePlayerName(); this.whitePlayerName = gameProposalState.getWhitePlayerName();
this.blackPlayerName = gameProposalState.getBlackPlayerName(); this.blackPlayerName = gameProposalState.getBlackPlayerName();
@ -37,15 +32,12 @@ public class GameBoardState implements ContractState, Game {
this.moveColor = Piece.Color.WHITE; this.moveColor = Piece.Color.WHITE;
this.moveNumber = 0; this.moveNumber = 0;
this.board = new LinkedHashMap<Integer, Piece>(initialBoard); this.board = new LinkedHashMap<Integer, Piece>(initialBoard);
this.message = null;
this.gameUuid = gameProposalState.getGameUuid();
this.participants = gameProposalState.getParticipants();
} }
public GameBoardState( public GameBoardState(
GameBoardState oldGameBoardState, Map<Integer, Piece> newBoard, Piece.Color moveColor GameBoardState oldGameBoardState, Map<Integer, Piece> newBoard, Piece.Color moveColor) {
) { super(oldGameBoardState.gameUuid, oldGameBoardState.message, oldGameBoardState.participants);
this.whitePlayerName = oldGameBoardState.getWhitePlayerName(); this.whitePlayerName = oldGameBoardState.getWhitePlayerName();
this.blackPlayerName = oldGameBoardState.getBlackPlayerName(); this.blackPlayerName = oldGameBoardState.getBlackPlayerName();
@ -53,32 +45,30 @@ public class GameBoardState implements ContractState, Game {
this.moveColor = moveColor; this.moveColor = moveColor;
this.moveNumber = oldGameBoardState.getMoveNumber() +1; this.moveNumber = oldGameBoardState.getMoveNumber() +1;
this.board = newBoard; this.board = newBoard;
this.message = null;
this.gameUuid = oldGameBoardState.getGameUuid();
this.participants = oldGameBoardState.getParticipants();
} }
@ConstructorForDeserialization @ConstructorForDeserialization
public GameBoardState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName, public GameBoardState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName,
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);
this.whitePlayerName = whitePlayerName; this.whitePlayerName = whitePlayerName;
this.blackPlayerName = blackPlayerName; this.blackPlayerName = blackPlayerName;
this.moveColor = moveColor; this.moveColor = moveColor;
this.moveNumber = moveNumber; this.moveNumber = moveNumber;
this.board = board; this.board = board;
this.message = message;
this.gameUuid = gameUuid;
this.participants = participants;
} }
public MemberX500Name getWhitePlayerName() { public MemberX500Name getMovePlayerName() {
return whitePlayerName; switch (moveColor) {
} case WHITE:
return whitePlayerName;
public MemberX500Name getBlackPlayerName() { case BLACK:
return blackPlayerName; return blackPlayerName;
default:
throw new Piece.Color.UnknownException();
}
} }
public Piece.Color getMoveColor() { public Piece.Color getMoveColor() {
@ -93,16 +83,9 @@ public class GameBoardState implements ContractState, Game {
return board; return board;
} }
public String getMessage() { @Override
return message; public MemberX500Name getWhitePlayerName() {
} return whitePlayerName;
public UUID getGameUuid() {
return gameUuid;
}
public List<PublicKey> getParticipants() {
return participants;
} }
@Override @Override
@ -113,26 +96,10 @@ public class GameBoardState implements ContractState, Game {
if (blackPlayerName.compareTo(myName) == 0) if (blackPlayerName.compareTo(myName) == 0)
return whitePlayerName; return whitePlayerName;
throw new Game.NotInvolved(myName, GameBoardState.class, this.gameUuid); throw new GameState.NotInvolved(myName, GameBoardState.class, this.gameUuid);
}
public Piece.Color getCounterpartyColor(MemberX500Name myName) throws NotInvolved {
final MemberX500Name opponentName = getCounterpartyName(myName);
return getWhitePlayerName().compareTo(opponentName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK;
}
public MemberX500Name getMovePlayerName() {
switch (moveColor) {
case WHITE:
return whitePlayerName;
case BLACK:
return blackPlayerName;
default:
throw new Piece.Color.UnknownException();
}
} }
// 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
Map.entry( 1, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), Map.entry( 1, new Piece(Piece.Color.BLACK, Piece.Type.MAN)),

View File

@ -8,17 +8,13 @@ import djmil.cordacheckers.contracts.GameProposalContract;
import net.corda.v5.base.annotations.ConstructorForDeserialization; import net.corda.v5.base.annotations.ConstructorForDeserialization;
import net.corda.v5.base.types.MemberX500Name; import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.BelongsToContract; import net.corda.v5.ledger.utxo.BelongsToContract;
import net.corda.v5.ledger.utxo.ContractState;
@BelongsToContract(GameProposalContract.class) @BelongsToContract(GameProposalContract.class)
public class GameProposalState implements ContractState, Game { public class GameProposalState extends GameState {
private final MemberX500Name issuer; private final MemberX500Name issuer;
private final MemberX500Name acquier; private final MemberX500Name acquier;
private final Piece.Color acquierColor; private final Piece.Color acquierColor;
private final String message;
private final UUID gameUuid;
private final List<PublicKey> participants;
@ConstructorForDeserialization @ConstructorForDeserialization
public GameProposalState( public GameProposalState(
@ -29,12 +25,10 @@ public class GameProposalState implements ContractState, Game {
UUID gameUuid, UUID gameUuid,
List<PublicKey> participants List<PublicKey> participants
) { ) {
super(gameUuid, message, participants);
this.issuer = issuer; this.issuer = issuer;
this.acquier = acquier; this.acquier = acquier;
this.acquierColor = acquierColor; this.acquierColor = acquierColor;
this.message = message;
this.gameUuid = gameUuid;
this.participants = participants;
} }
public MemberX500Name getIssuer() { public MemberX500Name getIssuer() {
@ -49,26 +43,10 @@ public class GameProposalState implements ContractState, Game {
return acquierColor; return acquierColor;
} }
public String getMessage() {
return message;
}
public UUID getGameUuid() {
return gameUuid;
}
public List<PublicKey> getParticipants() {
return participants;
}
public MemberX500Name getWhitePlayerName() { public MemberX500Name getWhitePlayerName() {
return acquierColor == Piece.Color.WHITE ? getAcquier() : getIssuer(); return acquierColor == Piece.Color.WHITE ? getAcquier() : getIssuer();
} }
public MemberX500Name getBlackPlayerName() {
return acquierColor == Piece.Color.BLACK ? getAcquier() : getIssuer();
}
@Override @Override
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved { public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
if (issuer.compareTo(myName) == 0) if (issuer.compareTo(myName) == 0)
@ -77,7 +55,7 @@ public class GameProposalState implements ContractState, Game {
if (acquier.compareTo(myName) == 0) if (acquier.compareTo(myName) == 0)
return issuer; return issuer;
throw new Game.NotInvolved(myName, GameProposalState.class, this.gameUuid); throw new GameState.NotInvolved(myName, GameProposalState.class, this.gameUuid);
} }
} }

View File

@ -8,68 +8,57 @@ import djmil.cordacheckers.contracts.GameResultContract;
import net.corda.v5.base.annotations.ConstructorForDeserialization; import net.corda.v5.base.annotations.ConstructorForDeserialization;
import net.corda.v5.base.types.MemberX500Name; import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.BelongsToContract; import net.corda.v5.ledger.utxo.BelongsToContract;
import net.corda.v5.ledger.utxo.ContractState;
@BelongsToContract(GameResultContract.class) @BelongsToContract(GameResultContract.class)
public class GameResultState implements ContractState, Game { public class GameResultState extends GameState {
private final MemberX500Name whitePlayerName; private final MemberX500Name winnerName;
private final MemberX500Name blackPlayerName; private final MemberX500Name opponentName;
private final Piece.Color victoryColor; private final Piece.Color winnerColor;
private final UUID gameUuid;
private final List<PublicKey> participants;
@ConstructorForDeserialization @ConstructorForDeserialization
public GameResultState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName, Piece.Color victoryColor, public GameResultState(MemberX500Name winnerName, MemberX500Name opponentName, Piece.Color winnerColor,
UUID gameUuid, List<PublicKey> participants) { UUID gameUuid, String message, List<PublicKey> participants) {
this.whitePlayerName = whitePlayerName; super(gameUuid, message, participants);
this.blackPlayerName = blackPlayerName; this.winnerName = winnerName;
this.victoryColor = victoryColor; this.opponentName = opponentName;
this.gameUuid = gameUuid; this.winnerColor = winnerColor;
this.participants = participants;
} }
public GameResultState(GameBoardState stateGameBoard, Piece.Color victoryColor) { public GameResultState(GameBoardState gameBoardState, Piece.Color victoryColor) {
this.whitePlayerName = stateGameBoard.getWhitePlayerName(); super(gameBoardState.gameUuid, null, gameBoardState.participants);
this.blackPlayerName = stateGameBoard.getBlackPlayerName(); this.winnerName = gameBoardState.getWhitePlayerName();
this.victoryColor = victoryColor; this.opponentName = gameBoardState.getBlackPlayerName();
this.winnerColor = victoryColor;
this.gameUuid = stateGameBoard.getGameUuid();
this.participants = stateGameBoard.getParticipants();
} }
public MemberX500Name getWhitePlayerName() { public MemberX500Name getWinnerName() {
return whitePlayerName; return winnerName;
} }
public MemberX500Name getBlackPlayerName() { public MemberX500Name getOpponentName() {
return blackPlayerName; return opponentName;
} }
public Piece.Color getVictoryColor() { public Piece.Color getWinnerColor() {
return victoryColor; return winnerColor;
}
public UUID getGameUuid() {
return gameUuid;
}
@Override
public List<PublicKey> getParticipants() {
return participants;
} }
@Override @Override
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved { public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
if (whitePlayerName.compareTo(myName) == 0) if (winnerName.compareTo(myName) == 0)
return blackPlayerName; return opponentName;
if (blackPlayerName.compareTo(myName) == 0) if (opponentName.compareTo(myName) == 0)
return whitePlayerName; return winnerName;
throw new Game.NotInvolved(myName, GameResultState.class, this.gameUuid); throw new GameState.NotInvolved(myName, GameResultState.class, this.gameUuid);
}
@Override
MemberX500Name getWhitePlayerName() {
return winnerColor == Piece.Color.WHITE ? winnerName : opponentName;
} }
} }

View File

@ -0,0 +1,59 @@
package djmil.cordacheckers.states;
import java.security.PublicKey;
import java.util.List;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import net.corda.v5.base.annotations.CordaSerializable;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.ContractState;
@CordaSerializable
public abstract class GameState implements ContractState {
final UUID gameUuid;
final String message;
final List<PublicKey> participants;
@NotNull
abstract MemberX500Name getWhitePlayerName();
@NotNull
abstract MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved;
GameState(UUID gameUuid, String message, List<PublicKey> participants) {
this.gameUuid = gameUuid;
this.message = message;
this.participants = participants;
}
public UUID getGameUuid() {
return gameUuid;
}
public String getMessage() {
return message;
}
public List<PublicKey> getParticipants() {
return participants;
}
public MemberX500Name getBlackPlayerName() {
return getCounterpartyName(getWhitePlayerName());
}
public Piece.Color getCounterpartyColor(MemberX500Name myName) throws NotInvolved {
final MemberX500Name opponentName = getCounterpartyName(myName);
return getWhitePlayerName().compareTo(opponentName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK;
}
public static class NotInvolved extends RuntimeException {
public <T> NotInvolved(MemberX500Name name, Class<T> clazz, UUID uuid) {
super(name +" is not involved in " +clazz.getSimpleName() +" UUID " +uuid);
}
}
}

View File

@ -5,7 +5,7 @@ import java.util.UUID;
import djmil.cordacheckers.contracts.GameBoardCommand; import djmil.cordacheckers.contracts.GameBoardCommand;
import djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil; import djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil;
import djmil.cordacheckers.states.Game.NotInvolved; import djmil.cordacheckers.states.GameState.NotInvolved;
import djmil.cordacheckers.states.GameBoardState; import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.Piece; import djmil.cordacheckers.states.Piece;
import net.corda.v5.base.types.MemberX500Name; import net.corda.v5.base.types.MemberX500Name;

View File

@ -8,7 +8,7 @@ import org.slf4j.LoggerFactory;
import djmil.cordacheckers.FlowResult; import djmil.cordacheckers.FlowResult;
import djmil.cordacheckers.states.GameBoardState; import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.Game.NotInvolved; import djmil.cordacheckers.states.GameState.NotInvolved;
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;
import net.corda.v5.application.flows.CordaInject; import net.corda.v5.application.flows.CordaInject;

View File

@ -10,7 +10,7 @@ import djmil.cordacheckers.contracts.GameProposalCommand;
import djmil.cordacheckers.gameboard.GameBoardView; import djmil.cordacheckers.gameboard.GameBoardView;
import djmil.cordacheckers.states.GameBoardState; import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.GameProposalState; import djmil.cordacheckers.states.GameProposalState;
import djmil.cordacheckers.states.Game.NotInvolved; import djmil.cordacheckers.states.GameState.NotInvolved;
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;
import net.corda.v5.application.flows.CordaInject; import net.corda.v5.application.flows.CordaInject;

View File

@ -20,9 +20,9 @@ public class GameResultView {
} }
public GameResultView(GameResultState gameResultState) { public GameResultView(GameResultState gameResultState) {
this.whitePlayerName = gameResultState.getWhitePlayerName().getCommonName(); this.whitePlayerName = gameResultState.getWinnerName().getCommonName();
this.blackPlayerName = gameResultState.getBlackPlayerName().getCommonName(); this.blackPlayerName = gameResultState.getOpponentName().getCommonName();
this.victoryColor = gameResultState.getVictoryColor(); this.victoryColor = gameResultState.getWinnerColor();
this.id = gameResultState.getGameUuid(); this.id = gameResultState.getGameUuid();
} }