mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 10:54:51 +00:00
Compare commits
8 Commits
afcd9be71e
...
8867d5a1ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8867d5a1ea | ||
|
|
b94d1b6c9d | ||
|
|
8cb0a86d4e | ||
|
|
c2f1df7143 | ||
|
|
0956286616 | ||
| 55de6b5b18 | |||
| 73d71f2a2d | |||
|
|
cbcce29780 |
@@ -17,11 +17,10 @@ import org.toop.framework.gameFramework.model.player.Player;
|
|||||||
import org.toop.framework.networking.connection.clients.TournamentNetworkingClient;
|
import org.toop.framework.networking.connection.clients.TournamentNetworkingClient;
|
||||||
import org.toop.framework.networking.connection.events.NetworkEvents;
|
import org.toop.framework.networking.connection.events.NetworkEvents;
|
||||||
import org.toop.framework.networking.connection.types.NetworkingConnector;
|
import org.toop.framework.networking.connection.types.NetworkingConnector;
|
||||||
import org.toop.framework.game.games.reversi.BitboardReversi;
|
|
||||||
import org.toop.framework.game.games.tictactoe.BitboardTicTacToe;
|
|
||||||
import org.toop.framework.game.players.ArtificialPlayer;
|
import org.toop.framework.game.players.ArtificialPlayer;
|
||||||
import org.toop.framework.game.players.OnlinePlayer;
|
import org.toop.framework.game.players.OnlinePlayer;
|
||||||
import org.toop.framework.game.players.RandomAI;
|
import org.toop.framework.game.players.RandomAI;
|
||||||
|
import org.toop.framework.networking.server.gateway.NettyGatewayServer;
|
||||||
import org.toop.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -32,7 +31,8 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public final class Server {
|
public final class Server {
|
||||||
// TODO: Keep track of listeners. Remove them on Server connection close so reference is deleted.
|
private NettyGatewayServer nettyGatewayServer;
|
||||||
|
|
||||||
private String user = "";
|
private String user = "";
|
||||||
private long clientId = -1;
|
private long clientId = -1;
|
||||||
|
|
||||||
@@ -60,10 +60,13 @@ public final class Server {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Server(String ip, String port, String user) {
|
||||||
|
this(ip, port, user, null);
|
||||||
|
}
|
||||||
|
|
||||||
// Server has to deal with ALL network related listen events. This "server" can then interact with the manager to make stuff happen.
|
// Server has to deal with ALL network related listen events. This "server" can then interact with the manager to make stuff happen.
|
||||||
// This prevents data races where events get sent to the game manager but the manager isn't ready yet.
|
// This prevents data races where events get sent to the game manager but the manager isn't ready yet.
|
||||||
public Server(String ip, String port, String user) {
|
public Server(String ip, String port, String user, NettyGatewayServer nettyGatewayServer) {
|
||||||
if (ip.split("\\.").length < 4) {
|
if (ip.split("\\.").length < 4) {
|
||||||
new ErrorPopup("\"" + ip + "\" " + AppContext.getString("is-not-a-valid-ip-address"));
|
new ErrorPopup("\"" + ip + "\" " + AppContext.getString("is-not-a-valid-ip-address"));
|
||||||
return;
|
return;
|
||||||
@@ -83,6 +86,8 @@ public final class Server {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.nettyGatewayServer = nettyGatewayServer;
|
||||||
|
|
||||||
final int reconnectAttempts = 10;
|
final int reconnectAttempts = 10;
|
||||||
|
|
||||||
LoadingWidget loading = new LoadingWidget(
|
LoadingWidget loading = new LoadingWidget(
|
||||||
@@ -113,7 +118,7 @@ public final class Server {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
primary = new ServerView(user, this::sendChallenge);
|
primary = new ServerView(user, this::sendChallenge, clientId);
|
||||||
WidgetContainer.getCurrentView().transitionNextCustom(primary, "disconnect", this::disconnect);
|
WidgetContainer.getCurrentView().transitionNextCustom(primary, "disconnect", this::disconnect);
|
||||||
|
|
||||||
a.unsubscribe("connecting");
|
a.unsubscribe("connecting");
|
||||||
@@ -154,7 +159,8 @@ public final class Server {
|
|||||||
.listen(NetworkEvents.GameMatchResponse.class, this::handleMatchResponse, false, "match-response")
|
.listen(NetworkEvents.GameMatchResponse.class, this::handleMatchResponse, false, "match-response")
|
||||||
.listen(NetworkEvents.GameResultResponse.class, this::handleGameResult, false, "game-result")
|
.listen(NetworkEvents.GameResultResponse.class, this::handleGameResult, false, "game-result")
|
||||||
.listen(NetworkEvents.GameMoveResponse.class, this::handleReceivedMove, false, "game-move")
|
.listen(NetworkEvents.GameMoveResponse.class, this::handleReceivedMove, false, "game-move")
|
||||||
.listen(NetworkEvents.YourTurnResponse.class, this::handleYourTurn, false, "your-turn");
|
.listen(NetworkEvents.YourTurnResponse.class, this::handleYourTurn, false, "your-turn")
|
||||||
|
.listen(NetworkEvents.ClosedConnection.class, this::closedConnection, false, "closed-connection");
|
||||||
|
|
||||||
connectFlow = a;
|
connectFlow = a;
|
||||||
}
|
}
|
||||||
@@ -229,6 +235,7 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (gameController != null) {
|
if (gameController != null) {
|
||||||
|
primary.reEnableButton();
|
||||||
gameController.start();
|
gameController.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,9 +288,27 @@ public final class Server {
|
|||||||
stopScheduler();
|
stopScheduler();
|
||||||
connectFlow.unsubscribeAll();
|
connectFlow.unsubscribeAll();
|
||||||
|
|
||||||
|
if (nettyGatewayServer != null) {
|
||||||
|
nettyGatewayServer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
WidgetContainer.getCurrentView().transitionPrevious();
|
WidgetContainer.getCurrentView().transitionPrevious();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void closedConnection(NetworkEvents.ClosedConnection e) {
|
||||||
|
new EventFlow().addPostEvent(new NetworkEvents.CloseClient(clientId)).postEvent();
|
||||||
|
isPolling = false;
|
||||||
|
stopScheduler();
|
||||||
|
connectFlow.unsubscribeAll();
|
||||||
|
|
||||||
|
if (nettyGatewayServer != null) {
|
||||||
|
nettyGatewayServer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetContainer.getCurrentView().transitionPrevious();
|
||||||
|
WidgetContainer.add(Pos.CENTER, new ErrorPopup("Server closed connection."));
|
||||||
|
}
|
||||||
|
|
||||||
private void forfeitGame() {
|
private void forfeitGame() {
|
||||||
new EventFlow().addPostEvent(new NetworkEvents.SendForfeit(clientId)).postEvent();
|
new EventFlow().addPostEvent(new NetworkEvents.SendForfeit(clientId)).postEvent();
|
||||||
}
|
}
|
||||||
@@ -330,7 +355,9 @@ public final class Server {
|
|||||||
|
|
||||||
private void gamesListFromServerHandler(NetworkEvents.GamelistResponse event) {
|
private void gamesListFromServerHandler(NetworkEvents.GamelistResponse event) {
|
||||||
gameList.clear();
|
gameList.clear();
|
||||||
gameList.addAll(List.of(event.gamelist()));
|
var gl = List.of(event.gamelist());
|
||||||
|
gameList.addAll(gl);
|
||||||
|
primary.updateGameList(gl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void populateGameList() {
|
public void populateGameList() {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ public class GenericGameController implements GameController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onGameFinish(GUIEvents.GameEnded event){
|
private void onGameFinish(GUIEvents.GameEnded event){
|
||||||
logger.info("Game Finished");
|
logger.info("OnlineTurnBasedGame Finished");
|
||||||
String name = event.winner() == -1 ? null : getPlayer(event.winner()).getName();
|
String name = event.winner() == -1 ? null : getPlayer(event.winner()).getName();
|
||||||
gameView.gameOver(event.winOrTie(), name);
|
gameView.gameOver(event.winOrTie(), name);
|
||||||
stop();
|
stop();
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public final class Primitive {
|
|||||||
return imageView;
|
return imageView;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Button button(String key, Runnable onAction, boolean localize) {
|
public static Button button(String key, Runnable onAction, boolean localize, boolean disableOnClick) {
|
||||||
var button = new Button();
|
var button = new Button();
|
||||||
button.getStyleClass().add("button");
|
button.getStyleClass().add("button");
|
||||||
|
|
||||||
@@ -75,6 +75,7 @@ public final class Primitive {
|
|||||||
|
|
||||||
if (onAction != null) {
|
if (onAction != null) {
|
||||||
button.setOnAction(_ -> {
|
button.setOnAction(_ -> {
|
||||||
|
if (disableOnClick) button.setDisable(true);
|
||||||
onAction.run();
|
onAction.run();
|
||||||
playButtonSound();
|
playButtonSound();
|
||||||
});
|
});
|
||||||
@@ -83,8 +84,8 @@ public final class Primitive {
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Button button(String key, Runnable onAction) {
|
public static Button button(String key, Runnable onAction, boolean disableOnClick) {
|
||||||
return button(key, onAction, true);
|
return button(key, onAction, true, disableOnClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TextField input(String promptKey, String text, Consumer<String> onValueChanged, boolean localize) {
|
public static TextField input(String promptKey, String text, Consumer<String> onValueChanged, boolean localize) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class ConfirmWidget implements Widget {
|
|||||||
|
|
||||||
public void addButton(String key, Runnable onClick) {
|
public void addButton(String key, Runnable onClick) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
var button = Primitive.button(key, onClick);
|
var button = Primitive.button(key, onClick, false);
|
||||||
buttonsContainer.getChildren().add(button);
|
buttonsContainer.getChildren().add(button);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class PlayerInfoWidget {
|
|||||||
information.computerDifficulty = depth;
|
information.computerDifficulty = depth;
|
||||||
information.computerThinkTime = thinktime;
|
information.computerThinkTime = thinktime;
|
||||||
this.playerName.setText(getName(name));
|
this.playerName.setText(getName(name));
|
||||||
});
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getName(String name) {
|
private String getName(String name) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public abstract class ViewWidget extends StackWidget {
|
|||||||
|
|
||||||
var backButton = Primitive.button("back", () -> {
|
var backButton = Primitive.button("back", () -> {
|
||||||
view.transitionPrevious();
|
view.transitionPrevious();
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
view.add(Pos.BOTTOM_LEFT, Primitive.vbox(backButton));
|
view.add(Pos.BOTTOM_LEFT, Primitive.vbox(backButton));
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ public abstract class ViewWidget extends StackWidget {
|
|||||||
var customButton = Primitive.button(key, () -> {
|
var customButton = Primitive.button(key, () -> {
|
||||||
runnable.run();
|
runnable.run();
|
||||||
view.transitionPrevious();
|
view.transitionPrevious();
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
view.add(Pos.BOTTOM_LEFT, Primitive.vbox(customButton));
|
view.add(Pos.BOTTOM_LEFT, Primitive.vbox(customButton));
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ public abstract class ViewWidget extends StackWidget {
|
|||||||
|
|
||||||
var backButton = Primitive.button("back", () -> {
|
var backButton = Primitive.button("back", () -> {
|
||||||
view.transitionPrevious();
|
view.transitionPrevious();
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
view.add(Pos.BOTTOM_LEFT, Primitive.vbox(backButton));
|
view.add(Pos.BOTTOM_LEFT, Primitive.vbox(backButton));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ public final class ChallengePopup extends PopupWidget {
|
|||||||
var acceptButton = Primitive.button("accept", () -> {
|
var acceptButton = Primitive.button("accept", () -> {
|
||||||
onAccept.accept(playerInformation);
|
onAccept.accept(playerInformation);
|
||||||
this.hide();
|
this.hide();
|
||||||
});
|
}, false);
|
||||||
var denyButton = Primitive.button("deny", () -> hide());
|
var denyButton = Primitive.button("deny", () -> hide(), false);
|
||||||
|
|
||||||
var leftSection = Primitive.vbox(
|
var leftSection = Primitive.vbox(
|
||||||
challengeText,
|
challengeText,
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ public class EscapePopup extends PopupWidget {
|
|||||||
ViewWidget currentView = WidgetContainer.getCurrentView();
|
ViewWidget currentView = WidgetContainer.getCurrentView();
|
||||||
ArrayList<Node> nodes = new ArrayList<>();
|
ArrayList<Node> nodes = new ArrayList<>();
|
||||||
|
|
||||||
nodes.add(Primitive.button("Continue", this::hide, false)); // TODO, localize
|
nodes.add(Primitive.button("Continue", this::hide, false, false)); // TODO, localize
|
||||||
|
|
||||||
if (!(currentView.getClass().isAssignableFrom(OptionsView.class))) {
|
if (!(currentView.getClass().isAssignableFrom(OptionsView.class))) {
|
||||||
var opt = Primitive.button("options", () -> {
|
var opt = Primitive.button("options", () -> {
|
||||||
hide();
|
hide();
|
||||||
WidgetContainer.getCurrentView().transitionNext(new OptionsView());
|
WidgetContainer.getCurrentView().transitionNext(new OptionsView());
|
||||||
});
|
}, false);
|
||||||
nodes.add(opt);
|
nodes.add(opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,14 +33,14 @@ public class EscapePopup extends PopupWidget {
|
|||||||
if (tut != null) {
|
if (tut != null) {
|
||||||
nodes.add(Primitive.button("tutorialstring", () -> {
|
nodes.add(Primitive.button("tutorialstring", () -> {
|
||||||
WidgetContainer.getCurrentView().add(Pos.CENTER, tut);
|
WidgetContainer.getCurrentView().add(Pos.CENTER, tut);
|
||||||
}));
|
}, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes.add(Primitive.button("quit", () -> {
|
nodes.add(Primitive.button("quit", () -> {
|
||||||
hide();
|
hide();
|
||||||
WidgetContainer.add(Pos.CENTER, new QuitPopup());
|
WidgetContainer.add(Pos.CENTER, new QuitPopup());
|
||||||
}));
|
}, false));
|
||||||
|
|
||||||
add(Pos.CENTER, Primitive.vbox(nodes.toArray(new Node[0])));
|
add(Pos.CENTER, Primitive.vbox(nodes.toArray(new Node[0])));
|
||||||
|
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ public final class SendChallengePopup extends PopupWidget {
|
|||||||
var sendButton = Primitive.button(
|
var sendButton = Primitive.button(
|
||||||
"send",
|
"send",
|
||||||
() -> { onSend.accept(playerInformation, gameChoice.getValue()); this.hide(); }
|
() -> { onSend.accept(playerInformation, gameChoice.getValue()); this.hide(); }
|
||||||
);
|
, false);
|
||||||
|
|
||||||
var cancelButton = Primitive.button("cancel", () -> hide());
|
var cancelButton = Primitive.button("cancel", () -> hide(), false);
|
||||||
|
|
||||||
var leftSection = Primitive.vbox(
|
var leftSection = Primitive.vbox(
|
||||||
challengeText,
|
challengeText,
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ public class BaseTutorialWidget extends PopupWidget implements Updatable {
|
|||||||
this.pages = pages;
|
this.pages = pages;
|
||||||
this.nextScreen = nextScreen;
|
this.nextScreen = nextScreen;
|
||||||
|
|
||||||
previousButton = Primitive.button("goback", () -> { update(false); this.hide(); });
|
previousButton = Primitive.button("goback", () -> { update(false); this.hide(); }, false);
|
||||||
nextButton = Primitive.button(">", () -> update(true));
|
nextButton = Primitive.button(">", () -> update(true), false);
|
||||||
|
|
||||||
var w = Primitive.hbox(
|
var w = Primitive.hbox(
|
||||||
previousButton,
|
previousButton,
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ public class ShowEnableTutorialWidget extends PopupWidget {
|
|||||||
|
|
||||||
public ShowEnableTutorialWidget(Runnable tutorial, Runnable nextScreen, Runnable appSettingsSetter) {
|
public ShowEnableTutorialWidget(Runnable tutorial, Runnable nextScreen, Runnable appSettingsSetter) {
|
||||||
var a = Primitive.hbox(
|
var a = Primitive.hbox(
|
||||||
Primitive.button("ok", () -> { appSettingsSetter.run(); tutorial.run(); this.hide(); }),
|
Primitive.button("ok", () -> { appSettingsSetter.run(); tutorial.run(); this.hide(); }, false),
|
||||||
Primitive.button("no", () -> { appSettingsSetter.run(); nextScreen.run(); this.hide(); }),
|
Primitive.button("no", () -> { appSettingsSetter.run(); nextScreen.run(); this.hide(); }, false),
|
||||||
Primitive.button("never", () -> { AppSettings.getSettings().setTutorialFlag(false); nextScreen.run(); this.hide(); })
|
Primitive.button("never", () -> { AppSettings.getSettings().setTutorialFlag(false); nextScreen.run(); this.hide(); }, false)
|
||||||
);
|
);
|
||||||
|
|
||||||
var txt = Primitive.text("tutorial");
|
var txt = Primitive.text("tutorial");
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public final class GameView extends ViewWidget {
|
|||||||
player2Icon = new Circle();
|
player2Icon = new Circle();
|
||||||
|
|
||||||
if (onForfeit != null) {
|
if (onForfeit != null) {
|
||||||
forfeitButton = Primitive.button("forfeit", () -> onForfeit.run());
|
forfeitButton = Primitive.button("forfeit", () -> onForfeit.run(), false);
|
||||||
} else {
|
} else {
|
||||||
forfeitButton = null;
|
forfeitButton = null;
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ public final class GameView extends ViewWidget {
|
|||||||
exitButton = Primitive.button("exit", () -> {
|
exitButton = Primitive.button("exit", () -> {
|
||||||
onExit.run();
|
onExit.run();
|
||||||
transitionPrevious();
|
transitionPrevious();
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
if (onMessage != null) {
|
if (onMessage != null) {
|
||||||
chatInput = Primitive.input("enter-your-message", "", null);
|
chatInput = Primitive.input("enter-your-message", "", null);
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class LocalMultiplayerView extends ViewWidget {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
var playerSection = setupPlayerSections();
|
var playerSection = setupPlayerSections();
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ public class LocalView extends ViewWidget {
|
|||||||
public LocalView() {
|
public LocalView() {
|
||||||
var ticTacToeButton = Primitive.button("tic-tac-toe", () -> {
|
var ticTacToeButton = Primitive.button("tic-tac-toe", () -> {
|
||||||
transitionNext(new LocalMultiplayerView(GameInformation.Type.TICTACTOE));
|
transitionNext(new LocalMultiplayerView(GameInformation.Type.TICTACTOE));
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
var reversiButton = Primitive.button("reversi", () -> {
|
var reversiButton = Primitive.button("reversi", () -> {
|
||||||
transitionNext(new LocalMultiplayerView(GameInformation.Type.REVERSI));
|
transitionNext(new LocalMultiplayerView(GameInformation.Type.REVERSI));
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
add(Pos.CENTER, Primitive.vbox(
|
add(Pos.CENTER, Primitive.vbox(
|
||||||
ticTacToeButton,
|
ticTacToeButton,
|
||||||
|
|||||||
@@ -9,24 +9,24 @@ public class MainView extends ViewWidget {
|
|||||||
public MainView() {
|
public MainView() {
|
||||||
var localButton = Primitive.button("local", () -> {
|
var localButton = Primitive.button("local", () -> {
|
||||||
transitionNext(new LocalView());
|
transitionNext(new LocalView());
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
var onlineButton = Primitive.button("online", () -> {
|
var onlineButton = Primitive.button("online", () -> {
|
||||||
transitionNext(new OnlineView());
|
transitionNext(new OnlineView());
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
var creditsButton = Primitive.button("credits", () -> {
|
var creditsButton = Primitive.button("credits", () -> {
|
||||||
transitionNext(new CreditsView());
|
transitionNext(new CreditsView());
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
var optionsButton = Primitive.button("options", () -> {
|
var optionsButton = Primitive.button("options", () -> {
|
||||||
transitionNext(new OptionsView());
|
transitionNext(new OptionsView());
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
var quitButton = Primitive.button("quit", () -> {
|
var quitButton = Primitive.button("quit", () -> {
|
||||||
var a = new QuitPopup();
|
var a = new QuitPopup();
|
||||||
a.show(Pos.CENTER);
|
a.show(Pos.CENTER);
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
add(Pos.CENTER, Primitive.vbox(
|
add(Pos.CENTER, Primitive.vbox(
|
||||||
localButton,
|
localButton,
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import javafx.geometry.Pos;
|
|||||||
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.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.server.MasterServer;
|
import org.toop.framework.networking.server.gateway.NettyGatewayServer;
|
||||||
|
import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@@ -28,14 +29,15 @@ public class OnlineView extends ViewWidget {
|
|||||||
serverPortInput.getValue(),
|
serverPortInput.getValue(),
|
||||||
playerNameInput.getValue()
|
playerNameInput.getValue()
|
||||||
);
|
);
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
var localHostButton = Primitive.button("host!", () -> {
|
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));
|
var tps = new TurnBasedGameTypeStore();
|
||||||
|
tps.register("tic-tac-toe", BitboardTicTacToe::new);
|
||||||
|
tps.register("reversi", BitboardReversi::new);
|
||||||
|
|
||||||
|
var a = new NettyGatewayServer(6666, tps, Duration.ofSeconds(10));
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -48,9 +50,10 @@ public class OnlineView extends ViewWidget {
|
|||||||
new Server(
|
new Server(
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
"6666",
|
"6666",
|
||||||
"host"
|
"host",
|
||||||
|
a
|
||||||
);
|
);
|
||||||
}, false);
|
}, false, false);
|
||||||
|
|
||||||
add(Pos.CENTER, Primitive.vbox(
|
add(Pos.CENTER, Primitive.vbox(
|
||||||
serverInformationHeader,
|
serverInformationHeader,
|
||||||
|
|||||||
@@ -1,26 +1,37 @@
|
|||||||
package org.toop.app.widget.view;
|
package org.toop.app.widget.view;
|
||||||
|
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.css.converter.StringConverter;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
import org.toop.app.widget.Primitive;
|
import org.toop.app.widget.Primitive;
|
||||||
import org.toop.app.widget.complex.ViewWidget;
|
import org.toop.app.widget.complex.ViewWidget;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
|
import org.toop.framework.networking.connection.events.NetworkEvents;
|
||||||
|
|
||||||
public final class ServerView extends ViewWidget {
|
public final class ServerView extends ViewWidget {
|
||||||
private final String user;
|
private final String user;
|
||||||
private final Consumer<String> onPlayerClicked;
|
private final Consumer<String> onPlayerClicked;
|
||||||
|
private final long clientId;
|
||||||
|
|
||||||
|
private final ComboBox<String> gameList;
|
||||||
private final ListView<Button> listView;
|
private final ListView<Button> listView;
|
||||||
|
private Button subscribeButton;
|
||||||
|
|
||||||
public ServerView(String user, Consumer<String> onPlayerClicked) {
|
public ServerView(String user, Consumer<String> onPlayerClicked, long clientId) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.onPlayerClicked = onPlayerClicked;
|
this.onPlayerClicked = onPlayerClicked;
|
||||||
|
this.clientId = clientId;
|
||||||
|
|
||||||
|
this.gameList = new ComboBox<>();
|
||||||
this.listView = new ListView<>();
|
this.listView = new ListView<>();
|
||||||
|
|
||||||
setupLayout();
|
setupLayout();
|
||||||
@@ -29,17 +40,26 @@ public final class ServerView extends ViewWidget {
|
|||||||
private void setupLayout() {
|
private void setupLayout() {
|
||||||
var playerHeader = Primitive.header(user, false);
|
var playerHeader = Primitive.header(user, false);
|
||||||
|
|
||||||
|
subscribeButton = Primitive.button(
|
||||||
|
"subscribe",
|
||||||
|
() -> new EventFlow().addPostEvent(new NetworkEvents.SendSubscribe(clientId, gameList.getValue())).postEvent(),
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
); // TODO localize
|
||||||
|
|
||||||
|
var subscribe = Primitive.hbox(gameList, subscribeButton);
|
||||||
|
|
||||||
var playerListSection = Primitive.vbox(
|
var playerListSection = Primitive.vbox(
|
||||||
playerHeader,
|
playerHeader,
|
||||||
Primitive.separator(),
|
Primitive.separator(),
|
||||||
|
subscribe,
|
||||||
listView
|
listView
|
||||||
);
|
);
|
||||||
|
|
||||||
add(Pos.CENTER, playerListSection);
|
add(Pos.CENTER, playerListSection);
|
||||||
|
|
||||||
var disconnectButton = Primitive.button("disconnect", () -> {
|
var disconnectButton = Primitive.button(
|
||||||
transitionPrevious();
|
"disconnect", () -> transitionPrevious(), false);
|
||||||
});
|
|
||||||
|
|
||||||
add(Pos.BOTTOM_LEFT, Primitive.vbox(disconnectButton));
|
add(Pos.BOTTOM_LEFT, Primitive.vbox(disconnectButton));
|
||||||
}
|
}
|
||||||
@@ -49,9 +69,21 @@ public final class ServerView extends ViewWidget {
|
|||||||
listView.getItems().clear();
|
listView.getItems().clear();
|
||||||
|
|
||||||
for (String player : players) {
|
for (String player : players) {
|
||||||
var playerButton = Primitive.button(player, () -> onPlayerClicked.accept(player), false);
|
var playerButton = Primitive.button(player, () -> onPlayerClicked.accept(player), false, false);
|
||||||
listView.getItems().add(playerButton);
|
listView.getItems().add(playerButton);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateGameList(List<String> games) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
gameList.getItems().clear();
|
||||||
|
gameList.setItems(FXCollections.observableArrayList(games));
|
||||||
|
gameList.getSelectionModel().select(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reEnableButton() {
|
||||||
|
subscribeButton.setDisable(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThrea
|
|||||||
import org.toop.framework.gameFramework.model.player.Player;
|
import org.toop.framework.gameFramework.model.player.Player;
|
||||||
import org.toop.framework.gameFramework.view.GUIEvents;
|
import org.toop.framework.gameFramework.view.GUIEvents;
|
||||||
import org.toop.framework.utils.ImmutablePair;
|
import org.toop.framework.utils.ImmutablePair;
|
||||||
|
import org.toop.framework.utils.Pair;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -15,19 +16,30 @@ import static org.toop.framework.gameFramework.GameState.TURN_SKIPPED;
|
|||||||
import static org.toop.framework.gameFramework.GameState.WIN;
|
import static org.toop.framework.gameFramework.GameState.WIN;
|
||||||
|
|
||||||
public class ServerThreadBehaviour extends AbstractThreadBehaviour implements Runnable {
|
public class ServerThreadBehaviour extends AbstractThreadBehaviour implements Runnable {
|
||||||
private Consumer<ImmutablePair<String, Integer>> onPlayerMove;
|
private final Consumer<ImmutablePair<String, Integer>> onPlayerMove;
|
||||||
|
private final Consumer<Pair<GameState, Integer>> onGameEnd;
|
||||||
/**
|
/**
|
||||||
* Creates a new base behaviour for the specified game.
|
* Creates a new base behaviour for the specified game.
|
||||||
*
|
*
|
||||||
* @param game the turn-based game to control
|
* @param game the turn-based game to control
|
||||||
*/
|
*/
|
||||||
public ServerThreadBehaviour(TurnBasedGame game, Consumer<ImmutablePair<String, Integer>> onPlayerMove) {
|
public ServerThreadBehaviour(TurnBasedGame game, Consumer<ImmutablePair<String, Integer>> onPlayerMove, Consumer<Pair<GameState, Integer>> onGameEnd) {
|
||||||
|
this.onPlayerMove = onPlayerMove;
|
||||||
|
this.onGameEnd = onGameEnd;
|
||||||
super(game);
|
super(game);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyPlayerMove(ImmutablePair<String, Integer> pair) {
|
private void notifyPlayerMove(ImmutablePair<String, Integer> pair) {
|
||||||
|
if (onPlayerMove != null) {
|
||||||
onPlayerMove.accept(pair);
|
onPlayerMove.accept(pair);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyGameEnd(ImmutablePair<GameState, Integer> pair) {
|
||||||
|
if (onGameEnd != null) {
|
||||||
|
onGameEnd.accept(pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Starts the game loop in a new thread. */
|
/** Starts the game loop in a new thread. */
|
||||||
@Override
|
@Override
|
||||||
@@ -51,14 +63,12 @@ public class ServerThreadBehaviour extends AbstractThreadBehaviour implements Ru
|
|||||||
PlayResult result = game.play(move);
|
PlayResult result = game.play(move);
|
||||||
|
|
||||||
GameState state = result.state();
|
GameState state = result.state();
|
||||||
|
|
||||||
if (state != TURN_SKIPPED){
|
|
||||||
notifyPlayerMove(new ImmutablePair<>(currentPlayer.getName(), Long.numberOfTrailingZeros(move)));
|
notifyPlayerMove(new ImmutablePair<>(currentPlayer.getName(), Long.numberOfTrailingZeros(move)));
|
||||||
}
|
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case WIN, DRAW -> {
|
case WIN, DRAW -> {
|
||||||
isRunning.set(false);
|
isRunning.set(false);
|
||||||
|
notifyGameEnd(new ImmutablePair<>(state, game.getWinner()));
|
||||||
}
|
}
|
||||||
case NORMAL, TURN_SKIPPED -> { /* continue normally */ }
|
case NORMAL, TURN_SKIPPED -> { /* continue normally */ }
|
||||||
default -> {
|
default -> {
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public class MiniMaxAI extends AbstractAI {
|
|||||||
* Simple heuristic evaluation for Reversi-like games.
|
* Simple heuristic evaluation for Reversi-like games.
|
||||||
* Positive = good for AI, Negative = good for opponent.
|
* Positive = good for AI, Negative = good for opponent.
|
||||||
*
|
*
|
||||||
* @param game Game state
|
* @param game OnlineTurnBasedGame state
|
||||||
* @param aiPlayer AI's player index
|
* @param aiPlayer AI's player index
|
||||||
* @return heuristic score
|
* @return heuristic score
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ package org.toop.framework.game.players;
|
|||||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||||
import org.toop.framework.gameFramework.model.player.AbstractPlayer;
|
import org.toop.framework.gameFramework.model.player.AbstractPlayer;
|
||||||
import org.toop.framework.gameFramework.model.player.Player;
|
import org.toop.framework.gameFramework.model.player.Player;
|
||||||
import org.toop.framework.networking.server.User;
|
import org.toop.framework.networking.server.client.NettyClient;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public class ServerPlayer extends AbstractPlayer {
|
public class ServerPlayer extends AbstractPlayer {
|
||||||
private User user;
|
private NettyClient client;
|
||||||
private CompletableFuture<Long> lastMove;
|
private CompletableFuture<Long> lastMove;
|
||||||
|
|
||||||
public ServerPlayer(User user) {
|
public ServerPlayer(NettyClient client) {
|
||||||
super(user.name());
|
super(client.name());
|
||||||
this.user = user;
|
this.client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMove(long move) {
|
public void setMove(long move) {
|
||||||
@@ -30,7 +30,7 @@ public class ServerPlayer extends AbstractPlayer {
|
|||||||
public long getMove(TurnBasedGame game) {
|
public long getMove(TurnBasedGame game) {
|
||||||
lastMove = new CompletableFuture<>();
|
lastMove = new CompletableFuture<>();
|
||||||
System.out.println("Sending yourturn");
|
System.out.println("Sending yourturn");
|
||||||
user.sendMessage("SVR GAME YOURTURN {TURNMESSAGE: \"<bericht voor deze beurt>\"}\n");
|
client.send("SVR GAME YOURTURN {TURNMESSAGE: \"<bericht voor deze beurt>\"}\n");
|
||||||
try {
|
try {
|
||||||
return lastMove.get();
|
return lastMove.get();
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
@@ -38,9 +38,4 @@ public class ServerPlayer extends AbstractPlayer {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ package org.toop.framework.gameFramework;
|
|||||||
* Represents the current state of a turn-based game.
|
* Represents the current state of a turn-based game.
|
||||||
*/
|
*/
|
||||||
public enum GameState {
|
public enum GameState {
|
||||||
/** Game is ongoing and no special condition applies. */
|
/** OnlineTurnBasedGame is ongoing and no special condition applies. */
|
||||||
NORMAL,
|
NORMAL,
|
||||||
|
|
||||||
/** Game ended in a draw. */
|
/** OnlineTurnBasedGame ended in a draw. */
|
||||||
DRAW,
|
DRAW,
|
||||||
|
|
||||||
/** Game ended with a win for a player. */
|
/** OnlineTurnBasedGame ended with a win for a player. */
|
||||||
WIN,
|
WIN,
|
||||||
|
|
||||||
/** Next player's turn was skipped. */
|
/** Next player's turn was skipped. */
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public abstract class AbstractPlayer implements Player {
|
|||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
protected AbstractPlayer(String name) {
|
protected AbstractPlayer(String name) {
|
||||||
|
System.out.println("Player " + name + " has been created");
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ public abstract class AbstractPlayer implements Player {
|
|||||||
throw new UnsupportedOperationException("Not supported yet.");
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName(){
|
public final String getName(){
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import io.netty.util.CharsetUtil;
|
|||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.toop.framework.eventbus.bus.EventBus;
|
import org.toop.framework.eventbus.bus.EventBus;
|
||||||
|
import org.toop.framework.networking.connection.events.NetworkEvents;
|
||||||
import org.toop.framework.networking.connection.exceptions.CouldNotConnectException;
|
import org.toop.framework.networking.connection.exceptions.CouldNotConnectException;
|
||||||
import org.toop.framework.networking.connection.handlers.NetworkingGameClientHandler;
|
import org.toop.framework.networking.connection.handlers.NetworkingGameClientHandler;
|
||||||
import org.toop.framework.networking.connection.interfaces.NetworkingClient;
|
import org.toop.framework.networking.connection.interfaces.NetworkingClient;
|
||||||
@@ -23,6 +24,7 @@ public class TournamentNetworkingClient implements NetworkingClient {
|
|||||||
|
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private Channel channel;
|
private Channel channel;
|
||||||
|
private long clientId;
|
||||||
|
|
||||||
public TournamentNetworkingClient(EventBus eventBus) {
|
public TournamentNetworkingClient(EventBus eventBus) {
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
@@ -35,6 +37,7 @@ public class TournamentNetworkingClient implements NetworkingClient {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connect(long clientId, String host, int port) throws CouldNotConnectException {
|
public void connect(long clientId, String host, int port) throws CouldNotConnectException {
|
||||||
|
this.clientId = clientId;
|
||||||
try {
|
try {
|
||||||
Bootstrap bootstrap = new Bootstrap();
|
Bootstrap bootstrap = new Bootstrap();
|
||||||
EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
|
EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
|
||||||
@@ -75,6 +78,7 @@ public class TournamentNetworkingClient implements NetworkingClient {
|
|||||||
logger.info("Connection {} sent message: '{}' ", this.channel.remoteAddress(), literalMsg);
|
logger.info("Connection {} sent message: '{}' ", this.channel.remoteAddress(), literalMsg);
|
||||||
} else {
|
} else {
|
||||||
logger.warn("Cannot send message: '{}', connection inactive. ", literalMsg);
|
logger.warn("Cannot send message: '{}', connection inactive. ", literalMsg);
|
||||||
|
eventBus.post(new NetworkEvents.ClosedConnection(clientId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
package org.toop.framework.networking.server;
|
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
|
||||||
import org.apache.maven.surefire.shared.utils.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ConnectionHandler extends SimpleChannelInboundHandler<String> {
|
|
||||||
|
|
||||||
private final User user;
|
|
||||||
private final Server server;
|
|
||||||
|
|
||||||
public ConnectionHandler(User user, Server server) {
|
|
||||||
this.user = user;
|
|
||||||
this.server = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String returnQuotedString(Iterator<String> strings) { // TODO more places this could be useful
|
|
||||||
return "\"" + StringUtils.join(strings, "\",\"") + "\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelActive(ChannelHandlerContext ctx) {
|
|
||||||
ctx.writeAndFlush("WELCOME " + user.id() + "\n");
|
|
||||||
|
|
||||||
user.setCtx(ctx);
|
|
||||||
server.addUser(user); // TODO set correct name on login
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
|
|
||||||
|
|
||||||
IO.println(msg);
|
|
||||||
|
|
||||||
ParsedMessage p = parse(msg);
|
|
||||||
if (p == null) return;
|
|
||||||
|
|
||||||
IO.println(p.command() + " " + Arrays.toString(p.args()));
|
|
||||||
|
|
||||||
switch (p.command()) {
|
|
||||||
case "ping" -> ctx.writeAndFlush("PONG\n");
|
|
||||||
case "login" -> handleLogin(p);
|
|
||||||
case "get" -> handleGet(p);
|
|
||||||
case "subscribe" -> handleSubscribe(p);
|
|
||||||
case "move" -> handleMove(p);
|
|
||||||
case "challenge" -> handleChallenge(p);
|
|
||||||
case "message" -> handleMessage(p);
|
|
||||||
case "help" -> handleHelp(p);
|
|
||||||
default -> ctx.writeAndFlush("ERROR Unknown command\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DO NOT INVERT
|
|
||||||
private boolean hasArgs(String... args) {
|
|
||||||
return (args.length >= 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleLogin(ParsedMessage p) {
|
|
||||||
if (!hasArgs(p.args())) return;
|
|
||||||
|
|
||||||
user.setName(p.args()[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleGet(ParsedMessage p) {
|
|
||||||
if (!hasArgs(p.args())) return;
|
|
||||||
|
|
||||||
switch (p.args()[0]) {
|
|
||||||
case "playerlist" -> {
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSubscribe(ParsedMessage p) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleHelp(ParsedMessage p) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleMessage(ParsedMessage p) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleChallenge(ParsedMessage p) {
|
|
||||||
if (!hasArgs(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 \n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
server.acceptChallenge(id);
|
|
||||||
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
user.sendMessage("ERR id is not a valid number or too big \n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
server.challengeUser(user.name(), p.args()[0], p.args()[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleMove(ParsedMessage p) {
|
|
||||||
if(!hasArgs(p.args())) return;
|
|
||||||
|
|
||||||
// TODO check if not number
|
|
||||||
user.serverPlayer().setMove(1L << Integer.parseInt(p.args()[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ParsedMessage parse(String msg) {
|
|
||||||
// TODO, what if empty string.
|
|
||||||
|
|
||||||
if (msg.isEmpty()) return null;
|
|
||||||
|
|
||||||
msg = msg.trim().toLowerCase();
|
|
||||||
|
|
||||||
List<String> parts = new LinkedList<>(List.of(msg.split(" ")));
|
|
||||||
|
|
||||||
if (parts.size() > 1) {
|
|
||||||
String command = parts.removeFirst();
|
|
||||||
return new ParsedMessage(command, parts.toArray(String[]::new));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new ParsedMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
||||||
cause.printStackTrace();
|
|
||||||
ctx.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelInactive(ChannelHandlerContext ctx) {
|
|
||||||
server.removeUser(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package org.toop.framework.networking.server;
|
|
||||||
|
|
||||||
import org.toop.framework.game.gameThreads.ServerThreadBehaviour;
|
|
||||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
|
||||||
|
|
||||||
public class Game implements OnlineGame<TurnBasedGame> {
|
|
||||||
|
|
||||||
private long id;
|
|
||||||
private User[] users;
|
|
||||||
private TurnBasedGame game;
|
|
||||||
private ServerThreadBehaviour gameThread;
|
|
||||||
|
|
||||||
public Game(TurnBasedGame game, User... users) {
|
|
||||||
this.game = game;
|
|
||||||
this.gameThread = new ServerThreadBehaviour(game, (pair) -> notifyMoveMade(pair.getLeft(), pair.getRight()));
|
|
||||||
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
|
|
||||||
public long id() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TurnBasedGame game() {
|
|
||||||
return game;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User[] users() {
|
|
||||||
return users;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(){
|
|
||||||
this.gameThread.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package org.toop.framework.networking.server;
|
|
||||||
|
|
||||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
|
|
||||||
public class GameDefinition<T> {
|
|
||||||
private final String name;
|
|
||||||
private final Class<T> game;
|
|
||||||
|
|
||||||
public GameDefinition(String name, Class<T> game) {
|
|
||||||
this.name = name;
|
|
||||||
this.game = game;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String name() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T create(String... users) {
|
|
||||||
try {
|
|
||||||
return game.getDeclaredConstructor().newInstance(users);
|
|
||||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,22 @@
|
|||||||
package org.toop.framework.networking.server;
|
package org.toop.framework.networking.server;
|
||||||
|
|
||||||
public interface GameServer {
|
import java.util.List;
|
||||||
// List<?> gameTypes();
|
|
||||||
// List<?> ongoingGames();
|
public interface GameServer<GAMETYPE, CLIENT, CHALLENGEIDTYPE> {
|
||||||
// void startGame(String gameType, User... users);
|
void startGame(String gameType, CLIENT... clients);
|
||||||
// String[] onlineUsers();
|
|
||||||
|
void addClient(CLIENT client);
|
||||||
|
void removeClient(CLIENT client);
|
||||||
|
|
||||||
|
void challengeClient(String fromClientName, String toClientName, String gameTypeKey);
|
||||||
|
void acceptChallenge(CHALLENGEIDTYPE challengeId);
|
||||||
|
|
||||||
|
void subscribeClient(String clientName, String gameTypeKey);
|
||||||
|
void unsubscribeClient(String clientName);
|
||||||
|
|
||||||
|
List<String> gameTypes();
|
||||||
|
List<OnlineGame<GAMETYPE>> ongoingGames();
|
||||||
|
|
||||||
|
List<CLIENT> onlineUsers();
|
||||||
|
void shutdown();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
package org.toop.framework.networking.server;
|
|
||||||
|
|
||||||
import io.netty.bootstrap.ServerBootstrap;
|
|
||||||
import io.netty.channel.*;
|
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
|
||||||
import io.netty.channel.nio.NioIoHandler;
|
|
||||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
|
||||||
import io.netty.handler.codec.LineBasedFrameDecoder;
|
|
||||||
import io.netty.handler.codec.string.StringDecoder;
|
|
||||||
import io.netty.handler.codec.string.StringEncoder;
|
|
||||||
import io.netty.handler.logging.LogLevel;
|
|
||||||
import io.netty.handler.logging.LoggingHandler;
|
|
||||||
import org.toop.framework.SnowflakeGenerator;
|
|
||||||
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<String, Class<? extends TurnBasedGame>> gameTypes, Duration challengeDuration) {
|
|
||||||
this.port = port;
|
|
||||||
this.gs = new Server(gameTypes, challengeDuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() throws InterruptedException {
|
|
||||||
|
|
||||||
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
|
|
||||||
EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
|
||||||
bootstrap.group(workerGroup);
|
|
||||||
bootstrap.channel(NioServerSocketChannel.class);
|
|
||||||
bootstrap.option(ChannelOption.SO_BACKLOG, 128);
|
|
||||||
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
|
|
||||||
bootstrap.handler(new LoggingHandler(LogLevel.INFO));
|
|
||||||
bootstrap.childHandler(
|
|
||||||
new ChannelInitializer<NioSocketChannel>() {
|
|
||||||
@Override
|
|
||||||
protected void initChannel(NioSocketChannel ch) {
|
|
||||||
|
|
||||||
ChannelPipeline pipeline = ch.pipeline();
|
|
||||||
|
|
||||||
pipeline.addLast(new LineBasedFrameDecoder(8192));
|
|
||||||
pipeline.addLast(new StringDecoder());
|
|
||||||
pipeline.addLast(new StringEncoder());
|
|
||||||
|
|
||||||
long userid = SnowflakeGenerator.nextId();
|
|
||||||
User user = new User(userid, ""+userid);
|
|
||||||
pipeline.addLast(new ConnectionHandler(user, gs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
ChannelFuture future = bootstrap.bind(port).sync();
|
|
||||||
System.out.println("MasterServer listening on port " + port);
|
|
||||||
|
|
||||||
future.channel().closeFuture().sync();
|
|
||||||
} finally {
|
|
||||||
bossGroup.shutdownGracefully();
|
|
||||||
workerGroup.shutdownGracefully();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package org.toop.framework.networking.server;
|
|
||||||
|
|
||||||
public interface MessageStore {
|
|
||||||
void add(String message);
|
|
||||||
String get();
|
|
||||||
void reset();
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package org.toop.framework.networking.server;
|
package org.toop.framework.networking.server;
|
||||||
|
|
||||||
|
import org.toop.framework.networking.server.client.NettyClient;
|
||||||
|
|
||||||
public interface OnlineGame<T> {
|
public interface OnlineGame<T> {
|
||||||
long id();
|
long id();
|
||||||
T game();
|
T game();
|
||||||
User[] users();
|
NettyClient[] users();
|
||||||
void start();
|
void start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package org.toop.framework.networking.server;
|
||||||
|
|
||||||
|
import org.toop.framework.game.gameThreads.ServerThreadBehaviour;
|
||||||
|
import org.toop.framework.gameFramework.GameState;
|
||||||
|
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||||
|
import org.toop.framework.networking.server.client.NettyClient;
|
||||||
|
|
||||||
|
public class OnlineTurnBasedGame implements OnlineGame<TurnBasedGame> {
|
||||||
|
|
||||||
|
private long id;
|
||||||
|
private NettyClient[] clients;
|
||||||
|
private TurnBasedGame game;
|
||||||
|
private ServerThreadBehaviour gameThread;
|
||||||
|
|
||||||
|
public OnlineTurnBasedGame(TurnBasedGame game, NettyClient... clients) {
|
||||||
|
this.game = game;
|
||||||
|
this.gameThread = new ServerThreadBehaviour(
|
||||||
|
game,
|
||||||
|
(pair) -> notifyMoveMade(pair.getLeft(), pair.getRight()),
|
||||||
|
(pair) -> notifyGameEnd(pair.getLeft(), pair.getRight())
|
||||||
|
);
|
||||||
|
this.clients = clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyMoveMade(String speler, int move){
|
||||||
|
for (NettyClient client : clients) {
|
||||||
|
client.send(String.format("SVR GAME MOVE {PLAYER: \"%s\", MOVE: \"%s\", DETAILS: \"<reactie spel op zet>\"}\n", speler, move));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyGameEnd(GameState state, int winner){
|
||||||
|
if (state == GameState.DRAW){
|
||||||
|
for (NettyClient client : clients) {
|
||||||
|
client.send(String.format("SVR GAME DRAW {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"NettyClient disconnected\"}\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
clients[winner].send(String.format("SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"NettyClient disconnected\"}\n"));
|
||||||
|
clients[(winner + 1)%2].send(String.format("SVR GAME LOSS {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"NettyClient disconnected\"}\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long id() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.toop.framework.gameFramework.model.game.TurnBasedGame game() {
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NettyClient[] users() {
|
||||||
|
return clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(){
|
||||||
|
this.gameThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package org.toop.framework.networking.server;
|
|
||||||
|
|
||||||
public class Parser {
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package org.toop.framework.networking.server;
|
|
||||||
|
|
||||||
public interface ServableGame {
|
|
||||||
}
|
|
||||||
@@ -2,81 +2,88 @@ package org.toop.framework.networking.server;
|
|||||||
|
|
||||||
import org.toop.framework.game.players.ServerPlayer;
|
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.networking.server.challenges.gamechallenge.GameChallenge;
|
||||||
|
import org.toop.framework.networking.server.challenges.gamechallenge.GameChallengeTimer;
|
||||||
|
import org.toop.framework.networking.server.client.NettyClient;
|
||||||
|
import org.toop.framework.networking.server.stores.ClientStore;
|
||||||
|
import org.toop.framework.networking.server.stores.TurnBasedGameStore;
|
||||||
|
import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore;
|
||||||
import org.toop.framework.utils.ImmutablePair;
|
import org.toop.framework.utils.ImmutablePair;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
public class Server implements GameServer {
|
public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
|
||||||
|
|
||||||
final private Map<String, Class<? extends TurnBasedGame>> gameTypes;
|
final private TurnBasedGameTypeStore gameTypesStore;
|
||||||
final private Map<Long, User> users = new ConcurrentHashMap<>();
|
final private ClientStore<Long, NettyClient> clientStore;
|
||||||
final private List<GameChallenge> gameChallenges = new CopyOnWriteArrayList<>();
|
final private List<GameChallenge> gameChallenges = new CopyOnWriteArrayList<>();
|
||||||
final private List<OnlineGame<TurnBasedGame>> games = new CopyOnWriteArrayList<>();
|
final private TurnBasedGameStore gameStore;
|
||||||
|
|
||||||
|
final private ConcurrentHashMap<String, List<String>> subscriptions; // TODO move to own store / manager
|
||||||
|
|
||||||
final private Duration challengeDuration;
|
final private Duration challengeDuration;
|
||||||
final private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
final private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
public Server(Map<String, Class<? extends TurnBasedGame>> gameTypes, Duration challengeDuration) {
|
public Server(
|
||||||
this.gameTypes = gameTypes;
|
Duration challengeDuration,
|
||||||
|
TurnBasedGameTypeStore turnBasedGameTypeStore,
|
||||||
|
ClientStore<Long, NettyClient> clientStore,
|
||||||
|
TurnBasedGameStore gameStore
|
||||||
|
|
||||||
|
) {
|
||||||
|
this.gameTypesStore = turnBasedGameTypeStore;
|
||||||
this.challengeDuration = challengeDuration;
|
this.challengeDuration = challengeDuration;
|
||||||
|
this.clientStore = clientStore;
|
||||||
|
this.gameStore = gameStore;
|
||||||
|
this.subscriptions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
scheduler.schedule(this::serverTask, 0, TimeUnit.MILLISECONDS);
|
scheduler.schedule(this::serverTask, 0, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void serverTask() {
|
@Override
|
||||||
checkChallenges();
|
public void addClient(NettyClient client) {
|
||||||
scheduler.schedule(this::serverTask, 500, TimeUnit.MILLISECONDS);
|
clientStore.add(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addUser(User user) {
|
@Override
|
||||||
users.putIfAbsent(user.id(), user);
|
public void removeClient(NettyClient client) {
|
||||||
}
|
clientStore.remove(client.id());
|
||||||
|
|
||||||
public void removeUser(User user) {
|
|
||||||
users.remove(user.id());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public List<String> gameTypes() {
|
public List<String> gameTypes() {
|
||||||
return gameTypes.keySet().stream().toList();
|
return new ArrayList<>(gameTypesStore.all().keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public List<OnlineGame<TurnBasedGame>> ongoingGames() {
|
public List<OnlineGame<TurnBasedGame>> ongoingGames() {
|
||||||
return games;
|
return gameStore.all().stream().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public User getUser(String username) {
|
@Override
|
||||||
return users.values().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().orElse(null);
|
public void challengeClient(String fromUser, String toUser, String gameType) {
|
||||||
}
|
|
||||||
|
|
||||||
public User getUser(long id) {
|
NettyClient from = getUser(fromUser);
|
||||||
return users.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void challengeUser(String fromUser, String toUser, String gameType) {
|
|
||||||
|
|
||||||
User from = getUser(fromUser);
|
|
||||||
if (from == null) {
|
if (from == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gameTypes.containsKey(gameType)) {
|
if (!gameTypesStore.all().containsKey(gameType)) {
|
||||||
from.sendMessage("ERR gametype not found \n");
|
from.send("ERR gametype not found \n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
User to = getUser(toUser);
|
NettyClient to = getUser(toUser);
|
||||||
if (to == null) {
|
if (to == null) {
|
||||||
from.sendMessage("ERR user not found \n");
|
from.send("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.send(
|
||||||
"SVR GAME CHALLENGE {CHALLENGER: \"%s\", CHALLENGENUMBER: \"%s\", GAMETYPE: \"%s\"} \n"
|
"SVR GAME CHALLENGE {CHALLENGER: \"%s\", CHALLENGENUMBER: \"%s\", GAMETYPE: \"%s\"} \n"
|
||||||
.formatted(from.name(), ch.id(), gameType)
|
.formatted(from.name(), ch.id(), gameType)
|
||||||
);
|
);
|
||||||
@@ -90,29 +97,91 @@ public class Server implements GameServer {
|
|||||||
gameChallenges.addLast(ch);
|
gameChallenges.addLast(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void warnUserExpiredChallenge(User user, long challengeId) {
|
@Override
|
||||||
user.sendMessage("SVR GAME CHALLENGE CANCELLED {CHALLENGENUMBER: \"" + challengeId + "\"}" + "\n");
|
public void acceptChallenge(Long challengeId) {
|
||||||
|
for (var challenge : gameChallenges) {
|
||||||
|
if (challenge.id() == challengeId) {
|
||||||
|
startGame(challenge.acceptChallenge(), challenge.getUsers());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidChallenge(GameChallenge gameChallenge) {
|
|
||||||
for (var user : gameChallenge.getUsers()) {
|
|
||||||
if (users.get(user.id()) == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.game() != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameChallenge.isExpired()) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
@Override
|
||||||
|
public void subscribeClient(String clientName, String gameTypeKey) {
|
||||||
|
|
||||||
|
if (!gameTypesStore.all().containsKey(gameTypeKey)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkChallenges() {
|
subscriptions.forEach((_, clientNames) -> clientNames.remove(clientName));
|
||||||
|
subscriptions.computeIfAbsent(
|
||||||
|
gameTypeKey,
|
||||||
|
_ -> new ArrayList<>())
|
||||||
|
.add(clientName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unsubscribeClient(String clientName) {
|
||||||
|
subscriptions.forEach((_, clientNames) -> clientNames.remove(clientName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startGame(String gameType, NettyClient... clients) {
|
||||||
|
IO.println("------------------------------------------");
|
||||||
|
|
||||||
|
IO.println("USERS: " + clients.length + " " + Arrays.stream(clients).toList().toString());
|
||||||
|
|
||||||
|
if (!gameTypesStore.all().containsKey(gameType)) return;
|
||||||
|
|
||||||
|
IO.println("------------------------------------------");
|
||||||
|
|
||||||
|
try {
|
||||||
|
ServerPlayer[] players = new ServerPlayer[clients.length];
|
||||||
|
var game = new OnlineTurnBasedGame(gameTypesStore.create(gameType), clients);
|
||||||
|
|
||||||
|
for (int i = 0; i < clients.length; i++) {
|
||||||
|
players[i] = new ServerPlayer(clients[i]);
|
||||||
|
clients[i].addGame(new ImmutablePair<>(game, players[i]));
|
||||||
|
}
|
||||||
|
System.out.println("Starting OnlineTurnBasedGame");
|
||||||
|
|
||||||
|
game.game().init(players);
|
||||||
|
gameStore.add(game);
|
||||||
|
|
||||||
|
clients[0].send(String.format("SVR GAME MATCH {PLAYERTOMOVE: \"%s\", GAMETYPE: \"%s\", OPPONENT: \"%s\"}\n",
|
||||||
|
clients[0].name(),
|
||||||
|
gameType,
|
||||||
|
clients[1].name()));
|
||||||
|
clients[1].send(String.format("SVR GAME MATCH {PLAYERTOMOVE: \"%s\", GAMETYPE: \"%s\", OPPONENT: \"%s\"}\n",
|
||||||
|
clients[0].name(),
|
||||||
|
gameType,
|
||||||
|
clients[0].name()));
|
||||||
|
game.start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
IO.println("ERROR: Failed to start OnlineTurnBasedGame");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<NettyClient> onlineUsers() {
|
||||||
|
return clientStore.all().stream().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
scheduler.shutdown();
|
||||||
|
gameChallenges.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void serverTask() {
|
||||||
|
checkChallenges();
|
||||||
|
checkSubscriptions();
|
||||||
|
scheduler.schedule(this::serverTask, 500, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkChallenges() {
|
||||||
for (int i = gameChallenges.size() - 1; i >= 0; i--) {
|
for (int i = gameChallenges.size() - 1; i >= 0; i--) {
|
||||||
var challenge = gameChallenges.get(i);
|
var challenge = gameChallenges.get(i);
|
||||||
|
|
||||||
@@ -127,56 +196,63 @@ public class Server implements GameServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void acceptChallenge(long challengeId) {
|
private void checkSubscriptions() {
|
||||||
for (var challenge : gameChallenges) {
|
if (subscriptions.isEmpty()) return;
|
||||||
if (challenge.id() == challengeId) {
|
|
||||||
startGame(challenge.acceptChallenge(), challenge.getUsers());
|
List<String> keys = List.copyOf(subscriptions.keySet());
|
||||||
break;
|
Random ran = new Random();
|
||||||
|
|
||||||
|
for (String key : keys) {
|
||||||
|
List<String> userNames = subscriptions.get(key);
|
||||||
|
if (userNames.size() < 2) continue;
|
||||||
|
|
||||||
|
while (userNames.size() > 1) {
|
||||||
|
int left = ran.nextInt(userNames.size());
|
||||||
|
int right;
|
||||||
|
do {
|
||||||
|
right = ran.nextInt(userNames.size());
|
||||||
|
} while (left == right);
|
||||||
|
|
||||||
|
String userLeft = userNames.get(left);
|
||||||
|
String userRight = userNames.get(right);
|
||||||
|
|
||||||
|
int first = Math.max(left, right);
|
||||||
|
int second = Math.min(left, right);
|
||||||
|
userNames.remove(first);
|
||||||
|
userNames.remove(second);
|
||||||
|
|
||||||
|
startGame(key, getUser(userLeft), getUser(userRight));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<GameChallenge> gameChallenges() {
|
private NettyClient getUser(String username) {
|
||||||
return gameChallenges;
|
return clientStore.all().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startGame(String gameType, User... users) {
|
private NettyClient getUser(long id) {
|
||||||
if (!gameTypes.containsKey(gameType)) return;
|
return clientStore.get(id);
|
||||||
|
|
||||||
try {
|
|
||||||
ServerPlayer[] players = new ServerPlayer[users.length];
|
|
||||||
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);
|
|
||||||
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) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<User> onlineUsers() {
|
private void warnUserExpiredChallenge(NettyClient client, long challengeId) {
|
||||||
return users.values().stream().toList();
|
client.send("SVR GAME CHALLENGE CANCELLED {CHALLENGENUMBER: \"" + challengeId + "\"}" + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeServer() {
|
private boolean isValidChallenge(GameChallenge gameChallenge) { // TODO move to challenge class
|
||||||
scheduler.shutdown();
|
for (var user : gameChallenge.getUsers()) {
|
||||||
gameChallenges.clear();
|
if (clientStore.get(user.id()) == null) {
|
||||||
games.clear();
|
return false;
|
||||||
users.clear();
|
}
|
||||||
gameTypes.clear();
|
|
||||||
|
if (user.game() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameChallenge.isExpired()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package org.toop.framework.networking.server;
|
|
||||||
|
|
||||||
import java.util.Queue;
|
|
||||||
|
|
||||||
public class ServerMessageStore implements MessageStore {
|
|
||||||
|
|
||||||
Queue<String> messageQueue;
|
|
||||||
|
|
||||||
public ServerMessageStore(Queue<String> messageQueue) {
|
|
||||||
this.messageQueue = messageQueue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(String message) {
|
|
||||||
messageQueue.offer(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String get() {
|
|
||||||
return messageQueue.poll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() {
|
|
||||||
messageQueue.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package org.toop.framework.networking.server;
|
|
||||||
|
|
||||||
import org.toop.framework.game.players.ServerPlayer;
|
|
||||||
import org.toop.framework.utils.Pair;
|
|
||||||
|
|
||||||
public interface ServerUser {
|
|
||||||
long id();
|
|
||||||
String name();
|
|
||||||
Game game();
|
|
||||||
ServerPlayer serverPlayer();
|
|
||||||
void addGame(Pair<Game, ServerPlayer> gamePair);
|
|
||||||
void removeGame();
|
|
||||||
void setName(String name);
|
|
||||||
void sendMessage(String message);
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
package org.toop.framework.networking.server;
|
package org.toop.framework.networking.server.challenges.gamechallenge;
|
||||||
|
|
||||||
import org.toop.framework.SnowflakeGenerator;
|
import org.toop.framework.SnowflakeGenerator;
|
||||||
|
import org.toop.framework.networking.server.client.NettyClient;
|
||||||
|
import org.toop.framework.utils.SimpleTimer;
|
||||||
|
|
||||||
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 User from;
|
private final NettyClient from;
|
||||||
private final User to;
|
private final NettyClient 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(User from, User to, String gameType, SimpleTimer timer) {
|
public GameChallenge(NettyClient from, NettyClient 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 +25,8 @@ public class GameChallenge {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public User[] getUsers() {
|
public NettyClient[] getUsers() {
|
||||||
return new User[]{from, to};
|
return new NettyClient[]{from, to};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void forceExpire() {
|
public void forceExpire() {
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
package org.toop.framework.networking.server;
|
package org.toop.framework.networking.server.challenges.gamechallenge;
|
||||||
|
|
||||||
|
import org.toop.framework.utils.SimpleTimer;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.toop.framework.networking.server.client;
|
||||||
|
|
||||||
|
import org.toop.framework.networking.server.OnlineTurnBasedGame;
|
||||||
|
import org.toop.framework.utils.Pair;
|
||||||
|
|
||||||
|
public interface Client<G, P> {
|
||||||
|
long id();
|
||||||
|
|
||||||
|
String name();
|
||||||
|
void setName(String name);
|
||||||
|
|
||||||
|
OnlineTurnBasedGame game();
|
||||||
|
P player();
|
||||||
|
|
||||||
|
void addGame(Pair<G, P> gamePair);
|
||||||
|
void clearGame();
|
||||||
|
|
||||||
|
void send(String message);
|
||||||
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
package org.toop.framework.networking.server;
|
package org.toop.framework.networking.server.client;
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import org.toop.framework.game.players.ServerPlayer;
|
import org.toop.framework.game.players.ServerPlayer;
|
||||||
|
import org.toop.framework.networking.server.OnlineTurnBasedGame;
|
||||||
import org.toop.framework.utils.Pair;
|
import org.toop.framework.utils.Pair;
|
||||||
|
|
||||||
public class User implements ServerUser {
|
public class NettyClient implements Client<OnlineTurnBasedGame, ServerPlayer> {
|
||||||
final private long id;
|
final private long id;
|
||||||
|
private ChannelHandlerContext ctx;
|
||||||
private String name;
|
private String name;
|
||||||
private Pair<Game, ServerPlayer> gamePair;
|
private Pair<OnlineTurnBasedGame, ServerPlayer> gamePair;
|
||||||
private ChannelHandlerContext connectionContext;
|
|
||||||
|
|
||||||
public User(long userId, String name) {
|
public NettyClient(long userId, String name) {
|
||||||
this.id = userId;
|
this.id = userId;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
@@ -26,23 +27,27 @@ public class User implements ServerUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addGame(Pair<Game, ServerPlayer> gamePair) {
|
public void addGame(Pair<OnlineTurnBasedGame, ServerPlayer> gamePair) {
|
||||||
if (this.gamePair == null) {
|
if (this.gamePair == null) {
|
||||||
this.gamePair = gamePair;
|
this.gamePair = gamePair;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeGame() {
|
public void clearGame() {
|
||||||
this.gamePair = null;
|
this.gamePair = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Game game() {
|
public OnlineTurnBasedGame game() {
|
||||||
|
if (this.gamePair == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return this.gamePair.getLeft();
|
return this.gamePair.getLeft();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerPlayer serverPlayer() {
|
@Override
|
||||||
|
public ServerPlayer player() {
|
||||||
return this.gamePair.getRight();
|
return this.gamePair.getRight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,18 +57,12 @@ public class User implements ServerUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendMessage(String message) {
|
public void send(String message) {
|
||||||
IO.println(message);
|
IO.println(message);
|
||||||
ctx().channel().writeAndFlush(message);
|
ctx.channel().writeAndFlush(message + "\r\n");
|
||||||
}
|
|
||||||
|
|
||||||
public ChannelHandlerContext ctx() {
|
|
||||||
return connectionContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCtx(ChannelHandlerContext ctx) {
|
public void setCtx(ChannelHandlerContext ctx) {
|
||||||
this.connectionContext = ctx;
|
this.ctx = ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.toop.framework.networking.server.connectionHandler;
|
||||||
|
|
||||||
|
import org.toop.framework.networking.server.client.Client;
|
||||||
|
|
||||||
|
public interface ClientSession<G, P> {
|
||||||
|
Client<G, P> client();
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package org.toop.framework.networking.server.connectionHandler;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import org.toop.framework.game.players.ServerPlayer;
|
||||||
|
import org.toop.framework.networking.server.OnlineTurnBasedGame;
|
||||||
|
import org.toop.framework.networking.server.client.NettyClient;
|
||||||
|
import org.toop.framework.networking.server.handlers.Handler;
|
||||||
|
import org.toop.framework.networking.server.parsing.ParsedMessage;
|
||||||
|
import org.toop.framework.networking.server.Server;
|
||||||
|
import org.toop.framework.networking.server.client.Client;
|
||||||
|
import org.toop.framework.networking.server.parsing.Parser;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class NettyClientSession extends SimpleChannelInboundHandler<String> implements ClientSession<OnlineTurnBasedGame, ServerPlayer> {
|
||||||
|
|
||||||
|
private final NettyClient client;
|
||||||
|
private final Server server;
|
||||||
|
private final Handler<ParsedMessage> handler;
|
||||||
|
|
||||||
|
public NettyClientSession(NettyClient client, Server server, Handler<ParsedMessage> handler) {
|
||||||
|
this.client = client;
|
||||||
|
this.server = server;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Client<OnlineTurnBasedGame, ServerPlayer> client() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) {
|
||||||
|
ctx.writeAndFlush("Welcome " + client.id() + " please login" + "\n");
|
||||||
|
|
||||||
|
client.setCtx(ctx);
|
||||||
|
server.addClient(client); // TODO set correct name on login
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
|
||||||
|
|
||||||
|
IO.println(msg);
|
||||||
|
|
||||||
|
ParsedMessage p = Parser.parse(msg);
|
||||||
|
if (p == null) return;
|
||||||
|
|
||||||
|
IO.println(p.command() + " " + Arrays.toString(p.args()));
|
||||||
|
|
||||||
|
handler.handle(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
cause.printStackTrace();
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) {
|
||||||
|
server.removeClient(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.toop.framework.networking.server.gateway;
|
||||||
|
|
||||||
|
public interface GatewayServer {
|
||||||
|
void start() throws Exception;
|
||||||
|
void stop();
|
||||||
|
int port();
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package org.toop.framework.networking.server.gateway;
|
||||||
|
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.channel.*;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.nio.NioIoHandler;
|
||||||
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||||
|
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||||
|
import io.netty.handler.codec.string.StringDecoder;
|
||||||
|
import io.netty.handler.codec.string.StringEncoder;
|
||||||
|
import io.netty.handler.logging.LogLevel;
|
||||||
|
import io.netty.handler.logging.LoggingHandler;
|
||||||
|
import org.toop.framework.SnowflakeGenerator;
|
||||||
|
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||||
|
import org.toop.framework.networking.server.client.NettyClient;
|
||||||
|
import org.toop.framework.networking.server.connectionHandler.NettyClientSession;
|
||||||
|
import org.toop.framework.networking.server.Server;
|
||||||
|
import org.toop.framework.networking.server.handlers.MessageHandler;
|
||||||
|
import org.toop.framework.networking.server.stores.NettyClientStore;
|
||||||
|
import org.toop.framework.networking.server.stores.TurnBasedGameStore;
|
||||||
|
import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
public class NettyGatewayServer implements GatewayServer {
|
||||||
|
private final int port;
|
||||||
|
private final Server gs;
|
||||||
|
|
||||||
|
ChannelFuture future;
|
||||||
|
EventLoopGroup bossGroup;
|
||||||
|
EventLoopGroup workerGroup;
|
||||||
|
|
||||||
|
public NettyGatewayServer(
|
||||||
|
int port,
|
||||||
|
TurnBasedGameTypeStore turnBasedGameTypeStore,
|
||||||
|
Duration challengeDuration
|
||||||
|
) {
|
||||||
|
this.port = port;
|
||||||
|
this.gs = new Server(
|
||||||
|
challengeDuration,
|
||||||
|
turnBasedGameTypeStore,
|
||||||
|
new NettyClientStore(new ConcurrentHashMap<>()),
|
||||||
|
new TurnBasedGameStore(new CopyOnWriteArrayList<>())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() throws InterruptedException {
|
||||||
|
|
||||||
|
bossGroup = new NioEventLoopGroup(1);
|
||||||
|
workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
|
||||||
|
|
||||||
|
try {
|
||||||
|
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||||
|
bootstrap.group(workerGroup);
|
||||||
|
bootstrap.channel(NioServerSocketChannel.class);
|
||||||
|
bootstrap.option(ChannelOption.SO_BACKLOG, 128);
|
||||||
|
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
|
||||||
|
bootstrap.handler(new LoggingHandler(LogLevel.INFO));
|
||||||
|
bootstrap.childHandler(
|
||||||
|
new ChannelInitializer<NioSocketChannel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(NioSocketChannel ch) {
|
||||||
|
|
||||||
|
ChannelPipeline pipeline = ch.pipeline();
|
||||||
|
|
||||||
|
pipeline.addLast(new LineBasedFrameDecoder(8192));
|
||||||
|
pipeline.addLast(new StringDecoder());
|
||||||
|
pipeline.addLast(new StringEncoder());
|
||||||
|
|
||||||
|
long userid = SnowflakeGenerator.nextId();
|
||||||
|
NettyClient client = new NettyClient(userid, ""+userid);
|
||||||
|
pipeline.addLast(new NettyClientSession(client, gs, new MessageHandler(gs, client)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
future = bootstrap.bind(port).sync();
|
||||||
|
|
||||||
|
future.channel().closeFuture().sync();
|
||||||
|
} finally {
|
||||||
|
bossGroup.shutdownGracefully();
|
||||||
|
workerGroup.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
if (future == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
future.channel().close();
|
||||||
|
bossGroup.shutdownGracefully();
|
||||||
|
workerGroup.shutdownGracefully();
|
||||||
|
|
||||||
|
future = null;
|
||||||
|
bossGroup = null;
|
||||||
|
workerGroup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int port() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package org.toop.framework.networking.server.handlers;
|
||||||
|
|
||||||
|
public interface Handler<T> {
|
||||||
|
void handle(T message);
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package org.toop.framework.networking.server.handlers;
|
||||||
|
|
||||||
|
import org.toop.framework.game.players.ServerPlayer;
|
||||||
|
import org.toop.framework.networking.server.OnlineTurnBasedGame;
|
||||||
|
import org.toop.framework.networking.server.Server;
|
||||||
|
import org.toop.framework.networking.server.client.Client;
|
||||||
|
import org.toop.framework.networking.server.parsing.ParsedMessage;
|
||||||
|
import org.toop.framework.utils.Utils;
|
||||||
|
|
||||||
|
public class MessageHandler implements Handler<ParsedMessage> {
|
||||||
|
|
||||||
|
private final Server server;
|
||||||
|
private final Client<OnlineTurnBasedGame, ServerPlayer> client;
|
||||||
|
|
||||||
|
public MessageHandler(Server server, Client<OnlineTurnBasedGame, ServerPlayer> client) {
|
||||||
|
this.server = server;
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(ParsedMessage message) {
|
||||||
|
switch (message.command()) {
|
||||||
|
case "ping" -> client.send("PONG");
|
||||||
|
case "login" -> handleLogin(message, client);
|
||||||
|
case "get" -> handleGet(message, client);
|
||||||
|
case "subscribe" -> handleSubscribe(message, client);
|
||||||
|
case "move" -> handleMove(message, client);
|
||||||
|
case "challenge" -> handleChallenge(message, client);
|
||||||
|
case "message" -> handleMessage(message, client);
|
||||||
|
case "help" -> handleHelp(message, client);
|
||||||
|
default -> client.send("ERROR Unknown command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO NOT INVERT
|
||||||
|
private boolean hasArgs(String... args) {
|
||||||
|
if (args.length < 1) {
|
||||||
|
client.send("ERR not enough arguments");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleLogin(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
|
||||||
|
if (!hasArgs(p.args())) return;
|
||||||
|
|
||||||
|
client.setName(p.args()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSubscribe(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
|
||||||
|
if (!hasArgs(p.args())) return;
|
||||||
|
|
||||||
|
server.subscribeClient(client.name(), p.args()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleHelp(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMessage(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleGet(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
|
||||||
|
if (!hasArgs(p.args())) return;
|
||||||
|
|
||||||
|
switch (p.args()[0]) {
|
||||||
|
case "playerlist" -> {
|
||||||
|
var names = server.onlineUsers().stream().map(Client::name).iterator();
|
||||||
|
client.send("SVR PLAYERLIST " + Utils.returnQuotedString(names));
|
||||||
|
}
|
||||||
|
case "gamelist" -> {
|
||||||
|
var names = server.gameTypes().stream().iterator();
|
||||||
|
client.send("SVR GAMELIST " + Utils.returnQuotedString(names));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleChallenge(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
|
||||||
|
if (!hasArgs(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) {
|
||||||
|
client.send("ERR id must be a positive number");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.acceptChallenge(id);
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
client.send("ERR id is not a valid number or too big");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.challengeClient(client.name(), p.args()[0], p.args()[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMove(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
|
||||||
|
if(!hasArgs(p.args())) return;
|
||||||
|
|
||||||
|
// TODO check if not number
|
||||||
|
client.player().setMove(1L << Integer.parseInt(p.args()[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.toop.framework.networking.server;
|
package org.toop.framework.networking.server.parsing;
|
||||||
|
|
||||||
public record ParsedMessage(String command, String... args) {}
|
public record ParsedMessage(String command, String... args) {}
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.toop.framework.networking.server.parsing;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Parser {
|
||||||
|
public static ParsedMessage parse(String msg) {
|
||||||
|
// TODO, what if empty string.
|
||||||
|
|
||||||
|
if (msg.isEmpty()) return null;
|
||||||
|
|
||||||
|
msg = msg.trim().toLowerCase();
|
||||||
|
|
||||||
|
List<String> parts = new LinkedList<>(List.of(msg.split(" ")));
|
||||||
|
|
||||||
|
if (parts.size() > 1) {
|
||||||
|
String command = parts.removeFirst();
|
||||||
|
return new ParsedMessage(command, parts.toArray(String[]::new));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new ParsedMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.toop.framework.networking.server.stores;
|
||||||
|
|
||||||
|
import org.toop.framework.game.players.ServerPlayer;
|
||||||
|
import org.toop.framework.networking.server.OnlineTurnBasedGame;
|
||||||
|
import org.toop.framework.networking.server.client.Client;
|
||||||
|
|
||||||
|
public interface ClientStore<ID, T extends Client<OnlineTurnBasedGame, ServerPlayer>> extends Store<ID, T> {}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package org.toop.framework.networking.server.stores;
|
||||||
|
|
||||||
|
public interface GameStore<T, K> extends Store<T, K> {}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.toop.framework.networking.server.stores;
|
||||||
|
|
||||||
|
import org.toop.framework.networking.server.client.NettyClient;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class NettyClientStore implements ClientStore<Long, NettyClient> {
|
||||||
|
final private Map<Long, NettyClient> users;
|
||||||
|
|
||||||
|
public NettyClientStore(Map<Long, NettyClient> usersMap) {
|
||||||
|
this.users = usersMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(NettyClient adding) {
|
||||||
|
users.putIfAbsent(adding.id(), adding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(Long remover) {
|
||||||
|
users.remove(remover);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NettyClient get(Long getter) {
|
||||||
|
return users.get(getter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<NettyClient> all() {
|
||||||
|
return users.values();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.toop.framework.networking.server.stores;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface Store<IDENTIFIER, STORED> {
|
||||||
|
void add(STORED adding);
|
||||||
|
void remove(IDENTIFIER remover);
|
||||||
|
STORED get(IDENTIFIER getter);
|
||||||
|
Collection<STORED> all();
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.toop.framework.networking.server.stores;
|
||||||
|
|
||||||
|
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||||
|
import org.toop.framework.networking.server.OnlineGame;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TurnBasedGameStore implements GameStore<OnlineGame<TurnBasedGame>, OnlineGame<TurnBasedGame>> {
|
||||||
|
|
||||||
|
private List<OnlineGame<TurnBasedGame>> gameList;
|
||||||
|
|
||||||
|
public TurnBasedGameStore(List<OnlineGame<TurnBasedGame>> initGameList) {
|
||||||
|
this.gameList = initGameList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(OnlineGame<TurnBasedGame> adding) {
|
||||||
|
gameList.addLast(adding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(OnlineGame<TurnBasedGame> remover) {
|
||||||
|
gameList.remove(remover);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OnlineGame<TurnBasedGame> get(OnlineGame<TurnBasedGame> getter) {
|
||||||
|
return gameList.stream().filter(game->game.equals(getter)).findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<OnlineGame<TurnBasedGame>> all() {
|
||||||
|
return gameList;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.toop.framework.networking.server.stores;
|
||||||
|
|
||||||
|
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class TurnBasedGameTypeStore {
|
||||||
|
|
||||||
|
private final Map<String, Supplier<? extends TurnBasedGame>> gameFactories = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public TurnBasedGameTypeStore() {}
|
||||||
|
|
||||||
|
public void register(String key, Supplier<? extends TurnBasedGame> factory) {
|
||||||
|
gameFactories.put(key, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister(String key) {
|
||||||
|
gameFactories.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TurnBasedGame create(String key) {
|
||||||
|
Supplier<? extends TurnBasedGame> factory = gameFactories.get(key);
|
||||||
|
if (factory == null) throw new IllegalArgumentException("Unknown game type: " + key);
|
||||||
|
return factory.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Supplier<? extends TurnBasedGame>> all() {
|
||||||
|
return Map.copyOf(gameFactories);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.toop.framework.networking.server;
|
package org.toop.framework.utils;
|
||||||
|
|
||||||
public interface SimpleTimer {
|
public interface SimpleTimer {
|
||||||
void forceExpire();
|
void forceExpire();
|
||||||
11
framework/src/main/java/org/toop/framework/utils/Utils.java
Normal file
11
framework/src/main/java/org/toop/framework/utils/Utils.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package org.toop.framework.utils;
|
||||||
|
|
||||||
|
import org.apache.maven.surefire.shared.utils.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
public static String returnQuotedString(Iterator<String> strings) { // TODO more places this could be useful
|
||||||
|
return "\"" + StringUtils.join(strings, "\",\"") + "\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
//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.OnlineTurnBasedGame;
|
||||||
//import org.toop.framework.gameFramework.model.player.Player;
|
//import org.toop.framework.gameFramework.model.player.Player;
|
||||||
//
|
//
|
||||||
//import java.time.Duration;
|
//import java.time.Duration;
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
//
|
//
|
||||||
//public class ServerTest {
|
//public class ServerTest {
|
||||||
//
|
//
|
||||||
// static class TurnBasedGameMock implements TurnBasedGame {
|
// static class TurnBasedGameMock implements OnlineTurnBasedGame {
|
||||||
// private Player[] players;
|
// private Player[] players;
|
||||||
//
|
//
|
||||||
// public TurnBasedGameMock() {}
|
// public TurnBasedGameMock() {}
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// @Override
|
// @Override
|
||||||
// public TurnBasedGame deepCopy() {
|
// public OnlineTurnBasedGame deepCopy() {
|
||||||
// return null;
|
// return null;
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// static class TestUser implements ServerUser {
|
// static class TestUser implements Client {
|
||||||
//
|
//
|
||||||
// final private long id;
|
// final private long id;
|
||||||
//
|
//
|
||||||
@@ -87,17 +87,17 @@
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// @Override
|
// @Override
|
||||||
// public Game[] games() {
|
// public OnlineTurnBasedGame[] games() {
|
||||||
// return new Game[0];
|
// return new OnlineTurnBasedGame[0];
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// @Override
|
// @Override
|
||||||
// public void addGame(Game game) {
|
// public void addGame(OnlineTurnBasedGame game) {
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// @Override
|
// @Override
|
||||||
// public void removeGame(Game game) {
|
// public void removeGame(OnlineTurnBasedGame game) {
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
// @BeforeEach
|
// @BeforeEach
|
||||||
// void setup() {
|
// void setup() {
|
||||||
//
|
//
|
||||||
// var games = new ConcurrentHashMap<String, Class<? extends TurnBasedGame>>();
|
// var games = new ConcurrentHashMap<String, Class<? extends OnlineTurnBasedGame>>();
|
||||||
// games.put("tictactoe", TurnBasedGameMock.class);
|
// games.put("tictactoe", TurnBasedGameMock.class);
|
||||||
// games.put("reversi", TurnBasedGameMock.class);
|
// games.put("reversi", TurnBasedGameMock.class);
|
||||||
//
|
//
|
||||||
@@ -157,9 +157,9 @@
|
|||||||
//
|
//
|
||||||
// @Test
|
// @Test
|
||||||
// void testStartGame() {
|
// void testStartGame() {
|
||||||
// server.startGame("tictactoe", new User(0, "A"), new User(1, "B"));
|
// server.startGame("tictactoe", new NettyClient(0, "A"), new NettyClient(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 NettyClient(0, "A"), new NettyClient(1, "B"));
|
||||||
// Assertions.assertEquals(2, server.ongoingGames().size());
|
// Assertions.assertEquals(2, server.ongoingGames().size());
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user