GameBoard: naive MOVE implementation

This commit is contained in:
djmil 2023-09-25 15:13:27 +02:00
parent 8971462c74
commit 1f2ff242e4
17 changed files with 331 additions and 90 deletions

View File

@ -85,20 +85,17 @@ public class CordaClient {
.getResponce(requestBody); .getResponce(requestBody);
} }
public GameState gameStateGet(HoldingIdentity holdingIdentity, UUID gameStateUuid) { public GameState gameStateGet(HoldingIdentity holdingIdentity, UUID gameUuid) {
final RequestBody requestBody = new RequestBody( final RequestBody requestBody = new RequestBody(
"gs.get-" + UUID.randomUUID(), "gs.get-" + UUID.randomUUID(),
"djmil.cordacheckers.gamestate.GetFlow", "djmil.cordacheckers.gamestate.GetFlow",
gameStateUuid); gameUuid);
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
.getResponce(requestBody); .getResponce(requestBody);
} }
public GameState gameProposalCreate( public GameState gameProposalCreate(HoldingIdentity issuer, HoldingIdentity acquier, Piece.Color acquierColor,
HoldingIdentity issuer,
HoldingIdentity acquier,
Piece.Color acquierColor,
String message String message
) { ) {
final RequestBody requestBody = new RequestBody( final RequestBody requestBody = new RequestBody(
@ -113,53 +110,53 @@ public class CordaClient {
.getResponce(requestBody); .getResponce(requestBody);
} }
public GameState gameProposalReject(HoldingIdentity holdingIdentity, UUID gameProposalUuid) { public GameState gameProposalReject(HoldingIdentity holdingIdentity, UUID gameUuid) {
final RequestBody requestBody = new RequestBody( final RequestBody requestBody = new RequestBody(
"gp.reject-" +UUID.randomUUID(), "gp.reject-" +UUID.randomUUID(),
"djmil.cordacheckers.gameproposal.RejectFlow", "djmil.cordacheckers.gameproposal.RejectFlow",
gameProposalUuid); gameUuid);
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
.getResponce(requestBody); .getResponce(requestBody);
} }
public GameState gameProposalCancel(HoldingIdentity holdingIdentity, UUID gameProposalUuid) { public GameState gameProposalCancel(HoldingIdentity holdingIdentity, UUID gameUuid) {
final RequestBody requestBody = new RequestBody( final RequestBody requestBody = new RequestBody(
"gp.reject-" +UUID.randomUUID(), "gp.reject-" +UUID.randomUUID(),
"djmil.cordacheckers.gameproposal.CancelFlow", "djmil.cordacheckers.gameproposal.CancelFlow",
gameProposalUuid); gameUuid);
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
.getResponce(requestBody); .getResponce(requestBody);
} }
public GameState gameProposalAccept(HoldingIdentity holdingIdentity, UUID gameProposalUuid) { public GameState gameProposalAccept(HoldingIdentity holdingIdentity, UUID gameUuid) {
final RequestBody requestBody = new RequestBody( final RequestBody requestBody = new RequestBody(
"gp.accept-" +UUID.randomUUID(), "gp.accept-" +UUID.randomUUID(),
"djmil.cordacheckers.gameproposal.AcceptFlow", "djmil.cordacheckers.gameproposal.AcceptFlow",
gameProposalUuid); gameUuid);
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
.getResponce(requestBody); .getResponce(requestBody);
} }
public GameState gameBoardSurrender(HoldingIdentity holdingIdentity, UUID gameBoardUuid) { public GameState gameBoardSurrender(HoldingIdentity holdingIdentity, UUID gameUuid) {
final RequestBody requestBody = new RequestBody( final RequestBody requestBody = new RequestBody(
"gb.surrender-" +UUID.randomUUID(), "gb.surrender-" +UUID.randomUUID(),
"djmil.cordacheckers.gameboard.SurrenderFlow", "djmil.cordacheckers.gameboard.SurrenderFlow",
gameBoardUuid); gameUuid);
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
.getResponce(requestBody); .getResponce(requestBody);
} }
public GameState gameBoardMove(HoldingIdentity holdingIdentity, UUID gameBoardUuid, List<Integer> move) { public GameState gameBoardMove(HoldingIdentity holdingIdentity, UUID gameUuid, List<Integer> move,
String message) {
final RequestBody requestBody = new RequestBody( final RequestBody requestBody = new RequestBody(
"gb.move-" +UUID.randomUUID(), "gb.move-" +UUID.randomUUID(),
"djmil.cordacheckers.gameboard.CommandFlow", "djmil.cordacheckers.gameboard.MoveFlow",
new ReqGameBoardMove( new ReqGameBoardMove(
gameBoardUuid, move, gameUuid, message));
move));
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class) return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
.getResponce(requestBody); .getResponce(requestBody);

View File

@ -42,4 +42,20 @@ public class Piece {
return color +"." +type; return color +"." +type;
} }
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Piece other = (Piece) obj;
if (color != other.color)
return false;
if (type != other.type)
return false;
return true;
}
} }

View File

@ -4,8 +4,9 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
public record ReqGameBoardMove( public record ReqGameBoardMove(
UUID gameBoardUuid, List<Integer> move,
List<Integer> move UUID gameUuid,
String message
) { ) {
} }

View File

@ -3,6 +3,9 @@ package djmil.cordacheckers.cordaclient;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.ArrayList;
import java.util.Arrays;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -23,6 +26,11 @@ public class GameBoardTests {
final String whitePlayerName = "alice"; final String whitePlayerName = "alice";
final String blackPlayerName = "bob"; final String blackPlayerName = "bob";
final static Piece WHITE_MAN = new Piece(Piece.Color.WHITE, Piece.Type.MAN);
final static Piece WHITE_KING = new Piece(Piece.Color.WHITE, Piece.Type.KING);
final static Piece BLACK_MAN = new Piece(Piece.Color.BLACK, Piece.Type.MAN);
final static Piece BLACK_KING = new Piece(Piece.Color.BLACK, Piece.Type.KING);
@Test @Test
void testSurrender() { void testSurrender() {
final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName); final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
@ -60,4 +68,37 @@ public class GameBoardTests {
assertThat(winnerGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_WON); assertThat(winnerGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_WON);
} }
@Test
void testMove() {
final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
final String message = "GameBoard MOVE test";
final GameState game = cordaClient.gameProposalCreate(
hiWhite, hiBlack, Piece.Color.BLACK, message);
System.out.println("Game UUID " +game.uuid());
final var m0 = cordaClient.gameProposalAccept(hiBlack, game.uuid());
assertThatThrownBy(() -> {
cordaClient.gameBoardMove(hiBlack, game.uuid(), move(12, 16),
"Black can not move, since it is opponent's turn");
});
assertThatThrownBy(() -> {
cordaClient.gameBoardMove(hiWhite, game.uuid(), move(17, 14),
"Trying to move an empty tile");
});
final var m1 = cordaClient.gameBoardMove(hiWhite, game.uuid(), move(22, 18), null);
assertThat(m0.board().get(22)).isEqualTo(WHITE_MAN);
assertThat(m0.board().get(18)).isNull();
assertThat(m1.board().get(22)).isNull();
assertThat(m1.board().get(18)).isEqualTo(WHITE_MAN);
assertThat(m1.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
}
ArrayList<Integer> move(int from, int to) {
return new ArrayList<Integer>(Arrays.asList(from, to));
}
} }

View File

@ -9,7 +9,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import djmil.cordacheckers.cordaclient.dao.GameState; import djmil.cordacheckers.cordaclient.dao.GameState;
import djmil.cordacheckers.cordaclient.dao.GameState.Status;
import djmil.cordacheckers.cordaclient.dao.Piece; import djmil.cordacheckers.cordaclient.dao.Piece;
import djmil.cordacheckers.cordaclient.dao.Rank; import djmil.cordacheckers.cordaclient.dao.Rank;
import djmil.cordacheckers.user.HoldingIdentityResolver; import djmil.cordacheckers.user.HoldingIdentityResolver;
@ -25,6 +24,8 @@ public class RankingTests {
@Test @Test
void testGlobalRanking() { void testGlobalRanking() {
final var hiCustodian = holdingIdentityResolver.getCustodian(); final var hiCustodian = holdingIdentityResolver.getCustodian();
assertThat(hiCustodian).isNotNull();
final List<Rank> liderboard1 = cordaClient.fetchRanking(hiCustodian); final List<Rank> liderboard1 = cordaClient.fetchRanking(hiCustodian);
final var hiWinner = holdingIdentityResolver.getByUsername("Charlie"); final var hiWinner = holdingIdentityResolver.getByUsername("Charlie");

View File

@ -3,13 +3,35 @@ package djmil.cordacheckers.contracts;
import static djmil.cordacheckers.contracts.GameCommand.requireThat; import static djmil.cordacheckers.contracts.GameCommand.requireThat;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand; import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import djmil.cordacheckers.states.Piece;
import djmil.cordacheckers.states.Piece.Color;
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract { public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
public static class MoveResult {
final public Map<Integer, Piece> board;
final public Piece.Color moveColor;
public MoveResult(Map<Integer, Piece> board, Color moveColor) {
this.board = board;
this.moveColor = moveColor;
}
public static class Exception extends RuntimeException {
public Exception(String message) {
super(message);
}
}
}
private final static Logger log = LoggerFactory.getLogger(GameBoardContract.class); private final static Logger log = LoggerFactory.getLogger(GameBoardContract.class);
@Override @Override
@ -19,13 +41,13 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND); requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND);
final GameCommand command = getSingleCommand(trx, GameCommand.class); final GameCommand command = getSingleCommand(trx, GameCommand.class);
switch (command.action) { switch (command.getAction()) {
case GAME_PROPOSAL_ACCEPT: case GAME_PROPOSAL_ACCEPT:
command.validateGameProposalAccept(trx); command.validateGameProposalAccept(trx);
break; break;
case GAME_BOARD_MOVE: case GAME_BOARD_MOVE:
command.validateGameBoardMove(trx); command.validateGameBoardMove(trx, command.getMove());
break; break;
case GAME_BOARD_SURRENDER: case GAME_BOARD_SURRENDER:
@ -37,4 +59,60 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
} }
} }
public static MoveResult applyMove(List<Integer> move, Map<Integer, Piece> board, Piece.Color moveColor) {
final int mFrom = move.get(0);
final int mTo = move.get(1);
final Piece piece = board.get(mFrom);
if (piece == null)
throw new MoveResult.Exception("An empty starting tile");
if (piece.getColor() != moveColor)
throw new MoveResult.Exception("Can not move opponent's piece");
if (board.get(mTo) != null)
throw new MoveResult.Exception("An occupied finishing tile");
final Map<Integer, Piece> newBoard = new LinkedHashMap<Integer, Piece>(board);
newBoard.remove(mFrom);
newBoard.put(mTo, piece);
return new GameBoardContract.MoveResult(newBoard, moveColor.opposite());
}
final static Piece WHITE_MAN = new Piece(Piece.Color.WHITE, Piece.Type.MAN);
final static Piece WHITE_KING = new Piece(Piece.Color.WHITE, Piece.Type.KING);
final static Piece BLACK_MAN = new Piece(Piece.Color.BLACK, Piece.Type.MAN);
final static Piece BLACK_KING = new Piece(Piece.Color.BLACK, Piece.Type.KING);
public final static Map<Integer, Piece> initialBoard = Map.ofEntries(
// Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php
Map.entry( 1, BLACK_MAN),
Map.entry( 2, BLACK_MAN),
Map.entry( 3, BLACK_MAN),
Map.entry( 4, BLACK_MAN),
Map.entry( 5, BLACK_MAN),
Map.entry( 6, BLACK_MAN),
Map.entry( 7, BLACK_MAN),
Map.entry( 8, BLACK_MAN),
Map.entry( 9, BLACK_MAN),
Map.entry(10, BLACK_MAN),
Map.entry(11, BLACK_MAN),
Map.entry(12, BLACK_MAN),
Map.entry(21, WHITE_MAN),
Map.entry(22, WHITE_MAN),
Map.entry(23, WHITE_MAN),
Map.entry(24, WHITE_MAN),
Map.entry(25, WHITE_MAN),
Map.entry(26, WHITE_MAN),
Map.entry(27, WHITE_MAN),
Map.entry(28, WHITE_MAN),
Map.entry(29, WHITE_MAN),
Map.entry(30, WHITE_MAN),
Map.entry(31, WHITE_MAN),
Map.entry(32, WHITE_MAN)
);
} }

View File

@ -31,8 +31,8 @@ public class GameCommand implements Command {
GAME_RESULT_CREATE; GAME_RESULT_CREATE;
} }
public final Action action; private final Action action;
public final List<Integer> move; // [0] from, [1] to private final List<Integer> move;
public static class ActionException extends RuntimeException { public static class ActionException extends RuntimeException {
public ActionException() { public ActionException() {
@ -156,6 +156,7 @@ public class GameCommand implements Command {
final GameBoardState outGameBoard = getSingleOutputState(trx, GameBoardState.class); final GameBoardState outGameBoard = getSingleOutputState(trx, GameBoardState.class);
requireThat(inGameProposal.getParticipants().containsAll(outGameBoard.getParticipants()), IN_OUT_PARTICIPANTS); requireThat(inGameProposal.getParticipants().containsAll(outGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
requireThat(outGameBoard.getBoard().equals(GameBoardContract.initialBoard), "Bad GameBoard initial state");
} }
public void validateGameProposalReject(UtxoLedgerTransaction trx) { public void validateGameProposalReject(UtxoLedgerTransaction trx) {
@ -197,7 +198,7 @@ public class GameCommand implements Command {
"Expected winner "+expectedWinnerName.getCommonName() +", proposed winner " +outGameResultState.getWinnerName().getCommonName()); "Expected winner "+expectedWinnerName.getCommonName() +", proposed winner " +outGameResultState.getWinnerName().getCommonName());
} }
public void validateGameBoardMove(UtxoLedgerTransaction trx) { public void validateGameBoardMove(UtxoLedgerTransaction trx, List<Integer> move) {
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);
@ -206,6 +207,9 @@ public class GameCommand implements Command {
requireThat(inGameBoardState.getWhitePlayer().compareTo(outGameBoardState.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS); requireThat(inGameBoardState.getWhitePlayer().compareTo(outGameBoardState.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
requireThat(inGameBoardState.getBlackPlayer().compareTo(outGameBoardState.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS); requireThat(inGameBoardState.getBlackPlayer().compareTo(outGameBoardState.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
final var newGameBoard = new GameBoardState(inGameBoardState, move, outGameBoardState.getMessage());
requireThat(outGameBoardState.equals(newGameBoard), "Unexpected output state");
} }
public void validateGameResultCreate(UtxoLedgerTransaction trx) { public void validateGameResultCreate(UtxoLedgerTransaction trx) {

View File

@ -19,7 +19,7 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND); requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND);
final GameCommand command = getSingleCommand(trx, GameCommand.class); final GameCommand command = getSingleCommand(trx, GameCommand.class);
switch (command.action) { switch (command.getAction()) {
case GAME_PROPOSAL_CREATE: case GAME_PROPOSAL_CREATE:
command.validateGameProposalCreate(trx); command.validateGameProposalCreate(trx);
break; break;

View File

@ -19,7 +19,7 @@ public class GameResultContract implements net.corda.v5.ledger.utxo.Contract {
requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND); requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND);
final GameCommand command = getSingleCommand(trx, GameCommand.class); final GameCommand command = getSingleCommand(trx, GameCommand.class);
switch (command.action) { switch (command.getAction()) {
case GAME_BOARD_SURRENDER: case GAME_BOARD_SURRENDER:
command.validateGameBoardSurrender(trx); command.validateGameBoardSurrender(trx);
break; break;

View File

@ -7,6 +7,7 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import djmil.cordacheckers.contracts.GameBoardContract; import djmil.cordacheckers.contracts.GameBoardContract;
import djmil.cordacheckers.contracts.GameBoardContract.MoveResult;
import djmil.cordacheckers.states.Piece.Color; 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;
@ -22,19 +23,22 @@ public class GameBoardState extends GameState {
super(gameProposalState.whitePlayer, gameProposalState.blackPlayer, super(gameProposalState.whitePlayer, gameProposalState.blackPlayer,
gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants); gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants);
// Initial GameBoard state this.board = new LinkedHashMap<Integer, Piece>(GameBoardContract.initialBoard);
this.moveColor = Piece.Color.WHITE; this.moveColor = Piece.Color.WHITE;
this.moveNumber = 0; this.moveNumber = 0;
this.board = new LinkedHashMap<Integer, Piece>(initialBoard);
} }
public GameBoardState(GameBoardState oldGameBoardState, Map<Integer, Piece> newBoard, Piece.Color moveColor) { public GameBoardState(GameBoardState currentGameBoardState, List<Integer> move, String message) {
super(oldGameBoardState.whitePlayer, oldGameBoardState.blackPlayer, super(currentGameBoardState.whitePlayer, currentGameBoardState.blackPlayer, currentGameBoardState.gameUuid,
oldGameBoardState.gameUuid, oldGameBoardState.message, oldGameBoardState.participants); message, currentGameBoardState.participants);
this.moveColor = moveColor; final MoveResult moveResult = GameBoardContract.applyMove(move, currentGameBoardState.getBoard(), currentGameBoardState.getMoveColor());
this.moveNumber = oldGameBoardState.getMoveNumber() +1; this.moveColor = moveResult.moveColor;
this.board = newBoard; this.board = moveResult.board;
this.moveNumber = (currentGameBoardState.moveColor == this.moveColor)
? currentGameBoardState.getMoveNumber() // current player has not finished his move jet
: currentGameBoardState.getMoveNumber() +1;
} }
@ConstructorForDeserialization @ConstructorForDeserialization
@ -64,33 +68,29 @@ public class GameBoardState extends GameState {
return board; return board;
} }
// TODO: move to contract @Override
public final static Map<Integer, Piece> initialBoard = Map.ofEntries( public boolean equals(Object obj) {
// Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php if (this == obj)
Map.entry( 1, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), return true;
Map.entry( 2, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), if (obj == null)
Map.entry( 3, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), return false;
Map.entry( 4, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), if (getClass() != obj.getClass())
Map.entry( 5, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), return false;
Map.entry( 6, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), GameBoardState other = (GameBoardState) obj;
Map.entry( 7, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), if (moveColor != other.moveColor)
Map.entry( 8, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), return false;
Map.entry( 9, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), if (moveNumber == null) {
Map.entry(10, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), if (other.moveNumber != null)
Map.entry(11, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), return false;
Map.entry(12, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), } else if (!moveNumber.equals(other.moveNumber))
return false;
Map.entry(21, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), if (board == null) {
Map.entry(22, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), if (other.board != null)
Map.entry(23, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), return false;
Map.entry(24, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), } else if (!board.equals(other.board))
Map.entry(25, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), return false;
Map.entry(26, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), return true;
Map.entry(27, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), }
Map.entry(28, new Piece(Piece.Color.WHITE, Piece.Type.MAN)),
Map.entry(29, new Piece(Piece.Color.WHITE, Piece.Type.MAN)),
Map.entry(30, new Piece(Piece.Color.WHITE, Piece.Type.MAN)),
Map.entry(31, new Piece(Piece.Color.WHITE, Piece.Type.MAN)),
Map.entry(32, new Piece(Piece.Color.WHITE, Piece.Type.MAN))
);
} }

View File

@ -17,21 +17,14 @@ public class Piece {
WHITE, WHITE,
BLACK; BLACK;
public static Color oppositOf(Color color) { public Color opposite() {
switch (color) { switch (this) {
case WHITE: case WHITE:
return BLACK; return BLACK;
case BLACK: case BLACK:
return WHITE; return WHITE;
default:
throw new UnknownException();
}
}
public static class UnknownException extends RuntimeException {
public UnknownException() {
super("Unknown Color value");
} }
throw new RuntimeException("Unknown Color");
} }
} }
@ -52,4 +45,20 @@ public class Piece {
return type; return type;
} }
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Piece other = (Piece) obj;
if (color != other.color)
return false;
if (type != other.type)
return false;
return true;
}
} }

View File

@ -0,0 +1,85 @@
package djmil.cordacheckers.gameboard;
import java.time.Duration;
import java.time.Instant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import djmil.cordacheckers.contracts.GameCommand;
import djmil.cordacheckers.gamestate.CommitSubFlow;
import djmil.cordacheckers.gamestate.FlowResponce;
import djmil.cordacheckers.gamestate.GetFlow;
import djmil.cordacheckers.gamestate.View;
import djmil.cordacheckers.gamestate.ViewBuilder;
import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.GameState;
import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow;
import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.flows.FlowEngine;
import net.corda.v5.application.marshalling.JsonMarshallingService;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.crypto.SecureHash;
import net.corda.v5.ledger.utxo.StateAndRef;
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
public class MoveFlow implements ClientStartableFlow{
private final static Logger log = LoggerFactory.getLogger(MoveFlow.class);
@CordaInject
public JsonMarshallingService jsonMarshallingService;
@CordaInject
public UtxoLedgerService utxoLedgerService;
@CordaInject
public FlowEngine flowEngine;
@Suspendable
@Override
public String call(ClientRequestBody requestBody) {
SecureHash utxoTrxId = null;
try {
final MoveFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, MoveFlowArgs.class);
final GameCommand command = new GameCommand(args.move);
final StateAndRef<GameState> currntGameBoardSar = this.flowEngine
.subFlow(new GetFlow(args.gameUuid));
final GameBoardState newGameBoard = new GameBoardState(
(GameBoardState)currntGameBoardSar.getState().getContractState(),
args.move,
args.message);
final UtxoSignedTransaction gameBoardMoveTrx = utxoLedgerService.createTransactionBuilder()
.addCommand(command)
.addInputState(currntGameBoardSar.getRef())
.addOutputState(newGameBoard)
.addSignatories(newGameBoard.getParticipants())
.setNotary(currntGameBoardSar.getState().getNotaryName())
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.toSignedTransaction();
utxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameBoardMoveTrx, command.getCounterparty(currntGameBoardSar)));
final View gameStateView = this.flowEngine
.subFlow(new ViewBuilder(utxoTrxId));
return new FlowResponce(gameStateView, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
catch (Exception e) {
log.warn(requestBody + " " +e.getClass() +": " +e.getMessage());
e.printStackTrace(System.out);
return new FlowResponce(e, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
}
}

View File

@ -0,0 +1,17 @@
package djmil.cordacheckers.gameboard;
import java.util.List;
import java.util.UUID;
public class MoveFlowArgs {
public final List<Integer> move;
public final UUID gameUuid;
public final String message;
// Serialisation service requires a default constructor
public MoveFlowArgs() {
this.move = null;
this.gameUuid = null;
this.message = null;
}
}

View File

@ -21,10 +21,8 @@ import net.corda.v5.application.flows.ClientStartableFlow;
import net.corda.v5.application.flows.CordaInject; import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.flows.FlowEngine; import net.corda.v5.application.flows.FlowEngine;
import net.corda.v5.application.marshalling.JsonMarshallingService; import net.corda.v5.application.marshalling.JsonMarshallingService;
import net.corda.v5.application.membership.MemberLookup;
import net.corda.v5.base.annotations.Suspendable; import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.crypto.SecureHash; import net.corda.v5.crypto.SecureHash;
import net.corda.v5.ledger.common.NotaryLookup;
import net.corda.v5.ledger.utxo.StateAndRef; import net.corda.v5.ledger.utxo.StateAndRef;
import net.corda.v5.ledger.utxo.UtxoLedgerService; import net.corda.v5.ledger.utxo.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
@ -36,12 +34,6 @@ public class SurrenderFlow implements ClientStartableFlow{
@CordaInject @CordaInject
public JsonMarshallingService jsonMarshallingService; public JsonMarshallingService jsonMarshallingService;
@CordaInject
public MemberLookup memberLookup;
@CordaInject
public NotaryLookup notaryLookup;
@CordaInject @CordaInject
public UtxoLedgerService utxoLedgerService; public UtxoLedgerService utxoLedgerService;

View File

@ -76,7 +76,7 @@ public class CommitSubFlowResponder implements ResponderFlow {
*/ */
@Suspendable @Suspendable
GameState getActualGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) { GameState getActualGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) {
switch (command.action) { switch (command.getAction()) {
case GAME_PROPOSAL_CREATE: case GAME_PROPOSAL_CREATE:
return getSingleOutputState(gameStateTransaction, GameProposalState.class); return getSingleOutputState(gameStateTransaction, GameProposalState.class);
@ -89,7 +89,7 @@ public class CommitSubFlowResponder implements ResponderFlow {
return getSingleInputState(gameStateTransaction, GameBoardState.class); return getSingleInputState(gameStateTransaction, GameBoardState.class);
case GAME_BOARD_MOVE: case GAME_BOARD_MOVE:
return getSingleOutputState(gameStateTransaction, GameBoardState.class); return getSingleInputState(gameStateTransaction, GameBoardState.class);
case GAME_RESULT_CREATE: case GAME_RESULT_CREATE:
return getSingleOutputState(gameStateTransaction, GameResultState.class); return getSingleOutputState(gameStateTransaction, GameResultState.class);

View File

@ -17,7 +17,7 @@ public class FlowResponce {
public FlowResponce(Exception exception, SecureHash transactionId) { public FlowResponce(Exception exception, SecureHash transactionId) {
this.successStatus = null; this.successStatus = null;
this.transactionId = transactionId; this.transactionId = transactionId;
this.failureStatus = exception.getMessage(); this.failureStatus = exception.toString();
} }
public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) { public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) {

View File

@ -51,7 +51,7 @@ public class ViewBuilder implements SubFlow<View> {
final GameState state = getLatestGameStateFromTransaction(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.getAction()) {
case GAME_PROPOSAL_CREATE: case GAME_PROPOSAL_CREATE:
case GAME_PROPOSAL_CANCEL: case GAME_PROPOSAL_CANCEL:
case GAME_PROPOSAL_REJECT: case GAME_PROPOSAL_REJECT:
@ -62,7 +62,7 @@ public class ViewBuilder implements SubFlow<View> {
case GAME_PROPOSAL_ACCEPT: case GAME_PROPOSAL_ACCEPT:
case GAME_BOARD_MOVE: case GAME_BOARD_MOVE:
if (state instanceof GameBoardState) if (state instanceof GameBoardState)
return new View(viewStatus, (GameBoardState)state, command.move, myName); return new View(viewStatus, (GameBoardState)state, command.getMove(), myName);
break; break;
case GAME_BOARD_SURRENDER: case GAME_BOARD_SURRENDER:
@ -85,7 +85,7 @@ public class ViewBuilder implements SubFlow<View> {
*/ */
@Suspendable @Suspendable
GameState getLatestGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) { GameState getLatestGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) {
switch (command.action) { switch (command.getAction()) {
case GAME_PROPOSAL_CREATE: case GAME_PROPOSAL_CREATE:
return getSingleOutputState(gameStateTransaction, GameProposalState.class); return getSingleOutputState(gameStateTransaction, GameProposalState.class);
@ -109,7 +109,7 @@ public class ViewBuilder implements SubFlow<View> {
View.Status action2status(GameCommand command, GameState state, MemberX500Name myName) { View.Status action2status(GameCommand command, GameState state, MemberX500Name myName) {
final boolean myAction = command.getInitiator(state).compareTo(myName) == 0; final boolean myAction = command.getInitiator(state).compareTo(myName) == 0;
switch (command.action) { switch (command.getAction()) {
case GAME_PROPOSAL_CREATE: case GAME_PROPOSAL_CREATE:
if (myAction) if (myAction)
return View.Status.GAME_PROPOSAL_WAIT_FOR_OPPONENT; return View.Status.GAME_PROPOSAL_WAIT_FOR_OPPONENT;