Refactor done, added ability to subscribe

This commit is contained in:
lieght
2025-12-13 22:44:13 +01:00
parent 0956286616
commit c2f1df7143
12 changed files with 178 additions and 112 deletions

View File

@@ -93,7 +93,7 @@ public class GenericGameController implements GameController {
} }
private void onGameFinish(GUIEvents.GameEnded event){ private void onGameFinish(GUIEvents.GameEnded event){
logger.info("Game Finished"); logger.info("OnlineTurnBasedGame Finished");
String name = event.winner() == -1 ? null : getPlayer(event.winner()).getName(); String name = event.winner() == -1 ? null : getPlayer(event.winner()).getName();
gameView.gameOver(event.winOrTie(), name); gameView.gameOver(event.winOrTie(), name);
stop(); stop();

View File

@@ -126,7 +126,7 @@ public class MiniMaxAI extends AbstractAI {
* Simple heuristic evaluation for Reversi-like games. * Simple heuristic evaluation for Reversi-like games.
* Positive = good for AI, Negative = good for opponent. * Positive = good for AI, Negative = good for opponent.
* *
* @param game Game state * @param game OnlineTurnBasedGame state
* @param aiPlayer AI's player index * @param aiPlayer AI's player index
* @return heuristic score * @return heuristic score
*/ */

View File

@@ -4,13 +4,13 @@ package org.toop.framework.gameFramework;
* Represents the current state of a turn-based game. * Represents the current state of a turn-based game.
*/ */
public enum GameState { public enum GameState {
/** Game is ongoing and no special condition applies. */ /** OnlineTurnBasedGame is ongoing and no special condition applies. */
NORMAL, NORMAL,
/** Game ended in a draw. */ /** OnlineTurnBasedGame ended in a draw. */
DRAW, DRAW,
/** Game ended with a win for a player. */ /** OnlineTurnBasedGame ended with a win for a player. */
WIN, WIN,
/** Next player's turn was skipped. */ /** Next player's turn was skipped. */

View File

@@ -1,8 +1,22 @@
package org.toop.framework.networking.server; package org.toop.framework.networking.server;
public interface GameServer { import java.util.List;
// List<?> gameTypes();
// List<?> ongoingGames(); public interface GameServer<GAMETYPE, CLIENT, CHALLENGEIDTYPE> {
// void startGame(String gameType, NettyClient... users); void startGame(String gameType, CLIENT... clients);
// String[] onlineUsers();
void addClient(CLIENT client);
void removeClient(CLIENT client);
void challengeClient(String fromClientName, String toClientName, String gameTypeKey);
void acceptChallenge(CHALLENGEIDTYPE challengeId);
void subscribeClient(String clientName, String gameTypeKey);
void unsubscribeClient(String clientName);
List<String> gameTypes();
List<OnlineGame<GAMETYPE>> ongoingGames();
List<CLIENT> onlineUsers();
void shutdown();
} }

View File

@@ -5,14 +5,14 @@ import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.networking.server.client.NettyClient; import org.toop.framework.networking.server.client.NettyClient;
public class Game implements OnlineGame<TurnBasedGame> { public class OnlineTurnBasedGame implements OnlineGame<TurnBasedGame> {
private long id; private long id;
private NettyClient[] clients; private NettyClient[] clients;
private TurnBasedGame game; private TurnBasedGame game;
private ServerThreadBehaviour gameThread; private ServerThreadBehaviour gameThread;
public Game(TurnBasedGame game, NettyClient... clients) { public OnlineTurnBasedGame(TurnBasedGame game, NettyClient... clients) {
this.game = game; this.game = game;
this.gameThread = new ServerThreadBehaviour( this.gameThread = new ServerThreadBehaviour(
game, game,
@@ -47,7 +47,7 @@ public class Game implements OnlineGame<TurnBasedGame> {
} }
@Override @Override
public TurnBasedGame game() { public org.toop.framework.gameFramework.model.game.TurnBasedGame game() {
return game; return game;
} }

View File

@@ -10,19 +10,19 @@ import org.toop.framework.networking.server.stores.TurnBasedGameStore;
import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore; import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore;
import org.toop.framework.utils.ImmutablePair; import org.toop.framework.utils.ImmutablePair;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.time.Duration; import java.time.Duration;
public class Server implements GameServer { public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
final private TurnBasedGameTypeStore gameTypesStore; final private TurnBasedGameTypeStore gameTypesStore;
final private ClientStore<Long, NettyClient> clientStore; final private ClientStore<Long, NettyClient> clientStore;
final private List<GameChallenge> gameChallenges = new CopyOnWriteArrayList<>(); final private List<GameChallenge> gameChallenges = new CopyOnWriteArrayList<>();
final private TurnBasedGameStore gameStore; final private TurnBasedGameStore gameStore;
final private ConcurrentHashMap<String, List<String>> subscriptions; // TODO move to own store / manager
final private Duration challengeDuration; final private Duration challengeDuration;
final private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); final private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@@ -37,40 +37,33 @@ public class Server implements GameServer {
this.challengeDuration = challengeDuration; this.challengeDuration = challengeDuration;
this.clientStore = clientStore; this.clientStore = clientStore;
this.gameStore = gameStore; this.gameStore = gameStore;
this.subscriptions = new ConcurrentHashMap<>();
scheduler.schedule(this::serverTask, 0, TimeUnit.MILLISECONDS); scheduler.schedule(this::serverTask, 0, TimeUnit.MILLISECONDS);
} }
private void serverTask() { @Override
checkChallenges(); public void addClient(NettyClient client) {
scheduler.schedule(this::serverTask, 500, TimeUnit.MILLISECONDS);
}
public void addUser(NettyClient client) {
clientStore.add(client); clientStore.add(client);
} }
public void removeUser(NettyClient client) { @Override
public void removeClient(NettyClient client) {
clientStore.remove(client.id()); clientStore.remove(client.id());
} }
@Override
public List<String> gameTypes() { public List<String> gameTypes() {
return new ArrayList<>(gameTypesStore.all().keySet()); return new ArrayList<>(gameTypesStore.all().keySet());
} }
@Override
public List<OnlineGame<TurnBasedGame>> ongoingGames() { public List<OnlineGame<TurnBasedGame>> ongoingGames() {
return gameStore.all().stream().toList(); return gameStore.all().stream().toList();
} }
public NettyClient getUser(String username) { @Override
return clientStore.all().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().orElse(null); public void challengeClient(String fromUser, String toUser, String gameType) {
}
public NettyClient getUser(long id) {
return clientStore.get(id);
}
public void challengeUser(String fromUser, String toUser, String gameType) {
NettyClient from = getUser(fromUser); NettyClient from = getUser(fromUser);
if (from == null) { if (from == null) {
@@ -104,44 +97,8 @@ public class Server implements GameServer {
gameChallenges.addLast(ch); gameChallenges.addLast(ch);
} }
private void warnUserExpiredChallenge(NettyClient client, long challengeId) { @Override
client.send("SVR GAME CHALLENGE CANCELLED {CHALLENGENUMBER: \"" + challengeId + "\"}" + "\n"); public void acceptChallenge(Long challengeId) {
}
private boolean isValidChallenge(GameChallenge gameChallenge) {
for (var user : gameChallenge.getUsers()) {
if (clientStore.get(user.id()) == null) {
return false;
}
if (user.game() != null) {
return false;
}
if (gameChallenge.isExpired()) {
return false;
}
}
return true;
}
public void checkChallenges() {
for (int i = gameChallenges.size() - 1; i >= 0; i--) {
var challenge = gameChallenges.get(i);
if (isValidChallenge(challenge)) continue;
if (challenge.isExpired()) {
if (!challenge.isChallengeAccepted()) Arrays.stream(challenge.getUsers())
.forEach(user -> warnUserExpiredChallenge(user, challenge.id()));
gameChallenges.remove(i);
}
}
}
public void acceptChallenge(long challengeId) {
for (var challenge : gameChallenges) { for (var challenge : gameChallenges) {
if (challenge.id() == challengeId) { if (challenge.id() == challengeId) {
startGame(challenge.acceptChallenge(), challenge.getUsers()); startGame(challenge.acceptChallenge(), challenge.getUsers());
@@ -150,22 +107,36 @@ public class Server implements GameServer {
} }
} }
public List<GameChallenge> gameChallenges() { @Override
return gameChallenges; public void subscribeClient(String clientName, String gameTypeKey) {
if (!gameTypesStore.all().containsKey(gameTypeKey)) return;
subscriptions.forEach((_, clientNames) -> clientNames.remove(clientName));
subscriptions.computeIfAbsent(
gameTypeKey,
_ -> new ArrayList<>())
.add(clientName);
} }
@Override
public void unsubscribeClient(String clientName) {
subscriptions.forEach((_, clientNames) -> clientNames.remove(clientName));
}
@Override
public void startGame(String gameType, NettyClient... clients) { public void startGame(String gameType, NettyClient... clients) {
if (!gameTypesStore.all().containsKey(gameType)) return; if (!gameTypesStore.all().containsKey(gameType)) return;
try { try {
ServerPlayer[] players = new ServerPlayer[clients.length]; ServerPlayer[] players = new ServerPlayer[clients.length];
var game = new Game(gameTypesStore.create(gameType), clients); var game = new OnlineTurnBasedGame(gameTypesStore.create(gameType), clients);
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]);
clients[i].addGame(new ImmutablePair<>(game, players[i])); clients[i].addGame(new ImmutablePair<>(game, players[i]));
} }
System.out.println("Starting Game"); System.out.println("Starting OnlineTurnBasedGame");
game.game().init(players); game.game().init(players);
gameStore.add(game); gameStore.add(game);
@@ -182,12 +153,91 @@ public class Server implements GameServer {
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }
@Override
public List<NettyClient> onlineUsers() { public List<NettyClient> onlineUsers() {
return clientStore.all().stream().toList(); return clientStore.all().stream().toList();
} }
public void closeServer() { @Override
public void shutdown() {
scheduler.shutdown(); scheduler.shutdown();
gameChallenges.clear(); gameChallenges.clear();
} }
private void serverTask() {
checkChallenges();
checkSubscriptions();
scheduler.schedule(this::serverTask, 500, TimeUnit.MILLISECONDS);
}
private void checkChallenges() {
for (int i = gameChallenges.size() - 1; i >= 0; i--) {
var challenge = gameChallenges.get(i);
if (isValidChallenge(challenge)) continue;
if (challenge.isExpired()) {
if (!challenge.isChallengeAccepted()) Arrays.stream(challenge.getUsers())
.forEach(user -> warnUserExpiredChallenge(user, challenge.id()));
gameChallenges.remove(i);
}
}
}
private void checkSubscriptions() {
if (subscriptions.isEmpty()) return;
List<String> keys = subscriptions.keySet().stream().toList();
for (String key : keys) {
var userNames = subscriptions.get(key);
if (userNames.size() < 2) continue;
Random ran = new Random();
while (userNames.size() > 1) {
var left = ran.nextInt(userNames.size());
var right = ran.nextInt(userNames.size());
while (left == right) left = ran.nextInt(userNames.size());
subscriptions.get(key).remove(userNames.get(left));
subscriptions.get(key).remove(userNames.get(right));
startGame(key, getUser(left), getUser(right));
}
}
}
private NettyClient getUser(String username) {
return clientStore.all().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().orElse(null);
}
private NettyClient getUser(long id) {
return clientStore.get(id);
}
private void warnUserExpiredChallenge(NettyClient client, long challengeId) {
client.send("SVR GAME CHALLENGE CANCELLED {CHALLENGENUMBER: \"" + challengeId + "\"}" + "\n");
}
private boolean isValidChallenge(GameChallenge gameChallenge) { // TODO move to challenge class
for (var user : gameChallenge.getUsers()) {
if (clientStore.get(user.id()) == null) {
return false;
}
if (user.game() != null) {
return false;
}
if (gameChallenge.isExpired()) {
return false;
}
}
return true;
}
} }

View File

@@ -1,6 +1,6 @@
package org.toop.framework.networking.server.client; package org.toop.framework.networking.server.client;
import org.toop.framework.networking.server.Game; import org.toop.framework.networking.server.OnlineTurnBasedGame;
import org.toop.framework.utils.Pair; import org.toop.framework.utils.Pair;
public interface Client<G, P> { public interface Client<G, P> {
@@ -9,7 +9,7 @@ public interface Client<G, P> {
String name(); String name();
void setName(String name); void setName(String name);
Game game(); OnlineTurnBasedGame game();
P player(); P player();
void addGame(Pair<G, P> gamePair); void addGame(Pair<G, P> gamePair);

View File

@@ -2,14 +2,14 @@ package org.toop.framework.networking.server.client;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import org.toop.framework.game.players.ServerPlayer; import org.toop.framework.game.players.ServerPlayer;
import org.toop.framework.networking.server.Game; import org.toop.framework.networking.server.OnlineTurnBasedGame;
import org.toop.framework.utils.Pair; import org.toop.framework.utils.Pair;
public class NettyClient implements Client<Game, ServerPlayer> { public class NettyClient implements Client<OnlineTurnBasedGame, ServerPlayer> {
final private long id; final private long id;
private ChannelHandlerContext ctx; private ChannelHandlerContext ctx;
private String name; private String name;
private Pair<Game, ServerPlayer> gamePair; private Pair<OnlineTurnBasedGame, ServerPlayer> gamePair;
public NettyClient(long userId, String name) { public NettyClient(long userId, String name) {
this.id = userId; this.id = userId;
@@ -27,7 +27,7 @@ public class NettyClient implements Client<Game, ServerPlayer> {
} }
@Override @Override
public void addGame(Pair<Game, ServerPlayer> gamePair) { public void addGame(Pair<OnlineTurnBasedGame, ServerPlayer> gamePair) {
if (this.gamePair == null) { if (this.gamePair == null) {
this.gamePair = gamePair; this.gamePair = gamePair;
} }
@@ -39,7 +39,7 @@ public class NettyClient implements Client<Game, ServerPlayer> {
} }
@Override @Override
public Game game() { public OnlineTurnBasedGame game() {
if (this.gamePair == null) { if (this.gamePair == null) {
return null; return null;
} }

View File

@@ -3,7 +3,7 @@ package org.toop.framework.networking.server.connectionHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import org.toop.framework.game.players.ServerPlayer; import org.toop.framework.game.players.ServerPlayer;
import org.toop.framework.networking.server.Game; import org.toop.framework.networking.server.OnlineTurnBasedGame;
import org.toop.framework.networking.server.client.NettyClient; import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.handlers.Handler; import org.toop.framework.networking.server.handlers.Handler;
import org.toop.framework.networking.server.parsing.ParsedMessage; import org.toop.framework.networking.server.parsing.ParsedMessage;
@@ -13,7 +13,7 @@ import org.toop.framework.networking.server.parsing.Parser;
import java.util.Arrays; import java.util.Arrays;
public class NettyClientSession extends SimpleChannelInboundHandler<String> implements ClientSession<Game, ServerPlayer> { public class NettyClientSession extends SimpleChannelInboundHandler<String> implements ClientSession<OnlineTurnBasedGame, ServerPlayer> {
private final NettyClient client; private final NettyClient client;
private final Server server; private final Server server;
@@ -26,7 +26,7 @@ public class NettyClientSession extends SimpleChannelInboundHandler<String> impl
} }
@Override @Override
public Client<Game, ServerPlayer> client() { public Client<OnlineTurnBasedGame, ServerPlayer> client() {
return client; return client;
} }
@@ -35,7 +35,7 @@ public class NettyClientSession extends SimpleChannelInboundHandler<String> impl
ctx.writeAndFlush("Welcome " + client.id() + " please login" + "\n"); ctx.writeAndFlush("Welcome " + client.id() + " please login" + "\n");
client.setCtx(ctx); client.setCtx(ctx);
server.addUser(client); // TODO set correct name on login server.addClient(client); // TODO set correct name on login
} }
@Override @Override
@@ -59,6 +59,6 @@ public class NettyClientSession extends SimpleChannelInboundHandler<String> impl
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) { public void channelInactive(ChannelHandlerContext ctx) {
server.removeUser(client); server.removeClient(client);
} }
} }

View File

@@ -1,7 +1,7 @@
package org.toop.framework.networking.server.handlers; package org.toop.framework.networking.server.handlers;
import org.toop.framework.game.players.ServerPlayer; import org.toop.framework.game.players.ServerPlayer;
import org.toop.framework.networking.server.Game; import org.toop.framework.networking.server.OnlineTurnBasedGame;
import org.toop.framework.networking.server.Server; import org.toop.framework.networking.server.Server;
import org.toop.framework.networking.server.client.Client; import org.toop.framework.networking.server.client.Client;
import org.toop.framework.networking.server.parsing.ParsedMessage; import org.toop.framework.networking.server.parsing.ParsedMessage;
@@ -10,9 +10,9 @@ import org.toop.framework.utils.Utils;
public class MessageHandler implements Handler<ParsedMessage> { public class MessageHandler implements Handler<ParsedMessage> {
private final Server server; private final Server server;
private final Client<Game, ServerPlayer> client; private final Client<OnlineTurnBasedGame, ServerPlayer> client;
public MessageHandler(Server server, Client<Game, ServerPlayer> client) { public MessageHandler(Server server, Client<OnlineTurnBasedGame, ServerPlayer> client) {
this.server = server; this.server = server;
this.client = client; this.client = client;
} }
@@ -37,25 +37,27 @@ public class MessageHandler implements Handler<ParsedMessage> {
return (args.length >= 1); return (args.length >= 1);
} }
private void handleLogin(ParsedMessage p, Client<Game, ServerPlayer> client) { private void handleLogin(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
if (!hasArgs(p.args())) return; if (!hasArgs(p.args())) return;
client.setName(p.args()[0]); client.setName(p.args()[0]);
} }
private void handleSubscribe(ParsedMessage p, Client<Game, ServerPlayer> client) { private void handleSubscribe(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
if (!hasArgs(p.args())) return;
server.subscribeClient(p.args()[0], client.name());
}
private void handleHelp(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
// TODO // TODO
} }
private void handleHelp(ParsedMessage p, Client<Game, ServerPlayer> client) { private void handleMessage(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
// TODO // TODO
} }
private void handleMessage(ParsedMessage p, Client<Game, ServerPlayer> client) { private void handleGet(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
// TODO
}
private void handleGet(ParsedMessage p, Client<Game, ServerPlayer> client) {
if (!hasArgs(p.args())) return; if (!hasArgs(p.args())) return;
switch (p.args()[0]) { switch (p.args()[0]) {
@@ -70,7 +72,7 @@ public class MessageHandler implements Handler<ParsedMessage> {
} }
} }
private void handleChallenge(ParsedMessage p, Client<Game, ServerPlayer> client) { private void handleChallenge(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
if (!hasArgs(p.args())) return; if (!hasArgs(p.args())) return;
if (p.args().length < 2) return; if (p.args().length < 2) return;
@@ -92,10 +94,10 @@ public class MessageHandler implements Handler<ParsedMessage> {
return; return;
} }
server.challengeUser(client.name(), p.args()[0], p.args()[1]); server.challengeClient(client.name(), p.args()[0], p.args()[1]);
} }
private void handleMove(ParsedMessage p, Client<Game, ServerPlayer> client) { private void handleMove(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
if(!hasArgs(p.args())) return; if(!hasArgs(p.args())) return;
// TODO check if not number // TODO check if not number

View File

@@ -1,7 +1,7 @@
package org.toop.framework.networking.server.stores; package org.toop.framework.networking.server.stores;
import org.toop.framework.game.players.ServerPlayer; import org.toop.framework.game.players.ServerPlayer;
import org.toop.framework.networking.server.Game; import org.toop.framework.networking.server.OnlineTurnBasedGame;
import org.toop.framework.networking.server.client.Client; import org.toop.framework.networking.server.client.Client;
public interface ClientStore<ID, T extends Client<Game, ServerPlayer>> extends Store<ID, T> {} public interface ClientStore<ID, T extends Client<OnlineTurnBasedGame, ServerPlayer>> extends Store<ID, T> {}

View File

@@ -4,7 +4,7 @@
//import org.junit.jupiter.api.BeforeEach; //import org.junit.jupiter.api.BeforeEach;
//import org.junit.jupiter.api.Test; //import org.junit.jupiter.api.Test;
//import org.toop.framework.gameFramework.model.game.PlayResult; //import org.toop.framework.gameFramework.model.game.PlayResult;
//import org.toop.framework.gameFramework.model.game.TurnBasedGame; //import org.toop.framework.gameFramework.model.game.OnlineTurnBasedGame;
//import org.toop.framework.gameFramework.model.player.Player; //import org.toop.framework.gameFramework.model.player.Player;
// //
//import java.time.Duration; //import java.time.Duration;
@@ -13,7 +13,7 @@
// //
//public class ServerTest { //public class ServerTest {
// //
// static class TurnBasedGameMock implements TurnBasedGame { // static class TurnBasedGameMock implements OnlineTurnBasedGame {
// private Player[] players; // private Player[] players;
// //
// public TurnBasedGameMock() {} // public TurnBasedGameMock() {}
@@ -44,7 +44,7 @@
// } // }
// //
// @Override // @Override
// public TurnBasedGame deepCopy() { // public OnlineTurnBasedGame deepCopy() {
// return null; // return null;
// } // }
// //
@@ -87,17 +87,17 @@
// } // }
// //
// @Override // @Override
// public Game[] games() { // public OnlineTurnBasedGame[] games() {
// return new Game[0]; // return new OnlineTurnBasedGame[0];
// } // }
// //
// @Override // @Override
// public void addGame(Game game) { // public void addGame(OnlineTurnBasedGame game) {
// //
// } // }
// //
// @Override // @Override
// public void removeGame(Game game) { // public void removeGame(OnlineTurnBasedGame game) {
// //
// } // }
// //
@@ -118,7 +118,7 @@
// @BeforeEach // @BeforeEach
// void setup() { // void setup() {
// //
// var games = new ConcurrentHashMap<String, Class<? extends TurnBasedGame>>(); // var games = new ConcurrentHashMap<String, Class<? extends OnlineTurnBasedGame>>();
// games.put("tictactoe", TurnBasedGameMock.class); // games.put("tictactoe", TurnBasedGameMock.class);
// games.put("reversi", TurnBasedGameMock.class); // games.put("reversi", TurnBasedGameMock.class);
// //