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){
logger.info("Game Finished");
logger.info("OnlineTurnBasedGame Finished");
String name = event.winner() == -1 ? null : getPlayer(event.winner()).getName();
gameView.gameOver(event.winOrTie(), name);
stop();

View File

@@ -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
*/

View File

@@ -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. */

View File

@@ -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<GAMETYPE, CLIENT, CHALLENGEIDTYPE> {
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<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.networking.server.client.NettyClient;
public class Game implements OnlineGame<TurnBasedGame> {
public class OnlineTurnBasedGame implements OnlineGame<TurnBasedGame> {
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<TurnBasedGame> {
}
@Override
public TurnBasedGame game() {
public org.toop.framework.gameFramework.model.game.TurnBasedGame 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.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<TurnBasedGame, NettyClient, Long> {
final private TurnBasedGameTypeStore gameTypesStore;
final private ClientStore<Long, NettyClient> clientStore;
final private List<GameChallenge> gameChallenges = new CopyOnWriteArrayList<>();
final private TurnBasedGameStore gameStore;
final private ConcurrentHashMap<String, List<String>> 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<String> gameTypes() {
return new ArrayList<>(gameTypesStore.all().keySet());
}
@Override
public List<OnlineGame<TurnBasedGame>> 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<GameChallenge> 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<NettyClient> 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<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;
import org.toop.framework.networking.server.Game;
import org.toop.framework.networking.server.OnlineTurnBasedGame;
import org.toop.framework.utils.Pair;
public interface Client<G, P> {
@@ -9,7 +9,7 @@ public interface Client<G, P> {
String name();
void setName(String name);
Game game();
OnlineTurnBasedGame game();
P player();
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 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<Game, ServerPlayer> {
public class NettyClient implements Client<OnlineTurnBasedGame, ServerPlayer> {
final private long id;
private ChannelHandlerContext ctx;
private String name;
private Pair<Game, ServerPlayer> gamePair;
private Pair<OnlineTurnBasedGame, ServerPlayer> gamePair;
public NettyClient(long userId, String name) {
this.id = userId;
@@ -27,7 +27,7 @@ public class NettyClient implements Client<Game, ServerPlayer> {
}
@Override
public void addGame(Pair<Game, ServerPlayer> gamePair) {
public void addGame(Pair<OnlineTurnBasedGame, ServerPlayer> gamePair) {
if (this.gamePair == null) {
this.gamePair = gamePair;
}
@@ -39,7 +39,7 @@ public class NettyClient implements Client<Game, ServerPlayer> {
}
@Override
public Game game() {
public OnlineTurnBasedGame game() {
if (this.gamePair == 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.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<String> implements ClientSession<Game, ServerPlayer> {
public class NettyClientSession extends SimpleChannelInboundHandler<String> implements ClientSession<OnlineTurnBasedGame, ServerPlayer> {
private final NettyClient client;
private final Server server;
@@ -26,7 +26,7 @@ public class NettyClientSession extends SimpleChannelInboundHandler<String> impl
}
@Override
public Client<Game, ServerPlayer> client() {
public Client<OnlineTurnBasedGame, ServerPlayer> client() {
return client;
}
@@ -35,7 +35,7 @@ public class NettyClientSession extends SimpleChannelInboundHandler<String> 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<String> impl
@Override
public void channelInactive(ChannelHandlerContext ctx) {
server.removeUser(client);
server.removeClient(client);
}
}

View File

@@ -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<ParsedMessage> {
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.client = client;
}
@@ -37,25 +37,27 @@ public class MessageHandler implements Handler<ParsedMessage> {
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;
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
}
private void handleHelp(ParsedMessage p, Client<Game, ServerPlayer> client) {
private void handleMessage(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
// TODO
}
private void handleMessage(ParsedMessage p, Client<Game, ServerPlayer> client) {
// TODO
}
private void handleGet(ParsedMessage p, Client<Game, ServerPlayer> client) {
private void handleGet(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
if (!hasArgs(p.args())) return;
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 (p.args().length < 2) return;
@@ -92,10 +94,10 @@ public class MessageHandler implements Handler<ParsedMessage> {
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;
// TODO check if not number

View File

@@ -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<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.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<String, Class<? extends TurnBasedGame>>();
// var games = new ConcurrentHashMap<String, Class<? extends OnlineTurnBasedGame>>();
// games.put("tictactoe", TurnBasedGameMock.class);
// games.put("reversi", TurnBasedGameMock.class);
//