diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java index 3115e34..3b4fef3 100644 --- a/app/src/main/java/org/toop/Main.java +++ b/app/src/main/java/org/toop/Main.java @@ -1,13 +1,6 @@ package org.toop; import org.toop.app.App; -import org.toop.framework.audio.*; -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) { diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java index f543a35..3093c1c 100644 --- a/app/src/main/java/org/toop/app/App.java +++ b/app/src/main/java/org/toop/app/App.java @@ -1,18 +1,20 @@ package org.toop.app; import javafx.application.Platform; -import javafx.scene.paint.Color; -import org.toop.Main; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; + import org.toop.app.widget.Primitive; -import org.toop.app.widget.Widget; 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; @@ -30,6 +32,8 @@ import javafx.scene.Scene; import javafx.scene.layout.StackPane; import javafx.stage.Stage; +import java.util.Objects; + public final class App extends Application { private static Stage stage; private static Scene scene; @@ -37,8 +41,6 @@ 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); } @@ -78,10 +80,10 @@ public final class App extends Application { App.width = (int)stage.getWidth(); App.height = (int)stage.getHeight(); - App.isQuitting = false; - AppSettings.applySettings(); + setKeybinds(root); + LoadingWidget loading = new LoadingWidget(Primitive.text( "Loading...", false), 0, 0, Integer.MAX_VALUE, false, false // Just set a high default ); @@ -130,6 +132,33 @@ public final class App extends Application { } + 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; + } + + 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); + } + private void setOnLoadingSuccess(LoadingWidget loading) { loading.setOnSuccess(() -> { initSystems(); @@ -140,17 +169,29 @@ public final class App extends Application { WidgetContainer.add(Pos.BOTTOM_RIGHT, new SongDisplay()); stage.setOnCloseRequest(event -> { event.consume(); - startQuit(); + + if (WidgetContainer.getAllWidgets().stream().anyMatch(e -> e instanceof QuitPopup)) return; + + QuitPopup a = new QuitPopup(); + a.show(Pos.CENTER); + }); }); } private void initSystems() { // TODO Move to better place - new Thread(() -> new NetworkingClientEventListener(new NetworkingClientManager())).start(); + new Thread(() -> new NetworkingClientEventListener( + GlobalEventBus.get(), + new NetworkingClientManager(GlobalEventBus.get())) + ).start(); new Thread(() -> { MusicManager musicManager = - new MusicManager<>(ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class), true); + new MusicManager<>( + GlobalEventBus.get(), + ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class), + true + ); SoundEffectManager soundEffectManager = new SoundEffectManager<>(ResourceManager.getAllOfType(SoundEffectAsset.class)); @@ -162,6 +203,7 @@ public final class App extends Application { .registerManager(VolumeControl.MUSIC, musicManager); new AudioEventListener<>( + GlobalEventBus.get(), musicManager, soundEffectManager, audioVolumeManager @@ -177,19 +219,6 @@ public final class App extends Application { } } - public static void startQuit() { - if (isQuitting) { - return; - } - - WidgetContainer.add(Pos.CENTER, new QuitPopup()); - isQuitting = true; - } - - public static void stopQuit() { - isQuitting = false; - } - 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/Server.java b/app/src/main/java/org/toop/app/Server.java index d3751e9..0c4ad0e 100644 --- a/app/src/main/java/org/toop/app/Server.java +++ b/app/src/main/java/org/toop/app/Server.java @@ -13,6 +13,7 @@ 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; @@ -32,7 +33,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public final class Server { - // TODO: Keep track of listeners. Remove them on Server connection close so reference is deleted. private String user = ""; private long clientId = -1; @@ -48,7 +48,7 @@ public final class Server { private ScheduledExecutorService scheduler; - private EventFlow eventFlow = new EventFlow(); + private EventFlow connectFlow; public static GameInformation.Type gameToType(String game) { if (game.equalsIgnoreCase("tic-tac-toe")) { @@ -89,78 +89,85 @@ public final class Server { Primitive.text("connecting"), 0, 0, reconnectAttempts, true, true ); - WidgetContainer.getCurrentView().transitionNext(loading); + WidgetContainer.getCurrentView().transitionNextCustom(loading, "disconnect", this::disconnect); var a = new EventFlow() .addPostEvent(NetworkEvents.StartClient.class, - new TournamentNetworkingClient(), + new TournamentNetworkingClient(GlobalEventBus.get()), new NetworkingConnector(ip, parsedPort, reconnectAttempts, 1, TimeUnit.SECONDS) ); loading.setOnFailure(() -> { - WidgetContainer.getCurrentView().transitionPrevious(); - a.unsubscribe("connecting"); - a.unsubscribe("startclient"); + if (WidgetContainer.getCurrentView() == loading) WidgetContainer.getCurrentView().transitionPrevious(); + a.unsubscribeAll(); WidgetContainer.add( Pos.CENTER, new ErrorPopup(AppContext.getString("connecting-failed") + " " + ip + ":" + port) ); }); - a.onResponse(NetworkEvents.StartClientResponse.class, e -> { + a.onResponse(NetworkEvents.CreatedIdForClient.class, e -> clientId = e.clientId(), true); + a.onResponse(NetworkEvents.StartClientResponse.class, e -> { if (!e.successful()) { return; } - WidgetContainer.getCurrentView().transitionPrevious(); + primary = new ServerView(user, this::sendChallenge); + WidgetContainer.getCurrentView().transitionNextCustom(primary, "disconnect", this::disconnect); a.unsubscribe("connecting"); a.unsubscribe("startclient"); this.user = user; - clientId = e.clientId(); new EventFlow().addPostEvent(new NetworkEvents.SendLogin(clientId, user)).postEvent(); - primary = new ServerView(user, this::sendChallenge, this::disconnect); - WidgetContainer.getCurrentView().transitionNext(primary); - startPopulateScheduler(); populateGameList(); + + primary.removeViewFromPreviousChain(loading); + }, false, "startclient") .listen( - NetworkEvents.ConnectTry.class, - e -> Platform.runLater( - () -> { - try { - loading.setAmount(e.amount()); - if (e.amount() >= loading.getMaxAmount()) { - loading.triggerFailure(); - } - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - ), - false, "connecting" - ) + 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(); - eventFlow.listen(NetworkEvents.ChallengeResponse.class, this::handleReceivedChallenge, false) - .listen(NetworkEvents.GameMatchResponse.class, this::handleMatchResponse, false) - .listen(NetworkEvents.GameResultResponse.class, this::handleGameResult, false) - .listen(NetworkEvents.GameMoveResponse.class, this::handleReceivedMove, false) - .listen(NetworkEvents.YourTurnResponse.class, this::handleYourTurn, false); + 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) { @@ -200,7 +207,7 @@ public final class Server { switch (type){ case TICTACTOE ->{ - players[myTurn] = new ArtificialPlayer<>(new TicTacToeAIR(), user); + players[myTurn] = new ArtificialPlayer<>(new TicTacToeAIR(9), user); } case REVERSI ->{ players[myTurn] = new ArtificialPlayer<>(new ReversiAIR(), user); @@ -250,11 +257,13 @@ public final class Server { 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) { @@ -265,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() { diff --git a/app/src/main/java/org/toop/app/gameControllers/ReversiController.java b/app/src/main/java/org/toop/app/gameControllers/ReversiController.java index 3c46306..1f6842f 100644 --- a/app/src/main/java/org/toop/app/gameControllers/ReversiController.java +++ b/app/src/main/java/org/toop/app/gameControllers/ReversiController.java @@ -99,7 +99,7 @@ public class ReversiController extends AbstractGameController { }); animation.play(); - primary.nextPlayer(true, getCurrentPlayer().getName(), game.getCurrentTurn() == 0 ? "X" : "O", getPlayer((game.getCurrentTurn() + 1) % 2).getName()); + primary.nextPlayer(true, getCurrentPlayer().getName(), game.getCurrentTurn() == 0 ? "X" : "O", getPlayer((game.getCurrentTurn() + 1) % 2).getName(), 'R'); } @Override diff --git a/app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java b/app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java index 6bd6fb0..eb8d8c9 100644 --- a/app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java +++ b/app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java @@ -39,7 +39,7 @@ public class TicTacToeController extends AbstractGameController { 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()); + primary.nextPlayer(true, getCurrentPlayer().getName(), game.getCurrentTurn() == 0 ? "X" : "O", getPlayer((game.getCurrentTurn() + 1) % 2).getName(), 'T'); drawMoves(); } diff --git a/app/src/main/java/org/toop/app/widget/Widget.java b/app/src/main/java/org/toop/app/widget/Widget.java index 5f7a269..7278a05 100644 --- a/app/src/main/java/org/toop/app/widget/Widget.java +++ b/app/src/main/java/org/toop/app/widget/Widget.java @@ -2,19 +2,27 @@ package org.toop.app.widget; import javafx.geometry.Pos; import javafx.scene.Node; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public interface Widget { + Logger logger = LogManager.getLogger(Widget.class); + Node getNode(); default void show(Pos position) { + logger.debug("Showing Widget: {} at position: {}", this.getNode(), position.toString()); WidgetContainer.add(position, this); } default void hide() { + logger.debug("Hiding Widget: {}", this.getNode()); WidgetContainer.remove(this); } default void replace(Pos position, Widget widget) { + logger.debug("Replacing Widget: {}, with widget: {}, to position: {}", + this.getNode(), widget.getNode(), position.toString()); widget.show(position); hide(); } diff --git a/app/src/main/java/org/toop/app/widget/WidgetContainer.java b/app/src/main/java/org/toop/app/widget/WidgetContainer.java index fe54d45..44dbc9c 100644 --- a/app/src/main/java/org/toop/app/widget/WidgetContainer.java +++ b/app/src/main/java/org/toop/app/widget/WidgetContainer.java @@ -1,5 +1,7 @@ package org.toop.app.widget; +import javafx.collections.ObservableList; +import javafx.scene.Node; import org.toop.app.widget.complex.PopupWidget; import org.toop.app.widget.complex.ViewWidget; @@ -7,6 +9,10 @@ import javafx.application.Platform; import javafx.geometry.Pos; import javafx.scene.layout.StackPane; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + public final class WidgetContainer { private static StackPane root; private static ViewWidget currentView; @@ -38,7 +44,7 @@ public final class WidgetContainer { root.getChildren().addFirst(view.getNode()); currentView = view; } else if (widget instanceof PopupWidget popup) { - currentView.add(Pos.CENTER, popup); + currentView.add(Pos.CENTER, (Widget) popup); } else { root.getChildren().add(widget.getNode()); } @@ -52,13 +58,61 @@ public final class WidgetContainer { Platform.runLater(() -> { if (widget instanceof PopupWidget popup) { - currentView.remove(popup); + currentView.remove((Widget) popup); } else { root.getChildren().remove(widget.getNode()); } }); } + public static void remove(Class widgetClass) { + if (root == null || currentView == null) return; + + Platform.runLater(() -> + currentView.getChildren().removeIf(widget -> widget.getClass().isAssignableFrom(widgetClass)) + ); + } + + public static void removeFirst(Class widgetClass) { + if (root == null || currentView == null) return; + + Platform.runLater(() -> { + for (Node widget : currentView.getChildren()) { + if (widgetClass.isAssignableFrom(widget.getClass())) { + currentView.getChildren().remove(widget); + break; + } + } + }); + } + + public static List find(Class widgetClass) { + if (root == null || currentView == null) return null; + + return getAllWidgets() + .stream() + .filter(widget -> widget.getClass().isAssignableFrom(widgetClass)) + .toList(); + } + + public static List find(Predicate predicate) { + if (root == null || currentView == null) return null; + + return getAllWidgets() + .stream() + .filter(predicate) + .toList(); + } + + public static Widget findFirst(Class widgetClass) { + if (root == null || currentView == null) return null; + + return getAllWidgets() + .stream() + .filter(widget -> widget.getClass().isAssignableFrom(widgetClass)) + .findFirst().orElse(null); + } + public static ViewWidget getCurrentView() { return currentView; } @@ -74,4 +128,22 @@ public final class WidgetContainer { currentView = view; }); } + + public static List getAllWidgets() { + final List children = new ArrayList<>(); + + for (var child : root.getChildren()) { + if (child instanceof Widget widget) { + children.add(widget); + } + } + + for (var child : currentView.getNode().getChildren()) { + if (child instanceof Widget widget) { + children.add(widget); + } + } + + return children; + } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java b/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java index 241c4ce..61f4007 100644 --- a/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java +++ b/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java @@ -2,6 +2,7 @@ package org.toop.app.widget.complex; import javafx.application.Platform; import javafx.geometry.Pos; +import javafx.scene.Node; import javafx.scene.control.Control; import javafx.scene.control.ProgressBar; import javafx.scene.control.ProgressIndicator; @@ -144,11 +145,11 @@ public class LoadingWidget extends ViewWidget implements Update { // TODO make o if (successTrigger.call()) { triggerSuccess(); - this.remove(this); + this.remove((Node) this); return; } else if (failureTrigger.call()) { triggerFailure(); - this.remove(this); + this.remove((Node) this); return; } diff --git a/app/src/main/java/org/toop/app/widget/complex/PlayerInfoWidget.java b/app/src/main/java/org/toop/app/widget/complex/PlayerInfoWidget.java index 90ec2a5..c69edf4 100644 --- a/app/src/main/java/org/toop/app/widget/complex/PlayerInfoWidget.java +++ b/app/src/main/java/org/toop/app/widget/complex/PlayerInfoWidget.java @@ -5,10 +5,13 @@ import org.toop.app.widget.Primitive; import javafx.scene.Node; import javafx.scene.layout.VBox; +import javafx.scene.text.Text; public class PlayerInfoWidget { private final GameInformation.Player information; private final VBox container; + private Text playerName; + private boolean hasSet; public PlayerInfoWidget(GameInformation.Player information) { this.information = information; @@ -16,6 +19,7 @@ public class PlayerInfoWidget { buildToggle().getNode(), buildContent() ); + this.playerName = null; } private ToggleWidget buildToggle() { @@ -33,51 +37,69 @@ public class PlayerInfoWidget { } private Node buildContent() { - if (information.isHuman) { - var nameInput = new LabeledInputWidget( - "name", - "enter-your-name", - information.name, - newName -> information.name = newName - ); + if (information.isHuman) { + var nameInput = new LabeledInputWidget( + "name", + "enter-your-name", + information.name, + newName -> information.name = newName + ); - return nameInput.getNode(); - } else { - if (information.name == null || information.name.isEmpty()) { - information.name = "Pism Bot"; - } + return nameInput.getNode(); + } else { + var AIBox = Primitive.vbox( + makeAIButton(0, 1, "zwartepiet"), + makeAIButton(2, 1, "sinterklaas"), + makeAIButton(9, 1, "santa") + ); - var playerName = Primitive.text(""); - playerName.setText(information.name); + this.playerName = Primitive.text(""); + playerName.setText(information.name); - var nameDisplay = Primitive.vbox( - Primitive.text("name"), - playerName - ); + var nameDisplay = Primitive.vbox( + Primitive.text("name"), + playerName + ); - var difficultySlider = new LabeledSliderWidget( - "computer-difficulty", - 0, 5, - information.computerDifficulty, - newVal -> information.computerDifficulty = newVal - ); + if (!hasSet) { + doDefault(); + hasSet = true; + } - var thinkTimeSlider = new LabeledSliderWidget( - "computer-think-time", - 0, 5, - information.computerThinkTime, - newVal -> information.computerThinkTime = newVal - ); + return Primitive.vbox( + AIBox, + nameDisplay + ); - return Primitive.vbox( - nameDisplay, - difficultySlider.getNode(), - thinkTimeSlider.getNode() - ); - } - } + } + } public Node getNode() { return container; } + + private Node makeAIButton(int depth, int thinktime, String name) { + return Primitive.button(name, () -> { + information.name = getName(name); + information.computerDifficulty = depth; + information.computerThinkTime = thinktime; + this.playerName.setText(getName(name)); + }); + } + + private String getName(String name) { + return switch (name) { + case "sinterklaas" -> "Sint. R. Klaas"; + case "zwartepiet" -> "Zwarte Piet"; + case "santa" -> "Santa"; + default -> "Default"; + }; + } + + private void doDefault() { + information.name = getName("zwartepiet"); + information.computerDifficulty = 0; + information.computerThinkTime = 1; + this.playerName.setText(getName("zwartepiet")); + } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/widget/complex/StackWidget.java b/app/src/main/java/org/toop/app/widget/complex/StackWidget.java index 1bb70ff..3027c2e 100644 --- a/app/src/main/java/org/toop/app/widget/complex/StackWidget.java +++ b/app/src/main/java/org/toop/app/widget/complex/StackWidget.java @@ -7,22 +7,19 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.layout.StackPane; -public abstract class StackWidget implements Widget { - private final StackPane container; - +public abstract class StackWidget extends StackPane implements Widget { public StackWidget(String cssClass) { - container = new StackPane(); - container.getStyleClass().add(cssClass); + this.getStyleClass().add(cssClass); } public void add(Pos position, Node node) { Platform.runLater(() -> { - if (container.getChildren().contains(node)) { + if (this.getChildren().contains(node)) { return; } StackPane.setAlignment(node, position); - container.getChildren().add(node); + this.getChildren().add(node); }); } @@ -32,7 +29,7 @@ public abstract class StackWidget implements Widget { public void remove(Node node) { Platform.runLater(() -> { - container.getChildren().remove(node); + this.getChildren().remove(node); }); } @@ -41,7 +38,7 @@ public abstract class StackWidget implements Widget { } @Override - public Node getNode() { - return container; + public StackPane getNode() { + return this; } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/widget/complex/ViewWidget.java b/app/src/main/java/org/toop/app/widget/complex/ViewWidget.java index fd0dbb1..217ac86 100644 --- a/app/src/main/java/org/toop/app/widget/complex/ViewWidget.java +++ b/app/src/main/java/org/toop/app/widget/complex/ViewWidget.java @@ -37,6 +37,19 @@ public abstract class ViewWidget extends StackWidget { view.add(Pos.BOTTOM_LEFT, Primitive.vbox(backButton)); } + public void transitionNextCustom(ViewWidget view, String key, Runnable runnable) { + view.previous = this; + + replace(Pos.CENTER, view); + + var customButton = Primitive.button(key, () -> { + runnable.run(); + view.transitionPrevious(); + }); + + view.add(Pos.BOTTOM_LEFT, Primitive.vbox(customButton)); + } + public void transitionPrevious() { if (previous == null) { return; @@ -46,6 +59,38 @@ public abstract class ViewWidget extends StackWidget { previous = null; } + public void removeIndexFromPreviousChain(int index) { + ViewWidget view = this; + + while (index > 0 && view != null) { + index--; + + if (index == 0) { + if (view.previous != null && view.previous.previous != null) { + view.previous = view.previous.previous; + } + } + + view = view.previous; + } + } + + public void removeViewFromPreviousChain(ViewWidget view) { + ViewWidget prev = previous; + int index = 0; + + while (prev != null) { + index++; + + if (prev == view) { + removeIndexFromPreviousChain(index); + break; + } + + prev = prev.previous; + } + } + public void reload(ViewWidget view) { view.previous = previous; replace(Pos.CENTER, view); diff --git a/app/src/main/java/org/toop/app/widget/display/SongDisplay.java b/app/src/main/java/org/toop/app/widget/display/SongDisplay.java index ab9c345..9df8f21 100644 --- a/app/src/main/java/org/toop/app/widget/display/SongDisplay.java +++ b/app/src/main/java/org/toop/app/widget/display/SongDisplay.java @@ -49,11 +49,11 @@ public class SongDisplay extends VBox implements Widget { previousButton.getStyleClass().setAll("previous-button"); skipButton.setOnAction( event -> { - GlobalEventBus.post(new AudioEvents.SkipMusic()); + GlobalEventBus.get().post(new AudioEvents.SkipMusic()); }); pauseButton.setOnAction(event -> { - GlobalEventBus.post(new AudioEvents.PauseMusic()); + GlobalEventBus.get().post(new AudioEvents.PauseMusic()); if (pauseButton.getText().equals("⏸")) { pauseButton.setText("▶"); } @@ -63,7 +63,7 @@ public class SongDisplay extends VBox implements Widget { }); previousButton.setOnAction( event -> { - GlobalEventBus.post(new AudioEvents.PreviousMusic()); + GlobalEventBus.get().post(new AudioEvents.PreviousMusic()); }); HBox control = new HBox(10, previousButton, pauseButton, skipButton); diff --git a/app/src/main/java/org/toop/app/widget/popup/ChallengePopup.java b/app/src/main/java/org/toop/app/widget/popup/ChallengePopup.java index 1a2f755..cd4d87c 100644 --- a/app/src/main/java/org/toop/app/widget/popup/ChallengePopup.java +++ b/app/src/main/java/org/toop/app/widget/popup/ChallengePopup.java @@ -8,6 +8,7 @@ import org.toop.app.widget.complex.PopupWidget; import java.util.function.Consumer; import javafx.geometry.Pos; +import org.toop.local.AppContext; public final class ChallengePopup extends PopupWidget { private final GameInformation.Player playerInformation; @@ -28,19 +29,22 @@ public final class ChallengePopup extends PopupWidget { private void setupLayout() { var challengeText = Primitive.text("you-were-challenged-by"); - var challengerHeader = Primitive.header(""); - challengerHeader.setText(challenger); + var challengerHeader = Primitive.header(challenger, false); - var gameText = Primitive.text("to-a-game-of"); - gameText.setText(gameText.getText() + " " + game); + var toAGameOfText = Primitive.text("to-a-game-of"); + var gameHeader = Primitive.header(game, false); - var acceptButton = Primitive.button("accept", () -> onAccept.accept(playerInformation)); + var acceptButton = Primitive.button("accept", () -> { + onAccept.accept(playerInformation); + this.hide(); + }); var denyButton = Primitive.button("deny", () -> hide()); var leftSection = Primitive.vbox( challengeText, challengerHeader, - gameText, + toAGameOfText, + gameHeader, Primitive.separator(), Primitive.hbox( acceptButton, diff --git a/app/src/main/java/org/toop/app/widget/popup/EscapePopup.java b/app/src/main/java/org/toop/app/widget/popup/EscapePopup.java new file mode 100644 index 0000000..d742ab6 --- /dev/null +++ b/app/src/main/java/org/toop/app/widget/popup/EscapePopup.java @@ -0,0 +1,48 @@ +package org.toop.app.widget.popup; + +import javafx.geometry.Pos; +import javafx.scene.Node; +import org.toop.app.widget.Primitive; +import org.toop.app.widget.Widget; +import org.toop.app.widget.WidgetContainer; +import org.toop.app.widget.complex.PopupWidget; +import org.toop.app.widget.complex.ViewWidget; +import org.toop.app.widget.view.GameView; +import org.toop.app.widget.view.OptionsView; +import org.toop.local.AppContext; + +import java.util.ArrayList; + +public class EscapePopup extends PopupWidget { + public EscapePopup() { + ViewWidget currentView = WidgetContainer.getCurrentView(); + ArrayList nodes = new ArrayList<>(); + + nodes.add(Primitive.button("Continue", this::hide, false)); // TODO, localize + + if (!(currentView.getClass().isAssignableFrom(OptionsView.class))) { + var opt = Primitive.button("options", () -> { + hide(); + WidgetContainer.getCurrentView().transitionNext(new OptionsView()); + }); + nodes.add(opt); + } + + if (currentView.getClass().isAssignableFrom(GameView.class)) { + Widget tut = AppContext.currentTutorial(); + if (tut != null) { + nodes.add(Primitive.button("tutorialstring", () -> { + WidgetContainer.getCurrentView().add(Pos.CENTER, tut); + })); + } + } + + nodes.add(Primitive.button("quit", () -> { + hide(); + WidgetContainer.add(Pos.CENTER, new QuitPopup()); + })); + + add(Pos.CENTER, Primitive.vbox(nodes.toArray(new Node[0]))); + + } +} diff --git a/app/src/main/java/org/toop/app/widget/popup/QuitPopup.java b/app/src/main/java/org/toop/app/widget/popup/QuitPopup.java index 264e936..593a7d8 100644 --- a/app/src/main/java/org/toop/app/widget/popup/QuitPopup.java +++ b/app/src/main/java/org/toop/app/widget/popup/QuitPopup.java @@ -15,14 +15,12 @@ public class QuitPopup extends PopupWidget { }); confirmWidget.addButton("no", () -> { - App.stopQuit(); hide(); }); add(Pos.CENTER, confirmWidget); setOnPop(() -> { - App.stopQuit(); hide(); }); } diff --git a/app/src/main/java/org/toop/app/widget/popup/SendChallengePopup.java b/app/src/main/java/org/toop/app/widget/popup/SendChallengePopup.java index 6299fdc..bc42486 100644 --- a/app/src/main/java/org/toop/app/widget/popup/SendChallengePopup.java +++ b/app/src/main/java/org/toop/app/widget/popup/SendChallengePopup.java @@ -34,7 +34,7 @@ public final class SendChallengePopup extends PopupWidget { // --- Left side: challenge text and buttons --- var challengeText = Primitive.text("challenge"); - var opponentHeader = Primitive.header(opponent); + var opponentHeader = Primitive.header(opponent, false); var gameText = Primitive.text("to-a-game-of"); @@ -61,7 +61,7 @@ public final class SendChallengePopup extends PopupWidget { var sendButton = Primitive.button( "send", - () -> onSend.accept(playerInformation, gameChoice.getValue()) + () -> { onSend.accept(playerInformation, gameChoice.getValue()); this.hide(); } ); var cancelButton = Primitive.button("cancel", () -> hide()); diff --git a/app/src/main/java/org/toop/app/widget/tutorial/BaseTutorialWidget.java b/app/src/main/java/org/toop/app/widget/tutorial/BaseTutorialWidget.java index ee963b5..9d48840 100644 --- a/app/src/main/java/org/toop/app/widget/tutorial/BaseTutorialWidget.java +++ b/app/src/main/java/org/toop/app/widget/tutorial/BaseTutorialWidget.java @@ -59,8 +59,6 @@ public class BaseTutorialWidget extends PopupWidget implements Updatable { var x = Primitive.vbox(imagery, tutorialText); add(Pos.CENTER, Primitive.vbox(x, w)); - - WidgetContainer.add(Pos.CENTER, this); } @Override diff --git a/app/src/main/java/org/toop/app/widget/view/GameView.java b/app/src/main/java/org/toop/app/widget/view/GameView.java index b866ef4..94d8cd7 100644 --- a/app/src/main/java/org/toop/app/widget/view/GameView.java +++ b/app/src/main/java/org/toop/app/widget/view/GameView.java @@ -1,34 +1,43 @@ package org.toop.app.widget.view; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.text.Font; import org.toop.app.widget.Primitive; import org.toop.app.widget.complex.ViewWidget; import org.toop.app.widget.popup.GameOverPopup; - import java.util.function.Consumer; - import javafx.application.Platform; import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.TextField; import javafx.scene.text.Text; -import org.toop.app.widget.tutorial.BaseTutorialWidget; import org.toop.app.widget.tutorial.Connect4TutorialWidget; import org.toop.app.widget.tutorial.ReversiTutorialWidget; import org.toop.app.widget.tutorial.TicTacToeTutorialWidget; +import org.toop.local.AppContext; public final class GameView extends ViewWidget { - private final Text currentPlayerHeader; - private final Text currentMoveHeader; - private final Text nextPlayerHeader; + private final Text playerHeader; + private final Text turnHeader; + private final Text player1Header; + private final Text player2Header; + private Circle player1Icon; + private Circle player2Icon; private final Button forfeitButton; private final Button exitButton; - private final Button tutorialButton; private final TextField chatInput; + private final Text keyThingy; + private boolean hasSet = false; public GameView(Runnable onForfeit, Runnable onExit, Consumer onMessage, String gameType) { - currentPlayerHeader = Primitive.header(""); - currentMoveHeader = Primitive.header(""); - nextPlayerHeader = Primitive.header(""); + playerHeader = Primitive.header(""); + turnHeader = Primitive.header(""); + keyThingy = Primitive.text("turnof"); + player1Header = Primitive.header(""); + player2Header = Primitive.header(""); + player1Icon = new Circle(); + player2Icon = new Circle(); if (onForfeit != null) { forfeitButton = Primitive.button("forfeit", () -> onForfeit.run()); @@ -51,32 +60,27 @@ public final class GameView extends ViewWidget { chatInput = null; } - switch(gameType) { + switch (gameType) { case "TicTacToe": - this.tutorialButton = Primitive.button("tutorialstring", () -> new TicTacToeTutorialWidget(() -> {})); break; + AppContext.setCurrentTutorial(new TicTacToeTutorialWidget(() -> {})); + break; case "Reversi": - this.tutorialButton = Primitive.button("tutorialstring", () -> new ReversiTutorialWidget(() -> {})); break; + AppContext.setCurrentTutorial(new ReversiTutorialWidget(() -> {})); + break; case "Connect4": - this.tutorialButton = Primitive.button("tutorialstring", () -> new Connect4TutorialWidget(() -> {})); break; - default: - this.tutorialButton = null; break; + AppContext.setCurrentTutorial(new Connect4TutorialWidget(() -> {})); + break; } setupLayout(); } private void setupLayout() { - var playerInfo = Primitive.vbox( - currentPlayerHeader, - Primitive.hbox( - Primitive.separator(), - currentMoveHeader, - Primitive.separator() - ), - nextPlayerHeader - ); + var turnInfo = Primitive.vbox( + turnHeader + ); - add(Pos.TOP_RIGHT, playerInfo); + add(Pos.TOP_CENTER, turnInfo); var buttons = Primitive.vbox( forfeitButton, @@ -88,27 +92,88 @@ public final class GameView extends ViewWidget { if (chatInput != null) { add(Pos.BOTTOM_RIGHT, Primitive.vbox(chatInput)); } - - if (tutorialButton != null) { - add(Pos.TOP_LEFT, tutorialButton); - } } - public void nextPlayer(boolean isMe, String currentPlayer, String currentMove, String nextPlayer) { + public void nextPlayer(boolean isMe, String currentPlayer, String currentMove, String nextPlayer, char GameType) { Platform.runLater(() -> { - currentPlayerHeader.setText(currentPlayer); - currentMoveHeader.setText(currentMove); - nextPlayerHeader.setText(nextPlayer); - - if (isMe) { - currentPlayerHeader.getStyleClass().add("my-turn"); - } else { - currentPlayerHeader.getStyleClass().remove("my-turn"); - } + if (!(hasSet)) { + playerHeader.setText(currentPlayer + " vs. " + nextPlayer); + hasSet = true; + setPlayerHeaders(isMe, currentPlayer, nextPlayer, GameType); + } + //TODO idk if theres any way to check this? only EN uses 's and the rest doesnt. if theres a better way to do this pls let me know + if (AppContext.getLocale().toLanguageTag().equals("en")) { + turnHeader.setText(currentPlayer + keyThingy.getText()); + } }); } public void gameOver(boolean iWon, String winner) { new GameOverPopup(iWon, winner).show(Pos.CENTER); } + + private void setPlayerHeaders(boolean isMe, String currentPlayer, String nextPlayer, char GameType) { + if (GameType == 'T') { + if (isMe) { + player1Header.setText("X: " + currentPlayer); + player2Header.setText("O: " + nextPlayer); + } + else { + player1Header.setText("X: " + nextPlayer); + player2Header.setText("O: " + currentPlayer); + } + setPlayerInfoTTT(); + } + else if (GameType == 'R') { + if (isMe) { + player1Header.setText(currentPlayer); + player2Header.setText(nextPlayer); + } + else { + player1Header.setText(nextPlayer); + player2Header.setText(currentPlayer); + } + setPlayerInfoReversi(); + } + } + + private void setPlayerInfoTTT() { + var playerInfo = Primitive.vbox( + playerHeader, + Primitive.separator(), + player1Header, + player2Header + ); + + add(Pos.TOP_RIGHT, playerInfo); + } + + private void setPlayerInfoReversi() { + var player1box = Primitive.hbox( + player1Icon, + player1Header + ); + + player1box.getStyleClass().add("hboxspacing"); + + var player2box = Primitive.hbox( + player2Icon, + player2Header + ); + + player2box.getStyleClass().add("hboxspacing"); + + var playerInfo = Primitive.vbox( + playerHeader, + Primitive.separator(), + player1box, + player2box + ); + + player1Icon.setRadius(player1Header.fontProperty().map(Font::getSize).getValue()); + player2Icon.setRadius(player2Header.fontProperty().map(Font::getSize).getValue()); + player1Icon.setFill(Color.BLACK); + player2Icon.setFill(Color.WHITE); + add(Pos.TOP_RIGHT, playerInfo); + } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java b/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java index cbbf702..aa9cefe 100644 --- a/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java +++ b/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java @@ -6,6 +6,7 @@ import org.toop.app.gameControllers.AbstractGameController; import org.toop.app.gameControllers.ReversiController; import org.toop.app.gameControllers.TicTacToeController; import org.toop.framework.gameFramework.model.player.Player; +import org.toop.game.games.tictactoe.TicTacToeAIRSleep; import org.toop.game.players.ArtificialPlayer; import org.toop.game.players.LocalPlayer; import org.toop.app.widget.Primitive; @@ -52,12 +53,12 @@ public class LocalMultiplayerView extends ViewWidget { if (information.players[0].isHuman) { players[0] = new LocalPlayer<>(information.players[0].name); } else { - players[0] = new ArtificialPlayer<>(new TicTacToeAIR(), information.players[0].name); + players[0] = new ArtificialPlayer<>(new TicTacToeAIRSleep(information.players[0].computerDifficulty, information.players[1].computerThinkTime), information.players[0].name); } if (information.players[1].isHuman) { players[1] = new LocalPlayer<>(information.players[1].name); } else { - players[1] = new ArtificialPlayer<>(new TicTacToeAIR(), information.players[1].name); + players[1] = new ArtificialPlayer<>(new TicTacToeAIRSleep(information.players[1].computerDifficulty, information.players[1].computerThinkTime), information.players[1].name); } if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstTTT()) { new ShowEnableTutorialWidget( diff --git a/app/src/main/java/org/toop/app/widget/view/MainView.java b/app/src/main/java/org/toop/app/widget/view/MainView.java index 15e5c03..ee93350 100644 --- a/app/src/main/java/org/toop/app/widget/view/MainView.java +++ b/app/src/main/java/org/toop/app/widget/view/MainView.java @@ -4,6 +4,7 @@ import org.toop.app.App; import org.toop.app.widget.Primitive; import org.toop.app.widget.complex.ViewWidget; import javafx.geometry.Pos; +import org.toop.app.widget.popup.QuitPopup; public class MainView extends ViewWidget { public MainView() { @@ -24,7 +25,8 @@ public class MainView extends ViewWidget { }); var quitButton = Primitive.button("quit", () -> { - App.startQuit(); + var a = new QuitPopup(); + a.show(Pos.CENTER); }); add(Pos.CENTER, Primitive.vbox( diff --git a/app/src/main/java/org/toop/app/widget/view/ServerView.java b/app/src/main/java/org/toop/app/widget/view/ServerView.java index 94da1e2..7365e74 100644 --- a/app/src/main/java/org/toop/app/widget/view/ServerView.java +++ b/app/src/main/java/org/toop/app/widget/view/ServerView.java @@ -14,14 +14,12 @@ import javafx.scene.control.ListView; public final class ServerView extends ViewWidget { private final String user; private final Consumer onPlayerClicked; - private final Runnable onDisconnect; private final ListView