mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 02:44:50 +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.events.NetworkEvents;
|
||||
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.OnlinePlayer;
|
||||
import org.toop.framework.game.players.RandomAI;
|
||||
import org.toop.framework.networking.server.gateway.NettyGatewayServer;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
import java.util.List;
|
||||
@@ -32,7 +31,8 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
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 long clientId = -1;
|
||||
|
||||
@@ -60,10 +60,13 @@ public final class Server {
|
||||
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.
|
||||
// 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) {
|
||||
new ErrorPopup("\"" + ip + "\" " + AppContext.getString("is-not-a-valid-ip-address"));
|
||||
return;
|
||||
@@ -83,6 +86,8 @@ public final class Server {
|
||||
return;
|
||||
}
|
||||
|
||||
this.nettyGatewayServer = nettyGatewayServer;
|
||||
|
||||
final int reconnectAttempts = 10;
|
||||
|
||||
LoadingWidget loading = new LoadingWidget(
|
||||
@@ -113,7 +118,7 @@ public final class Server {
|
||||
return;
|
||||
}
|
||||
|
||||
primary = new ServerView(user, this::sendChallenge);
|
||||
primary = new ServerView(user, this::sendChallenge, clientId);
|
||||
WidgetContainer.getCurrentView().transitionNextCustom(primary, "disconnect", this::disconnect);
|
||||
|
||||
a.unsubscribe("connecting");
|
||||
@@ -154,7 +159,8 @@ public final class Server {
|
||||
.listen(NetworkEvents.GameMatchResponse.class, this::handleMatchResponse, false, "match-response")
|
||||
.listen(NetworkEvents.GameResultResponse.class, this::handleGameResult, false, "game-result")
|
||||
.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;
|
||||
}
|
||||
@@ -228,7 +234,8 @@ public final class Server {
|
||||
|
||||
}
|
||||
|
||||
if (gameController != null){
|
||||
if (gameController != null) {
|
||||
primary.reEnableButton();
|
||||
gameController.start();
|
||||
}
|
||||
}
|
||||
@@ -281,9 +288,27 @@ public final class Server {
|
||||
stopScheduler();
|
||||
connectFlow.unsubscribeAll();
|
||||
|
||||
if (nettyGatewayServer != null) {
|
||||
nettyGatewayServer.stop();
|
||||
}
|
||||
|
||||
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() {
|
||||
new EventFlow().addPostEvent(new NetworkEvents.SendForfeit(clientId)).postEvent();
|
||||
}
|
||||
@@ -330,7 +355,9 @@ public final class Server {
|
||||
|
||||
private void gamesListFromServerHandler(NetworkEvents.GamelistResponse event) {
|
||||
gameList.clear();
|
||||
gameList.addAll(List.of(event.gamelist()));
|
||||
var gl = List.of(event.gamelist());
|
||||
gameList.addAll(gl);
|
||||
primary.updateGameList(gl);
|
||||
}
|
||||
|
||||
public void populateGameList() {
|
||||
|
||||
@@ -93,7 +93,7 @@ public class GenericGameController implements GameController {
|
||||
}
|
||||
|
||||
private void onGameFinish(GUIEvents.GameEnded event){
|
||||
logger.info("Game Finished");
|
||||
logger.info("OnlineTurnBasedGame Finished");
|
||||
String name = event.winner() == -1 ? null : getPlayer(event.winner()).getName();
|
||||
gameView.gameOver(event.winOrTie(), name);
|
||||
stop();
|
||||
|
||||
@@ -64,7 +64,7 @@ public final class Primitive {
|
||||
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();
|
||||
button.getStyleClass().add("button");
|
||||
|
||||
@@ -75,6 +75,7 @@ public final class Primitive {
|
||||
|
||||
if (onAction != null) {
|
||||
button.setOnAction(_ -> {
|
||||
if (disableOnClick) button.setDisable(true);
|
||||
onAction.run();
|
||||
playButtonSound();
|
||||
});
|
||||
@@ -83,8 +84,8 @@ public final class Primitive {
|
||||
return button;
|
||||
}
|
||||
|
||||
public static Button button(String key, Runnable onAction) {
|
||||
return button(key, onAction, true);
|
||||
public static Button button(String key, Runnable onAction, boolean disableOnClick) {
|
||||
return button(key, onAction, true, disableOnClick);
|
||||
}
|
||||
|
||||
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) {
|
||||
Platform.runLater(() -> {
|
||||
var button = Primitive.button(key, onClick);
|
||||
var button = Primitive.button(key, onClick, false);
|
||||
buttonsContainer.getChildren().add(button);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class PlayerInfoWidget {
|
||||
information.computerDifficulty = depth;
|
||||
information.computerThinkTime = thinktime;
|
||||
this.playerName.setText(getName(name));
|
||||
});
|
||||
}, false);
|
||||
}
|
||||
|
||||
private String getName(String name) {
|
||||
|
||||
@@ -32,7 +32,7 @@ public abstract class ViewWidget extends StackWidget {
|
||||
|
||||
var backButton = Primitive.button("back", () -> {
|
||||
view.transitionPrevious();
|
||||
});
|
||||
}, false);
|
||||
|
||||
view.add(Pos.BOTTOM_LEFT, Primitive.vbox(backButton));
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public abstract class ViewWidget extends StackWidget {
|
||||
var customButton = Primitive.button(key, () -> {
|
||||
runnable.run();
|
||||
view.transitionPrevious();
|
||||
});
|
||||
}, false);
|
||||
|
||||
view.add(Pos.BOTTOM_LEFT, Primitive.vbox(customButton));
|
||||
}
|
||||
@@ -97,7 +97,7 @@ public abstract class ViewWidget extends StackWidget {
|
||||
|
||||
var backButton = Primitive.button("back", () -> {
|
||||
view.transitionPrevious();
|
||||
});
|
||||
}, false);
|
||||
|
||||
view.add(Pos.BOTTOM_LEFT, Primitive.vbox(backButton));
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ public final class ChallengePopup extends PopupWidget {
|
||||
var acceptButton = Primitive.button("accept", () -> {
|
||||
onAccept.accept(playerInformation);
|
||||
this.hide();
|
||||
});
|
||||
var denyButton = Primitive.button("deny", () -> hide());
|
||||
}, false);
|
||||
var denyButton = Primitive.button("deny", () -> hide(), false);
|
||||
|
||||
var leftSection = Primitive.vbox(
|
||||
challengeText,
|
||||
|
||||
@@ -18,13 +18,13 @@ public class EscapePopup extends PopupWidget {
|
||||
ViewWidget currentView = WidgetContainer.getCurrentView();
|
||||
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))) {
|
||||
var opt = Primitive.button("options", () -> {
|
||||
hide();
|
||||
WidgetContainer.getCurrentView().transitionNext(new OptionsView());
|
||||
});
|
||||
}, false);
|
||||
nodes.add(opt);
|
||||
}
|
||||
|
||||
@@ -33,14 +33,14 @@ public class EscapePopup extends PopupWidget {
|
||||
if (tut != null) {
|
||||
nodes.add(Primitive.button("tutorialstring", () -> {
|
||||
WidgetContainer.getCurrentView().add(Pos.CENTER, tut);
|
||||
}));
|
||||
}, false));
|
||||
}
|
||||
}
|
||||
|
||||
nodes.add(Primitive.button("quit", () -> {
|
||||
hide();
|
||||
WidgetContainer.add(Pos.CENTER, new QuitPopup());
|
||||
}));
|
||||
}, false));
|
||||
|
||||
add(Pos.CENTER, Primitive.vbox(nodes.toArray(new Node[0])));
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ public final class SendChallengePopup extends PopupWidget {
|
||||
var sendButton = Primitive.button(
|
||||
"send",
|
||||
() -> { 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(
|
||||
challengeText,
|
||||
|
||||
@@ -47,8 +47,8 @@ public class BaseTutorialWidget extends PopupWidget implements Updatable {
|
||||
this.pages = pages;
|
||||
this.nextScreen = nextScreen;
|
||||
|
||||
previousButton = Primitive.button("goback", () -> { update(false); this.hide(); });
|
||||
nextButton = Primitive.button(">", () -> update(true));
|
||||
previousButton = Primitive.button("goback", () -> { update(false); this.hide(); }, false);
|
||||
nextButton = Primitive.button(">", () -> update(true), false);
|
||||
|
||||
var w = Primitive.hbox(
|
||||
previousButton,
|
||||
|
||||
@@ -10,9 +10,9 @@ public class ShowEnableTutorialWidget extends PopupWidget {
|
||||
|
||||
public ShowEnableTutorialWidget(Runnable tutorial, Runnable nextScreen, Runnable appSettingsSetter) {
|
||||
var a = Primitive.hbox(
|
||||
Primitive.button("ok", () -> { appSettingsSetter.run(); tutorial.run(); this.hide(); }),
|
||||
Primitive.button("no", () -> { appSettingsSetter.run(); nextScreen.run(); this.hide(); }),
|
||||
Primitive.button("never", () -> { AppSettings.getSettings().setTutorialFlag(false); nextScreen.run(); this.hide(); })
|
||||
Primitive.button("ok", () -> { appSettingsSetter.run(); tutorial.run(); this.hide(); }, false),
|
||||
Primitive.button("no", () -> { appSettingsSetter.run(); nextScreen.run(); this.hide(); }, false),
|
||||
Primitive.button("never", () -> { AppSettings.getSettings().setTutorialFlag(false); nextScreen.run(); this.hide(); }, false)
|
||||
);
|
||||
|
||||
var txt = Primitive.text("tutorial");
|
||||
|
||||
@@ -40,7 +40,7 @@ public final class GameView extends ViewWidget {
|
||||
player2Icon = new Circle();
|
||||
|
||||
if (onForfeit != null) {
|
||||
forfeitButton = Primitive.button("forfeit", () -> onForfeit.run());
|
||||
forfeitButton = Primitive.button("forfeit", () -> onForfeit.run(), false);
|
||||
} else {
|
||||
forfeitButton = null;
|
||||
}
|
||||
@@ -48,7 +48,7 @@ public final class GameView extends ViewWidget {
|
||||
exitButton = Primitive.button("exit", () -> {
|
||||
onExit.run();
|
||||
transitionPrevious();
|
||||
});
|
||||
}, false);
|
||||
|
||||
if (onMessage != null) {
|
||||
chatInput = Primitive.input("enter-your-message", "", null);
|
||||
|
||||
@@ -107,7 +107,7 @@ public class LocalMultiplayerView extends ViewWidget {
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}, false);
|
||||
|
||||
var playerSection = setupPlayerSections();
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ public class LocalView extends ViewWidget {
|
||||
public LocalView() {
|
||||
var ticTacToeButton = Primitive.button("tic-tac-toe", () -> {
|
||||
transitionNext(new LocalMultiplayerView(GameInformation.Type.TICTACTOE));
|
||||
});
|
||||
}, false);
|
||||
|
||||
var reversiButton = Primitive.button("reversi", () -> {
|
||||
transitionNext(new LocalMultiplayerView(GameInformation.Type.REVERSI));
|
||||
});
|
||||
}, false);
|
||||
|
||||
add(Pos.CENTER, Primitive.vbox(
|
||||
ticTacToeButton,
|
||||
|
||||
@@ -9,24 +9,24 @@ public class MainView extends ViewWidget {
|
||||
public MainView() {
|
||||
var localButton = Primitive.button("local", () -> {
|
||||
transitionNext(new LocalView());
|
||||
});
|
||||
}, false);
|
||||
|
||||
var onlineButton = Primitive.button("online", () -> {
|
||||
transitionNext(new OnlineView());
|
||||
});
|
||||
}, false);
|
||||
|
||||
var creditsButton = Primitive.button("credits", () -> {
|
||||
transitionNext(new CreditsView());
|
||||
});
|
||||
}, false);
|
||||
|
||||
var optionsButton = Primitive.button("options", () -> {
|
||||
transitionNext(new OptionsView());
|
||||
});
|
||||
}, false);
|
||||
|
||||
var quitButton = Primitive.button("quit", () -> {
|
||||
var a = new QuitPopup();
|
||||
a.show(Pos.CENTER);
|
||||
});
|
||||
}, false);
|
||||
|
||||
add(Pos.CENTER, Primitive.vbox(
|
||||
localButton,
|
||||
|
||||
@@ -9,7 +9,8 @@ import javafx.geometry.Pos;
|
||||
import org.toop.framework.game.games.reversi.BitboardReversi;
|
||||
import org.toop.framework.game.games.tictactoe.BitboardTicTacToe;
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.networking.server.MasterServer;
|
||||
import org.toop.framework.networking.server.gateway.NettyGatewayServer;
|
||||
import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -28,14 +29,15 @@ public class OnlineView extends ViewWidget {
|
||||
serverPortInput.getValue(),
|
||||
playerNameInput.getValue()
|
||||
);
|
||||
});
|
||||
}, false);
|
||||
|
||||
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(() -> {
|
||||
try {
|
||||
@@ -48,9 +50,10 @@ public class OnlineView extends ViewWidget {
|
||||
new Server(
|
||||
"127.0.0.1",
|
||||
"6666",
|
||||
"host"
|
||||
"host",
|
||||
a
|
||||
);
|
||||
}, false);
|
||||
}, false, false);
|
||||
|
||||
add(Pos.CENTER, Primitive.vbox(
|
||||
serverInformationHeader,
|
||||
|
||||
@@ -1,26 +1,37 @@
|
||||
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.complex.ViewWidget;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
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 {
|
||||
private final String user;
|
||||
private final Consumer<String> onPlayerClicked;
|
||||
private final long clientId;
|
||||
|
||||
private final ComboBox<String> gameList;
|
||||
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.onPlayerClicked = onPlayerClicked;
|
||||
this.clientId = clientId;
|
||||
|
||||
this.gameList = new ComboBox<>();
|
||||
this.listView = new ListView<>();
|
||||
|
||||
setupLayout();
|
||||
@@ -29,17 +40,26 @@ public final class ServerView extends ViewWidget {
|
||||
private void setupLayout() {
|
||||
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(
|
||||
playerHeader,
|
||||
Primitive.separator(),
|
||||
subscribe,
|
||||
listView
|
||||
);
|
||||
|
||||
add(Pos.CENTER, playerListSection);
|
||||
|
||||
var disconnectButton = Primitive.button("disconnect", () -> {
|
||||
transitionPrevious();
|
||||
});
|
||||
var disconnectButton = Primitive.button(
|
||||
"disconnect", () -> transitionPrevious(), false);
|
||||
|
||||
add(Pos.BOTTOM_LEFT, Primitive.vbox(disconnectButton));
|
||||
}
|
||||
@@ -49,9 +69,21 @@ public final class ServerView extends ViewWidget {
|
||||
listView.getItems().clear();
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.view.GUIEvents;
|
||||
import org.toop.framework.utils.ImmutablePair;
|
||||
import org.toop.framework.utils.Pair;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -15,18 +16,29 @@ import static org.toop.framework.gameFramework.GameState.TURN_SKIPPED;
|
||||
import static org.toop.framework.gameFramework.GameState.WIN;
|
||||
|
||||
public class ServerThreadBehaviour extends AbstractThreadBehaviour implements Runnable {
|
||||
private Consumer<ImmutablePair<String, Integer>> onPlayerMove;
|
||||
private final Consumer<ImmutablePair<String, Integer>> onPlayerMove;
|
||||
private final Consumer<Pair<GameState, Integer>> onGameEnd;
|
||||
/**
|
||||
* Creates a new base behaviour for the specified game.
|
||||
*
|
||||
* @param game the turn-based game to control
|
||||
*/
|
||||
public ServerThreadBehaviour(TurnBasedGame game, Consumer<ImmutablePair<String, Integer>> onPlayerMove) {
|
||||
public ServerThreadBehaviour(TurnBasedGame game, Consumer<ImmutablePair<String, Integer>> onPlayerMove, Consumer<Pair<GameState, Integer>> onGameEnd) {
|
||||
this.onPlayerMove = onPlayerMove;
|
||||
this.onGameEnd = onGameEnd;
|
||||
super(game);
|
||||
}
|
||||
|
||||
private void notifyPlayerMove(ImmutablePair<String, Integer> pair) {
|
||||
onPlayerMove.accept(pair);
|
||||
if (onPlayerMove != null) {
|
||||
onPlayerMove.accept(pair);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyGameEnd(ImmutablePair<GameState, Integer> pair) {
|
||||
if (onGameEnd != null) {
|
||||
onGameEnd.accept(pair);
|
||||
}
|
||||
}
|
||||
|
||||
/** Starts the game loop in a new thread. */
|
||||
@@ -51,14 +63,12 @@ public class ServerThreadBehaviour extends AbstractThreadBehaviour implements Ru
|
||||
PlayResult result = game.play(move);
|
||||
|
||||
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) {
|
||||
case WIN, DRAW -> {
|
||||
isRunning.set(false);
|
||||
notifyGameEnd(new ImmutablePair<>(state, game.getWinner()));
|
||||
}
|
||||
case NORMAL, TURN_SKIPPED -> { /* continue normally */ }
|
||||
default -> {
|
||||
|
||||
@@ -126,7 +126,7 @@ public class MiniMaxAI extends AbstractAI {
|
||||
* Simple heuristic evaluation for Reversi-like games.
|
||||
* Positive = good for AI, Negative = good for opponent.
|
||||
*
|
||||
* @param game Game state
|
||||
* @param game OnlineTurnBasedGame state
|
||||
* @param aiPlayer AI's player index
|
||||
* @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.player.AbstractPlayer;
|
||||
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.ExecutionException;
|
||||
|
||||
public class ServerPlayer extends AbstractPlayer {
|
||||
private User user;
|
||||
private NettyClient client;
|
||||
private CompletableFuture<Long> lastMove;
|
||||
|
||||
public ServerPlayer(User user) {
|
||||
super(user.name());
|
||||
this.user = user;
|
||||
public ServerPlayer(NettyClient client) {
|
||||
super(client.name());
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public void setMove(long move) {
|
||||
@@ -30,7 +30,7 @@ public class ServerPlayer extends AbstractPlayer {
|
||||
public long getMove(TurnBasedGame game) {
|
||||
lastMove = new CompletableFuture<>();
|
||||
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 {
|
||||
return lastMove.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
@@ -38,9 +38,4 @@ public class ServerPlayer extends AbstractPlayer {
|
||||
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.
|
||||
*/
|
||||
public enum GameState {
|
||||
/** Game is ongoing and no special condition applies. */
|
||||
/** OnlineTurnBasedGame is ongoing and no special condition applies. */
|
||||
NORMAL,
|
||||
|
||||
/** Game ended in a draw. */
|
||||
/** OnlineTurnBasedGame ended in a draw. */
|
||||
DRAW,
|
||||
|
||||
/** Game ended with a win for a player. */
|
||||
/** OnlineTurnBasedGame ended with a win for a player. */
|
||||
WIN,
|
||||
|
||||
/** Next player's turn was skipped. */
|
||||
|
||||
@@ -21,6 +21,7 @@ public abstract class AbstractPlayer implements Player {
|
||||
private final String name;
|
||||
|
||||
protected AbstractPlayer(String name) {
|
||||
System.out.println("Player " + name + " has been created");
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@@ -44,7 +45,7 @@ public abstract class AbstractPlayer implements Player {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public String getName(){
|
||||
public final String getName(){
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.netty.util.CharsetUtil;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
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.handlers.NetworkingGameClientHandler;
|
||||
import org.toop.framework.networking.connection.interfaces.NetworkingClient;
|
||||
@@ -23,6 +24,7 @@ public class TournamentNetworkingClient implements NetworkingClient {
|
||||
|
||||
private final EventBus eventBus;
|
||||
private Channel channel;
|
||||
private long clientId;
|
||||
|
||||
public TournamentNetworkingClient(EventBus eventBus) {
|
||||
this.eventBus = eventBus;
|
||||
@@ -35,6 +37,7 @@ public class TournamentNetworkingClient implements NetworkingClient {
|
||||
|
||||
@Override
|
||||
public void connect(long clientId, String host, int port) throws CouldNotConnectException {
|
||||
this.clientId = clientId;
|
||||
try {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
|
||||
@@ -75,6 +78,7 @@ public class TournamentNetworkingClient implements NetworkingClient {
|
||||
logger.info("Connection {} sent message: '{}' ", this.channel.remoteAddress(), literalMsg);
|
||||
} else {
|
||||
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;
|
||||
|
||||
public interface GameServer {
|
||||
// List<?> gameTypes();
|
||||
// List<?> ongoingGames();
|
||||
// void startGame(String gameType, User... users);
|
||||
// String[] onlineUsers();
|
||||
import java.util.List;
|
||||
|
||||
public interface GameServer<GAMETYPE, CLIENT, CHALLENGEIDTYPE> {
|
||||
void startGame(String gameType, CLIENT... clients);
|
||||
|
||||
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;
|
||||
|
||||
import org.toop.framework.networking.server.client.NettyClient;
|
||||
|
||||
public interface OnlineGame<T> {
|
||||
long id();
|
||||
T game();
|
||||
User[] users();
|
||||
NettyClient[] users();
|
||||
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.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 java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
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 Map<Long, User> users = new ConcurrentHashMap<>();
|
||||
final private TurnBasedGameTypeStore gameTypesStore;
|
||||
final private ClientStore<Long, NettyClient> clientStore;
|
||||
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 ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
public Server(Map<String, Class<? extends TurnBasedGame>> gameTypes, Duration challengeDuration) {
|
||||
this.gameTypes = gameTypes;
|
||||
public Server(
|
||||
Duration challengeDuration,
|
||||
TurnBasedGameTypeStore turnBasedGameTypeStore,
|
||||
ClientStore<Long, NettyClient> clientStore,
|
||||
TurnBasedGameStore gameStore
|
||||
|
||||
) {
|
||||
this.gameTypesStore = turnBasedGameTypeStore;
|
||||
this.challengeDuration = challengeDuration;
|
||||
this.clientStore = clientStore;
|
||||
this.gameStore = gameStore;
|
||||
this.subscriptions = new ConcurrentHashMap<>();
|
||||
|
||||
scheduler.schedule(this::serverTask, 0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void serverTask() {
|
||||
checkChallenges();
|
||||
scheduler.schedule(this::serverTask, 500, TimeUnit.MILLISECONDS);
|
||||
@Override
|
||||
public void addClient(NettyClient client) {
|
||||
clientStore.add(client);
|
||||
}
|
||||
|
||||
public void addUser(User user) {
|
||||
users.putIfAbsent(user.id(), user);
|
||||
}
|
||||
|
||||
public void removeUser(User user) {
|
||||
users.remove(user.id());
|
||||
@Override
|
||||
public void removeClient(NettyClient client) {
|
||||
clientStore.remove(client.id());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> gameTypes() {
|
||||
return gameTypes.keySet().stream().toList();
|
||||
return new ArrayList<>(gameTypesStore.all().keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OnlineGame<TurnBasedGame>> ongoingGames() {
|
||||
return games;
|
||||
return gameStore.all().stream().toList();
|
||||
}
|
||||
|
||||
public User getUser(String username) {
|
||||
return users.values().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().orElse(null);
|
||||
}
|
||||
@Override
|
||||
public void challengeClient(String fromUser, String toUser, String gameType) {
|
||||
|
||||
public User getUser(long id) {
|
||||
return users.get(id);
|
||||
}
|
||||
|
||||
public void challengeUser(String fromUser, String toUser, String gameType) {
|
||||
|
||||
User from = getUser(fromUser);
|
||||
NettyClient from = getUser(fromUser);
|
||||
if (from == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gameTypes.containsKey(gameType)) {
|
||||
from.sendMessage("ERR gametype not found \n");
|
||||
if (!gameTypesStore.all().containsKey(gameType)) {
|
||||
from.send("ERR gametype not found \n");
|
||||
return;
|
||||
}
|
||||
|
||||
User to = getUser(toUser);
|
||||
NettyClient to = getUser(toUser);
|
||||
if (to == null) {
|
||||
from.sendMessage("ERR user not found \n");
|
||||
from.send("ERR user not found \n");
|
||||
return;
|
||||
}
|
||||
|
||||
var ch = new GameChallenge(from, to, gameType, new GameChallengeTimer(challengeDuration));
|
||||
|
||||
to.sendMessage(
|
||||
to.send(
|
||||
"SVR GAME CHALLENGE {CHALLENGER: \"%s\", CHALLENGENUMBER: \"%s\", GAMETYPE: \"%s\"} \n"
|
||||
.formatted(from.name(), ch.id(), gameType)
|
||||
);
|
||||
@@ -90,29 +97,91 @@ public class Server implements GameServer {
|
||||
gameChallenges.addLast(ch);
|
||||
}
|
||||
|
||||
private void warnUserExpiredChallenge(User user, long challengeId) {
|
||||
user.sendMessage("SVR GAME CHALLENGE CANCELLED {CHALLENGENUMBER: \"" + challengeId + "\"}" + "\n");
|
||||
}
|
||||
|
||||
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;
|
||||
@Override
|
||||
public void acceptChallenge(Long challengeId) {
|
||||
for (var challenge : gameChallenges) {
|
||||
if (challenge.id() == challengeId) {
|
||||
startGame(challenge.acceptChallenge(), challenge.getUsers());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void checkChallenges() {
|
||||
@Override
|
||||
public void subscribeClient(String clientName, String gameTypeKey) {
|
||||
|
||||
if (!gameTypesStore.all().containsKey(gameTypeKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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--) {
|
||||
var challenge = gameChallenges.get(i);
|
||||
|
||||
@@ -127,56 +196,63 @@ public class Server implements GameServer {
|
||||
}
|
||||
}
|
||||
|
||||
public void acceptChallenge(long challengeId) {
|
||||
for (var challenge : gameChallenges) {
|
||||
if (challenge.id() == challengeId) {
|
||||
startGame(challenge.acceptChallenge(), challenge.getUsers());
|
||||
break;
|
||||
private void checkSubscriptions() {
|
||||
if (subscriptions.isEmpty()) return;
|
||||
|
||||
List<String> keys = List.copyOf(subscriptions.keySet());
|
||||
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() {
|
||||
return gameChallenges;
|
||||
private NettyClient getUser(String username) {
|
||||
return clientStore.all().stream().filter(e -> e.name().equalsIgnoreCase(username)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public void startGame(String gameType, User... users) {
|
||||
if (!gameTypes.containsKey(gameType)) return;
|
||||
private NettyClient getUser(long id) {
|
||||
return clientStore.get(id);
|
||||
}
|
||||
|
||||
try {
|
||||
ServerPlayer[] players = new ServerPlayer[users.length];
|
||||
var game = new Game(gameTypes.get(gameType).getDeclaredConstructor().newInstance(), users);
|
||||
private void warnUserExpiredChallenge(NettyClient client, long challengeId) {
|
||||
client.send("SVR GAME CHALLENGE CANCELLED {CHALLENGENUMBER: \"" + challengeId + "\"}" + "\n");
|
||||
}
|
||||
|
||||
for (int i = 0; i < users.length; i++) {
|
||||
players[i] = new ServerPlayer(users[i]);
|
||||
users[i].addGame(new ImmutablePair<>(game, players[i]));
|
||||
private boolean isValidChallenge(GameChallenge gameChallenge) { // TODO move to challenge class
|
||||
for (var user : gameChallenge.getUsers()) {
|
||||
if (clientStore.get(user.id()) == null) {
|
||||
return false;
|
||||
}
|
||||
System.out.println("Starting Game");
|
||||
|
||||
game.game().init(players);
|
||||
games.addLast(game);
|
||||
if (user.game() != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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) {}
|
||||
}
|
||||
if (gameChallenge.isExpired()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public List<User> onlineUsers() {
|
||||
return users.values().stream().toList();
|
||||
}
|
||||
|
||||
public void closeServer() {
|
||||
scheduler.shutdown();
|
||||
gameChallenges.clear();
|
||||
games.clear();
|
||||
users.clear();
|
||||
gameTypes.clear();
|
||||
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.networking.server.client.NettyClient;
|
||||
import org.toop.framework.utils.SimpleTimer;
|
||||
|
||||
public class GameChallenge {
|
||||
private final long id = SnowflakeGenerator.nextId(); // I don't need this, but the tournament server uses it...
|
||||
|
||||
private final User from;
|
||||
private final User to;
|
||||
private final NettyClient from;
|
||||
private final NettyClient to;
|
||||
private final String gameType;
|
||||
private final SimpleTimer timer;
|
||||
|
||||
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.to = to;
|
||||
this.gameType = gameType;
|
||||
@@ -23,8 +25,8 @@ public class GameChallenge {
|
||||
return id;
|
||||
}
|
||||
|
||||
public User[] getUsers() {
|
||||
return new User[]{from, to};
|
||||
public NettyClient[] getUsers() {
|
||||
return new NettyClient[]{from, to};
|
||||
}
|
||||
|
||||
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.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 org.toop.framework.game.players.ServerPlayer;
|
||||
import org.toop.framework.networking.server.OnlineTurnBasedGame;
|
||||
import org.toop.framework.utils.Pair;
|
||||
|
||||
public class User implements ServerUser {
|
||||
public class NettyClient implements Client<OnlineTurnBasedGame, ServerPlayer> {
|
||||
final private long id;
|
||||
private ChannelHandlerContext ctx;
|
||||
private String name;
|
||||
private Pair<Game, ServerPlayer> gamePair;
|
||||
private ChannelHandlerContext connectionContext;
|
||||
private Pair<OnlineTurnBasedGame, ServerPlayer> gamePair;
|
||||
|
||||
public User(long userId, String name) {
|
||||
public NettyClient(long userId, String name) {
|
||||
this.id = userId;
|
||||
this.name = name;
|
||||
}
|
||||
@@ -26,23 +27,27 @@ public class User implements ServerUser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGame(Pair<Game, ServerPlayer> gamePair) {
|
||||
public void addGame(Pair<OnlineTurnBasedGame, ServerPlayer> gamePair) {
|
||||
if (this.gamePair == null) {
|
||||
this.gamePair = gamePair;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGame() {
|
||||
public void clearGame() {
|
||||
this.gamePair = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Game game() {
|
||||
public OnlineTurnBasedGame game() {
|
||||
if (this.gamePair == null) {
|
||||
return null;
|
||||
}
|
||||
return this.gamePair.getLeft();
|
||||
}
|
||||
|
||||
public ServerPlayer serverPlayer() {
|
||||
@Override
|
||||
public ServerPlayer player() {
|
||||
return this.gamePair.getRight();
|
||||
}
|
||||
|
||||
@@ -52,18 +57,12 @@ public class User implements ServerUser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(String message) {
|
||||
public void send(String message) {
|
||||
IO.println(message);
|
||||
ctx().channel().writeAndFlush(message);
|
||||
}
|
||||
|
||||
public ChannelHandlerContext ctx() {
|
||||
return connectionContext;
|
||||
ctx.channel().writeAndFlush(message + "\r\n");
|
||||
}
|
||||
|
||||
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) {}
|
||||
|
||||
@@ -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 {
|
||||
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.Test;
|
||||
//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 java.time.Duration;
|
||||
@@ -13,7 +13,7 @@
|
||||
//
|
||||
//public class ServerTest {
|
||||
//
|
||||
// static class TurnBasedGameMock implements TurnBasedGame {
|
||||
// static class TurnBasedGameMock implements OnlineTurnBasedGame {
|
||||
// private Player[] players;
|
||||
//
|
||||
// public TurnBasedGameMock() {}
|
||||
@@ -44,7 +44,7 @@
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public TurnBasedGame deepCopy() {
|
||||
// public OnlineTurnBasedGame deepCopy() {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
@@ -65,7 +65,7 @@
|
||||
//
|
||||
// }
|
||||
//
|
||||
// static class TestUser implements ServerUser {
|
||||
// static class TestUser implements Client {
|
||||
//
|
||||
// final private long id;
|
||||
//
|
||||
@@ -87,17 +87,17 @@
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Game[] games() {
|
||||
// return new Game[0];
|
||||
// public OnlineTurnBasedGame[] games() {
|
||||
// return new OnlineTurnBasedGame[0];
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void addGame(Game game) {
|
||||
// public void addGame(OnlineTurnBasedGame game) {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void removeGame(Game game) {
|
||||
// public void removeGame(OnlineTurnBasedGame game) {
|
||||
//
|
||||
// }
|
||||
//
|
||||
@@ -118,7 +118,7 @@
|
||||
// @BeforeEach
|
||||
// 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("reversi", TurnBasedGameMock.class);
|
||||
//
|
||||
@@ -157,9 +157,9 @@
|
||||
//
|
||||
// @Test
|
||||
// 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());
|
||||
// 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());
|
||||
// }
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user