Merge remote-tracking branch 'origin/Development' into Development

This commit is contained in:
ramollia
2025-12-03 12:49:14 +01:00
7 changed files with 181 additions and 107 deletions

View File

@@ -1,10 +1,13 @@
package org.toop.app; package org.toop.app;
import javafx.application.Platform;
import javafx.geometry.Pos;
import org.toop.app.game.Connect4Game; import org.toop.app.game.Connect4Game;
import org.toop.app.game.gameControllers.AbstractGameController; import org.toop.app.game.ReversiGame;
import org.toop.app.game.gameControllers.ReversiController; import org.toop.app.game.TicTacToeGame;
import org.toop.app.game.gameControllers.TicTacToeController; import org.toop.app.widget.Primitive;
import org.toop.app.widget.WidgetContainer; 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.ChallengePopup;
import org.toop.app.widget.popup.ErrorPopup; import org.toop.app.widget.popup.ErrorPopup;
import org.toop.app.widget.popup.SendChallengePopup; 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.clients.TournamentNetworkingClient;
import org.toop.framework.networking.events.NetworkEvents; import org.toop.framework.networking.events.NetworkEvents;
import org.toop.framework.networking.types.NetworkingConnector; 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 org.toop.local.AppContext;
import java.util.List; import java.util.List;
@@ -28,7 +26,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public final class Server { public final class Server {
// TODO: Keep track of listeners. Remove them on Server connection close so reference is deleted.
private String user = ""; private String user = "";
private long clientId = -1; private long clientId = -1;
@@ -38,14 +35,10 @@ public final class Server {
private ServerView primary; private ServerView primary;
private boolean isPolling = true; private boolean isPolling = true;
private AbstractGameController<?> gameController;
private final AtomicBoolean isSingleGame = new AtomicBoolean(false); private final AtomicBoolean isSingleGame = new AtomicBoolean(false);
private ScheduledExecutorService scheduler; private ScheduledExecutorService scheduler;
private EventFlow eventFlow = new EventFlow();
public static GameInformation.Type gameToType(String game) { public static GameInformation.Type gameToType(String game) {
if (game.equalsIgnoreCase("tic-tac-toe")) { if (game.equalsIgnoreCase("tic-tac-toe")) {
return GameInformation.Type.TICTACTOE; return GameInformation.Type.TICTACTOE;
@@ -60,9 +53,6 @@ public final class Server {
return null; 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) { public Server(String ip, String port, String user) {
if (ip.split("\\.").length < 4) { if (ip.split("\\.").length < 4) {
new ErrorPopup("\"" + ip + "\" " + AppContext.getString("is-not-a-valid-ip-address")); new ErrorPopup("\"" + ip + "\" " + AppContext.getString("is-not-a-valid-ip-address"));
@@ -83,7 +73,13 @@ public final class Server {
return; 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() var a = new EventFlow()
.addPostEvent(NetworkEvents.StartClient.class, .addPostEvent(NetworkEvents.StartClient.class,
@@ -91,8 +87,31 @@ public final class Server {
new NetworkingConnector(ip, parsedPort, reconnectAttempts, 1, TimeUnit.SECONDS) 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 -> { 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"); a.unsubscribe("startclient");
this.user = user; this.user = user;
@@ -105,16 +124,25 @@ public final class Server {
startPopulateScheduler(); startPopulateScheduler();
populateGameList(); 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(); new EventFlow()
.listen(NetworkEvents.ChallengeResponse.class, this::handleReceivedChallenge, false)
eventFlow.listen(NetworkEvents.ChallengeResponse.class, this::handleReceivedChallenge, false) .listen(NetworkEvents.GameMatchResponse.class, this::handleMatchResponse, 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();
} }
private void sendChallenge(String opponent) { private void sendChallenge(String opponent) {
@@ -127,16 +155,10 @@ public final class Server {
} }
private void handleMatchResponse(NetworkEvents.GameMatchResponse response) { private void handleMatchResponse(NetworkEvents.GameMatchResponse response) {
// TODO: Redo all of this mess if (!isPolling) return;
if (gameController != null) {
gameController.stop();
}
gameController = null;
//if (!isPolling) return;
String gameType = extractQuotedValue(response.gameType()); String gameType = extractQuotedValue(response.gameType());
if (response.clientId() == clientId) { if (response.clientId() == clientId) {
isPolling = false; isPolling = false;
onlinePlayers.clear(); onlinePlayers.clear();
@@ -157,57 +179,20 @@ public final class Server {
information.players[0].computerThinkTime = 1; information.players[0].computerThinkTime = 1;
information.players[1].name = response.opponent(); 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; Runnable onGameOverRunnable = isSingleGame.get()? null: this::gameOver;
switch (type) { switch (type) {
case TICTACTOE ->{ case TICTACTOE ->
gameController = new TicTacToeController(players, false); new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
}
case REVERSI -> case REVERSI ->
gameController = new ReversiController(players, false); new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
case CONNECT4 -> case CONNECT4 ->
new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
default -> new ErrorPopup("Unsupported game type."); 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) { private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) {
if (!isPolling) return; if (!isPolling) return;

View File

@@ -1,6 +1,8 @@
package org.toop.app.widget; package org.toop.app.widget;
import javafx.scene.image.ImageView; 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.framework.resource.resources.ImageAsset;
import org.toop.local.AppContext; import org.toop.local.AppContext;
@@ -73,8 +75,11 @@ public final class Primitive {
} }
if (onAction != null) { if (onAction != null) {
button.setOnAction(_ -> button.setOnAction(_ -> {
onAction.run()); onAction.run();
playButtonSound();
System.out.println("HI I got called button");
});
} }
return button; return button;
@@ -116,10 +121,16 @@ public final class Primitive {
slider.setValue(value); slider.setValue(value);
if (onValueChanged != null) { if (onValueChanged != null) {
slider.valueProperty().addListener((_, _, newValue) -> slider.valueProperty().addListener((_, _, newValue) -> {
onValueChanged.accept(newValue.intValue())); onValueChanged.accept(newValue.intValue());
});
} }
slider.setOnMouseReleased(event -> {
playButtonSound();
System.out.println("I got called!");
});
return slider; return slider;
} }
@@ -137,8 +148,11 @@ public final class Primitive {
} }
if (onValueChanged != null) { if (onValueChanged != null) {
choice.valueProperty().addListener((_, _, newValue) -> choice.valueProperty().addListener((_, _, newValue) -> {
onValueChanged.accept(newValue)); onValueChanged.accept(newValue);
playButtonSound();
System.out.println("hi i got called choice");
});
} }
choice.setItems(FXCollections.observableArrayList(items)); choice.setItems(FXCollections.observableArrayList(items));
@@ -191,4 +205,8 @@ public final class Primitive {
return vbox; return vbox;
} }
private static void playButtonSound() {
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).postEvent();
}
} }

View File

@@ -2,26 +2,45 @@ package org.toop.app.widget.complex;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.ProgressBar; 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 public class LoadingWidget extends ViewWidget implements Update { // TODO make of widget type
private final ProgressBar progressBar; private final ProgressBar progressBar;
private final Text loadingText;
private Runnable success = () -> {}; private Runnable success = () -> {};
private Runnable failure = () -> {}; private Runnable failure = () -> {};
private boolean successTriggered = false;
private boolean failureTriggered = false;
private int maxAmount; private int maxAmount;
private int minAmount;
private int amount; private int amount;
private Callable<Boolean> successTrigger = () -> (amount >= maxAmount);
private Callable<Boolean> failureTrigger = () -> (amount < minAmount);
private float percentage = 0.0f; private float percentage = 0.0f;
/**
public LoadingWidget(int startAmount, int maxAmount) { *
* Widget that shows a loading bar.
amount = startAmount; *
* @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.maxAmount = maxAmount;
this.minAmount = minAmount;
amount = startAmount;
progressBar = new ProgressBar(); progressBar = new ProgressBar();
this.loadingText = loadingText;
HBox box = new HBox(10, progressBar); VBox box = Primitive.vbox(this.loadingText, progressBar);
add(Pos.CENTER, box); add(Pos.CENTER, box);
} }
@@ -29,42 +48,88 @@ public class LoadingWidget extends ViewWidget implements Update { // TODO make o
this.maxAmount = maxAmount; this.maxAmount = maxAmount;
} }
public void setAmount(int amount) { public void setAmount(int amount) throws Exception {
this.amount = amount; this.amount = amount;
update(); update();
} }
public void setAmount() { public int getMaxAmount() {
setAmount(this.amount+1); 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) { public void setOnSuccess(Runnable onSuccess) {
success = onSuccess; success = onSuccess;
} }
/**
* What to do when failure is triggered.
* @param onFailure The lambda that gets run on failure.
*/
public void setOnFailure(Runnable onFailure) { public void setOnFailure(Runnable onFailure) {
failure = onFailure; failure = onFailure;
} }
/**
* The trigger to activate onSuccess.
* @param trigger The lambda that triggers onSuccess.
*/
public void setSuccessTrigger(Callable<Boolean> trigger) {
successTrigger = trigger;
}
/**
* The trigger to activate onFailure.
* @param trigger The lambda that triggers onFailure.
*/
public void setFailureTrigger(Callable<Boolean> trigger) {
failureTrigger = trigger;
}
/**
* Forcefully trigger success.
*/
public void triggerSuccess() { public void triggerSuccess() {
successTriggered = true; // TODO, else it will double call... why?
success.run(); success.run();
} }
/**
* Forcefully trigger failure.
*/
public void triggerFailure() { public void triggerFailure() {
failureTriggered = true; // TODO, else it will double call... why?
failure.run(); failure.run();
} }
@Override @Override
public void update() { public void update() throws Exception { // TODO Better exception
if (amount >= maxAmount) { if (successTriggered || failureTriggered) { // If already triggered, throw exception.
throw new RuntimeException();
}
if (successTrigger.call()) {
triggerSuccess(); triggerSuccess();
System.out.println("triggered"); this.remove(this);
this.hide();
return; return;
} else if (amount < 0) { } else if (failureTrigger.call()) {
triggerFailure(); triggerFailure();
System.out.println("triggerFailure"); this.remove(this);
this.hide();
return; return;
} }

View File

@@ -2,6 +2,8 @@ package org.toop.app.widget.complex;
import org.toop.app.widget.Primitive; import org.toop.app.widget.Primitive;
import org.toop.app.widget.Widget; 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 org.toop.local.AppContext;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -30,6 +32,7 @@ public class ToggleWidget implements Widget {
updateText(); updateText();
if (onToggle != null) { if (onToggle != null) {
onToggle.accept(state); onToggle.accept(state);
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).postEvent(); // TODO FIX PRIMITIVES
} }
}); });

View File

@@ -1,5 +1,5 @@
package org.toop.app.widget.complex; package org.toop.app.widget.complex;
public interface Update { public interface Update {
void update(); void update() throws Exception;
} }

View File

@@ -3,6 +3,7 @@ package org.toop.app.widget.view;
import org.toop.app.Server; import org.toop.app.Server;
import org.toop.app.widget.Primitive; import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.LabeledInputWidget; import org.toop.app.widget.complex.LabeledInputWidget;
import org.toop.app.widget.complex.LoadingWidget;
import org.toop.app.widget.complex.ViewWidget; import org.toop.app.widget.complex.ViewWidget;
import javafx.geometry.Pos; import javafx.geometry.Pos;

View File

@@ -9,6 +9,8 @@ computer-difficulty=Computer difficulty
computer-think-time=Computer think time computer-think-time=Computer think time
computer=Computer computer=Computer
connect=Connect connect=Connect
connecting=Connecting to server...
connecting-failed=Could not connect to server:
credits=Credits credits=Credits
dark=Dark dark=Dark
deny=Deny deny=Deny