Fixed major issue in game deepcopy

This commit is contained in:
2025-11-29 00:39:53 +01:00
parent a11ba71417
commit 3376bc2682
11 changed files with 123 additions and 102 deletions

View File

@@ -1,17 +1,24 @@
/*package org.toop.game; package org.toop.app.game;
import org.toop.app.canvas.GameCanvas; import org.toop.app.canvas.GameCanvas;
import org.toop.app.game.TurnBasedGameThread; import org.toop.app.game.TurnBasedGameThread;
import org.toop.app.widget.view.GameView; import org.toop.app.widget.view.GameView;
public abstract class GameController implements UpdatesGameUI { 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 GameView primary = new GameView(null, null, null);
protected final GameCanvas canvas;
protected final TurnBasedGameThread gameThread;
protected GameController(GameCanvas canvas, TurnBasedGameThread gameThread) { // Reference to game canvas
this.gameThread = gameThread; protected final GameCanvas canvas;
// Reference to gameThread
protected TurnBasedGameThread gameThread;
protected GameController(GameCanvas canvas) {
this.canvas = canvas; this.canvas = canvas;
} }
}*/
protected void setThread(TurnBasedGameThread gameThread){
this.gameThread = gameThread;
}
}

View File

@@ -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 -> {}
}
}
}
}

View File

@@ -1,97 +1,75 @@
package org.toop.app.game; 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.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.TurnBasedGameR;
import org.toop.game.enumerators.GameState; import org.toop.game.enumerators.GameState;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class TurnBasedGameThread implements Runnable { public class TurnBasedGameThread implements Runnable {
private final Player[] players; // List of players, can't be changed. private final Player[] players; // List of players, can't be changed.
private final TurnBasedGameR game; // Reference to game instance private final TurnBasedGameR game; // Reference to game instance
private final AtomicBoolean isRunning = new AtomicBoolean(); private final AtomicBoolean isRunning = new AtomicBoolean(true);
//private final GameController controller; 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;
public TurnBasedGameThread(Player[] players, TurnBasedGameR game, GameController controller) {
// Make sure player list matches expected size // Make sure player list matches expected size
if (players.length != game.getPlayerCount()){ if (players.length != game.getPlayerCount()){
throw new IllegalArgumentException("players and game's players must have same length"); throw new IllegalArgumentException("players and game's players must have same length");
} }
// Set vars
this.controller = controller;
this.players = players; this.players = players;
this.game = game; this.game = game;
// Create and run thread
Thread thread = new Thread(this::run); Thread thread = new Thread(this::run);
thread.start(); thread.start();
}
// UI SHIZ TO MOVE public int[] getBoard(){
canvas = new TicTacToeCanvas(Color.GRAY, return game.getBoard();
(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 Player[] getPlayers() { public Player[] getPlayers() {
return players; 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() { public void run() {
isRunning.set(true);
// Game logic loop // Game logic loop
while(isRunning.get()) { while(isRunning.get()) {
// Get current player // Get current player
Player currentPlayer = players[game.getCurrentTurn()]; Player currentPlayer = players[game.getCurrentTurn()];
System.out.println(game.getCurrentTurn() + "'s turn");
// Get this player's valid moves // Get this player's valid moves
int[] validMoves = game.getLegalMoves(); int[] validMoves = game.getLegalMoves();
// Get player's move, reask if Move is invalid // Make sure provided move is valid
// TODO: Limit amount of retries? // TODO: Limit amount of retries?
int move = currentPlayer.getMove(game.clone()); int move = currentPlayer.getMove(game.clone());
while (!contains(validMoves, move)) { while (!contains(validMoves, move)) {
move = currentPlayer.getMove(game.clone()); 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.NORMAL) {
if (state == GameState.WIN) { if (state == GameState.WIN) {
// Someone won // Win, do something
System.out.println(result.winner() + " WON");
} else if (state == GameState.DRAW) { } else if (state == GameState.DRAW) {
// THere was a draw // Draw, do something
System.out.println("DRAW");
} }
System.out.println(state);
isRunning.set(false); isRunning.set(false);
} }
} }

View File

@@ -1,4 +1,4 @@
package org.toop.game; package org.toop.app.game;
public interface UpdatesGameUI { public interface UpdatesGameUI {
void updateUI(); void updateUI();

View File

@@ -36,7 +36,7 @@ public class LocalMultiplayerView extends ViewWidget {
} }
switch (information.type) { 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 REVERSI -> new ReversiGame(information);
case CONNECT4 -> new Connect4Game(information); case CONNECT4 -> new Connect4Game(information);
// case BATTLESHIP -> new BattleshipGame(information); // case BATTLESHIP -> new BattleshipGame(information);

View File

@@ -0,0 +1,6 @@
package org.toop.game;
import org.toop.game.enumerators.GameState;
public record PlayResult(GameState state, int winner) {
}

View File

@@ -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() {
}
}*/

View File

@@ -12,6 +12,7 @@ public abstract class TurnBasedGameR extends GameR {
protected TurnBasedGameR(TurnBasedGameR other){ protected TurnBasedGameR(TurnBasedGameR other){
super(other); super(other);
this.playerCount = other.playerCount; this.playerCount = other.playerCount;
this.turn = other.turn;
} }
public int getPlayerCount(){return this.playerCount;} public int getPlayerCount(){return this.playerCount;}

View File

@@ -1,5 +1,6 @@
package org.toop.game.interfaces; package org.toop.game.interfaces;
import org.toop.game.PlayResult;
import org.toop.game.enumerators.GameState; import org.toop.game.enumerators.GameState;
/** /**
@@ -26,5 +27,5 @@ public interface IPlayableR {
* @param move the move to play, represented as an integer * @param move the move to play, represented as an integer
* @return the {@link GameState} after the move is played * @return the {@link GameState} after the move is played
*/ */
GameState play(int move); PlayResult play(int move);
} }

View File

@@ -1,8 +1,11 @@
package org.toop.game.tictactoe; package org.toop.game.tictactoe;
import org.toop.game.AIR; import org.toop.game.AIR;
import org.toop.game.PlayResult;
import org.toop.game.enumerators.GameState; import org.toop.game.enumerators.GameState;
import java.util.Arrays;
/** /**
* AI implementation for playing Tic-Tac-Toe. * AI implementation for playing Tic-Tac-Toe.
* <p> * <p>
@@ -30,13 +33,14 @@ public final class TicTacToeAIR extends AIR<TicTacToeR> {
public int findBestMove(TicTacToeR game, int depth) { public int findBestMove(TicTacToeR game, int depth) {
assert game != null; assert game != null;
assert depth >= 0; assert depth >= 0;
final int[] legalMoves = game.getLegalMoves(); final int[] legalMoves = game.getLegalMoves();
// If there are no moves, return -1
if (legalMoves.length == 0) { if (legalMoves.length == 0) {
return -1; return -1;
} }
// If first move, pick a corner
if (legalMoves.length == 9) { if (legalMoves.length == 9) {
return switch ((int)(Math.random() * 4)) { return switch ((int)(Math.random() * 4)) {
case 0 -> legalMoves[2]; case 0 -> legalMoves[2];
@@ -49,6 +53,7 @@ public final class TicTacToeAIR extends AIR<TicTacToeR> {
int bestScore = -depth; int bestScore = -depth;
int bestMove = -1; int bestMove = -1;
// Calculate Move score of each move, keep track what moves had the best score
for (final int move : legalMoves) { for (final int move : legalMoves) {
final int score = getMoveScore(game, depth, move, true); final int score = getMoveScore(game, depth, move, true);
@@ -57,7 +62,6 @@ public final class TicTacToeAIR extends AIR<TicTacToeR> {
bestScore = score; bestScore = score;
} }
} }
return bestMove != -1 ? bestMove : legalMoves[(int)(Math.random() * legalMoves.length)]; return bestMove != -1 ? bestMove : legalMoves[(int)(Math.random() * legalMoves.length)];
} }
@@ -72,7 +76,9 @@ public final class TicTacToeAIR extends AIR<TicTacToeR> {
*/ */
private int getMoveScore(TicTacToeR game, int depth, int move, boolean maximizing) { private int getMoveScore(TicTacToeR game, int depth, int move, boolean maximizing) {
final TicTacToeR copy = game.clone(); final TicTacToeR copy = game.clone();
final GameState state = copy.play(move); final PlayResult result = copy.play(move);
GameState state = result.state();
switch (state) { switch (state) {
case DRAW: return 0; case DRAW: return 0;

View File

@@ -1,10 +1,10 @@
package org.toop.game.tictactoe; package org.toop.game.tictactoe;
import org.toop.game.PlayResult;
import org.toop.game.TurnBasedGameR; import org.toop.game.TurnBasedGameR;
import org.toop.game.enumerators.GameState; import org.toop.game.enumerators.GameState;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
public final class TicTacToeR extends TurnBasedGameR { public final class TicTacToeR extends TurnBasedGameR {
@@ -23,72 +23,75 @@ public final class TicTacToeR extends TurnBasedGameR {
@Override @Override
public int[] getLegalMoves() { public int[] getLegalMoves() {
final ArrayList<Integer> legalMoves = new ArrayList<Integer>(); final ArrayList<Integer> legalMoves = new ArrayList<Integer>();
final char currentValue = getCurrentValue();
for (int i = 0; i < this.getBoard().length; i++) { for (int i = 0; i < this.getBoard().length; i++) {
if (Objects.equals(this.getBoard()[i], EMPTY)) { if (Objects.equals(this.getBoard()[i], EMPTY)) {
legalMoves.add(i); legalMoves.add(i);
} }
} }
System.out.println(Arrays.toString(legalMoves.stream().mapToInt(Integer::intValue).toArray()));
return legalMoves.stream().mapToInt(Integer::intValue).toArray(); return legalMoves.stream().mapToInt(Integer::intValue).toArray();
} }
@Override @Override
public GameState play(int move) { public PlayResult play(int move) {
assert move >= 0 && move < this.getBoard().length; assert move >= 0 && move < this.getBoard().length;
// TODO: Make sure this move is allowed, maybe on the board side? // TODO: Make sure this move is allowed, maybe on the board side?
this.setBoard(move); this.setBoard(move);
movesLeft--; movesLeft--;
nextTurn(); int t = checkForWin();
if (t != -1) {
if (checkForWin()) { return new PlayResult(GameState.WIN, t);
return GameState.WIN;
} }
if (movesLeft <= 2) { if (movesLeft <= 2) {
if (movesLeft <= 0 || checkForEarlyDraw()) { 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 // Horizontal
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
final int index = i * 3; final int index = i * 3;
if (!Objects.equals(this.getBoard()[index], EMPTY) if (!Objects.equals(this.getBoard()[index], EMPTY)
&& Objects.equals(this.getBoard()[index], this.getBoard()[index + 1]) && Objects.equals(this.getBoard()[index], this.getBoard()[index + 1])
&& Objects.equals(this.getBoard()[index], this.getBoard()[index + 2])) { && Objects.equals(this.getBoard()[index], this.getBoard()[index + 2])) {
return true; return this.getBoard()[index];
} }
} }
// Vertical // Vertical
for (int i = 0; i < 3; i++) { 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])) { 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 // 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])) { 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 // 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() { private boolean checkForEarlyDraw() {
for (final int move : this.getLegalMoves()) { for (final int move : this.getLegalMoves()) {
final TicTacToeR copy = this.clone(); final TicTacToeR copy = this.clone();
if (copy.play(move) == GameState.WIN || !copy.checkForEarlyDraw()) { if (copy.play(move).state() == GameState.WIN || !copy.checkForEarlyDraw()) {
return false; return false;
} }
} }
@@ -96,10 +99,6 @@ public final class TicTacToeR extends TurnBasedGameR {
return true; return true;
} }
private char getCurrentValue() {
return this.getCurrentTurn() == 0 ? 'X' : 'O';
}
@Override @Override
public TicTacToeR clone() { public TicTacToeR clone() {
return new TicTacToeR(this); return new TicTacToeR(this);