mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 10:54:51 +00:00
(RANDOM COMMIT) Hope it works
This commit is contained in:
@@ -4,35 +4,48 @@ import javafx.scene.paint.Color;
|
||||
|
||||
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) {
|
||||
super(color, Color.TRANSPARENT, width, height, 3, 3, 30, false, onCellClicked,null);
|
||||
}
|
||||
|
||||
public void drawX(Color color, int cell) {
|
||||
graphics.setStroke(color);
|
||||
graphics.setLineWidth(gapSize);
|
||||
public void drawPlayer(char a, Color color, int cell) {
|
||||
graphics.setStroke(color);
|
||||
graphics.setLineWidth(gapSize);
|
||||
|
||||
final float x = cells[cell].x() + gapSize;
|
||||
final float y = cells[cell].y() + gapSize;
|
||||
drawChar(a, color, cell);
|
||||
draw();
|
||||
}
|
||||
|
||||
final float width = cells[cell].width() - gapSize * 2;
|
||||
final float height = cells[cell].height() - gapSize * 2;
|
||||
// public void drawX(Color color, int cell) {
|
||||
// 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);
|
||||
graphics.strokeLine(x + width, y, x, y + height);
|
||||
}
|
||||
@Override
|
||||
public void draw() {
|
||||
|
||||
public void drawO(Color color, int cell) {
|
||||
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.strokeOval(x, y, width, height);
|
||||
}
|
||||
}
|
||||
//
|
||||
// public void drawO(Color color, int cell) {
|
||||
// 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.strokeOval(x, y, width, height);
|
||||
// }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.toop.app.game;
|
||||
|
||||
import org.toop.game.records.Move;
|
||||
|
||||
public interface MoveBehaviour
|
||||
{
|
||||
int getMove();
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package org.toop.app.game;
|
||||
|
||||
import org.toop.game.records.Move;
|
||||
|
||||
public abstract class Player implements MoveBehaviour{
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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.LinkedBlockingQueue;
|
||||
|
||||
public class LocalPlayer extends Player{
|
||||
private BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
|
||||
public class LocalPlayer extends Player {
|
||||
private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
|
||||
|
||||
public LocalPlayer() {}
|
||||
|
||||
@Override
|
||||
public int getMove() {
|
||||
public int getMove(GameR gameCopy) {
|
||||
try {
|
||||
return queue.take();
|
||||
}catch (InterruptedException e){
|
||||
23
app/src/main/java/org/toop/app/game/Players/MakesMove.java
Normal file
23
app/src/main/java/org/toop/app/game/Players/MakesMove.java
Normal 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);
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
35
app/src/main/java/org/toop/app/game/Players/Player.java
Normal file
35
app/src/main/java/org/toop/app/game/Players/Player.java
Normal 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.");
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@ package org.toop.app.widget.view;
|
||||
|
||||
import org.toop.app.GameInformation;
|
||||
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.complex.PlayerInfoWidget;
|
||||
import org.toop.app.widget.complex.ViewWidget;
|
||||
import org.toop.app.widget.popup.ErrorPopup;
|
||||
import org.toop.game.tictactoe.TicTacToe;
|
||||
import org.toop.game.tictactoe.TicTacToeAI;
|
||||
import org.toop.game.TurnBasedGameThread;
|
||||
import org.toop.game.tictactoe.TicTacToeAIR;
|
||||
import org.toop.game.tictactoe.TicTacToeR;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
@@ -33,7 +36,7 @@ public class LocalMultiplayerView extends ViewWidget {
|
||||
}
|
||||
|
||||
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 CONNECT4 -> new Connect4Game(information);
|
||||
// case BATTLESHIP -> new BattleshipGame(information);
|
||||
|
||||
@@ -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>
|
||||
|
||||
17
game/src/main/java/org/toop/game/AIR.java
Normal file
17
game/src/main/java/org/toop/game/AIR.java
Normal 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)
|
||||
}
|
||||
17
game/src/main/java/org/toop/game/GameController.java
Normal file
17
game/src/main/java/org/toop/game/GameController.java
Normal 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;
|
||||
}
|
||||
}*/
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
24
game/src/main/java/org/toop/game/TicTacToeController.java
Normal file
24
game/src/main/java/org/toop/game/TicTacToeController.java
Normal 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() {
|
||||
|
||||
}
|
||||
}*/
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
package org.toop.app.game;
|
||||
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.TurnBasedGameR;
|
||||
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.concurrent.atomic.AtomicBoolean;
|
||||
@@ -20,12 +19,15 @@ public class TurnBasedGameThread implements Runnable {
|
||||
private final TurnBasedGameR game; // Reference to game instance
|
||||
|
||||
private final AtomicBoolean isRunning = new AtomicBoolean();
|
||||
//private final GameController controller;
|
||||
|
||||
// TODO: Seperate this from game Thread
|
||||
private final GameView primary = new GameView(null, null, null);
|
||||
private final TicTacToeCanvas canvas;
|
||||
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");
|
||||
@@ -45,10 +47,18 @@ public class TurnBasedGameThread implements Runnable {
|
||||
|
||||
}
|
||||
|
||||
public Player[] getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
// Move to UI shiz
|
||||
private void drawMove(int move) {
|
||||
if (game.getCurrentTurn() == 1) canvas.drawX(Color.RED, move);
|
||||
else canvas.drawO(Color.BLUE, move);
|
||||
if (game.getCurrentTurn() == 1){
|
||||
canvas.drawChar('X', Color.RED, move);
|
||||
}
|
||||
else{
|
||||
canvas.drawChar('O', Color.RED, move);
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
@@ -59,19 +69,18 @@ public class TurnBasedGameThread implements Runnable {
|
||||
|
||||
// Get current player
|
||||
Player currentPlayer = players[game.getCurrentTurn()];
|
||||
|
||||
System.out.println(game.getCurrentTurn() + "'s turn");
|
||||
// Get this player's valid moves
|
||||
Integer[] validMoves = game.getLegalMoves();
|
||||
int[] validMoves = game.getLegalMoves();
|
||||
|
||||
// Get player's move, reask if Move is invalid
|
||||
// TODO: Limit amount of retries?
|
||||
int move = currentPlayer.getMove();
|
||||
while (!Arrays.asList(validMoves).contains(move)) {
|
||||
System.out.println("Invalid move");;
|
||||
move = currentPlayer.getMove();
|
||||
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);
|
||||
|
||||
@@ -81,12 +90,16 @@ public class TurnBasedGameThread implements Runnable {
|
||||
} else if (state == GameState.DRAW) {
|
||||
// THere was a draw
|
||||
}
|
||||
System.out.println(state);
|
||||
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;
|
||||
}
|
||||
}
|
||||
5
game/src/main/java/org/toop/game/UpdatesGameUI.java
Normal file
5
game/src/main/java/org/toop/game/UpdatesGameUI.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package org.toop.game;
|
||||
|
||||
public interface UpdatesGameUI {
|
||||
void updateUI();
|
||||
}
|
||||
26
game/src/main/java/org/toop/game/interfaces/IAIMoveR.java
Normal file
26
game/src/main/java/org/toop/game/interfaces/IAIMoveR.java
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
99
game/src/main/java/org/toop/game/tictactoe/TicTacToeAIR.java
Normal file
99
game/src/main/java/org/toop/game/tictactoe/TicTacToeAIR.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user