From a1f0d48477510222e9120733fdafdc1cf323eb93 Mon Sep 17 00:00:00 2001 From: Bas de Jong Date: Mon, 12 Jan 2026 08:33:54 +0100 Subject: [PATCH] Refactored Tournament to use matchExecutor and ResultBroadcaster. Added turnTime and players are now added through Tournament creation instead of on MatchMaker/ScoreSystem creation --- .../networking/server/GameServer.java | 3 +- .../networking/server/MatchExecutor.java | 10 +++ .../framework/networking/server/Server.java | 37 ++++------ .../tournaments/AsyncTournamentRunner.java | 17 +++-- .../tournaments/BasicTournamentRunner.java | 20 +++-- .../server/tournaments/ResultBroadcaster.java | 8 ++ .../server/tournaments/Tournament.java | 73 +++++++++++++++---- .../server/tournaments/TournamentMatch.java | 4 +- .../server/tournaments/TournamentRunner.java | 9 ++- .../tournaments/matchmakers/MatchMaker.java | 1 + .../matchmakers/RoundRobinMatchMaker.java | 11 ++- .../scoresystems/BasicScoreSystem.java | 33 ++++----- .../scoresystems/IntegerScoreSystem.java | 6 ++ .../tournaments/scoresystems/ScoreSystem.java | 18 +---- 14 files changed, 159 insertions(+), 91 deletions(-) create mode 100644 framework/src/main/java/org/toop/framework/networking/server/MatchExecutor.java create mode 100644 framework/src/main/java/org/toop/framework/networking/server/tournaments/ResultBroadcaster.java create mode 100644 framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/IntegerScoreSystem.java diff --git a/framework/src/main/java/org/toop/framework/networking/server/GameServer.java b/framework/src/main/java/org/toop/framework/networking/server/GameServer.java index 190ed46..2b39e7f 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/GameServer.java +++ b/framework/src/main/java/org/toop/framework/networking/server/GameServer.java @@ -2,11 +2,12 @@ package org.toop.framework.networking.server; import org.toop.framework.gameFramework.model.game.TurnBasedGame; +import java.time.Duration; import java.util.List; import java.util.concurrent.CompletableFuture; public interface GameServer { - GameResultFuture startGame(String gameType, CLIENT... clients); + GameResultFuture startGame(String gameType, Duration turnTime, CLIENT... clients); void addClient(CLIENT client); void removeClient(CLIENT client); diff --git a/framework/src/main/java/org/toop/framework/networking/server/MatchExecutor.java b/framework/src/main/java/org/toop/framework/networking/server/MatchExecutor.java new file mode 100644 index 0000000..ff8ba29 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/server/MatchExecutor.java @@ -0,0 +1,10 @@ +package org.toop.framework.networking.server; + +import org.toop.framework.networking.server.client.NettyClient; + +import java.time.Duration; + +@FunctionalInterface +public interface MatchExecutor { + GameResultFuture submit(String gameType, Duration turnTime, NettyClient... clients); +} diff --git a/framework/src/main/java/org/toop/framework/networking/server/Server.java b/framework/src/main/java/org/toop/framework/networking/server/Server.java index 6a473d7..4dbbfb2 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/Server.java +++ b/framework/src/main/java/org/toop/framework/networking/server/Server.java @@ -13,7 +13,7 @@ import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore; import org.toop.framework.networking.server.tournaments.*; import org.toop.framework.networking.server.tournaments.matchmakers.RoundRobinMatchMaker; import org.toop.framework.networking.server.tournaments.scoresystems.BasicScoreSystem; -import org.toop.framework.networking.server.tournaments.shufflers.RandomShuffle; +import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem; import org.toop.framework.utils.ImmutablePair; import java.util.*; @@ -111,7 +111,7 @@ public class Server implements GameServer { public void acceptChallenge(Long challengeId) { for (var challenge : gameChallenges) { if (challenge.id() == challengeId) { - startGame(challenge.acceptChallenge(), challenge.getUsers()); + startGame(challenge.acceptChallenge(), Duration.ofSeconds(10), challenge.getUsers()); break; } } @@ -133,7 +133,7 @@ public class Server implements GameServer { } @Override - public GameResultFuture startGame(String gameType, NettyClient... clients) { + public GameResultFuture startGame(String gameType, Duration turnTime, NettyClient... clients) { if (!gameTypesStore.all().containsKey(gameType)) return null; try { @@ -250,7 +250,7 @@ public class Server implements GameServer { userNames.remove(first); userNames.remove(second); - startGame(key, getUser(userLeft), getUser(userRight)); + startGame(key, Duration.ofSeconds(10), getUser(userLeft), getUser(userRight)); } } } @@ -294,33 +294,26 @@ public class Server implements GameServer { var tournamentUsers = new ArrayList<>(onlineUsers()); tournamentUsers.removeIf(admins::contains); - var matchMaker = new RoundRobinMatchMaker(tournamentUsers); - if (shuffle) { - matchMaker.shuffle(new RandomShuffle()); // Remove if not wanting to shuffle - } - Tournament tournament = new Tournament.Builder() - .server(this) + .matchExecutor(this::startGame) .tournamentRunner(new AsyncTournamentRunner()) - .matchMaker(matchMaker) - .scoreSystem(new BasicScoreSystem(tournamentUsers)) + .matchMaker(new RoundRobinMatchMaker()) + .scoreSystem(new BasicScoreSystem()) + .resultBroadcaster(this::endTournament) + .turnTimeout(Duration.ofSeconds(5)) + .addPlayers(tournamentUsers.toArray(NettyClient[]::new)) + .addAdmins(admins.toArray(NettyClient[]::new)) .build(); - try { - new Thread(() -> tournament.run(gameType)).start(); - } catch (IllegalArgumentException e) { - admins.forEach(c -> c.send("ERR not enough clients to start a tournament")); - } catch (RuntimeException e) { - admins.forEach(c -> c.send("ERR no matches could be created to start a tournament with")); - } + new Thread(() -> tournament.run(gameType)).start(); } - public void endTournament(Map score, String gameType) { + public void endTournament(IntegerScoreSystem score) { List u = new ArrayList<>(); List s = new ArrayList<>(); - for (var entry : score.entrySet()) { + for (var entry : score.getScore().entrySet()) { u.add(entry.getKey().name()); s.add(entry.getValue()); } @@ -332,7 +325,7 @@ public class Server implements GameServer { String msg = String.format( "SVR RESULTS {GAMETYPE: \"%s\", USERS: %s, SCORES: %s, TOURNAMENT: 1}", - gameType, + "none", // TODO gametype users, scores ); diff --git a/framework/src/main/java/org/toop/framework/networking/server/tournaments/AsyncTournamentRunner.java b/framework/src/main/java/org/toop/framework/networking/server/tournaments/AsyncTournamentRunner.java index 61f9faa..ebeded5 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/tournaments/AsyncTournamentRunner.java +++ b/framework/src/main/java/org/toop/framework/networking/server/tournaments/AsyncTournamentRunner.java @@ -1,11 +1,12 @@ package org.toop.framework.networking.server.tournaments; import org.toop.framework.networking.server.GameResultFuture; -import org.toop.framework.networking.server.Server; +import org.toop.framework.networking.server.MatchExecutor; import org.toop.framework.networking.server.client.NettyClient; import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker; -import org.toop.framework.networking.server.tournaments.scoresystems.ScoreSystem; +import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem; +import java.time.Duration; import java.util.*; import java.util.concurrent.*; @@ -13,9 +14,11 @@ public class AsyncTournamentRunner implements TournamentRunner { @Override public void run( - Server server, + MatchExecutor matchRunner, MatchMaker matchMaker, - ScoreSystem scoreSystem, + IntegerScoreSystem scoreSystem, + ResultBroadcaster broadcaster, + Duration turnTime, String gameType ) { @@ -52,8 +55,8 @@ public class AsyncTournamentRunner implements TournamentRunner { CompletableFuture f = CompletableFuture.runAsync(() -> { try { - GameResultFuture game = server.startGame(gameType, a, b); - scoreSystem.matchEndAwait(game); + GameResultFuture game = matchRunner.submit(gameType, turnTime, a, b); + scoreSystem.result(match, game.result().join()); } finally { a.clearGame(); b.clearGame(); @@ -70,7 +73,7 @@ public class AsyncTournamentRunner implements TournamentRunner { Thread.sleep(10); // Safety } - server.endTournament(scoreSystem.getScore(), gameType); + broadcaster.broadcast(scoreSystem); } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/framework/src/main/java/org/toop/framework/networking/server/tournaments/BasicTournamentRunner.java b/framework/src/main/java/org/toop/framework/networking/server/tournaments/BasicTournamentRunner.java index 4e4dc20..d1ca4fa 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/tournaments/BasicTournamentRunner.java +++ b/framework/src/main/java/org/toop/framework/networking/server/tournaments/BasicTournamentRunner.java @@ -1,28 +1,36 @@ package org.toop.framework.networking.server.tournaments; import org.toop.framework.networking.server.GameResultFuture; -import org.toop.framework.networking.server.Server; +import org.toop.framework.networking.server.MatchExecutor; import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker; -import org.toop.framework.networking.server.tournaments.scoresystems.ScoreSystem; +import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem; +import java.time.Duration; import java.util.concurrent.*; public class BasicTournamentRunner implements TournamentRunner { @Override - public void run(Server server, MatchMaker matchMaker, ScoreSystem scoreSystem, String gameType) { + public void run( + MatchExecutor matchExecutor, + MatchMaker matchMaker, + IntegerScoreSystem scoreSystem, + ResultBroadcaster broadcaster, + Duration turnTime, + String gameType + ) { ExecutorService threadPool = Executors.newSingleThreadExecutor(); try { threadPool.execute(() -> { for (TournamentMatch match : matchMaker) { // Play game and await the results - GameResultFuture game = server.startGame(gameType, match.getClient0(), match.getClient1()); - scoreSystem.matchEndAwait(game); + GameResultFuture game = matchExecutor.submit(gameType, turnTime, match.getClient0(), match.getClient1()); + scoreSystem.result(match, game.result().join()); match.getClient0().clearGame(); match.getClient1().clearGame(); } - server.endTournament(scoreSystem.getScore(), gameType); + broadcaster.broadcast(scoreSystem); }); } finally { threadPool.shutdown(); diff --git a/framework/src/main/java/org/toop/framework/networking/server/tournaments/ResultBroadcaster.java b/framework/src/main/java/org/toop/framework/networking/server/tournaments/ResultBroadcaster.java new file mode 100644 index 0000000..e0aa1e9 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/server/tournaments/ResultBroadcaster.java @@ -0,0 +1,8 @@ +package org.toop.framework.networking.server.tournaments; + +import org.toop.framework.networking.server.tournaments.scoresystems.ScoreSystem; + +@FunctionalInterface +public interface ResultBroadcaster> { + void broadcast(T scoreSystem); +} diff --git a/framework/src/main/java/org/toop/framework/networking/server/tournaments/Tournament.java b/framework/src/main/java/org/toop/framework/networking/server/tournaments/Tournament.java index 94aa3a4..770556e 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/tournaments/Tournament.java +++ b/framework/src/main/java/org/toop/framework/networking/server/tournaments/Tournament.java @@ -1,44 +1,62 @@ package org.toop.framework.networking.server.tournaments; -import org.toop.framework.networking.server.Server; +import org.toop.framework.networking.server.MatchExecutor; +import org.toop.framework.networking.server.client.NettyClient; import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker; -import org.toop.framework.networking.server.tournaments.scoresystems.ScoreSystem; +import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem; +import java.time.Duration; +import java.util.Arrays; import java.util.Objects; public class Tournament { - private final Server server; - private final ScoreSystem scoreSystem; + private final MatchExecutor matchExecutor; + private final IntegerScoreSystem scoreSystem; private final TournamentRunner tournamentRunner; private final MatchMaker matchMaker; + private final ResultBroadcaster broadcaster; + private final NettyClient[] players; + private final Duration turnTime; private Tournament(Tournament.Builder builder) { - server = builder.server; + matchExecutor = builder.matchExecutor; scoreSystem = builder.scoreSystem; tournamentRunner = builder.tournamentRunner; matchMaker = builder.matchMaker; + broadcaster = builder.broadcaster; + players = builder.players; + turnTime = builder.turnTime; } public void run(String gameType) throws IllegalArgumentException { - if (server.gameTypes().stream().noneMatch(e -> e.equalsIgnoreCase(gameType))) - throw new IllegalArgumentException("Invalid game type"); - tournamentRunner.run(server, matchMaker, scoreSystem, gameType); + Arrays.stream(players).forEach(e -> { + matchMaker.addPlayer(e); + scoreSystem.addPlayer(e); + }); + + tournamentRunner.run(matchExecutor, matchMaker, scoreSystem, broadcaster, turnTime, gameType); + } public static class Builder { - private Server server; - private ScoreSystem scoreSystem; + private MatchExecutor matchExecutor; + private IntegerScoreSystem scoreSystem; private TournamentRunner tournamentRunner; private MatchMaker matchMaker; + private ResultBroadcaster broadcaster; + private NettyClient[] players; + private NettyClient[] observors; + private NettyClient[] admins; + private Duration turnTime = Duration.ofSeconds(10); - public Builder server(Server server) { - this.server = server; + public Builder matchExecutor(MatchExecutor matchExecutor) { + this.matchExecutor = matchExecutor; return this; } - public Builder scoreSystem(ScoreSystem scoreSystem) { + public Builder scoreSystem(IntegerScoreSystem scoreSystem) { this.scoreSystem = scoreSystem; return this; } @@ -53,11 +71,38 @@ public class Tournament { return this; } + public Builder resultBroadcaster(ResultBroadcaster broadcaster) { + this.broadcaster = broadcaster; + return this; + } + + public Builder addPlayers(NettyClient[] players) { + this.players = players; + return this; + } + + public Builder addObservers(NettyClient[] observors) { // TODO + this.observors = observors; + return this; + } + + public Builder addAdmins(NettyClient[] admins) { // TODO + this.admins = admins; + return this; + } + + public Builder turnTimeout(Duration turnTime) { + this.turnTime = turnTime; + return this; + } + public Tournament build() { - Objects.requireNonNull(server, "server"); + Objects.requireNonNull(matchExecutor, "matchExecutor"); Objects.requireNonNull(scoreSystem, "scoreSystem"); Objects.requireNonNull(tournamentRunner, "tournamentRunner"); Objects.requireNonNull(matchMaker, "matchMaker"); + Objects.requireNonNull(broadcaster, "resultBroadcaster"); // TODO is not always necessary and needs to be more generic, not just at the end + Objects.requireNonNull(players, "players"); return new Tournament(this); } } diff --git a/framework/src/main/java/org/toop/framework/networking/server/tournaments/TournamentMatch.java b/framework/src/main/java/org/toop/framework/networking/server/tournaments/TournamentMatch.java index ab703e7..1b3f8d1 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/tournaments/TournamentMatch.java +++ b/framework/src/main/java/org/toop/framework/networking/server/tournaments/TournamentMatch.java @@ -8,11 +8,11 @@ public class TournamentMatch extends ImmutablePair { super(a, b); } - NettyClient getClient0() { + public NettyClient getClient0() { return getLeft(); } - NettyClient getClient1() { + public NettyClient getClient1() { return getRight(); } } diff --git a/framework/src/main/java/org/toop/framework/networking/server/tournaments/TournamentRunner.java b/framework/src/main/java/org/toop/framework/networking/server/tournaments/TournamentRunner.java index fe0e9cb..ffd430e 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/tournaments/TournamentRunner.java +++ b/framework/src/main/java/org/toop/framework/networking/server/tournaments/TournamentRunner.java @@ -1,9 +1,12 @@ package org.toop.framework.networking.server.tournaments; -import org.toop.framework.networking.server.Server; +import org.toop.framework.networking.server.MatchExecutor; import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker; -import org.toop.framework.networking.server.tournaments.scoresystems.ScoreSystem; +import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem; + +import java.time.Duration; public interface TournamentRunner { - void run(Server server, MatchMaker matchMaker, ScoreSystem scoreSystem, String gameType); + void run(MatchExecutor matchExecutor, MatchMaker matchMaker, IntegerScoreSystem scoreSystem, + ResultBroadcaster broadcaster, Duration turnTime, String gameType); } diff --git a/framework/src/main/java/org/toop/framework/networking/server/tournaments/matchmakers/MatchMaker.java b/framework/src/main/java/org/toop/framework/networking/server/tournaments/matchmakers/MatchMaker.java index 6aba783..af4ef63 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/tournaments/matchmakers/MatchMaker.java +++ b/framework/src/main/java/org/toop/framework/networking/server/tournaments/matchmakers/MatchMaker.java @@ -7,6 +7,7 @@ import org.toop.framework.networking.server.tournaments.shufflers.Shuffler; import java.util.List; public interface MatchMaker extends Iterable { + void addPlayer(NettyClient player); List getPlayers(); void shuffle(Shuffler shuffler); } diff --git a/framework/src/main/java/org/toop/framework/networking/server/tournaments/matchmakers/RoundRobinMatchMaker.java b/framework/src/main/java/org/toop/framework/networking/server/tournaments/matchmakers/RoundRobinMatchMaker.java index 5ef3e10..0bea9e9 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/tournaments/matchmakers/RoundRobinMatchMaker.java +++ b/framework/src/main/java/org/toop/framework/networking/server/tournaments/matchmakers/RoundRobinMatchMaker.java @@ -4,20 +4,25 @@ import org.toop.framework.networking.server.client.NettyClient; import org.toop.framework.networking.server.tournaments.TournamentMatch; import org.toop.framework.networking.server.tournaments.shufflers.Shuffler; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; public class RoundRobinMatchMaker implements MatchMaker { - private final List players; + private final List players = new ArrayList<>(); - public RoundRobinMatchMaker(List players) { - this.players = players; + public RoundRobinMatchMaker() {} // TODO let user decide store type + + @Override + public void addPlayer(NettyClient player) { + players.addLast(player); } @Override public void shuffle(Shuffler shuffler) { + if (players.size() < 2) return; shuffler.shuffle(players); } diff --git a/framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/BasicScoreSystem.java b/framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/BasicScoreSystem.java index 32b5b43..78b56ec 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/BasicScoreSystem.java +++ b/framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/BasicScoreSystem.java @@ -1,40 +1,35 @@ package org.toop.framework.networking.server.tournaments.scoresystems; -import org.toop.framework.networking.server.GameResultFuture; import org.toop.framework.networking.server.client.NettyClient; +import org.toop.framework.networking.server.tournaments.TournamentMatch; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -public class BasicScoreSystem implements ScoreSystem { +public class BasicScoreSystem implements IntegerScoreSystem { private final Map scores = new ConcurrentHashMap<>(); + private final int INIT_SCORE = 0; + private final int WIN_POINTS = 1; - public BasicScoreSystem(List store) { - for (NettyClient c : store) { - scores.putIfAbsent(c, getInitScore()); - } + public BasicScoreSystem() {} // TODO let user decide store type + + @Override + public void addPlayer(NettyClient user) { + scores.putIfAbsent(user, INIT_SCORE); } @Override - public void matchEndAwait(GameResultFuture result) { - - if (result.game().users().length < 2) return; - - switch (result.result().join()) { - case 0 -> givePoints(result.game().users()[0]); - case 1 -> givePoints(result.game().users()[1]); + public void result(TournamentMatch match, Integer result) { + switch (result) { + case 0 -> scores.merge(match.getClient0(), WIN_POINTS, Integer::sum); + case 1 -> scores.merge(match.getClient1(), WIN_POINTS, Integer::sum); case -1 -> {} // Draw - default -> {} + default -> throw new IllegalArgumentException("Unknown result: " + result); } } - private void givePoints(NettyClient client) { - int clientScore = scores.get(client); - scores.put(client, clientScore + getWinPointAmount()); - } - @Override public Map getScore() { return scores; diff --git a/framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/IntegerScoreSystem.java b/framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/IntegerScoreSystem.java new file mode 100644 index 0000000..ed75ac0 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/IntegerScoreSystem.java @@ -0,0 +1,6 @@ +package org.toop.framework.networking.server.tournaments.scoresystems; + +import org.toop.framework.networking.server.client.NettyClient; +import org.toop.framework.networking.server.tournaments.TournamentMatch; + +public interface IntegerScoreSystem extends ScoreSystem {} diff --git a/framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/ScoreSystem.java b/framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/ScoreSystem.java index 36cd8c4..8a8a453 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/ScoreSystem.java +++ b/framework/src/main/java/org/toop/framework/networking/server/tournaments/scoresystems/ScoreSystem.java @@ -1,19 +1,9 @@ package org.toop.framework.networking.server.tournaments.scoresystems; -import org.toop.framework.networking.server.GameResultFuture; -import org.toop.framework.networking.server.client.NettyClient; -import org.toop.framework.networking.server.tournaments.TournamentMatch; - import java.util.Map; -public interface ScoreSystem { - void matchEndAwait(GameResultFuture result); - Map getScore(); - - default int getWinPointAmount() { - return 1; - } - default int getInitScore() { - return 0; - } +public interface ScoreSystem { + void addPlayer(USERTYPE user); + void result(MATCHTYPE match, SCORETYPE result); + Map getScore(); }