4 Commits

Author SHA1 Message Date
lieght
cc7acf9f0c Moved scoring calculation into scoring system 2026-01-11 07:45:55 +01:00
Bas de Jong
955cb6109c Added back ability to shuffle matchmaker 2026-01-11 01:54:40 +01:00
Bas de Jong
013dd90705 Async tournament runner 2026-01-11 01:42:59 +01:00
Bas de Jong
c77499c36d Added result comeback with a draw 2026-01-11 00:53:31 +01:00
11 changed files with 165 additions and 46 deletions

View File

@@ -0,0 +1,7 @@
package org.toop.framework.networking.server;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import java.util.concurrent.CompletableFuture;
public record GameResultFuture(OnlineGame<TurnBasedGame> game, CompletableFuture<Integer> result) {}

View File

@@ -6,7 +6,7 @@ import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public interface GameServer<GAMETYPE, CLIENT, CHALLENGEIDTYPE> { public interface GameServer<GAMETYPE, CLIENT, CHALLENGEIDTYPE> {
OnlineGame<TurnBasedGame> startGame(String gameType, CompletableFuture<Void> futureOrNull, CLIENT... clients); GameResultFuture startGame(String gameType, CLIENT... clients);
void addClient(CLIENT client); void addClient(CLIENT client);
void removeClient(CLIENT client); void removeClient(CLIENT client);

View File

@@ -17,24 +17,20 @@ public class OnlineTurnBasedGame implements OnlineGame<TurnBasedGame> {
private TurnBasedGame game; private TurnBasedGame game;
private ServerThreadBehaviour gameThread; private ServerThreadBehaviour gameThread;
private final CompletableFuture<Void> futureOrNull; private final CompletableFuture<Integer> resultFuture;
public OnlineTurnBasedGame(NettyClient[] admins, TurnBasedGame game, CompletableFuture<Void> futureOrNull, NettyClient... clients) { public OnlineTurnBasedGame(NettyClient[] admins, TurnBasedGame game, CompletableFuture<Integer> resultFuture, NettyClient... clients) {
this.game = game; this.game = game;
this.gameThread = new ServerThreadBehaviour( this.gameThread = new ServerThreadBehaviour(
game, game,
(pair) -> notifyMoveMade(pair.getLeft(), pair.getRight()), (pair) -> notifyMoveMade(pair.getLeft(), pair.getRight()),
(pair) -> notifyGameEnd(pair.getLeft(), pair.getRight()) (pair) -> notifyGameEnd(pair.getLeft(), pair.getRight())
); );
this.futureOrNull = futureOrNull; this.resultFuture = resultFuture;
this.clients = clients; this.clients = clients;
this.admins = admins; this.admins = admins;
} }
public OnlineTurnBasedGame(NettyClient[] admins, TurnBasedGame game, NettyClient... clients) {
this(admins, game, null, clients);
}
private void notifyMoveMade(String speler, int move){ private void notifyMoveMade(String speler, int move){
for (NettyClient admin : admins) { for (NettyClient admin : admins) {
admin.send(String.format("SVR GAME MOVE {PLAYER: \"%s\", MOVE: \"%s\", DETAILS: \"<reactie spel op zet>\"}", speler, move)); admin.send(String.format("SVR GAME MOVE {PLAYER: \"%s\", MOVE: \"%s\", DETAILS: \"<reactie spel op zet>\"}", speler, move));
@@ -56,7 +52,7 @@ public class OnlineTurnBasedGame implements OnlineGame<TurnBasedGame> {
} else { } else {
Arrays.stream(admins).forEach(a -> a.send("SVR GAME END")); Arrays.stream(admins).forEach(a -> a.send("SVR GAME END"));
clients[winner].send(String.format("SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}")); clients[winner].send(String.format("SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}"));
clients[1-winner].send(String.format("SVR GAME LOSS {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}")); clients[(winner+1)%2].send(String.format("SVR GAME LOSS {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}"));
} }
// Remove game from clients // Remove game from clients
@@ -65,8 +61,9 @@ public class OnlineTurnBasedGame implements OnlineGame<TurnBasedGame> {
client.clearGame(); client.clearGame();
} }
if (futureOrNull != null) { if (resultFuture != null) {
futureOrNull.complete(null); if (state.equals(GameState.DRAW)) resultFuture.complete(-1); // Return -1 if draw
else resultFuture.complete(winner); // Return number for winner's index
} }
} }

View File

@@ -13,6 +13,7 @@ import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore;
import org.toop.framework.networking.server.tournaments.*; import org.toop.framework.networking.server.tournaments.*;
import org.toop.framework.networking.server.tournaments.matchmakers.RoundRobinMatchMaker; 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.scoresystems.BasicScoreSystem;
import org.toop.framework.networking.server.tournaments.shufflers.RandomShuffle;
import org.toop.framework.utils.ImmutablePair; import org.toop.framework.utils.ImmutablePair;
import java.util.*; import java.util.*;
@@ -110,7 +111,7 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
public void acceptChallenge(Long challengeId) { public void acceptChallenge(Long challengeId) {
for (var challenge : gameChallenges) { for (var challenge : gameChallenges) {
if (challenge.id() == challengeId) { if (challenge.id() == challengeId) {
startGame(challenge.acceptChallenge(), null, challenge.getUsers()); startGame(challenge.acceptChallenge(), challenge.getUsers());
break; break;
} }
} }
@@ -132,12 +133,23 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
} }
@Override @Override
public OnlineGame<TurnBasedGame> startGame(String gameType, CompletableFuture<Void> futureOrNull, NettyClient... clients) { public GameResultFuture startGame(String gameType, NettyClient... clients) {
if (!gameTypesStore.all().containsKey(gameType)) return null; if (!gameTypesStore.all().containsKey(gameType)) return null;
try { try {
ServerPlayer[] players = new ServerPlayer[clients.length]; ServerPlayer[] players = new ServerPlayer[clients.length];
var game = new OnlineTurnBasedGame(getAdmins().toArray(NettyClient[]::new), gameTypesStore.create(gameType), futureOrNull, clients);
var gameResult = new CompletableFuture<Integer>();
var game = new OnlineTurnBasedGame(
getAdmins().toArray(NettyClient[]::new),
gameTypesStore.create(gameType),
gameResult,
clients
);
var grfReturn = new GameResultFuture(game, gameResult);
for (int i = 0; i < clients.length; i++) { for (int i = 0; i < clients.length; i++) {
players[i] = new ServerPlayer(clients[i]); players[i] = new ServerPlayer(clients[i]);
@@ -156,7 +168,7 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
gameType, gameType,
clients[0].name())); clients[0].name()));
game.start(); game.start();
return game; return grfReturn;
} catch (Exception e) { } catch (Exception e) {
IO.println("ERROR: Failed to start OnlineTurnBasedGame"); IO.println("ERROR: Failed to start OnlineTurnBasedGame");
e.printStackTrace(); e.printStackTrace();
@@ -273,7 +285,7 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
return true; return true;
} }
public void startTournament(String gameType, NettyClient requestor) { public void startTournament(String gameType, NettyClient requestor, boolean shuffle) {
if (!admins.contains(requestor)) { if (!admins.contains(requestor)) {
requestor.send("ERR you do not have the privileges to start a tournament"); requestor.send("ERR you do not have the privileges to start a tournament");
return; return;
@@ -282,10 +294,15 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
var tournamentUsers = new ArrayList<>(onlineUsers()); var tournamentUsers = new ArrayList<>(onlineUsers());
tournamentUsers.removeIf(admins::contains); tournamentUsers.removeIf(admins::contains);
var matchMaker = new RoundRobinMatchMaker(tournamentUsers);
if (shuffle) {
matchMaker.shuffle(new RandomShuffle()); // Remove if not wanting to shuffle
}
Tournament tournament = new BasicTournament(new TournamentBuilder( Tournament tournament = new BasicTournament(new TournamentBuilder(
this, this,
new BasicTournamentRunner(), new AsyncTournamentRunner(),
new RoundRobinMatchMaker(tournamentUsers), matchMaker,
new BasicScoreSystem(tournamentUsers) new BasicScoreSystem(tournamentUsers)
)); ));

View File

@@ -119,7 +119,7 @@ public class MessageHandler implements Handler<ParsedMessage> {
if(!hasArgs(p.args())) return; if(!hasArgs(p.args())) return;
if (p.args()[0].equalsIgnoreCase("start") && p.args().length > 1) { if (p.args()[0].equalsIgnoreCase("start") && p.args().length > 1) {
server.startTournament(p.args()[1], client); server.startTournament(p.args()[1], client, false); // TODO add shuffle to msg
} }
} }
} }

View File

@@ -0,0 +1,81 @@
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.client.NettyClient;
import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker;
import org.toop.framework.networking.server.tournaments.scoresystems.ScoreSystem;
import java.util.*;
import java.util.concurrent.*;
public class AsyncTournamentRunner implements TournamentRunner {
@Override
public void run(
Server server,
MatchMaker matchMaker,
ScoreSystem scoreSystem,
String gameType
) {
ExecutorService matchExecutor =
Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
Queue<TournamentMatch> pendingMatches = new ConcurrentLinkedQueue<>();
matchMaker.forEach(pendingMatches::add);
Set<NettyClient> busyPlayers = ConcurrentHashMap.newKeySet();
List<CompletableFuture<Void>> runningMatches = new CopyOnWriteArrayList<>();
try {
while (!pendingMatches.isEmpty() || !runningMatches.isEmpty()) {
Iterator<TournamentMatch> it = pendingMatches.iterator();
while (it.hasNext()) {
TournamentMatch match = it.next();
NettyClient a = match.getClient0();
NettyClient b = match.getClient1();
// TODO game != null doesn't work here, fix later
if (busyPlayers.contains(a) || busyPlayers.contains(b)) {
continue;
}
busyPlayers.add(a);
busyPlayers.add(b);
it.remove();
CompletableFuture<Void> f =
CompletableFuture.runAsync(() -> {
try {
GameResultFuture game = server.startGame(gameType, a, b);
scoreSystem.matchEndAwait(game);
} finally {
a.clearGame();
b.clearGame();
busyPlayers.remove(a);
busyPlayers.remove(b);
}
}, matchExecutor);
runningMatches.add(f);
f.whenComplete((_, _) -> runningMatches.remove(f));
}
Thread.sleep(10); // Safety
}
server.endTournament(scoreSystem.getScore(), gameType);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
matchExecutor.shutdown();
}
}
}

View File

@@ -1,7 +1,6 @@
package org.toop.framework.networking.server.tournaments; package org.toop.framework.networking.server.tournaments;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.networking.server.GameResultFuture;
import org.toop.framework.networking.server.OnlineGame;
import org.toop.framework.networking.server.Server; import org.toop.framework.networking.server.Server;
import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker; 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.ScoreSystem;
@@ -9,29 +8,15 @@ import org.toop.framework.networking.server.tournaments.scoresystems.ScoreSystem
import java.util.concurrent.*; import java.util.concurrent.*;
public class BasicTournamentRunner implements TournamentRunner { public class BasicTournamentRunner implements TournamentRunner {
public BasicTournamentRunner() {}
@Override @Override
public void run(Server server, MatchMaker matchMaker, ScoreSystem scoreSystem, String gameType) { public void run(Server server, MatchMaker matchMaker, ScoreSystem scoreSystem, String gameType) {
ExecutorService threadPool = Executors.newSingleThreadExecutor(); ExecutorService threadPool = Executors.newSingleThreadExecutor();
try { try {
threadPool.execute(() -> { threadPool.execute(() -> {
for (var match : matchMaker) { for (TournamentMatch match : matchMaker) {
final CompletableFuture<Void> finished = new CompletableFuture<>();
// Play game and await the results // Play game and await the results
OnlineGame<TurnBasedGame> game = server.startGame(gameType, finished, match.getClient0(), match.getClient1()); // TODO can possibly create a race condition GameResultFuture game = server.startGame(gameType, match.getClient0(), match.getClient1());
finished.join(); scoreSystem.matchEndAwait(game);
// End
// Get result and calculate new score
switch (game.game().getWinner()) {
case 0 -> scoreSystem.addScore(match.getClient0());
case 1 -> scoreSystem.addScore(match.getClient1());
default -> {
}
}
match.getClient0().clearGame(); match.getClient0().clearGame();
match.getClient1().clearGame(); match.getClient1().clearGame();

View File

@@ -1,5 +1,12 @@
package org.toop.framework.networking.server.tournaments.matchmakers; package org.toop.framework.networking.server.tournaments.matchmakers;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch; import org.toop.framework.networking.server.tournaments.TournamentMatch;
import org.toop.framework.networking.server.tournaments.shufflers.Shuffler;
public interface MatchMaker extends Iterable<TournamentMatch> {} import java.util.List;
public interface MatchMaker extends Iterable<TournamentMatch> {
List<NettyClient> getPlayers();
void shuffle(Shuffler shuffler);
}

View File

@@ -2,6 +2,7 @@ package org.toop.framework.networking.server.tournaments.matchmakers;
import org.toop.framework.networking.server.client.NettyClient; import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch; import org.toop.framework.networking.server.tournaments.TournamentMatch;
import org.toop.framework.networking.server.tournaments.shufflers.Shuffler;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@@ -15,6 +16,16 @@ public class RoundRobinMatchMaker implements MatchMaker {
this.players = players; this.players = players;
} }
@Override
public void shuffle(Shuffler shuffler) {
shuffler.shuffle(players);
}
@Override
public List<NettyClient> getPlayers() {
return players;
}
@Override @Override
public Iterator<TournamentMatch> iterator() { public Iterator<TournamentMatch> iterator() {
return new Iterator<>() { return new Iterator<>() {

View File

@@ -1,14 +1,15 @@
package org.toop.framework.networking.server.tournaments.scoresystems; 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.client.NettyClient;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class BasicScoreSystem implements ScoreSystem { public class BasicScoreSystem implements ScoreSystem {
private final Map<NettyClient, Integer> scores = new HashMap<>(); private final Map<NettyClient, Integer> scores = new ConcurrentHashMap<>();
public BasicScoreSystem(List<NettyClient> store) { public BasicScoreSystem(List<NettyClient> store) {
for (NettyClient c : store) { for (NettyClient c : store) {
@@ -17,7 +18,19 @@ public class BasicScoreSystem implements ScoreSystem {
} }
@Override @Override
public void addScore(NettyClient client) { 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]);
case -1 -> {} // Draw
default -> {}
}
}
private void givePoints(NettyClient client) {
int clientScore = scores.get(client); int clientScore = scores.get(client);
scores.put(client, clientScore + getWinPointAmount()); scores.put(client, clientScore + getWinPointAmount());
} }

View File

@@ -1,17 +1,18 @@
package org.toop.framework.networking.server.tournaments.scoresystems; 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.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import java.util.Map; import java.util.Map;
public interface ScoreSystem { public interface ScoreSystem {
void addScore(NettyClient client); void matchEndAwait(GameResultFuture result);
Map<NettyClient, Integer> getScore(); Map<NettyClient, Integer> getScore();
default int getWinPointAmount() { default int getWinPointAmount() {
return 1; return 1;
} }
default int getInitScore() { default int getInitScore() {
return 0; return 0;
} }