diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java index 2a36d69..16ae194 100644 --- a/app/src/main/java/org/toop/Main.java +++ b/app/src/main/java/org/toop/Main.java @@ -1,45 +1,12 @@ package org.toop; import org.toop.app.App; -import org.toop.framework.audio.*; import org.toop.framework.machinelearning.NeuralNetwork; -import org.toop.framework.networking.NetworkingClientEventListener; -import org.toop.framework.networking.NetworkingClientManager; -import org.toop.framework.resource.ResourceLoader; -import org.toop.framework.resource.ResourceManager; -import org.toop.framework.resource.resources.MusicAsset; -import org.toop.framework.resource.resources.SoundEffectAsset; public final class Main { static void main(String[] args) { - initSystems(); App.run(args); - } - - private static void initSystems() { - ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets")); - new Thread(() -> new NetworkingClientEventListener(new NetworkingClientManager())).start(); - - new Thread(() -> { - MusicManager musicManager = new MusicManager<>(ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class), true); - SoundEffectManager soundEffectManager = new SoundEffectManager<>(ResourceManager.getAllOfType(SoundEffectAsset.class)); - AudioVolumeManager audioVolumeManager = new AudioVolumeManager() - .registerManager(VolumeControl.MASTERVOLUME, musicManager) - .registerManager(VolumeControl.MASTERVOLUME, soundEffectManager) - .registerManager(VolumeControl.FX, soundEffectManager) - .registerManager(VolumeControl.MUSIC, musicManager); - - new AudioEventListener<>( - musicManager, - soundEffectManager, - audioVolumeManager - ).initListeners("medium-button-click.wav"); - - }).start(); - - NeuralNetwork nn = new NeuralNetwork(); nn.init(); } - } diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java index 7b909ba..8809c26 100644 --- a/app/src/main/java/org/toop/app/App.java +++ b/app/src/main/java/org/toop/app/App.java @@ -1,13 +1,28 @@ package org.toop.app; +import javafx.application.Platform; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; + +import org.toop.app.widget.Primitive; import org.toop.app.widget.WidgetContainer; +import org.toop.app.widget.complex.LoadingWidget; import org.toop.app.widget.display.SongDisplay; +import org.toop.app.widget.popup.EscapePopup; import org.toop.app.widget.popup.QuitPopup; import org.toop.app.widget.view.MainView; +import org.toop.framework.audio.*; import org.toop.framework.audio.events.AudioEvents; import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.eventbus.GlobalEventBus; +import org.toop.framework.networking.NetworkingClientEventListener; +import org.toop.framework.networking.NetworkingClientManager; +import org.toop.framework.resource.ResourceLoader; import org.toop.framework.resource.ResourceManager; +import org.toop.framework.resource.events.AssetLoaderEvents; import org.toop.framework.resource.resources.CssAsset; +import org.toop.framework.resource.resources.MusicAsset; +import org.toop.framework.resource.resources.SoundEffectAsset; import org.toop.local.AppContext; import org.toop.local.AppSettings; @@ -17,6 +32,11 @@ import javafx.scene.Scene; import javafx.scene.layout.StackPane; import javafx.stage.Stage; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + public final class App extends Application { private static Stage stage; private static Scene scene; @@ -24,22 +44,26 @@ public final class App extends Application { private static int height; private static int width; - private static boolean isQuitting; - public static void run(String[] args) { launch(args); } @Override - public void start(Stage stage) throws Exception { + public void start(Stage stage) { + // Start loading localization + ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/localization")); + ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/style")); + final StackPane root = WidgetContainer.setup(); final Scene scene = new Scene(root); + stage.setOpacity(0.0); + stage.setTitle(AppContext.getString("app-title")); stage.titleProperty().bind(AppContext.bindToKey("app-title")); - stage.setWidth(1080); - stage.setHeight(720); + stage.setWidth(0); + stage.setHeight(0); scene.getRoot(); @@ -47,42 +71,179 @@ public final class App extends Application { stage.setMinHeight(720); stage.setOnCloseRequest(event -> { event.consume(); - startQuit(); + quit(); }); stage.setScene(scene); stage.setResizable(true); - stage.show(); - App.stage = stage; App.scene = scene; App.width = (int)stage.getWidth(); App.height = (int)stage.getHeight(); - App.isQuitting = false; - AppSettings.applySettings(); - new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent(); - WidgetContainer.add(Pos.CENTER, new MainView()); - WidgetContainer.add(Pos.BOTTOM_RIGHT, new SongDisplay()); + setKeybinds(root); + + LoadingWidget loading = new LoadingWidget(Primitive.text( + "Loading...", false), 0, 0, Integer.MAX_VALUE, false, false // Just set a high default + ); + + WidgetContainer.setCurrentView(loading); + + setOnLoadingSuccess(loading); + + EventFlow loadingFlow = new EventFlow(); + + final boolean[] hasRun = {false}; + loadingFlow + .listen(AssetLoaderEvents.LoadingProgressUpdate.class, e -> { + if (!hasRun[0]) { + hasRun[0] = true; + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + Platform.runLater(() -> stage.setOpacity(1.0)); + } + + Platform.runLater(() -> loading.setMaxAmount(e.isLoadingAmount())); + + Platform.runLater(() -> { + try { + loading.setAmount(e.hasLoadedAmount()); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + }); + + if (e.hasLoadedAmount() >= e.isLoadingAmount()) { + Platform.runLater(loading::triggerSuccess); + loadingFlow.unsubscribe("init_loading"); + } + + }, false, "init_loading"); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + executor.submit( + () -> ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets") + )); + } finally { + executor.shutdown(); + } + + stage.show(); + } - public static void startQuit() { - if (isQuitting) { + private void setKeybinds(StackPane root) { + root.addEventHandler(KeyEvent.KEY_PRESSED,event -> { + if (event.getCode() == KeyCode.ESCAPE) { + escapePopup(); + } + }); + } + + public void escapePopup() { + + if ( WidgetContainer.getCurrentView() == null + || WidgetContainer.getCurrentView() instanceof MainView) { return; } - WidgetContainer.add(Pos.CENTER, new QuitPopup()); - isQuitting = true; + if (!Objects.requireNonNull( + WidgetContainer.find(widget -> widget instanceof QuitPopup || widget instanceof EscapePopup) + ).isEmpty()) { + WidgetContainer.removeFirst(QuitPopup.class); + WidgetContainer.removeFirst(EscapePopup.class); + return; + } + + EscapePopup escPopup = new EscapePopup(); + escPopup.show(Pos.CENTER); } - public static void stopQuit() { - isQuitting = false; + private void setOnLoadingSuccess(LoadingWidget loading) { + loading.setOnSuccess(() -> { + + try { + initSystems(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + AppSettings.applyMusicVolumeSettings(); + new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).postEvent(); + loading.hide(); + WidgetContainer.add(Pos.CENTER, new MainView()); + WidgetContainer.add(Pos.BOTTOM_RIGHT, new SongDisplay()); + stage.setOnCloseRequest(event -> { + event.consume(); + + if (WidgetContainer.getAllWidgets().stream().anyMatch(e -> e instanceof QuitPopup)) return; + + QuitPopup a = new QuitPopup(); + a.show(Pos.CENTER); + + }); + }); } + private void initSystems() throws InterruptedException { // TODO Move to better place + + final int THREAD_COUNT = 2; + CountDownLatch latch = new CountDownLatch(THREAD_COUNT); + + @SuppressWarnings("resource") + ExecutorService threads = Executors.newFixedThreadPool(THREAD_COUNT); + + try { + + threads.submit(() -> { + new NetworkingClientEventListener( + GlobalEventBus.get(), + new NetworkingClientManager(GlobalEventBus.get())); + + latch.countDown(); + }); + + threads.submit(() -> { + MusicManager musicManager = + new MusicManager<>( + GlobalEventBus.get(), + ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class), + true + ); + + SoundEffectManager soundEffectManager = + new SoundEffectManager<>(ResourceManager.getAllOfType(SoundEffectAsset.class)); + + AudioVolumeManager audioVolumeManager = new AudioVolumeManager() + .registerManager(VolumeControl.MASTERVOLUME, musicManager) + .registerManager(VolumeControl.MASTERVOLUME, soundEffectManager) + .registerManager(VolumeControl.FX, soundEffectManager) + .registerManager(VolumeControl.MUSIC, musicManager); + + new AudioEventListener<>( + GlobalEventBus.get(), + musicManager, + soundEffectManager, + audioVolumeManager + ).initListeners("medium-button-click.wav"); + + latch.countDown(); + }); + + } finally { + latch.await(); + threads.shutdown(); + } + } + public static void quit() { stage.close(); System.exit(0); // TODO: This is like dropping a nuke diff --git a/app/src/main/java/org/toop/app/GameInformation.java b/app/src/main/java/org/toop/app/GameInformation.java index d8f8390..08e3f46 100644 --- a/app/src/main/java/org/toop/app/GameInformation.java +++ b/app/src/main/java/org/toop/app/GameInformation.java @@ -3,9 +3,7 @@ package org.toop.app; public class GameInformation { public enum Type { TICTACTOE(2, 5), - REVERSI(2, 10), - CONNECT4(2, 7), - BATTLESHIP(2, 5); + REVERSI(2, 10); private final int playerCount; private final int maxDepth; diff --git a/app/src/main/java/org/toop/app/Server.java b/app/src/main/java/org/toop/app/Server.java index 00f91cc..0c4ad0e 100644 --- a/app/src/main/java/org/toop/app/Server.java +++ b/app/src/main/java/org/toop/app/Server.java @@ -1,17 +1,28 @@ package org.toop.app; -import org.toop.app.game.Connect4Game; -import org.toop.app.game.ReversiGame; -import org.toop.app.game.TicTacToeGame; +import javafx.application.Platform; +import javafx.geometry.Pos; +import org.toop.app.gameControllers.AbstractGameController; +import org.toop.app.gameControllers.ReversiController; +import org.toop.app.gameControllers.TicTacToeController; +import org.toop.app.widget.Primitive; import org.toop.app.widget.WidgetContainer; +import org.toop.app.widget.complex.LoadingWidget; import org.toop.app.widget.popup.ChallengePopup; import org.toop.app.widget.popup.ErrorPopup; import org.toop.app.widget.popup.SendChallengePopup; import org.toop.app.widget.view.ServerView; import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.eventbus.GlobalEventBus; +import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.networking.clients.TournamentNetworkingClient; import org.toop.framework.networking.events.NetworkEvents; import org.toop.framework.networking.types.NetworkingConnector; +import org.toop.game.players.ArtificialPlayer; +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.local.AppContext; import java.util.List; @@ -31,24 +42,27 @@ public final class Server { private ServerView primary; private boolean isPolling = true; + private AbstractGameController gameController; + private final AtomicBoolean isSingleGame = new AtomicBoolean(false); private ScheduledExecutorService scheduler; + private EventFlow connectFlow; + public static GameInformation.Type gameToType(String game) { if (game.equalsIgnoreCase("tic-tac-toe")) { return GameInformation.Type.TICTACTOE; } else if (game.equalsIgnoreCase("reversi")) { return GameInformation.Type.REVERSI; - } else if (game.equalsIgnoreCase("connect4")) { - return GameInformation.Type.CONNECT4; -// } else if (game.equalsIgnoreCase("battleship")) { -// return GameInformation.Type.BATTLESHIP; } return null; } + + // Server has to deal with ALL network related listen events. This "server" can then interact with the manager to make stuff happen. + // This prevents data races where events get sent to the game manager but the manager isn't ready yet. public Server(String ip, String port, String user) { if (ip.split("\\.").length < 4) { new ErrorPopup("\"" + ip + "\" " + AppContext.getString("is-not-a-valid-ip-address")); @@ -69,43 +83,104 @@ public final class Server { return; } - new EventFlow() + final int reconnectAttempts = 10; + + LoadingWidget loading = new LoadingWidget( + Primitive.text("connecting"), 0, 0, reconnectAttempts, true, true + ); + + WidgetContainer.getCurrentView().transitionNextCustom(loading, "disconnect", this::disconnect); + + var a = new EventFlow() .addPostEvent(NetworkEvents.StartClient.class, - new TournamentNetworkingClient(), - new NetworkingConnector(ip, parsedPort, 10, 1, TimeUnit.SECONDS) - ) - .onResponse(NetworkEvents.StartClientResponse.class, e -> { - this.user = user; - clientId = e.clientId(); + new TournamentNetworkingClient(GlobalEventBus.get()), + new NetworkingConnector(ip, parsedPort, reconnectAttempts, 1, TimeUnit.SECONDS) + ); - new EventFlow().addPostEvent(new NetworkEvents.SendLogin(clientId, user)).postEvent(); + loading.setOnFailure(() -> { + if (WidgetContainer.getCurrentView() == loading) WidgetContainer.getCurrentView().transitionPrevious(); + a.unsubscribeAll(); + WidgetContainer.add( + Pos.CENTER, + new ErrorPopup(AppContext.getString("connecting-failed") + " " + ip + ":" + port) + ); + }); - primary = new ServerView(user, this::sendChallenge, this::disconnect); - WidgetContainer.getCurrentView().transitionNext(primary); + a.onResponse(NetworkEvents.CreatedIdForClient.class, e -> clientId = e.clientId(), true); - startPopulateScheduler(); - populateGameList(); + a.onResponse(NetworkEvents.StartClientResponse.class, e -> { + if (!e.successful()) { + return; + } - }).postEvent(); + primary = new ServerView(user, this::sendChallenge); + WidgetContainer.getCurrentView().transitionNextCustom(primary, "disconnect", this::disconnect); - new EventFlow().listen(this::handleReceivedChallenge) - .listen(this::handleMatchResponse); + a.unsubscribe("connecting"); + a.unsubscribe("startclient"); + + this.user = user; + + new EventFlow().addPostEvent(new NetworkEvents.SendLogin(clientId, user)).postEvent(); + + startPopulateScheduler(); + populateGameList(); + + primary.removeViewFromPreviousChain(loading); + + }, false, "startclient") + .listen( + NetworkEvents.ConnectTry.class, + e -> { + if (clientId != e.clientId()) return; + Platform.runLater( + () -> { + try { + loading.setAmount(e.amount()); + if (e.amount() >= loading.getMaxAmount()) { + loading.triggerFailure(); + } + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + ); + }, + false, "connecting" + ) + .postEvent(); + + a.listen(NetworkEvents.ChallengeResponse.class, this::handleReceivedChallenge, false, "challenge") + .listen(NetworkEvents.GameMatchResponse.class, this::handleMatchResponse, false, "match-response") + .listen(NetworkEvents.GameResultResponse.class, this::handleGameResult, false, "game-result") + .listen(NetworkEvents.GameMoveResponse.class, this::handleReceivedMove, false, "game-move") + .listen(NetworkEvents.YourTurnResponse.class, this::handleYourTurn, false, "your-turn"); + + connectFlow = a; } private void sendChallenge(String opponent) { if (!isPolling) return; - new SendChallengePopup(this, opponent, (playerInformation, gameType) -> { + var a = new SendChallengePopup(this, opponent, (playerInformation, gameType) -> { new EventFlow().addPostEvent(new NetworkEvents.SendChallenge(clientId, opponent, gameType)).postEvent(); isSingleGame.set(true); }); + + a.show(Pos.CENTER); } private void handleMatchResponse(NetworkEvents.GameMatchResponse response) { - if (!isPolling) return; + // TODO: Redo all of this mess + if (gameController != null) { + gameController.stop(); + } + + gameController = null; + + //if (!isPolling) return; String gameType = extractQuotedValue(response.gameType()); - if (response.clientId() == clientId) { isPolling = false; onlinePlayers.clear(); @@ -126,32 +201,69 @@ public final class Server { information.players[0].computerThinkTime = 1; information.players[1].name = response.opponent(); - Runnable onGameOverRunnable = isSingleGame.get()? null: this::gameOver; + Player[] players = new Player[2]; + players[(myTurn + 1) % 2] = new OnlinePlayer(response.opponent()); + + switch (type){ + case TICTACTOE ->{ + players[myTurn] = new ArtificialPlayer<>(new TicTacToeAIR(9), user); + } + case REVERSI ->{ + players[myTurn] = new ArtificialPlayer<>(new ReversiAIR(), user); + } + } switch (type) { - case TICTACTOE -> - new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); + case TICTACTOE ->{ + gameController = new TicTacToeController(players, false); + } case REVERSI -> - new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); - case CONNECT4 -> - new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); + gameController = new ReversiController(players, false); default -> new ErrorPopup("Unsupported game type."); } + + if (gameController != null){ + gameController.start(); + } } } + private void handleYourTurn(NetworkEvents.YourTurnResponse response) { + if (gameController == null) { + return; + } + gameController.onYourTurn(response); + + } + + private void handleGameResult(NetworkEvents.GameResultResponse response) { + if (gameController == null) { + return; + } + gameController.gameFinished(response); + } + + private void handleReceivedMove(NetworkEvents.GameMoveResponse response) { + if (gameController == null) { + return; + } + gameController.onMoveReceived(response); + } + private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) { if (!isPolling) return; String challengerName = extractQuotedValue(response.challengerName()); String gameType = extractQuotedValue(response.gameType()); final String finalGameType = gameType; - new ChallengePopup(challengerName, gameType, (playerInformation) -> { + var a = new ChallengePopup(challengerName, gameType, (playerInformation) -> { final int challengeId = Integer.parseInt(response.challengeId().replaceAll("\\D", "")); new EventFlow().addPostEvent(new NetworkEvents.SendAcceptChallenge(clientId, challengeId)).postEvent(); isSingleGame.set(true); }); + + a.show(Pos.CENTER); } private void sendMessage(String message) { @@ -162,7 +274,9 @@ public final class Server { new EventFlow().addPostEvent(new NetworkEvents.CloseClient(clientId)).postEvent(); isPolling = false; stopScheduler(); - primary.transitionPrevious(); + connectFlow.unsubscribeAll(); + + WidgetContainer.getCurrentView().transitionPrevious(); } private void forfeitGame() { @@ -200,7 +314,7 @@ public final class Server { } else { stopScheduler(); } - }, 0, 5, TimeUnit.SECONDS); + }, 0, 1, TimeUnit.SECONDS); } private void stopScheduler() { diff --git a/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java b/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java index c17dafd..4a8c7a2 100644 --- a/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java +++ b/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java @@ -1,6 +1,7 @@ package org.toop.app.canvas; import javafx.scene.paint.Color; +import org.toop.framework.gameFramework.model.game.AbstractGame; import java.util.function.Consumer; @@ -8,4 +9,9 @@ public class Connect4Canvas extends GameCanvas { public Connect4Canvas(Color color, int width, int height, Consumer onCellClicked) { super(color, Color.TRANSPARENT, width, height, 7, 6, 10, true, onCellClicked,null); } + + @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/DrawPlayerHover.java b/app/src/main/java/org/toop/app/canvas/DrawPlayerHover.java new file mode 100644 index 0000000..0c40a25 --- /dev/null +++ b/app/src/main/java/org/toop/app/canvas/DrawPlayerHover.java @@ -0,0 +1,7 @@ +package org.toop.app.canvas; + +import org.toop.framework.gameFramework.model.game.AbstractGame; + +public interface DrawPlayerHover { + void drawPlayerHover(int player, int move, AbstractGame game); +} diff --git a/app/src/main/java/org/toop/app/canvas/DrawPlayerMove.java b/app/src/main/java/org/toop/app/canvas/DrawPlayerMove.java new file mode 100644 index 0000000..fca2b46 --- /dev/null +++ b/app/src/main/java/org/toop/app/canvas/DrawPlayerMove.java @@ -0,0 +1,5 @@ +package org.toop.app.canvas; + +public interface DrawPlayerMove { + void drawPlayerMove(int player, int move); +} diff --git a/app/src/main/java/org/toop/app/canvas/GameCanvas.java b/app/src/main/java/org/toop/app/canvas/GameCanvas.java index f8f6d70..0e78ab4 100644 --- a/app/src/main/java/org/toop/app/canvas/GameCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/GameCanvas.java @@ -6,12 +6,13 @@ 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.AbstractGame; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -public abstract class GameCanvas { +public abstract class GameCanvas 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 && @@ -36,10 +37,18 @@ public abstract class GameCanvas { protected final Cell[] cells; + private Consumer onCellCLicked; + + public void setOnCellClicked(Consumer onClick) { + this.onCellCLicked = onClick; + } + protected GameCanvas(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; @@ -78,7 +87,7 @@ public abstract class GameCanvas { if (cell.isInside(event.getX(), event.getY())) { event.consume(); - onCellClicked.accept(column + row * rowSize); + this.onCellCLicked.accept(column + row * rowSize); } }); @@ -143,6 +152,19 @@ public abstract class GameCanvas { } } + @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; diff --git a/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java index b3f1628..090df29 100644 --- a/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java @@ -1,12 +1,14 @@ package org.toop.app.canvas; import javafx.scene.paint.Color; -import org.toop.game.records.Move; +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; -public final class ReversiCanvas extends GameCanvas { +public final class ReversiCanvas extends GameCanvas { 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); @@ -81,4 +83,14 @@ public final class ReversiCanvas extends GameCanvas { } 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/TicTacToeCanvas.java b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java index 890eb39..41d398f 100644 --- a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java @@ -1,14 +1,25 @@ package org.toop.app.canvas; import javafx.scene.paint.Color; +import org.toop.framework.gameFramework.model.game.AbstractGame; +import org.toop.game.games.tictactoe.TicTacToeR; import java.util.function.Consumer; -public final class TicTacToeCanvas extends GameCanvas { +public final class TicTacToeCanvas extends GameCanvas { 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); @@ -35,4 +46,9 @@ public final class TicTacToeCanvas extends GameCanvas { 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/game/BaseGameThread.java b/app/src/main/java/org/toop/app/game/BaseGameThread.java deleted file mode 100644 index e8d917b..0000000 --- a/app/src/main/java/org/toop/app/game/BaseGameThread.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.toop.app.game; - -import org.toop.app.GameInformation; -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.Game; -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; -import java.util.function.Function; -import java.util.function.Supplier; - -public abstract class BaseGameThread { - protected final GameInformation information; - protected final int myTurn; - protected final Runnable onGameOver; - protected final BlockingQueue moveQueue; - - protected final TGame game; - protected final TAI ai; - - protected final GameView primary; - protected final TCanvas canvas; - - protected final AtomicBoolean isRunning = new AtomicBoolean(true); - - protected BaseGameThread( - GameInformation information, - int myTurn, - Runnable onForfeit, - Runnable onExit, - Consumer onMessage, - Runnable onGameOver, - Supplier gameSupplier, - Supplier aiSupplier, - Function, TCanvas> canvasFactory) { - - this.information = information; - this.myTurn = myTurn; - this.onGameOver = onGameOver; - this.moveQueue = new LinkedBlockingQueue<>(); - - this.game = gameSupplier.get(); - this.ai = aiSupplier.get(); - - String type = information.type.getTypeToString(); - if (onForfeit == null || onExit == null) { - primary = new GameView(null, () -> { - isRunning.set(false); - WidgetContainer.getCurrentView().transitionPrevious(); - }, null, type); - } else { - primary = new GameView(onForfeit, () -> { - isRunning.set(false); - onExit.run(); - }, onMessage, type); - } - - this.canvas = canvasFactory.apply(this::onCellClicked); - - addCanvasToPrimary(); - - 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); - } - - private void onCellClicked(int cell) { - if (!isRunning.get()) return; - - final int currentTurn = getCurrentTurn(); - if (!information.players[currentTurn].isHuman) return; - - final char value = getSymbolForTurn(currentTurn); - - try { - moveQueue.put(new Move(cell, value)); - } catch (InterruptedException _) {} - } - - protected void gameOver() { - if (onGameOver != null) { - isRunning.set(false); - onGameOver.run(); - } - } - - protected void setGameLabels(boolean isMe) { - final int currentTurn = getCurrentTurn(); - final String turnName = getNameForTurn(currentTurn); - - primary.nextPlayer( - isMe, - information.players[isMe ? 0 : 1].name, - turnName, - information.players[isMe ? 1 : 0].name - ); - } - - protected abstract void addCanvasToPrimary(); - - protected abstract int getCurrentTurn(); - protected abstract char getSymbolForTurn(int turn); - protected abstract String getNameForTurn(int turn); - - protected abstract void onMoveResponse(NetworkEvents.GameMoveResponse response); - protected abstract void onYourTurnResponse(NetworkEvents.YourTurnResponse response); - - protected abstract void localGameThread(); -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/game/Connect4Game.java b/app/src/main/java/org/toop/app/game/Connect4Game.java deleted file mode 100644 index cd8e0e1..0000000 --- a/app/src/main/java/org/toop/app/game/Connect4Game.java +++ /dev/null @@ -1,265 +0,0 @@ -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.view.GameView; -import org.toop.app.widget.WidgetContainer; -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, "Connect4"); - } else { - primary = new GameView(onForfeit, () -> { - isRunning.set(false); - onExit.run(); - }, onMessage, "Connect4"); - } - - 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); - } -} diff --git a/app/src/main/java/org/toop/app/game/TicTacToeGame.java b/app/src/main/java/org/toop/app/game/TicTacToeGame.java deleted file mode 100644 index b0454c4..0000000 --- a/app/src/main/java/org/toop/app/game/TicTacToeGame.java +++ /dev/null @@ -1,250 +0,0 @@ -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 final 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, "TicTacToe"); - } else { - primary = new GameView(onForfeit, () -> { - isRunning.set(false); - onExit.run(); - }, onMessage, "TicTacToe"); - } - - 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/game/TicTacToeGameThread.java b/app/src/main/java/org/toop/app/game/TicTacToeGameThread.java deleted file mode 100644 index 960b2a2..0000000 --- a/app/src/main/java/org/toop/app/game/TicTacToeGameThread.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.toop.app.game; - -import org.toop.app.App; -import org.toop.app.GameInformation; -import org.toop.app.canvas.TicTacToeCanvas; -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 java.util.function.Consumer; -import javafx.geometry.Pos; -import javafx.scene.paint.Color; - -public final class TicTacToeGameThread extends BaseGameThread { - public TicTacToeGameThread(GameInformation info, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage, Runnable onGameOver) { - super(info, myTurn, onForfeit, onExit, onMessage, onGameOver, - TicTacToe::new, - TicTacToeAI::new, - clickHandler -> new TicTacToeCanvas(Color.GRAY, (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3, clickHandler) - ); - } - - public TicTacToeGameThread(GameInformation info) { - this(info, 0, null, null, null, null); - } - - @Override - protected void addCanvasToPrimary() { - primary.add(Pos.CENTER, canvas.getCanvas()); - } - - @Override - protected int getCurrentTurn() { - return game.getCurrentTurn(); - } - - @Override - protected char getSymbolForTurn(int turn) { - return turn == 0 ? 'X' : 'O'; - } - - @Override - protected String getNameForTurn(int turn) { - return turn == 0 ? "X" : "O"; - } - - private void drawMove(Move move) { - if (move.value() == 'X') canvas.drawX(Color.RED, move.position()); - else canvas.drawO(Color.BLUE, move.position()); - } - - @Override - protected 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) { - primary.gameOver(false, ""); - gameOver(); - } - } - } - - drawMove(move); - setGameLabels(game.getCurrentTurn() == myTurn); - } - - @Override - protected 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; - if (information.players[1].name.equalsIgnoreCase("pism")) { - move = ai.findWorstMove(game,9); - }else{ - 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(); - } - - @Override - protected void localGameThread() { - while (isRunning.get()) { - final int currentTurn = game.getCurrentTurn(); - setGameLabels(currentTurn == myTurn); - - 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); - drawMove(move); - - if (state != GameState.NORMAL) { - if (state == GameState.WIN) { - primary.gameOver(information.players[currentTurn].isHuman, information.players[currentTurn].name); - } else if (state == GameState.DRAW) { - primary.gameOver(false, ""); - } - - isRunning.set(false); - } - } - } -} \ 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 new file mode 100644 index 0000000..7d670e4 --- /dev/null +++ b/app/src/main/java/org/toop/app/gameControllers/AbstractGameController.java @@ -0,0 +1,127 @@ +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.view.GUIEvents; +import org.toop.app.canvas.GameCanvas; +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.AbstractGame; +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; + +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 primary view + protected final GameView primary; + + // Reference to game canvas + protected final GameCanvas canvas; + + private final Player[] players; // List of players, can't be changed. + protected final T 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(GameCanvas canvas, Player[] players, T game, ThreadBehaviour gameThreadBehaviour, String gameType) { + logger.info("Creating AbstractGameController"); + // Make sure player list matches expected size + if (players.length != game.getPlayerCount()){ + logger.error("Player count mismatch"); + throw new IllegalArgumentException("players and game's players must have same length"); + } + + 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/ReversiController.java b/app/src/main/java/org/toop/app/gameControllers/ReversiController.java new file mode 100644 index 0000000..1f6842f --- /dev/null +++ b/app/src/main/java/org/toop/app/gameControllers/ReversiController.java @@ -0,0 +1,138 @@ +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; + +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, players) : new OnlineThreadBehaviour<>(ReversiR, players), // 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(), 'R'); + } + + @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 new file mode 100644 index 0000000..eb8d8c9 --- /dev/null +++ b/app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java @@ -0,0 +1,63 @@ +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.TicTacToeR; + +public class TicTacToeController extends AbstractGameController { + + public TicTacToeController(Player[] players, boolean local) { + TicTacToeR ticTacToeR = new TicTacToeR(players); + super( + new TicTacToeCanvas(Color.GRAY, (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,(c) -> {new EventFlow().addPostEvent(GUIEvents.PlayerAttemptedMove.class, c).postEvent();}), + players, + ticTacToeR, + local ? new LocalThreadBehaviour(ticTacToeR, players) : new OnlineThreadBehaviour<>(ticTacToeR, players), // 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(), 'T'); + 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 < 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/view/View.java b/app/src/main/java/org/toop/app/view/View.java deleted file mode 100644 index c08a9b7..0000000 --- a/app/src/main/java/org/toop/app/view/View.java +++ /dev/null @@ -1,400 +0,0 @@ -package org.toop.app.view; - -import org.toop.framework.audio.events.AudioEvents; -import org.toop.framework.eventbus.EventFlow; - -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.control.ComboBox; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.Separator; -import javafx.scene.control.Slider; -import javafx.scene.control.TextField; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; -import javafx.scene.text.Text; - -import java.util.HashMap; -import java.util.Map; - -public abstract class View { - private final boolean mainView; - - private final StackPane view; - private final Map nodeMap; - - protected View(boolean mainView, String cssClass) { - this.mainView = mainView; - - view = new StackPane(); - view.getStyleClass().add(cssClass); - - nodeMap = new HashMap(); - } - - public void add(Pos position, Node node) { - assert node != null; - - StackPane.setAlignment(node, position); - view.getChildren().add(node); - } - - protected Region hspacer() { - final Region hspacer = new Region(); - hspacer.getStyleClass().add("hspacer"); - - return hspacer; - } - - protected Region vspacer() { - final Region vspacer = new Region(); - vspacer.getStyleClass().add("vspacer"); - - return vspacer; - } - - protected ScrollPane fit(String identifier, String cssClass, Node node) { - assert node != null; - - final ScrollPane fit = new ScrollPane(node); - fit.getStyleClass().add(cssClass); - - fit.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); - fit.setFitToWidth(true); - fit.setFitToHeight(true); - - if (!identifier.isEmpty()) { - nodeMap.put(identifier, fit); - } - - return fit; - } - - protected ScrollPane fit(String identifier, Node node) { - return fit(identifier, "fit", node); - } - - protected ScrollPane fit(Node node) { - return fit("", node); - } - - protected HBox hbox(String identifier, String cssClass, Node... nodes) { - assert !nodeMap.containsKey(identifier); - - final HBox hbox = new HBox(); - hbox.getStyleClass().add(cssClass); - hbox.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); - - for (final Node node : nodes) { - if (node != null) { - hbox.getChildren().add(node); - } - } - - if (!identifier.isEmpty()) { - nodeMap.put(identifier, hbox); - } - - return hbox; - } - - protected HBox hbox(String identifier, Node... nodes) { - return hbox(identifier, "container", nodes); - } - - protected HBox hbox(Node... nodes) { - return hbox("", nodes); - } - - protected HBox hboxFill(String identifier, String cssClass, Node... nodes) { - final HBox hbox = hbox(identifier, cssClass, nodes); - - for (final Node node : hbox.getChildren()) { - if (node instanceof Region) { - ((Region)node).setMaxHeight(Double.MAX_VALUE); - } - } - - return hbox; - } - - protected HBox hboxFill(String identifier, Node... nodes) { - final HBox hbox = hbox(identifier, nodes); - - for (final Node node : hbox.getChildren()) { - if (node instanceof Region) { - ((Region)node).setMaxHeight(Double.MAX_VALUE); - } - } - - return hbox; - } - - protected HBox hboxFill(Node... nodes) { - final HBox hbox = hbox(nodes); - - for (final Node node : hbox.getChildren()) { - if (node instanceof Region) { - ((Region)node).setMaxHeight(Double.MAX_VALUE); - } - } - - return hbox; - } - - protected VBox vbox(String identifier, String cssClass, Node... nodes) { - assert !nodeMap.containsKey(identifier); - - final VBox vbox = new VBox(); - vbox.getStyleClass().add(cssClass); - vbox.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); - - for (final Node node : nodes) { - if (node != null) { - vbox.getChildren().add(node); - } - } - - if (!identifier.isEmpty()) { - nodeMap.put(identifier, vbox); - } - - return vbox; - } - - protected VBox vbox(String identifier, Node... nodes) { - return vbox(identifier, "container", nodes); - } - - protected VBox vbox(Node... nodes) { - return vbox("", nodes); - } - - protected VBox vboxFill(String identifier, String cssClass, Node... nodes) { - final VBox vbox = vbox(identifier, cssClass, nodes); - - for (final Node node : vbox.getChildren()) { - if (node instanceof Region) { - ((Region)node).setMaxWidth(Double.MAX_VALUE); - } - } - - return vbox; - } - - protected VBox vboxFill(String identifier, Node... nodes) { - final VBox vbox = vbox(identifier, nodes); - - for (final Node node : vbox.getChildren()) { - if (node instanceof Region) { - ((Region)node).setMaxWidth(Double.MAX_VALUE); - } - } - - return vbox; - } - - protected VBox vboxFill(Node... nodes) { - final VBox vbox = vbox(nodes); - - for (final Node node : vbox.getChildren()) { - if (node instanceof Region) { - ((Region)node).setMaxWidth(Double.MAX_VALUE); - } - } - - return vbox; - } - - protected Separator separator(String identifier, String cssClass) { - assert !nodeMap.containsKey(identifier); - - final Separator separator = new Separator(); - separator.getStyleClass().add(cssClass); - - if (!identifier.isEmpty()) { - nodeMap.put(identifier, separator); - } - - return separator; - } - - protected Separator separator(String identifier) { - return separator(identifier, "separator"); - } - - protected Separator separator() { - return separator(""); - } - - protected Text header(String identifier, String cssClass) { - assert !nodeMap.containsKey(identifier); - - final Text header = new Text(); - header.getStyleClass().add(cssClass); - - if (!identifier.isEmpty()) { - nodeMap.put(identifier, header); - } - - return header; - } - - protected Text header(String identifier) { - return header(identifier, "header"); - } - - protected Text header() { - return header(""); - } - - protected Text text(String identifier, String cssClass) { - assert !nodeMap.containsKey(identifier); - - final Text text = new Text(); - text.getStyleClass().add(cssClass); - - if (!identifier.isEmpty()) { - nodeMap.put(identifier, text); - } - - return text; - } - - protected Text text(String identifier) { - return text(identifier, "text"); - } - - protected Text text() { - return text(""); - } - - protected Button button(String identifier, String cssClass) { - assert !nodeMap.containsKey(identifier); - - final Button button = new Button(); - button.getStyleClass().add(cssClass); - - button.setOnMouseClicked(_ -> { - new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); - }); - - if (!identifier.isEmpty()) { - nodeMap.put(identifier, button); - } - - return button; - } - - protected Button button(String identifier) { - return button(identifier, "button"); - } - - protected Button button() { - return button(""); - } - - protected Slider slider(String identifier, String cssClass) { - assert !nodeMap.containsKey(identifier); - - final Slider slider = new Slider(); - slider.getStyleClass().add(cssClass); - - slider.setMinorTickCount(0); - slider.setMajorTickUnit(1); - slider.setBlockIncrement(1); - - slider.setSnapToTicks(true); - slider.setShowTickLabels(true); - - slider.setOnMouseClicked(_ -> { - new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); - }); - - if (!identifier.isEmpty()) { - nodeMap.put(identifier, slider); - } - - return slider; - } - - protected Slider slider(String identifier) { - return slider(identifier, "slider"); - } - - protected Slider slider() { - return slider(""); - } - - protected TextField input(String identifier, String cssClass) { - assert !nodeMap.containsKey(identifier); - - final TextField input = new TextField(); - input.getStyleClass().add(cssClass); - - input.setOnMouseClicked(_ -> { - new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); - }); - - if (!identifier.isEmpty()) { - nodeMap.put(identifier, input); - } - - return input; - } - - protected TextField input(String identifier) { - return input(identifier, "input"); - } - - protected TextField input() { - return input(""); - } - - protected ComboBox combobox(String identifier, String cssClass) { - assert !nodeMap.containsKey(identifier); - - final ComboBox combobox = new ComboBox(); - combobox.getStyleClass().add(cssClass); - - combobox.setOnMouseClicked(_ -> { - new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); - }); - - if (!identifier.isEmpty()) { - nodeMap.put(identifier, combobox); - } - - return combobox; - } - - protected ComboBox combobox(String identifier) { - return combobox(identifier, "combo-box"); - } - - protected ComboBox combobox() { - return combobox(""); - } - - @SuppressWarnings("unchecked") - protected T get(String identifier) { - assert nodeMap.containsKey(identifier); - return (T) nodeMap.get(identifier); - } - - protected void clear() { - view.getChildren().clear(); - nodeMap.clear(); - } - - public boolean isMainView() { return mainView; } - public Region getView() { return view; } - - public abstract void setup(); - public void cleanup() { - clear(); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/ViewStack.java b/app/src/main/java/org/toop/app/view/ViewStack.java deleted file mode 100644 index 20d0ff3..0000000 --- a/app/src/main/java/org/toop/app/view/ViewStack.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.toop.app.view; - -import javafx.application.Platform; -import javafx.scene.Scene; -import javafx.scene.layout.StackPane; - -import java.util.Stack; - -public final class ViewStack { - private static boolean setup = false; - - private static StackPane root; - - private static View active; - private static Stack stack; - - public static void setup(Scene scene) { - assert scene != null; - - if (setup) { - return; - } - - root = new StackPane(); - - active = null; - stack = new Stack(); - - scene.setRoot(root); - - setup = true; - } - - public static void cleanup() { - assert setup; - - final var count = stack.size(); - - for (int i = 0; i < count; i++) { - pop(); - } - - if (active != null) { - active.cleanup(); - } - - setup = false; - } - - public static void reload() { - assert setup; - - for (final var view : stack) { - view.cleanup(); - } - - if (active != null) { - active.cleanup(); - active.setup(); - } - - for (final var view : stack) { - view.setup(); - } - } - - public static void push(View view) { - assert setup; - assert view != null; - - if (view.isMainView()) { - Platform.runLater(() -> { - if (active != null) { - root.getChildren().removeFirst(); - active.cleanup(); - } - - root.getChildren().addFirst(view.getView()); - view.setup(); - - active = view; - }); - } else { - Platform.runLater(() -> { - stack.push(view); - root.getChildren().addLast(view.getView()); - view.setup(); - }); - } - } - - public static void pop() { - assert setup; - - if (stack.isEmpty()) { - return; - } - - Platform.runLater(() -> { - final var last = stack.pop(); - root.getChildren().removeLast(); - last.cleanup(); - }); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/displays/SongDisplay.java b/app/src/main/java/org/toop/app/view/displays/SongDisplay.java deleted file mode 100644 index 3bd8a6c..0000000 --- a/app/src/main/java/org/toop/app/view/displays/SongDisplay.java +++ /dev/null @@ -1,128 +0,0 @@ -package org.toop.app.view.displays; - -import javafx.application.Platform; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.control.ProgressBar; -import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import org.toop.app.widget.Widget; -import org.toop.framework.audio.AudioEventListener; -import org.toop.framework.audio.events.AudioEvents; -import org.toop.framework.eventbus.EventFlow; -import javafx.geometry.Pos; -import javafx.scene.text.Text; -import org.toop.framework.eventbus.GlobalEventBus; - -public class SongDisplay extends VBox implements Widget { - - private final Text songTitle; - private final ProgressBar progressBar; - private final Text progressText; - private boolean paused = false; - - public SongDisplay() { - new EventFlow() - .listen(this::updateTheSong); - - setAlignment(Pos.CENTER); - getStyleClass().add("song-display"); - - songTitle = new Text("song playing"); - songTitle.getStyleClass().add("song-title"); - - progressBar = new ProgressBar(0); - progressBar.getStyleClass().add("progress-bar"); - - progressText = new Text("0:00/0:00"); - progressText.getStyleClass().add("progress-text"); - - Button skipButton = new Button(">>"); - Button pauseButton = new Button("â¸"); - Button previousButton = new Button("<<"); - - skipButton.getStyleClass().setAll("skip-button"); - pauseButton.getStyleClass().setAll("pause-button"); - previousButton.getStyleClass().setAll("previous-button"); - - skipButton.setOnAction( event -> { - GlobalEventBus.post(new AudioEvents.SkipMusic()); - paused = false; - pauseButton.setText(getPlayString(paused)); - }); - - pauseButton.setOnAction(event -> { - GlobalEventBus.post(new AudioEvents.PauseMusic()); - paused = !paused; - pauseButton.setText(getPlayString(paused)); - }); - - previousButton.setOnAction( event -> { - GlobalEventBus.post(new AudioEvents.PreviousMusic()); - paused = false; - pauseButton.setText(getPlayString(paused)); - }); - - HBox control = new HBox(10, previousButton, pauseButton, skipButton); - control.setAlignment(Pos.CENTER); - control.getStyleClass().add("controls"); - - getChildren().addAll(songTitle, progressBar, progressText, control); - } - - private void updateTheSong(AudioEvents.PlayingMusic event) { - Platform.runLater(() -> { - String text = event.name(); - text = text.substring(0, text.length() - 4); - songTitle.setText(text); - double currentPos = event.currentPosition(); - double duration = event.duration(); - if (currentPos / duration > 0.05) { - double progress = currentPos / duration; - progressBar.setProgress(progress); - } - else if (currentPos / duration < 0.05) { - progressBar.setProgress(0.05); - } - progressText.setText(getTimeString(event.currentPosition(), event.duration())); - }); - } - - private String getTimeString(long position, long duration) { - long positionMinutes = position / 60; - long durationMinutes = duration / 60; - long positionSeconds = position % 60; - long durationSeconds = duration % 60; - String positionSecondsStr = String.valueOf(positionSeconds); - String durationSecondsStr = String.valueOf(durationSeconds); - - if (positionSeconds < 10) { - positionSecondsStr = "0" + positionSeconds; - } - if (durationSeconds < 10) { - durationSecondsStr = "0" + durationSeconds; - } - - String time = positionMinutes + ":" + positionSecondsStr + " / " + durationMinutes + ":" + durationSecondsStr; - return time; - } - - @Override - public Node getNode() { - return this; - } - private String getPlayString(boolean paused) { - if (paused) { - return "â–¶"; - } - else { - return "â¸"; - } - } -} - - - - - diff --git a/app/src/main/java/org/toop/app/view/views/ChallengeView.java b/app/src/main/java/org/toop/app/view/views/ChallengeView.java deleted file mode 100644 index 36184f6..0000000 --- a/app/src/main/java/org/toop/app/view/views/ChallengeView.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.toop.app.view.views; - -import org.toop.app.GameInformation; -import org.toop.app.Server; -import org.toop.app.view.View; -import org.toop.app.view.ViewStack; -import org.toop.app.view.displays.SongDisplay; -import org.toop.local.AppContext; - -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.control.Slider; -import javafx.scene.text.Text; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -public final class ChallengeView extends View { - private final GameInformation.Player playerInformation; - - private final String challenger; - private final String game; - - private final Consumer onAccept; - - public ChallengeView(String challenger, String game, Consumer onAccept) { - super(false, "bg-popup"); - - playerInformation = new GameInformation.Player(); - - this.challenger = challenger; - this.game = game; - - this.onAccept = onAccept; - } - - @Override - public void setup() { - final Text challengeText = text(); - challengeText.setText(AppContext.getString("you-were-challenged-by")); - - final Text challengerHeader = header(); - challengerHeader.setText(challenger); - - final Text gameText = text(); - gameText.setText(AppContext.getString("to-a-game-of") + " " + game); - - final Button acceptButton = button(); - acceptButton.setText(AppContext.getString("accept")); - acceptButton.setOnAction(_ -> { - onAccept.accept(playerInformation); - }); - - final Button denyButton = button(); - denyButton.setText(AppContext.getString("deny")); - denyButton.setOnAction(_ -> { - ViewStack.pop(); - }); - - final List nodes = new ArrayList<>(); - - if (playerInformation.isHuman) { - final Button playerToggle = button(); - playerToggle.setText(AppContext.getString("player")); - playerToggle.setOnAction(_ -> { - playerInformation.isHuman = false; - cleanup(); - setup(); - }); - - nodes.add(vbox(playerToggle)); - } else { - final Button computerToggle = button(); - computerToggle.setText(AppContext.getString("computer")); - computerToggle.setOnAction(_ -> { - playerInformation.isHuman = true; - cleanup(); - setup(); - }); - - nodes.add(vbox(computerToggle)); - - final Text computerDifficultyText = text(); - computerDifficultyText.setText(AppContext.getString("computer-difficulty")); - - final Slider computerDifficultySlider = slider(); - computerDifficultySlider.setMin(0); - computerDifficultySlider.setMax(Server.gameToType(game).getMaxDepth()); - computerDifficultySlider.setValue(playerInformation.computerDifficulty); - computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> { - playerInformation.computerDifficulty = newValue.intValue(); - }); - - nodes.add(vbox(computerDifficultyText, computerDifficultySlider)); - } - - final SongDisplay songdisplay = new SongDisplay(); - - - add(Pos.BOTTOM_RIGHT, - fit(vboxFill( - songdisplay - ))); - - add(Pos.CENTER, - fit(hboxFill( - vboxFill( - challengeText, - challengerHeader, - gameText, - separator(), - - hboxFill( - acceptButton, - denyButton - ) - ), - - vboxFill( - nodes.toArray(new Node[0]) - ) - )) - ); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/CreditsView.java b/app/src/main/java/org/toop/app/view/views/CreditsView.java deleted file mode 100644 index 3f02ef8..0000000 --- a/app/src/main/java/org/toop/app/view/views/CreditsView.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.toop.app.view.views; - -import org.toop.app.App; -import org.toop.app.view.View; -import org.toop.app.view.ViewStack; -import org.toop.app.view.displays.SongDisplay; -import org.toop.local.AppContext; - -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.control.ScrollPane; -import javafx.scene.layout.VBox; -import javafx.scene.text.Text; -import javafx.util.Duration; - -public final class CreditsView extends View { - public CreditsView() { - super(false, "bg-primary"); - } - - @Override - public void setup() { - final Text scrumMasterHeader = header(); - scrumMasterHeader.setText(AppContext.getString("scrum-master") + ": Stef"); - - final Text productOwnerHeader = header(); - productOwnerHeader.setText(AppContext.getString("product-owner") + ": Omar"); - - final Text mergeCommanderHeader = header(); - mergeCommanderHeader.setText(AppContext.getString("merge-commander") + ": Bas"); - - final Text localizationHeader = header(); - localizationHeader.setText(AppContext.getString("localization") + ": Ticho"); - - final Text aiHeader = header(); - aiHeader.setText(AppContext.getString("ai") + ": Michiel"); - - final Text developersHeader = header(); - developersHeader.setText(AppContext.getString("developers") + ": Michiel, Bas, Stef, Omar, Ticho"); - - final Text moralSupportHeader = header(); - moralSupportHeader.setText(AppContext.getString("moral-support") + ": Wesley"); - - final Text openglHeader = header(); - openglHeader.setText(AppContext.getString("opengl") + ": Omar"); - - final SongDisplay songdisplay = new SongDisplay(); - - - add(Pos.BOTTOM_RIGHT, - fit(vboxFill( - songdisplay - ))); - - add(Pos.CENTER, - fit("credits-fit", vboxFill("credits-container", "credits-container", - vbox("credits-spacer-top", ""), - - scrumMasterHeader, - productOwnerHeader, - mergeCommanderHeader, - localizationHeader, - aiHeader, - developersHeader, - moralSupportHeader, - openglHeader, - - vbox("credits-spacer-bottom", "") - )) - ); - - final Button backButton = button(); - backButton.setText(AppContext.getString("back")); - backButton.setOnAction(_ -> { ViewStack.pop(); }); - - add(Pos.BOTTOM_LEFT, - vboxFill( - backButton - ) - ); - - playCredits(100, 20); - } - - private void playCredits(int lineHeight, int length) { - final ScrollPane creditsFit = get("credits-fit"); - creditsFit.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); - creditsFit.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); - - final VBox creditsContainer = get("credits-container"); - creditsContainer.setSpacing(lineHeight); - - final VBox creditsSpacerTop = get("credits-spacer-top"); - creditsSpacerTop.setMinHeight(App.getHeight() - lineHeight); - - final VBox creditsSpacerBottom = get("credits-spacer-bottom"); - creditsSpacerBottom.setMinHeight(App.getHeight() - lineHeight); - - final Timeline timeline = new Timeline( - new KeyFrame(Duration.seconds(0), new KeyValue(creditsFit.vvalueProperty(), 0.0)), - new KeyFrame(Duration.seconds(length), new KeyValue(creditsFit.vvalueProperty(), 1.0)) - ); - - timeline.setCycleCount(Timeline.INDEFINITE); - timeline.play(); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/ErrorView.java b/app/src/main/java/org/toop/app/view/views/ErrorView.java deleted file mode 100644 index 5dc85fa..0000000 --- a/app/src/main/java/org/toop/app/view/views/ErrorView.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.toop.app.view.views; - -import org.toop.app.view.View; -import org.toop.app.view.ViewStack; -import org.toop.local.AppContext; - -import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.text.Text; - -public final class ErrorView extends View { - private final String error; - - public ErrorView(String error) { - super(false, "bg-popup"); - this.error = error; - } - - @Override - public void setup() { - final Text errorHeader = header(); - errorHeader.setText(AppContext.getString("error")); - - final Text errorText = text(); - errorText.setText(error); - - final Button okButton = button(); - okButton.setText(AppContext.getString("ok")); - okButton.setOnAction(_ -> { ViewStack.pop(); }); - - add(Pos.CENTER, - vboxFill( - errorHeader, - separator(), - - vspacer(), - errorText, - vspacer(), - - separator(), - okButton - ) - ); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/GameView.java b/app/src/main/java/org/toop/app/view/views/GameView.java deleted file mode 100644 index 5b9b1d6..0000000 --- a/app/src/main/java/org/toop/app/view/views/GameView.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.toop.app.view.views; - -import javafx.application.Platform; -import org.toop.app.view.View; -import org.toop.app.view.ViewStack; -import org.toop.app.view.displays.SongDisplay; -import org.toop.local.AppContext; - -import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.control.ListView; -import javafx.scene.control.TextField; -import javafx.scene.text.Text; - -import java.util.function.Consumer; - -public final class GameView extends View { - // TODO: This should be it's own file... - private static class GameOverView extends View { - private final boolean iWon; - private final String winner; - - // TODO: Make winner generic, there is no "I won" unless you play online or against bot. Should be a generic "... won" to simplify - public GameOverView(boolean iWon, String winner) { - super(false, "bg-popup"); - - this.iWon = iWon; - this.winner = winner; - } - - @Override - public void setup() { - final Text gameOverHeader = header(); - gameOverHeader.setText(AppContext.getString("game-over")); - - final Button okButton = button(); - okButton.setText(AppContext.getString("ok")); - okButton.setOnAction(_ -> { ViewStack.pop(); }); - - Text gameOverText = text(); - - if (winner.isEmpty()) { - gameOverText.setText(AppContext.getString("the-game-ended-in-a-draw")); - } else { - if (iWon) { - gameOverText.setText(AppContext.getString("you-win") + " " + winner); - } else { - gameOverText.setText(AppContext.getString("you-lost-against") + " " + winner); - } - } - - add(Pos.CENTER, - fit(vboxFill( - gameOverHeader, - separator(), - - vspacer(), - gameOverText, - vspacer(), - - separator(), - okButton - )) - ); - } - } - - private final Button forfeitButton; - private final Button exitButton; - - private final Text currentPlayerHeader; - private final Text currentMoveHeader; - - private final Text nextPlayerHeader; - - private final Text gameStateFeedback = text(); - - private final ListView chatListView; - private final TextField chatInput; - - public GameView(Runnable onForfeit, Runnable onExit, Consumer onMessage) { - assert onExit != null; - - super(true, "bg-primary"); - - if (onForfeit != null) { - forfeitButton = button(); - forfeitButton.setText(AppContext.getString("forfeit")); - forfeitButton.setOnAction(_ -> onForfeit.run()); - } else { - forfeitButton = null; - } - - final SongDisplay songdisplay = new SongDisplay(); - - - add(Pos.BOTTOM_RIGHT, - fit(vboxFill( - songdisplay - ))); - - if (onMessage != null) { - chatListView = new ListView(); - - chatInput = input(); - chatInput.setOnAction(_ -> { - onMessage.accept(chatInput.getText()); - chatInput.setText(""); - }); - } else { - chatListView = null; - chatInput = null; - } - - exitButton = button(); - exitButton.setText(AppContext.getString("exit")); - exitButton.setOnAction(_ -> onExit.run()); - - currentPlayerHeader = header("", "header"); - currentMoveHeader = header(); - nextPlayerHeader = header(); - } - - @Override - public void setup() { - add( - Pos.TOP_CENTER, - gameStateFeedback - ); - - add(Pos.BOTTOM_LEFT, - vboxFill( - forfeitButton, - exitButton - ) - ); - - if (chatListView != null) { - add(Pos.BOTTOM_RIGHT, - fit(vboxFill( - chatListView, - chatInput - ) - )); - } - } - - public void nextPlayer(boolean isMe, String currentPlayer, String currentMove, String nextPlayer) { - Platform.runLater(() -> { - gameStateFeedback.setText("Waiting on " + currentPlayer + " to make their move."); - currentPlayerHeader.setText(currentPlayer); - currentMoveHeader.setText(currentMove); - - nextPlayerHeader.setText(nextPlayer); - - if (isMe) { - currentPlayerHeader.getStyleClass().add("my-turn"); - } else { - currentPlayerHeader.getStyleClass().remove("my-turn"); - } - }); - - } - - public void updateChat(String message) { - if (chatListView == null) { - return; - } - - final Text messageText = text(); - messageText.setText(message); - - chatListView.getItems().add(messageText); - } - - public void gameOver(boolean iWon, String winner) { - ViewStack.push(new GameOverView(iWon, winner)); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/LocalMultiplayerView.java b/app/src/main/java/org/toop/app/view/views/LocalMultiplayerView.java deleted file mode 100644 index 7a4acd1..0000000 --- a/app/src/main/java/org/toop/app/view/views/LocalMultiplayerView.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.toop.app.view.views; - -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.view.View; -import org.toop.app.view.ViewStack; -import org.toop.app.view.displays.SongDisplay; -import org.toop.app.widget.Primitive; -import org.toop.app.widget.tutorial.BaseTutorialWidget; -import org.toop.app.widget.tutorial.TicTacToeTutorialWidget; -import org.toop.local.AppContext; - -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.control.Slider; -import javafx.scene.control.TextField; -import javafx.scene.layout.VBox; -import javafx.scene.text.Text; -import org.toop.local.AppSettings; - -import java.util.ArrayList; -import java.util.List; - -public final class LocalMultiplayerView extends View { - private final GameInformation information; - - public LocalMultiplayerView(GameInformation information) { - super(true, "bg-primary"); - this.information = information; - } - - public LocalMultiplayerView(GameInformation.Type type) { - this(new GameInformation(type)); - } - - @Override - public void setup() { - final Button playButton = button(); - playButton.setText(AppContext.getString("play")); - playButton.setOnAction(_ -> { - for (final GameInformation.Player player : information.players) { - if (player.name.isEmpty()) { - ViewStack.push(new ErrorView(AppContext.getString("please-enter-your-name"))); - return; - } - } - - switch (information.type) { - case TICTACTOE: new TicTacToeGameThread(information); break; - case REVERSI: new ReversiGame(information); break; - case CONNECT4: new Connect4Game(information); break; - // case BATTLESHIP: new BattleshipGame(information); break; - } - }); - - final SongDisplay songdisplay = new SongDisplay(); - - - add(Pos.BOTTOM_RIGHT, - fit(vboxFill( - songdisplay - ))); - - add(Pos.CENTER, - fit(vboxFill( - hbox( - setupPlayers() - ), - - separator(), - playButton - )) - ); - - final Button backButton = button(); - backButton.setText(AppContext.getString("back")); - backButton.setOnAction(_ -> { ViewStack.push(new MainView()); }); - - add(Pos.BOTTOM_LEFT, - vboxFill( - backButton - ) - ); - } - - private VBox[] setupPlayers() { - final VBox[] playerBoxes = new VBox[information.type.getPlayerCount()]; - - for (int i = 0; i < playerBoxes.length; i++) { - final int index = i; - - List nodes = new ArrayList<>(); - - final Text playerHeader = header(); - playerHeader.setText(AppContext.getString("player") + " #" + (i + 1)); - - nodes.add(playerHeader); - nodes.add(separator()); - - final Text nameText = text(); - nameText.setText(AppContext.getString("name")); - - if (information.players[i].isHuman) { - final Button playerToggle = button(); - playerToggle.setText(AppContext.getString("player")); - playerToggle.setOnAction(_ -> { - information.players[index].isHuman = false; - cleanup(); - setup(); - }); - - nodes.add(vboxFill(playerToggle)); - - final TextField playerNameInput = input(); - playerNameInput.setPromptText(AppContext.getString("enter-your-name")); - playerNameInput.setText(information.players[i].name); - playerNameInput.textProperty().addListener((_, _, newValue) -> { - information.players[index].name = newValue; - }); - - nodes.add(vboxFill(nameText, playerNameInput)); - } else { - final Button computerToggle = button(); - computerToggle.setText(AppContext.getString("computer")); - computerToggle.setOnAction(_ -> { - information.players[index].isHuman = true; - cleanup(); - setup(); - }); - - nodes.add(vboxFill(computerToggle)); - - information.players[i].name = "Pism Bot V" + i; - - final Text computerNameText = text(); - computerNameText.setText(information.players[index].name); - - nodes.add(vboxFill(nameText, computerNameText)); - - final Text computerDifficultyText = text(); - computerDifficultyText.setText(AppContext.getString("computer-difficulty")); - - final Slider computerDifficultySlider = slider(); - computerDifficultySlider.setMin(0); - computerDifficultySlider.setMax(information.type.getMaxDepth()); - computerDifficultySlider.setValue(information.players[i].computerDifficulty); - computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> { - information.players[index].computerDifficulty = newValue.intValue(); - }); - - nodes.add(vboxFill(computerDifficultyText, computerDifficultySlider)); - - final Text computerThinkTimeText = text(); - computerThinkTimeText.setText(AppContext.getString("computer-think-time")); - - final Slider computerThinkTimeSlider = slider(); - computerThinkTimeSlider.setMin(0); - computerThinkTimeSlider.setMax(5); - computerThinkTimeSlider.setValue(information.players[i].computerThinkTime); - computerThinkTimeSlider.valueProperty().addListener((_, _, newValue) -> { - information.players[index].computerThinkTime = newValue.intValue(); - }); - - nodes.add(vboxFill(computerThinkTimeText, computerThinkTimeSlider)); - } - - playerBoxes[i] = vboxFill(nodes.toArray(new Node[0])); - } - - return playerBoxes; - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/LocalView.java b/app/src/main/java/org/toop/app/view/views/LocalView.java deleted file mode 100644 index c4a1c4b..0000000 --- a/app/src/main/java/org/toop/app/view/views/LocalView.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.toop.app.view.views; - -import org.toop.app.GameInformation; -import org.toop.app.view.View; -import org.toop.app.view.ViewStack; -import org.toop.app.view.displays.SongDisplay; -import org.toop.local.AppContext; - -import javafx.geometry.Pos; -import javafx.scene.control.Button; - -public final class LocalView extends View { - public LocalView() { - super(true, "bg-primary"); - } - - @Override - public void setup() { - final Button ticTacToeButton = button(); - ticTacToeButton.setText(AppContext.getString("tic-tac-toe")); - ticTacToeButton.setOnAction(_ -> { ViewStack.push(new LocalMultiplayerView(GameInformation.Type.TICTACTOE)); }); - - final Button reversiButton = button(); - reversiButton.setText(AppContext.getString("reversi")); - reversiButton.setOnAction(_ -> { ViewStack.push(new LocalMultiplayerView(GameInformation.Type.REVERSI)); }); - - final Button connect4Button = button(); - connect4Button.setText(AppContext.getString("connect4")); - connect4Button.setOnAction(_ -> { ViewStack.push(new LocalMultiplayerView(GameInformation.Type.CONNECT4)); }); - - add(Pos.CENTER, - fit(vboxFill( - ticTacToeButton, - reversiButton, - connect4Button - )) - ); - - final Button backButton = button(); - backButton.setText(AppContext.getString("back")); - backButton.setOnAction(_ -> { ViewStack.push(new MainView()); }); - - final SongDisplay songdisplay = new SongDisplay(); - - - add(Pos.BOTTOM_RIGHT, - fit(vboxFill( - songdisplay - ))); - - add(Pos.BOTTOM_LEFT, - vboxFill( - backButton - ) - ); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/MainView.java b/app/src/main/java/org/toop/app/view/views/MainView.java deleted file mode 100644 index 10fa0fc..0000000 --- a/app/src/main/java/org/toop/app/view/views/MainView.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.toop.app.view.views; - -import org.toop.app.App; -import org.toop.app.view.View; -import org.toop.app.view.ViewStack; -import org.toop.local.AppContext; -import org.toop.app.view.displays.SongDisplay; -import javafx.geometry.Pos; -import javafx.scene.control.Button; - -public final class MainView extends View { - public MainView() { - super(true, "bg-primary"); - } - - @Override - public void setup() { - final Button localButton = button(); - localButton.setText(AppContext.getString("local")); - localButton.setOnAction(_ -> { ViewStack.push(new LocalView()); }); - - final Button onlineButton = button(); - onlineButton.setText(AppContext.getString("online")); - onlineButton.setOnAction(_ -> { ViewStack.push(new OnlineView()); }); - - final Button creditsButton = button(); - creditsButton.setText(AppContext.getString("credits")); - creditsButton.setOnAction(_ -> { ViewStack.push(new CreditsView()); }); - - final Button optionsButton = button(); - optionsButton.setText(AppContext.getString("options")); - optionsButton.setOnAction(_ -> { ViewStack.push(new OptionsView()); }); - - final Button quitButton = button(); - quitButton.setText(AppContext.getString("quit")); - quitButton.setOnAction(_ -> { App.startQuit(); }); - - final SongDisplay songdisplay = new SongDisplay(); - - add(Pos.BOTTOM_RIGHT, - fit(vboxFill( - songdisplay - ))); - - add(Pos.CENTER, - fit(vboxFill( - localButton, - onlineButton, - creditsButton, - optionsButton, - quitButton - )) - ); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/OnlineView.java b/app/src/main/java/org/toop/app/view/views/OnlineView.java deleted file mode 100644 index 3d7750d..0000000 --- a/app/src/main/java/org/toop/app/view/views/OnlineView.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.toop.app.view.views; - -import org.toop.app.Server; -import org.toop.app.view.View; -import org.toop.app.view.ViewStack; -import org.toop.app.view.displays.SongDisplay; -import org.toop.local.AppContext; - -import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.control.TextField; -import javafx.scene.text.Text; - -public class OnlineView extends View { - public OnlineView() { - super(true, "bg-primary"); - } - - @Override - public void setup() { - final Text serverInformationHeader = header(); - serverInformationHeader.setText(AppContext.getString("server-information")); - - final Text serverIPText = text(); - serverIPText.setText(AppContext.getString("ip-address")); - - final TextField serverIPInput = input(); - serverIPInput.setPromptText(AppContext.getString("enter-the-server-ip")); - - final Text serverPortText = text(); - serverPortText.setText(AppContext.getString("port")); - - final TextField serverPortInput = input(); - serverPortInput.setPromptText(AppContext.getString("enter-the-server-port")); - - final Text playerNameText = text(); - playerNameText.setText(AppContext.getString("player-name")); - - final TextField playerNameInput = input(); - playerNameInput.setPromptText(AppContext.getString("enter-your-name")); - - final Button connectButton = button(); - connectButton.setText(AppContext.getString("connect")); - connectButton.setOnAction(_ -> { - new Server(serverIPInput.getText(), serverPortInput.getText(), playerNameInput.getText()); - }); - - final SongDisplay songdisplay = new SongDisplay(); - - add(Pos.BOTTOM_RIGHT, - fit(vboxFill( - songdisplay - ))); - - add(Pos.CENTER, - fit(vboxFill( - serverInformationHeader, - separator(), - - vboxFill( - serverIPText, - serverIPInput - ), - - vboxFill( - serverPortText, - serverPortInput - ), - - vboxFill( - playerNameText, - playerNameInput - ), - - vboxFill( - connectButton - ) - )) - ); - - final Button backButton = button(); - backButton.setText(AppContext.getString("back")); - backButton.setOnAction(_ -> { ViewStack.push(new MainView()); }); - - add(Pos.BOTTOM_LEFT, - vboxFill( - backButton - ) - ); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/OptionsView.java b/app/src/main/java/org/toop/app/view/views/OptionsView.java deleted file mode 100644 index 08cdfa0..0000000 --- a/app/src/main/java/org/toop/app/view/views/OptionsView.java +++ /dev/null @@ -1,258 +0,0 @@ -package org.toop.app.view.views; - -import org.toop.app.App; -import org.toop.app.view.View; -import org.toop.app.view.ViewStack; -import org.toop.app.view.displays.SongDisplay; -import org.toop.framework.audio.VolumeControl; -import org.toop.framework.audio.events.AudioEvents; -import org.toop.framework.eventbus.EventFlow; -import org.toop.local.AppContext; -import org.toop.local.AppSettings; - -import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Slider; -import javafx.scene.text.Text; -import javafx.util.StringConverter; - -import java.util.Locale; - -public final class OptionsView extends View { - public OptionsView() { - super(false, "bg-secondary"); - } - - @Override - public void setup() { - final Text generalHeader = header(); - generalHeader.setText(AppContext.getString("general")); - - final Text volumeHeader = header(); - volumeHeader.setText(AppContext.getString("volume")); - - final Text styleHeader = header(); - styleHeader.setText(AppContext.getString("style")); - - add(Pos.CENTER, - fit(hboxFill( - vboxFill( - generalHeader, - separator(), - - vboxFill( - text("language-text"), - combobox("language-combobox") - ), - - vboxFill( - button("fullscreen-button") - ) - ), - - vboxFill( - volumeHeader, - separator(), - - vboxFill( - text("master-volume-text"), - slider("master-volume-slider") - ), - - vboxFill( - text("effects-volume-text"), - slider("effects-volume-slider") - ), - - vboxFill( - text("music-volume-text"), - slider("music-volume-slider") - ) - ), - - vboxFill( - styleHeader, - separator(), - - vboxFill( - text("theme-text"), - combobox("theme-combobox") - ), - - vboxFill( - text("layout-text"), - combobox("layout-combobox") - ) - ) - )) - ); - - setupLanguageOption(); - setupMasterVolumeOption(); - setupEffectsVolumeOption(); - setupMusicVolumeOption(); - setupThemeOption(); - setupLayoutOption(); - setupFullscreenOption(); - - final Button backButton = button(); - backButton.setText(AppContext.getString("back")); - backButton.setOnAction(_ -> { ViewStack.pop(); }); - - final SongDisplay songdisplay = new SongDisplay(); - - - add(Pos.BOTTOM_RIGHT, - fit(vboxFill( - songdisplay - ))); - - add(Pos.BOTTOM_LEFT, - vboxFill( - backButton - ) - ); - } - - private void setupLanguageOption() { - final Text languageText = get("language-text"); - languageText.setText(AppContext.getString("language")); - - final ComboBox languageCombobox = get("language-combobox"); - languageCombobox.getItems().addAll(AppContext.getLocalization().getAvailableLocales()); - languageCombobox.setValue(AppContext.getLocale()); - - languageCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> { - AppSettings.getSettings().setLocale(newValue.toString()); - AppContext.setLocale(newValue); - }); - - languageCombobox.setConverter(new StringConverter<>() { - @Override - public String toString(Locale locale) { - return AppContext.getString(locale.getDisplayName().toLowerCase()); - } - - @Override - public Locale fromString(String s) { - return null; - } - }); - } - - private void setupMasterVolumeOption() { - final Text masterVolumeText = get("master-volume-text"); - masterVolumeText.setText(AppContext.getString("master-volume")); - - final Slider masterVolumeSlider = get("master-volume-slider"); - masterVolumeSlider.setMin(0); - masterVolumeSlider.setMax(100); - masterVolumeSlider.setValue(AppSettings.getSettings().getVolume()); - - masterVolumeSlider.valueProperty().addListener((_, _, newValue) -> { - AppSettings.getSettings().setVolume(newValue.intValue()); - new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.MASTERVOLUME)).asyncPostEvent(); - }); - } - - private void setupEffectsVolumeOption() { - final Text effectsVolumeText = get("effects-volume-text"); - effectsVolumeText.setText(AppContext.getString("effects-volume")); - - final Slider effectsVolumeSlider = get("effects-volume-slider"); - effectsVolumeSlider.setMin(0); - effectsVolumeSlider.setMax(100); - effectsVolumeSlider.setValue(AppSettings.getSettings().getFxVolume()); - - effectsVolumeSlider.valueProperty().addListener((_, _, newValue) -> { - AppSettings.getSettings().setFxVolume(newValue.intValue()); - new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.FX)).asyncPostEvent(); - }); - } - - private void setupMusicVolumeOption() { - final Text musicVolumeText = get("music-volume-text"); - musicVolumeText.setText(AppContext.getString("music-volume")); - - final Slider musicVolumeSlider = get("music-volume-slider"); - musicVolumeSlider.setMin(0); - musicVolumeSlider.setMax(100); - musicVolumeSlider.setValue(AppSettings.getSettings().getMusicVolume()); - - musicVolumeSlider.valueProperty().addListener((_, _, newValue) -> { - AppSettings.getSettings().setMusicVolume(newValue.intValue()); - new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.MUSIC)).asyncPostEvent(); - }); - } - - private void setupThemeOption() { - final Text themeText = get("theme-text"); - themeText.setText(AppContext.getString("theme")); - - final ComboBox themeCombobox = get("theme-combobox"); - themeCombobox.getItems().addAll("dark", "light", "high-contrast"); - themeCombobox.setValue(AppSettings.getSettings().getTheme()); - - themeCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> { - AppSettings.getSettings().setTheme(newValue); - App.setStyle(newValue, AppSettings.getSettings().getLayoutSize()); - }); - - themeCombobox.setConverter(new StringConverter<>() { - @Override - public String toString(String theme) { - return AppContext.getString(theme); - } - - @Override - public String fromString(String s) { - return null; - } - }); - } - - private void setupLayoutOption() { - final Text layoutText = get("layout-text"); - layoutText.setText(AppContext.getString("layout-size")); - - final ComboBox layoutCombobox = get("layout-combobox"); - layoutCombobox.getItems().addAll("small", "medium", "large"); - layoutCombobox.setValue(AppSettings.getSettings().getLayoutSize()); - - layoutCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> { - AppSettings.getSettings().setLayoutSize(newValue); - App.setStyle(AppSettings.getSettings().getTheme(), newValue); - }); - - layoutCombobox.setConverter(new StringConverter<>() { - @Override - public String toString(String layout) { - return AppContext.getString(layout); - } - - @Override - public String fromString(String s) { - return null; - } - }); - } - - private void setupFullscreenOption() { - final Button fullscreenButton = get("fullscreen-button"); - - if (AppSettings.getSettings().getFullscreen()) { - fullscreenButton.setText(AppContext.getString("windowed")); - fullscreenButton.setOnAction(_ -> { - AppSettings.getSettings().setFullscreen(false); - App.setFullscreen(false); - }); - } else { - fullscreenButton.setText(AppContext.getString("fullscreen")); - fullscreenButton.setOnAction(_ -> { - AppSettings.getSettings().setFullscreen(true); - App.setFullscreen(true); - }); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/QuitView.java b/app/src/main/java/org/toop/app/view/views/QuitView.java deleted file mode 100644 index c2f37eb..0000000 --- a/app/src/main/java/org/toop/app/view/views/QuitView.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.toop.app.view.views; - -import org.toop.app.App; -import org.toop.app.view.View; -import org.toop.local.AppContext; - -import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.text.Text; - -public final class QuitView extends View { - public QuitView() { - super(false, "bg-popup"); - } - - @Override - public void setup() { - final Text sureHeader = header(); - sureHeader.setText(AppContext.getString("are-you-sure")); - - final Button yesButton = button(); - yesButton.setText(AppContext.getString("yes")); - yesButton.setOnAction(_ -> { App.quit(); }); - - final Button noButton = button(); - noButton.setText(AppContext.getString("no")); - noButton.setOnAction(_ -> { App.stopQuit(); }); - - add(Pos.CENTER, - fit(vbox( - sureHeader, - - hbox( - yesButton, - noButton - ) - )) - ); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/SendChallengeView.java b/app/src/main/java/org/toop/app/view/views/SendChallengeView.java deleted file mode 100644 index d72560c..0000000 --- a/app/src/main/java/org/toop/app/view/views/SendChallengeView.java +++ /dev/null @@ -1,119 +0,0 @@ -package org.toop.app.view.views; - -import org.toop.app.GameInformation; -import org.toop.app.Server; -import org.toop.app.view.View; -import org.toop.app.view.ViewStack; -import org.toop.local.AppContext; - -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Slider; -import javafx.scene.text.Text; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.BiConsumer; - -public final class SendChallengeView extends View { - private final Server server; - private final String opponent; - private final BiConsumer onSend; - - private final GameInformation.Player playerInformation; - - public SendChallengeView(Server server, String opponent, BiConsumer onSend) { - super(false, "bg-popup"); - - this.server = server; - this.opponent = opponent; - this.onSend = onSend; - - playerInformation = new GameInformation.Player(); - } - - @Override - public void setup() { - final Text challengeText = text(); - challengeText.setText(AppContext.getString("challenge")); - - final Text opponentHeader = header(); - opponentHeader.setText(opponent); - - final Text gameText = text(); - gameText.setText(AppContext.getString("to-a-game-of")); - - final ComboBox gamesCombobox = combobox(); - gamesCombobox.getItems().addAll(server.getGameList()); - gamesCombobox.setValue(gamesCombobox.getItems().getFirst()); - - final Button sendButton = button(); - sendButton.setText(AppContext.getString("send")); - sendButton.setOnAction(_ -> { onSend.accept(playerInformation, gamesCombobox.getValue()); }); - - final Button cancelButton = button(); - cancelButton.setText(AppContext.getString("cancel")); - cancelButton.setOnAction(_ -> { - ViewStack.pop(); }); - - final List nodes = new ArrayList<>(); - - if (playerInformation.isHuman) { - final Button playerToggle = button(); - playerToggle.setText(AppContext.getString("player")); - playerToggle.setOnAction(_ -> { - playerInformation.isHuman = false; - cleanup(); - setup(); - }); - - nodes.add(vbox(playerToggle)); - } else { - final Button computerToggle = button(); - computerToggle.setText(AppContext.getString("computer")); - computerToggle.setOnAction(_ -> { - playerInformation.isHuman = true; - cleanup(); - setup(); - }); - - nodes.add(vbox(computerToggle)); - - final Text computerDifficultyText = text(); - computerDifficultyText.setText(AppContext.getString("computer-difficulty")); - - final Slider computerDifficultySlider = slider(); - computerDifficultySlider.setMin(0); - computerDifficultySlider.setMax(Server.gameToType(gamesCombobox.getValue()).getMaxDepth()); - computerDifficultySlider.setValue(playerInformation.computerDifficulty); - computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> { - playerInformation.computerDifficulty = newValue.intValue(); - }); - - nodes.add(vbox(computerDifficultyText, computerDifficultySlider)); - } - - add(Pos.CENTER, - fit(hboxFill( - vboxFill( - challengeText, - opponentHeader, - gameText, - gamesCombobox, - separator(), - - hboxFill( - sendButton, - cancelButton - ) - ), - - vboxFill( - nodes.toArray(new Node[0]) - ) - )) - ); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/ServerView.java b/app/src/main/java/org/toop/app/view/views/ServerView.java deleted file mode 100644 index c819c14..0000000 --- a/app/src/main/java/org/toop/app/view/views/ServerView.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.toop.app.view.views; - -import org.toop.app.view.View; -import org.toop.app.view.ViewStack; -import org.toop.app.view.displays.SongDisplay; -import org.toop.local.AppContext; - -import javafx.application.Platform; -import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.control.ListView; -import javafx.scene.text.Text; - -import java.util.List; -import java.util.function.Consumer; - -public final class ServerView extends View { - private final String user; - private final Consumer onPlayerClicked; - private final Runnable onDisconnect; - - private ListView