(RANDOM COMMIT) Hope it works

This commit is contained in:
2025-11-28 15:04:12 +01:00
parent 3b6017b369
commit f69df69a36
21 changed files with 524 additions and 88 deletions

View File

@@ -99,8 +99,14 @@
<artifactId>error_prone_annotations</artifactId>
<version>2.42.0</version>
</dependency>
<dependency>
<groupId>org.toop</groupId>
<artifactId>app</artifactId>
<version>0.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</dependencies>
<build>
<plugins>

View File

@@ -0,0 +1,17 @@
package org.toop.game;
import org.toop.game.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,17 @@
/*package org.toop.game;
import org.toop.app.canvas.GameCanvas;
import org.toop.game.TurnBasedGameThread;
import org.toop.app.widget.view.GameView;
public abstract class GameController implements UpdatesGameUI {
// TODO: Seperate this from game Thread
protected final GameView primary = new GameView(null, null, null);
protected final GameCanvas canvas;
protected final TurnBasedGameThread gameThread;
protected GameController(GameCanvas canvas, TurnBasedGameThread gameThread) {
this.gameThread = gameThread;
this.canvas = canvas;
}
}*/

View File

@@ -1,41 +1,109 @@
package org.toop.game;
import org.toop.game.interfaces.IPlayable;
import org.toop.game.interfaces.IPlayableR;
import org.toop.game.records.Move;
import java.util.Arrays;
public abstract class GameR implements IPlayableR {
/**
* 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 {
public static final Integer EMPTY = null; // Constant
/** 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;
private final int columnSize;
private final Integer[] board;
/** 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 Integer[rowSize * columnSize];
board = new int[rowSize * columnSize];
Arrays.fill(board, EMPTY);
}
protected GameR(GameR other) {
rowSize = other.rowSize;
columnSize = other.columnSize;
board = Arrays.copyOf(other.board, other.board.length);
/**
* 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();
}
public int getRowSize() {return this.rowSize;}
/**
* Returns the number of rows in the board.
*
* @return number of rows
*/
public int getRowSize() {
return this.rowSize;
}
public int getColumnSize() {return this.columnSize;}
/**
* Returns the number of columns in the board.
*
* @return number of columns
*/
public int getColumnSize() {
return this.columnSize;
}
public Integer[] getBoard() {return this.board;}
/**
* Returns a copy of the current board state.
*
* @return a cloned array representing the board
*/
public int[] getBoard() {
return this.board.clone();
}
protected void setBoard(int position, int player){this.board[position] = player;}
/**
* 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,24 @@
/*package org.toop.game;
import javafx.geometry.Pos;
import javafx.scene.paint.Color;
import org.toop.app.App;
import org.toop.app.canvas.TicTacToeCanvas;
import org.toop.app.game.Players.LocalPlayer;
import org.toop.game.TurnBasedGameThread;
import org.toop.app.widget.WidgetContainer;
public class TicTacToeController extends GameController {
public TicTacToeController() {
super(new TicTacToeCanvas(Color.GRAY,
(App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,(c) -> {if (players[game.getCurrentTurn()] instanceof LocalPlayer lp) {lp.enqueueMove(c);}}), new TurnBasedGameThread());
primary.add(Pos.CENTER, canvas.getCanvas());
WidgetContainer.getCurrentView().transitionNext(primary));
}
@Override
public void updateUI() {
}
}*/

View File

@@ -9,10 +9,9 @@ public abstract class TurnBasedGameR extends GameR {
this.playerCount = playerCount;
}
protected TurnBasedGameR(TurnBasedGameR other) {
protected TurnBasedGameR(TurnBasedGameR other){
super(other);
playerCount = other.playerCount;
turn = other.turn;
this.playerCount = other.playerCount;
}
public int getPlayerCount(){return this.playerCount;}
@@ -26,6 +25,9 @@ public abstract class TurnBasedGameR extends GameR {
}
protected void setBoard(int position) {
super.setBoard(position, getCurrentTurn());
super.setBoardPosition(position, getCurrentTurn());
}
@Override
public abstract TurnBasedGameR clone();
}

View File

@@ -0,0 +1,105 @@
package org.toop.game;
import javafx.geometry.Pos;
import javafx.scene.paint.Color;
import org.toop.app.App;
import org.toop.app.canvas.GameCanvas;
import org.toop.app.canvas.TicTacToeCanvas;
import org.toop.app.game.Players.LocalPlayer;
import org.toop.app.game.Players.Player;
import org.toop.app.widget.WidgetContainer;
import org.toop.app.widget.view.GameView;
import org.toop.game.enumerators.GameState;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
public class TurnBasedGameThread implements Runnable {
private final Player[] players; // List of players, can't be changed.
private final TurnBasedGameR game; // Reference to game instance
private final AtomicBoolean isRunning = new AtomicBoolean();
//private final GameController controller;
protected final GameView primary = new GameView(null, null, null);
protected final GameCanvas canvas;
public TurnBasedGameThread(Player[] players, TurnBasedGameR game) {
// Set reference to controller
//this.controller = controller;
// Make sure player list matches expected size
if (players.length != game.getPlayerCount()){
throw new IllegalArgumentException("players and game's players must have same length");
}
this.players = players;
this.game = game;
Thread thread = new Thread(this::run);
thread.start();
// UI SHIZ TO MOVE
canvas = new TicTacToeCanvas(Color.GRAY,
(App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,(c) -> {if (players[game.getCurrentTurn()] instanceof LocalPlayer lp) {lp.enqueueMove(c);}});
primary.add(Pos.CENTER, canvas.getCanvas());
WidgetContainer.getCurrentView().transitionNext(primary);
}
public Player[] getPlayers() {
return players;
}
// Move to UI shiz
private void drawMove(int move) {
if (game.getCurrentTurn() == 1){
canvas.drawChar('X', Color.RED, move);
}
else{
canvas.drawChar('O', Color.RED, move);
}
}
public void run() {
isRunning.set(true);
// Game logic loop
while(isRunning.get()) {
// Get current player
Player currentPlayer = players[game.getCurrentTurn()];
System.out.println(game.getCurrentTurn() + "'s turn");
// Get this player's valid moves
int[] validMoves = game.getLegalMoves();
// Get player's move, reask if Move is invalid
// TODO: Limit amount of retries?
int move = currentPlayer.getMove(game.clone());
while (!contains(validMoves, move)) {
move = currentPlayer.getMove(game.clone());
}
// Make move
System.out.println(Arrays.toString(game.getBoard()));
GameState state = game.play(move);
drawMove(move);
if (state != GameState.NORMAL) {
if (state == GameState.WIN) {
// Someone won
} else if (state == GameState.DRAW) {
// THere was a draw
}
System.out.println(state);
isRunning.set(false);
}
}
}
// helper function, would like to replace to get rid of this method
public static boolean contains(int[] array, int value){
for (int i : array) if (i == value) return true;
return false;
}
}

View File

@@ -0,0 +1,5 @@
package org.toop.game;
public interface UpdatesGameUI {
void updateUI();
}

View File

@@ -0,0 +1,26 @@
package org.toop.game.interfaces;
import org.toop.game.GameR;
/**
* Interface defining the behavior of an AI capable of selecting the best move
* in a game represented by {@link GameR}.
*
* @param <T> the specific type of game this AI can play, extending {@link GameR}
*/
public interface IAIMoveR<T extends GameR> {
/**
* Determines the best move for the given game state.
* <p>
* Implementations of this method should analyze the provided game state and
* return the most optimal move for the current player. The analysis can
* consider future moves up to the specified depth.
* </p>
*
* @param game the current game state to analyze
* @param depth the search depth or lookahead for evaluating moves
* @return an integer representing the chosen move
*/
int findBestMove(T game, int depth);
}

View File

@@ -1,9 +1,30 @@
package org.toop.game.interfaces;
import org.toop.game.enumerators.GameState;
import org.toop.game.records.Move;
/**
* Interface representing a playable game with rules for determining legal moves
* and executing moves.
* <p>
* Any game class implementing this interface should provide methods to query
* the current legal moves and to apply a move to the game state, returning
* the resulting game state.
* </p>
*/
public interface IPlayableR {
Integer[] getLegalMoves();
/**
* Returns an array of legal moves that can currently be played in the game.
*
* @return an array of integers representing valid moves; may be empty if no moves are possible
*/
int[] getLegalMoves();
/**
* Applies a move to the game and returns the resulting state.
*
* @param move the move to play, represented as an integer
* @return the {@link GameState} after the move is played
*/
GameState play(int move);
}

View File

@@ -0,0 +1,99 @@
package org.toop.game.tictactoe;
import org.toop.game.AIR;
import org.toop.game.enumerators.GameState;
/**
* AI implementation for playing Tic-Tac-Toe.
* <p>
* This AI uses a recursive minimax-like strategy with a limited depth to
* evaluate moves. It attempts to maximize its chances of winning while
* minimizing the opponent's opportunities. Random moves are used in the
* opening or when no clear best move is found.
* </p>
*/
public final class TicTacToeAIR extends AIR<TicTacToeR> {
/**
* Determines the best move for the given Tic-Tac-Toe game state.
* <p>
* Uses a depth-limited recursive strategy to score each legal move and
* selects the move with the highest score. If no legal moves are available,
* returns -1. If multiple moves are equally good, picks one randomly.
* </p>
*
* @param game the current Tic-Tac-Toe game state
* @param depth the depth of lookahead for evaluating moves (non-negative)
* @return the index of the best move, or -1 if no moves are available
*/
@Override
public int findBestMove(TicTacToeR game, int depth) {
assert game != null;
assert depth >= 0;
final int[] legalMoves = game.getLegalMoves();
if (legalMoves.length == 0) {
return -1;
}
if (legalMoves.length == 9) {
return switch ((int)(Math.random() * 4)) {
case 0 -> legalMoves[2];
case 1 -> legalMoves[6];
case 2 -> legalMoves[8];
default -> legalMoves[0];
};
}
int bestScore = -depth;
int bestMove = -1;
for (final int move : legalMoves) {
final int score = getMoveScore(game, depth, move, true);
if (score > bestScore) {
bestMove = move;
bestScore = score;
}
}
return bestMove != -1 ? bestMove : legalMoves[(int)(Math.random() * legalMoves.length)];
}
/**
* Recursively evaluates the score of a potential move using a minimax-like approach.
*
* @param game the current Tic-Tac-Toe game state
* @param depth remaining depth to evaluate
* @param move the move to evaluate
* @param maximizing true if the AI is to maximize score, false if minimizing
* @return the score of the move
*/
private int getMoveScore(TicTacToeR game, int depth, int move, boolean maximizing) {
final TicTacToeR copy = game.clone();
final GameState state = copy.play(move);
switch (state) {
case DRAW: return 0;
case WIN: return maximizing ? depth + 1 : -depth - 1;
}
if (depth <= 0) {
return 0;
}
final int[] legalMoves = copy.getLegalMoves();
int score = maximizing ? depth + 1 : -depth - 1;
for (final int next : legalMoves) {
if (maximizing) {
score = Math.min(score, getMoveScore(copy, depth - 1, next, false));
} else {
score = Math.max(score, getMoveScore(copy, depth - 1, next, true));
}
}
return score;
}
}

View File

@@ -1,11 +1,13 @@
package org.toop.game.tictactoe;
import org.toop.game.GameR;
import org.toop.game.TurnBasedGame;
import org.toop.game.TurnBasedGameR;
import org.toop.game.enumerators.GameState;
import org.toop.game.records.Move;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
public final class TicTacToeR extends TurnBasedGameR {
@@ -22,7 +24,7 @@ public final class TicTacToeR extends TurnBasedGameR {
}
@Override
public Integer[] getLegalMoves() {
public int[] getLegalMoves() {
final ArrayList<Integer> legalMoves = new ArrayList<Integer>();
final char currentValue = getCurrentValue();
@@ -31,8 +33,8 @@ public final class TicTacToeR extends TurnBasedGameR {
legalMoves.add(i);
}
}
return legalMoves.toArray(new Integer[0]);
System.out.println(Arrays.toString(legalMoves.stream().mapToInt(Integer::intValue).toArray()));
return legalMoves.stream().mapToInt(Integer::intValue).toArray();
}
@Override
@@ -87,7 +89,7 @@ public final class TicTacToeR extends TurnBasedGameR {
private boolean checkForEarlyDraw() {
for (final int move : this.getLegalMoves()) {
final TicTacToeR copy = new TicTacToeR(this);
final TicTacToeR copy = this.clone();
if (copy.play(move) == GameState.WIN || !copy.checkForEarlyDraw()) {
return false;
@@ -100,4 +102,9 @@ public final class TicTacToeR extends TurnBasedGameR {
private char getCurrentValue() {
return this.getCurrentTurn() == 0 ? 'X' : 'O';
}
@Override
public TicTacToeR clone() {
return new TicTacToeR(this);
}
}