diff --git a/app/src/main/java/org/toop/app/gameControllers/GenericGameController.java b/app/src/main/java/org/toop/app/gameControllers/GenericGameController.java index 37429a0..1b20204 100644 --- a/app/src/main/java/org/toop/app/gameControllers/GenericGameController.java +++ b/app/src/main/java/org/toop/app/gameControllers/GenericGameController.java @@ -93,7 +93,7 @@ public class GenericGameController implements GameController { } private void onGameFinish(GUIEvents.GameEnded event){ - logger.info("Game Finished"); + logger.info("OnlineTurnBasedGame Finished"); String name = event.winner() == -1 ? null : getPlayer(event.winner()).getName(); gameView.gameOver(event.winOrTie(), name); stop(); diff --git a/framework/src/main/java/org/toop/framework/game/players/MiniMaxAI.java b/framework/src/main/java/org/toop/framework/game/players/MiniMaxAI.java index 972ddea..666d432 100644 --- a/framework/src/main/java/org/toop/framework/game/players/MiniMaxAI.java +++ b/framework/src/main/java/org/toop/framework/game/players/MiniMaxAI.java @@ -126,7 +126,7 @@ public class MiniMaxAI extends AbstractAI { * Simple heuristic evaluation for Reversi-like games. * Positive = good for AI, Negative = good for opponent. * - * @param game Game state + * @param game OnlineTurnBasedGame state * @param aiPlayer AI's player index * @return heuristic score */ diff --git a/framework/src/main/java/org/toop/framework/gameFramework/GameState.java b/framework/src/main/java/org/toop/framework/gameFramework/GameState.java index bc5a2bf..df802fe 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/GameState.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/GameState.java @@ -4,13 +4,13 @@ package org.toop.framework.gameFramework; * Represents the current state of a turn-based game. */ public enum GameState { - /** Game is ongoing and no special condition applies. */ + /** OnlineTurnBasedGame is ongoing and no special condition applies. */ NORMAL, - /** Game ended in a draw. */ + /** OnlineTurnBasedGame ended in a draw. */ DRAW, - /** Game ended with a win for a player. */ + /** OnlineTurnBasedGame ended with a win for a player. */ WIN, /** Next player's turn was skipped. */ 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 00993a0..37a4905 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 @@ -1,8 +1,22 @@ package org.toop.framework.networking.server; -public interface GameServer { -// List gameTypes(); -// List ongoingGames(); -// void startGame(String gameType, NettyClient... users); -// String[] onlineUsers(); +import java.util.List; + +public interface GameServer { + void startGame(String gameType, CLIENT... clients); + + 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 gameTypes(); + List> ongoingGames(); + + List onlineUsers(); + void shutdown(); } diff --git a/framework/src/main/java/org/toop/framework/networking/server/Game.java b/framework/src/main/java/org/toop/framework/networking/server/OnlineTurnBasedGame.java similarity index 89% rename from framework/src/main/java/org/toop/framework/networking/server/Game.java rename to framework/src/main/java/org/toop/framework/networking/server/OnlineTurnBasedGame.java index 3ac78b8..3248922 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/Game.java +++ b/framework/src/main/java/org/toop/framework/networking/server/OnlineTurnBasedGame.java @@ -5,14 +5,14 @@ import org.toop.framework.gameFramework.GameState; import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.networking.server.client.NettyClient; -public class Game implements OnlineGame { +public class OnlineTurnBasedGame implements OnlineGame { private long id; private NettyClient[] clients; private TurnBasedGame game; private ServerThreadBehaviour gameThread; - public Game(TurnBasedGame game, NettyClient... clients) { + public OnlineTurnBasedGame(TurnBasedGame game, NettyClient... clients) { this.game = game; this.gameThread = new ServerThreadBehaviour( game, @@ -47,7 +47,7 @@ public class Game implements OnlineGame { } @Override - public TurnBasedGame game() { + public org.toop.framework.gameFramework.model.game.TurnBasedGame game() { return game; } 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 cb4f543..6b82050 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 @@ -10,19 +10,19 @@ import org.toop.framework.networking.server.stores.TurnBasedGameStore; import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore; import org.toop.framework.utils.ImmutablePair; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; import java.util.concurrent.*; import java.time.Duration; -public class Server implements GameServer { +public class Server implements GameServer { final private TurnBasedGameTypeStore gameTypesStore; final private ClientStore clientStore; final private List gameChallenges = new CopyOnWriteArrayList<>(); final private TurnBasedGameStore gameStore; + final private ConcurrentHashMap> subscriptions; // TODO move to own store / manager + final private Duration challengeDuration; final private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); @@ -37,40 +37,33 @@ public class Server implements GameServer { this.challengeDuration = challengeDuration; this.clientStore = clientStore; this.gameStore = gameStore; + this.subscriptions = new ConcurrentHashMap<>(); scheduler.schedule(this::serverTask, 0, TimeUnit.MILLISECONDS); } - private void serverTask() { - checkChallenges(); - scheduler.schedule(this::serverTask, 500, TimeUnit.MILLISECONDS); - } - - public void addUser(NettyClient client) { + @Override + public void addClient(NettyClient client) { clientStore.add(client); } - public void removeUser(NettyClient client) { + @Override + public void removeClient(NettyClient client) { clientStore.remove(client.id()); } + @Override public List gameTypes() { return new ArrayList<>(gameTypesStore.all().keySet()); } + @Override public List> ongoingGames() { return gameStore.all().stream().toList(); } - public NettyClient getUser(String username) { - return clientStore.all().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().orElse(null); - } - - public NettyClient getUser(long id) { - return clientStore.get(id); - } - - public void challengeUser(String fromUser, String toUser, String gameType) { + @Override + public void challengeClient(String fromUser, String toUser, String gameType) { NettyClient from = getUser(fromUser); if (from == null) { @@ -104,44 +97,8 @@ public class Server implements GameServer { gameChallenges.addLast(ch); } - private void warnUserExpiredChallenge(NettyClient client, long challengeId) { - client.send("SVR GAME CHALLENGE CANCELLED {CHALLENGENUMBER: \"" + challengeId + "\"}" + "\n"); - } - - 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) { + @Override + public void acceptChallenge(Long challengeId) { for (var challenge : gameChallenges) { if (challenge.id() == challengeId) { startGame(challenge.acceptChallenge(), challenge.getUsers()); @@ -150,22 +107,36 @@ public class Server implements GameServer { } } - public List gameChallenges() { - return gameChallenges; + @Override + 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) { if (!gameTypesStore.all().containsKey(gameType)) return; try { 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++) { players[i] = new ServerPlayer(clients[i]); clients[i].addGame(new ImmutablePair<>(game, players[i])); } - System.out.println("Starting Game"); + System.out.println("Starting OnlineTurnBasedGame"); game.game().init(players); gameStore.add(game); @@ -182,12 +153,91 @@ public class Server implements GameServer { } catch (Exception ignored) {} } + @Override public List onlineUsers() { return clientStore.all().stream().toList(); } - public void closeServer() { + @Override + public void shutdown() { scheduler.shutdown(); 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 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; + } } diff --git a/framework/src/main/java/org/toop/framework/networking/server/client/Client.java b/framework/src/main/java/org/toop/framework/networking/server/client/Client.java index 96ea515..cf97318 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/client/Client.java +++ b/framework/src/main/java/org/toop/framework/networking/server/client/Client.java @@ -1,6 +1,6 @@ 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; public interface Client { @@ -9,7 +9,7 @@ public interface Client { String name(); void setName(String name); - Game game(); + OnlineTurnBasedGame game(); P player(); void addGame(Pair gamePair); diff --git a/framework/src/main/java/org/toop/framework/networking/server/client/NettyClient.java b/framework/src/main/java/org/toop/framework/networking/server/client/NettyClient.java index 37eeb4b..22b5254 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/client/NettyClient.java +++ b/framework/src/main/java/org/toop/framework/networking/server/client/NettyClient.java @@ -2,14 +2,14 @@ package org.toop.framework.networking.server.client; import io.netty.channel.ChannelHandlerContext; 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; -public class NettyClient implements Client { +public class NettyClient implements Client { final private long id; private ChannelHandlerContext ctx; private String name; - private Pair gamePair; + private Pair gamePair; public NettyClient(long userId, String name) { this.id = userId; @@ -27,7 +27,7 @@ public class NettyClient implements Client { } @Override - public void addGame(Pair gamePair) { + public void addGame(Pair gamePair) { if (this.gamePair == null) { this.gamePair = gamePair; } @@ -39,7 +39,7 @@ public class NettyClient implements Client { } @Override - public Game game() { + public OnlineTurnBasedGame game() { if (this.gamePair == null) { return null; } diff --git a/framework/src/main/java/org/toop/framework/networking/server/connectionHandler/NettyClientSession.java b/framework/src/main/java/org/toop/framework/networking/server/connectionHandler/NettyClientSession.java index ae1f071..d9df676 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/connectionHandler/NettyClientSession.java +++ b/framework/src/main/java/org/toop/framework/networking/server/connectionHandler/NettyClientSession.java @@ -3,7 +3,7 @@ package org.toop.framework.networking.server.connectionHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; 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.handlers.Handler; import org.toop.framework.networking.server.parsing.ParsedMessage; @@ -13,7 +13,7 @@ import org.toop.framework.networking.server.parsing.Parser; import java.util.Arrays; -public class NettyClientSession extends SimpleChannelInboundHandler implements ClientSession { +public class NettyClientSession extends SimpleChannelInboundHandler implements ClientSession { private final NettyClient client; private final Server server; @@ -26,7 +26,7 @@ public class NettyClientSession extends SimpleChannelInboundHandler impl } @Override - public Client client() { + public Client client() { return client; } @@ -35,7 +35,7 @@ public class NettyClientSession extends SimpleChannelInboundHandler impl ctx.writeAndFlush("Welcome " + client.id() + " please login" + "\n"); client.setCtx(ctx); - server.addUser(client); // TODO set correct name on login + server.addClient(client); // TODO set correct name on login } @Override @@ -59,6 +59,6 @@ public class NettyClientSession extends SimpleChannelInboundHandler impl @Override public void channelInactive(ChannelHandlerContext ctx) { - server.removeUser(client); + server.removeClient(client); } } diff --git a/framework/src/main/java/org/toop/framework/networking/server/handlers/MessageHandler.java b/framework/src/main/java/org/toop/framework/networking/server/handlers/MessageHandler.java index 28bc24a..b6da22c 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/handlers/MessageHandler.java +++ b/framework/src/main/java/org/toop/framework/networking/server/handlers/MessageHandler.java @@ -1,7 +1,7 @@ package org.toop.framework.networking.server.handlers; 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.client.Client; import org.toop.framework.networking.server.parsing.ParsedMessage; @@ -10,9 +10,9 @@ import org.toop.framework.utils.Utils; public class MessageHandler implements Handler { private final Server server; - private final Client client; + private final Client client; - public MessageHandler(Server server, Client client) { + public MessageHandler(Server server, Client client) { this.server = server; this.client = client; } @@ -37,25 +37,27 @@ public class MessageHandler implements Handler { return (args.length >= 1); } - private void handleLogin(ParsedMessage p, Client client) { + private void handleLogin(ParsedMessage p, Client client) { if (!hasArgs(p.args())) return; client.setName(p.args()[0]); } - private void handleSubscribe(ParsedMessage p, Client client) { + private void handleSubscribe(ParsedMessage p, Client client) { + if (!hasArgs(p.args())) return; + + server.subscribeClient(p.args()[0], client.name()); + } + + private void handleHelp(ParsedMessage p, Client client) { // TODO } - private void handleHelp(ParsedMessage p, Client client) { + private void handleMessage(ParsedMessage p, Client client) { // TODO } - private void handleMessage(ParsedMessage p, Client client) { - // TODO - } - - private void handleGet(ParsedMessage p, Client client) { + private void handleGet(ParsedMessage p, Client client) { if (!hasArgs(p.args())) return; switch (p.args()[0]) { @@ -70,7 +72,7 @@ public class MessageHandler implements Handler { } } - private void handleChallenge(ParsedMessage p, Client client) { + private void handleChallenge(ParsedMessage p, Client client) { if (!hasArgs(p.args())) return; if (p.args().length < 2) return; @@ -92,10 +94,10 @@ public class MessageHandler implements Handler { 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 client) { + private void handleMove(ParsedMessage p, Client client) { if(!hasArgs(p.args())) return; // TODO check if not number diff --git a/framework/src/main/java/org/toop/framework/networking/server/stores/ClientStore.java b/framework/src/main/java/org/toop/framework/networking/server/stores/ClientStore.java index 9d13127..15fbaae 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/stores/ClientStore.java +++ b/framework/src/main/java/org/toop/framework/networking/server/stores/ClientStore.java @@ -1,7 +1,7 @@ package org.toop.framework.networking.server.stores; 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; -public interface ClientStore> extends Store {} +public interface ClientStore> extends Store {} diff --git a/framework/src/test/java/org/toop/framework/networking/server/ServerTest.java b/framework/src/test/java/org/toop/framework/networking/server/ServerTest.java index 6529fd3..447a11d 100644 --- a/framework/src/test/java/org/toop/framework/networking/server/ServerTest.java +++ b/framework/src/test/java/org/toop/framework/networking/server/ServerTest.java @@ -4,7 +4,7 @@ //import org.junit.jupiter.api.BeforeEach; //import org.junit.jupiter.api.Test; //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 java.time.Duration; @@ -13,7 +13,7 @@ // //public class ServerTest { // -// static class TurnBasedGameMock implements TurnBasedGame { +// static class TurnBasedGameMock implements OnlineTurnBasedGame { // private Player[] players; // // public TurnBasedGameMock() {} @@ -44,7 +44,7 @@ // } // // @Override -// public TurnBasedGame deepCopy() { +// public OnlineTurnBasedGame deepCopy() { // return null; // } // @@ -87,17 +87,17 @@ // } // // @Override -// public Game[] games() { -// return new Game[0]; +// public OnlineTurnBasedGame[] games() { +// return new OnlineTurnBasedGame[0]; // } // // @Override -// public void addGame(Game game) { +// public void addGame(OnlineTurnBasedGame game) { // // } // // @Override -// public void removeGame(Game game) { +// public void removeGame(OnlineTurnBasedGame game) { // // } // @@ -118,7 +118,7 @@ // @BeforeEach // void setup() { // -// var games = new ConcurrentHashMap>(); +// var games = new ConcurrentHashMap>(); // games.put("tictactoe", TurnBasedGameMock.class); // games.put("reversi", TurnBasedGameMock.class); //