2 Commits

Author SHA1 Message Date
Stef
1ae79daef0 Added documentation to player classes and improved method names (#295) 2025-12-10 13:17:01 +01:00
Stef
cd8eb99559 Merge 292 into development (#293)
Applied template method pattern to abstract player
2025-12-10 12:39:40 +01:00
9 changed files with 148 additions and 118 deletions

View File

@@ -21,7 +21,7 @@ import org.toop.game.games.reversi.BitboardReversi;
import org.toop.game.games.tictactoe.BitboardTicTacToe; import org.toop.game.games.tictactoe.BitboardTicTacToe;
import org.toop.game.players.ArtificialPlayer; import org.toop.game.players.ArtificialPlayer;
import org.toop.game.players.OnlinePlayer; import org.toop.game.players.OnlinePlayer;
import org.toop.game.players.RandomAI; import org.toop.game.players.ai.RandomAI;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import java.util.List; import java.util.List;

View File

@@ -55,7 +55,7 @@ public class GenericGameController<T extends TurnBasedGame<T>> implements GameCo
// Listen to updates // Listen to updates
eventFlow eventFlow
.listen(GUIEvents.GameEnded.class, this::onGameFinish, false) .listen(GUIEvents.GameEnded.class, this::onGameFinish, false)
.listen(GUIEvents.PlayerAttemptedMove.class, event -> {if (getCurrentPlayer() instanceof LocalPlayer<T> lp){lp.setMove(event.move());}}, false); .listen(GUIEvents.PlayerAttemptedMove.class, event -> {if (getCurrentPlayer() instanceof LocalPlayer<T> lp){lp.setLastMove(event.move());}}, false);
} }
public void start(){ public void start(){

View File

@@ -2,9 +2,6 @@ package org.toop.app.widget.view;
import javafx.application.Platform; import javafx.application.Platform;
import org.toop.app.GameInformation; import org.toop.app.GameInformation;
import org.toop.app.canvas.ReversiBitCanvas;
import org.toop.app.canvas.TicTacToeBitCanvas;
import org.toop.app.gameControllers.GenericGameController;
import org.toop.app.gameControllers.ReversiBitController; import org.toop.app.gameControllers.ReversiBitController;
import org.toop.app.gameControllers.TicTacToeBitController; import org.toop.app.gameControllers.TicTacToeBitController;
import org.toop.framework.gameFramework.controller.GameController; import org.toop.framework.gameFramework.controller.GameController;
@@ -18,8 +15,8 @@ import org.toop.app.widget.complex.PlayerInfoWidget;
import org.toop.app.widget.complex.ViewWidget; import org.toop.app.widget.complex.ViewWidget;
import org.toop.app.widget.popup.ErrorPopup; import org.toop.app.widget.popup.ErrorPopup;
import org.toop.app.widget.tutorial.*; import org.toop.app.widget.tutorial.*;
import org.toop.game.players.MiniMaxAI; import org.toop.game.players.ai.MiniMaxAI;
import org.toop.game.players.RandomAI; import org.toop.game.players.ai.RandomAI;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.geometry.Pos; import javafx.geometry.Pos;
@@ -27,9 +24,6 @@ import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import org.toop.local.AppSettings; import org.toop.local.AppSettings;
import java.util.Arrays;
import java.util.Random;
public class LocalMultiplayerView extends ViewWidget { public class LocalMultiplayerView extends ViewWidget {
private final GameInformation information; private final GameInformation information;

View File

@@ -5,46 +5,66 @@ import org.apache.logging.log4j.Logger;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
/** /**
* Abstract class representing a player in a game. * Base class for players in a turn-based game.
* <p> *
* Players are entities that can make moves based on the current state of a game. * @param <T> the game type
* player types, such as human players or AI players.
* </p>
* <p>
* Subclasses should override the {@link #getMove(GameR)} method to provide
* specific move logic.
* </p>
*/ */
public abstract class AbstractPlayer<T extends TurnBasedGame<T>> implements Player<T> { public abstract class AbstractPlayer<T extends TurnBasedGame<T>> implements Player<T> {
private final Logger logger = LogManager.getLogger(this.getClass());
private final Logger logger = LogManager.getLogger(this.getClass());
private final String name; private final String name;
/**
* Creates a new player with the given name.
*
* @param name the player name
*/
protected AbstractPlayer(String name) { protected AbstractPlayer(String name) {
this.name = name; this.name = name;
} }
/**
* Creates a copy of another player.
*
* @param other the player to copy
*/
protected AbstractPlayer(AbstractPlayer<T> other) { protected AbstractPlayer(AbstractPlayer<T> other) {
this.name = other.name; this.name = other.name;
} }
/** /**
* Determines the next move based on the provided game state. * Gets the player's move for the given game state.
* A deep copy is provided so the player cannot modify the real state.
* <p> * <p>
* The default implementation throws an {@link UnsupportedOperationException}, * This method uses the Template Method Pattern: it defines the fixed
* indicating that concrete subclasses must override this method to provide * algorithm and delegates the variable part to {@link #determineMove(T)}.
* actual move logic.
* </p>
* *
* @param gameCopy a snapshot of the current game state * @param game the current game
* @return an integer representing the chosen move * @return the chosen move
* @throws UnsupportedOperationException if the method is not overridden
*/ */
public long getMove(T gameCopy) { public final long getMove(T game) {
logger.error("Method getMove not implemented."); return determineMove(game.deepCopy());
throw new UnsupportedOperationException("Not supported yet.");
} }
public String getName(){
/**
* Determines the player's move using a safe copy of the game.
* <p>
* This method is called by {@link #getMove(T)} and should contain
* the player's strategy for choosing a move.
*
* @param gameCopy a deep copy of the game
* @return the chosen move
*/
protected abstract long determineMove(T gameCopy);
/**
* Returns the player's name.
*
* @return the name
*/
public String getName() {
return this.name; return this.name;
} }
} }

View File

@@ -4,52 +4,52 @@ import org.toop.framework.gameFramework.model.player.*;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
/** /**
* Represents a player controlled by an AI in a game. * Represents a player controlled by an AI.
* <p>
* This player uses an {@link AbstractAI} instance to determine its moves. The generic
* parameter {@code T} specifies the type of {@link GameR} the AI can handle.
* </p>
* *
* @param <T> the specific type of game this AI player can play * @param <T> the type of turn-based game
*/ */
public class ArtificialPlayer<T extends TurnBasedGame<T>> extends AbstractPlayer<T> { public class ArtificialPlayer<T extends TurnBasedGame<T>> extends AbstractPlayer<T> {
/** The AI instance used to calculate moves. */
private final AI<T> ai; private final AI<T> ai;
/** /**
* Constructs a new ArtificialPlayer using the specified AI. * Creates a new AI-controlled player.
* *
* @param ai the AI instance that determines moves for this player * @param ai the AI controlling this player
* @param name the player's name
*/ */
public ArtificialPlayer(AI<T> ai, String name) { public ArtificialPlayer(AI<T> ai, String name) {
super(name); super(name);
this.ai = ai; this.ai = ai;
} }
/**
* Creates a copy of another AI-controlled player.
*
* @param other the player to copy
*/
public ArtificialPlayer(ArtificialPlayer<T> other) { public ArtificialPlayer(ArtificialPlayer<T> other) {
super(other); super(other);
this.ai = other.ai.deepCopy(); this.ai = other.ai.deepCopy();
} }
/** /**
* Determines the next move for this player using its AI. * Determines the player's move using the AI.
* <p>
* This method overrides {@link AbstractPlayer#getMove(GameR)}. Because the AI is
* typed to {@code T}, a runtime cast is required. It is the caller's
* responsibility to ensure that {@code gameCopy} is of type {@code T}.
* </p>
* *
* @param gameCopy a copy of the current game state * @param gameCopy a copy of the current game
* @return the integer representing the chosen move * @return the move chosen by the AI
* @throws ClassCastException if {@code gameCopy} is not of type {@code T}
*/ */
public long getMove(T gameCopy) { protected long determineMove(T gameCopy) {
return ai.getMove(gameCopy); return ai.getMove(gameCopy);
} }
/**
* Creates a deep copy of this AI player.
*
* @return a copy of this player
*/
@Override @Override
public ArtificialPlayer<T> deepCopy() { public ArtificialPlayer<T> deepCopy() {
return new ArtificialPlayer<T>(this); return new ArtificialPlayer<>(this);
} }
} }

View File

@@ -2,85 +2,86 @@ package org.toop.game.players;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.AbstractPlayer; import org.toop.framework.gameFramework.model.player.AbstractPlayer;
import org.toop.framework.gameFramework.model.player.Player;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
/**
* Represents a local player who provides moves manually.
*
* @param <T> the type of turn-based game
*/
public class LocalPlayer<T extends TurnBasedGame<T>> extends AbstractPlayer<T> { public class LocalPlayer<T extends TurnBasedGame<T>> extends AbstractPlayer<T> {
// Future can be used with event system, IF unsubscribeAfterSuccess works...
// private CompletableFuture<Integer> LastMove = new CompletableFuture<>();
private CompletableFuture<Long> LastMove; private CompletableFuture<Long> LastMove = new CompletableFuture<>();
/**
* Creates a new local player with the given name.
*
* @param name the player's name
*/
public LocalPlayer(String name) { public LocalPlayer(String name) {
super(name); super(name);
} }
/**
* Creates a copy of another local player.
*
* @param other the player to copy
*/
public LocalPlayer(LocalPlayer<T> other) { public LocalPlayer(LocalPlayer<T> other) {
super(other); super(other);
this.LastMove = other.LastMove;
} }
/**
* Waits for and returns the player's next legal move.
*
* @param gameCopy a copy of the current game
* @return the chosen move
*/
@Override @Override
public long getMove(T gameCopy) { protected long determineMove(T gameCopy) {
return getValidMove(gameCopy); long legalMoves = gameCopy.getLegalMoves();
long move;
do {
move = getLastMove();
} while ((legalMoves & move) == 0);
return move;
} }
public void setMove(long move) { /**
* Sets the player's last move.
*
* @param move the move to set
*/
public void setLastMove(long move) {
LastMove.complete(move); LastMove.complete(move);
} }
// TODO: helper function, would like to replace to get rid of this method /**
public static boolean contains(int[] array, int value){ * Waits for the next move from the player.
for (int i : array) if (i == value) return true; *
return false; * @return the chosen move or 0 if interrupted
} */
private long getLastMove() {
private long getMove2(T gameCopy) { LastMove = new CompletableFuture<>(); // Reset the future
LastMove = new CompletableFuture<>();
long move = 0;
try { try {
move = LastMove.get(); return LastMove.get();
System.out.println(Long.toBinaryString(move)); } catch (ExecutionException | InterruptedException e) {
} catch (InterruptedException | ExecutionException e) { return 0;
// TODO: Add proper logging.
e.printStackTrace();
} }
return move;
}
protected long getValidMove(T gameCopy){
// Get this player's valid moves
long validMoves = gameCopy.getLegalMoves();
// Make sure provided move is valid
// TODO: Limit amount of retries?
// TODO: Stop copying game so many times
long move = getMove2(gameCopy.deepCopy());
while ((validMoves & move) == 0) {
System.out.println("Not a valid move, try again");
move = getMove2(gameCopy.deepCopy());
}
return move;
} }
/**
* Creates a deep copy of this local player.
*
* @return a copy of this player
*/
@Override @Override
public LocalPlayer<T> deepCopy() { public LocalPlayer<T> deepCopy() {
return new LocalPlayer<T>(this.getName()); return new LocalPlayer<>(this);
} }
/*public void register() {
// Listening to PlayerAttemptedMove
new EventFlow().listen(GUIEvents.PlayerAttemptedMove.class, event -> {
if (!LastMove.isDone()) {
LastMove.complete(event.move()); // complete the future
}
}, true); // auto-unsubscribe
}
// This blocks until the next move arrives
public int take() throws ExecutionException, InterruptedException {
int move = LastMove.get(); // blocking
LastMove = new CompletableFuture<>(); // reset for next move
return move;
}*/
} }

View File

@@ -5,30 +5,45 @@ import org.toop.framework.gameFramework.model.player.AbstractPlayer;
import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.gameFramework.model.player.Player;
/** /**
* Represents a player controlled remotely or over a network. * Represents a player that participates online.
* <p> *
* This class extends {@link AbstractPlayer} and can be used to implement game logic * @param <T> the type of turn-based game
* where moves are provided by an external source (e.g., another user or a server).
* Currently, this class is a placeholder and does not implement move logic.
* </p>
*/ */
public class OnlinePlayer<T extends TurnBasedGame<T>> extends AbstractPlayer<T> { public class OnlinePlayer<T extends TurnBasedGame<T>> extends AbstractPlayer<T> {
/** /**
* Constructs a new OnlinePlayer. * Creates a new online player with the given name.
* <p> *
* Currently, no additional initialization is performed. Subclasses or * @param name the name of the player
* future implementations should provide mechanisms to receive moves from
* an external source.
*/ */
public OnlinePlayer(String name) { public OnlinePlayer(String name) {
super(name); super(name);
} }
/**
* Creates a copy of another online player.
*
* @param other the player to copy
*/
public OnlinePlayer(OnlinePlayer<T> other) { public OnlinePlayer(OnlinePlayer<T> other) {
super(other); super(other);
} }
/**
* {@inheritDoc}
* <p>
* This method is not supported for online players.
*
* @throws UnsupportedOperationException always
*/
@Override
protected long determineMove(T gameCopy) {
throw new UnsupportedOperationException("An online player does not support determining move");
}
/**
* {@inheritDoc}
*/
@Override @Override
public Player<T> deepCopy() { public Player<T> deepCopy() {
return new OnlinePlayer<>(this); return new OnlinePlayer<>(this);

View File

@@ -1,4 +1,4 @@
package org.toop.game.players; package org.toop.game.players.ai;
import org.toop.framework.gameFramework.GameState; import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult; import org.toop.framework.gameFramework.model.game.PlayResult;

View File

@@ -1,4 +1,4 @@
package org.toop.game.players; package org.toop.game.players.ai;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.AbstractAI; import org.toop.framework.gameFramework.model.player.AbstractAI;