Merge remote-tracking branch 'origin/289-server' into Development

# Conflicts:
#	app/src/main/java/org/toop/app/Server.java
#	app/src/main/java/org/toop/app/gameControllers/GenericGameController.java
#	app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java
#	framework/src/main/java/org/toop/framework/game/BitboardGame.java
#	framework/src/main/java/org/toop/framework/game/players/ArtificialPlayer.java
#	framework/src/main/java/org/toop/framework/game/players/LocalPlayer.java
#	framework/src/main/java/org/toop/framework/game/players/OnlinePlayer.java
#	framework/src/main/java/org/toop/framework/game/players/ai/MiniMaxAI.java
#	framework/src/main/java/org/toop/framework/game/players/ai/RandomAI.java
#	framework/src/main/java/org/toop/framework/gameFramework/model/game/TurnBasedGame.java
#	framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractPlayer.java
#	game/src/main/java/org/toop/game/players/MiniMaxAI.java
#	game/src/main/java/org/toop/game/players/RandomAI.java
#	game/src/main/java/org/toop/game/players/ai/MiniMaxAI.java
#	game/src/main/java/org/toop/game/players/ai/RandomAI.java
This commit is contained in:
2026-01-07 16:13:53 +01:00
112 changed files with 2031 additions and 477 deletions

View File

@@ -1,105 +0,0 @@
package org.toop.game;
import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.Player;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
// There is AI performance to be gained by getting rid of non-primitives and thus speeding up deepCopy
public abstract class BitboardGame<T extends BitboardGame<T>> implements TurnBasedGame<T> {
private final int columnSize;
private final int rowSize;
protected PlayResult state;
private Player<T>[] players;
// long is 64 bits. Every game has a limit of 64 cells maximum.
private final long[] playerBitboard;
private int currentTurn = 0;
public BitboardGame(int columnSize, int rowSize, int playerCount, Player<T>[] players) {
this.columnSize = columnSize;
this.rowSize = rowSize;
this.state = new PlayResult(GameState.NORMAL, -1);
this.players = players;
this.playerBitboard = new long[playerCount];
Arrays.fill(playerBitboard, 0L);
}
public BitboardGame(BitboardGame<T> other) {
this.columnSize = other.columnSize;
this.rowSize = other.rowSize;
this.state = other.state;
this.playerBitboard = other.playerBitboard.clone();
this.currentTurn = other.currentTurn;
this.players = Arrays.stream(other.players)
.map(Player<T>::deepCopy)
.toArray(Player[]::new);
}
public int getColumnSize() {
return this.columnSize;
}
public int getRowSize() {
return this.rowSize;
}
public long getPlayerBitboard(int player) {
return this.playerBitboard[player];
}
public void setPlayerBitboard(int player, long bitboard) {
this.playerBitboard[player] = bitboard;
}
public int getPlayerCount() {
return playerBitboard.length;
}
public int getCurrentTurn() {
return getCurrentPlayerIndex();
}
public Player<T> getPlayer(int index) {
return players[index];
}
public int getCurrentPlayerIndex() {
return currentTurn % playerBitboard.length;
}
public int getNextPlayer() {
return (currentTurn + 1) % playerBitboard.length;
}
public Player<T> getCurrentPlayer(){
return players[getCurrentPlayerIndex()];
}
@Override
public PlayResult getState() {
return state;
}
@Override
public boolean isTerminal() {
return state.state() == GameState.WIN || state.state() == GameState.DRAW;
}
@Override
public long[] getBoard() {return this.playerBitboard;}
public void nextTurn() {
currentTurn++;
}
}

View File

@@ -1,3 +0,0 @@
package org.toop.game;
// TODO: Remove this, only used in ReversiCanvas. Needs to not
public record Move(int position, char value) {}

View File

@@ -1,88 +0,0 @@
package org.toop.game.gameThreads;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour;
import org.toop.framework.gameFramework.view.GUIEvents;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.Player;
import java.util.function.Consumer;
/**
* Handles local turn-based game logic at a fixed update rate.
* <p>
* Runs a separate thread that executes game turns at a fixed frequency (default 60 updates/sec),
* applying player moves, updating the game state, and dispatching UI events.
*/
public class LocalFixedRateThreadBehaviour<T extends TurnBasedGame<T>> extends AbstractThreadBehaviour<T> implements Runnable {
/**
* Creates a fixed-rate behaviour for a local turn-based game.
*
* @param game the game instance
*/
public LocalFixedRateThreadBehaviour(T game) {
super(game);
}
/** Starts the game loop thread if not already running. */
@Override
public void start() {
if (isRunning.compareAndSet(false, true)) {
new Thread(this).start();
}
}
/** Stops the game loop after the current iteration. */
@Override
public void stop() {
isRunning.set(false);
}
/**
* Main loop running at a fixed rate.
* <p>
* Fetches the current player's move, applies it to the game,
* updates the UI, and handles game-ending states.
*/
@Override
public void run() {
final int UPS = 1;
final long UPDATE_INTERVAL = 1_000_000_000L / UPS;
long nextUpdate = System.nanoTime();
while (isRunning.get()) {
long now = System.nanoTime();
if (now >= nextUpdate) {
nextUpdate += UPDATE_INTERVAL;
Player<T> currentPlayer = game.getPlayer(game.getCurrentTurn());
long move = currentPlayer.getMove(game.deepCopy());
PlayResult result = game.play(move);
updateUI();
GameState state = result.state();
switch (state) {
case WIN, DRAW -> {
isRunning.set(false);
new EventFlow().addPostEvent(GUIEvents.GameEnded.class, state == GameState.WIN, result.player()).postEvent();
}
case NORMAL, TURN_SKIPPED -> { /* continue */ }
default -> {
logger.error("Unexpected state {}", state);
isRunning.set(false);
throw new RuntimeException("Unknown state: " + state);
}
}
} else {
try {
Thread.sleep(10);
} catch (InterruptedException ignored) {}
}
}
}
}

View File

@@ -1,76 +0,0 @@
package org.toop.game.gameThreads;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour;
import org.toop.framework.gameFramework.view.GUIEvents;
import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.Player;
import java.util.function.Consumer;
/**
* Handles local turn-based game logic in its own thread.
* <p>
* Repeatedly gets the current player's move, applies it to the game,
* updates the UI, and stops when the game ends or {@link #stop()} is called.
*/
public class LocalThreadBehaviour<T extends TurnBasedGame<T>> extends AbstractThreadBehaviour<T> implements Runnable {
/**
* Creates a new behaviour for a local turn-based game.
*
* @param game the game instance
*/
public LocalThreadBehaviour(T game) {
super(game);
}
/** Starts the game loop in a new thread. */
@Override
public void start() {
if (isRunning.compareAndSet(false, true)) {
new Thread(this).start();
}
}
/** Stops the game loop after the current iteration. */
@Override
public void stop() {
isRunning.set(false);
}
/**
* Main game loop: gets the current player's move, applies it,
* updates the UI, and handles end-of-game states.
*/
@Override
public void run() {
while (isRunning.get()) {
Player<T> currentPlayer = game.getPlayer(game.getCurrentTurn());
long move = currentPlayer.getMove(game.deepCopy());
PlayResult result = game.play(move);
updateUI();
GameState state = result.state();
switch (state) {
case WIN, DRAW -> {
isRunning.set(false);
new EventFlow().addPostEvent(
GUIEvents.GameEnded.class,
state == GameState.WIN,
result.player()
).postEvent();
}
case NORMAL, TURN_SKIPPED -> { /* continue normally */ }
default -> {
logger.error("Unexpected state {}", state);
isRunning.set(false);
throw new RuntimeException("Unknown state: " + state);
}
}
}
}
}

View File

@@ -1,84 +0,0 @@
package org.toop.game.gameThreads;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour;
import org.toop.framework.gameFramework.view.GUIEvents;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.game.SupportsOnlinePlay;
import org.toop.framework.gameFramework.model.player.Player;
import org.toop.game.players.OnlinePlayer;
/**
* Handles online multiplayer game logic.
* <p>
* Reacts to server events, sending moves and updating the game state
* for the local player while receiving moves from other players.
*/
public class OnlineThreadBehaviour<T extends TurnBasedGame<T>> extends AbstractThreadBehaviour<T> implements SupportsOnlinePlay {
/**
* Creates behaviour and sets the first local player
* (non-online player) from the given array.
*/
public OnlineThreadBehaviour(T game) {
super(game);
}
/** Finds the first non-online player in the array. */
private int getFirstNotOnlinePlayer(Player<T>[] players) {
for (int i = 0; i < players.length; i++) {
if (!(players[i] instanceof OnlinePlayer)) {
return i;
}
}
throw new RuntimeException("All players are online players");
}
/** Starts processing network events for the local player. */
@Override
public void start() {
isRunning.set(true);
}
/** Stops processing network events. */
@Override
public void stop() {
isRunning.set(false);
}
/**
* Called when the server notifies that it is the local player's turn.
* Sends the generated move back to the server.
*/
@Override
public void onYourTurn(long clientId) {
if (!isRunning.get()) return;
long move = game.getPlayer(game.getCurrentTurn()).getMove(game.deepCopy());
sendMove(clientId, move);
}
/**
* Handles a move received from the server for any player.
* Updates the game state and triggers a UI refresh.
*/
public void onMoveReceived(long move) {
if (!isRunning.get()) return;
game.play(move);
updateUI();
}
/**
* Handles the end of the game as notified by the server.
* Updates the UI to show a win or draw result for the local player.
*/
public void gameFinished(String condition) {
switch(condition.toUpperCase()){
case "WIN", "LOSS" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, true, game.getWinner()).postEvent();
case "DRAW" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, false, -1).postEvent();
default -> {
logger.error("Invalid condition");
throw new RuntimeException("Unknown condition");
}
}
}
}

View File

@@ -1,40 +0,0 @@
package org.toop.game.gameThreads;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.networking.events.NetworkEvents;
/**
* Online thread behaviour that adds a fixed delay before processing
* the local player's turn.
* <p>
* This is identical to {@link OnlineThreadBehaviour}, but inserts a
* short sleep before delegating to the base implementation.
*/
public class OnlineWithSleepThreadBehaviour<T extends TurnBasedGame<T>> extends OnlineThreadBehaviour<T> {
/**
* Creates the behaviour and forwards the players to the base class.
*
* @param game the online-capable turn-based game
*/
public OnlineWithSleepThreadBehaviour(T game) {
super(game);
}
/**
* Waits briefly before handling the "your turn" event.
*
* @param event the network event indicating it's this client's turn
*/
@Override
public void onYourTurn(long clientId) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
super.onYourTurn(clientId);
}
}

View File

@@ -1,323 +0,0 @@
package org.toop.game.games.reversi;
import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.model.player.Player;
import org.toop.game.BitboardGame;
public class BitboardReversi extends BitboardGame<BitboardReversi> {
public record Score(int black, int white) {}
private final long notAFile = 0xfefefefefefefefeL;
private final long notHFile = 0x7f7f7f7f7f7f7f7fL;
public BitboardReversi(Player<BitboardReversi>[] players) {
super(8, 8, 2, players);
// Black (player 0)
setPlayerBitboard(0, (1L << (3 + 4 * 8)) | (1L << (4 + 3 * 8)));
// White (player 1)
setPlayerBitboard(1, (1L << (3 + 3 * 8)) | (1L << (4 + 4 * 8)));
}
public BitboardReversi(BitboardReversi other) {
super(other);
}
public long getLegalMoves() {
long legalMoves = 0L;
final long player = getPlayerBitboard(getCurrentPlayerIndex());
final long opponent = getPlayerBitboard(getNextPlayer());
final long empty = ~(player | opponent);
long mask;
long direction;
// north
mask = opponent;
direction = (player << 8) & mask;
direction |= (direction << 8) & mask;
direction |= (direction << 8) & mask;
direction |= (direction << 8) & mask;
direction |= (direction << 8) & mask;
direction |= (direction << 8) & mask;
legalMoves |= (direction << 8) & empty;
// south
mask = opponent;
direction = (player >>> 8) & mask;
direction |= (direction >>> 8) & mask;
direction |= (direction >>> 8) & mask;
direction |= (direction >>> 8) & mask;
direction |= (direction >>> 8) & mask;
direction |= (direction >>> 8) & mask;
legalMoves |= (direction >>> 8) & empty;
// east
mask = opponent & notAFile;
direction = (player << 1) & mask;
direction |= (direction << 1) & mask;
direction |= (direction << 1) & mask;
direction |= (direction << 1) & mask;
direction |= (direction << 1) & mask;
direction |= (direction << 1) & mask;
legalMoves |= (direction << 1) & empty & notAFile;
// west
mask = opponent & notHFile;
direction = (player >>> 1) & mask;
direction |= (direction >>> 1) & mask;
direction |= (direction >>> 1) & mask;
direction |= (direction >>> 1) & mask;
direction |= (direction >>> 1) & mask;
direction |= (direction >>> 1) & mask;
legalMoves |= (direction >>> 1) & empty & notHFile;
// north-east
mask = opponent & notAFile;
direction = (player << 9) & mask;
direction |= (direction << 9) & mask;
direction |= (direction << 9) & mask;
direction |= (direction << 9) & mask;
direction |= (direction << 9) & mask;
direction |= (direction << 9) & mask;
legalMoves |= (direction << 9) & empty & notAFile;
// north-west
mask = opponent & notHFile;
direction = (player << 7) & mask;
direction |= (direction << 7) & mask;
direction |= (direction << 7) & mask;
direction |= (direction << 7) & mask;
direction |= (direction << 7) & mask;
direction |= (direction << 7) & mask;
legalMoves |= (direction << 7) & empty & notHFile;
// south-east
mask = opponent & notAFile;
direction = (player >>> 7) & mask;
direction |= (direction >>> 7) & mask;
direction |= (direction >>> 7) & mask;
direction |= (direction >>> 7) & mask;
direction |= (direction >>> 7) & mask;
direction |= (direction >>> 7) & mask;
legalMoves |= (direction >>> 7) & empty & notAFile;
// south-west
mask = opponent & notHFile;
direction = (player >>> 9) & mask;
direction |= (direction >>> 9) & mask;
direction |= (direction >>> 9) & mask;
direction |= (direction >>> 9) & mask;
direction |= (direction >>> 9) & mask;
direction |= (direction >>> 9) & mask;
legalMoves |= (direction >>> 9) & empty & notHFile;
return legalMoves;
}
public long getFlips(long move) {
long flips = 0L;
final long player = getPlayerBitboard(getCurrentPlayerIndex());
final long opponent = getPlayerBitboard(getNextPlayer());
long mask;
long direction;
// north
mask = opponent;
direction = (move << 8) & mask;
direction |= (direction << 8) & mask;
direction |= (direction << 8) & mask;
direction |= (direction << 8) & mask;
direction |= (direction << 8) & mask;
direction |= (direction << 8) & mask;
if (((direction << 8) & player) != 0) {
flips |= direction;
}
// south
mask = opponent;
direction = (move >>> 8) & mask;
direction |= (direction >>> 8) & mask;
direction |= (direction >>> 8) & mask;
direction |= (direction >>> 8) & mask;
direction |= (direction >>> 8) & mask;
direction |= (direction >>> 8) & mask;
if (((direction >>> 8) & player) != 0) {
flips |= direction;
}
// east
mask = opponent & notAFile;
direction = (move << 1) & mask;
direction |= (direction << 1) & mask;
direction |= (direction << 1) & mask;
direction |= (direction << 1) & mask;
direction |= (direction << 1) & mask;
direction |= (direction << 1) & mask;
if (((direction << 1) & player & notAFile) != 0) {
flips |= direction;
}
// west
mask = opponent & notHFile;
direction = (move >>> 1) & mask;
direction |= (direction >>> 1) & mask;
direction |= (direction >>> 1) & mask;
direction |= (direction >>> 1) & mask;
direction |= (direction >>> 1) & mask;
direction |= (direction >>> 1) & mask;
if (((direction >>> 1) & player & notHFile) != 0) {
flips |= direction;
}
// north-east
mask = opponent & notAFile;
direction = (move << 9) & mask;
direction |= (direction << 9) & mask;
direction |= (direction << 9) & mask;
direction |= (direction << 9) & mask;
direction |= (direction << 9) & mask;
direction |= (direction << 9) & mask;
if (((direction << 9) & player & notAFile) != 0) {
flips |= direction;
}
// north-west
mask = opponent & notHFile;
direction = (move << 7) & mask;
direction |= (direction << 7) & mask;
direction |= (direction << 7) & mask;
direction |= (direction << 7) & mask;
direction |= (direction << 7) & mask;
direction |= (direction << 7) & mask;
if (((direction << 7) & player & notHFile) != 0) {
flips |= direction;
}
// south-east
mask = opponent & notAFile;
direction = (move >>> 7) & mask;
direction |= (direction >>> 7) & mask;
direction |= (direction >>> 7) & mask;
direction |= (direction >>> 7) & mask;
direction |= (direction >>> 7) & mask;
direction |= (direction >>> 7) & mask;
if (((direction >>> 7) & player & notAFile) != 0) {
flips |= direction;
}
// south-west
mask = opponent & notHFile;
direction = (move >>> 9) & mask;
direction |= (direction >>> 9) & mask;
direction |= (direction >>> 9) & mask;
direction |= (direction >>> 9) & mask;
direction |= (direction >>> 9) & mask;
direction |= (direction >>> 9) & mask;
if (((direction >>> 9) & player & notHFile) != 0) {
flips |= direction;
}
return flips;
}
@Override
public BitboardReversi deepCopy() {return new BitboardReversi(this);}
public PlayResult play(long move) {
final long flips = getFlips(move);
long player = getPlayerBitboard(getCurrentPlayerIndex());
long opponent = getPlayerBitboard(getNextPlayer());
player |= move | flips;
opponent &= ~flips;
setPlayerBitboard(getCurrentPlayerIndex(), player);
setPlayerBitboard(getNextPlayer(), opponent);
nextTurn();
final long nextLegalMoves = getLegalMoves();
if (nextLegalMoves == 0) {
nextTurn();
final long skippedLegalMoves = getLegalMoves();
if (skippedLegalMoves == 0) {
int winner = getWinner();
if (winner == -1) {
state = new PlayResult(GameState.DRAW, -1);
return state;
}
state = new PlayResult(GameState.WIN, winner);
return state;
}
state = new PlayResult(GameState.TURN_SKIPPED, getCurrentPlayerIndex());
return state;
}
state = new PlayResult(GameState.NORMAL, getCurrentPlayerIndex());
return state;
}
public Score getScore() {
return new Score(
Long.bitCount(getPlayerBitboard(0)),
Long.bitCount(getPlayerBitboard(1))
);
}
public int getWinner(){
final long black = getPlayerBitboard(0);
final long white = getPlayerBitboard(1);
final int blackCount = Long.bitCount(black);
final int whiteCount = Long.bitCount(white);
if (blackCount == whiteCount){
return -1;
}
else if (blackCount > whiteCount){
return 0;
}
else{
return 1;
}
}
}

View File

@@ -1,107 +0,0 @@
package org.toop.game.games.tictactoe;
import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.model.player.Player;
import org.toop.game.BitboardGame;
public class BitboardTicTacToe extends BitboardGame<BitboardTicTacToe> {
private final long[] winningLines = {
0b111000000L, // top row
0b000111000L, // middle row
0b000000111L, // bottom row
0b100100100L, // left column
0b010010010L, // middle column
0b001001001L, // right column
0b100010001L, // diagonal
0b001010100L // anti-diagonal
};
public BitboardTicTacToe(Player<BitboardTicTacToe>[] players) {
super(3, 3, 2, players);
}
public BitboardTicTacToe(BitboardTicTacToe other) {
super(other);
}
public long getLegalMoves() {
final long xBitboard = getPlayerBitboard(0);
final long oBitboard = getPlayerBitboard(1);
final long taken = (xBitboard | oBitboard);
return (~taken) & 0x1ffL;
}
public int getWinner(){
return getCurrentPlayerIndex();
}
public PlayResult play(long move) {
// Player loses if move is invalid
if ((move & getLegalMoves()) == 0 || Long.bitCount(move) != 1){
state = new PlayResult(GameState.WIN, getNextPlayer());
return state;
}
// Move is legal, make move
long playerBitboard = getPlayerBitboard(getCurrentPlayerIndex());
playerBitboard |= move;
setPlayerBitboard(getCurrentPlayerIndex(), playerBitboard);
// Check if current player won
if (checkWin(playerBitboard)) {
state = new PlayResult(GameState.WIN, getCurrentPlayerIndex());
return state;
}
// Proceed to next turn
nextTurn();
// Check for early draw
if (getLegalMoves() == 0L || checkEarlyDraw()) {
state = new PlayResult(GameState.DRAW, -1);
return state;
}
// Nothing weird happened, continue on as normal
state = new PlayResult(GameState.NORMAL, -1);
return state;
}
private boolean checkWin(long board) {
for (final long line : winningLines) {
if ((board & line) == line) {
return true;
}
}
return false;
}
private boolean checkEarlyDraw() {
final long xBitboard = getPlayerBitboard(0);
final long oBitboard = getPlayerBitboard(1);
final long taken = (xBitboard | oBitboard);
final long empty = (~taken) & 0x1FFL;
for (final long line : winningLines) {
if (((line & xBitboard) != 0 && (line & oBitboard) != 0)) {
continue;
}
if ((line & empty) != 0) {
return false;
}
}
return true;
}
@Override
public BitboardTicTacToe deepCopy() {
return new BitboardTicTacToe(this);
}
}

View File

@@ -1,55 +0,0 @@
package org.toop.game.players;
import org.toop.framework.gameFramework.model.player.*;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
/**
* Represents a player controlled by an AI.
*
* @param <T> the type of turn-based game
*/
public class ArtificialPlayer<T extends TurnBasedGame<T>> extends AbstractPlayer<T> {
private final AI<T> ai;
/**
* Creates a new AI-controlled player.
*
* @param ai the AI controlling this player
* @param name the player's name
*/
public ArtificialPlayer(AI<T> ai, String name) {
super(name);
this.ai = ai;
}
/**
* Creates a copy of another AI-controlled player.
*
* @param other the player to copy
*/
public ArtificialPlayer(ArtificialPlayer<T> other) {
super(other);
this.ai = other.ai.deepCopy();
}
/**
* Determines the player's move using the AI.
*
* @param gameCopy a copy of the current game
* @return the move chosen by the AI
*/
protected long determineMove(T gameCopy) {
return ai.getMove(gameCopy);
}
/**
* Creates a deep copy of this AI player.
*
* @return a copy of this player
*/
@Override
public ArtificialPlayer<T> deepCopy() {
return new ArtificialPlayer<>(this);
}
}

View File

@@ -1,87 +0,0 @@
package org.toop.game.players;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.AbstractPlayer;
import java.util.concurrent.CompletableFuture;
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> {
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) {
super(name);
}
/**
* Creates a copy of another local player.
*
* @param other the player to copy
*/
public LocalPlayer(LocalPlayer<T> 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
protected long determineMove(T gameCopy) {
long legalMoves = gameCopy.getLegalMoves();
long move;
do {
move = getLastMove();
} while ((legalMoves & move) == 0);
return move;
}
/**
* Sets the player's last move.
*
* @param move the move to set
*/
public void setLastMove(long move) {
LastMove.complete(move);
}
/**
* Waits for the next move from the player.
*
* @return the chosen move or 0 if interrupted
*/
private long getLastMove() {
LastMove = new CompletableFuture<>(); // Reset the future
try {
return LastMove.get();
} catch (ExecutionException | InterruptedException e) {
return 0;
}
}
/**
* Creates a deep copy of this local player.
*
* @return a copy of this player
*/
@Override
public LocalPlayer<T> deepCopy() {
return new LocalPlayer<>(this);
}
}

View File

@@ -1,51 +0,0 @@
package org.toop.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;
/**
* Represents a player that participates online.
*
* @param <T> the type of turn-based game
*/
public class OnlinePlayer<T extends TurnBasedGame<T>> extends AbstractPlayer<T> {
/**
* Creates a new online player with the given name.
*
* @param name the name of the player
*/
public OnlinePlayer(String name) {
super(name);
}
/**
* Creates a copy of another online player.
*
* @param other the player to copy
*/
public OnlinePlayer(OnlinePlayer<T> 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
public Player<T> deepCopy() {
return new OnlinePlayer<>(this);
}
}

View File

@@ -7,9 +7,9 @@ import org.toop.framework.gameFramework.model.player.AbstractAI;
import java.util.Random;
public class MCTSAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
public class MCTSAI extends AbstractAI {
private static class Node {
public TurnBasedGame<?> state;
public TurnBasedGame state;
public long move;
public Node parent;
@@ -20,7 +20,7 @@ public class MCTSAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
public int visits;
public float value;
public Node(TurnBasedGame<?> state, long move, Node parent) {
public Node(TurnBasedGame state, long move, Node parent) {
this.state = state;
this.move = move;
@@ -33,7 +33,7 @@ public class MCTSAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
this.value = 0.0f;
}
public Node(TurnBasedGame<?> state) {
public Node(TurnBasedGame state) {
this(state, 0L, null);
}
@@ -71,17 +71,17 @@ public class MCTSAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
this.milliseconds = milliseconds;
}
public MCTSAI(MCTSAI<T> other) {
public MCTSAI(MCTSAI other) {
this.milliseconds = other.milliseconds;
}
@Override
public MCTSAI<T> deepCopy() {
return new MCTSAI<>(this);
public MCTSAI deepCopy() {
return new MCTSAI(this);
}
@Override
public long getMove(T game) {
public long getMove(TurnBasedGame game) {
Node root = new Node(game.deepCopy());
long endTime = System.currentTimeMillis() + milliseconds;
@@ -135,7 +135,7 @@ public class MCTSAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
long move = randomSetBit(legalMoves);
TurnBasedGame<?> copy = node.state.deepCopy();
TurnBasedGame copy = node.state.deepCopy();
copy.play(move);
Node newlyExpanded = new Node(copy, move, node);
@@ -146,8 +146,8 @@ public class MCTSAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
return newlyExpanded;
}
private float simulation(TurnBasedGame<?> state, int playerIndex) {
TurnBasedGame<?> copy = state.deepCopy();
private float simulation(TurnBasedGame state, int playerIndex) {
TurnBasedGame copy = state.deepCopy();
long legalMoves = copy.getLegalMoves();
PlayResult result = null;

View File

@@ -5,9 +5,9 @@ import org.toop.framework.gameFramework.model.player.AbstractAI;
import java.util.Random;
public class MCTSAI2<T extends TurnBasedGame<T>> extends AbstractAI<T> {
public class MCTSAI2 extends AbstractAI {
private static class Node {
public TurnBasedGame<?> state;
public TurnBasedGame state;
public long move;
public long unexpandedMoves;
@@ -20,7 +20,7 @@ public class MCTSAI2<T extends TurnBasedGame<T>> extends AbstractAI<T> {
public float value;
public int visits;
public Node(TurnBasedGame<?> state, Node parent, long move) {
public Node(TurnBasedGame state, Node parent, long move) {
final long legalMoves = state.getLegalMoves();
this.state = state;
@@ -37,7 +37,7 @@ public class MCTSAI2<T extends TurnBasedGame<T>> extends AbstractAI<T> {
this.visits = 0;
}
public Node(TurnBasedGame<?> state) {
public Node(TurnBasedGame state) {
this(state, null, 0L);
}
@@ -78,18 +78,18 @@ public class MCTSAI2<T extends TurnBasedGame<T>> extends AbstractAI<T> {
this.milliseconds = milliseconds;
}
public MCTSAI2(MCTSAI2<?> other) {
public MCTSAI2(MCTSAI2 other) {
this.random = other.random;
this.milliseconds = other.milliseconds;
}
@Override
public MCTSAI2<T> deepCopy() {
return new MCTSAI2<>(this);
public MCTSAI2 deepCopy() {
return new MCTSAI2(this);
}
@Override
public long getMove(T game) {
public long getMove(TurnBasedGame game) {
final Node root = new Node(game, null, 0L);
final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
@@ -135,7 +135,7 @@ public class MCTSAI2<T extends TurnBasedGame<T>> extends AbstractAI<T> {
final long unexpandedMove = leaf.unexpandedMoves & -leaf.unexpandedMoves;
final TurnBasedGame<?> copiedState = leaf.state.deepCopy();
final TurnBasedGame copiedState = leaf.state.deepCopy();
copiedState.play(unexpandedMove);
final Node expandedChild = new Node(copiedState, leaf, unexpandedMove);
@@ -149,7 +149,7 @@ public class MCTSAI2<T extends TurnBasedGame<T>> extends AbstractAI<T> {
}
private float simulation(Node leaf) {
final TurnBasedGame<?> copiedState = leaf.state.deepCopy();
final TurnBasedGame copiedState = leaf.state.deepCopy();
final int playerIndex = 1 - copiedState.getCurrentTurn();
while (!copiedState.isTerminal()) {

View File

@@ -5,9 +5,9 @@ import org.toop.framework.gameFramework.model.player.AbstractAI;
import java.util.Random;
public class MCTSAI3<T extends TurnBasedGame<T>> extends AbstractAI<T> {
public class MCTSAI3 extends AbstractAI {
private static class Node {
public TurnBasedGame<?> state;
public TurnBasedGame state;
public long move;
public long unexpandedMoves;
@@ -20,7 +20,7 @@ public class MCTSAI3<T extends TurnBasedGame<T>> extends AbstractAI<T> {
public float value;
public int visits;
public Node(TurnBasedGame<?> state, Node parent, long move) {
public Node(TurnBasedGame state, Node parent, long move) {
final long legalMoves = state.getLegalMoves();
this.state = state;
@@ -37,7 +37,7 @@ public class MCTSAI3<T extends TurnBasedGame<T>> extends AbstractAI<T> {
this.visits = 0;
}
public Node(TurnBasedGame<?> state) {
public Node(TurnBasedGame state) {
this(state, null, 0L);
}
@@ -82,7 +82,7 @@ public class MCTSAI3<T extends TurnBasedGame<T>> extends AbstractAI<T> {
this.milliseconds = milliseconds;
}
public MCTSAI3(MCTSAI3<?> other) {
public MCTSAI3(MCTSAI3 other) {
this.random = other.random;
this.root = other.root;
@@ -90,12 +90,12 @@ public class MCTSAI3<T extends TurnBasedGame<T>> extends AbstractAI<T> {
}
@Override
public MCTSAI3<T> deepCopy() {
return new MCTSAI3<>(this);
public MCTSAI3 deepCopy() {
return new MCTSAI3(this);
}
@Override
public long getMove(T game) {
public long getMove(TurnBasedGame game) {
detectRoot(game);
final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
@@ -129,7 +129,7 @@ public class MCTSAI3<T extends TurnBasedGame<T>> extends AbstractAI<T> {
return mostVisitedChild;
}
private void detectRoot(T game) {
private void detectRoot(TurnBasedGame game) {
if (root == null) {
root = new Node(game.deepCopy());
return;
@@ -198,7 +198,7 @@ public class MCTSAI3<T extends TurnBasedGame<T>> extends AbstractAI<T> {
final long unexpandedMove = leaf.unexpandedMoves & -leaf.unexpandedMoves;
final TurnBasedGame<?> copiedState = leaf.state.deepCopy();
final TurnBasedGame copiedState = leaf.state.deepCopy();
copiedState.play(unexpandedMove);
final Node expandedChild = new Node(copiedState, leaf, unexpandedMove);
@@ -212,7 +212,7 @@ public class MCTSAI3<T extends TurnBasedGame<T>> extends AbstractAI<T> {
}
private float simulation(Node leaf) {
final TurnBasedGame<?> copiedState = leaf.state.deepCopy();
final TurnBasedGame copiedState = leaf.state.deepCopy();
final int playerIndex = 1 - copiedState.getCurrentTurn();
while (!copiedState.isTerminal()) {

View File

@@ -9,7 +9,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
public class MiniMaxAI extends AbstractAI {
private final int maxDepth;
private final Random random = new Random();
@@ -18,17 +18,17 @@ public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
this.maxDepth = depth;
}
public MiniMaxAI(MiniMaxAI<T> other) {
public MiniMaxAI(MiniMaxAI other) {
this.maxDepth = other.maxDepth;
}
@Override
public MiniMaxAI<T> deepCopy() {
return new MiniMaxAI<>(this);
public MiniMaxAI deepCopy() {
return new MiniMaxAI(this);
}
@Override
public long getMove(T game) {
public long getMove(TurnBasedGame game) {
long legalMoves = game.getLegalMoves();
if (legalMoves == 0) return 0;
@@ -39,7 +39,7 @@ public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
long movesLoop = legalMoves;
while (movesLoop != 0) {
long move = 1L << Long.numberOfTrailingZeros(movesLoop);
T copy = game.deepCopy();
TurnBasedGame copy = game.deepCopy();
PlayResult result = copy.play(move);
int score;
@@ -75,7 +75,7 @@ public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
* @param beta Beta value
* @return score of the position
*/
private int getMoveScore(T game, int depth, boolean maximizing, int aiPlayer, int alpha, int beta) {
private int getMoveScore(TurnBasedGame game, int depth, boolean maximizing, int aiPlayer, int alpha, int beta) {
long legalMoves = game.getLegalMoves();
// Terminal state
@@ -95,7 +95,7 @@ public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
while (movesLoop != 0) {
long move = 1L << Long.numberOfTrailingZeros(movesLoop);
T copy = game.deepCopy();
TurnBasedGame copy = game.deepCopy();
PlayResult result = copy.play(move);
int score;
@@ -126,11 +126,11 @@ public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
* 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
*/
private int evaluateBoard(T game, int aiPlayer) {
private int evaluateBoard(TurnBasedGame game, int aiPlayer) {
long[] board = game.getBoard();
int aiCount = 0;
int opponentCount = 0;

View File

@@ -6,19 +6,19 @@ import org.toop.framework.gameFramework.model.player.AbstractAI;
import java.util.Random;
public class RandomAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
public class RandomAI extends AbstractAI {
public RandomAI() {
super();
}
@Override
public RandomAI<T> deepCopy() {
return new RandomAI<T>();
public RandomAI deepCopy() {
return new RandomAI();
}
@Override
public long getMove(T game) {
public long getMove(TurnBasedGame game) {
long legalMoves = game.getLegalMoves();
int move = new Random().nextInt(Long.bitCount(legalMoves));
return nthBitIndex(legalMoves, move);