From b3a97a8978fe4fe5634db82be1ba9d4e0fe7d450 Mon Sep 17 00:00:00 2001 From: Stef Date: Fri, 5 Dec 2025 01:05:11 +0100 Subject: [PATCH] Removed old AI and old files. Added a new generic random AI. game no longer deals with translation. --- app/src/main/java/org/toop/app/Server.java | 9 +- .../org/toop/app/canvas/BaseGameCanvas.java | 237 ---------------- .../org/toop/app/canvas/BitGameCanvas.java | 26 ++ .../org/toop/app/canvas/DrawPlayerHover.java | 4 +- .../org/toop/app/canvas/ReversiBitCanvas.java | 16 +- .../org/toop/app/canvas/ReversiCanvas.java | 96 ------- .../toop/app/canvas/TicTacToeBitCanvas.java | 9 +- .../org/toop/app/canvas/TicTacToeCanvas.java | 54 ---- .../AbstractGameController.java | 122 -------- .../GenericGameController.java | 25 +- .../gameControllers/ReversiBitController.java | 1 - .../gameControllers/ReversiController.java | 138 ---------- .../gameControllers/TicTacToeController.java | 63 ----- .../app/widget/view/LocalMultiplayerView.java | 14 +- .../controller/GameController.java | 13 +- .../model/game/AbstractGame.java | 108 -------- .../model/game/BoardProvider.java | 2 +- .../gameFramework/model/game/Playable.java | 4 +- .../model/game/SupportsOnlinePlay.java | 6 +- .../AbstractThreadBehaviour.java | 8 +- .../game/threadBehaviour/ThreadBehaviour.java | 2 + .../model/player/AbstractPlayer.java | 2 +- .../model/player/MoveProvider.java | 2 +- .../main/java/org/toop/game/BitboardGame.java | 34 +-- .../LocalFixedRateThreadBehaviour.java | 2 +- .../gameThreads/LocalThreadBehaviour.java | 4 +- .../gameThreads/OnlineThreadBehaviour.java | 20 +- .../OnlineWithSleepThreadBehaviour.java | 8 +- .../game/games/reversi/BitboardReversi.java | 40 +-- .../toop/game/games/reversi/ReversiAIR.java | 20 -- .../org/toop/game/games/reversi/ReversiR.java | 260 ------------------ .../games/tictactoe/BitboardTicTacToe.java | 23 +- .../game/games/tictactoe/TicTacToeAIR.java | 108 -------- .../toop/game/games/tictactoe/TicTacToeR.java | 118 -------- .../toop/game/players/ArtificialPlayer.java | 2 +- .../org/toop/game/players/LocalPlayer.java | 19 +- .../java/org/toop/game/players/RandomAI.java | 41 +++ 37 files changed, 179 insertions(+), 1481 deletions(-) delete mode 100644 app/src/main/java/org/toop/app/canvas/BaseGameCanvas.java delete mode 100644 app/src/main/java/org/toop/app/canvas/ReversiCanvas.java delete mode 100644 app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java delete mode 100644 app/src/main/java/org/toop/app/gameControllers/AbstractGameController.java delete mode 100644 app/src/main/java/org/toop/app/gameControllers/ReversiController.java delete mode 100644 app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java delete mode 100644 framework/src/main/java/org/toop/framework/gameFramework/model/game/AbstractGame.java delete mode 100644 game/src/main/java/org/toop/game/games/reversi/ReversiAIR.java delete mode 100644 game/src/main/java/org/toop/game/games/reversi/ReversiR.java delete mode 100644 game/src/main/java/org/toop/game/games/tictactoe/TicTacToeAIR.java delete mode 100644 game/src/main/java/org/toop/game/games/tictactoe/TicTacToeR.java create mode 100644 game/src/main/java/org/toop/game/players/RandomAI.java diff --git a/app/src/main/java/org/toop/app/Server.java b/app/src/main/java/org/toop/app/Server.java index a42c224..d6c4552 100644 --- a/app/src/main/java/org/toop/app/Server.java +++ b/app/src/main/java/org/toop/app/Server.java @@ -19,11 +19,8 @@ import org.toop.framework.networking.types.NetworkingConnector; import org.toop.game.games.reversi.BitboardReversi; import org.toop.game.games.tictactoe.BitboardTicTacToe; import org.toop.game.players.ArtificialPlayer; -import org.toop.game.players.LocalPlayer; import org.toop.game.players.OnlinePlayer; -import org.toop.game.games.reversi.ReversiAIR; -import org.toop.game.games.reversi.ReversiR; -import org.toop.game.games.tictactoe.TicTacToeAIR; +import org.toop.game.players.RandomAI; import org.toop.local.AppContext; import java.util.List; @@ -211,13 +208,13 @@ public final class Server { case TICTACTOE ->{ Player[] players = new Player[2]; players[(myTurn + 1) % 2] = new OnlinePlayer<>(response.opponent()); - players[myTurn] = new ArtificialPlayer<>(new TicTacToeAIR(), user); + players[myTurn] = new ArtificialPlayer<>(new RandomAI(), user); gameController = new TicTacToeBitController(players); } case REVERSI -> { Player[] players = new Player[2]; players[(myTurn + 1) % 2] = new OnlinePlayer<>(response.opponent()); - players[myTurn] = new ArtificialPlayer<>(new ReversiAIR(), user); + players[myTurn] = new ArtificialPlayer<>(new RandomAI(), user); gameController = new ReversiBitController(players);} default -> new ErrorPopup("Unsupported game type."); diff --git a/app/src/main/java/org/toop/app/canvas/BaseGameCanvas.java b/app/src/main/java/org/toop/app/canvas/BaseGameCanvas.java deleted file mode 100644 index b645142..0000000 --- a/app/src/main/java/org/toop/app/canvas/BaseGameCanvas.java +++ /dev/null @@ -1,237 +0,0 @@ -package org.toop.app.canvas; - -import javafx.animation.KeyFrame; -import javafx.animation.Timeline; -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; -import javafx.scene.input.MouseButton; -import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import javafx.util.Duration; -import org.toop.framework.gameFramework.model.game.TurnBasedGame; - -import java.util.function.Consumer; -@Deprecated -public abstract class BaseGameCanvas> implements DrawPlayerMove, DrawPlayerHover { - protected record Cell(float x, float y, float width, float height) { - public boolean isInside(double x, double y) { - return x >= this.x && x <= this.x + width && - y >= this.y && y <= this.y + height; - } - } - - protected final Canvas canvas; - protected final GraphicsContext graphics; - - protected final Color color; - protected final Color backgroundColor; - - protected final int width; - protected final int height; - - protected final int rowSize; - protected final int columnSize; - - protected final int gapSize; - protected final boolean edges; - - protected final Cell[] cells; - - private Consumer onCellCLicked; - - public void setOnCellClicked(Consumer onClick) { - this.onCellCLicked = onClick; - } - - protected BaseGameCanvas(Color color, Color backgroundColor, int width, int height, int rowSize, int columnSize, int gapSize, boolean edges, Consumer onCellClicked, Consumer newCellEntered) { - canvas = new Canvas(width, height); - graphics = canvas.getGraphicsContext2D(); - - this.onCellCLicked = onCellClicked; - - this.color = color; - this.backgroundColor = backgroundColor; - - this.width = width; - this.height = height; - - this.rowSize = rowSize; - this.columnSize = columnSize; - - this.gapSize = gapSize; - this.edges = edges; - - cells = new Cell[rowSize * columnSize]; - - final float cellWidth = ((float) width - gapSize * rowSize - gapSize) / rowSize; - final float cellHeight = ((float) height - gapSize * columnSize - gapSize) / columnSize; - - for (int y = 0; y < columnSize; y++) { - final float startY = y * cellHeight + y * gapSize + gapSize; - - for (int x = 0; x < rowSize; x++) { - final float startX = x * cellWidth + x * gapSize + gapSize; - cells[x + y * rowSize] = new Cell(startX, startY, cellWidth, cellHeight); - } - } - - canvas.setOnMouseClicked(event -> { - if (event.getButton() != MouseButton.PRIMARY) { - return; - } - - final int column = (int) ((event.getX() / this.width) * rowSize); - final int row = (int) ((event.getY() / this.height) * columnSize); - - final Cell cell = cells[column + row * rowSize]; - - if (cell.isInside(event.getX(), event.getY())) { - event.consume(); - this.onCellCLicked.accept(column + row * rowSize); - } - }); - - - - - render(); - } - - private void render() { - graphics.setFill(backgroundColor); - graphics.fillRect(0, 0, width, height); - - graphics.setFill(color); - - for (int x = 0; x < rowSize - 1; x++) { - final float start = cells[x].x + cells[x].width; - graphics.fillRect(start, gapSize, gapSize, height - gapSize * 2); - } - - for (int y = 0; y < columnSize - 1; y++) { - final float start = cells[y * rowSize].y + cells[y * rowSize].height; - graphics.fillRect(gapSize, start, width - gapSize * 2, gapSize); - } - - if (edges) { - graphics.fillRect(0, 0, width, gapSize); - graphics.fillRect(0, 0, gapSize, height); - - graphics.fillRect(width - gapSize, 0, gapSize, height); - graphics.fillRect(0, height - gapSize, width, gapSize); - } - } - - public void fill(Color color, int cell) { - final float x = cells[cell].x(); - final float y = cells[cell].y(); - - final float width = cells[cell].width(); - final float height = cells[cell].height(); - - graphics.setFill(color); - graphics.fillRect(x, y, width, height); - } - - public void clear(int cell) { - final float x = cells[cell].x(); - final float y = cells[cell].y(); - - final float width = cells[cell].width(); - final float height = cells[cell].height(); - - graphics.clearRect(x, y, width, height); - - graphics.setFill(backgroundColor); - graphics.fillRect(x, y, width, height); - } - - public void clearAll() { - for (int i = 0; i < cells.length; i++) { - clear(i); - } - } - - @Override - public void drawPlayerMove(int player, int move) { - final float x = cells[move].x() + gapSize; - final float y = cells[move].y() + gapSize; - - final float width = cells[move].width() - gapSize * 2; - final float height = cells[move].height() - gapSize * 2; - - graphics.setFill(color); - graphics.setFont(Font.font("Arial", 40)); // TODO different font and size - graphics.fillText(String.valueOf(player), x + width, y + height); - } - - public void drawDot(Color color, int cell) { - 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.setFill(color); - graphics.fillOval(x, y, width, height); - } - - public void drawInnerDot(Color color, int cell, boolean slightlyBigger) { - final float x = cells[cell].x() + gapSize; - final float y = cells[cell].y() + gapSize; - - float multiplier = slightlyBigger?1.4f:1.5f; - - final float width = (cells[cell].width() - gapSize * 2)/multiplier; - final float height = (cells[cell].height() - gapSize * 2)/multiplier; - - float offset = slightlyBigger?5f:4f; - - graphics.setFill(color); - graphics.fillOval(x + width/offset, y + height/offset, width, height); - } - - private void drawDotScaled(Color color, int cell, double scale) { - final float cx = cells[cell].x() + gapSize; - final float cy = cells[cell].y() + gapSize; - - final float fullWidth = cells[cell].width() - gapSize * 2; - final float height = cells[cell].height() - gapSize * 2; - - final float scaledWidth = (float)(fullWidth * scale); - final float offsetX = (fullWidth - scaledWidth) / 2; - - graphics.setFill(color); - graphics.fillOval(cx + offsetX, cy, scaledWidth, height); - } - - public Timeline flipDot(Color fromColor, Color toColor, int cell) { - final int steps = 60; - final long duration = 250; - final double interval = duration / (double) steps; - - final Timeline timeline = new Timeline(); - - for (int i = 0; i <= steps; i++) { - final double t = i / (double) steps; - final KeyFrame keyFrame = new KeyFrame(Duration.millis(i * interval), - _ -> { - clear(cell); - - final double scale = t <= 0.5 ? 1 - 2 * t : 2 * t - 1; - final Color currentColor = t < 0.5 ? fromColor : toColor; - - drawDotScaled(currentColor, cell, scale); - } - ); - - timeline.getKeyFrames().add(keyFrame); - } - - return timeline; - } - - public Canvas getCanvas() { - return canvas; - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/canvas/BitGameCanvas.java b/app/src/main/java/org/toop/app/canvas/BitGameCanvas.java index 03d29f6..5b62274 100644 --- a/app/src/main/java/org/toop/app/canvas/BitGameCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/BitGameCanvas.java @@ -13,6 +13,7 @@ import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.view.GUIEvents; import org.toop.game.BitboardGame; +import java.util.Arrays; import java.util.function.Consumer; public abstract class BitGameCanvas> implements GameCanvas { @@ -100,6 +101,31 @@ public abstract class BitGameCanvas> implements GameCa render(); } + protected int[] translateLegalMoves(long legalMoves){ + int[] output = new int[Long.bitCount(legalMoves)]; + int j = 0; + for(int i = 0; i < 64; i++){ + if ((legalMoves & (1L << i)) != 0){ + output[j] = i; + j++; + } + } + return output; + } + + protected static int[] translateBoard(long[] playerBitboard){ + int[] output = new int[64]; + Arrays.fill(output, -1); + for(int i = 0; i < playerBitboard.length; i++){ + for (int j = 0; j < 64; j++){ + if ((playerBitboard[i] & (1L << j)) != 0){ + output[j] = i; + } + } + } + return output; + } + private void render() { graphics.setFill(backgroundColor); graphics.fillRect(0, 0, width, height); diff --git a/app/src/main/java/org/toop/app/canvas/DrawPlayerHover.java b/app/src/main/java/org/toop/app/canvas/DrawPlayerHover.java index 0c40a25..abf9ac1 100644 --- a/app/src/main/java/org/toop/app/canvas/DrawPlayerHover.java +++ b/app/src/main/java/org/toop/app/canvas/DrawPlayerHover.java @@ -1,7 +1,7 @@ package org.toop.app.canvas; -import org.toop.framework.gameFramework.model.game.AbstractGame; +import org.toop.framework.gameFramework.model.game.TurnBasedGame; public interface DrawPlayerHover { - void drawPlayerHover(int player, int move, AbstractGame game); + void drawPlayerHover(int player, int move, TurnBasedGame game); } diff --git a/app/src/main/java/org/toop/app/canvas/ReversiBitCanvas.java b/app/src/main/java/org/toop/app/canvas/ReversiBitCanvas.java index 30ea6e9..a4701f6 100644 --- a/app/src/main/java/org/toop/app/canvas/ReversiBitCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/ReversiBitCanvas.java @@ -4,6 +4,7 @@ import javafx.scene.paint.Color; import org.toop.app.App; import org.toop.game.games.reversi.BitboardReversi; +import java.util.Arrays; import java.util.function.Consumer; public class ReversiBitCanvas extends BitGameCanvas{ @@ -31,21 +32,14 @@ public class ReversiBitCanvas extends BitGameCanvas{ return column + row * rowSize; } - public void drawStartingDots() { - drawDot(Color.BLACK, 28); - drawDot(Color.WHITE, 36); - drawDot(Color.BLACK, 35); - drawDot(Color.WHITE, 27); - } - @Override public void redraw(BitboardReversi gameCopy) { clearAll(); - drawStartingDots(); - for (int i = 0; i < gameCopy.getBoard().length; i++) { - if (gameCopy.getBoard()[i] == 0) { + int[] board = translateBoard(gameCopy.getBoard()); + for (int i = 0; i < board.length; i++) { + if (board[i] == 0) { drawDot(Color.WHITE, i); - } else if (gameCopy.getBoard()[i] == 1) { + } else if (board[i] == 1) { drawDot(Color.BLACK, i); } } diff --git a/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java deleted file mode 100644 index 10ad24c..0000000 --- a/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.toop.app.canvas; - -import javafx.scene.paint.Color; -import org.toop.framework.gameFramework.model.game.AbstractGame; -import org.toop.game.Move; -import org.toop.game.games.reversi.ReversiR; - -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -@Deprecated -public final class ReversiCanvas extends BaseGameCanvas { - private Move[] currentlyHighlightedMoves = null; - public ReversiCanvas(Color color, int width, int height, Consumer onCellClicked, Consumer newCellEntered) { - super(color, new Color(0f,0.4f,0.2f,1f), width, height, 8, 8, 5, true, onCellClicked, newCellEntered); - drawStartingDots(); - - final AtomicReference lastHoveredCell = new AtomicReference<>(null); - - canvas.setOnMouseMoved(event -> { - double mouseX = event.getX(); - double mouseY = event.getY(); - int cellId = -1; - - Cell hovered = null; - for (Cell cell : cells) { - if (cell.isInside(mouseX, mouseY)) { - hovered = cell; - cellId = turnCoordsIntoCellId(mouseX, mouseY); - break; - } - } - - Cell previous = lastHoveredCell.get(); - - if (hovered != previous) { - lastHoveredCell.set(hovered); - newCellEntered.accept(cellId); - } - }); - } - - public void setCurrentlyHighlightedMovesNull() { - currentlyHighlightedMoves = null; - } - - public void drawHighlightDots(Move[] moves){ - if (currentlyHighlightedMoves != null){ - for (final Move move : currentlyHighlightedMoves){ - Color color = move.value() == 'W'? Color.BLACK: Color.WHITE; - drawInnerDot(color, move.position(), true); - } - } - currentlyHighlightedMoves = moves; - if (moves != null) { - for (Move move : moves) { - Color color = move.value() == 'B' ? Color.BLACK : Color.WHITE; - drawInnerDot(color, move.position(), false); - } - } - } - - private int turnCoordsIntoCellId(double x, double y) { - final int column = (int) ((x / this.width) * rowSize); - final int row = (int) ((y / this.height) * columnSize); - return column + row * rowSize; - } - - public void drawStartingDots() { - drawDot(Color.BLACK, 28); - drawDot(Color.WHITE, 36); - drawDot(Color.BLACK, 35); - drawDot(Color.WHITE, 27); - } - - public void drawLegalPosition(int cell, char player) { - - Color innerColor; - if (player == 'B') { - innerColor = new Color(0.0f, 0.0f, 0.0f, 0.6f); - } - else { - innerColor = new Color(1.0f, 1.0f, 1.0f, 0.75f); - } - drawInnerDot(innerColor, cell,false); - } - - @Override - public void drawPlayerMove(int player ,int move){ - super.drawPlayerMove(player, move); - } - - @Override - public void drawPlayerHover(int player, int move, AbstractGame game) { - - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/canvas/TicTacToeBitCanvas.java b/app/src/main/java/org/toop/app/canvas/TicTacToeBitCanvas.java index 9c5464c..fec412a 100644 --- a/app/src/main/java/org/toop/app/canvas/TicTacToeBitCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/TicTacToeBitCanvas.java @@ -2,12 +2,9 @@ package org.toop.app.canvas; import javafx.scene.paint.Color; import org.toop.app.App; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.gameFramework.model.game.AbstractGame; -import org.toop.framework.gameFramework.view.GUIEvents; import org.toop.game.games.tictactoe.BitboardTicTacToe; -import java.util.function.Consumer; +import java.util.Arrays; public class TicTacToeBitCanvas extends BitGameCanvas{ public TicTacToeBitCanvas() { @@ -26,14 +23,14 @@ public class TicTacToeBitCanvas extends BitGameCanvas{ @Override public void redraw(BitboardTicTacToe gameCopy) { clearAll(); - drawMoves(gameCopy.getBoard()); + drawMoves(translateBoard(gameCopy.getBoard())); } private void drawMoves(int[] gameBoard){ // Draw each square for (int i = 0; i < 9; i++){ // If square isn't empty, draw player move - if (gameBoard[i] != AbstractGame.EMPTY){ + if (gameBoard[i] != -1){ drawPlayerMove(gameBoard[i], i); } } diff --git a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java deleted file mode 100644 index b84f9b3..0000000 --- a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.toop.app.canvas; - -import javafx.scene.paint.Color; -import org.toop.framework.gameFramework.model.game.AbstractGame; -import org.toop.game.games.tictactoe.BitboardTicTacToe; - -import java.util.function.Consumer; -@Deprecated -public final class TicTacToeCanvas extends BaseGameCanvas { - public TicTacToeCanvas(Color color, int width, int height, Consumer onCellClicked) { - super(color, Color.TRANSPARENT, width, height, 3, 3, 30, false, onCellClicked,null); - } - - @Override - public void drawPlayerMove(int player, int move) { - switch (player) { - case 0 -> drawX(Color.RED, move); - case 1 -> drawO(Color.BLUE, move); - default -> super.drawPlayerMove(player, move); - } - } - - 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); - } - - 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); - } - - @Override - public void drawPlayerHover(int player, int move, AbstractGame game) { - - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/gameControllers/AbstractGameController.java b/app/src/main/java/org/toop/app/gameControllers/AbstractGameController.java deleted file mode 100644 index b12062e..0000000 --- a/app/src/main/java/org/toop/app/gameControllers/AbstractGameController.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.toop.app.gameControllers; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.toop.framework.gameFramework.controller.UpdatesGameUI; -import org.toop.framework.gameFramework.model.game.TurnBasedGame; -import org.toop.framework.gameFramework.view.GUIEvents; -import org.toop.app.canvas.BaseGameCanvas; -import org.toop.framework.networking.events.NetworkEvents; -import org.toop.framework.gameFramework.model.game.threadBehaviour.ThreadBehaviour; -import org.toop.app.widget.view.GameView; -import org.toop.framework.eventbus.EventFlow; -import org.toop.game.gameThreads.OnlineThreadBehaviour; -import org.toop.framework.gameFramework.model.game.SupportsOnlinePlay; -import org.toop.framework.gameFramework.model.player.Player; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -@Deprecated -public abstract class AbstractGameController> implements UpdatesGameUI, ThreadBehaviour { - protected final EventFlow eventFlow = new EventFlow(); - - protected final List> listeners = new ArrayList<>(); - - // Logger for logging ofcourse - protected final Logger logger = LogManager.getLogger(this.getClass()); - - // Reference to gameView view - protected final GameView primary; - - // Reference to game canvas - protected final BaseGameCanvas canvas; - - private final Player[] players; // List of players, can't be changed. - protected final TurnBasedGame game; // Reference to game instance - private final ThreadBehaviour gameThreadBehaviour; - - // TODO: Change gameType to automatically happen with either dependency injection or something else. - // TODO: Make visualisation of moves a behaviour. - protected AbstractGameController(BaseGameCanvas canvas, Player[] players, T game, ThreadBehaviour gameThreadBehaviour, String gameType) { - logger.info("Creating AbstractGameController"); - - this.canvas = canvas; - this.players = players; - this.game = game; - this.gameThreadBehaviour = gameThreadBehaviour; - - primary = new GameView(null, null, null, gameType); - addListeners(); - } - - public void start(){ - logger.info("Starting GameManager"); - gameThreadBehaviour.start();; - } - - public void stop(){ - logger.info("Stopping GameManager"); - removeListeners(); - gameThreadBehaviour.stop(); - } - - public Player getCurrentPlayer(){ - return game.getPlayer(getCurrentPlayerIndex()); - }; - - public int getCurrentPlayerIndex(){ - return game.getCurrentTurn(); - } - - private void addListeners(){ - eventFlow - .listen(GUIEvents.RefreshGameCanvas.class, this::onUpdateGameUI, false) - .listen(GUIEvents.GameEnded.class, this::onGameFinish, false); - } - - private void removeListeners(){ - eventFlow.unsubscribeAll(); - } - - private void onUpdateGameUI(GUIEvents.RefreshGameCanvas event){ - this.updateUI(); - } - - private void onGameFinish(GUIEvents.GameEnded event){ - logger.info("Game Finished"); - String name = event.winner() == -1 ? null : getPlayer(event.winner()).getName(); - primary.gameOver(event.winOrTie(), name); - stop(); - } - - public Player getPlayer(int player){ - if (player < 0 || player >= players.length){ - logger.error("Invalid player index"); - throw new IllegalArgumentException("player out of range"); - } - return players[player]; - } - - private boolean isOnline(){ - return this.gameThreadBehaviour instanceof SupportsOnlinePlay; - } - - public void onYourTurn(NetworkEvents.YourTurnResponse event){ - if (isOnline()){ - ((OnlineThreadBehaviour) this.gameThreadBehaviour).onYourTurn(event); - } - } - - public void onMoveReceived(NetworkEvents.GameMoveResponse event){ - if (isOnline()){ - ((OnlineThreadBehaviour) this.gameThreadBehaviour).onMoveReceived(event); - } - } - - public void gameFinished(NetworkEvents.GameResultResponse event){ - if (isOnline()){ - ((OnlineThreadBehaviour) this.gameThreadBehaviour).gameFinished(event); - } - } -} diff --git a/app/src/main/java/org/toop/app/gameControllers/GenericGameController.java b/app/src/main/java/org/toop/app/gameControllers/GenericGameController.java index fcd7f1d..f502e35 100644 --- a/app/src/main/java/org/toop/app/gameControllers/GenericGameController.java +++ b/app/src/main/java/org/toop/app/gameControllers/GenericGameController.java @@ -8,14 +8,12 @@ import org.toop.app.widget.WidgetContainer; import org.toop.app.widget.view.GameView; import org.toop.framework.eventbus.EventFlow; import org.toop.framework.gameFramework.controller.GameController; -import org.toop.framework.gameFramework.controller.UpdatesGameUI; import org.toop.framework.gameFramework.model.game.SupportsOnlinePlay; import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.threadBehaviour.ThreadBehaviour; import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.gameFramework.view.GUIEvents; import org.toop.framework.networking.events.NetworkEvents; -import org.toop.game.gameThreads.OnlineThreadBehaviour; import org.toop.game.players.LocalPlayer; import java.util.ArrayList; @@ -27,7 +25,7 @@ public class GenericGameController> implements GameCo protected final List> listeners = new ArrayList<>(); - // Logger for logging ofcourse + // Logger for logging protected final Logger logger = LogManager.getLogger(this.getClass()); // Reference to gameView view @@ -46,6 +44,7 @@ public class GenericGameController> implements GameCo this.canvas = canvas; this.game = game; this.gameThreadBehaviour = gameThreadBehaviour; + this.gameThreadBehaviour.setController(this); gameView = new GameView(null, null, null, gameType); @@ -78,7 +77,11 @@ public class GenericGameController> implements GameCo eventFlow .listen(GUIEvents.RefreshGameCanvas.class, this::onUpdateGameUI, false) .listen(GUIEvents.GameEnded.class, this::onGameFinish, false) - .listen(GUIEvents.PlayerAttemptedMove.class, event -> {if (getCurrentPlayer() instanceof LocalPlayer lp){lp.setMove(event.move());}}, false); + .listen(GUIEvents.PlayerAttemptedMove.class, event -> {if (getCurrentPlayer() instanceof LocalPlayer lp){lp.setMove(translateMove(event.move()));}}, false); + } + + protected long translateMove(int move){ + return 1L << move; } private void removeListeners(){ @@ -110,24 +113,32 @@ public class GenericGameController> implements GameCo public void onYourTurn(NetworkEvents.YourTurnResponse event){ if (isOnline()){ - ((OnlineThreadBehaviour) this.gameThreadBehaviour).onYourTurn(event); + ((SupportsOnlinePlay) this.gameThreadBehaviour).onYourTurn(event.clientId()); } } public void onMoveReceived(NetworkEvents.GameMoveResponse event){ if (isOnline()){ - ((OnlineThreadBehaviour) this.gameThreadBehaviour).onMoveReceived(event); + ((SupportsOnlinePlay) this.gameThreadBehaviour).onMoveReceived( + translateMove(Integer.parseInt(event.move()))); } } public void gameFinished(NetworkEvents.GameResultResponse event){ if (isOnline()){ - ((OnlineThreadBehaviour) this.gameThreadBehaviour).gameFinished(event); + ((SupportsOnlinePlay) this.gameThreadBehaviour).gameFinished(event.condition()); } } + @Override + public void sendMove(long clientId, long move) { + new EventFlow().addPostEvent(NetworkEvents.SendMove.class, clientId, (short) Long.numberOfTrailingZeros(move)).asyncPostEvent(); + } + @Override public void updateUI() { + System.out.println("DRAWING GAME"); + // Drawing game canvas.redraw(game.deepCopy()); } } diff --git a/app/src/main/java/org/toop/app/gameControllers/ReversiBitController.java b/app/src/main/java/org/toop/app/gameControllers/ReversiBitController.java index 89fceba..40784b0 100644 --- a/app/src/main/java/org/toop/app/gameControllers/ReversiBitController.java +++ b/app/src/main/java/org/toop/app/gameControllers/ReversiBitController.java @@ -3,7 +3,6 @@ package org.toop.app.gameControllers; import org.toop.app.canvas.ReversiBitCanvas; import org.toop.framework.gameFramework.model.game.threadBehaviour.ThreadBehaviour; import org.toop.framework.gameFramework.model.player.Player; -import org.toop.game.gameThreads.LocalFixedRateThreadBehaviour; import org.toop.game.gameThreads.LocalThreadBehaviour; import org.toop.game.gameThreads.OnlineThreadBehaviour; import org.toop.game.games.reversi.BitboardReversi; diff --git a/app/src/main/java/org/toop/app/gameControllers/ReversiController.java b/app/src/main/java/org/toop/app/gameControllers/ReversiController.java deleted file mode 100644 index efa8323..0000000 --- a/app/src/main/java/org/toop/app/gameControllers/ReversiController.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.toop.app.gameControllers; - -import javafx.animation.SequentialTransition; -import javafx.geometry.Pos; -import javafx.scene.paint.Color; -import org.toop.app.App; -import org.toop.app.canvas.ReversiCanvas; -import org.toop.app.widget.WidgetContainer; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.gameFramework.model.game.AbstractGame; -import org.toop.framework.gameFramework.view.GUIEvents; -import org.toop.game.gameThreads.LocalFixedRateThreadBehaviour; -import org.toop.game.gameThreads.OnlineThreadBehaviour; -import org.toop.game.players.LocalPlayer; -import org.toop.framework.gameFramework.model.player.Player; -import org.toop.game.games.reversi.ReversiR; -@Deprecated -public class ReversiController extends AbstractGameController { - // TODO: Refactor GUI update methods to follow designed system - public ReversiController(Player[] players, boolean local) { - ReversiR ReversiR = new ReversiR(players); - super( - new ReversiCanvas(Color.GRAY, (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,(c) -> {new EventFlow().addPostEvent(GUIEvents.PlayerAttemptedMove.class, c).postEvent();}, (c) -> {new EventFlow().addPostEvent(GUIEvents.PlayerMoveHovered.class, c).postEvent();}), - players, - ReversiR, - local ? new LocalFixedRateThreadBehaviour<>(ReversiR) : new OnlineThreadBehaviour<>(ReversiR), // TODO: Player order matters here, this won't work atm - "Reversi"); - eventFlow.listen(GUIEvents.PlayerAttemptedMove.class, event -> {if (getCurrentPlayer() instanceof LocalPlayer lp){lp.setMove(event.move());}}, false); - eventFlow.listen(GUIEvents.PlayerMoveHovered.class, this::onHoverMove, false); - initUI(); - } - - - private void onHoverMove(GUIEvents.PlayerMoveHovered event){ - int cellEntered = event.move(); - //canvas.drawPlayerHover(-1, cellEntered, game); - /*// (information.players[game.getCurrentTurn()].isHuman) { - int[] legalMoves = game.getLegalMoves(); - boolean isLegalMove = false; - for (int move : legalMoves) { - if (move == cellEntered){ - isLegalMove = true; - break; - } - } - - if (cellEntered >= 0){ - int[] moves = null; - if (isLegalMove) { - moves = game.getFlipsForPotentialMove( - new Point(cellEntered%game.getColumnSize(),cellEntered/game.getRowSize()), - game.getCurrentPlayer()); - } - canvas.drawHighlightDots(moves); - } - //}*/ - } - - public ReversiController(Player[] players) { - this(players, true); - } - - private void updateCanvas(boolean animate) { - // Todo: this is very inefficient. still very fast but if the grid is bigger it might cause issues. improve. - canvas.clearAll(); - - for (int i = 0; i < game.getBoard().length; i++) { - if (game.getBoard()[i] == 0) { - canvas.drawDot(Color.WHITE, i); - } else if (game.getBoard()[i] == 1) { - canvas.drawDot(Color.BLACK, i); - } - } - - //final int[] flipped = game.getMostRecentlyFlippedPieces(); - - final SequentialTransition animation = new SequentialTransition(); - - final Color fromColor = getCurrentPlayerIndex() == 0? Color.WHITE : Color.BLACK; - final Color toColor = getCurrentPlayerIndex() == 0? Color.BLACK : Color.WHITE; - - /*if (animate && flipped != null) { - for (final int flip : flipped) { - canvas.clear(flip); - canvas.drawDot(fromColor, flip); - animation.getChildren().addFirst(canvas.flipDot(fromColor, toColor, flip)); - } - }*/ - - animation.setOnFinished(_ -> { - - if (getCurrentPlayer() instanceof LocalPlayer) { - final int[] legalMoves = game.getLegalMoves(); - - for (final int legalMove : legalMoves) { - drawLegalPosition(legalMove, getCurrentPlayerIndex()); - } - } - }); - - animation.play(); - primary.nextPlayer(true, getCurrentPlayer().getName(), game.getCurrentTurn() == 0 ? "X" : "O", getPlayer((game.getCurrentTurn() + 1) % 2).getName()); - } - - @Override - public void updateUI() { - updateCanvas(false); - } - - public void drawLegalPosition(int cell, int player) { - Color innerColor; - if (player == 1) { - innerColor = new Color(0.0f, 0.0f, 0.0f, 0.6f); - } - else { - innerColor = new Color(1.0f, 1.0f, 1.0f, 0.75f); - } - canvas.drawInnerDot(innerColor, cell,false); - } - - private void initUI(){ - primary.add(Pos.CENTER, canvas.getCanvas()); - WidgetContainer.getCurrentView().transitionNext(primary, true); - updateCanvas(false); - } - - private void drawMoves(){ - int[] board = game.getBoard(); - - // Draw each square - for (int i = 0; i < board.length; i++){ - // If square isn't empty, draw player move - if (board[i] != AbstractGame.EMPTY){ - canvas.drawPlayerMove(board[i], i); - } - } - } -} diff --git a/app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java b/app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java deleted file mode 100644 index cd7c5fe..0000000 --- a/app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.toop.app.gameControllers; - -import javafx.geometry.Pos; -import javafx.scene.paint.Color; -import org.toop.app.App; -import org.toop.app.canvas.TicTacToeCanvas; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.gameFramework.model.player.Player; -import org.toop.framework.gameFramework.view.GUIEvents; -import org.toop.framework.gameFramework.model.game.AbstractGame; -import org.toop.game.gameThreads.LocalThreadBehaviour; -import org.toop.game.gameThreads.OnlineThreadBehaviour; -import org.toop.game.players.LocalPlayer; -import org.toop.app.widget.WidgetContainer; -import org.toop.game.games.tictactoe.BitboardTicTacToe; -@Deprecated -public class TicTacToeController extends AbstractGameController { - - public TicTacToeController(Player[] players, boolean local) { - BitboardTicTacToe BitboardTicTacToe = new BitboardTicTacToe(players); - super( - new TicTacToeCanvas(Color.GRAY, (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,(c) -> {new EventFlow().addPostEvent(GUIEvents.PlayerAttemptedMove.class, c).postEvent();}), - players, - BitboardTicTacToe, - local ? new LocalThreadBehaviour(BitboardTicTacToe) : new OnlineThreadBehaviour<>(BitboardTicTacToe), // TODO: Player order matters here, this won't work atm - "TicTacToe"); - - initUI(); - eventFlow.listen(GUIEvents.PlayerAttemptedMove.class, event -> {if (getCurrentPlayer() instanceof LocalPlayer lp){lp.setMove(event.move());}}, false); - //addListener(GlobalEventBus.subscribe(GUIEvents.PlayerAttemptedMove.class, event -> {if (getCurrentPlayer() instanceof LocalPlayer lp){lp.setMove(event.move());}})); - //new EventFlow().listen(GUIEvents.PlayerAttemptedMove.class, event -> {if (getCurrentPlayer() instanceof LocalPlayer lp){lp.setMove(event.move());}}); - } - - public TicTacToeController(Player[] players) { - this(players, true); - } - - @Override - public void updateUI() { - canvas.clearAll(); - // TODO: wtf is even this pile of poop temp fix - primary.nextPlayer(true, getCurrentPlayer().getName(), game.getCurrentTurn() == 0 ? "X" : "O", getPlayer((game.getCurrentTurn() + 1) % 2).getName()); - drawMoves(); - } - - private void initUI(){ - primary.add(Pos.CENTER, canvas.getCanvas()); - WidgetContainer.getCurrentView().transitionNext(primary, true); - updateUI(); - } - - private void drawMoves(){ - int[] board = game.getBoard(); - - // Draw each square - for (int i = 0; i < 9; i++){ - // If square isn't empty, draw player move - if (board[i] != AbstractGame.EMPTY){ - canvas.drawPlayerMove(board[i], i); - } - } - } -} diff --git a/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java b/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java index e192705..da3be76 100644 --- a/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java +++ b/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java @@ -9,7 +9,6 @@ import org.toop.app.gameControllers.ReversiBitController; import org.toop.app.gameControllers.TicTacToeBitController; import org.toop.framework.gameFramework.controller.GameController; import org.toop.framework.gameFramework.model.player.Player; -import org.toop.game.gameThreads.LocalThreadBehaviour; import org.toop.game.games.reversi.BitboardReversi; import org.toop.game.games.tictactoe.BitboardTicTacToe; import org.toop.game.players.ArtificialPlayer; @@ -18,9 +17,8 @@ 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.games.reversi.ReversiAIR; -import org.toop.game.games.tictactoe.TicTacToeAIR; import org.toop.app.widget.tutorial.*; +import org.toop.game.players.RandomAI; import org.toop.local.AppContext; import javafx.geometry.Pos; @@ -52,7 +50,7 @@ public class LocalMultiplayerView extends ViewWidget { } } - // TODO: Fix this temporary ass way of setting the players (Only works for TicTacToe) + // TODO: Fix this temporary ass way of setting the players Player[] players = new Player[2]; switch (information.type) { @@ -60,12 +58,12 @@ public class LocalMultiplayerView extends ViewWidget { if (information.players[0].isHuman) { players[0] = new LocalPlayer<>(information.players[0].name); } else { - players[0] = new ArtificialPlayer<>(new TicTacToeAIR(), information.players[0].name); + players[0] = new ArtificialPlayer<>(new RandomAI(), information.players[0].name); } if (information.players[1].isHuman) { players[1] = new LocalPlayer<>(information.players[1].name); } else { - players[1] = new ArtificialPlayer<>(new TicTacToeAIR(), information.players[1].name); + players[1] = new ArtificialPlayer<>(new RandomAI(), information.players[1].name); } if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstTTT()) { new ShowEnableTutorialWidget( @@ -88,12 +86,12 @@ public class LocalMultiplayerView extends ViewWidget { if (information.players[0].isHuman) { players[0] = new LocalPlayer<>(information.players[0].name); } else { - players[0] = new ArtificialPlayer<>(new ReversiAIR(), information.players[0].name); + players[0] = new ArtificialPlayer<>(new RandomAI(), information.players[0].name); } if (information.players[1].isHuman) { players[1] = new LocalPlayer<>(information.players[1].name); } else { - players[1] = new ArtificialPlayer<>(new ReversiAIR(), information.players[1].name); + players[1] = new ArtificialPlayer<>(new RandomAI(), information.players[1].name); } if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstReversi()) { new ShowEnableTutorialWidget( diff --git a/framework/src/main/java/org/toop/framework/gameFramework/controller/GameController.java b/framework/src/main/java/org/toop/framework/gameFramework/controller/GameController.java index 6a91e2d..f355dc5 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/controller/GameController.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/controller/GameController.java @@ -2,6 +2,17 @@ package org.toop.framework.gameFramework.controller; import org.toop.framework.gameFramework.model.game.SupportsOnlinePlay; import org.toop.framework.gameFramework.model.game.threadBehaviour.Controllable; +import org.toop.framework.networking.events.NetworkEvents; -public interface GameController extends Controllable, SupportsOnlinePlay, UpdatesGameUI { +public interface GameController extends Controllable, UpdatesGameUI { + /** Called when it is this player's turn to make a move. */ + void onYourTurn(NetworkEvents.YourTurnResponse event); + + /** Called when a move from another player is received. */ + void onMoveReceived(NetworkEvents.GameMoveResponse event); + + /** Called when the game has finished, with the final result. */ + void gameFinished(NetworkEvents.GameResultResponse event); + + void sendMove(long clientId, long move); } diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/AbstractGame.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/AbstractGame.java deleted file mode 100644 index ec0dae2..0000000 --- a/framework/src/main/java/org/toop/framework/gameFramework/model/game/AbstractGame.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.toop.framework.gameFramework.model.game; - -import org.toop.framework.gameFramework.model.player.Player; - -import java.util.Arrays; - -public abstract class AbstractGame> implements TurnBasedGame { - private final int playerCount; // How many players are playing - private final Player[] players; - private int turn = 0; // What turn it is in the game - - /** 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; - - /** Number of columns in the game board. */ - private final int columnSize; - - /** The game board stored as a one-dimensional array. */ - private final int[] board; - - - - protected AbstractGame(int rowSize, int columnSize, int playerCount, Player[] players) { - assert rowSize > 0 && columnSize > 0; - - this.rowSize = rowSize; - this.columnSize = columnSize; - - this.players = players; - - board = new int[rowSize * columnSize]; - Arrays.fill(board, EMPTY); - - this.playerCount = playerCount; - } - - protected AbstractGame(AbstractGame other){ - this.rowSize = other.rowSize; - this.columnSize = other.columnSize; - this.board = other.board.clone(); - this.playerCount = other.playerCount; - this.turn = other.turn; - // TODO: Make this a deep copy, add deep copy interface to Player - this.players = other.players; - - } - - public static boolean contains(int[] array, int value) { - // O(n) - for (int element : array){ - if (element == value) return true; - } - return false; - } - - public Player getPlayer(int index) { - return players[index]; - } - - public int getPlayerCount(){return this.playerCount;} - - protected void nextTurn() { - turn += 1; - } - - public int getCurrentTurn() { - return turn % playerCount; - } - - protected void setBoard(int position) { - setBoard(position, getCurrentTurn()); - } - - protected void setBoard(int position, int player) { - this.board[position] = player; - } - - /** - * Returns the number of rows in the board. - * - * @return number of rows - */ - public int getRowSize() { - return this.rowSize; - } - - /** - * Returns the number of columns in the board. - * - * @return number of columns - */ - public int getColumnSize() { - return this.columnSize; - } - - /** - * Returns a copy of the current board state. - * - * @return a cloned array representing the board - */ - public int[] getBoard() { - return this.board.clone(); - } - -} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/BoardProvider.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/BoardProvider.java index 06ab870..fb70cf8 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/model/game/BoardProvider.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/BoardProvider.java @@ -1,5 +1,5 @@ package org.toop.framework.gameFramework.model.game; public interface BoardProvider { - int[] getBoard(); + long[] getBoard(); } diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/Playable.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/Playable.java index f8013c5..21956b0 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/model/game/Playable.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/Playable.java @@ -12,7 +12,7 @@ public interface Playable { * * @return an array of integers representing legal moves */ - int[] getLegalMoves(); + long getLegalMoves(); /** * Plays the given move and returns the resulting game state. @@ -20,5 +20,5 @@ public interface Playable { * @param move the move to apply * @return the {@link GameState} and additional info after the move */ - PlayResult play(int move); + PlayResult play(long move); } diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/SupportsOnlinePlay.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/SupportsOnlinePlay.java index d610296..1cc1641 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/model/game/SupportsOnlinePlay.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/SupportsOnlinePlay.java @@ -10,11 +10,11 @@ import org.toop.framework.networking.events.NetworkEvents; public interface SupportsOnlinePlay { /** Called when it is this player's turn to make a move. */ - void onYourTurn(NetworkEvents.YourTurnResponse event); + void onYourTurn(long clientId); /** Called when a move from another player is received. */ - void onMoveReceived(NetworkEvents.GameMoveResponse event); + void onMoveReceived(long move); /** Called when the game has finished, with the final result. */ - void gameFinished(NetworkEvents.GameResultResponse event); + void gameFinished(String condition); } diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/AbstractThreadBehaviour.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/AbstractThreadBehaviour.java index e059292..595ccd7 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/AbstractThreadBehaviour.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/AbstractThreadBehaviour.java @@ -2,6 +2,7 @@ package org.toop.framework.gameFramework.model.game.threadBehaviour; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.toop.framework.gameFramework.controller.GameController; import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.player.Player; @@ -15,7 +16,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * Subclasses implement the actual game-loop logic. */ public abstract class AbstractThreadBehaviour> implements ThreadBehaviour { - + protected GameController controller; /** Indicates whether the game loop or event processing is active. */ protected final AtomicBoolean isRunning = new AtomicBoolean(); @@ -33,4 +34,9 @@ public abstract class AbstractThreadBehaviour> implem public AbstractThreadBehaviour(T game) { this.game = game; } + + @Override + public void setController(GameController controller) { + this.controller = controller; + } } diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/ThreadBehaviour.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/ThreadBehaviour.java index 42b667d..d0e832e 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/ThreadBehaviour.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/ThreadBehaviour.java @@ -1,5 +1,6 @@ package org.toop.framework.gameFramework.model.game.threadBehaviour; +import org.toop.framework.gameFramework.controller.GameController; import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.player.AbstractPlayer; import org.toop.framework.gameFramework.model.player.Player; @@ -10,4 +11,5 @@ import org.toop.framework.gameFramework.model.player.Player; * Defines how a game's execution is started, stopped, and which player is active. */ public interface ThreadBehaviour extends Controllable { + void setController(GameController controller); } diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractPlayer.java b/framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractPlayer.java index bca7a01..57e2f18 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractPlayer.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractPlayer.java @@ -39,7 +39,7 @@ public abstract class AbstractPlayer> implements Play * @return an integer representing the chosen move * @throws UnsupportedOperationException if the method is not overridden */ - public int getMove(T gameCopy) { + public long getMove(T gameCopy) { logger.error("Method getMove not implemented."); throw new UnsupportedOperationException("Not supported yet."); } diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/player/MoveProvider.java b/framework/src/main/java/org/toop/framework/gameFramework/model/player/MoveProvider.java index 5a8555e..b9ab448 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/model/player/MoveProvider.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/player/MoveProvider.java @@ -3,5 +3,5 @@ package org.toop.framework.gameFramework.model.player; import org.toop.framework.gameFramework.model.game.TurnBasedGame; public interface MoveProvider> { - int getMove(T game); + long getMove(T game); } diff --git a/game/src/main/java/org/toop/game/BitboardGame.java b/game/src/main/java/org/toop/game/BitboardGame.java index 395e076..c6292fb 100644 --- a/game/src/main/java/org/toop/game/BitboardGame.java +++ b/game/src/main/java/org/toop/game/BitboardGame.java @@ -26,40 +26,11 @@ public abstract class BitboardGame> implements TurnBas Arrays.fill(playerBitboard, 0L); } - protected int[] translateLegalMoves(long legalMoves){ - int[] output = new int[Long.bitCount(legalMoves)]; - int j = 0; - for(int i = 0; i < 64; i++){ - if ((legalMoves & (1L << i)) != 0){ - output[j] = i; - j++; - } - } - return output; - } - - protected long translateMove(int move){ - return 1L << move; - } - - protected int[] translateBoard(){ - int[] output = new int[64]; - Arrays.fill(output, -1); - for(int i = 0; i < this.playerBitboard.length; i++){ - for (int j = 0; j < 64; j++){ - if ((this.playerBitboard[i] & (1L << j)) != 0){ - output[j] = i; - } - } - } - return output; - } - public BitboardGame(BitboardGame other) { this.columnSize = other.columnSize; this.rowSize = other.rowSize; - this.playerBitboard = Arrays.copyOf(other.playerBitboard, other.playerBitboard.length); + this.playerBitboard = other.playerBitboard.clone(); this.currentTurn = other.currentTurn; this.players = Arrays.stream(other.players) .map(Player::deepCopy) @@ -104,6 +75,9 @@ public abstract class BitboardGame> implements TurnBas return players[getCurrentPlayerIndex()]; } + @Override + public long[] getBoard() {return this.playerBitboard;} + public void nextTurn() { currentTurn++; } diff --git a/game/src/main/java/org/toop/game/gameThreads/LocalFixedRateThreadBehaviour.java b/game/src/main/java/org/toop/game/gameThreads/LocalFixedRateThreadBehaviour.java index b7a2002..9d55565 100644 --- a/game/src/main/java/org/toop/game/gameThreads/LocalFixedRateThreadBehaviour.java +++ b/game/src/main/java/org/toop/game/gameThreads/LocalFixedRateThreadBehaviour.java @@ -58,7 +58,7 @@ public class LocalFixedRateThreadBehaviour> extends A nextUpdate += UPDATE_INTERVAL; Player currentPlayer = game.getPlayer(game.getCurrentTurn()); - int move = currentPlayer.getMove(game.deepCopy()); + long move = currentPlayer.getMove(game.deepCopy()); PlayResult result = game.play(move); new EventFlow().addPostEvent(GUIEvents.RefreshGameCanvas.class).postEvent(); diff --git a/game/src/main/java/org/toop/game/gameThreads/LocalThreadBehaviour.java b/game/src/main/java/org/toop/game/gameThreads/LocalThreadBehaviour.java index 5a5c54d..c81ce4d 100644 --- a/game/src/main/java/org/toop/game/gameThreads/LocalThreadBehaviour.java +++ b/game/src/main/java/org/toop/game/gameThreads/LocalThreadBehaviour.java @@ -47,9 +47,9 @@ public class LocalThreadBehaviour> extends AbstractTh public void run() { while (isRunning.get()) { Player currentPlayer = game.getPlayer(game.getCurrentTurn()); - int move = currentPlayer.getMove(game.deepCopy()); + long move = currentPlayer.getMove(game.deepCopy()); PlayResult result = game.play(move); - new EventFlow().addPostEvent(GUIEvents.RefreshGameCanvas.class).postEvent(); + controller.updateUI(); GameState state = result.state(); switch (state) { diff --git a/game/src/main/java/org/toop/game/gameThreads/OnlineThreadBehaviour.java b/game/src/main/java/org/toop/game/gameThreads/OnlineThreadBehaviour.java index 6fa886a..385fd4f 100644 --- a/game/src/main/java/org/toop/game/gameThreads/OnlineThreadBehaviour.java +++ b/game/src/main/java/org/toop/game/gameThreads/OnlineThreadBehaviour.java @@ -3,9 +3,7 @@ package org.toop.game.gameThreads; import org.toop.framework.eventbus.EventFlow; import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour; import org.toop.framework.gameFramework.view.GUIEvents; -import org.toop.framework.gameFramework.model.game.AbstractGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame; -import org.toop.framework.networking.events.NetworkEvents; import org.toop.framework.gameFramework.model.game.SupportsOnlinePlay; import org.toop.framework.gameFramework.model.player.Player; import org.toop.game.players.OnlinePlayer; @@ -52,20 +50,19 @@ public class OnlineThreadBehaviour> extends AbstractT * Sends the generated move back to the server. */ @Override - public void onYourTurn(NetworkEvents.YourTurnResponse event) { + public void onYourTurn(long clientId) { if (!isRunning.get()) return; - int move = game.getPlayer(game.getCurrentTurn()).getMove(game.deepCopy()); - new EventFlow().addPostEvent(NetworkEvents.SendMove.class, event.clientId(), (short) move).postEvent(); + long move = game.getPlayer(game.getCurrentTurn()).getMove(game.deepCopy()); + controller.sendMove(clientId, move); } /** * Handles a move received from the server for any player. * Updates the game state and triggers a UI refresh. */ - @Override - public void onMoveReceived(NetworkEvents.GameMoveResponse event) { + public void onMoveReceived(long move) { if (!isRunning.get()) return; - game.play(Integer.parseInt(event.move())); + game.play(move); new EventFlow().addPostEvent(GUIEvents.RefreshGameCanvas.class).postEvent(); } @@ -73,11 +70,10 @@ public class OnlineThreadBehaviour> extends AbstractT * Handles the end of the game as notified by the server. * Updates the UI to show a win or draw result for the local player. */ - @Override - public void gameFinished(NetworkEvents.GameResultResponse event) { - switch(event.condition().toUpperCase()){ + public void gameFinished(String condition) { + switch(condition.toUpperCase()){ case "WIN" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, true, game.getCurrentTurn()).postEvent(); - case "DRAW" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, false, AbstractGame.EMPTY).postEvent(); + case "DRAW" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, false, -1).postEvent(); case "LOSS" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, true, (game.getCurrentTurn() + 1)%2).postEvent(); default -> { logger.error("Invalid condition"); diff --git a/game/src/main/java/org/toop/game/gameThreads/OnlineWithSleepThreadBehaviour.java b/game/src/main/java/org/toop/game/gameThreads/OnlineWithSleepThreadBehaviour.java index f9cc672..a666f8d 100644 --- a/game/src/main/java/org/toop/game/gameThreads/OnlineWithSleepThreadBehaviour.java +++ b/game/src/main/java/org/toop/game/gameThreads/OnlineWithSleepThreadBehaviour.java @@ -1,9 +1,7 @@ package org.toop.game.gameThreads; -import org.toop.framework.gameFramework.model.game.AbstractGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.networking.events.NetworkEvents; -import org.toop.framework.gameFramework.model.player.AbstractPlayer; /** * Online thread behaviour that adds a fixed delay before processing @@ -17,7 +15,7 @@ public class OnlineWithSleepThreadBehaviour> extends /** * Creates the behaviour and forwards the players to the base class. * - * @param game the online-capable turn-based game + * @param game the online-capable turn-based game */ public OnlineWithSleepThreadBehaviour(T game) { super(game); @@ -29,7 +27,7 @@ public class OnlineWithSleepThreadBehaviour> extends * @param event the network event indicating it's this client's turn */ @Override - public void onYourTurn(NetworkEvents.YourTurnResponse event) { + public void onYourTurn(long clientId) { try { Thread.sleep(50); @@ -37,6 +35,6 @@ public class OnlineWithSleepThreadBehaviour> extends e.printStackTrace(); } - super.onYourTurn(event); + super.onYourTurn(clientId); } } diff --git a/game/src/main/java/org/toop/game/games/reversi/BitboardReversi.java b/game/src/main/java/org/toop/game/games/reversi/BitboardReversi.java index cb9f36e..63a4eaa 100644 --- a/game/src/main/java/org/toop/game/games/reversi/BitboardReversi.java +++ b/game/src/main/java/org/toop/game/games/reversi/BitboardReversi.java @@ -5,15 +5,8 @@ import org.toop.framework.gameFramework.model.game.PlayResult; import org.toop.framework.gameFramework.model.player.Player; import org.toop.game.BitboardGame; -import java.util.Arrays; - public class BitboardReversi extends BitboardGame { - @Override - public int[] getBoard() { - return translateBoard(); - } - public record Score(int black, int white) {} private final long notAFile = 0xfefefefefefefefeL; @@ -29,7 +22,11 @@ public class BitboardReversi extends BitboardGame { setPlayerBitboard(1, (1L << (3 + 3 * 8)) | (1L << (4 + 4 * 8))); } - public long getLegalMoves2() { + public BitboardReversi(BitboardReversi other) { + super(other); + } + + public long getLegalMoves() { final long player = getPlayerBitboard(getCurrentPlayerIndex()); final long opponent = getPlayerBitboard(getNextPlayer()); @@ -76,20 +73,9 @@ public class BitboardReversi extends BitboardGame { } @Override - public int[] getLegalMoves(){ - return translateLegalMoves(getLegalMoves2()); - } + public BitboardReversi deepCopy() {return new BitboardReversi(this);} - @Override - public PlayResult play(int move) { - return new PlayResult(playBit(translateMove(move)), getCurrentPlayerIndex()); - } - - // TODO: Implement - @Override - public BitboardReversi deepCopy() {return this;}; - - public GameState playBit(long move) { + public PlayResult play(long move) { final long flips = getFlips(move); long player = getPlayerBitboard(getCurrentPlayerIndex()); @@ -103,12 +89,12 @@ public class BitboardReversi extends BitboardGame { nextTurn(); - final long nextLegalMoves = getLegalMoves2(); + final long nextLegalMoves = getLegalMoves(); if (nextLegalMoves == 0) { nextTurn(); - final long skippedLegalMoves = getLegalMoves2(); + final long skippedLegalMoves = getLegalMoves(); if (skippedLegalMoves == 0) { final long black = getPlayerBitboard(0); @@ -118,16 +104,16 @@ public class BitboardReversi extends BitboardGame { final int whiteCount = Long.bitCount(white); if (blackCount == whiteCount) { - return GameState.DRAW; + return new PlayResult(GameState.DRAW, -1); } - return GameState.WIN; + return new PlayResult(GameState.WIN, blackCount > whiteCount ? 0 : 1); } - return GameState.TURN_SKIPPED; + return new PlayResult(GameState.TURN_SKIPPED, getCurrentPlayerIndex()); } - return GameState.NORMAL; + return new PlayResult(GameState.NORMAL, getCurrentPlayerIndex()); } public Score getScore() { diff --git a/game/src/main/java/org/toop/game/games/reversi/ReversiAIR.java b/game/src/main/java/org/toop/game/games/reversi/ReversiAIR.java deleted file mode 100644 index 928f4e8..0000000 --- a/game/src/main/java/org/toop/game/games/reversi/ReversiAIR.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.toop.game.games.reversi; - -import org.toop.framework.gameFramework.model.player.AI; -import org.toop.framework.gameFramework.model.player.AbstractAI; - -import java.util.Random; - -public final class ReversiAIR extends AbstractAI{ - public int getMove(BitboardReversi game) { - int[] moves = game.getLegalMoves(); - if (moves.length == 0) return -1; - - int inty = new Random().nextInt(0, moves.length); - return moves[inty]; - } - - public ReversiAIR deepCopy() { - return new ReversiAIR(); - } -} diff --git a/game/src/main/java/org/toop/game/games/reversi/ReversiR.java b/game/src/main/java/org/toop/game/games/reversi/ReversiR.java deleted file mode 100644 index 56f5e28..0000000 --- a/game/src/main/java/org/toop/game/games/reversi/ReversiR.java +++ /dev/null @@ -1,260 +0,0 @@ -package org.toop.game.games.reversi; - -import org.toop.framework.gameFramework.GameState; -import org.toop.framework.gameFramework.model.game.PlayResult; -import org.toop.framework.gameFramework.model.game.AbstractGame; -import org.toop.framework.gameFramework.model.player.Player; - -import java.awt.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - - -public final class ReversiR extends AbstractGame { - private int movesTaken; - private Set filledCells = new HashSet<>(); - private int[] mostRecentlyFlippedPieces; - - @Override - public ReversiR deepCopy() { - return new ReversiR(this); - } - - // TODO: Don't hardcore for two players :) - public record Score(int player1Score, int player2Score) {} - - public ReversiR(Player[] players) { - super(8, 8, 2, players); - addStartPieces(); - } - - public ReversiR(ReversiR other) { - super(other); - this.movesTaken = other.movesTaken; - this.filledCells = other.filledCells; - this.mostRecentlyFlippedPieces = other.mostRecentlyFlippedPieces; - } - - - private void addStartPieces() { - this.setBoard(27, 1); - this.setBoard(28, 0); - this.setBoard(35, 0); - this.setBoard(36, 1); - updateFilledCellsSet(); - } - private void updateFilledCellsSet() { - for (int i = 0; i < 64; i++) { - if (this.getBoard()[i] != EMPTY) { - filledCells.add(new Point(i % this.getColumnSize(), i / this.getRowSize())); - } - } - } - - @Override - public int[] getLegalMoves() { - final ArrayList legalMoves = new ArrayList<>(); - int[][] boardGrid = makeBoardAGrid(); - int currentPlayer = this.getCurrentTurn(); - Set adjCell = getAdjacentCells(boardGrid); - for (Point point : adjCell){ - int[] moves = getFlipsForPotentialMove(point,currentPlayer); - int score = moves.length; - if (score > 0){ - legalMoves.add(point.x + point.y * this.getRowSize()); - } - } - return legalMoves.stream().mapToInt(Integer::intValue).toArray(); - } - - private Set getAdjacentCells(int[][] boardGrid) { - Set possibleCells = new HashSet<>(); - for (Point point : filledCells) { //for every filled cell - for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++){ //check adjacent cells - for (int deltaRow = -1; deltaRow <= 1; deltaRow++){ //orthogonally and diagonally - int newX = point.x + deltaColumn, newY = point.y + deltaRow; - if (deltaColumn == 0 && deltaRow == 0 //continue if out of bounds - || !isOnBoard(newX, newY)) { - continue; - } - if (boardGrid[newY][newX] == EMPTY) { //check if the cell is empty - possibleCells.add(new Point(newX, newY)); //and then add it to the set of possible moves - } - } - } - } - return possibleCells; - } - - public int[] getFlipsForPotentialMove(Point point, int currentPlayer) { - final ArrayList movesToFlip = new ArrayList<>(); - for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++) { //for all directions - for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { - if (deltaColumn == 0 && deltaRow == 0){ - continue; - } - int[] moves = getFlipsInDirection(point,makeBoardAGrid(),currentPlayer,deltaColumn,deltaRow); - if (moves != null) { //getFlipsInDirection - Arrays.stream(moves).forEach(movesToFlip::add); - } - } - } - return movesToFlip.stream().mapToInt(Integer::intValue).toArray(); - } - - private int[] getFlipsInDirection(Point point, int[][] boardGrid, int currentPlayer, int dirX, int dirY) { - int opponent = getOpponent(currentPlayer); - final ArrayList movesToFlip = new ArrayList<>(); - int x = point.x + dirX; - int y = point.y + dirY; - - if (!isOnBoard(x, y) || boardGrid[y][x] != opponent) { //there must first be an opponents tile - return null; - } - - while (isOnBoard(x, y) && boardGrid[y][x] == opponent) { //count the opponents tiles in this direction - - movesToFlip.add(x+y*this.getRowSize()); - x += dirX; - y += dirY; - } - if (isOnBoard(x, y) && boardGrid[y][x] == currentPlayer) { - return movesToFlip.stream().mapToInt(Integer::intValue).toArray(); //only return the count if last tile is ours - } - return null; - } - - private boolean isOnBoard(int x, int y) { - return x >= 0 && x < this.getColumnSize() && y >= 0 && y < this.getRowSize(); - } - - private int[][] makeBoardAGrid() { - int[][] boardGrid = new int[this.getRowSize()][this.getColumnSize()]; - for (int i = 0; i < 64; i++) { - boardGrid[i / this.getRowSize()][i % this.getColumnSize()] = this.getBoard()[i]; //boardGrid[y -> row] [x -> column] - } - return boardGrid; - } - - private boolean gameOver(){ - ReversiR gameCopy = deepCopy(); - return gameCopy.getLegalMoves().length == 0 && gameCopy.skipTurn().getLegalMoves().length == 0; - } - - @Override - public PlayResult play(int move) { - /*int[] legalMoves = getLegalMoves(); - boolean moveIsLegal = false; - for (int legalMove : legalMoves) { //check if the move is legal - if (move == legalMove) { - moveIsLegal = true; - break; - } - } - if (!moveIsLegal) { - return null; - } - - int[] moves = sortMovesFromCenter(Arrays.stream(getFlipsForPotentialMove(new Point(move%this.getColumnSize(),move/this.getRowSize()), getCurrentTurn())).boxed().toArray(Integer[]::new),move); - mostRecentlyFlippedPieces = moves; - this.setBoard(move); //place the move on the board - for (int m : moves) { - this.setBoard(m); //flip the correct pieces on the board - } - filledCells.add(new Point(move % this.getRowSize(), move / this.getColumnSize())); - nextTurn(); - if (getLegalMoves().length == 0) { //skip the players turn when there are no legal moves - skipMyTurn(); - if (getLegalMoves().length > 0) { - return new PlayResult(GameState.TURN_SKIPPED, getCurrentTurn()); - } - else { //end the game when neither player has a legal move - Score score = getScore(); - if (score.player1Score() == score.player2Score()) { - return new PlayResult(GameState.DRAW, EMPTY); - } - else { - return new PlayResult(GameState.WIN, getCurrentTurn()); - } - } - } - return new PlayResult(GameState.NORMAL, EMPTY);*/ - - // Check if move is legal - if (!contains(getLegalMoves(), move)){ - // Next person wins - return new PlayResult(GameState.WIN, (getCurrentTurn() + 1) % 2); - } - - // Move is legal, proceed as normal - int[] moves = sortMovesFromCenter(Arrays.stream(getFlipsForPotentialMove(new Point(move%this.getColumnSize(),move/this.getRowSize()), getCurrentTurn())).boxed().toArray(Integer[]::new),move); - mostRecentlyFlippedPieces = moves; - this.setBoard(move); //place the move on the board - for (int m : moves) { - this.setBoard(m); //flip the correct pieces on the board - } - filledCells.add(new Point(move % this.getRowSize(), move / this.getColumnSize())); - - nextTurn(); - - // Check for forced turn skip - if (getLegalMoves().length == 0){ - PlayResult result; - // Check if next turn is also a force skip - if (deepCopy().skipTurn().getLegalMoves().length == 0){ - // Game over - int winner = getWinner(); - result = new PlayResult(winner == EMPTY ? GameState.DRAW : GameState.WIN, winner); - }else{ - // Turn skipped - result = new PlayResult(GameState.TURN_SKIPPED, getCurrentTurn()); - skipTurn(); - } - return result; - } - return new PlayResult(GameState.NORMAL, EMPTY); - } - - private ReversiR skipTurn(){ - nextTurn(); - return this; - } - - private int getOpponent(int currentPlayer){ - return (currentPlayer + 1)%2; - } - - public int getWinner(){ - int player1Score = 0, player2Score = 0; - for (int count = 0; count < this.getRowSize() * this.getColumnSize(); count++) { - if (this.getBoard()[count] == 0) { - player1Score += 1; - } - if (this.getBoard()[count] == 1) { - player2Score += 1; - } - } - return player1Score == player2Score? -1 : player1Score > player2Score ? 0 : 1; - } - private int[] sortMovesFromCenter(Integer[] moves, int center) { //sorts the pieces to be flipped for animation purposes - int centerX = center%this.getColumnSize(); - int centerY = center/this.getRowSize(); - Arrays.sort(moves, (a, b) -> { - int dxA = a%this.getColumnSize() - centerX; - int dyA = a/this.getRowSize() - centerY; - int dxB = b%this.getColumnSize() - centerX; - int dyB = b/this.getRowSize() - centerY; - - int distA = dxA * dxA + dyA * dyA; - int distB = dxB * dxB + dyB * dyB; - - return Integer.compare(distA, distB); - }); - return Arrays.stream(moves).mapToInt(Integer::intValue).toArray(); - } - public int[] getMostRecentlyFlippedPieces() { - return mostRecentlyFlippedPieces; - } -} \ No newline at end of file diff --git a/game/src/main/java/org/toop/game/games/tictactoe/BitboardTicTacToe.java b/game/src/main/java/org/toop/game/games/tictactoe/BitboardTicTacToe.java index df2568a..f0deb0c 100644 --- a/game/src/main/java/org/toop/game/games/tictactoe/BitboardTicTacToe.java +++ b/game/src/main/java/org/toop/game/games/tictactoe/BitboardTicTacToe.java @@ -24,17 +24,7 @@ public class BitboardTicTacToe extends BitboardGame { super(other); } - @Override - public int[] getLegalMoves(){ - return translateLegalMoves(getLegalMoves2()); - } - - @Override - public PlayResult play(int move) { - return play2(translateMove(move)); - } - - public long getLegalMoves2() { + public long getLegalMoves() { final long xBitboard = getPlayerBitboard(0); final long oBitboard = getPlayerBitboard(1); @@ -42,9 +32,9 @@ public class BitboardTicTacToe extends BitboardGame { return (~taken) & 0x1ffL; } - public PlayResult play2(long move) { + public PlayResult play(long move) { // Player loses if move is invalid - if ((move & getLegalMoves2()) == 0 || Long.bitCount(move) != 1){ + if ((move & getLegalMoves()) == 0 || Long.bitCount(move) != 1){ return new PlayResult(GameState.WIN, getNextPlayer()); } @@ -64,7 +54,7 @@ public class BitboardTicTacToe extends BitboardGame { // Check for early draw - if (getLegalMoves2() == 0L || checkEarlyDraw()) { + if (getLegalMoves() == 0L || checkEarlyDraw()) { return new PlayResult(GameState.DRAW, -1); } @@ -102,11 +92,6 @@ public class BitboardTicTacToe extends BitboardGame { return true; } - @Override - public int[] getBoard() { - return translateBoard(); - } - @Override public BitboardTicTacToe deepCopy() { return new BitboardTicTacToe(this); diff --git a/game/src/main/java/org/toop/game/games/tictactoe/TicTacToeAIR.java b/game/src/main/java/org/toop/game/games/tictactoe/TicTacToeAIR.java deleted file mode 100644 index 497a0b8..0000000 --- a/game/src/main/java/org/toop/game/games/tictactoe/TicTacToeAIR.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.toop.game.games.tictactoe; - -import org.toop.framework.gameFramework.model.player.AI; -import org.toop.framework.gameFramework.model.player.AbstractAI; -import org.toop.framework.gameFramework.model.game.PlayResult; -import org.toop.framework.gameFramework.GameState; - -/** - * AI implementation for playing Tic-Tac-Toe. - *

- * 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. - *

- */ -public final class TicTacToeAIR extends AbstractAI { - - /** - * Determines the best move for the given Tic-Tac-Toe game state. - *

- * 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. - *

- * - * @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 - */ - public int getMove(BitboardTicTacToe game) { - int depth = 9; - assert game != null; - final int[] legalMoves = game.getLegalMoves(); - - // If there are no moves, return -1 - if (legalMoves.length == 0) { - return -1; - } - - // If first move, pick a corner - 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; - - // Calculate Move score of each move, keep track what moves had the best score - 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(BitboardTicTacToe game, int depth, int move, boolean maximizing) { - final BitboardTicTacToe copy = game.deepCopy(); - final PlayResult result = copy.play(move); - - GameState state = result.state(); - - 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; - } - - @Override - public AI deepCopy() { - return new TicTacToeAIR(); - } -} diff --git a/game/src/main/java/org/toop/game/games/tictactoe/TicTacToeR.java b/game/src/main/java/org/toop/game/games/tictactoe/TicTacToeR.java deleted file mode 100644 index a04b5f9..0000000 --- a/game/src/main/java/org/toop/game/games/tictactoe/TicTacToeR.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.toop.game.games.tictactoe; - -import org.toop.framework.gameFramework.model.game.PlayResult; -import org.toop.framework.gameFramework.model.game.AbstractGame; -import org.toop.framework.gameFramework.GameState; -import org.toop.framework.gameFramework.model.player.Player; - -import java.util.ArrayList; -import java.util.Objects; - -public final class TicTacToeR extends AbstractGame { - private int movesLeft; - - public TicTacToeR(Player[] players) { - super(3, 3, 2, players); - movesLeft = this.getBoard().length; - } - - public TicTacToeR(TicTacToeR other) { - super(other); - movesLeft = other.movesLeft; - } - - @Override - public int[] getLegalMoves() { - final ArrayList legalMoves = new ArrayList(); - - for (int i = 0; i < this.getBoard().length; i++) { - if (Objects.equals(this.getBoard()[i], EMPTY)) { - legalMoves.add(i); - } - } - return legalMoves.stream().mapToInt(Integer::intValue).toArray(); - } - - @Override - public PlayResult play(int move) { - // NOT MY ASSERTIONS - Stef - assert move >= 0 && move < this.getBoard().length; - - // Player loses if move is invalid - if (!contains(getLegalMoves(), move)) { - // Next player wins - return new PlayResult(GameState.WIN, (getCurrentTurn() + 1)%2); // TODO: Make this a generic method like getNextPlayer() or something similar. - } - - // Move is valid, make move. - this.setBoard(move); - movesLeft--; - nextTurn(); - - // Check if current player won TODO: Make this generic? - // Not sure why I am checking for ANY win when only current player should be able to win. - int t = checkForWin(); - if (t != EMPTY) { - return new PlayResult(GameState.WIN, t); - } - - // Check for (early) draw - if (movesLeft <= 3) { - if (checkForEarlyDraw()) { - return new PlayResult(GameState.DRAW, EMPTY); - } - } - - // Nothing weird happened, continue on as normal - return new PlayResult(GameState.NORMAL, EMPTY); - } - - private int checkForWin() { - // Horizontal - for (int i = 0; i < 3; i++) { - - final int index = i * 3; - - if (!Objects.equals(this.getBoard()[index], EMPTY) - && Objects.equals(this.getBoard()[index], this.getBoard()[index + 1]) - && Objects.equals(this.getBoard()[index], this.getBoard()[index + 2])) { - return this.getBoard()[index]; - } - } - - // Vertical - for (int i = 0; i < 3; i++) { - if (!Objects.equals(this.getBoard()[i], EMPTY) && Objects.equals(this.getBoard()[i], this.getBoard()[i + 3]) && Objects.equals(this.getBoard()[i], this.getBoard()[i + 6])) { - return this.getBoard()[i]; - } - } - - // B-Slash - if (!Objects.equals(this.getBoard()[0], EMPTY) && Objects.equals(this.getBoard()[0], this.getBoard()[4]) && Objects.equals(this.getBoard()[0], this.getBoard()[8])) { - return this.getBoard()[0]; - } - - // F-Slash - if (!Objects.equals(this.getBoard()[2], EMPTY) && Objects.equals(this.getBoard()[2], this.getBoard()[4]) && Objects.equals(this.getBoard()[2], this.getBoard()[6])) - return this.getBoard()[2]; - - // Default return - return EMPTY; - } - - private boolean checkForEarlyDraw() { - for (final int move : this.getLegalMoves()) { - final TicTacToeR copy = this.deepCopy(); - - if (copy.play(move).state() == GameState.WIN || !copy.checkForEarlyDraw()) { - return false; - } - } - - return true; - } - - public TicTacToeR deepCopy() { - return new TicTacToeR(this); - } -} diff --git a/game/src/main/java/org/toop/game/players/ArtificialPlayer.java b/game/src/main/java/org/toop/game/players/ArtificialPlayer.java index 41883a5..418cbed 100644 --- a/game/src/main/java/org/toop/game/players/ArtificialPlayer.java +++ b/game/src/main/java/org/toop/game/players/ArtificialPlayer.java @@ -44,7 +44,7 @@ public class ArtificialPlayer> extends AbstractPlayer * @return the integer representing the chosen move * @throws ClassCastException if {@code gameCopy} is not of type {@code T} */ - public int getMove(T gameCopy) { + public long getMove(T gameCopy) { return ai.getMove(gameCopy); } diff --git a/game/src/main/java/org/toop/game/players/LocalPlayer.java b/game/src/main/java/org/toop/game/players/LocalPlayer.java index 200ac3a..8f3b94d 100644 --- a/game/src/main/java/org/toop/game/players/LocalPlayer.java +++ b/game/src/main/java/org/toop/game/players/LocalPlayer.java @@ -11,7 +11,7 @@ public class LocalPlayer> extends AbstractPlayer { // Future can be used with event system, IF unsubscribeAfterSuccess works... // private CompletableFuture LastMove = new CompletableFuture<>(); - private CompletableFuture LastMove; + private CompletableFuture LastMove; public LocalPlayer(String name) { super(name); @@ -22,11 +22,11 @@ public class LocalPlayer> extends AbstractPlayer { } @Override - public int getMove(T gameCopy) { + public long getMove(T gameCopy) { return getValidMove(gameCopy); } - public void setMove(int move) { + public void setMove(long move) { LastMove.complete(move); } @@ -36,11 +36,12 @@ public class LocalPlayer> extends AbstractPlayer { return false; } - private int getMove2(T gameCopy) { + private long getMove2(T gameCopy) { LastMove = new CompletableFuture<>(); - int move = -1; + long move = 0; try { move = LastMove.get(); + System.out.println(Long.toBinaryString(move)); } catch (InterruptedException | ExecutionException e) { // TODO: Add proper logging. e.printStackTrace(); @@ -48,14 +49,14 @@ public class LocalPlayer> extends AbstractPlayer { return move; } - protected int getValidMove(T gameCopy){ + protected long getValidMove(T gameCopy){ // Get this player's valid moves - int[] validMoves = gameCopy.getLegalMoves(); + long validMoves = gameCopy.getLegalMoves(); // Make sure provided move is valid // TODO: Limit amount of retries? // TODO: Stop copying game so many times - int move = getMove2(gameCopy.deepCopy()); - while (!contains(validMoves, move)) { + long move = getMove2(gameCopy.deepCopy()); + while ((validMoves & move) == 0) { System.out.println("Not a valid move, try again"); move = getMove2(gameCopy.deepCopy()); } diff --git a/game/src/main/java/org/toop/game/players/RandomAI.java b/game/src/main/java/org/toop/game/players/RandomAI.java new file mode 100644 index 0000000..72dfe1c --- /dev/null +++ b/game/src/main/java/org/toop/game/players/RandomAI.java @@ -0,0 +1,41 @@ +package org.toop.game.players; + +import org.toop.framework.gameFramework.model.game.TurnBasedGame; +import org.toop.framework.gameFramework.model.player.AbstractAI; + +import java.util.Random; + + +public class RandomAI> extends AbstractAI { + + public RandomAI() { + super(); + } + + @Override + public RandomAI deepCopy() { + return new RandomAI(); + } + + @Override + public long getMove(T game) { + System.out.println("Getting move?"); + long legalMoves = game.getLegalMoves(); + int move = new Random().nextInt(Long.bitCount(legalMoves)); + System.out.println("Legal moves: " + Long.toBinaryString(legalMoves)); + System.out.println("Playing: " + Long.toBinaryString(nthBitIndex(legalMoves, move))); + return nthBitIndex(legalMoves, move); + } + + public static long nthBitIndex(long bb, int n) { + while (bb != 0) { + int tz = Long.numberOfTrailingZeros(bb); + if (n == 0) { + return 1L << tz; + } + bb &= bb - 1; // clear the least significant 1 + n--; + } + return 0L; // not enough 1s + } +}