Ranking: use Map instead of List

This commit is contained in:
djmil 2023-10-02 19:27:12 +02:00
parent f9f6920512
commit 7fe2898eef
9 changed files with 129 additions and 48 deletions

View File

@ -67,7 +67,7 @@ public class CordaClient {
} }
// use custodian holding identity to get ranking table of all players // use custodian holding identity to get ranking table of all players
public List<Rank> fetchRanking(HoldingIdentity holdingIdentity) { public Map<String, Rank> fetchRanking(HoldingIdentity holdingIdentity) {
final RequestBody requestBody = new RequestBody( final RequestBody requestBody = new RequestBody(
"ranking-" +UUID.randomUUID(), "ranking-" +UUID.randomUUID(),
"djmil.cordacheckers.gameresult.RankingFlow", "djmil.cordacheckers.gameresult.RankingFlow",

View File

@ -1,7 +1,6 @@
package djmil.cordacheckers.cordaclient.dao; package djmil.cordacheckers.cordaclient.dao;
public record Rank( public record Rank(
String name,
Integer gamesPlayed, Integer gamesPlayed,
Integer gamesWon Integer gamesWon
) { ) {

View File

@ -1,11 +1,11 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments; package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import java.util.List; import java.util.Map;
import djmil.cordacheckers.cordaclient.dao.Rank; import djmil.cordacheckers.cordaclient.dao.Rank;
public record RspRankList( public record RspRankList(
List<Rank> successStatus, Map<String, Rank> successStatus,
String failureStatus) implements Rsp<List<Rank>> { String failureStatus) implements Rsp<Map<String, Rank>> {
} }

View File

@ -144,7 +144,7 @@ public class GameProposalTests {
assertThat(acceptedGameAcquierView.opponentName()).isEqualToIgnoringCase(issuer); assertThat(acceptedGameAcquierView.opponentName()).isEqualToIgnoringCase(issuer);
assertThat(acceptedGameAcquierView.opponentColor()).isEqualByComparingTo(acquierColor.opposite()); assertThat(acceptedGameAcquierView.opponentColor()).isEqualByComparingTo(acquierColor.opposite());
assertThat(acceptedGameAcquierView.board()).containsAllEntriesOf(GameState.defaultGameBoard); assertThat(acceptedGameAcquierView.board()).containsAllEntriesOf(GameState.defaultGameBoard);
assertThat(acceptedGameAcquierView.previousMove()).isEmpty(); assertThat(acceptedGameAcquierView.previousMove()).isNull();
assertThat(acceptedGameAcquierView.moveNumber() == 0); assertThat(acceptedGameAcquierView.moveNumber() == 0);
assertThat(acceptedGameAcquierView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_YOU); assertThat(acceptedGameAcquierView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_YOU);
assertThat(acceptedGameAcquierView.uuid()).isEqualByComparingTo(game.uuid()); assertThat(acceptedGameAcquierView.uuid()).isEqualByComparingTo(game.uuid());
@ -160,7 +160,7 @@ public class GameProposalTests {
assertThat(acceptedGameIssuerView.opponentName()).isEqualToIgnoringCase(acquier); assertThat(acceptedGameIssuerView.opponentName()).isEqualToIgnoringCase(acquier);
assertThat(acceptedGameIssuerView.opponentColor()).isEqualByComparingTo(acquierColor); assertThat(acceptedGameIssuerView.opponentColor()).isEqualByComparingTo(acquierColor);
assertThat(acceptedGameIssuerView.board()).containsAllEntriesOf(GameState.defaultGameBoard); assertThat(acceptedGameIssuerView.board()).containsAllEntriesOf(GameState.defaultGameBoard);
assertThat(acceptedGameIssuerView.previousMove()).isEmpty(); assertThat(acceptedGameIssuerView.previousMove()).isNull();
assertThat(acceptedGameIssuerView.moveNumber() == 0); assertThat(acceptedGameIssuerView.moveNumber() == 0);
assertThat(acceptedGameIssuerView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT); assertThat(acceptedGameIssuerView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
assertThat(acceptedGameIssuerView.uuid()).isEqualByComparingTo(game.uuid()); assertThat(acceptedGameIssuerView.uuid()).isEqualByComparingTo(game.uuid());

View File

@ -1,8 +1,12 @@
package djmil.cordacheckers.cordaclient; 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.assertThatNoException;
import java.util.List; import java.util.Arrays;
import java.util.Map;
import javax.naming.InvalidNameException;
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;
@ -24,33 +28,77 @@ public class RankingTests {
final String player1 = "kumar"; final String player1 = "kumar";
final String player2 = "bobik"; final String player2 = "bobik";
final static Stone WHITE_MAN = new Stone(Stone.Color.WHITE, Stone.Type.MAN);
final static Stone WHITE_KING = new Stone(Stone.Color.WHITE, Stone.Type.KING);
final static Stone BLACK_MAN = new Stone(Stone.Color.BLACK, Stone.Type.MAN);
final static Stone BLACK_KING = new Stone(Stone.Color.BLACK, Stone.Type.KING);
final static Map<Integer, Stone> board1 = Map.ofEntries(
Map.entry(10, BLACK_KING),
Map.entry(7, WHITE_KING)
);
@Test @Test
void testGlobalRanking() { void testSurrender() throws InvalidNameException {
final var hiCustodian = holdingIdentityResolver.getCustodian(); final var hiCustodian = holdingIdentityResolver.getCustodian();
assertThat(hiCustodian).isNotNull(); assertThat(hiCustodian).isNotNull();
final List<Rank> liderboard1 = cordaClient.fetchRanking(hiCustodian); final Map<String, Rank> liderboard1 = cordaClient.fetchRanking(hiCustodian);
final var hiWinner = holdingIdentityResolver.getByUsername(player1); final var hiWinner = holdingIdentityResolver.getByUsername(player1);
final var hiLooser = holdingIdentityResolver.getByUsername(player2); final var hiLooser = holdingIdentityResolver.getByUsername(player2);
final var winnerName = hiWinner.getName();
final var losserName = hiLooser.getName();
final GameState game = cordaClient.gameProposalCreate( final GameState game = cordaClient.gameProposalCreate(hiWinner, hiLooser, Stone.Color.WHITE,
hiWinner, hiLooser, Stone.Color.WHITE, "GameBoard GLOBAL_RANKING test"); "GameBoard GLOBAL_RANKING surrender test");
cordaClient.gameProposalAccept(hiLooser, game.uuid()); cordaClient.gameProposalAccept(hiLooser, game.uuid());
cordaClient.gameBoardSurrender(hiLooser, game.uuid()); cordaClient.gameBoardSurrender(hiLooser, game.uuid());
final List<Rank> liderboard2 = cordaClient.fetchRanking(hiCustodian); final Map<String, Rank> liderboard2 = cordaClient.fetchRanking(hiCustodian);
System.out.println(liderboard1); assertThat(liderboard1.get(winnerName).gamesWon() +1 == liderboard2.get(winnerName).gamesWon() );
System.out.println(liderboard2); assertThat(liderboard1.get(winnerName).gamesPlayed() +1 == liderboard2.get(winnerName).gamesPlayed());
assertThat(liderboard1.get(losserName).gamesWon() == liderboard2.get(losserName).gamesWon() );
assertThat(liderboard1.get(losserName).gamesPlayed() +1 == liderboard2.get(losserName).gamesPlayed());
}
@Test
void testVictory() throws InvalidNameException {
final var hiCustodian = holdingIdentityResolver.getCustodian();
assertThat(hiCustodian).isNotNull();
final Map<String, Rank> liderboard1 = cordaClient.fetchRanking(hiCustodian);
final var hiWinner = holdingIdentityResolver.getByUsername(player2);
final var hiLooser = holdingIdentityResolver.getByUsername(player1);
final var winnerName = hiWinner.getName();
final var losserName = hiLooser.getName();
final GameState game = cordaClient.gameProposalCreate(hiWinner, hiLooser, Stone.Color.BLACK,
board1, "GameBoard GLOBAL_RANKING victory test");
cordaClient.gameProposalAccept(hiLooser, game.uuid());
cordaClient.gameBoardMove(hiWinner, game.uuid(), Arrays.asList(7, 14), null);
final Map<String, Rank> liderboard2 = cordaClient.fetchRanking(hiCustodian);
assertThat(liderboard1.get(winnerName).gamesWon() +1 == liderboard2.get(winnerName).gamesWon() );
assertThat(liderboard1.get(winnerName).gamesPlayed() +1 == liderboard2.get(winnerName).gamesPlayed());
assertThat(liderboard1.get(losserName).gamesWon() == liderboard2.get(losserName).gamesWon() );
assertThat(liderboard1.get(losserName).gamesPlayed() +1 == liderboard2.get(losserName).gamesPlayed());
} }
@Test @Test
void testIndividualRanking() { void testIndividualRanking() {
final var hiCustodian = holdingIdentityResolver.getByUsername(player2); final var hiCustodian = holdingIdentityResolver.getByUsername(player2);
final List<Rank> liderboard = cordaClient.fetchRanking(hiCustodian);
System.out.println(liderboard); assertThatNoException().isThrownBy( () ->
cordaClient.fetchRanking(hiCustodian)
);
} }
} }

View File

@ -8,20 +8,30 @@ import net.corda.v5.membership.NotaryInfo;
public class VNode { public class VNode {
static final String CHECKERS = "Checkers"; // O aka Organization static final String CHECKERS = "Checkers"; // O aka Organization
static final String PLAYER = "Player"; // OU aka OrganizationUnit
static final String CUSTODIAN = "Custodian"; // OU aka OrganizationUnit static final String CUSTODIAN = "Custodian"; // OU aka OrganizationUnit
static final String NOTARY = "Notary"; // OU aka OrganizationUnit static final String NOTARY = "Notary"; // OU aka OrganizationUnit
@CordaInject @CordaInject
MemberLookup memberLookup; MemberLookup memberLookup;
public static boolean isCordaCherckersPlayer(MemberInfo memberInfo) {
final MemberX500Name memberName = memberInfo.getName();
return memberName.getOrganization().equals(CHECKERS) &&
memberName.getOrganizationUnit().equals(PLAYER);
}
public static boolean isCordaCherckersCustodian(MemberInfo memberInfo) { public static boolean isCordaCherckersCustodian(MemberInfo memberInfo) {
final MemberX500Name memberName = memberInfo.getName(); final MemberX500Name memberName = memberInfo.getName();
return memberName.getOrganization().equals(CHECKERS) && return memberName.getOrganization().equals(CHECKERS) &&
memberName.getOrganizationUnit().equals(CUSTODIAN); memberName.getOrganizationUnit().equals(CUSTODIAN);
} }
public static boolean isCordaCherckersNotary(NotaryInfo notaryInfo) { public static boolean isCordaCherckersNotary(NotaryInfo notaryInfo) {
final MemberX500Name memberName = notaryInfo.getName(); final MemberX500Name memberName = notaryInfo.getName();
return memberName.getOrganization().equals(CHECKERS) && return memberName.getOrganization().equals(CHECKERS) &&
memberName.getOrganizationUnit().equals(NOTARY); memberName.getOrganizationUnit().equals(NOTARY);
} }

View File

@ -1,20 +1,36 @@
package djmil.cordacheckers.gameresult; package djmil.cordacheckers.gameresult;
public class Rank { public class Rank {
public final String name; private Integer gamesPlayed;
public final Integer gamesPlayed; private Integer gamesWon;
public final Integer gamesWon;
// Serialisation service requires a default constructor // Serialisation service requires a default constructor
Rank() { public Rank() {
name = null; gamesPlayed = 0;
gamesPlayed = null; gamesWon = 0;
gamesWon = null;
} }
public Rank(String name, Integer gamesPlayed, Integer gamesWon) { public Rank(Integer gamesPlayed, Integer gamesWon) {
this.name = name;
this.gamesPlayed = gamesPlayed; this.gamesPlayed = gamesPlayed;
this.gamesWon = gamesWon; this.gamesWon = gamesWon;
} }
Rank gamePlayed() {
gamesPlayed++;
return this;
}
Rank gameWon() {
gamesWon++;
return this;
}
public Integer getGamesPlayed() {
return gamesPlayed;
}
public Integer getGamesWon() {
return gamesWon;
}
} }

View File

@ -1,7 +1,6 @@
package djmil.cordacheckers.gameresult; package djmil.cordacheckers.gameresult;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -9,12 +8,13 @@ import java.util.stream.Collectors;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import djmil.cordacheckers.VNode;
import djmil.cordacheckers.states.GameResultState; import djmil.cordacheckers.states.GameResultState;
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;
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.ledger.utxo.UtxoLedgerService; import net.corda.v5.ledger.utxo.UtxoLedgerService;
@ -29,7 +29,7 @@ public class RankingFlow implements ClientStartableFlow {
public JsonMarshallingService jsonMarshallingService; public JsonMarshallingService jsonMarshallingService;
@CordaInject @CordaInject
public FlowEngine flowEngine; public MemberLookup memberLookup;
@Suspendable @Suspendable
@Override @Override
@ -37,27 +37,23 @@ public class RankingFlow implements ClientStartableFlow {
try { try {
final List<GameResultState> gameStateResutList = getGameResultsList(); final List<GameResultState> gameStateResutList = getGameResultsList();
Map<String, Integer> won = new HashMap<>(); Map<String, Rank> leaderboard = newLeaderboard();
for (GameResultState gs : gameStateResutList) { for (GameResultState gs : gameStateResutList) {
final var winner = gs.getWinnerName().getCommonName(); final var winner = gs.getWinnerName().getCommonName();
won.put(winner, won.getOrDefault(winner, 0) +1); if (winner != null)
leaderboard.put(winner, leaderboard.get(winner).gameWon() );
final var blackPlayer = gs.getBlackPlayer().getCommonName();
leaderboard.put(blackPlayer, leaderboard.get(blackPlayer).gamePlayed() );
final var whitePlayer = gs.getWhitePlayer().getCommonName();
leaderboard.put(whitePlayer, leaderboard.get(whitePlayer).gamePlayed() );
} }
Map<String, Integer> played = new HashMap<>(won); return new RankingFlowResponce(leaderboard)
for (GameResultState gs : gameStateResutList) {
final var looseer = gs.getLooserName().getCommonName();
played.put(looseer, played.getOrDefault(looseer, 0) +1);
}
List<Rank> board = new LinkedList<Rank>();
for (String name : played.keySet()) {
board.add(new Rank(name, played.getOrDefault(name, 0), won.getOrDefault(name, 0)));
}
return new RankingFlowResponce(board)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} catch (Exception e) { } catch (Exception e) {
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage()); log.warn(requestBody + " [ERROR] " +e.toString());
return new RankingFlowResponce(e) return new RankingFlowResponce(e)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
@ -72,4 +68,16 @@ public class RankingFlow implements ClientStartableFlow {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@Suspendable
Map<String, Rank> newLeaderboard() {
Map<String, Rank> leaderboard = new HashMap<>();
memberLookup.lookup()
.stream()
.filter(member -> VNode.isCordaCherckersPlayer(member) )
.forEach(member -> leaderboard.put(member.getName().getCommonName(), new Rank()) );
return leaderboard;
}
} }

View File

@ -1,14 +1,14 @@
package djmil.cordacheckers.gameresult; package djmil.cordacheckers.gameresult;
import java.util.List; import java.util.Map;
import net.corda.v5.application.marshalling.JsonMarshallingService; import net.corda.v5.application.marshalling.JsonMarshallingService;
public class RankingFlowResponce { public class RankingFlowResponce {
public final List<Rank> successStatus; public final Map<String, Rank> successStatus;
public final String failureStatus; public final String failureStatus;
public RankingFlowResponce(List<Rank> success) { public RankingFlowResponce(Map<String, Rank> success) {
this.successStatus = success; this.successStatus = success;
this.failureStatus = null; this.failureStatus = null;
} }