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 new file mode 100644 index 0000000..ba71f89 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/server/GameChallenge.java @@ -0,0 +1,24 @@ +package org.toop.framework.networking.server; + +public class GameChallenge { + private final ServerUser from; + private final ServerUser to; + private final SimpleTimer timer; + + private boolean isChallengeAccepted = false; + + public GameChallenge(ServerUser from, ServerUser to, SimpleTimer timer) { + this.from = from; + this.to = to; + this.timer = timer; + } + + public void acceptChallenge() { + isChallengeAccepted = true; + timer.forceExpire(); + } + + public boolean isExpired() { + return timer.isExpired(); + } +} \ No newline at end of file diff --git a/framework/src/main/java/org/toop/framework/networking/server/GameChallengeTimer.java b/framework/src/main/java/org/toop/framework/networking/server/GameChallengeTimer.java new file mode 100644 index 0000000..1406648 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/server/GameChallengeTimer.java @@ -0,0 +1,33 @@ +package org.toop.framework.networking.server; + +import java.time.Instant; +import java.time.Duration; + +public class GameChallengeTimer implements SimpleTimer { + + private final Instant createdAt; + private final Duration timeout; + + private boolean isExpired = false; + + public GameChallengeTimer(Duration duration) { + this.createdAt = Instant.now(); + this.timeout = duration; + } + + @Override + public void forceExpire() { + this.isExpired = true; + } + + @Override + public boolean isExpired() { + if (this.isExpired) return true; + return Instant.now().isAfter(createdAt.plus(timeout)); + } + + @Override + public long secondsRemaining() { + return Duration.between(Instant.now(), createdAt.plus(timeout)).toSeconds(); + } +} diff --git a/framework/src/main/java/org/toop/framework/networking/server/MasterServer.java b/framework/src/main/java/org/toop/framework/networking/server/MasterServer.java index 3f607b4..587a569 100644 --- a/framework/src/main/java/org/toop/framework/networking/server/MasterServer.java +++ b/framework/src/main/java/org/toop/framework/networking/server/MasterServer.java @@ -15,15 +15,16 @@ import org.toop.framework.SnowflakeGenerator; import org.toop.framework.game.BitboardGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame; +import java.time.Duration; import java.util.Map; public class MasterServer { private final int port; public final Server gs; - public MasterServer(int port, Map> gameTypes) { + public MasterServer(int port, Map> gameTypes, Duration challengeDuration) { this.port = port; - this.gs = new Server(gameTypes); + this.gs = new Server(gameTypes, challengeDuration); } public void start() throws InterruptedException { 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 deb3d99..be209ca 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 @@ -1,23 +1,34 @@ package org.toop.framework.networking.server; -import org.toop.framework.game.BitboardGame; 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.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.*; +import java.time.Duration; public class Server implements GameServer { final private Map> gameTypes; - final private List> games = new ArrayList<>(); final private Map users = new ConcurrentHashMap<>(); + final private List gameChallenges = new CopyOnWriteArrayList<>(); + final private List> games = new CopyOnWriteArrayList<>(); - public Server(Map> gameTypes) { + final private Duration challengeDuration; + final private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + public Server(Map> gameTypes, Duration challengeDuration) { this.gameTypes = gameTypes; + this.challengeDuration = challengeDuration; + + scheduler.schedule(this::serverTask, 0, TimeUnit.MILLISECONDS); + } + + private void serverTask() { + checkChallenges(); + scheduler.schedule(this::serverTask, 500, TimeUnit.MILLISECONDS); } public void addUser(ServerUser user) { @@ -36,6 +47,37 @@ public class Server implements GameServer { return games; } + public ServerUser getUser(String username) { + return users.values().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().get(); + } + + public ServerUser getUser(long id) { + return users.get(id); + } + + public void challengeUser(String fromUser, String toUser) { + ServerUser from = getUser(fromUser); + if (from == null) { + return; + } + ServerUser to = getUser(toUser); + if (to == null) { + return; + } + + gameChallenges.addLast(new GameChallenge(from, to, new GameChallengeTimer(challengeDuration))); + } + + public void checkChallenges() { + for (int i = gameChallenges.size() - 1; i >= 0; i--) { + if (gameChallenges.get(i).isExpired()) gameChallenges.remove(i); + } + } + + public List gameChallenges() { + return gameChallenges; + } + public void startGame(String gameType, User... users) { if (!gameTypes.containsKey(gameType)) return; @@ -53,6 +95,13 @@ public class Server implements GameServer { } catch (Exception ignored) {} } +// public void checkGames() { +// for (int i = games.size() - 1; i >= 0; i--) { +// var game = games.get(i); +// if (game.game().getWinner() >= 0) games.remove(i); +// } +// } + public String[] onlineUsers() { return users.values().stream().map(ServerUser::name).toArray(String[]::new); } diff --git a/framework/src/main/java/org/toop/framework/networking/server/SimpleTimer.java b/framework/src/main/java/org/toop/framework/networking/server/SimpleTimer.java new file mode 100644 index 0000000..c9c25aa --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/server/SimpleTimer.java @@ -0,0 +1,7 @@ +package org.toop.framework.networking.server; + +public interface SimpleTimer { + void forceExpire(); + boolean isExpired(); + long secondsRemaining(); +} 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 c85674c..6f663bb 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 @@ -7,6 +7,7 @@ import org.toop.framework.gameFramework.model.game.PlayResult; import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.player.Player; +import java.time.Duration; import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; @@ -66,7 +67,35 @@ public class ServerTest { } + static class TestUser implements ServerUser { + + final private long id; + + private String name; + + public TestUser(long id, String name) { + this.id = id; + this.name = name; + } + + @Override + public long id() { + return id; + } + + @Override + public String name() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + } + private Server server; + private Duration waitTime = Duration.ofSeconds(2); @BeforeEach void setup() { @@ -75,7 +104,7 @@ public class ServerTest { games.put("tictactoe", TurnBasedGameMock.class); games.put("reversi", TurnBasedGameMock.class); - server = new Server(games); + server = new Server(games, waitTime); } @Test @@ -89,9 +118,32 @@ public class ServerTest { Assertions.assertArrayEquals(expected, actual); } + @Test + void testChallenge() { + server.addUser(new TestUser(1, "test1")); + server.addUser(new TestUser(2, "test2")); + server.challengeUser("test1", "test2"); + + IO.println(server.gameChallenges()); + + Assertions.assertEquals(1, server.gameChallenges().size()); + + try { + Thread.sleep(waitTime); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + Assertions.assertEquals(0, server.gameChallenges().size()); + } + @Test void testStartGame() { server.startGame("tictactoe", new User(0, "A"), new User(1, "B")); Assertions.assertEquals(1, server.ongoingGames().size()); + server.startGame("reversi", new User(0, "A"), new User(1, "B")); + Assertions.assertEquals(2, server.ongoingGames().size()); } + + }