Merge new framework into development (#269)

* Created a somewhat generic TurnBasedGame thread. Temporary UI that only works for TicTacToe rn. Added a LocalPlayer with the intent to add more players

* (RANDOM COMMIT) Hope it works

* Changes by bas

* Fixed dependency issues

* Fixed major issue in game deepcopy

* Merge conflict fix

* Removed unused import

* Update GTBGT branch from dev branch (#263)

* started a basis for the tutorials, tic tac toe is almost done with some general stuff still to do.

* rest van de tutorials toegevoegd

* Removed views

* Merge conflict fix

* Removed unused import

---------

Co-authored-by: michiel301b <m.brands.3@st.hanze.nl>
Co-authored-by: ramollia <>
Co-authored-by: Bas Antonius de Jong <49651652+BAFGdeJong@users.noreply.github.com>

* Revert "Update GTBGT branch from dev branch (#263)"

This reverts commit 9134d7e343.

* Fixed frontend not using GameController because of spaghetti code.

* Removed unused imports

* GameCanvas not implements a DrawPlayerMove that can be overridden for specific implementations

* Created an event that will request the controller to refresh the UI.

* ADDED DEPENDENCY. Renamed GameControllers to GameManagers, gameThread is not game controller.

* Attempt at adding an online player. I think it doesn't work because of unsubscriben after success not working

* Multiplayer is functional through OnlineThreadBehaviour. Empty slots are currently represented by -1 in the GUI.

* Removed sout spam, added logger than I can't get to work.

* Idek what these changes are

* Te lang geen commit, sorry

* Multiplayer seems to work pretty well now, hopefully I can add the other games soon.

* Added unsubscribe to EventFlow. ListenerHandler now functional. GlobalEventbus now user listenerHandler

* getAllListeners

* Removed nulls

* Inbetween commit of adding Reversi. This is a lot of spaghetti.

* Fixed stress tests

* Fixed typo in NetworkingGameClientHandler that prevented losses from being received

* Missed 2nd typo. Fixed

* Added docs, no more list creation when adding events to the bus.

* Fixed unsubscribe not working.

* Moved away from deprecated functions

* moved from wildcard to typed

* Moved away from deprecated function

* Added debugging to GlobalEventBus

* Fixed cleaning flow

* Fixed unsubscribe all

* Fixed unsubscribe all

* Removed unused import

* Works now with updated EventFlow(). Unsubscribing works. ReversiAIR has an issue where a forced move returns -1 and local play back button doesn't work properly. To be fixed

* Fixed ReversiR issue that caused skip turn desync

* Fixed color mismatch with server and online main player is now correct.

* Added a bunch of java doc and small changes

* Small changes

* Added a new Thread Behaviour to test framework.

* Fixed human error I made in TicTacToeR logic...

* Fixed broken event and wrong player being presented as winner.

* Idk changes

* Fixed PR conflicts

---------

Co-authored-by: michiel301b <m.brands.3@st.hanze.nl>
Co-authored-by: Bas Antonius de Jong <49651652+BAFGdeJong@users.noreply.github.com>
This commit is contained in:
Stef
2025-12-02 11:25:22 +01:00
committed by GitHub
parent d9437c1b8a
commit 9f55f8e1c7
54 changed files with 2121 additions and 78 deletions

View File

@@ -0,0 +1,30 @@
package org.toop.framework.gameFramework;
import org.toop.framework.eventbus.events.EventsBase;
import org.toop.framework.eventbus.events.GenericEvent;
/**
* Defines GUI-related events for the event bus.
* <p>
* These events notify the UI about updates such as game progress,
* player actions, and game completion.
*/
public class GUIEvents extends EventsBase {
/** Event to refresh or redraw the game canvas. */
public record RefreshGameCanvas() implements GenericEvent {}
/**
* Event indicating the game has ended.
*
* @param winOrTie true if the game ended in a win, false for a draw
* @param winner the index of the winning player, or -1 if no winner
*/
public record GameEnded(boolean winOrTie, int winner) implements GenericEvent {}
/** Event indicating a player has attempted a move. */
public record PlayerAttemptedMove(int move) implements GenericEvent {}
/** Event indicating a player is hovering over a move (for UI feedback). */
public record PlayerMoveHovered(int move) implements GenericEvent {}
}

View File

@@ -0,0 +1,18 @@
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. */
NORMAL,
/** Game ended in a draw. */
DRAW,
/** Game ended with a win for a player. */
WIN,
/** Next player's turn was skipped. */
TURN_SKIPPED,
}

View File

@@ -0,0 +1,12 @@
package org.toop.framework.gameFramework;
import org.toop.framework.gameFramework.GameState;
/**
* Represents the result of a move in a turn-based game.
*
* @param state the resulting {@link GameState} after the move
* @param player the index of the player associated with the result (winner or relevant player)
*/
public record PlayResult(GameState state, int player) {
}

View File

@@ -0,0 +1,17 @@
package org.toop.framework.gameFramework.abstractClasses;
import org.toop.framework.gameFramework.interfaces.IAIMoveR;
/**
* Abstract base class for AI implementations for games extending {@link GameR}.
* <p>
* Provides a common superclass for specific AI algorithms. Concrete subclasses
* must implement the {@link #findBestMove(GameR, int)} method defined by
* {@link IAIMoveR} to determine the best move given a game state and a search depth.
* </p>
*
* @param <T> the specific type of game this AI can play, extending {@link GameR}
*/
public abstract class AIR<T extends GameR> implements IAIMoveR<T> {
// Concrete AI implementations should override findBestMove(T game, int depth)
}

View File

@@ -0,0 +1,127 @@
package org.toop.framework.gameFramework.abstractClasses;
import org.toop.framework.gameFramework.interfaces.IPlayableR;
import java.util.Arrays;
/**
* Abstract base class representing a general grid-based game.
* <p>
* Provides the basic structure for games with a two-dimensional board stored as a
* one-dimensional array. Tracks the board state, row and column sizes, and provides
* helper methods for accessing and modifying the board.
* </p>
* <p>
* Concrete subclasses must implement the {@link #clone()} method and can extend this
* class with specific game rules, winning conditions, and move validation logic.
* </p>
*/
public abstract class GameR implements IPlayableR, Cloneable {
/** Constant representing an empty position on the board. */
public static final int EMPTY = -1;
/** Number of rows in the game board. */
private final int rowSize;
/** Number of columns in the game board. */
private final int columnSize;
/** The game board stored as a one-dimensional array. */
private final int[] board;
/**
* Constructs a new game board with the specified row and column size.
*
* @param rowSize number of rows (> 0)
* @param columnSize number of columns (> 0)
* @throws AssertionError if rowSize or columnSize is not positive
*/
protected GameR(int rowSize, int columnSize) {
assert rowSize > 0 && columnSize > 0;
this.rowSize = rowSize;
this.columnSize = columnSize;
board = new int[rowSize * columnSize];
Arrays.fill(board, EMPTY);
}
/**
* Copy constructor for creating a deep copy of another game instance.
*
* @param copy the game instance to copy
*/
protected GameR(GameR copy) {
this.rowSize = copy.rowSize;
this.columnSize = copy.columnSize;
this.board = copy.board.clone();
}
/**
* Check if an array contains a value.
*
* @param array array containing ints
* @param value int to check for
*
* @return true if array contains value
*/
public static boolean contains(int[] array, int value) {
// O(n)
for (int element : array){
if (element == value) return true;
}
return false;
}
/**
* Returns the number of rows in the board.
*
* @return number of rows
*/
public int getRowSize() {
return this.rowSize;
}
/**
* Returns the number of columns in the board.
*
* @return number of columns
*/
public int getColumnSize() {
return this.columnSize;
}
/**
* Returns a copy of the current board state.
*
* @return a cloned array representing the board
*/
public int[] getBoard() {
return this.board.clone();
}
/**
* Sets the value of a specific position on the board.
*
* @param position the index in the board array
* @param player the value to set (e.g., player number)
*/
protected void setBoardPosition(int position, int player) {
this.board[position] = player;
}
/**
* Creates and returns a deep copy of this game instance.
* <p>
* Subclasses must implement this method to ensure proper copying of any
* additional fields beyond the base board structure.
* </p>
*
* @return a cloned instance of this game
*/
@Override
public abstract GameR clone();
}

View File

@@ -0,0 +1,31 @@
package org.toop.framework.gameFramework.abstractClasses;
public abstract class TurnBasedGameR extends GameR {
private final int playerCount; // How many players are playing
private int turn = 0; // What turn it is in the game
protected TurnBasedGameR(int rowSize, int columnSize, int playerCount) {
super(rowSize, columnSize);
this.playerCount = playerCount;
}
protected TurnBasedGameR(TurnBasedGameR other){
super(other);
this.playerCount = other.playerCount;
this.turn = other.turn;
}
public int getPlayerCount(){return this.playerCount;}
protected void nextTurn() {
turn += 1;
}
public int getCurrentTurn() {
return turn % playerCount;
}
protected void setBoard(int position) {
super.setBoardPosition(position, getCurrentTurn());
}
}

View File

@@ -0,0 +1,20 @@
package org.toop.framework.gameFramework.interfaces;
import org.toop.framework.gameFramework.abstractClasses.GameR;
/**
* AI interface for selecting the best move in a game.
*
* @param <T> the type of game this AI can play, extending {@link GameR}
*/
public interface IAIMoveR<T extends GameR> {
/**
* Determines the optimal move for the current player.
*
* @param game the current game state
* @param depth the search depth for evaluating moves
* @return an integer representing the chosen move
*/
int findBestMove(T game, int depth);
}

View File

@@ -0,0 +1,25 @@
package org.toop.framework.gameFramework.interfaces;
import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.PlayResult;
/**
* Interface for turn-based games that can be played and queried for legal moves.
*/
public interface IPlayableR {
/**
* Returns the moves that are currently valid in the game.
*
* @return an array of integers representing legal moves
*/
int[] 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(int move);
}

View File

@@ -0,0 +1,20 @@
package org.toop.framework.gameFramework.interfaces;
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 yourTurn(NetworkEvents.YourTurnResponse event);
/** Called when a move from another player is received. */
void moveReceived(NetworkEvents.GameMoveResponse event);
/** Called when the game has finished, with the final result. */
void gameFinished(NetworkEvents.GameResultResponse event);
}

View File

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

View File

@@ -2,6 +2,8 @@ package org.toop.framework.networking.handlers;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.logging.log4j.LogManager;
@@ -70,7 +72,7 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
case "CHALLENGE":
gameChallengeHandler(recSrvRemoved);
return;
case "WIN", "DRAW", "LOSE":
case "WIN", "DRAW", "LOSS":
gameWinConditionHandler(recSrvRemoved);
return;
default:
@@ -119,13 +121,12 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
}
private void gameWinConditionHandler(String rec) {
@SuppressWarnings("StreamToString")
String condition =
Pattern.compile("\\b(win|draw|lose)\\b", Pattern.CASE_INSENSITIVE)
.matcher(rec)
.results()
.toString()
.trim();
String condition = Pattern.compile("\\b(win|draw|loss)\\b", Pattern.CASE_INSENSITIVE)
.matcher(rec)
.results()
.map(MatchResult::group)
.findFirst()
.orElse("");
new EventFlow()
.addPostEvent(new NetworkEvents.GameResultResponse(this.connectionId, condition))