diff --git a/app/src/main/java/org/toop/app/Server.java b/app/src/main/java/org/toop/app/Server.java index 222a3d3..f28e7f3 100644 --- a/app/src/main/java/org/toop/app/Server.java +++ b/app/src/main/java/org/toop/app/Server.java @@ -1,10 +1,13 @@ package org.toop.app; +import javafx.application.Platform; +import javafx.geometry.Pos; import org.toop.app.game.Connect4Game; -import org.toop.app.game.gameControllers.AbstractGameController; -import org.toop.app.game.gameControllers.ReversiController; -import org.toop.app.game.gameControllers.TicTacToeController; +import org.toop.app.game.ReversiGame; +import org.toop.app.game.TicTacToeGame; +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; @@ -13,11 +16,6 @@ import org.toop.framework.eventbus.EventFlow; 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.players.AbstractPlayer; -import org.toop.game.reversi.ReversiAIR; -import org.toop.game.tictactoe.TicTacToeAIR; import org.toop.local.AppContext; import java.util.List; @@ -28,7 +26,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; @@ -38,14 +35,10 @@ 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 eventFlow = new EventFlow(); - public static GameInformation.Type gameToType(String game) { if (game.equalsIgnoreCase("tic-tac-toe")) { return GameInformation.Type.TICTACTOE; @@ -60,9 +53,6 @@ public final class Server { 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")); @@ -83,7 +73,13 @@ public final class Server { return; } - final int reconnectAttempts = 10; + final int reconnectAttempts = 5; + + LoadingWidget loading = new LoadingWidget( + Primitive.text("connecting"), 0, 0, reconnectAttempts + ); + + WidgetContainer.getCurrentView().transitionNext(loading); var a = new EventFlow() .addPostEvent(NetworkEvents.StartClient.class, @@ -91,8 +87,31 @@ public final class Server { new NetworkingConnector(ip, parsedPort, reconnectAttempts, 1, TimeUnit.SECONDS) ); + loading.setOnFailure(() -> { + WidgetContainer.getCurrentView().transitionPrevious(); + a.unsubscribe("connecting"); + WidgetContainer.add( + Pos.CENTER, + new ErrorPopup(AppContext.getString("connecting-failed") + " " + ip + ":" + port) + ); + }); + a.onResponse(NetworkEvents.StartClientResponse.class, e -> { + if (!e.successful()) { +// loading.triggerFailure(); + return; + } + + try { + TimeUnit.MILLISECONDS.sleep(500); // TODO temp fix for index bug + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + + WidgetContainer.getCurrentView().transitionPrevious(); + + a.unsubscribe("connecting"); a.unsubscribe("startclient"); this.user = user; @@ -103,18 +122,27 @@ public final class Server { primary = new ServerView(user, this::sendChallenge, this::disconnect); WidgetContainer.getCurrentView().transitionNext(primary); - startPopulateScheduler(); - populateGameList(); + startPopulateScheduler(); + populateGameList(); + }, false, "startclient") + .listen( + NetworkEvents.ConnectTry.class, + e -> Platform.runLater( + () -> { + try { + loading.setAmount(e.amount()); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + ), + false, "connecting" + ) + .postEvent(); - }).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); - startPopulateScheduler(); - populateGameList(); + new EventFlow() + .listen(NetworkEvents.ChallengeResponse.class, this::handleReceivedChallenge, false) + .listen(NetworkEvents.GameMatchResponse.class, this::handleMatchResponse, false); } private void sendChallenge(String opponent) { @@ -127,16 +155,10 @@ public final class Server { } private void handleMatchResponse(NetworkEvents.GameMatchResponse response) { - // TODO: Redo all of this mess - if (gameController != null) { - gameController.stop(); - } - - gameController = null; - - //if (!isPolling) return; + if (!isPolling) return; String gameType = extractQuotedValue(response.gameType()); + if (response.clientId() == clientId) { isPolling = false; onlinePlayers.clear(); @@ -157,58 +179,21 @@ public final class Server { information.players[0].computerThinkTime = 1; information.players[1].name = response.opponent(); - AbstractPlayer[] players = new AbstractPlayer[2]; - - players[(myTurn + 1) % 2] = new OnlinePlayer(response.opponent()); - - switch (type){ - case TICTACTOE ->{ - players[myTurn] = new ArtificialPlayer<>(new TicTacToeAIR(), user); - } - case REVERSI ->{ - players[myTurn] = new ArtificialPlayer<>(new ReversiAIR(), user); - } - } - Runnable onGameOverRunnable = isSingleGame.get()? null: this::gameOver; + + switch (type) { - case TICTACTOE ->{ - gameController = new TicTacToeController(players, false); - } + case TICTACTOE -> + new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); case REVERSI -> - gameController = new ReversiController(players, false); + new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); case CONNECT4 -> new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); default -> new ErrorPopup("Unsupported game type."); } - - if (gameController != null){ - gameController.start(); - } } } - private void handleYourTurn(NetworkEvents.YourTurnResponse response) { - if (gameController == null) { - return; - } - gameController.yourTurn(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.moveReceived(response); - } - private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) { if (!isPolling) return; diff --git a/app/src/main/java/org/toop/app/widget/Primitive.java b/app/src/main/java/org/toop/app/widget/Primitive.java index a50e4e4..451aebe 100644 --- a/app/src/main/java/org/toop/app/widget/Primitive.java +++ b/app/src/main/java/org/toop/app/widget/Primitive.java @@ -1,6 +1,8 @@ package org.toop.app.widget; import javafx.scene.image.ImageView; +import org.toop.framework.audio.events.AudioEvents; +import org.toop.framework.eventbus.EventFlow; import org.toop.framework.resource.resources.ImageAsset; import org.toop.local.AppContext; @@ -73,8 +75,11 @@ public final class Primitive { } if (onAction != null) { - button.setOnAction(_ -> - onAction.run()); + button.setOnAction(_ -> { + onAction.run(); + playButtonSound(); + System.out.println("HI I got called button"); + }); } return button; @@ -115,12 +120,18 @@ public final class Primitive { slider.setMax(max); slider.setValue(value); - if (onValueChanged != null) { - slider.valueProperty().addListener((_, _, newValue) -> - onValueChanged.accept(newValue.intValue())); - } + if (onValueChanged != null) { + slider.valueProperty().addListener((_, _, newValue) -> { + onValueChanged.accept(newValue.intValue()); + }); + } - return slider; + slider.setOnMouseReleased(event -> { + playButtonSound(); + System.out.println("I got called!"); + }); + + return slider; } @SafeVarargs @@ -137,9 +148,12 @@ public final class Primitive { } if (onValueChanged != null) { - choice.valueProperty().addListener((_, _, newValue) -> - onValueChanged.accept(newValue)); - } + choice.valueProperty().addListener((_, _, newValue) -> { + onValueChanged.accept(newValue); + playButtonSound(); + System.out.println("hi i got called choice"); + }); + } choice.setItems(FXCollections.observableArrayList(items)); @@ -191,4 +205,8 @@ public final class Primitive { return vbox; } + + private static void playButtonSound() { + new EventFlow().addPostEvent(new AudioEvents.ClickButton()).postEvent(); + } } \ 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 461110f..3b97357 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,26 +2,45 @@ package org.toop.app.widget.complex; import javafx.geometry.Pos; import javafx.scene.control.ProgressBar; -import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import org.toop.app.widget.Primitive; + +import java.util.concurrent.Callable; public class LoadingWidget extends ViewWidget implements Update { // TODO make of widget type private final ProgressBar progressBar; + private final Text loadingText; private Runnable success = () -> {}; private Runnable failure = () -> {}; + private boolean successTriggered = false; + private boolean failureTriggered = false; private int maxAmount; + private int minAmount; private int amount; + private Callable successTrigger = () -> (amount >= maxAmount); + private Callable failureTrigger = () -> (amount < minAmount); private float percentage = 0.0f; - - public LoadingWidget(int startAmount, int maxAmount) { - - amount = startAmount; + /** + * + * Widget that shows a loading bar. + * + * @param loadingText Text above the loading bar. + * @param minAmount The minimum amount. + * @param startAmount The starting amount. + * @param maxAmount The max amount. + */ + public LoadingWidget(Text loadingText, int minAmount, int startAmount, int maxAmount) { this.maxAmount = maxAmount; + this.minAmount = minAmount; + amount = startAmount; progressBar = new ProgressBar(); + this.loadingText = loadingText; - HBox box = new HBox(10, progressBar); + VBox box = Primitive.vbox(this.loadingText, progressBar); add(Pos.CENTER, box); } @@ -29,42 +48,88 @@ public class LoadingWidget extends ViewWidget implements Update { // TODO make o this.maxAmount = maxAmount; } - public void setAmount(int amount) { + public void setAmount(int amount) throws Exception { this.amount = amount; update(); } - public void setAmount() { - setAmount(this.amount+1); + public int getMaxAmount() { + return maxAmount; } + public int getAmount() { + return amount; + } + + public float getPercentage() { + return percentage; + } + + public boolean isTriggered() { + return (failureTriggered || successTriggered); + } + + /** + * What to do when success is triggered. + * @param onSuccess The lambda that gets run on success. + */ public void setOnSuccess(Runnable onSuccess) { success = onSuccess; } + /** + * What to do when failure is triggered. + * @param onFailure The lambda that gets run on failure. + */ public void setOnFailure(Runnable onFailure) { failure = onFailure; } + /** + * The trigger to activate onSuccess. + * @param trigger The lambda that triggers onSuccess. + */ + public void setSuccessTrigger(Callable trigger) { + successTrigger = trigger; + } + + /** + * The trigger to activate onFailure. + * @param trigger The lambda that triggers onFailure. + */ + public void setFailureTrigger(Callable trigger) { + failureTrigger = trigger; + } + + /** + * Forcefully trigger success. + */ public void triggerSuccess() { + successTriggered = true; // TODO, else it will double call... why? success.run(); } + /** + * Forcefully trigger failure. + */ public void triggerFailure() { + failureTriggered = true; // TODO, else it will double call... why? failure.run(); } @Override - public void update() { - if (amount >= maxAmount) { + public void update() throws Exception { // TODO Better exception + if (successTriggered || failureTriggered) { // If already triggered, throw exception. + throw new RuntimeException(); + } + + if (successTrigger.call()) { triggerSuccess(); - System.out.println("triggered"); - this.hide(); + this.remove(this); return; - } else if (amount < 0) { + } else if (failureTrigger.call()) { triggerFailure(); - System.out.println("triggerFailure"); - this.hide(); + this.remove(this); return; } diff --git a/app/src/main/java/org/toop/app/widget/complex/ToggleWidget.java b/app/src/main/java/org/toop/app/widget/complex/ToggleWidget.java index 67f338c..1ffe419 100644 --- a/app/src/main/java/org/toop/app/widget/complex/ToggleWidget.java +++ b/app/src/main/java/org/toop/app/widget/complex/ToggleWidget.java @@ -2,6 +2,8 @@ package org.toop.app.widget.complex; import org.toop.app.widget.Primitive; import org.toop.app.widget.Widget; +import org.toop.framework.audio.events.AudioEvents; +import org.toop.framework.eventbus.EventFlow; import org.toop.local.AppContext; import java.util.function.Consumer; @@ -29,8 +31,9 @@ public class ToggleWidget implements Widget { state = !state; updateText(); if (onToggle != null) { - onToggle.accept(state); - } + onToggle.accept(state); + new EventFlow().addPostEvent(new AudioEvents.ClickButton()).postEvent(); // TODO FIX PRIMITIVES + } }); container = Primitive.vbox(button); diff --git a/app/src/main/java/org/toop/app/widget/complex/Update.java b/app/src/main/java/org/toop/app/widget/complex/Update.java index c44fe00..db93c38 100644 --- a/app/src/main/java/org/toop/app/widget/complex/Update.java +++ b/app/src/main/java/org/toop/app/widget/complex/Update.java @@ -1,5 +1,5 @@ package org.toop.app.widget.complex; public interface Update { - void update(); + void update() throws Exception; } diff --git a/app/src/main/java/org/toop/app/widget/view/OnlineView.java b/app/src/main/java/org/toop/app/widget/view/OnlineView.java index 16ed1df..3b0608c 100644 --- a/app/src/main/java/org/toop/app/widget/view/OnlineView.java +++ b/app/src/main/java/org/toop/app/widget/view/OnlineView.java @@ -3,6 +3,7 @@ package org.toop.app.widget.view; import org.toop.app.Server; import org.toop.app.widget.Primitive; import org.toop.app.widget.complex.LabeledInputWidget; +import org.toop.app.widget.complex.LoadingWidget; import org.toop.app.widget.complex.ViewWidget; import javafx.geometry.Pos; diff --git a/app/src/main/resources/assets/localization/localization_en.properties b/app/src/main/resources/assets/localization/localization_en.properties index 44043fc..0d70ace 100644 --- a/app/src/main/resources/assets/localization/localization_en.properties +++ b/app/src/main/resources/assets/localization/localization_en.properties @@ -9,6 +9,8 @@ computer-difficulty=Computer difficulty computer-think-time=Computer think time computer=Computer connect=Connect +connecting=Connecting to server... +connecting-failed=Could not connect to server: credits=Credits dark=Dark deny=Deny