diff --git a/app/src/main/java/org/toop/app/game/LocalPlayer.java b/app/src/main/java/org/toop/app/game/LocalPlayer.java new file mode 100644 index 0000000..6b11ac3 --- /dev/null +++ b/app/src/main/java/org/toop/app/game/LocalPlayer.java @@ -0,0 +1,24 @@ +package org.toop.app.game; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public class LocalPlayer extends Player{ + private BlockingQueue queue = new LinkedBlockingQueue(); + + public LocalPlayer() {} + + @Override + public int getMove() { + try { + return queue.take(); + }catch (InterruptedException e){ + return -1; + } + } + + public void enqueueMove(int move) { + System.out.println(move); + queue.offer(move); + } +} diff --git a/app/src/main/java/org/toop/app/game/MoveBehaviour.java b/app/src/main/java/org/toop/app/game/MoveBehaviour.java new file mode 100644 index 0000000..aa8a7a7 --- /dev/null +++ b/app/src/main/java/org/toop/app/game/MoveBehaviour.java @@ -0,0 +1,8 @@ +package org.toop.app.game; + +import org.toop.game.records.Move; + +public interface MoveBehaviour +{ + int getMove(); +} diff --git a/app/src/main/java/org/toop/app/game/Player.java b/app/src/main/java/org/toop/app/game/Player.java new file mode 100644 index 0000000..33d5689 --- /dev/null +++ b/app/src/main/java/org/toop/app/game/Player.java @@ -0,0 +1,6 @@ +package org.toop.app.game; + +import org.toop.game.records.Move; + +public abstract class Player implements MoveBehaviour{ +} diff --git a/app/src/main/java/org/toop/app/game/TurnBasedGameThread.java b/app/src/main/java/org/toop/app/game/TurnBasedGameThread.java new file mode 100644 index 0000000..2a04fd0 --- /dev/null +++ b/app/src/main/java/org/toop/app/game/TurnBasedGameThread.java @@ -0,0 +1,92 @@ +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.widget.WidgetContainer; +import org.toop.app.widget.view.GameView; +import org.toop.game.TurnBasedGameR; +import org.toop.game.enumerators.GameState; +import org.toop.game.records.Move; +import org.toop.game.tictactoe.TicTacToe; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +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(); + + // TODO: Seperate this from game Thread + private final GameView primary = new GameView(null, null, null); + private final TicTacToeCanvas canvas; + + public TurnBasedGameThread(Player[] players, TurnBasedGameR game) { + // Make sure player list matches expected size + if (players.length != game.getPlayerCount()){ + throw new IllegalArgumentException("players and game's players must have same length"); + } + + this.players = players; + this.game = game; + + 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); + + } + + // Move to UI shiz + private void drawMove(int move) { + if (game.getCurrentTurn() == 1) canvas.drawX(Color.RED, move); + else canvas.drawO(Color.BLUE, move); + } + + public void run() { + isRunning.set(true); + + // Game logic loop + while(isRunning.get()) { + + // Get current player + Player currentPlayer = players[game.getCurrentTurn()]; + + // Get this player's valid moves + Integer[] validMoves = game.getLegalMoves(); + + // Get player's move, reask if Move is invalid + // TODO: Limit amount of retries? + int move = currentPlayer.getMove(); + while (!Arrays.asList(validMoves).contains(move)) { + System.out.println("Invalid move");; + move = currentPlayer.getMove(); + } + + // Make move + GameState state = game.play(move); + drawMove(move); + + if (state != GameState.NORMAL) { + if (state == GameState.WIN) { + // Someone won + } else if (state == GameState.DRAW) { + // THere was a draw + } + isRunning.set(false); + } + } + } + + private 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 9ddd5f3..439b00b 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 @@ -1,13 +1,14 @@ package org.toop.app.widget.view; import org.toop.app.GameInformation; -import org.toop.app.game.Connect4Game; -import org.toop.app.game.ReversiGame; -import org.toop.app.game.TicTacToeGameThread; +import org.toop.app.game.*; import org.toop.app.widget.Primitive; import org.toop.app.widget.complex.PlayerInfoWidget; import org.toop.app.widget.complex.ViewWidget; import org.toop.app.widget.popup.ErrorPopup; +import org.toop.game.tictactoe.TicTacToe; +import org.toop.game.tictactoe.TicTacToeAI; +import org.toop.game.tictactoe.TicTacToeR; import org.toop.local.AppContext; import javafx.geometry.Pos; @@ -32,7 +33,7 @@ public class LocalMultiplayerView extends ViewWidget { } switch (information.type) { - case TICTACTOE -> new TicTacToeGameThread(information); + case TICTACTOE -> new TurnBasedGameThread(new Player[]{new LocalPlayer(), new LocalPlayer()}, new TicTacToeR()); 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/GameR.java b/game/src/main/java/org/toop/game/GameR.java new file mode 100644 index 0000000..e21f6a3 --- /dev/null +++ b/game/src/main/java/org/toop/game/GameR.java @@ -0,0 +1,41 @@ +package org.toop.game; + +import org.toop.game.interfaces.IPlayable; +import org.toop.game.interfaces.IPlayableR; +import org.toop.game.records.Move; + +import java.util.Arrays; + +public abstract class GameR implements IPlayableR { + + public static final Integer EMPTY = null; // Constant + + private final int rowSize; + private final int columnSize; + private final Integer[] board; + + protected GameR(int rowSize, int columnSize) { + assert rowSize > 0 && columnSize > 0; + + this.rowSize = rowSize; + this.columnSize = columnSize; + + board = new Integer[rowSize * columnSize]; + Arrays.fill(board, EMPTY); + } + + protected GameR(GameR other) { + rowSize = other.rowSize; + columnSize = other.columnSize; + board = Arrays.copyOf(other.board, other.board.length); + } + + public int getRowSize() {return this.rowSize;} + + public int getColumnSize() {return this.columnSize;} + + public Integer[] getBoard() {return this.board;} + + protected void setBoard(int position, int player){this.board[position] = player;} + +} diff --git a/game/src/main/java/org/toop/game/TurnBasedGameR.java b/game/src/main/java/org/toop/game/TurnBasedGameR.java new file mode 100644 index 0000000..702a516 --- /dev/null +++ b/game/src/main/java/org/toop/game/TurnBasedGameR.java @@ -0,0 +1,31 @@ +package org.toop.game; + +public abstract class TurnBasedGameR extends GameR { + private final int playerCount; // How many players are playing + private int turn = 0; // What turn it is in the game + + protected TurnBasedGameR(int rowSize, int columnSize, int playerCount) { + super(rowSize, columnSize); + this.playerCount = playerCount; + } + + protected TurnBasedGameR(TurnBasedGameR other) { + super(other); + playerCount = other.playerCount; + turn = other.turn; + } + + public int getPlayerCount(){return this.playerCount;} + + protected void nextTurn() { + turn += 1; + } + + public int getCurrentTurn() { + return turn % playerCount; + } + + protected void setBoard(int position) { + super.setBoard(position, getCurrentTurn()); + } +} diff --git a/game/src/main/java/org/toop/game/interfaces/IPlayableR.java b/game/src/main/java/org/toop/game/interfaces/IPlayableR.java new file mode 100644 index 0000000..f360a4d --- /dev/null +++ b/game/src/main/java/org/toop/game/interfaces/IPlayableR.java @@ -0,0 +1,9 @@ +package org.toop.game.interfaces; + +import org.toop.game.enumerators.GameState; +import org.toop.game.records.Move; + +public interface IPlayableR { + Integer[] getLegalMoves(); + GameState play(int move); +} diff --git a/game/src/main/java/org/toop/game/tictactoe/TicTacToeR.java b/game/src/main/java/org/toop/game/tictactoe/TicTacToeR.java new file mode 100644 index 0000000..cc09e3c --- /dev/null +++ b/game/src/main/java/org/toop/game/tictactoe/TicTacToeR.java @@ -0,0 +1,103 @@ +package org.toop.game.tictactoe; + +import org.toop.game.TurnBasedGame; +import org.toop.game.TurnBasedGameR; +import org.toop.game.enumerators.GameState; +import org.toop.game.records.Move; + +import java.util.ArrayList; +import java.util.Objects; + +public final class TicTacToeR extends TurnBasedGameR { + private int movesLeft; + + public TicTacToeR() { + super(3, 3, 2); + movesLeft = this.getBoard().length; + } + + public TicTacToeR(TicTacToeR other) { + super(other); + movesLeft = other.movesLeft; + } + + @Override + public Integer[] 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); + } + } + + return legalMoves.toArray(new Integer[0]); + } + + @Override + public GameState 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; + } + + if (movesLeft <= 2) { + if (movesLeft <= 0 || checkForEarlyDraw()) { + return GameState.DRAW; + } + } + + return GameState.NORMAL; + } + + private boolean 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; + } + } + + // 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; + } + } + + // 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; + } + + // 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]); + } + + private boolean checkForEarlyDraw() { + for (final int move : this.getLegalMoves()) { + final TicTacToeR copy = new TicTacToeR(this); + + if (copy.play(move) == GameState.WIN || !copy.checkForEarlyDraw()) { + return false; + } + } + + return true; + } + + private char getCurrentValue() { + return this.getCurrentTurn() == 0 ? 'X' : 'O'; + } +}