Working challenges

This commit is contained in:
lieght
2025-12-12 21:48:57 +01:00
parent fc47d81b8e
commit 4d31a8ed44
8 changed files with 202 additions and 17 deletions

View File

@@ -22,11 +22,13 @@ import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.eventbus.GlobalEventBus; import org.toop.framework.eventbus.GlobalEventBus;
import org.toop.framework.game.BitboardGame; import org.toop.framework.game.BitboardGame;
import org.toop.framework.game.games.reversi.BitboardReversi; 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.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.networking.connection.NetworkingClientEventListener; import org.toop.framework.networking.connection.NetworkingClientEventListener;
import org.toop.framework.networking.connection.NetworkingClientManager; import org.toop.framework.networking.connection.NetworkingClientManager;
import org.toop.framework.networking.server.GameDefinition; import org.toop.framework.networking.server.GameDefinition;
import org.toop.framework.networking.server.MasterServer; 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.ResourceLoader;
import org.toop.framework.resource.ResourceManager; import org.toop.framework.resource.ResourceManager;
import org.toop.framework.resource.events.AssetLoaderEvents; 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.AppContext;
import org.toop.local.AppSettings; import org.toop.local.AppSettings;
import java.time.Duration;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@@ -98,7 +101,18 @@ public final class App extends Application {
WidgetContainer.setCurrentView(loading); WidgetContainer.setCurrentView(loading);
setOnLoadingSuccess(loading); var games = new ConcurrentHashMap<String, Class<? extends TurnBasedGame>>();
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(); EventFlow loadingFlow = new EventFlow();

View File

@@ -1,21 +1,40 @@
package org.toop.framework.networking.server; package org.toop.framework.networking.server;
import org.toop.framework.SnowflakeGenerator;
public class GameChallenge { 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 from;
private final ServerUser to; private final ServerUser to;
private final String gameType;
private final SimpleTimer timer; private final SimpleTimer timer;
private boolean isChallengeAccepted = false; 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.from = from;
this.to = to; this.to = to;
this.gameType = gameType;
this.timer = timer; 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; isChallengeAccepted = true;
timer.forceExpire(); timer.forceExpire();
return gameType;
} }
public boolean isExpired() { public boolean isExpired() {

View File

@@ -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.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.gameFramework.model.player.Player;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.*; import java.util.concurrent.*;
@@ -48,29 +49,88 @@ public class Server implements GameServer {
} }
public ServerUser getUser(String username) { 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) { public ServerUser getUser(long id) {
return users.get(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); ServerUser from = getUser(fromUser);
if (from == null) { if (from == null) {
return; return;
} }
ServerUser to = getUser(toUser);
if (to == null) { if (!gameTypes.containsKey(gameType)) {
from.sendMessage("ERR gametype not found");
return; 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() { public void checkChallenges() {
for (int i = gameChallenges.size() - 1; i >= 0; i--) { 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() { public String[] onlineUsers() {
return users.values().stream().map(ServerUser::name).toArray(String[]::new); return users.values().stream().map(ServerUser::name).toArray(String[]::new);
} }
public void closeServer() {
scheduler.shutdown();
gameChallenges.clear();
games.clear();
users.clear();
gameTypes.clear();
}
} }

View File

@@ -79,7 +79,28 @@ public class ServerHandler extends SimpleChannelInboundHandler<String> {
} }
private void handleChallenge(ParsedMessage p) { 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) { private void handleMove(ParsedMessage p) {

View File

@@ -1,9 +1,11 @@
package org.toop.framework.networking.server; package org.toop.framework.networking.server;
import java.net.InetSocketAddress;
public interface ServerUser { public interface ServerUser {
long id(); long id();
String name(); String name();
Game[] games();
void addGame(Game game);
void removeGame(Game game);
void setName(String name); void setName(String name);
void sendMessage(String message);
} }

View File

@@ -2,11 +2,13 @@ package org.toop.framework.networking.server;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import java.net.InetSocketAddress; import java.util.ArrayList;
import java.util.List;
public class User implements ServerUser { public class User implements ServerUser {
final private long id; final private long id;
private String name; private String name;
private final List<Game> games = new ArrayList<>();
private ChannelHandlerContext connectionContext; private ChannelHandlerContext connectionContext;
public User(long userId, String name) { public User(long userId, String name) {
@@ -24,11 +26,34 @@ public class User implements ServerUser {
return name; 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 @Override
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
@Override
public void sendMessage(String message) {
IO.println(message);
ctx().channel().writeAndFlush(message);
}
public ChannelHandlerContext ctx() { public ChannelHandlerContext ctx() {
return connectionContext; return connectionContext;
} }
@@ -37,4 +62,5 @@ public class User implements ServerUser {
this.connectionContext = ctx; this.connectionContext = ctx;
} }
} }

View File

@@ -11,8 +11,6 @@ import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ServerTest { public class ServerTest {
static class TurnBasedGameMock implements TurnBasedGame { static class TurnBasedGameMock implements TurnBasedGame {
@@ -88,10 +86,30 @@ public class ServerTest {
return name; return name;
} }
@Override
public Game[] games() {
return new Game[0];
}
@Override
public void addGame(Game game) {
}
@Override
public void removeGame(Game game) {
}
@Override @Override
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
@Override
public void sendMessage(String message) {
}
} }
private Server server; private Server server;
@@ -122,14 +140,14 @@ public class ServerTest {
void testChallenge() { void testChallenge() {
server.addUser(new TestUser(1, "test1")); server.addUser(new TestUser(1, "test1"));
server.addUser(new TestUser(2, "test2")); server.addUser(new TestUser(2, "test2"));
server.challengeUser("test1", "test2"); server.challengeUser("test1", "test2", "tictactoe");
IO.println(server.gameChallenges()); IO.println(server.gameChallenges());
Assertions.assertEquals(1, server.gameChallenges().size()); Assertions.assertEquals(1, server.gameChallenges().size());
try { try {
Thread.sleep(waitTime); Thread.sleep(waitTime.plusMillis(100));
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -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()