Event bus now testable, improved UI (#284)

* turn updates

* smalle fixes aan turn updates

* better human/ai selector with bot selection and depth on TicTacToeAIR

* depth + thinktime back to AIs, along with a a specific TicTacToeAIRSleep

* fixed overlapping back and disconnect buttons

* Changed to debug instead of info

* changed the transitionNextCustom to be easier to use

* added getAllWidgets to WidgetContainer

* Correct back view

* added replacePrevious in ViewWidget

* added removeIndexFromPreviousChain

* fixed incorrect index counting

* Fixt wrong view order

* Removed todo

* Challenge popups "Fixed"

* Popups now remove themselves

* localize the ChallengePopup text

* made the game text a header instead

* fixed getAllWidgets

* Escape popup

* fixed redundant container

* Escape remove popup

* Working escape menu

* Added find functionality

* Tutorials moved to escape menu

* Escape can't be opened in mainview now

* Can now test the event bus, created testable interfaces

* Logging errors

* Made events and handlers more generic

* Suppress

* Managers now have changeable eventbus

* Tutorials fixed

* Removed import

* Single threaded eventbus

* Fixed wrong eventbus

* Removed get

* Removed old code

* Renaming

* Optimization

* Removed useless comment

* Removed unnecessary imports

* Rename

* Renaming, refactor and type safety

* Rename

* Removed import

---------

Co-authored-by: michiel301b <m.brands.3@st.hanze.nl>
Co-authored-by: ramollia <>
This commit is contained in:
Bas Antonius de Jong
2025-12-07 17:38:34 +01:00
committed by GitHub
parent f60df73b66
commit 38f50cc16d
68 changed files with 1100 additions and 538 deletions

View File

@@ -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) {

View File

@@ -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<MusicAsset> musicManager =
new MusicManager<>(ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class), true);
new MusicManager<>(
GlobalEventBus.get(),
ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class),
true
);
SoundEffectManager<SoundEffectAsset> 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

View File

@@ -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() {

View File

@@ -99,7 +99,7 @@ public class ReversiController extends AbstractGameController<ReversiR> {
});
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

View File

@@ -39,7 +39,7 @@ public class TicTacToeController extends AbstractGameController<TicTacToeR> {
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();
}

View File

@@ -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();
}

View File

@@ -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<? extends Widget> widgetClass) {
if (root == null || currentView == null) return;
Platform.runLater(() ->
currentView.getChildren().removeIf(widget -> widget.getClass().isAssignableFrom(widgetClass))
);
}
public static void removeFirst(Class<? extends Widget> 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<Widget> find(Class<? extends Widget> widgetClass) {
if (root == null || currentView == null) return null;
return getAllWidgets()
.stream()
.filter(widget -> widget.getClass().isAssignableFrom(widgetClass))
.toList();
}
public static List<Widget> find(Predicate<Widget> predicate) {
if (root == null || currentView == null) return null;
return getAllWidgets()
.stream()
.filter(predicate)
.toList();
}
public static Widget findFirst(Class<? extends Widget> 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<Widget> getAllWidgets() {
final List<Widget> 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;
}
}

View File

@@ -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;
}

View File

@@ -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"));
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,

View File

@@ -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<Node> 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])));
}
}

View File

@@ -15,14 +15,12 @@ public class QuitPopup extends PopupWidget {
});
confirmWidget.addButton("no", () -> {
App.stopQuit();
hide();
});
add(Pos.CENTER, confirmWidget);
setOnPop(() -> {
App.stopQuit();
hide();
});
}

View File

@@ -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());

View File

@@ -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

View File

@@ -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<String> 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);
}
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -14,14 +14,12 @@ import javafx.scene.control.ListView;
public final class ServerView extends ViewWidget {
private final String user;
private final Consumer<String> onPlayerClicked;
private final Runnable onDisconnect;
private final ListView<Button> listView;
public ServerView(String user, Consumer<String> onPlayerClicked, Runnable onDisconnect) {
public ServerView(String user, Consumer<String> onPlayerClicked) {
this.user = user;
this.onPlayerClicked = onPlayerClicked;
this.onDisconnect = onDisconnect;
this.listView = new ListView<>();
@@ -40,7 +38,6 @@ public final class ServerView extends ViewWidget {
add(Pos.CENTER, playerListSection);
var disconnectButton = Primitive.button("disconnect", () -> {
onDisconnect.run();
transitionPrevious();
});

View File

@@ -5,6 +5,7 @@ import java.util.MissingResourceException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.app.widget.tutorial.BaseTutorialWidget;
import org.toop.framework.resource.ResourceManager;
import org.toop.framework.resource.resources.LocalizationAsset;
@@ -16,11 +17,13 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
public class AppContext {
private static final Logger logger = LogManager.getLogger(AppContext.class);
private static final LocalizationAsset localization = ResourceManager.get("localization");
private static Locale locale = Locale.forLanguageTag("en");
private static final ObjectProperty<Locale> localeProperty = new SimpleObjectProperty<>(locale);
private static final Logger logger = LogManager.getLogger(AppContext.class);
private static BaseTutorialWidget tutorialWidget;
public static LocalizationAsset getLocalization() {
return localization;
@@ -73,4 +76,12 @@ public class AppContext {
public static StringBinding bindToKey(String key) {
return bindToKey(key, true);
}
public static void setCurrentTutorial(BaseTutorialWidget tutorial) {
AppContext.tutorialWidget = tutorial;
}
public static BaseTutorialWidget currentTutorial() {
return AppContext.tutorialWidget;
}
}