From 3376bc2682980615fdd9b92200b5df8c3793eab8 Mon Sep 17 00:00:00 2001 From: Stef Date: Sat, 29 Nov 2025 00:39:53 +0100 Subject: [PATCH] Fixed major issue in game deepcopy --- .../org/toop/app}/game/GameController.java | 21 ++++-- .../toop/app/game/TicTacToeController.java | 47 +++++++++++++ .../toop/app/game/TurnBasedGameThread.java | 68 +++++++------------ .../org/toop/app}/game/UpdatesGameUI.java | 2 +- .../app/widget/view/LocalMultiplayerView.java | 2 +- .../main/java/org/toop/game/PlayResult.java | 6 ++ .../org/toop/game/TicTacToeController.java | 24 ------- .../java/org/toop/game/TurnBasedGameR.java | 1 + .../org/toop/game/interfaces/IPlayableR.java | 3 +- .../org/toop/game/tictactoe/TicTacToeAIR.java | 12 +++- .../org/toop/game/tictactoe/TicTacToeR.java | 39 ++++++----- 11 files changed, 123 insertions(+), 102 deletions(-) rename {game/src/main/java/org/toop => app/src/main/java/org/toop/app}/game/GameController.java (56%) create mode 100644 app/src/main/java/org/toop/app/game/TicTacToeController.java rename {game/src/main/java/org/toop => app/src/main/java/org/toop/app}/game/UpdatesGameUI.java (67%) create mode 100644 game/src/main/java/org/toop/game/PlayResult.java delete mode 100644 game/src/main/java/org/toop/game/TicTacToeController.java diff --git a/game/src/main/java/org/toop/game/GameController.java b/app/src/main/java/org/toop/app/game/GameController.java similarity index 56% rename from game/src/main/java/org/toop/game/GameController.java rename to app/src/main/java/org/toop/app/game/GameController.java index ff24bb2..e27c08e 100644 --- a/game/src/main/java/org/toop/game/GameController.java +++ b/app/src/main/java/org/toop/app/game/GameController.java @@ -1,17 +1,24 @@ -/*package org.toop.game; +package org.toop.app.game; import org.toop.app.canvas.GameCanvas; import org.toop.app.game.TurnBasedGameThread; import org.toop.app.widget.view.GameView; public abstract class GameController implements UpdatesGameUI { - // TODO: Seperate this from game Thread + // Reference to primary view 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; + // Reference to game canvas + protected final GameCanvas canvas; + + // Reference to gameThread + protected TurnBasedGameThread gameThread; + + protected GameController(GameCanvas canvas) { this.canvas = canvas; } -}*/ + + protected void setThread(TurnBasedGameThread gameThread){ + this.gameThread = gameThread; + } +} diff --git a/app/src/main/java/org/toop/app/game/TicTacToeController.java b/app/src/main/java/org/toop/app/game/TicTacToeController.java new file mode 100644 index 0000000..eb97545 --- /dev/null +++ b/app/src/main/java/org/toop/app/game/TicTacToeController.java @@ -0,0 +1,47 @@ +package org.toop.app.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.app.game.Players.Player; +import org.toop.app.widget.WidgetContainer; +import org.toop.game.tictactoe.TicTacToeR; + +public class TicTacToeController extends GameController { + + public TicTacToeController(Player[] players) { + TicTacToeR ticTacToeR = new TicTacToeR(); + super(new TicTacToeCanvas(Color.GRAY, + (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,(c) -> {if (players[ticTacToeR.getCurrentTurn()] instanceof LocalPlayer lp) {lp.enqueueMove(c);}})); + // TODO: Deal with this thread better. Can't give it to super because of "this" refence. + setThread(new TurnBasedGameThread(players, ticTacToeR, this)); + + initUI(); + } + + @Override + public void updateUI() { + canvas.clearAll(); + drawMoves(); + } + + private void initUI(){ + primary.add(Pos.CENTER, canvas.getCanvas()); + WidgetContainer.getCurrentView().transitionNext(primary); + } + + private void drawMoves(){ + int[] board = gameThread.getBoard(); + + // Draw each move + for (int i = 0; i < board.length; i++){ + switch(board[i]){ + case 0 -> canvas.drawChar('X', Color.RED, i); + case 1 -> canvas.drawChar('O', Color.BLUE, i); + default -> {} + } + } + } +} diff --git a/app/src/main/java/org/toop/app/game/TurnBasedGameThread.java b/app/src/main/java/org/toop/app/game/TurnBasedGameThread.java index f2506bd..dfb285f 100644 --- a/app/src/main/java/org/toop/app/game/TurnBasedGameThread.java +++ b/app/src/main/java/org/toop/app/game/TurnBasedGameThread.java @@ -1,97 +1,75 @@ package org.toop.app.game; -import javafx.geometry.Pos; -import javafx.scene.paint.Color; -import org.toop.app.App; -import org.toop.app.canvas.GameCanvas; -import org.toop.app.canvas.TicTacToeCanvas; -import org.toop.app.game.Players.LocalPlayer; import org.toop.app.game.Players.Player; -import org.toop.app.widget.WidgetContainer; -import org.toop.app.widget.view.GameView; + +import org.toop.game.PlayResult; import org.toop.game.TurnBasedGameR; import org.toop.game.enumerators.GameState; -import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; public class TurnBasedGameThread implements Runnable { private final Player[] players; // List of players, can't be changed. private final TurnBasedGameR game; // Reference to game instance - private final AtomicBoolean isRunning = new AtomicBoolean(); - //private final GameController controller; - - protected final GameView primary = new GameView(null, null, null); - protected final GameCanvas canvas; - - public TurnBasedGameThread(Player[] players, TurnBasedGameR game) { - // Set reference to controller - //this.controller = controller; + private final AtomicBoolean isRunning = new AtomicBoolean(true); + private final GameController controller; + public TurnBasedGameThread(Player[] players, TurnBasedGameR game, GameController controller) { // Make sure player list matches expected size if (players.length != game.getPlayerCount()){ throw new IllegalArgumentException("players and game's players must have same length"); } + // Set vars + this.controller = controller; this.players = players; this.game = game; + // Create and run thread Thread thread = new Thread(this::run); thread.start(); + } - // UI SHIZ TO MOVE - canvas = new TicTacToeCanvas(Color.GRAY, - (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,(c) -> {if (players[game.getCurrentTurn()] instanceof LocalPlayer lp) {lp.enqueueMove(c);}}); - primary.add(Pos.CENTER, canvas.getCanvas()); - WidgetContainer.getCurrentView().transitionNext(primary); - + public int[] getBoard(){ + return game.getBoard(); } public Player[] getPlayers() { return players; } - // Move to UI shiz - private void drawMove(int move) { - if (game.getCurrentTurn() == 1){ - canvas.drawChar('X', Color.RED, move); - } - else{ - canvas.drawChar('O', Color.RED, move); - } - } - public void run() { - isRunning.set(true); - // Game logic loop while(isRunning.get()) { // Get current player Player currentPlayer = players[game.getCurrentTurn()]; - System.out.println(game.getCurrentTurn() + "'s turn"); + // Get this player's valid moves int[] validMoves = game.getLegalMoves(); - // Get player's move, reask if Move is invalid + // Make sure provided move is valid // TODO: Limit amount of retries? int move = currentPlayer.getMove(game.clone()); while (!contains(validMoves, move)) { move = currentPlayer.getMove(game.clone()); } - // Make move - System.out.println(Arrays.toString(game.getBoard())); - GameState state = game.play(move); - drawMove(move); + // Make move + PlayResult result = game.play(move); + + // Tell controller to update UI + controller.updateUI(); + GameState state = result.state(); if (state != GameState.NORMAL) { if (state == GameState.WIN) { - // Someone won + // Win, do something + System.out.println(result.winner() + " WON"); } else if (state == GameState.DRAW) { - // THere was a draw + // Draw, do something + System.out.println("DRAW"); } - System.out.println(state); isRunning.set(false); } } diff --git a/game/src/main/java/org/toop/game/UpdatesGameUI.java b/app/src/main/java/org/toop/app/game/UpdatesGameUI.java similarity index 67% rename from game/src/main/java/org/toop/game/UpdatesGameUI.java rename to app/src/main/java/org/toop/app/game/UpdatesGameUI.java index 89cd195..503037c 100644 --- a/game/src/main/java/org/toop/game/UpdatesGameUI.java +++ b/app/src/main/java/org/toop/app/game/UpdatesGameUI.java @@ -1,4 +1,4 @@ -package org.toop.game; +package org.toop.app.game; public interface UpdatesGameUI { void updateUI(); 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 68cd088..757358a 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 @@ -36,7 +36,7 @@ public class LocalMultiplayerView extends ViewWidget { } switch (information.type) { - case TICTACTOE -> new TurnBasedGameThread(new Player[]{new LocalPlayer(), new ArtificialPlayer<>(new TicTacToeAIR())}, new TicTacToeR()); + case TICTACTOE -> new TicTacToeController(new Player[]{new LocalPlayer(), new ArtificialPlayer<>(new TicTacToeAIR())}); case REVERSI -> new ReversiGame(information); case CONNECT4 -> new Connect4Game(information); // case BATTLESHIP -> new BattleshipGame(information); diff --git a/game/src/main/java/org/toop/game/PlayResult.java b/game/src/main/java/org/toop/game/PlayResult.java new file mode 100644 index 0000000..36737bc --- /dev/null +++ b/game/src/main/java/org/toop/game/PlayResult.java @@ -0,0 +1,6 @@ +package org.toop.game; + +import org.toop.game.enumerators.GameState; + +public record PlayResult(GameState state, int winner) { +} diff --git a/game/src/main/java/org/toop/game/TicTacToeController.java b/game/src/main/java/org/toop/game/TicTacToeController.java deleted file mode 100644 index c09e53a..0000000 --- a/game/src/main/java/org/toop/game/TicTacToeController.java +++ /dev/null @@ -1,24 +0,0 @@ -/*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.app.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() { - - } -}*/ diff --git a/game/src/main/java/org/toop/game/TurnBasedGameR.java b/game/src/main/java/org/toop/game/TurnBasedGameR.java index f3b65a8..d5b576a 100644 --- a/game/src/main/java/org/toop/game/TurnBasedGameR.java +++ b/game/src/main/java/org/toop/game/TurnBasedGameR.java @@ -12,6 +12,7 @@ public abstract class TurnBasedGameR extends GameR { protected TurnBasedGameR(TurnBasedGameR other){ super(other); this.playerCount = other.playerCount; + this.turn = other.turn; } public int getPlayerCount(){return this.playerCount;} diff --git a/game/src/main/java/org/toop/game/interfaces/IPlayableR.java b/game/src/main/java/org/toop/game/interfaces/IPlayableR.java index 677617f..6d86231 100644 --- a/game/src/main/java/org/toop/game/interfaces/IPlayableR.java +++ b/game/src/main/java/org/toop/game/interfaces/IPlayableR.java @@ -1,5 +1,6 @@ package org.toop.game.interfaces; +import org.toop.game.PlayResult; import org.toop.game.enumerators.GameState; /** @@ -26,5 +27,5 @@ public interface IPlayableR { * @param move the move to play, represented as an integer * @return the {@link GameState} after the move is played */ - GameState play(int move); + PlayResult play(int move); } diff --git a/game/src/main/java/org/toop/game/tictactoe/TicTacToeAIR.java b/game/src/main/java/org/toop/game/tictactoe/TicTacToeAIR.java index f9b2fb6..1fddcfc 100644 --- a/game/src/main/java/org/toop/game/tictactoe/TicTacToeAIR.java +++ b/game/src/main/java/org/toop/game/tictactoe/TicTacToeAIR.java @@ -1,8 +1,11 @@ package org.toop.game.tictactoe; import org.toop.game.AIR; +import org.toop.game.PlayResult; import org.toop.game.enumerators.GameState; +import java.util.Arrays; + /** * AI implementation for playing Tic-Tac-Toe. *

@@ -30,13 +33,14 @@ public final class TicTacToeAIR extends AIR { public int findBestMove(TicTacToeR game, int depth) { assert game != null; assert depth >= 0; - 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]; @@ -49,6 +53,7 @@ public final class TicTacToeAIR extends AIR { 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); @@ -57,7 +62,6 @@ public final class TicTacToeAIR extends AIR { bestScore = score; } } - return bestMove != -1 ? bestMove : legalMoves[(int)(Math.random() * legalMoves.length)]; } @@ -72,7 +76,9 @@ public final class TicTacToeAIR extends AIR { */ private int getMoveScore(TicTacToeR game, int depth, int move, boolean maximizing) { final TicTacToeR copy = game.clone(); - final GameState state = copy.play(move); + final PlayResult result = copy.play(move); + + GameState state = result.state(); switch (state) { case DRAW: return 0; diff --git a/game/src/main/java/org/toop/game/tictactoe/TicTacToeR.java b/game/src/main/java/org/toop/game/tictactoe/TicTacToeR.java index 6f58645..24df43f 100644 --- a/game/src/main/java/org/toop/game/tictactoe/TicTacToeR.java +++ b/game/src/main/java/org/toop/game/tictactoe/TicTacToeR.java @@ -1,10 +1,10 @@ package org.toop.game.tictactoe; +import org.toop.game.PlayResult; import org.toop.game.TurnBasedGameR; import org.toop.game.enumerators.GameState; import java.util.ArrayList; -import java.util.Arrays; import java.util.Objects; public final class TicTacToeR extends TurnBasedGameR { @@ -23,72 +23,75 @@ public final class TicTacToeR extends TurnBasedGameR { @Override public int[] getLegalMoves() { final ArrayList legalMoves = new ArrayList(); - final char currentValue = getCurrentValue(); for (int i = 0; i < this.getBoard().length; i++) { if (Objects.equals(this.getBoard()[i], EMPTY)) { legalMoves.add(i); } } - System.out.println(Arrays.toString(legalMoves.stream().mapToInt(Integer::intValue).toArray())); return legalMoves.stream().mapToInt(Integer::intValue).toArray(); } @Override - public GameState play(int move) { + public PlayResult play(int move) { assert move >= 0 && move < this.getBoard().length; // TODO: Make sure this move is allowed, maybe on the board side? this.setBoard(move); movesLeft--; - nextTurn(); - - if (checkForWin()) { - return GameState.WIN; + int t = checkForWin(); + if (t != -1) { + return new PlayResult(GameState.WIN, t); } if (movesLeft <= 2) { if (movesLeft <= 0 || checkForEarlyDraw()) { - return GameState.DRAW; + return new PlayResult(GameState.DRAW, EMPTY); } } - return GameState.NORMAL; + nextTurn(); + return new PlayResult(GameState.NORMAL, EMPTY); } - private boolean checkForWin() { + 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 true; + 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 true; + 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 true; + return this.getBoard()[0]; } // F-Slash - return !Objects.equals(this.getBoard()[2], EMPTY) && Objects.equals(this.getBoard()[2], this.getBoard()[4]) && Objects.equals(this.getBoard()[2], this.getBoard()[6]); + 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.clone(); - if (copy.play(move) == GameState.WIN || !copy.checkForEarlyDraw()) { + if (copy.play(move).state() == GameState.WIN || !copy.checkForEarlyDraw()) { return false; } } @@ -96,10 +99,6 @@ public final class TicTacToeR extends TurnBasedGameR { return true; } - private char getCurrentValue() { - return this.getCurrentTurn() == 0 ? 'X' : 'O'; - } - @Override public TicTacToeR clone() { return new TicTacToeR(this);