From d24feef73e0ba05357776d24e2a607d8be4aa849 Mon Sep 17 00:00:00 2001 From: ramollia <> Date: Thu, 27 Nov 2025 16:53:58 +0100 Subject: [PATCH] readd previous game thread code --- app/src/main/java/org/toop/app/Server.java | 12 +- .../java/org/toop/app/game/Connect4Game.java | 266 ++++++++++++++ .../java/org/toop/app/game/ReversiGame.java | 341 ++++++++++++++++++ .../java/org/toop/app/game/TicTacToeGame.java | 250 +++++++++++++ .../app/widget/view/LocalMultiplayerView.java | 19 +- 5 files changed, 872 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/org/toop/app/game/Connect4Game.java create mode 100644 app/src/main/java/org/toop/app/game/ReversiGame.java create mode 100644 app/src/main/java/org/toop/app/game/TicTacToeGame.java diff --git a/app/src/main/java/org/toop/app/Server.java b/app/src/main/java/org/toop/app/Server.java index 9df2e2d..00f91cc 100644 --- a/app/src/main/java/org/toop/app/Server.java +++ b/app/src/main/java/org/toop/app/Server.java @@ -1,8 +1,8 @@ package org.toop.app; -import org.toop.app.game.Connect4GameThread; -import org.toop.app.game.ReversiGameThread; -import org.toop.app.game.TicTacToeGameThread; +import org.toop.app.game.Connect4Game; +import org.toop.app.game.ReversiGame; +import org.toop.app.game.TicTacToeGame; import org.toop.app.widget.WidgetContainer; import org.toop.app.widget.popup.ChallengePopup; import org.toop.app.widget.popup.ErrorPopup; @@ -131,11 +131,11 @@ public final class Server { switch (type) { case TICTACTOE -> - new TicTacToeGameThread(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); + new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); case REVERSI -> - new ReversiGameThread(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); + new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); case CONNECT4 -> - new Connect4GameThread(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); + new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); default -> new ErrorPopup("Unsupported game type."); } } diff --git a/app/src/main/java/org/toop/app/game/Connect4Game.java b/app/src/main/java/org/toop/app/game/Connect4Game.java new file mode 100644 index 0000000..9fe006d --- /dev/null +++ b/app/src/main/java/org/toop/app/game/Connect4Game.java @@ -0,0 +1,266 @@ +package org.toop.app.game; + +import javafx.geometry.Pos; +import javafx.scene.paint.Color; +import org.toop.app.App; +import org.toop.app.GameInformation; +import org.toop.app.canvas.Connect4Canvas; +import org.toop.app.widget.WidgetContainer; +import org.toop.app.widget.view.GameView; +import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.networking.events.NetworkEvents; +import org.toop.game.Connect4.Connect4; +import org.toop.game.Connect4.Connect4AI; +import org.toop.game.enumerators.GameState; +import org.toop.game.records.Move; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +public class Connect4Game { + private final GameInformation information; + + private final int myTurn; + private Runnable onGameOver; + private final BlockingQueue moveQueue; + + private final Connect4 game; + private final Connect4AI ai; + private final int columnSize = 7; + private final int rowSize = 6; + + private final GameView primary; + private final Connect4Canvas canvas; + + private final AtomicBoolean isRunning; + + public Connect4Game(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage, Runnable onGameOver) { + this.information = information; + this.myTurn = myTurn; + this.onGameOver = onGameOver; + moveQueue = new LinkedBlockingQueue(); + + + game = new Connect4(); + ai = new Connect4AI(); + + isRunning = new AtomicBoolean(true); + + if (onForfeit == null || onExit == null) { + primary = new GameView(null, () -> { + isRunning.set(false); + WidgetContainer.getCurrentView().transitionPrevious(); + }, null); + } else { + primary = new GameView(onForfeit, () -> { + isRunning.set(false); + onExit.run(); + }, onMessage); + } + + canvas = new Connect4Canvas(Color.GRAY, + (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3, + (cell) -> { + if (onForfeit == null || onExit == null) { + if (information.players[game.getCurrentTurn()].isHuman) { + final char value = game.getCurrentTurn() == 0? 'X' : 'O'; + + try { + moveQueue.put(new Move(cell%columnSize, value)); + } catch (InterruptedException _) {} + } + } else { + if (information.players[0].isHuman) { + final char value = myTurn == 0? 'X' : 'O'; + + try { + moveQueue.put(new Move(cell%columnSize, value)); + } catch (InterruptedException _) {} + } + } + }); + + primary.add(Pos.CENTER, canvas.getCanvas()); + WidgetContainer.getCurrentView().transitionNext(primary); + + if (onForfeit == null || onExit == null) { + new Thread(this::localGameThread).start(); + setGameLabels(information.players[0].isHuman); + } else { + new EventFlow() + .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse) + .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse); + + setGameLabels(myTurn == 0); + } + + updateCanvas(); + } + + public Connect4Game(GameInformation information) { + this(information, 0, null, null, null, null); + } + private void localGameThread() { + while (isRunning.get()) { + final int currentTurn = game.getCurrentTurn(); + final String currentValue = currentTurn == 0? "RED" : "BLUE"; + final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount(); + + primary.nextPlayer(information.players[currentTurn].isHuman, + information.players[currentTurn].name, + currentValue, + information.players[nextTurn].name); + + Move move = null; + + if (information.players[currentTurn].isHuman) { + try { + final Move wants = moveQueue.take(); + final Move[] legalMoves = game.getLegalMoves(); + + for (final Move legalMove : legalMoves) { + if (legalMove.position() == wants.position() && + legalMove.value() == wants.value()) { + move = wants; + break; + } + } + } catch (InterruptedException _) {} + } else { + final long start = System.currentTimeMillis(); + + move = ai.findBestMove(game, information.players[currentTurn].computerDifficulty); + + if (information.players[currentTurn].computerThinkTime > 0) { + final long elapsedTime = System.currentTimeMillis() - start; + final long sleepTime = Math.abs(information.players[currentTurn].computerThinkTime * 1000L - elapsedTime); + + try { + Thread.sleep((long)(sleepTime * Math.random())); + } catch (InterruptedException _) {} + } + } + + if (move == null) { + continue; + } + + final GameState state = game.play(move); + updateCanvas(); +/* + if (move.value() == 'X') { + canvas.drawX(Color.INDIANRED, move.position()); + } else if (move.value() == 'O') { + canvas.drawO(Color.ROYALBLUE, move.position()); + } +*/ + if (state != GameState.NORMAL) { + if (state == GameState.WIN) { + primary.gameOver(true, information.players[currentTurn].name); + } else if (state == GameState.DRAW) { + primary.gameOver(false, ""); + } + + isRunning.set(false); + } + } + } + + private void onMoveResponse(NetworkEvents.GameMoveResponse response) { + if (!isRunning.get()) { + return; + } + + char playerChar; + + if (response.player().equalsIgnoreCase(information.players[0].name)) { + playerChar = myTurn == 0? 'X' : 'O'; + } else { + playerChar = myTurn == 0? 'O' : 'X'; + } + + final Move move = new Move(Integer.parseInt(response.move()), playerChar); + final GameState state = game.play(move); + + if (state != GameState.NORMAL) { + if (state == GameState.WIN) { + if (response.player().equalsIgnoreCase(information.players[0].name)) { + primary.gameOver(true, information.players[0].name); + gameOver(); + } else { + primary.gameOver(false, information.players[1].name); + gameOver(); + } + } else if (state == GameState.DRAW) { + primary.gameOver(false, ""); + gameOver(); + } + } + + if (move.value() == 'X') { + canvas.drawDot(Color.INDIANRED, move.position()); + } else if (move.value() == 'O') { + canvas.drawDot(Color.ROYALBLUE, move.position()); + } + + updateCanvas(); + setGameLabels(game.getCurrentTurn() == myTurn); + } + + private void gameOver() { + if (onGameOver == null){ + return; + } + isRunning.set(false); + onGameOver.run(); + } + + private void onYourTurnResponse(NetworkEvents.YourTurnResponse response) { + + if (!isRunning.get()) { + return; + } + + moveQueue.clear(); + + int position = -1; + + if (information.players[0].isHuman) { + try { + position = moveQueue.take().position(); + } catch (InterruptedException _) {} + } else { + final Move move = ai.findBestMove(game, information.players[0].computerDifficulty); + + assert move != null; + position = move.position(); + } + + new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short)position)) + .postEvent(); + } + + private void updateCanvas() { + canvas.clearAll(); + + for (int i = 0; i < game.getBoard().length; i++) { + if (game.getBoard()[i] == 'X') { + canvas.drawDot(Color.RED, i); + } else if (game.getBoard()[i] == 'O') { + canvas.drawDot(Color.BLUE, i); + } + } + } + + private void setGameLabels(boolean isMe) { + final int currentTurn = game.getCurrentTurn(); + final String currentValue = currentTurn == 0? "RED" : "BLUE"; + + primary.nextPlayer(isMe, + information.players[isMe? 0 : 1].name, + currentValue, + information.players[isMe? 1 : 0].name); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/game/ReversiGame.java b/app/src/main/java/org/toop/app/game/ReversiGame.java new file mode 100644 index 0000000..fb0e69e --- /dev/null +++ b/app/src/main/java/org/toop/app/game/ReversiGame.java @@ -0,0 +1,341 @@ +package org.toop.app.game; + +import javafx.animation.SequentialTransition; +import org.toop.app.App; +import org.toop.app.GameInformation; +import org.toop.app.canvas.ReversiCanvas; +import org.toop.app.widget.WidgetContainer; +import org.toop.app.widget.view.GameView; +import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.networking.events.NetworkEvents; +import org.toop.game.enumerators.GameState; +import org.toop.game.records.Move; +import org.toop.game.reversi.Reversi; +import org.toop.game.reversi.ReversiAI; + +import javafx.geometry.Pos; +import javafx.scene.paint.Color; + +import java.awt.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +public final class ReversiGame { + private final GameInformation information; + + private final int myTurn; + private Runnable onGameOver; + private final BlockingQueue moveQueue; + + private final Reversi game; + private final ReversiAI ai; + + private final GameView primary; + private final ReversiCanvas canvas; + + private final AtomicBoolean isRunning; + private final AtomicBoolean isPaused; + + public ReversiGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage, Runnable onGameOver) { + this.information = information; + + this.myTurn = myTurn; + this.onGameOver = onGameOver; + moveQueue = new LinkedBlockingQueue(); + + game = new Reversi(); + ai = new ReversiAI(); + + isRunning = new AtomicBoolean(true); + isPaused = new AtomicBoolean(false); + + if (onForfeit == null || onExit == null) { + primary = new GameView(null, () -> { + isRunning.set(false); + WidgetContainer.getCurrentView().transitionPrevious(); + }, null); + } else { + primary = new GameView(onForfeit, () -> { + isRunning.set(false); + onExit.run(); + }, onMessage); + } + + canvas = new ReversiCanvas(Color.BLACK, + (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3, + (cell) -> { + if (onForfeit == null || onExit == null) { + if (information.players[game.getCurrentTurn()].isHuman) { + final char value = game.getCurrentTurn() == 0 ? 'B' : 'W'; + + try { + moveQueue.put(new Move(cell, value)); + } catch (InterruptedException _) { + } + } + } else { + if (information.players[0].isHuman) { + final char value = myTurn == 0 ? 'B' : 'W'; + + try { + moveQueue.put(new Move(cell, value)); + } catch (InterruptedException _) { + } + } + } + }); + + canvas.setOnCellEntered(this::highlightCells); + + primary.add(Pos.CENTER, canvas.getCanvas()); + WidgetContainer.getCurrentView().transitionNext(primary); + + if (onForfeit == null || onExit == null) { + new Thread(this::localGameThread).start(); + setGameLabels(information.players[0].isHuman); + } else { + new EventFlow() + .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse) + .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse); + + setGameLabels(myTurn == 0); + } + + updateCanvas(false); + } + + public ReversiGame(GameInformation information) { + this(information, 0, null, null, null, null); + } + + private void localGameThread() { + while (isRunning.get()) { + if (isPaused.get()) { + try { + Thread.sleep(200); + } catch (InterruptedException _) { + } + + continue; + } + + final int currentTurn = game.getCurrentTurn(); + final String currentValue = currentTurn == 0 ? "BLACK" : "WHITE"; + final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount(); + + primary.nextPlayer(information.players[currentTurn].isHuman, + information.players[currentTurn].name, + currentValue, + information.players[nextTurn].name); + + Move move = null; + + if (information.players[currentTurn].isHuman) { + try { + final Move wants = moveQueue.take(); + final Move[] legalMoves = game.getLegalMoves(); + + for (final Move legalMove : legalMoves) { + if (legalMove.position() == wants.position() && + legalMove.value() == wants.value()) { + move = wants; + break; + } + } + } catch (InterruptedException _) { + } + } else { + final long start = System.currentTimeMillis(); + + move = ai.findBestMove(game, information.players[currentTurn].computerDifficulty); + + if (information.players[currentTurn].computerThinkTime > 0) { + final long elapsedTime = System.currentTimeMillis() - start; + final long sleepTime = information.players[currentTurn].computerThinkTime * 1000L - elapsedTime; + + try { + Thread.sleep((long) (sleepTime * Math.random())); + } catch (InterruptedException _) { + } + } + } + + if (move == null) { + continue; + } + + canvas.setCurrentlyHighlightedMovesNull(); + final GameState state = game.play(move); + updateCanvas(true); + + if (state != GameState.NORMAL) { + if (state == GameState.TURN_SKIPPED) { + continue; + } + int winningPLayerNumber = getPlayerNumberWithHighestScore(); + if (state == GameState.WIN && winningPLayerNumber > -1) { + primary.gameOver(true, information.players[winningPLayerNumber].name); + } else if (state == GameState.DRAW || winningPLayerNumber == -1) { + primary.gameOver(false, ""); + } + + isRunning.set(false); + } + } + } + + private int getPlayerNumberWithHighestScore() { + Reversi.Score score = game.getScore(); + if (score.player1Score() > score.player2Score()) return 0; + if (score.player1Score() < score.player2Score()) return 1; + return -1; + } + + private void onMoveResponse(NetworkEvents.GameMoveResponse response) { + if (!isRunning.get()) { + return; + } + + char playerChar; + + if (response.player().equalsIgnoreCase(information.players[0].name)) { + playerChar = myTurn == 0 ? 'B' : 'W'; + } else { + playerChar = myTurn == 0 ? 'W' : 'B'; + } + + final Move move = new Move(Integer.parseInt(response.move()), playerChar); + final GameState state = game.play(move); + + if (state != GameState.NORMAL) { + if (state == GameState.WIN) { + if (response.player().equalsIgnoreCase(information.players[0].name)) { + primary.gameOver(true, information.players[0].name); + gameOver(); + } else { + primary.gameOver(false, information.players[1].name); + gameOver(); + } + } else if (state == GameState.DRAW) { + primary.gameOver(false, ""); + game.play(move); + } + } + + updateCanvas(false); + setGameLabels(game.getCurrentTurn() == myTurn); + } + + private void gameOver() { + if (onGameOver == null) { + return; + } + isRunning.set(false); + onGameOver.run(); + } + + private void onYourTurnResponse(NetworkEvents.YourTurnResponse response) { + if (!isRunning.get()) { + return; + } + + moveQueue.clear(); + + int position = -1; + + if (information.players[0].isHuman) { + try { + position = moveQueue.take().position(); + } catch (InterruptedException _) { + } + } else { + final Move move = ai.findBestMove(game, information.players[0].computerDifficulty); + + assert move != null; + position = move.position(); + } + + new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short) position)) + .postEvent(); + } + + 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] == 'B') { + canvas.drawDot(Color.BLACK, i); + } else if (game.getBoard()[i] == 'W') { + canvas.drawDot(Color.WHITE, i); + } + } + + final Move[] flipped = game.getMostRecentlyFlippedPieces(); + + final SequentialTransition animation = new SequentialTransition(); + isPaused.set(true); + + if (animate && flipped != null) { + for (final Move flip : flipped) { + canvas.clear(flip.position()); + + final Color from = flip.value() == 'W' ? Color.BLACK : Color.WHITE; + final Color to = flip.value() == 'W' ? Color.WHITE : Color.BLACK; + + canvas.drawDot(from, flip.position()); + + animation.getChildren().addFirst(canvas.flipDot(from, to, flip.position())); + } + } + + animation.setOnFinished(_ -> { + isPaused.set(false); + + if (information.players[game.getCurrentTurn()].isHuman) { + final Move[] legalMoves = game.getLegalMoves(); + + for (final Move legalMove : legalMoves) { + canvas.drawLegalPosition(legalMove.position(), game.getCurrentPlayer()); + } + } + }); + + animation.play(); + } + + private void setGameLabels(boolean isMe) { + final int currentTurn = game.getCurrentTurn(); + final String currentValue = currentTurn == 0 ? "BLACK" : "WHITE"; + + primary.nextPlayer(isMe, + information.players[isMe ? 0 : 1].name, + currentValue, + information.players[isMe ? 1 : 0].name); + } + + private void highlightCells(int cellEntered) { + if (information.players[game.getCurrentTurn()].isHuman) { + Move[] legalMoves = game.getLegalMoves(); + boolean isLegalMove = false; + for (Move move : legalMoves) { + if (move.position() == cellEntered) { + isLegalMove = true; + break; + } + } + + if (cellEntered >= 0) { + Move[] moves = null; + if (isLegalMove) { + moves = game.getFlipsForPotentialMove( + new Point(cellEntered % game.getColumnSize(), cellEntered / game.getRowSize()), + game.getCurrentPlayer()); + } + canvas.drawHighlightDots(moves); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/game/TicTacToeGame.java b/app/src/main/java/org/toop/app/game/TicTacToeGame.java new file mode 100644 index 0000000..f87b98f --- /dev/null +++ b/app/src/main/java/org/toop/app/game/TicTacToeGame.java @@ -0,0 +1,250 @@ +package org.toop.app.game; + +import org.toop.app.App; +import org.toop.app.GameInformation; +import org.toop.app.canvas.TicTacToeCanvas; +import org.toop.app.widget.WidgetContainer; +import org.toop.app.widget.view.GameView; +import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.networking.events.NetworkEvents; +import org.toop.game.enumerators.GameState; +import org.toop.game.records.Move; +import org.toop.game.tictactoe.TicTacToe; +import org.toop.game.tictactoe.TicTacToeAI; + +import javafx.geometry.Pos; +import javafx.scene.paint.Color; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +public final class TicTacToeGame { + private final GameInformation information; + + private final int myTurn; + private Runnable onGameOver; + private final BlockingQueue moveQueue; + + private final TicTacToe game; + private final TicTacToeAI ai; + + private final GameView primary; + private final TicTacToeCanvas canvas; + + private final AtomicBoolean isRunning; + + public TicTacToeGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage, Runnable onGameOver) { + this.information = information; + + this.myTurn = myTurn; + this.onGameOver = onGameOver; + moveQueue = new LinkedBlockingQueue(); + + game = new TicTacToe(); + ai = new TicTacToeAI(); + + isRunning = new AtomicBoolean(true); + + if (onForfeit == null || onExit == null) { + primary = new GameView(null, () -> { + isRunning.set(false); + WidgetContainer.getCurrentView().transitionPrevious(); + }, null); + } else { + primary = new GameView(onForfeit, () -> { + isRunning.set(false); + onExit.run(); + }, onMessage); + } + + canvas = new TicTacToeCanvas(Color.GRAY, + (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3, + (cell) -> { + if (onForfeit == null || onExit == null) { + if (information.players[game.getCurrentTurn()].isHuman) { + final char value = game.getCurrentTurn() == 0? 'X' : 'O'; + + try { + moveQueue.put(new Move(cell, value)); + } catch (InterruptedException _) {} + } + } else { + if (information.players[0].isHuman) { + final char value = myTurn == 0? 'X' : 'O'; + + try { + moveQueue.put(new Move(cell, value)); + } catch (InterruptedException _) {} + } + } + }); + + primary.add(Pos.CENTER, canvas.getCanvas()); + WidgetContainer.getCurrentView().transitionNext(primary); + + if (onForfeit == null || onExit == null) { + new Thread(this::localGameThread).start(); + } else { + new EventFlow() + .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse) + .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse); + + setGameLabels(myTurn == 0); + } + } + + public TicTacToeGame(GameInformation information) { + this(information, 0, null, null, null, null); + } + + private void localGameThread() { + while (isRunning.get()) { + final int currentTurn = game.getCurrentTurn(); + final String currentValue = currentTurn == 0? "X" : "O"; + final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount(); + + primary.nextPlayer(information.players[currentTurn].isHuman, + information.players[currentTurn].name, + currentValue, + information.players[nextTurn].name); + + Move move = null; + + if (information.players[currentTurn].isHuman) { + try { + final Move wants = moveQueue.take(); + final Move[] legalMoves = game.getLegalMoves(); + + for (final Move legalMove : legalMoves) { + if (legalMove.position() == wants.position() && + legalMove.value() == wants.value()) { + move = wants; + break; + } + } + } catch (InterruptedException _) {} + } else { + final long start = System.currentTimeMillis(); + + move = ai.findBestMove(game, information.players[currentTurn].computerDifficulty); + + if (information.players[currentTurn].computerThinkTime > 0) { + final long elapsedTime = System.currentTimeMillis() - start; + final long sleepTime = information.players[currentTurn].computerThinkTime * 1000L - elapsedTime; + + try { + Thread.sleep((long)(sleepTime * Math.random())); + } catch (InterruptedException _) {} + } + } + + if (move == null) { + continue; + } + + final GameState state = game.play(move); + + if (move.value() == 'X') { + canvas.drawX(Color.INDIANRED, move.position()); + } else if (move.value() == 'O') { + canvas.drawO(Color.ROYALBLUE, move.position()); + } + + if (state != GameState.NORMAL) { + if (state == GameState.WIN) { + primary.gameOver(true, information.players[currentTurn].name); + } else if (state == GameState.DRAW) { + primary.gameOver(false, ""); + } + + isRunning.set(false); + } + } + } + + private void onMoveResponse(NetworkEvents.GameMoveResponse response) { + if (!isRunning.get()) { + return; + } + + char playerChar; + + if (response.player().equalsIgnoreCase(information.players[0].name)) { + playerChar = myTurn == 0? 'X' : 'O'; + } else { + playerChar = myTurn == 0? 'O' : 'X'; + } + + final Move move = new Move(Integer.parseInt(response.move()), playerChar); + final GameState state = game.play(move); + + if (state != GameState.NORMAL) { + if (state == GameState.WIN) { + if (response.player().equalsIgnoreCase(information.players[0].name)) { + primary.gameOver(true, information.players[0].name); + gameOver(); + } else { + primary.gameOver(false, information.players[1].name); + gameOver(); + } + } else if (state == GameState.DRAW) { + if(game.getLegalMoves().length == 0) { //only return draw in online multiplayer if the game is actually over. + primary.gameOver(false, ""); + gameOver(); + } + } + } + + if (move.value() == 'X') { + canvas.drawX(Color.RED, move.position()); + } else if (move.value() == 'O') { + canvas.drawO(Color.BLUE, move.position()); + } + + setGameLabels(game.getCurrentTurn() == myTurn); + } + + private void gameOver() { + if (onGameOver == null){ + return; + } + isRunning.set(false); + onGameOver.run(); + } + + private void onYourTurnResponse(NetworkEvents.YourTurnResponse response) { + if (!isRunning.get()) { + return; + } + + moveQueue.clear(); + + int position = -1; + + if (information.players[0].isHuman) { + try { + position = moveQueue.take().position(); + } catch (InterruptedException _) {} + } else { + final Move move; + move = ai.findBestMove(game, information.players[0].computerDifficulty); + assert move != null; + position = move.position(); + } + + new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short)position)) + .postEvent(); + } + + private void setGameLabels(boolean isMe) { + final int currentTurn = game.getCurrentTurn(); + final String currentValue = currentTurn == 0? "X" : "O"; + + primary.nextPlayer(isMe, + information.players[isMe? 0 : 1].name, + currentValue, + information.players[isMe? 1 : 0].name); + } +} \ No newline at end of file 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 6ff3fe7..614e9d4 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,19 +1,18 @@ package org.toop.app.widget.view; +import javafx.geometry.Pos; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.VBox; import org.toop.app.GameInformation; -import org.toop.app.game.Connect4GameThread; -import org.toop.app.game.ReversiGameThread; -import org.toop.app.game.TicTacToeGameThread; +import org.toop.app.game.Connect4Game; +import org.toop.app.game.ReversiGame; +import org.toop.app.game.TicTacToeGame; 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.local.AppContext; -import javafx.geometry.Pos; -import javafx.scene.control.ScrollPane; -import javafx.scene.layout.VBox; - public class LocalMultiplayerView extends ViewWidget { private final GameInformation information; @@ -32,9 +31,9 @@ public class LocalMultiplayerView extends ViewWidget { } switch (information.type) { - case TICTACTOE -> new TicTacToeGameThread(information); - case REVERSI -> new ReversiGameThread(information); - case CONNECT4 -> new Connect4GameThread(information); + case TICTACTOE -> new TicTacToeGame(information); + case REVERSI -> new ReversiGame(information); + case CONNECT4 -> new Connect4Game(information); // case BATTLESHIP -> new BattleshipGame(information); } });