Fixed bugs, easy to use host button

This commit is contained in:
lieght
2025-12-13 15:01:28 +01:00
parent 4d31a8ed44
commit 9c20fcbc39
7 changed files with 83 additions and 51 deletions

View File

@@ -20,15 +20,8 @@ import org.toop.framework.audio.*;
import org.toop.framework.audio.events.AudioEvents; import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow; 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.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.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.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;
@@ -38,9 +31,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.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@@ -101,17 +92,6 @@ public final class App extends Application {
WidgetContainer.setCurrentView(loading); WidgetContainer.setCurrentView(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); setOnLoadingSuccess(loading);
EventFlow loadingFlow = new EventFlow(); EventFlow loadingFlow = new EventFlow();

View File

@@ -263,7 +263,7 @@ public final class Server {
String gameType = extractQuotedValue(response.gameType()); String gameType = extractQuotedValue(response.gameType());
final String finalGameType = gameType; final String finalGameType = gameType;
var a = new ChallengePopup(challengerName, gameType, (playerInformation) -> { var a = new ChallengePopup(challengerName, gameType, (playerInformation) -> {
final int challengeId = Integer.parseInt(response.challengeId().replaceAll("\\D", "")); final long challengeId = Long.parseLong(response.challengeId().replaceAll("\\D", ""));
new EventFlow().addPostEvent(new NetworkEvents.SendAcceptChallenge(clientId, challengeId)).postEvent(); new EventFlow().addPostEvent(new NetworkEvents.SendAcceptChallenge(clientId, challengeId)).postEvent();
isSingleGame.set(true); isSingleGame.set(true);
}); });

View File

@@ -6,6 +6,13 @@ import org.toop.app.widget.complex.LabeledInputWidget;
import org.toop.app.widget.complex.ViewWidget; import org.toop.app.widget.complex.ViewWidget;
import javafx.geometry.Pos; import javafx.geometry.Pos;
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.server.MasterServer;
import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;
public class OnlineView extends ViewWidget { public class OnlineView extends ViewWidget {
public OnlineView() { public OnlineView() {
@@ -23,6 +30,28 @@ public class OnlineView extends ViewWidget {
); );
}); });
var localHostButton = Primitive.button("host!", () -> {
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(10));
new Thread(() -> {
try {
a.start();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Server(
"127.0.0.1",
"6666",
"host"
);
}, false);
add(Pos.CENTER, Primitive.vbox( add(Pos.CENTER, Primitive.vbox(
serverInformationHeader, serverInformationHeader,
Primitive.separator(), Primitive.separator(),
@@ -32,7 +61,9 @@ public class OnlineView extends ViewWidget {
playerNameInput.getNode(), playerNameInput.getNode(),
Primitive.separator(), Primitive.separator(),
connectButton connectButton,
Primitive.separator(),
localHostButton
)); ));
} }
} }

View File

@@ -102,7 +102,7 @@ public class NetworkEvents extends EventsBase {
implements GenericEvent {} implements GenericEvent {}
/** Requests to accept an existing challenge. */ /** Requests to accept an existing challenge. */
public record SendAcceptChallenge(long clientId, int challengeId) public record SendAcceptChallenge(long clientId, long challengeId)
implements GenericEvent {} implements GenericEvent {}
/** Requests to forfeit the current game. */ /** Requests to forfeit the current game. */

View File

@@ -5,14 +5,14 @@ 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 long id = SnowflakeGenerator.nextId(); // I don't need this, but the tournament server uses it...
private final ServerUser from; private final User from;
private final ServerUser to; private final User to;
private final String gameType; 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, String gameType, SimpleTimer timer) { public GameChallenge(User from, User to, String gameType, SimpleTimer timer) {
this.from = from; this.from = from;
this.to = to; this.to = to;
this.gameType = gameType; this.gameType = gameType;
@@ -23,8 +23,8 @@ public class GameChallenge {
return id; return id;
} }
public ServerUser[] getUsers() { public User[] getUsers() {
return new ServerUser[]{from, to}; return new User[]{from, to};
} }
public void forceExpire() { public void forceExpire() {
@@ -37,6 +37,10 @@ public class GameChallenge {
return gameType; return gameType;
} }
public boolean isChallengeAccepted() {
return isChallengeAccepted;
}
public boolean isExpired() { public boolean isExpired() {
return timer.isExpired(); return timer.isExpired();
} }

View File

@@ -13,7 +13,7 @@ import java.time.Duration;
public class Server implements GameServer { public class Server implements GameServer {
final private Map<String, Class<? extends TurnBasedGame>> gameTypes; final private Map<String, Class<? extends TurnBasedGame>> gameTypes;
final private Map<Long, ServerUser> users = new ConcurrentHashMap<>(); final private Map<Long, User> users = new ConcurrentHashMap<>();
final private List<GameChallenge> gameChallenges = new CopyOnWriteArrayList<>(); final private List<GameChallenge> gameChallenges = new CopyOnWriteArrayList<>();
final private List<OnlineGame<TurnBasedGame>> games = new CopyOnWriteArrayList<>(); final private List<OnlineGame<TurnBasedGame>> games = new CopyOnWriteArrayList<>();
@@ -32,53 +32,53 @@ public class Server implements GameServer {
scheduler.schedule(this::serverTask, 500, TimeUnit.MILLISECONDS); scheduler.schedule(this::serverTask, 500, TimeUnit.MILLISECONDS);
} }
public void addUser(ServerUser user) { public void addUser(User user) {
users.putIfAbsent(user.id(), user); users.putIfAbsent(user.id(), user);
} }
public void removeUser(ServerUser user) { public void removeUser(User user) {
users.remove(user.id()); users.remove(user.id());
} }
public String[] gameTypes() { public List<String> gameTypes() {
return gameTypes.keySet().toArray(new String[0]); return gameTypes.keySet().stream().toList();
} }
public List<OnlineGame<TurnBasedGame>> ongoingGames() { public List<OnlineGame<TurnBasedGame>> ongoingGames() {
return games; return games;
} }
public ServerUser getUser(String username) { public User getUser(String username) {
return users.values().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().orElse(null); return users.values().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().orElse(null);
} }
public ServerUser getUser(long id) { public User getUser(long id) {
return users.get(id); return users.get(id);
} }
public void challengeUser(String fromUser, String toUser, String gameType) { public void challengeUser(String fromUser, String toUser, String gameType) {
ServerUser from = getUser(fromUser); User from = getUser(fromUser);
if (from == null) { if (from == null) {
return; return;
} }
if (!gameTypes.containsKey(gameType)) { if (!gameTypes.containsKey(gameType)) {
from.sendMessage("ERR gametype not found"); from.sendMessage("ERR gametype not found \n");
return; return;
} }
ServerUser to = getUser(toUser); User to = getUser(toUser);
if (to == null) { if (to == null) {
from.sendMessage("ERR user not found"); from.sendMessage("ERR user not found \n");
return; return;
} }
var ch = new GameChallenge(from, to, gameType, new GameChallengeTimer(challengeDuration)); var ch = new GameChallenge(from, to, gameType, new GameChallengeTimer(challengeDuration));
to.sendMessage( to.sendMessage(
"\"SVR GAME CHALLENGE {CHALLENGER: \"%s\", GAMETYPE: \"%s\", CHALLENGENUMBER: \"%s\"}" "SVR GAME CHALLENGE {CHALLENGER: \"%s\", CHALLENGENUMBER: \"%s\", GAMETYPE: \"%s\"} \n"
.formatted(from.name(), gameType, ch.id()) .formatted(from.name(), ch.id(), gameType)
); );
if (!isValidChallenge(ch)) { if (!isValidChallenge(ch)) {
@@ -90,8 +90,8 @@ public class Server implements GameServer {
gameChallenges.addLast(ch); gameChallenges.addLast(ch);
} }
private void warnUserExpiredChallenge(ServerUser user, long challengeId) { private void warnUserExpiredChallenge(User user, long challengeId) {
user.sendMessage("SVR GAME CHALLENGE CANCELLED {CHALLENGENUMBER: \"" + challengeId + "\"}"); user.sendMessage("SVR GAME CHALLENGE CANCELLED {CHALLENGENUMBER: \"" + challengeId + "\"}" + "\n");
} }
private boolean isValidChallenge(GameChallenge gameChallenge) { private boolean isValidChallenge(GameChallenge gameChallenge) {
@@ -119,7 +119,9 @@ public class Server implements GameServer {
if (isValidChallenge(challenge)) continue; if (isValidChallenge(challenge)) continue;
if (challenge.isExpired()) { if (challenge.isExpired()) {
Arrays.stream(challenge.getUsers()).forEach(user -> warnUserExpiredChallenge(user, challenge.id())); if (!challenge.isChallengeAccepted()) Arrays.stream(challenge.getUsers())
.forEach(user -> warnUserExpiredChallenge(user, challenge.id()));
gameChallenges.remove(i); gameChallenges.remove(i);
} }
} }
@@ -128,7 +130,7 @@ public class Server implements GameServer {
public void acceptChallenge(long challengeId) { public void acceptChallenge(long challengeId) {
for (var challenge : gameChallenges) { for (var challenge : gameChallenges) {
if (challenge.id() == challengeId) { if (challenge.id() == challengeId) {
startGame(challenge.acceptChallenge(), (User[]) challenge.getUsers()); startGame(challenge.acceptChallenge(), challenge.getUsers());
break; break;
} }
} }
@@ -162,8 +164,8 @@ public class Server implements GameServer {
// } // }
// } // }
public String[] onlineUsers() { public List<User> onlineUsers() {
return users.values().stream().map(ServerUser::name).toArray(String[]::new); return users.values().stream().toList();
} }
public void closeServer() { public void closeServer() {

View File

@@ -2,8 +2,10 @@ package org.toop.framework.networking.server;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import org.apache.maven.surefire.shared.utils.StringUtils;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@@ -17,6 +19,10 @@ public class ServerHandler extends SimpleChannelInboundHandler<String> {
this.server = server; this.server = server;
} }
private String returnQuotedString(Iterator<String> strings) { // TODO more places this could be useful
return "\"" + StringUtils.join(strings, "\",\"") + "\"";
}
@Override @Override
public void channelActive(ChannelHandlerContext ctx) { public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush("WELCOME " + user.id() + "\n"); ctx.writeAndFlush("WELCOME " + user.id() + "\n");
@@ -27,6 +33,9 @@ public class ServerHandler extends SimpleChannelInboundHandler<String> {
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) { protected void channelRead0(ChannelHandlerContext ctx, String msg) {
IO.println(msg);
ParsedMessage p = parse(msg); ParsedMessage p = parse(msg);
if (p == null) return; if (p == null) return;
@@ -61,8 +70,14 @@ public class ServerHandler extends SimpleChannelInboundHandler<String> {
if (!allowedArgs(p.args())) return; if (!allowedArgs(p.args())) return;
switch (p.args()[0]) { switch (p.args()[0]) {
case "playerlist" -> user.ctx().writeAndFlush(Arrays.toString(server.onlineUsers())); case "playerlist" -> {
case "gamelist" -> user.ctx().writeAndFlush(Arrays.toString(server.gameTypes())); var names = server.onlineUsers().stream().map(ServerUser::name).iterator();
user.ctx().writeAndFlush("SVR PLAYERLIST " + returnQuotedString(names) + "\n");
}
case "gamelist" -> {
var names = server.gameTypes().stream().iterator();
user.ctx().writeAndFlush("SVR GAMELIST " + returnQuotedString(names) + "\n");
}
} }
} }
@@ -87,14 +102,14 @@ public class ServerHandler extends SimpleChannelInboundHandler<String> {
long id = Long.parseLong(p.args()[1]); long id = Long.parseLong(p.args()[1]);
if (id <= 0) { if (id <= 0) {
user.sendMessage("ERR id must be a positive number"); user.sendMessage("ERR id must be a positive number \n");
return; return;
} }
server.acceptChallenge(id); server.acceptChallenge(id);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
user.sendMessage("ERR id is not a valid number or too big"); user.sendMessage("ERR id is not a valid number or too big \n");
return; return;
} }
return; return;