(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

@@ -4,35 +4,48 @@ import javafx.scene.paint.Color;
import java.util.function.Consumer; import java.util.function.Consumer;
public final class TicTacToeCanvas extends GameCanvas { public final class TicTacToeCanvas extends GameCanvas implements Drawable {
public TicTacToeCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) { public TicTacToeCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) {
super(color, Color.TRANSPARENT, width, height, 3, 3, 30, false, onCellClicked,null); super(color, Color.TRANSPARENT, width, height, 3, 3, 30, false, onCellClicked,null);
} }
public void drawX(Color color, int cell) { public void drawPlayer(char a, Color color, int cell) {
graphics.setStroke(color); graphics.setStroke(color);
graphics.setLineWidth(gapSize); graphics.setLineWidth(gapSize);
final float x = cells[cell].x() + gapSize; drawChar(a, color, cell);
final float y = cells[cell].y() + gapSize; draw();
}
final float width = cells[cell].width() - gapSize * 2; // public void drawX(Color color, int cell) {
final float height = cells[cell].height() - gapSize * 2; // graphics.setStroke(color);
// graphics.setLineWidth(gapSize);
//
// final float x = cells[cell].x() + gapSize;
// final float y = cells[cell].y() + gapSize;
//
// final float width = cells[cell].width() - gapSize * 2;
// final float height = cells[cell].height() - gapSize * 2;
//
// graphics.strokeLine(x, y, x + width, y + height);
// graphics.strokeLine(x + width, y, x, y + height);
// }
graphics.strokeLine(x, y, x + width, y + height); @Override
graphics.strokeLine(x + width, y, x, y + height); public void draw() {
}
public void drawO(Color color, int cell) { }
graphics.setStroke(color); //
graphics.setLineWidth(gapSize); // public void drawO(Color color, int cell) {
// graphics.setStroke(color);
final float x = cells[cell].x() + gapSize; // graphics.setLineWidth(gapSize);
final float y = cells[cell].y() + gapSize; //
// final float x = cells[cell].x() + gapSize;
final float width = cells[cell].width() - gapSize * 2; // final float y = cells[cell].y() + gapSize;
final float height = cells[cell].height() - gapSize * 2; //
// final float width = cells[cell].width() - gapSize * 2;
graphics.strokeOval(x, y, width, height); // final float height = cells[cell].height() - gapSize * 2;
} //
// graphics.strokeOval(x, y, width, height);
// }
} }

View File

@@ -1,8 +0,0 @@
package org.toop.app.game;
import org.toop.game.records.Move;
public interface MoveBehaviour
{
int getMove();
}

View File

@@ -1,6 +0,0 @@
package org.toop.app.game;
import org.toop.game.records.Move;
public abstract class Player implements MoveBehaviour{
}

View File

@@ -0,0 +1,45 @@
package org.toop.app.game.Players;
import org.toop.game.AIR;
import org.toop.game.GameR;
/**
* Represents a player controlled by an AI in a game.
* <p>
* This player uses an {@link AIR} 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
*/
public class ArtificialPlayer<T extends GameR> extends Player {
/** The AI instance used to calculate moves. */
private final AIR<T> ai;
/**
* Constructs a new ArtificialPlayer using the specified AI.
*
* @param ai the AI instance that determines moves for this player
*/
public ArtificialPlayer(AIR<T> ai) {
this.ai = ai;
}
/**
* Determines the next move for this player using its AI.
* <p>
* This method overrides {@link Player#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
* @return the integer representing the chosen move
* @throws ClassCastException if {@code gameCopy} is not of type {@code T}
*/
@Override
public int getMove(GameR gameCopy) {
return ai.findBestMove((T) gameCopy, 9); // TODO: Make depth configurable
}
}

View File

@@ -1,15 +1,17 @@
package org.toop.app.game; package org.toop.app.game.Players;
import org.toop.game.GameR;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
public class LocalPlayer extends Player{ public class LocalPlayer extends Player {
private BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(); private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
public LocalPlayer() {} public LocalPlayer() {}
@Override @Override
public int getMove() { public int getMove(GameR gameCopy) {
try { try {
return queue.take(); return queue.take();
}catch (InterruptedException e){ }catch (InterruptedException e){

View File

@@ -0,0 +1,23 @@
package org.toop.app.game.Players;
import org.toop.game.GameR;
/**
* Interface representing an entity capable of making a move in a game.
* <p>
* Any class implementing this interface should provide logic to determine
* the next move given a snapshot of the current game state.
* </p>
*/
public interface MakesMove {
/**
* Determines the next move based on the provided game state.
*
* @param gameCopy a copy or snapshot of the current game state
* (never null)
* @return an integer representing the chosen move.
* The interpretation of this value depends on the specific game.
*/
int getMove(GameR gameCopy);
}

View File

@@ -0,0 +1,24 @@
package org.toop.app.game.Players;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Represents a player controlled remotely or over a network.
* <p>
* This class extends {@link Player} and can be used to implement game logic
* 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 extends Player {
/**
* Constructs a new OnlinePlayer.
* <p>
* Currently, no additional initialization is performed. Subclasses or
* future implementations should provide mechanisms to receive moves from
* an external source.
*/
public OnlinePlayer() {}
}

View File

@@ -0,0 +1,35 @@
package org.toop.app.game.Players;
import org.toop.game.GameR;
/**
* Abstract class representing a player in a game.
* <p>
* Players are entities that can make moves based on the current state of a game.
* This class implements {@link MakesMove} and serves as a base for concrete
* 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 Player implements MakesMove {
/**
* Determines the next move based on the provided game state.
* <p>
* The default implementation throws an {@link UnsupportedOperationException},
* indicating that concrete subclasses must override this method to provide
* actual move logic.
* </p>
*
* @param gameCopy a snapshot of the current game state
* @return an integer representing the chosen move
* @throws UnsupportedOperationException if the method is not overridden
*/
@Override
public int getMove(GameR gameCopy) {
throw new UnsupportedOperationException("Not supported yet.");
}
}

View File

@@ -2,12 +2,15 @@ package org.toop.app.widget.view;
import org.toop.app.GameInformation; import org.toop.app.GameInformation;
import org.toop.app.game.*; import org.toop.app.game.*;
import org.toop.app.game.Players.ArtificialPlayer;
import org.toop.app.game.Players.LocalPlayer;
import org.toop.app.game.Players.Player;
import org.toop.app.widget.Primitive; import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.PlayerInfoWidget; 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.game.tictactoe.TicTacToe; import org.toop.game.TurnBasedGameThread;
import org.toop.game.tictactoe.TicTacToeAI; import org.toop.game.tictactoe.TicTacToeAIR;
import org.toop.game.tictactoe.TicTacToeR; import org.toop.game.tictactoe.TicTacToeR;
import org.toop.local.AppContext; import org.toop.local.AppContext;
@@ -33,7 +36,7 @@ public class LocalMultiplayerView extends ViewWidget {
} }
switch (information.type) { switch (information.type) {
case TICTACTOE -> new TurnBasedGameThread(new Player[]{new LocalPlayer(), new LocalPlayer()}, new TicTacToeR()); case TICTACTOE -> new TurnBasedGameThread(new Player[]{new LocalPlayer(), new ArtificialPlayer<>(new TicTacToeAIR())}, new TicTacToeR());
case REVERSI -> new ReversiGame(information); case REVERSI -> new ReversiGame(information);
case CONNECT4 -> new Connect4Game(information); case CONNECT4 -> new Connect4Game(information);
// case BATTLESHIP -> new BattleshipGame(information); // case BATTLESHIP -> new BattleshipGame(information);

View File

@@ -99,8 +99,14 @@
<artifactId>error_prone_annotations</artifactId> <artifactId>error_prone_annotations</artifactId>
<version>2.42.0</version> <version>2.42.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.toop</groupId>
<artifactId>app</artifactId>
<version>0.1</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <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; package org.toop.game;
import org.toop.game.interfaces.IPlayable;
import org.toop.game.interfaces.IPlayableR; import org.toop.game.interfaces.IPlayableR;
import org.toop.game.records.Move;
import java.util.Arrays; 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 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) { protected GameR(int rowSize, int columnSize) {
assert rowSize > 0 && columnSize > 0; assert rowSize > 0 && columnSize > 0;
this.rowSize = rowSize; this.rowSize = rowSize;
this.columnSize = columnSize; this.columnSize = columnSize;
board = new Integer[rowSize * columnSize]; board = new int[rowSize * columnSize];
Arrays.fill(board, EMPTY); Arrays.fill(board, EMPTY);
} }
protected GameR(GameR other) { /**
rowSize = other.rowSize; * Copy constructor for creating a deep copy of another game instance.
columnSize = other.columnSize; *
board = Arrays.copyOf(other.board, other.board.length); * @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; this.playerCount = playerCount;
} }
protected TurnBasedGameR(TurnBasedGameR other) { protected TurnBasedGameR(TurnBasedGameR other){
super(other); super(other);
playerCount = other.playerCount; this.playerCount = other.playerCount;
turn = other.turn;
} }
public int getPlayerCount(){return this.playerCount;} public int getPlayerCount(){return this.playerCount;}
@@ -26,6 +25,9 @@ public abstract class TurnBasedGameR extends GameR {
} }
protected void setBoard(int position) { protected void setBoard(int position) {
super.setBoard(position, getCurrentTurn()); super.setBoardPosition(position, getCurrentTurn());
} }
@Override
public abstract TurnBasedGameR clone();
} }

View File

@@ -1,16 +1,15 @@
package org.toop.app.game; package org.toop.game;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.canvas.GameCanvas; import org.toop.app.canvas.GameCanvas;
import org.toop.app.canvas.TicTacToeCanvas; 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.WidgetContainer;
import org.toop.app.widget.view.GameView; import org.toop.app.widget.view.GameView;
import org.toop.game.TurnBasedGameR;
import org.toop.game.enumerators.GameState; import org.toop.game.enumerators.GameState;
import org.toop.game.records.Move;
import org.toop.game.tictactoe.TicTacToe;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -20,12 +19,15 @@ public class TurnBasedGameThread implements Runnable {
private final TurnBasedGameR game; // Reference to game instance private final TurnBasedGameR game; // Reference to game instance
private final AtomicBoolean isRunning = new AtomicBoolean(); private final AtomicBoolean isRunning = new AtomicBoolean();
//private final GameController controller;
// TODO: Seperate this from game Thread protected final GameView primary = new GameView(null, null, null);
private final GameView primary = new GameView(null, null, null); protected final GameCanvas canvas;
private final TicTacToeCanvas canvas;
public TurnBasedGameThread(Player[] players, TurnBasedGameR game) { public TurnBasedGameThread(Player[] players, TurnBasedGameR game) {
// Set reference to controller
//this.controller = controller;
// Make sure player list matches expected size // Make sure player list matches expected size
if (players.length != game.getPlayerCount()){ if (players.length != game.getPlayerCount()){
throw new IllegalArgumentException("players and game's players must have same length"); throw new IllegalArgumentException("players and game's players must have same length");
@@ -45,10 +47,18 @@ public class TurnBasedGameThread implements Runnable {
} }
public Player[] getPlayers() {
return players;
}
// Move to UI shiz // Move to UI shiz
private void drawMove(int move) { private void drawMove(int move) {
if (game.getCurrentTurn() == 1) canvas.drawX(Color.RED, move); if (game.getCurrentTurn() == 1){
else canvas.drawO(Color.BLUE, move); canvas.drawChar('X', Color.RED, move);
}
else{
canvas.drawChar('O', Color.RED, move);
}
} }
public void run() { public void run() {
@@ -59,19 +69,18 @@ public class TurnBasedGameThread implements Runnable {
// Get current player // Get current player
Player currentPlayer = players[game.getCurrentTurn()]; Player currentPlayer = players[game.getCurrentTurn()];
System.out.println(game.getCurrentTurn() + "'s turn");
// Get this player's valid moves // Get this player's valid moves
Integer[] validMoves = game.getLegalMoves(); int[] validMoves = game.getLegalMoves();
// Get player's move, reask if Move is invalid // Get player's move, reask if Move is invalid
// TODO: Limit amount of retries? // TODO: Limit amount of retries?
int move = currentPlayer.getMove(); int move = currentPlayer.getMove(game.clone());
while (!Arrays.asList(validMoves).contains(move)) { while (!contains(validMoves, move)) {
System.out.println("Invalid move");; move = currentPlayer.getMove(game.clone());
move = currentPlayer.getMove();
} }
// Make move // Make move
System.out.println(Arrays.toString(game.getBoard()));
GameState state = game.play(move); GameState state = game.play(move);
drawMove(move); drawMove(move);
@@ -81,12 +90,16 @@ public class TurnBasedGameThread implements Runnable {
} else if (state == GameState.DRAW) { } else if (state == GameState.DRAW) {
// THere was a draw // THere was a draw
} }
System.out.println(state);
isRunning.set(false); isRunning.set(false);
} }
} }
} }
private void updateUI(){ // 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; package org.toop.game.interfaces;
import org.toop.game.enumerators.GameState; 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 { 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); 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; package org.toop.game.tictactoe;
import org.toop.game.GameR;
import org.toop.game.TurnBasedGame; import org.toop.game.TurnBasedGame;
import org.toop.game.TurnBasedGameR; import org.toop.game.TurnBasedGameR;
import org.toop.game.enumerators.GameState; import org.toop.game.enumerators.GameState;
import org.toop.game.records.Move; import org.toop.game.records.Move;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
public final class TicTacToeR extends TurnBasedGameR { public final class TicTacToeR extends TurnBasedGameR {
@@ -22,7 +24,7 @@ public final class TicTacToeR extends TurnBasedGameR {
} }
@Override @Override
public Integer[] getLegalMoves() { public int[] getLegalMoves() {
final ArrayList<Integer> legalMoves = new ArrayList<Integer>(); final ArrayList<Integer> legalMoves = new ArrayList<Integer>();
final char currentValue = getCurrentValue(); final char currentValue = getCurrentValue();
@@ -31,8 +33,8 @@ public final class TicTacToeR extends TurnBasedGameR {
legalMoves.add(i); legalMoves.add(i);
} }
} }
System.out.println(Arrays.toString(legalMoves.stream().mapToInt(Integer::intValue).toArray()));
return legalMoves.toArray(new Integer[0]); return legalMoves.stream().mapToInt(Integer::intValue).toArray();
} }
@Override @Override
@@ -87,7 +89,7 @@ public final class TicTacToeR extends TurnBasedGameR {
private boolean checkForEarlyDraw() { private boolean checkForEarlyDraw() {
for (final int move : this.getLegalMoves()) { 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()) { if (copy.play(move) == GameState.WIN || !copy.checkForEarlyDraw()) {
return false; return false;
@@ -100,4 +102,9 @@ public final class TicTacToeR extends TurnBasedGameR {
private char getCurrentValue() { private char getCurrentValue() {
return this.getCurrentTurn() == 0 ? 'X' : 'O'; return this.getCurrentTurn() == 0 ? 'X' : 'O';
} }
@Override
public TicTacToeR clone() {
return new TicTacToeR(this);
}
} }