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);
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);
requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameResultState.getWinnerName()) == 0, IN_OUT_PARTICIPANTS);
requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getOpponentName()) == 0, IN_OUT_PARTICIPANTS);
}
public static void validateMoveTrx(UtxoLedgerTransaction trx) {
@ -97,8 +97,8 @@ public class GameBoardCommand implements Command {
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);
requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameResultState.getWinnerName()) == 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";

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.types.MemberX500Name;
import net.corda.v5.ledger.utxo.BelongsToContract;
import net.corda.v5.ledger.utxo.ContractState;
@BelongsToContract(GameBoardContract.class)
public class GameBoardState implements ContractState, Game {
public class GameBoardState extends GameState {
private final MemberX500Name whitePlayerName;
private final MemberX500Name blackPlayerName;
@ -22,14 +21,10 @@ public class GameBoardState implements ContractState, Game {
private final Piece.Color moveColor;
private final Integer moveNumber;
private final Map<Integer, Piece> board;
private final String message;
private final UUID gameUuid;
private final List<PublicKey> participants;
public GameBoardState(GameProposalState gameProposalState) {
super(gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants);
public GameBoardState(
GameProposalState gameProposalState
) {
this.whitePlayerName = gameProposalState.getWhitePlayerName();
this.blackPlayerName = gameProposalState.getBlackPlayerName();
@ -37,15 +32,12 @@ public class GameBoardState implements ContractState, Game {
this.moveColor = Piece.Color.WHITE;
this.moveNumber = 0;
this.board = new LinkedHashMap<Integer, Piece>(initialBoard);
this.message = null;
this.gameUuid = gameProposalState.getGameUuid();
this.participants = gameProposalState.getParticipants();
}
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.blackPlayerName = oldGameBoardState.getBlackPlayerName();
@ -53,32 +45,30 @@ public class GameBoardState implements ContractState, Game {
this.moveColor = moveColor;
this.moveNumber = oldGameBoardState.getMoveNumber() +1;
this.board = newBoard;
this.message = null;
this.gameUuid = oldGameBoardState.getGameUuid();
this.participants = oldGameBoardState.getParticipants();
}
@ConstructorForDeserialization
public GameBoardState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName,
Color moveColor, Integer moveNumber, Map<Integer, Piece> board, String message,
UUID gameUuid, List<PublicKey> participants) {
super(gameUuid, message, participants);
this.whitePlayerName = whitePlayerName;
this.blackPlayerName = blackPlayerName;
this.moveColor = moveColor;
this.moveNumber = moveNumber;
this.board = board;
this.message = message;
this.gameUuid = gameUuid;
this.participants = participants;
}
public MemberX500Name getWhitePlayerName() {
public MemberX500Name getMovePlayerName() {
switch (moveColor) {
case WHITE:
return whitePlayerName;
}
public MemberX500Name getBlackPlayerName() {
case BLACK:
return blackPlayerName;
default:
throw new Piece.Color.UnknownException();
}
}
public Piece.Color getMoveColor() {
@ -93,16 +83,9 @@ public class GameBoardState implements ContractState, Game {
return board;
}
public String getMessage() {
return message;
}
public UUID getGameUuid() {
return gameUuid;
}
public List<PublicKey> getParticipants() {
return participants;
@Override
public MemberX500Name getWhitePlayerName() {
return whitePlayerName;
}
@Override
@ -113,26 +96,10 @@ public class GameBoardState implements ContractState, Game {
if (blackPlayerName.compareTo(myName) == 0)
return whitePlayerName;
throw new Game.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();
}
throw new GameState.NotInvolved(myName, GameBoardState.class, this.gameUuid);
}
// TODO: move to contract
public final static Map<Integer, Piece> initialBoard = Map.ofEntries(
// Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php
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.types.MemberX500Name;
import net.corda.v5.ledger.utxo.BelongsToContract;
import net.corda.v5.ledger.utxo.ContractState;
@BelongsToContract(GameProposalContract.class)
public class GameProposalState implements ContractState, Game {
public class GameProposalState extends GameState {
private final MemberX500Name issuer;
private final MemberX500Name acquier;
private final Piece.Color acquierColor;
private final String message;
private final UUID gameUuid;
private final List<PublicKey> participants;
@ConstructorForDeserialization
public GameProposalState(
@ -29,12 +25,10 @@ public class GameProposalState implements ContractState, Game {
UUID gameUuid,
List<PublicKey> participants
) {
super(gameUuid, message, participants);
this.issuer = issuer;
this.acquier = acquier;
this.acquierColor = acquierColor;
this.message = message;
this.gameUuid = gameUuid;
this.participants = participants;
}
public MemberX500Name getIssuer() {
@ -49,26 +43,10 @@ public class GameProposalState implements ContractState, Game {
return acquierColor;
}
public String getMessage() {
return message;
}
public UUID getGameUuid() {
return gameUuid;
}
public List<PublicKey> getParticipants() {
return participants;
}
public MemberX500Name getWhitePlayerName() {
return acquierColor == Piece.Color.WHITE ? getAcquier() : getIssuer();
}
public MemberX500Name getBlackPlayerName() {
return acquierColor == Piece.Color.BLACK ? getAcquier() : getIssuer();
}
@Override
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
if (issuer.compareTo(myName) == 0)
@ -77,7 +55,7 @@ public class GameProposalState implements ContractState, Game {
if (acquier.compareTo(myName) == 0)
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.types.MemberX500Name;
import net.corda.v5.ledger.utxo.BelongsToContract;
import net.corda.v5.ledger.utxo.ContractState;
@BelongsToContract(GameResultContract.class)
public class GameResultState implements ContractState, Game {
public class GameResultState extends GameState {
private final MemberX500Name whitePlayerName;
private final MemberX500Name blackPlayerName;
private final MemberX500Name winnerName;
private final MemberX500Name opponentName;
private final Piece.Color victoryColor;
private final UUID gameUuid;
private final List<PublicKey> participants;
private final Piece.Color winnerColor;
@ConstructorForDeserialization
public GameResultState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName, Piece.Color victoryColor,
UUID gameUuid, List<PublicKey> participants) {
this.whitePlayerName = whitePlayerName;
this.blackPlayerName = blackPlayerName;
this.victoryColor = victoryColor;
this.gameUuid = gameUuid;
this.participants = participants;
public GameResultState(MemberX500Name winnerName, MemberX500Name opponentName, Piece.Color winnerColor,
UUID gameUuid, String message, List<PublicKey> participants) {
super(gameUuid, message, participants);
this.winnerName = winnerName;
this.opponentName = opponentName;
this.winnerColor = winnerColor;
}
public GameResultState(GameBoardState stateGameBoard, Piece.Color victoryColor) {
this.whitePlayerName = stateGameBoard.getWhitePlayerName();
this.blackPlayerName = stateGameBoard.getBlackPlayerName();
this.victoryColor = victoryColor;
this.gameUuid = stateGameBoard.getGameUuid();
this.participants = stateGameBoard.getParticipants();
public GameResultState(GameBoardState gameBoardState, Piece.Color victoryColor) {
super(gameBoardState.gameUuid, null, gameBoardState.participants);
this.winnerName = gameBoardState.getWhitePlayerName();
this.opponentName = gameBoardState.getBlackPlayerName();
this.winnerColor = victoryColor;
}
public MemberX500Name getWhitePlayerName() {
return whitePlayerName;
public MemberX500Name getWinnerName() {
return winnerName;
}
public MemberX500Name getBlackPlayerName() {
return blackPlayerName;
public MemberX500Name getOpponentName() {
return opponentName;
}
public Piece.Color getVictoryColor() {
return victoryColor;
}
public UUID getGameUuid() {
return gameUuid;
}
@Override
public List<PublicKey> getParticipants() {
return participants;
public Piece.Color getWinnerColor() {
return winnerColor;
}
@Override
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
if (whitePlayerName.compareTo(myName) == 0)
return blackPlayerName;
if (winnerName.compareTo(myName) == 0)
return opponentName;
if (blackPlayerName.compareTo(myName) == 0)
return whitePlayerName;
if (opponentName.compareTo(myName) == 0)
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.UtxoLedgerTransactionUtil;
import djmil.cordacheckers.states.Game.NotInvolved;
import djmil.cordacheckers.states.GameState.NotInvolved;
import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.Piece;
import net.corda.v5.base.types.MemberX500Name;

View File

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

View File

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