From 4d31a8ed4488690cf5ca4fe1c64422c226ca27c3 Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:48:57 +0100 Subject: [PATCH] Working challenges --- app/src/main/java/org/toop/app/App.java | 16 +++- .../networking/server/GameChallenge.java | 23 +++++- .../framework/networking/server/Server.java | 80 +++++++++++++++++-- .../networking/server/ServerHandler.java | 23 +++++- .../networking/server/ServerUser.java | 6 +- .../framework/networking/server/User.java | 28 ++++++- .../networking/server/ServerTest.java | 26 +++++- .../networking/server/servertests.ps1 | 17 ++++ 8 files changed, 202 insertions(+), 17 deletions(-) create mode 100644 framework/src/test/java/org/toop/framework/networking/server/servertests.ps1 diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java index aa2e704..9245318 100644 --- a/app/src/main/java/org/toop/app/App.java +++ b/app/src/main/java/org/toop/app/App.java @@ -22,11 +22,13 @@ import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.GlobalEventBus; import org.toop.framework.game.BitboardGame; import org.toop.framework.game.games.reversi.BitboardReversi; +import org.toop.framework.game.games.tictactoe.BitboardTicTacToe; import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.networking.connection.NetworkingClientEventListener; import org.toop.framework.networking.connection.NetworkingClientManager; import org.toop.framework.networking.server.GameDefinition; import org.toop.framework.networking.server.MasterServer; +import org.toop.framework.networking.server.Server; import org.toop.framework.resource.ResourceLoader; import org.toop.framework.resource.ResourceManager; import org.toop.framework.resource.events.AssetLoaderEvents; @@ -36,6 +38,7 @@ import org.toop.framework.resource.resources.SoundEffectAsset; import org.toop.local.AppContext; import org.toop.local.AppSettings; +import java.time.Duration; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; @@ -98,7 +101,18 @@ public final class App extends Application { WidgetContainer.setCurrentView(loading); - setOnLoadingSuccess(loading); + var games = new ConcurrentHashMap>(); + games.put("tictactoe", BitboardTicTacToe.class); + games.put("reversi", BitboardReversi.class); + + var a = new MasterServer(6666, games, Duration.ofSeconds(5)); + try { + a.start(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + setOnLoadingSuccess(loading); EventFlow loadingFlow = new EventFlow(); diff --git a/framework/src/main/java/org/toop/framework/networking/server/GameChallenge.java b/framework/src/main/java/org/toop/framework/networking/server/GameChallenge.java index ba71f89..0c936a5 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/GameChallenge.java +++ b/framework/src/main/java/org/toop/framework/networking/server/GameChallenge.java @@ -1,21 +1,40 @@ package org.toop.framework.networking.server; +import org.toop.framework.SnowflakeGenerator; + public class GameChallenge { + private final long id = SnowflakeGenerator.nextId(); // I don't need this, but the tournament server uses it... + private final ServerUser from; private final ServerUser to; + private final String gameType; private final SimpleTimer timer; private boolean isChallengeAccepted = false; - public GameChallenge(ServerUser from, ServerUser to, SimpleTimer timer) { + public GameChallenge(ServerUser from, ServerUser to, String gameType, SimpleTimer timer) { this.from = from; this.to = to; + this.gameType = gameType; this.timer = timer; } - public void acceptChallenge() { + public long id() { + return id; + } + + public ServerUser[] getUsers() { + return new ServerUser[]{from, to}; + } + + public void forceExpire() { + timer.forceExpire(); + } + + public String acceptChallenge() { isChallengeAccepted = true; timer.forceExpire(); + return gameType; } public boolean isExpired() { 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 be209ca..80e7792 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 @@ -4,6 +4,7 @@ import org.toop.framework.game.players.LocalPlayer; import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.player.Player; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.*; @@ -48,29 +49,88 @@ public class Server implements GameServer { } public ServerUser getUser(String username) { - return users.values().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().get(); + return users.values().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().orElse(null); } public ServerUser getUser(long id) { return users.get(id); } - public void challengeUser(String fromUser, String toUser) { + public void challengeUser(String fromUser, String toUser, String gameType) { + ServerUser from = getUser(fromUser); if (from == null) { return; } - ServerUser to = getUser(toUser); - if (to == null) { + + if (!gameTypes.containsKey(gameType)) { + from.sendMessage("ERR gametype not found"); return; } - gameChallenges.addLast(new GameChallenge(from, to, new GameChallengeTimer(challengeDuration))); + ServerUser to = getUser(toUser); + if (to == null) { + from.sendMessage("ERR user not found"); + return; + } + + var ch = new GameChallenge(from, to, gameType, new GameChallengeTimer(challengeDuration)); + + to.sendMessage( + "\"SVR GAME CHALLENGE {CHALLENGER: \"%s\", GAMETYPE: \"%s\", CHALLENGENUMBER: \"%s\"}" + .formatted(from.name(), gameType, ch.id()) + ); + + if (!isValidChallenge(ch)) { + warnUserExpiredChallenge(from, ch.id()); + ch.forceExpire(); + return; + } + + gameChallenges.addLast(ch); + } + + private void warnUserExpiredChallenge(ServerUser user, long challengeId) { + user.sendMessage("SVR GAME CHALLENGE CANCELLED {CHALLENGENUMBER: \"" + challengeId + "\"}"); + } + + private boolean isValidChallenge(GameChallenge gameChallenge) { + for (var user : gameChallenge.getUsers()) { + if (users.get(user.id()) == null) { + return false; + } + + if (user.games().length > 0) { + return false; + } + + if (gameChallenge.isExpired()) { + return false; + } + } + + return true; } public void checkChallenges() { for (int i = gameChallenges.size() - 1; i >= 0; i--) { - if (gameChallenges.get(i).isExpired()) gameChallenges.remove(i); + var challenge = gameChallenges.get(i); + + if (isValidChallenge(challenge)) continue; + + if (challenge.isExpired()) { + Arrays.stream(challenge.getUsers()).forEach(user -> warnUserExpiredChallenge(user, challenge.id())); + gameChallenges.remove(i); + } + } + } + + public void acceptChallenge(long challengeId) { + for (var challenge : gameChallenges) { + if (challenge.id() == challengeId) { + startGame(challenge.acceptChallenge(), (User[]) challenge.getUsers()); + break; + } } } @@ -105,4 +165,12 @@ public class Server implements GameServer { public String[] onlineUsers() { return users.values().stream().map(ServerUser::name).toArray(String[]::new); } + + public void closeServer() { + scheduler.shutdown(); + gameChallenges.clear(); + games.clear(); + users.clear(); + gameTypes.clear(); + } } diff --git a/framework/src/main/java/org/toop/framework/networking/server/ServerHandler.java b/framework/src/main/java/org/toop/framework/networking/server/ServerHandler.java index f5da259..8357abb 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/ServerHandler.java +++ b/framework/src/main/java/org/toop/framework/networking/server/ServerHandler.java @@ -79,7 +79,28 @@ public class ServerHandler extends SimpleChannelInboundHandler { } private void handleChallenge(ParsedMessage p) { - // TODO + if(!allowedArgs(p.args())) return; + if (p.args().length < 2) return; + + if (p.args()[0].equalsIgnoreCase("accept")) { + try { + long id = Long.parseLong(p.args()[1]); + + if (id <= 0) { + user.sendMessage("ERR id must be a positive number"); + return; + } + + server.acceptChallenge(id); + + } catch (NumberFormatException e) { + user.sendMessage("ERR id is not a valid number or too big"); + return; + } + return; + } + + server.challengeUser(user.name(), p.args()[0], p.args()[1]); } private void handleMove(ParsedMessage p) { diff --git a/framework/src/main/java/org/toop/framework/networking/server/ServerUser.java b/framework/src/main/java/org/toop/framework/networking/server/ServerUser.java index 8e6e7a5..474c26d 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/ServerUser.java +++ b/framework/src/main/java/org/toop/framework/networking/server/ServerUser.java @@ -1,9 +1,11 @@ package org.toop.framework.networking.server; -import java.net.InetSocketAddress; - public interface ServerUser { long id(); String name(); + Game[] games(); + void addGame(Game game); + void removeGame(Game game); void setName(String name); + void sendMessage(String message); } diff --git a/framework/src/main/java/org/toop/framework/networking/server/User.java b/framework/src/main/java/org/toop/framework/networking/server/User.java index 8ae5026..9863b12 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/User.java +++ b/framework/src/main/java/org/toop/framework/networking/server/User.java @@ -2,11 +2,13 @@ package org.toop.framework.networking.server; import io.netty.channel.ChannelHandlerContext; -import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; public class User implements ServerUser { final private long id; private String name; + private final List games = new ArrayList<>(); private ChannelHandlerContext connectionContext; public User(long userId, String name) { @@ -24,11 +26,34 @@ public class User implements ServerUser { return name; } + + + @Override + public void addGame(Game game) { + games.add(game); + } + + @Override + public void removeGame(Game game) { + games.remove(game); + } + + @Override + public Game[] games() { + return games.toArray(new Game[0]); + } + @Override public void setName(String name) { this.name = name; } + @Override + public void sendMessage(String message) { + IO.println(message); + ctx().channel().writeAndFlush(message); + } + public ChannelHandlerContext ctx() { return connectionContext; } @@ -37,4 +62,5 @@ public class User implements ServerUser { this.connectionContext = ctx; } + } 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 6f663bb..e5dcd09 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 @@ -11,8 +11,6 @@ import java.time.Duration; import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; -import static org.junit.jupiter.api.Assertions.assertEquals; - public class ServerTest { static class TurnBasedGameMock implements TurnBasedGame { @@ -88,10 +86,30 @@ public class ServerTest { return name; } + @Override + public Game[] games() { + return new Game[0]; + } + + @Override + public void addGame(Game game) { + + } + + @Override + public void removeGame(Game game) { + + } + @Override public void setName(String name) { this.name = name; } + + @Override + public void sendMessage(String message) { + + } } private Server server; @@ -122,14 +140,14 @@ public class ServerTest { void testChallenge() { server.addUser(new TestUser(1, "test1")); server.addUser(new TestUser(2, "test2")); - server.challengeUser("test1", "test2"); + server.challengeUser("test1", "test2", "tictactoe"); IO.println(server.gameChallenges()); Assertions.assertEquals(1, server.gameChallenges().size()); try { - Thread.sleep(waitTime); + Thread.sleep(waitTime.plusMillis(100)); } catch (InterruptedException e) { throw new RuntimeException(e); } diff --git a/framework/src/test/java/org/toop/framework/networking/server/servertests.ps1 b/framework/src/test/java/org/toop/framework/networking/server/servertests.ps1 new file mode 100644 index 0000000..9edf089 --- /dev/null +++ b/framework/src/test/java/org/toop/framework/networking/server/servertests.ps1 @@ -0,0 +1,17 @@ +$client = New-Object System.Net.Sockets.TcpClient("localhost", 6666) +$stream = $client.GetStream() +$writer = New-Object System.IO.StreamWriter($stream) +$reader = New-Object System.IO.StreamReader($stream) + +$client2 = New-Object System.Net.Sockets.TcpClient("localhost", 6666) +$stream2 = $client2.GetStream() +$writer2 = New-Object System.IO.StreamWriter($stream2) +$reader2 = New-Object System.IO.StreamReader($stream2) + +$writer.WriteLine("login john") +$writer.Flush() +$reader.ReadLine() + +$writer2.WriteLine("login hendrik") +$writer2.Flush() +$reader2.ReadLine() \ No newline at end of file