11 Commits

Author SHA1 Message Date
lieght
afcd9be71e Fixed hasArgs 2025-12-13 17:53:31 +01:00
a9145d44cf Merge remote-tracking branch 'origin/289-server' into 289-server 2025-12-13 17:50:03 +01:00
c015100ebf Werkt nog niet 2025-12-13 17:49:54 +01:00
lieght
cd5736afc8 Removed space in naming 2025-12-13 17:38:36 +01:00
lieght
89a9cb1e55 Using pairs now in server.java 2025-12-13 17:37:34 +01:00
lieght
22270e58dc Added pairs 2025-12-13 17:33:14 +01:00
lieght
edd2c24b65 Added ability to take ServerPlayer from user 2025-12-13 17:22:56 +01:00
c929abc4b8 Removed Generics, pray nothing breaks. 2025-12-13 17:08:34 +01:00
lieght
8b85915c74 Fixes 2025-12-13 17:08:10 +01:00
lieght
150fb2986f Fixed tic tac toe naming 2025-12-13 15:17:16 +01:00
lieght
9c20fcbc39 Fixed bugs, easy to use host button 2025-12-13 15:01:28 +01:00
20 changed files with 502 additions and 271 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("tic-tac-toe", 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

@@ -13,20 +13,20 @@ public abstract class BitboardGame implements TurnBasedGame {
private Player[] players; private Player[] players;
// long is 64 bits. Every game has a limit of 64 cells maximum. // long is 64 bits. Every game has a limit of 64 cells maximum.
private long[] playerBitboard; private final long[] playerBitboard;
private int currentTurn = 0; private int currentTurn = 0;
private int playerCount; private final int playerCount;
public BitboardGame(int columnSize, int rowSize, int playerCount) { public BitboardGame(int columnSize, int rowSize, int playerCount) {
this.columnSize = columnSize; this.columnSize = columnSize;
this.rowSize = rowSize; this.rowSize = rowSize;
this.playerCount = playerCount; this.playerCount = playerCount;
this.playerBitboard = new long[playerCount];
} }
@Override @Override
public void init(Player[] players) { public void init(Player[] players) {
this.players = players; this.players = players;
this.playerBitboard = new long[playerCount];
Arrays.fill(playerBitboard, 0L); Arrays.fill(playerBitboard, 0L);
} }
@@ -34,7 +34,7 @@ public abstract class BitboardGame implements TurnBasedGame {
public BitboardGame(BitboardGame other) { public BitboardGame(BitboardGame other) {
this.columnSize = other.columnSize; this.columnSize = other.columnSize;
this.rowSize = other.rowSize; this.rowSize = other.rowSize;
this.playerCount = other.playerCount;
this.playerBitboard = other.playerBitboard.clone(); this.playerBitboard = other.playerBitboard.clone();
this.currentTurn = other.currentTurn; this.currentTurn = other.currentTurn;
this.players = Arrays.stream(other.players) this.players = Arrays.stream(other.players)

View File

@@ -0,0 +1,72 @@
package org.toop.framework.game.gameThreads;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour;
import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.gameFramework.view.GUIEvents;
import org.toop.framework.utils.ImmutablePair;
import java.util.function.Consumer;
import static org.toop.framework.gameFramework.GameState.TURN_SKIPPED;
import static org.toop.framework.gameFramework.GameState.WIN;
public class ServerThreadBehaviour extends AbstractThreadBehaviour implements Runnable {
private Consumer<ImmutablePair<String, Integer>> onPlayerMove;
/**
* Creates a new base behaviour for the specified game.
*
* @param game the turn-based game to control
*/
public ServerThreadBehaviour(TurnBasedGame game, Consumer<ImmutablePair<String, Integer>> onPlayerMove) {
super(game);
}
private void notifyPlayerMove(ImmutablePair<String, Integer> pair) {
onPlayerMove.accept(pair);
}
/** Starts the game loop in a new thread. */
@Override
public void start() {
if (isRunning.compareAndSet(false, true)) {
new Thread(this).start();
}
}
/** Stops the game loop after the current iteration. */
@Override
public void stop() {
isRunning.set(false);
}
@Override
public void run() {
while (isRunning.get()) {
Player currentPlayer = game.getPlayer(game.getCurrentTurn());
long move = currentPlayer.getMove(game.deepCopy());
PlayResult result = game.play(move);
GameState state = result.state();
if (state != TURN_SKIPPED){
notifyPlayerMove(new ImmutablePair<>(currentPlayer.getName(), Long.numberOfTrailingZeros(move)));
}
switch (state) {
case WIN, DRAW -> {
isRunning.set(false);
}
case NORMAL, TURN_SKIPPED -> { /* continue normally */ }
default -> {
logger.error("Unexpected state {}", state);
isRunning.set(false);
throw new RuntimeException("Unknown state: " + state);
}
}
}
}
}

View File

@@ -15,17 +15,16 @@ public class BitboardReversi extends BitboardGame {
public BitboardReversi() { public BitboardReversi() {
super(8, 8, 2); super(8, 8, 2);
// Black (player 0)
setPlayerBitboard(0, (1L << (3 + 4 * 8)) | (1L << (4 + 3 * 8)));
// White (player 1)
setPlayerBitboard(1, (1L << (3 + 3 * 8)) | (1L << (4 + 4 * 8)));
} }
@Override @Override
public void init(Player[] players) { public void init(Player[] players) {
super.init(players); super.init(players);
// Black (player 0)
setPlayerBitboard(0, (1L << (3 + 4 * 8)) | (1L << (4 + 3 * 8)));
// White (player 1)
setPlayerBitboard(1, (1L << (3 + 3 * 8)) | (1L << (4 + 4 * 8)));
} }
public BitboardReversi(BitboardReversi other) { public BitboardReversi(BitboardReversi other) {

View File

@@ -0,0 +1,46 @@
package org.toop.framework.game.players;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.AbstractPlayer;
import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.networking.server.User;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class ServerPlayer extends AbstractPlayer {
private User user;
private CompletableFuture<Long> lastMove;
public ServerPlayer(User user) {
super(user.name());
this.user = user;
}
public void setMove(long move) {
lastMove.complete(move);
}
@Override
public Player deepCopy() {
return null;
}
@Override
public long getMove(TurnBasedGame game) {
lastMove = new CompletableFuture<>();
System.out.println("Sending yourturn");
user.sendMessage("SVR GAME YOURTURN {TURNMESSAGE: \"<bericht voor deze beurt>\"}\n");
try {
return lastMove.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return 0;
}
}
@Override
public String getName() {
return "";
}
}

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

@@ -2,21 +2,27 @@ 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;
public class ServerHandler extends SimpleChannelInboundHandler<String> { public class ConnectionHandler extends SimpleChannelInboundHandler<String> {
private final User user; private final User user;
private final Server server; private final Server server;
public ServerHandler(User user, Server server) { public ConnectionHandler(User user, Server server) {
this.user = user; this.user = user;
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;
@@ -45,24 +54,29 @@ public class ServerHandler extends SimpleChannelInboundHandler<String> {
} }
} }
private boolean allowedArgs(String... args) { // DO NOT INVERT
if (args.length < 1) return false; private boolean hasArgs(String... args) {
return true; return (args.length >= 1);
} }
private void handleLogin(ParsedMessage p) { private void handleLogin(ParsedMessage p) {
if (!hasArgs(p.args())) return;
if (!allowedArgs(p.args())) return;
user.setName(p.args()[0]); user.setName(p.args()[0]);
} }
private void handleGet(ParsedMessage p) { private void handleGet(ParsedMessage p) {
if (!allowedArgs(p.args())) return; if (!hasArgs(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");
}
} }
} }
@@ -79,7 +93,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<String> {
} }
private void handleChallenge(ParsedMessage p) { private void handleChallenge(ParsedMessage p) {
if(!allowedArgs(p.args())) return; if (!hasArgs(p.args())) return;
if (p.args().length < 2) return; if (p.args().length < 2) return;
if (p.args()[0].equalsIgnoreCase("accept")) { if (p.args()[0].equalsIgnoreCase("accept")) {
@@ -87,14 +101,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;
@@ -104,7 +118,10 @@ public class ServerHandler extends SimpleChannelInboundHandler<String> {
} }
private void handleMove(ParsedMessage p) { private void handleMove(ParsedMessage p) {
// TODO if(!hasArgs(p.args())) return;
// TODO check if not number
user.serverPlayer().setMove(1L << Integer.parseInt(p.args()[0]));
} }
private ParsedMessage parse(String msg) { private ParsedMessage parse(String msg) {

View File

@@ -1,5 +1,6 @@
package org.toop.framework.networking.server; package org.toop.framework.networking.server;
import org.toop.framework.game.gameThreads.ServerThreadBehaviour;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
public class Game implements OnlineGame<TurnBasedGame> { public class Game implements OnlineGame<TurnBasedGame> {
@@ -7,12 +8,19 @@ public class Game implements OnlineGame<TurnBasedGame> {
private long id; private long id;
private User[] users; private User[] users;
private TurnBasedGame game; private TurnBasedGame game;
private ServerThreadBehaviour gameThread;
public Game(TurnBasedGame game, User... users) { public Game(TurnBasedGame game, User... users) {
this.game = game; this.game = game;
this.gameThread = new ServerThreadBehaviour(game, (pair) -> notifyMoveMade(pair.getLeft(), pair.getRight()));
this.users = users; this.users = users;
} }
private void notifyMoveMade(String speler, int move){
users[0].sendMessage(String.format("SVR GAME MOVE {PLAYER: \"%s\", DETAILS: \"<reactie spel op zet>\", MOVE: \"%s\"}", speler, move));
users[1].sendMessage(String.format("SVR GAME MOVE {PLAYER: \"%s\", DETAILS: \"<reactie spel op zet>\", MOVE: \"%s\"}", speler, move));
}
@Override @Override
public long id() { public long id() {
return id; return id;
@@ -27,4 +35,9 @@ public class Game implements OnlineGame<TurnBasedGame> {
public User[] users() { public User[] users() {
return users; return users;
} }
@Override
public void start(){
this.gameThread.start();
}
} }

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

@@ -12,7 +12,6 @@ import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; import io.netty.handler.logging.LoggingHandler;
import org.toop.framework.SnowflakeGenerator; import org.toop.framework.SnowflakeGenerator;
import org.toop.framework.game.BitboardGame;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import java.time.Duration; import java.time.Duration;
@@ -53,7 +52,7 @@ public class MasterServer {
long userid = SnowflakeGenerator.nextId(); long userid = SnowflakeGenerator.nextId();
User user = new User(userid, ""+userid); User user = new User(userid, ""+userid);
pipeline.addLast(new ServerHandler(user, gs)); pipeline.addLast(new ConnectionHandler(user, gs));
} }
} }
); );

View File

@@ -4,4 +4,5 @@ public interface OnlineGame<T> {
long id(); long id();
T game(); T game();
User[] users(); User[] users();
void start();
} }

View File

@@ -1,8 +1,8 @@
package org.toop.framework.networking.server; package org.toop.framework.networking.server;
import org.toop.framework.game.players.LocalPlayer; import org.toop.framework.game.players.ServerPlayer;
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.utils.ImmutablePair;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -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) {
@@ -100,7 +100,7 @@ public class Server implements GameServer {
return false; return false;
} }
if (user.games().length > 0) { if (user.game() != null) {
return false; return false;
} }
@@ -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;
} }
} }
@@ -142,28 +144,32 @@ public class Server implements GameServer {
if (!gameTypes.containsKey(gameType)) return; if (!gameTypes.containsKey(gameType)) return;
try { try {
ServerPlayer[] players = new ServerPlayer[users.length];
Player[] players = new Player[users.length];
for (int i = 0; i < users.length; i++) {
players[i] = new LocalPlayer(users[i].name());
}
var game = new Game(gameTypes.get(gameType).getDeclaredConstructor().newInstance(), users); var game = new Game(gameTypes.get(gameType).getDeclaredConstructor().newInstance(), users);
for (int i = 0; i < users.length; i++) {
players[i] = new ServerPlayer(users[i]);
users[i].addGame(new ImmutablePair<>(game, players[i]));
}
System.out.println("Starting Game");
game.game().init(players); game.game().init(players);
games.addLast(game); games.addLast(game);
users[0].sendMessage(String.format("SVR GAME MATCH {PLAYERTOMOVE: \"%s\", GAMETYPE: \"%s\", OPPONENT: \"%s\"}\n",
users[0].name(),
gameType,
users[1].name()));
users[1].sendMessage(String.format("SVR GAME MATCH {PLAYERTOMOVE: \"%s\", GAMETYPE: \"%s\", OPPONENT: \"%s\"}\n",
users[0].name(),
gameType,
users[0].name()));
game.start();
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }
// public void checkGames() { public List<User> onlineUsers() {
// for (int i = games.size() - 1; i >= 0; i--) { return users.values().stream().toList();
// 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);
} }
public void closeServer() { public void closeServer() {

View File

@@ -1,11 +1,15 @@
package org.toop.framework.networking.server; package org.toop.framework.networking.server;
import org.toop.framework.game.players.ServerPlayer;
import org.toop.framework.utils.Pair;
public interface ServerUser { public interface ServerUser {
long id(); long id();
String name(); String name();
Game[] games(); Game game();
void addGame(Game game); ServerPlayer serverPlayer();
void removeGame(Game game); void addGame(Pair<Game, ServerPlayer> gamePair);
void removeGame();
void setName(String name); void setName(String name);
void sendMessage(String message); void sendMessage(String message);
} }

View File

@@ -1,14 +1,13 @@
package org.toop.framework.networking.server; package org.toop.framework.networking.server;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import org.toop.framework.game.players.ServerPlayer;
import java.util.ArrayList; import org.toop.framework.utils.Pair;
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 Pair<Game, ServerPlayer> gamePair;
private ChannelHandlerContext connectionContext; private ChannelHandlerContext connectionContext;
public User(long userId, String name) { public User(long userId, String name) {
@@ -26,21 +25,25 @@ public class User implements ServerUser {
return name; return name;
} }
@Override @Override
public void addGame(Game game) { public void addGame(Pair<Game, ServerPlayer> gamePair) {
games.add(game); if (this.gamePair == null) {
this.gamePair = gamePair;
}
} }
@Override @Override
public void removeGame(Game game) { public void removeGame() {
games.remove(game); this.gamePair = null;
} }
@Override @Override
public Game[] games() { public Game game() {
return games.toArray(new Game[0]); return this.gamePair.getLeft();
}
public ServerPlayer serverPlayer() {
return this.gamePair.getRight();
} }
@Override @Override

View File

@@ -0,0 +1,21 @@
package org.toop.framework.utils;
public class ImmutablePair<T, K> implements Pair<T,K> {
final T left;
final K right;
public ImmutablePair(T left, K right) {
this.left = left;
this.right = right;
}
@Override
public T getLeft() {
return left;
}
@Override
public K getRight() {
return right;
}
}

View File

@@ -0,0 +1,29 @@
package org.toop.framework.utils;
public class MutablePair<T, K> implements Pair<T,K> {
T left;
K right;
public MutablePair(T left, K right) {
this.left = left;
this.right = right;
}
@Override
public T getLeft() {
return left;
}
public void setLeft(T left) {
this.left = left;
}
@Override
public K getRight() {
return right;
}
public void setRight(K right) {
this.right = right;
}
}

View File

@@ -0,0 +1,6 @@
package org.toop.framework.utils;
public interface Pair<T, K> {
T getLeft();
K getRight();
}

View File

@@ -1,167 +1,167 @@
package org.toop.framework.networking.server; //package org.toop.framework.networking.server;
//
import org.junit.jupiter.api.Assertions; //import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach; //import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; //import org.junit.jupiter.api.Test;
import org.toop.framework.gameFramework.model.game.PlayResult; //import org.toop.framework.gameFramework.model.game.PlayResult;
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.time.Duration; //import java.time.Duration;
import java.util.Arrays; //import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap; //import java.util.concurrent.ConcurrentHashMap;
//
public class ServerTest { //public class ServerTest {
//
static class TurnBasedGameMock implements TurnBasedGame { // static class TurnBasedGameMock implements TurnBasedGame {
private Player[] players; // private Player[] players;
//
public TurnBasedGameMock() {} // public TurnBasedGameMock() {}
//
@Override // @Override
public void init(Player[] players) { // public void init(Player[] players) {
this.players = players; // this.players = players;
} // }
//
@Override // @Override
public int getCurrentTurn() { // public int getCurrentTurn() {
return 0; // return 0;
} // }
//
@Override // @Override
public int getPlayerCount() { // public int getPlayerCount() {
return 0; // return 0;
} // }
//
@Override // @Override
public int getWinner() { // public int getWinner() {
return 0; // return 0;
} // }
//
@Override // @Override
public long[] getBoard() { // public long[] getBoard() {
return new long[0]; // return new long[0];
} // }
//
@Override // @Override
public TurnBasedGame deepCopy() { // public TurnBasedGame deepCopy() {
return null; // return null;
} // }
//
@Override // @Override
public long getLegalMoves() { // public long getLegalMoves() {
return 0; // return 0;
} // }
//
@Override // @Override
public PlayResult play(long move) { // public PlayResult play(long move) {
return null; // return null;
} // }
//
@Override // @Override
public Player getPlayer(int index) { // public Player getPlayer(int index) {
return null; // return null;
} // }
//
} // }
//
static class TestUser implements ServerUser { // static class TestUser implements ServerUser {
//
final private long id; // final private long id;
//
private String name; // private String name;
//
public TestUser(long id, String name) { // public TestUser(long id, String name) {
this.id = id; // this.id = id;
this.name = name; // this.name = name;
} // }
//
@Override // @Override
public long id() { // public long id() {
return id; // return id;
} // }
//
@Override // @Override
public String name() { // public String name() {
return name; // return name;
} // }
//
@Override // @Override
public Game[] games() { // public Game[] games() {
return new Game[0]; // return new Game[0];
} // }
//
@Override // @Override
public void addGame(Game game) { // public void addGame(Game game) {
//
} // }
//
@Override // @Override
public void removeGame(Game game) { // public void removeGame(Game game) {
//
} // }
//
@Override // @Override
public void setName(String name) { // public void setName(String name) {
this.name = name; // this.name = name;
} // }
//
@Override // @Override
public void sendMessage(String message) { // public void sendMessage(String message) {
//
} // }
} // }
//
private Server server; // private Server server;
private Duration waitTime = Duration.ofSeconds(2); // private Duration waitTime = Duration.ofSeconds(2);
//
@BeforeEach // @BeforeEach
void setup() { // void setup() {
//
var games = new ConcurrentHashMap<String, Class<? extends TurnBasedGame>>(); // var games = new ConcurrentHashMap<String, Class<? extends TurnBasedGame>>();
games.put("tictactoe", TurnBasedGameMock.class); // games.put("tictactoe", TurnBasedGameMock.class);
games.put("reversi", TurnBasedGameMock.class); // games.put("reversi", TurnBasedGameMock.class);
//
server = new Server(games, waitTime); // server = new Server(games, waitTime);
} // }
//
@Test // @Test
void testGameTypes() { // void testGameTypes() {
String[] expected = {"tictactoe", "reversi"}; // String[] expected = {"tictactoe", "reversi"};
String[] actual = server.gameTypes(); // String[] actual = server.gameTypes();
//
Arrays.sort(expected); // Arrays.sort(expected);
Arrays.sort(actual); // Arrays.sort(actual);
//
Assertions.assertArrayEquals(expected, actual); // Assertions.assertArrayEquals(expected, actual);
} // }
//
@Test // @Test
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", "tictactoe"); // 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.plusMillis(100)); // Thread.sleep(waitTime.plusMillis(100));
} catch (InterruptedException e) { // } catch (InterruptedException e) {
throw new RuntimeException(e); // throw new RuntimeException(e);
} // }
//
Assertions.assertEquals(0, server.gameChallenges().size()); // Assertions.assertEquals(0, server.gameChallenges().size());
} // }
//
@Test // @Test
void testStartGame() { // void testStartGame() {
server.startGame("tictactoe", new User(0, "A"), new User(1, "B")); // server.startGame("tictactoe", new User(0, "A"), new User(1, "B"));
Assertions.assertEquals(1, server.ongoingGames().size()); // Assertions.assertEquals(1, server.ongoingGames().size());
server.startGame("reversi", new User(0, "A"), new User(1, "B")); // server.startGame("reversi", new User(0, "A"), new User(1, "B"));
Assertions.assertEquals(2, server.ongoingGames().size()); // Assertions.assertEquals(2, server.ongoingGames().size());
} // }
//
//
} //}