Merge remote-tracking branch 'refs/remotes/origin/main' into Development

# Conflicts:
#	app/src/main/java/org/toop/Main.java
#	app/src/main/java/org/toop/app/App.java
#	app/src/main/java/org/toop/app/Server.java
#	app/src/main/java/org/toop/app/canvas/BitGameCanvas.java
#	app/src/main/java/org/toop/app/canvas/GameCanvas.java
#	app/src/main/java/org/toop/app/canvas/ReversiBitCanvas.java
#	app/src/main/java/org/toop/app/canvas/TicTacToeBitCanvas.java
#	app/src/main/java/org/toop/app/gameControllers/GenericGameController.java
#	app/src/main/java/org/toop/app/gameControllers/ReversiBitController.java
#	app/src/main/java/org/toop/app/gameControllers/TicTacToeBitController.java
#	app/src/main/java/org/toop/app/widget/Primitive.java
#	app/src/main/java/org/toop/app/widget/complex/ConfirmWidget.java
#	app/src/main/java/org/toop/app/widget/complex/PlayerInfoWidget.java
#	app/src/main/java/org/toop/app/widget/complex/ViewWidget.java
#	app/src/main/java/org/toop/app/widget/popup/ChallengePopup.java
#	app/src/main/java/org/toop/app/widget/popup/EscapePopup.java
#	app/src/main/java/org/toop/app/widget/popup/SendChallengePopup.java
#	app/src/main/java/org/toop/app/widget/tutorial/BaseTutorialWidget.java
#	app/src/main/java/org/toop/app/widget/tutorial/ShowEnableTutorialWidget.java
#	app/src/main/java/org/toop/app/widget/view/GameView.java
#	app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java
#	app/src/main/java/org/toop/app/widget/view/LocalView.java
#	app/src/main/java/org/toop/app/widget/view/MainView.java
#	app/src/main/java/org/toop/app/widget/view/OnlineView.java
#	app/src/main/java/org/toop/app/widget/view/ServerView.java
#	framework/pom.xml
#	framework/src/main/java/org/toop/framework/gameFramework/GameState.java
#	framework/src/main/java/org/toop/framework/gameFramework/controller/GameController.java
#	framework/src/main/java/org/toop/framework/gameFramework/model/game/TurnBasedGame.java
#	framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/AbstractThreadBehaviour.java
#	framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/ThreadBehaviour.java
#	framework/src/main/java/org/toop/framework/gameFramework/model/player/AI.java
#	framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractAI.java
#	framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractPlayer.java
#	framework/src/main/java/org/toop/framework/gameFramework/model/player/Player.java
#	framework/src/main/java/org/toop/framework/networking/NetworkingClient.java
#	framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java
#	framework/src/main/java/org/toop/framework/networking/NetworkingGameClientHandler.java
#	framework/src/main/java/org/toop/framework/networking/NetworkingInitializationException.java
#	framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java
#	framework/src/main/java/org/toop/framework/networking/connection/clients/TournamentNetworkingClient.java
#	framework/src/main/java/org/toop/framework/networking/connection/exceptions/NetworkingInitializationException.java
#	framework/src/main/java/org/toop/framework/networking/connection/handlers/NetworkingGameClientHandler.java
#	framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java
#	framework/src/main/java/org/toop/framework/networking/exceptions/NetworkingInitializationException.java
#	framework/src/main/java/org/toop/framework/networking/handlers/NetworkingGameClientHandler.java
#	framework/src/test/java/org/toop/framework/networking/NetworkingClientManagerTest.java
#	framework/src/test/java/org/toop/framework/networking/events/NetworkEventsTest.java
This commit is contained in:
Bas de Jong
2026-01-09 19:22:14 +01:00
33 changed files with 1393 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
package org.toop.framework.gameFramework.controller;
/**
* Interface for classes that can trigger a UI update.
*/
public interface UpdatesGameUI {
/** Called to refresh or update the game UI. */
void updateUI();
}

View File

@@ -0,0 +1,5 @@
package org.toop.framework.gameFramework.model.game;
public interface BoardProvider {
long[] getBoard();
}

View File

@@ -0,0 +1,24 @@
package org.toop.framework.gameFramework.model.game;
import org.toop.framework.gameFramework.GameState;
/**
* Interface for turn-based games that can be played and queried for legal moves.
*/
public interface Playable {
/**
* Returns the moves that are currently valid in the game.
*
* @return an array of integers representing legal moves
*/
long getLegalMoves();
/**
* Plays the given move and returns the resulting game state.
*
* @param move the move to apply
* @return the {@link GameState} and additional info after the move
*/
PlayResult play(long move);
}

View File

@@ -0,0 +1,7 @@
package org.toop.framework.gameFramework.model.game;
import org.toop.framework.gameFramework.model.player.Player;
public interface PlayerProvider<T extends TurnBasedGame<T>> {
Player<T> getPlayer(int index);
}

View File

@@ -0,0 +1,20 @@
package org.toop.framework.gameFramework.model.game;
import org.toop.framework.networking.events.NetworkEvents;
/**
* Interface for games that support online multiplayer play.
* <p>
* Methods are called in response to network events from the server.
*/
public interface SupportsOnlinePlay {
/** Called when it is this player's turn to make a move. */
void onYourTurn(long clientId);
/** Called when a move from another player is received. */
void onMoveReceived(long move);
/** Called when the game has finished, with the final result. */
void gameFinished(String condition);
}

View File

@@ -0,0 +1,7 @@
package org.toop.framework.gameFramework.model.game.threadBehaviour;
public interface Controllable {
void start();
void stop();
}

View File

@@ -0,0 +1,7 @@
package org.toop.framework.gameFramework.model.player;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
public interface MoveProvider<T extends TurnBasedGame<T>> {
long getMove(T game);
}

View File

@@ -0,0 +1,5 @@
package org.toop.framework.gameFramework.model.player;
public interface NameProvider {
String getName();
}

View File

@@ -0,0 +1,152 @@
package org.toop.framework.networking;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.SnowflakeGenerator;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.eventbus.bus.EventBus;
import org.toop.framework.networking.events.NetworkEvents;
import org.toop.framework.networking.exceptions.ClientNotFoundException;
import org.toop.framework.networking.interfaces.NetworkingClientManager;
public class NetworkingClientEventListener {
private static final Logger logger = LogManager.getLogger(NetworkingClientEventListener.class);
private final NetworkingClientManager clientManager;
/** Starts a connection manager, to manage, connections. */
public NetworkingClientEventListener(EventBus eventBus, NetworkingClientManager clientManager) {
this.clientManager = clientManager;
new EventFlow(eventBus)
.listen(NetworkEvents.StartClient.class, this::handleStartClient, false)
.listen(NetworkEvents.SendCommand.class, this::handleCommand, false)
.listen(NetworkEvents.SendLogin.class, this::handleSendLogin, false)
.listen(NetworkEvents.SendLogout.class, this::handleSendLogout, false)
.listen(NetworkEvents.SendGetPlayerlist.class, this::handleSendGetPlayerlist, false)
.listen(NetworkEvents.SendGetGamelist.class, this::handleSendGetGamelist, false)
.listen(NetworkEvents.SendSubscribe.class, this::handleSendSubscribe, false)
.listen(NetworkEvents.SendMove.class, this::handleSendMove, false)
.listen(NetworkEvents.SendChallenge.class, this::handleSendChallenge, false)
.listen(NetworkEvents.SendAcceptChallenge.class, this::handleSendAcceptChallenge, false)
.listen(NetworkEvents.SendForfeit.class, this::handleSendForfeit, false)
.listen(NetworkEvents.SendMessage.class, this::handleSendMessage, false)
.listen(NetworkEvents.SendHelp.class, this::handleSendHelp, false)
.listen(NetworkEvents.SendHelpForCommand.class, this::handleSendHelpForCommand, false)
.listen(NetworkEvents.CloseClient.class, this::handleCloseClient, false)
.listen(NetworkEvents.Reconnect.class, this::handleReconnect, false)
.listen(NetworkEvents.ChangeAddress.class, this::handleChangeAddress, false)
.listen(NetworkEvents.RequestsAllClients.class, this::handleGetAllConnections, false)
.listen(NetworkEvents.ForceCloseAllClients.class, this::handleShutdownAll, false);
}
void handleStartClient(NetworkEvents.StartClient event) {
long clientId = SnowflakeGenerator.nextId();
new EventFlow().addPostEvent(new NetworkEvents.CreatedIdForClient(clientId, event.identifier())).postEvent();
clientManager.startClient(
clientId,
event.networkingClient(),
event.networkingConnector(),
() -> new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(clientId, true, event.identifier())).postEvent(),
() -> new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(clientId, false, event.identifier())).postEvent()
);
}
private void sendCommand(long clientId, String command) {
try {
clientManager.sendCommand(clientId, command);
} catch (ClientNotFoundException e) {
logger.error(e);
}
}
private void handleCommand(NetworkEvents.SendCommand event) {
String args = String.join(" ", event.args());
sendCommand(event.clientId(), args);
}
private void handleSendLogin(NetworkEvents.SendLogin event) {
sendCommand(event.clientId(), String.format("LOGIN %s", event.username()));
}
private void handleSendLogout(NetworkEvents.SendLogout event) {
sendCommand(event.clientId(), "LOGOUT");
}
private void handleSendGetPlayerlist(NetworkEvents.SendGetPlayerlist event) {
sendCommand(event.clientId(), "GET PLAYERLIST");
}
private void handleSendGetGamelist(NetworkEvents.SendGetGamelist event) {
sendCommand(event.clientId(), "GET GAMELIST");
}
private void handleSendSubscribe(NetworkEvents.SendSubscribe event) {
sendCommand(event.clientId(), String.format("SUBSCRIBE %s", event.gameType()));
}
private void handleSendMove(NetworkEvents.SendMove event) {
sendCommand(event.clientId(), String.format("MOVE %d", event.moveNumber()));
}
private void handleSendChallenge(NetworkEvents.SendChallenge event) {
sendCommand(event.clientId(), String.format("CHALLENGE %s %s", event.usernameToChallenge(), event.gameType()));
}
private void handleSendAcceptChallenge(NetworkEvents.SendAcceptChallenge event) {
sendCommand(event.clientId(), String.format("CHALLENGE ACCEPT %d", event.challengeId()));
}
private void handleSendForfeit(NetworkEvents.SendForfeit event) {
sendCommand(event.clientId(), "FORFEIT");
}
private void handleSendMessage(NetworkEvents.SendMessage event) {
sendCommand(event.clientId(), String.format("MESSAGE %s", event.message()));
}
private void handleSendHelp(NetworkEvents.SendHelp event) {
sendCommand(event.clientId(), "HELP");
}
private void handleSendHelpForCommand(NetworkEvents.SendHelpForCommand event) {
sendCommand(event.clientId(), String.format("HELP %s", event.command()));
}
private void handleReconnect(NetworkEvents.Reconnect event) {
clientManager.startClient(
event.clientId(),
event.networkingClient(),
event.networkingConnector(),
() -> new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(true, event.identifier())).postEvent(),
() -> new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(false, event.identifier())).postEvent()
);
}
private void handleChangeAddress(NetworkEvents.ChangeAddress event) {
clientManager.startClient(
event.clientId(),
event.networkingClient(),
event.networkingConnector(),
() -> new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(true, event.identifier())).postEvent(),
() -> new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(false, event.identifier())).postEvent()
);
}
void handleCloseClient(NetworkEvents.CloseClient event) {
try {
this.clientManager.closeClient(event.clientId());
} catch (ClientNotFoundException e) {
logger.error(e);
}
}
void handleGetAllConnections(NetworkEvents.RequestsAllClients request) {
// List<NetworkingClient> a = new ArrayList<>(this.networkClients.values());
// request.future().complete(a);
// TODO
}
public void handleShutdownAll(NetworkEvents.ForceCloseAllClients request) {
// TODO
}
}

View File

@@ -1,4 +1,8 @@
<<<<<<<< HEAD:framework/src/main/java/org/toop/framework/networking/connection/clients/TournamentNetworkingClient.java
package org.toop.framework.networking.connection.clients;
========
package org.toop.framework.networking.clients;
>>>>>>>> refs/remotes/origin/main:framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
@@ -12,10 +16,16 @@ import io.netty.util.CharsetUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.eventbus.bus.EventBus;
<<<<<<<< HEAD:framework/src/main/java/org/toop/framework/networking/connection/clients/TournamentNetworkingClient.java
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;
========
import org.toop.framework.networking.exceptions.CouldNotConnectException;
import org.toop.framework.networking.handlers.NetworkingGameClientHandler;
import org.toop.framework.networking.interfaces.NetworkingClient;
>>>>>>>> refs/remotes/origin/main:framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java
import java.net.InetSocketAddress;
@@ -24,7 +34,10 @@ public class TournamentNetworkingClient implements NetworkingClient {
private final EventBus eventBus;
private Channel channel;
<<<<<<<< HEAD:framework/src/main/java/org/toop/framework/networking/connection/clients/TournamentNetworkingClient.java
private long clientId;
========
>>>>>>>> refs/remotes/origin/main:framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java
public TournamentNetworkingClient(EventBus eventBus) {
this.eventBus = eventBus;
@@ -37,7 +50,10 @@ public class TournamentNetworkingClient implements NetworkingClient {
@Override
public void connect(long clientId, String host, int port) throws CouldNotConnectException {
<<<<<<<< HEAD:framework/src/main/java/org/toop/framework/networking/connection/clients/TournamentNetworkingClient.java
this.clientId = clientId;
========
>>>>>>>> refs/remotes/origin/main:framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java
try {
Bootstrap bootstrap = new Bootstrap();
EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
@@ -57,6 +73,7 @@ public class TournamentNetworkingClient implements NetworkingClient {
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(handler);
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
this.channel = channelFuture.channel();
@@ -78,7 +95,10 @@ public class TournamentNetworkingClient implements NetworkingClient {
logger.info("Connection {} sent message: '{}' ", this.channel.remoteAddress(), literalMsg);
} else {
logger.warn("Cannot send message: '{}', connection inactive. ", literalMsg);
<<<<<<<< HEAD:framework/src/main/java/org/toop/framework/networking/connection/clients/TournamentNetworkingClient.java
eventBus.post(new NetworkEvents.ClosedConnection(clientId));
========
>>>>>>>> refs/remotes/origin/main:framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java
}
}

View File

@@ -1,4 +1,8 @@
<<<<<<<< HEAD:framework/src/main/java/org/toop/framework/networking/connection/exceptions/NetworkingInitializationException.java
package org.toop.framework.networking.connection.exceptions;
========
package org.toop.framework.networking.exceptions;
>>>>>>>> refs/remotes/origin/main:framework/src/main/java/org/toop/framework/networking/exceptions/NetworkingInitializationException.java
public class NetworkingInitializationException extends RuntimeException {
public NetworkingInitializationException(String message, Throwable cause) {

View File

@@ -1,4 +1,8 @@
<<<<<<<< HEAD:framework/src/main/java/org/toop/framework/networking/connection/handlers/NetworkingGameClientHandler.java
package org.toop.framework.networking.connection.handlers;
========
package org.toop.framework.networking.handlers;
>>>>>>>> refs/remotes/origin/main:framework/src/main/java/org/toop/framework/networking/handlers/NetworkingGameClientHandler.java
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@@ -9,7 +13,11 @@ import java.util.regex.Pattern;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.eventbus.bus.EventBus;
<<<<<<<< HEAD:framework/src/main/java/org/toop/framework/networking/connection/handlers/NetworkingGameClientHandler.java
import org.toop.framework.networking.connection.events.NetworkEvents;
========
import org.toop.framework.networking.events.NetworkEvents;
>>>>>>>> refs/remotes/origin/main:framework/src/main/java/org/toop/framework/networking/handlers/NetworkingGameClientHandler.java
public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LogManager.getLogger(NetworkingGameClientHandler.class);

View File

@@ -0,0 +1,25 @@
package org.toop.framework.networking.exceptions;
/**
* Thrown when an operation is attempted on a networking client
* that does not exist or has already been closed.
*/
public class ClientNotFoundException extends RuntimeException {
private final long clientId;
public ClientNotFoundException(long clientId) {
super("Networking client with ID " + clientId + " was not found.");
this.clientId = clientId;
}
public ClientNotFoundException(long clientId, Throwable cause) {
super("Networking client with ID " + clientId + " was not found.", cause);
this.clientId = clientId;
}
public long getClientId() {
return clientId;
}
}

View File

@@ -0,0 +1,21 @@
package org.toop.framework.networking.exceptions;
public class CouldNotConnectException extends RuntimeException {
private final long clientId;
public CouldNotConnectException(long clientId) {
super("Networking client with ID " + clientId + " could not connect.");
this.clientId = clientId;
}
public CouldNotConnectException(long clientId, Throwable cause) {
super("Networking client with ID " + clientId + " could not connect.", cause);
this.clientId = clientId;
}
public long getClientId() {
return clientId;
}
}

View File

@@ -0,0 +1,13 @@
package org.toop.framework.networking.interfaces;
import org.toop.framework.networking.exceptions.CouldNotConnectException;
import java.net.InetSocketAddress;
public interface NetworkingClient {
InetSocketAddress getAddress();
void connect(long clientId, String host, int port) throws CouldNotConnectException;
boolean isActive();
void writeAndFlush(String msg);
void closeConnection();
}

View File

@@ -0,0 +1,17 @@
package org.toop.framework.networking.interfaces;
import org.toop.framework.networking.exceptions.ClientNotFoundException;
import org.toop.framework.networking.exceptions.CouldNotConnectException;
import org.toop.framework.networking.types.NetworkingConnector;
public interface NetworkingClientManager {
void startClient(
long id,
NetworkingClient nClient,
NetworkingConnector nConnector,
Runnable onSuccess,
Runnable onFailure
) throws CouldNotConnectException;
void sendCommand(long id, String command) throws ClientNotFoundException;
void closeClient(long id) throws ClientNotFoundException;
}

View File

@@ -0,0 +1,5 @@
package org.toop.framework.networking.types;
import java.util.concurrent.TimeUnit;
public record NetworkingConnector(String host, int port, int reconnectAttempts, long timeout, TimeUnit timeUnit) {}

View File

@@ -0,0 +1,3 @@
package org.toop.framework.networking.types;
public record ServerCommand(long clientId, String command) {}

View File

@@ -0,0 +1,3 @@
package org.toop.framework.networking.types;
public record ServerMessage(String message) {}