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;
|
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);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
@@ -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.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){
|
||||||
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.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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
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;
|
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();
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user