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

# Conflicts:
#	app/src/main/java/org/toop/app/view/views/GameView.java
This commit is contained in:
ramollia
2025-12-02 11:24:42 +01:00
77 changed files with 1495 additions and 2489 deletions

View File

@@ -5,6 +5,7 @@
<w>clid</w> <w>clid</w>
<w>dcompile</w> <w>dcompile</w>
<w>errorprone</w> <w>errorprone</w>
<w>español</w>
<w>flushnl</w> <w>flushnl</w>
<w>gaaf</w> <w>gaaf</w>
<w>gamelist</w> <w>gamelist</w>

View File

@@ -36,4 +36,5 @@ public final class Main {
}).start(); }).start();
} }
} }

View File

@@ -22,6 +22,17 @@ public class GameInformation {
public int getMaxDepth() { public int getMaxDepth() {
return maxDepth; return maxDepth;
} }
public String getTypeToString() {
String name = this.name();
return switch (name) {
case "TICTACTOE" -> "TicTacToe";
case "REVERSI" -> "Reversi";
case "CONNECT4" -> "Connect4";
case "BATTLESHIP" -> "Battleship";
default -> name;
};
}
} }
public static class Player { public static class Player {

View File

@@ -1,18 +1,23 @@
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.ReversiGame; import org.toop.app.game.ReversiGame;
import org.toop.app.game.TicTacToeGame; import org.toop.app.game.TicTacToeGame;
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;
import org.toop.app.widget.view.ServerView; import org.toop.app.widget.view.ServerView;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.eventbus.ListenerHandler;
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.local.AppContext; import org.toop.local.AppContext;
import java.util.function.Consumer;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@@ -69,27 +74,34 @@ public final class Server {
return; return;
} }
new EventFlow() final int reconnectAttempts = 10;
var a = new EventFlow()
.addPostEvent(NetworkEvents.StartClient.class, .addPostEvent(NetworkEvents.StartClient.class,
new TournamentNetworkingClient(), new TournamentNetworkingClient(),
new NetworkingConnector(ip, parsedPort, 10, 1, TimeUnit.SECONDS) new NetworkingConnector(ip, parsedPort, reconnectAttempts, 1, TimeUnit.SECONDS)
) );
.onResponse(NetworkEvents.StartClientResponse.class, e -> {
this.user = user;
clientId = e.clientId();
new EventFlow().addPostEvent(new NetworkEvents.SendLogin(clientId, user)).postEvent(); a.onResponse(NetworkEvents.StartClientResponse.class, e -> {
primary = new ServerView(user, this::sendChallenge, this::disconnect); a.unsubscribe("startclient");
WidgetContainer.getCurrentView().transitionNext(primary);
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(); startPopulateScheduler();
populateGameList(); populateGameList();
}).postEvent(); }).postEvent();
new EventFlow().listen(NetworkEvents.ChallengeResponse.class, this::handleReceivedChallenge, false)
new EventFlow().listen(this::handleReceivedChallenge) .listen(NetworkEvents.GameMatchResponse.class, this::handleMatchResponse, false);
.listen(this::handleMatchResponse); startPopulateScheduler();
populateGameList();
} }
private void sendChallenge(String opponent) { private void sendChallenge(String opponent) {

View File

@@ -48,16 +48,17 @@ public abstract class BaseGameThread<TGame extends Game, TAI, TCanvas> {
this.game = gameSupplier.get(); this.game = gameSupplier.get();
this.ai = aiSupplier.get(); this.ai = aiSupplier.get();
if (onForfeit == null || onExit == null) { String type = information.type.getTypeToString();
primary = new GameView(null, () -> { if (onForfeit == null || onExit == null) {
isRunning.set(false); primary = new GameView(null, () -> {
WidgetContainer.getCurrentView().transitionPrevious(); isRunning.set(false);
}, null); WidgetContainer.getCurrentView().transitionPrevious();
}, null, type);
} else { } else {
primary = new GameView(onForfeit, () -> { primary = new GameView(onForfeit, () -> {
isRunning.set(false); isRunning.set(false);
onExit.run(); onExit.run();
}, onMessage); }, onMessage, type);
} }
this.canvas = canvasFactory.apply(this::onCellClicked); this.canvas = canvasFactory.apply(this::onCellClicked);
@@ -70,8 +71,8 @@ public abstract class BaseGameThread<TGame extends Game, TAI, TCanvas> {
new Thread(this::localGameThread).start(); new Thread(this::localGameThread).start();
else else
new EventFlow() new EventFlow()
.listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse) .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse, false)
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse); .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse, false);
setGameLabels(myTurn == 0); setGameLabels(myTurn == 0);
} }

View File

@@ -5,9 +5,8 @@ import javafx.scene.paint.Color;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.GameInformation; import org.toop.app.GameInformation;
import org.toop.app.canvas.Connect4Canvas; import org.toop.app.canvas.Connect4Canvas;
import org.toop.app.view.ViewStack; import org.toop.app.widget.view.GameView;
import org.toop.app.view.views.GameView; import org.toop.app.widget.WidgetContainer;
import org.toop.app.view.views.LocalMultiplayerView;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.networking.events.NetworkEvents; import org.toop.framework.networking.events.NetworkEvents;
import org.toop.game.Connect4.Connect4; import org.toop.game.Connect4.Connect4;
@@ -32,7 +31,7 @@ public class Connect4Game {
private final int columnSize = 7; private final int columnSize = 7;
private final int rowSize = 6; private final int rowSize = 6;
private final GameView view; private final GameView primary;
private final Connect4Canvas canvas; private final Connect4Canvas canvas;
private final AtomicBoolean isRunning; private final AtomicBoolean isRunning;
@@ -50,15 +49,15 @@ public class Connect4Game {
isRunning = new AtomicBoolean(true); isRunning = new AtomicBoolean(true);
if (onForfeit == null || onExit == null) { if (onForfeit == null || onExit == null) {
view = new GameView(null, () -> { primary = new GameView(null, () -> {
isRunning.set(false); isRunning.set(false);
ViewStack.push(new LocalMultiplayerView(information)); WidgetContainer.getCurrentView().transitionPrevious();
}, null); }, null, "Connect4");
} else { } else {
view = new GameView(onForfeit, () -> { primary = new GameView(onForfeit, () -> {
isRunning.set(false); isRunning.set(false);
onExit.run(); onExit.run();
}, onMessage); }, onMessage, "Connect4");
} }
canvas = new Connect4Canvas(Color.GRAY, canvas = new Connect4Canvas(Color.GRAY,
@@ -83,17 +82,16 @@ public class Connect4Game {
} }
}); });
view.add(Pos.CENTER, canvas.getCanvas()); primary.add(Pos.CENTER, canvas.getCanvas());
ViewStack.push(view); WidgetContainer.getCurrentView().transitionNext(primary);
if (onForfeit == null || onExit == null) { if (onForfeit == null || onExit == null) {
new Thread(this::localGameThread).start(); new Thread(this::localGameThread).start();
setGameLabels(information.players[0].isHuman); setGameLabels(information.players[0].isHuman);
} else { } else {
new EventFlow() new EventFlow()
.listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse) .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse, false)
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse) .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse, false);
.listen(NetworkEvents.ReceivedMessage.class, this::onReceivedMessage);
setGameLabels(myTurn == 0); setGameLabels(myTurn == 0);
} }
@@ -109,7 +107,7 @@ public class Connect4Game {
final String currentValue = currentTurn == 0? "RED" : "BLUE"; final String currentValue = currentTurn == 0? "RED" : "BLUE";
final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount(); final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount();
view.nextPlayer(information.players[currentTurn].isHuman, primary.nextPlayer(information.players[currentTurn].isHuman,
information.players[currentTurn].name, information.players[currentTurn].name,
currentValue, currentValue,
information.players[nextTurn].name); information.players[nextTurn].name);
@@ -159,9 +157,9 @@ public class Connect4Game {
*/ */
if (state != GameState.NORMAL) { if (state != GameState.NORMAL) {
if (state == GameState.WIN) { if (state == GameState.WIN) {
view.gameOver(true, information.players[currentTurn].name); primary.gameOver(true, information.players[currentTurn].name);
} else if (state == GameState.DRAW) { } else if (state == GameState.DRAW) {
view.gameOver(false, ""); primary.gameOver(false, "");
} }
isRunning.set(false); isRunning.set(false);
@@ -188,14 +186,14 @@ public class Connect4Game {
if (state != GameState.NORMAL) { if (state != GameState.NORMAL) {
if (state == GameState.WIN) { if (state == GameState.WIN) {
if (response.player().equalsIgnoreCase(information.players[0].name)) { if (response.player().equalsIgnoreCase(information.players[0].name)) {
view.gameOver(true, information.players[0].name); primary.gameOver(true, information.players[0].name);
gameOver(); gameOver();
} else { } else {
view.gameOver(false, information.players[1].name); primary.gameOver(false, information.players[1].name);
gameOver(); gameOver();
} }
} else if (state == GameState.DRAW) { } else if (state == GameState.DRAW) {
view.gameOver(false, ""); primary.gameOver(false, "");
gameOver(); gameOver();
} }
} }
@@ -243,14 +241,6 @@ public class Connect4Game {
.postEvent(); .postEvent();
} }
private void onReceivedMessage(NetworkEvents.ReceivedMessage msg) {
if (!isRunning.get()) {
return;
}
view.updateChat(msg.message());
}
private void updateCanvas() { private void updateCanvas() {
canvas.clearAll(); canvas.clearAll();
@@ -267,7 +257,7 @@ public class Connect4Game {
final int currentTurn = game.getCurrentTurn(); final int currentTurn = game.getCurrentTurn();
final String currentValue = currentTurn == 0? "RED" : "BLUE"; final String currentValue = currentTurn == 0? "RED" : "BLUE";
view.nextPlayer(isMe, primary.nextPlayer(isMe,
information.players[isMe? 0 : 1].name, information.players[isMe? 0 : 1].name,
currentValue, currentValue,
information.players[isMe? 1 : 0].name); information.players[isMe? 1 : 0].name);

View File

@@ -55,12 +55,12 @@ public final class ReversiGame {
primary = new GameView(null, () -> { primary = new GameView(null, () -> {
isRunning.set(false); isRunning.set(false);
WidgetContainer.getCurrentView().transitionPrevious(); WidgetContainer.getCurrentView().transitionPrevious();
}, null); }, null, "Reversi");
} else { } else {
primary = new GameView(onForfeit, () -> { primary = new GameView(onForfeit, () -> {
isRunning.set(false); isRunning.set(false);
onExit.run(); onExit.run();
}, onMessage); }, onMessage, "Reversi");
} }
canvas = new ReversiCanvas(Color.BLACK, canvas = new ReversiCanvas(Color.BLACK,
@@ -95,8 +95,8 @@ public final class ReversiGame {
setGameLabels(information.players[0].isHuman); setGameLabels(information.players[0].isHuman);
} else { } else {
new EventFlow() new EventFlow()
.listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse) .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse, false)
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse); .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse, false);
setGameLabels(myTurn == 0); setGameLabels(myTurn == 0);
} }

View File

@@ -51,12 +51,12 @@ public final class TicTacToeGame {
primary = new GameView(null, () -> { primary = new GameView(null, () -> {
isRunning.set(false); isRunning.set(false);
WidgetContainer.getCurrentView().transitionPrevious(); WidgetContainer.getCurrentView().transitionPrevious();
}, null); }, null, "TicTacToe");
} else { } else {
primary = new GameView(onForfeit, () -> { primary = new GameView(onForfeit, () -> {
isRunning.set(false); isRunning.set(false);
onExit.run(); onExit.run();
}, onMessage); }, onMessage, "TicTacToe");
} }
canvas = new TicTacToeCanvas(Color.GRAY, canvas = new TicTacToeCanvas(Color.GRAY,
@@ -88,8 +88,8 @@ public final class TicTacToeGame {
new Thread(this::localGameThread).start(); new Thread(this::localGameThread).start();
} else { } else {
new EventFlow() new EventFlow()
.listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse) .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse, false)
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse); .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse, false);
setGameLabels(myTurn == 0); setGameLabels(myTurn == 0);
} }

View File

@@ -9,9 +9,7 @@ import org.toop.game.enumerators.GameState;
import org.toop.game.records.Move; import org.toop.game.records.Move;
import org.toop.game.tictactoe.TicTacToe; import org.toop.game.tictactoe.TicTacToe;
import org.toop.game.tictactoe.TicTacToeAI; import org.toop.game.tictactoe.TicTacToeAI;
import java.util.function.Consumer; import java.util.function.Consumer;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
@@ -25,8 +23,8 @@ public final class TicTacToeGameThread extends BaseGameThread<TicTacToe, TicTacT
} }
public TicTacToeGameThread(GameInformation info) { public TicTacToeGameThread(GameInformation info) {
this(info, 0, null, null, null, null); this(info, 0, null, null, null, null);
} }
@Override @Override
protected void addCanvasToPrimary() { protected void addCanvasToPrimary() {

View File

@@ -1,400 +0,0 @@
package org.toop.app.view;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Separator;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import java.util.HashMap;
import java.util.Map;
public abstract class View {
private final boolean mainView;
private final StackPane view;
private final Map<String, Node> nodeMap;
protected View(boolean mainView, String cssClass) {
this.mainView = mainView;
view = new StackPane();
view.getStyleClass().add(cssClass);
nodeMap = new HashMap<String, Node>();
}
public void add(Pos position, Node node) {
assert node != null;
StackPane.setAlignment(node, position);
view.getChildren().add(node);
}
protected Region hspacer() {
final Region hspacer = new Region();
hspacer.getStyleClass().add("hspacer");
return hspacer;
}
protected Region vspacer() {
final Region vspacer = new Region();
vspacer.getStyleClass().add("vspacer");
return vspacer;
}
protected ScrollPane fit(String identifier, String cssClass, Node node) {
assert node != null;
final ScrollPane fit = new ScrollPane(node);
fit.getStyleClass().add(cssClass);
fit.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
fit.setFitToWidth(true);
fit.setFitToHeight(true);
if (!identifier.isEmpty()) {
nodeMap.put(identifier, fit);
}
return fit;
}
protected ScrollPane fit(String identifier, Node node) {
return fit(identifier, "fit", node);
}
protected ScrollPane fit(Node node) {
return fit("", node);
}
protected HBox hbox(String identifier, String cssClass, Node... nodes) {
assert !nodeMap.containsKey(identifier);
final HBox hbox = new HBox();
hbox.getStyleClass().add(cssClass);
hbox.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
for (final Node node : nodes) {
if (node != null) {
hbox.getChildren().add(node);
}
}
if (!identifier.isEmpty()) {
nodeMap.put(identifier, hbox);
}
return hbox;
}
protected HBox hbox(String identifier, Node... nodes) {
return hbox(identifier, "container", nodes);
}
protected HBox hbox(Node... nodes) {
return hbox("", nodes);
}
protected HBox hboxFill(String identifier, String cssClass, Node... nodes) {
final HBox hbox = hbox(identifier, cssClass, nodes);
for (final Node node : hbox.getChildren()) {
if (node instanceof Region) {
((Region)node).setMaxHeight(Double.MAX_VALUE);
}
}
return hbox;
}
protected HBox hboxFill(String identifier, Node... nodes) {
final HBox hbox = hbox(identifier, nodes);
for (final Node node : hbox.getChildren()) {
if (node instanceof Region) {
((Region)node).setMaxHeight(Double.MAX_VALUE);
}
}
return hbox;
}
protected HBox hboxFill(Node... nodes) {
final HBox hbox = hbox(nodes);
for (final Node node : hbox.getChildren()) {
if (node instanceof Region) {
((Region)node).setMaxHeight(Double.MAX_VALUE);
}
}
return hbox;
}
protected VBox vbox(String identifier, String cssClass, Node... nodes) {
assert !nodeMap.containsKey(identifier);
final VBox vbox = new VBox();
vbox.getStyleClass().add(cssClass);
vbox.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
for (final Node node : nodes) {
if (node != null) {
vbox.getChildren().add(node);
}
}
if (!identifier.isEmpty()) {
nodeMap.put(identifier, vbox);
}
return vbox;
}
protected VBox vbox(String identifier, Node... nodes) {
return vbox(identifier, "container", nodes);
}
protected VBox vbox(Node... nodes) {
return vbox("", nodes);
}
protected VBox vboxFill(String identifier, String cssClass, Node... nodes) {
final VBox vbox = vbox(identifier, cssClass, nodes);
for (final Node node : vbox.getChildren()) {
if (node instanceof Region) {
((Region)node).setMaxWidth(Double.MAX_VALUE);
}
}
return vbox;
}
protected VBox vboxFill(String identifier, Node... nodes) {
final VBox vbox = vbox(identifier, nodes);
for (final Node node : vbox.getChildren()) {
if (node instanceof Region) {
((Region)node).setMaxWidth(Double.MAX_VALUE);
}
}
return vbox;
}
protected VBox vboxFill(Node... nodes) {
final VBox vbox = vbox(nodes);
for (final Node node : vbox.getChildren()) {
if (node instanceof Region) {
((Region)node).setMaxWidth(Double.MAX_VALUE);
}
}
return vbox;
}
protected Separator separator(String identifier, String cssClass) {
assert !nodeMap.containsKey(identifier);
final Separator separator = new Separator();
separator.getStyleClass().add(cssClass);
if (!identifier.isEmpty()) {
nodeMap.put(identifier, separator);
}
return separator;
}
protected Separator separator(String identifier) {
return separator(identifier, "separator");
}
protected Separator separator() {
return separator("");
}
protected Text header(String identifier, String cssClass) {
assert !nodeMap.containsKey(identifier);
final Text header = new Text();
header.getStyleClass().add(cssClass);
if (!identifier.isEmpty()) {
nodeMap.put(identifier, header);
}
return header;
}
protected Text header(String identifier) {
return header(identifier, "header");
}
protected Text header() {
return header("");
}
protected Text text(String identifier, String cssClass) {
assert !nodeMap.containsKey(identifier);
final Text text = new Text();
text.getStyleClass().add(cssClass);
if (!identifier.isEmpty()) {
nodeMap.put(identifier, text);
}
return text;
}
protected Text text(String identifier) {
return text(identifier, "text");
}
protected Text text() {
return text("");
}
protected Button button(String identifier, String cssClass) {
assert !nodeMap.containsKey(identifier);
final Button button = new Button();
button.getStyleClass().add(cssClass);
button.setOnMouseClicked(_ -> {
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
});
if (!identifier.isEmpty()) {
nodeMap.put(identifier, button);
}
return button;
}
protected Button button(String identifier) {
return button(identifier, "button");
}
protected Button button() {
return button("");
}
protected Slider slider(String identifier, String cssClass) {
assert !nodeMap.containsKey(identifier);
final Slider slider = new Slider();
slider.getStyleClass().add(cssClass);
slider.setMinorTickCount(0);
slider.setMajorTickUnit(1);
slider.setBlockIncrement(1);
slider.setSnapToTicks(true);
slider.setShowTickLabels(true);
slider.setOnMouseClicked(_ -> {
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
});
if (!identifier.isEmpty()) {
nodeMap.put(identifier, slider);
}
return slider;
}
protected Slider slider(String identifier) {
return slider(identifier, "slider");
}
protected Slider slider() {
return slider("");
}
protected TextField input(String identifier, String cssClass) {
assert !nodeMap.containsKey(identifier);
final TextField input = new TextField();
input.getStyleClass().add(cssClass);
input.setOnMouseClicked(_ -> {
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
});
if (!identifier.isEmpty()) {
nodeMap.put(identifier, input);
}
return input;
}
protected TextField input(String identifier) {
return input(identifier, "input");
}
protected TextField input() {
return input("");
}
protected <T> ComboBox<T> combobox(String identifier, String cssClass) {
assert !nodeMap.containsKey(identifier);
final ComboBox<T> combobox = new ComboBox<T>();
combobox.getStyleClass().add(cssClass);
combobox.setOnMouseClicked(_ -> {
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
});
if (!identifier.isEmpty()) {
nodeMap.put(identifier, combobox);
}
return combobox;
}
protected <T> ComboBox<T> combobox(String identifier) {
return combobox(identifier, "combo-box");
}
protected <T> ComboBox<T> combobox() {
return combobox("");
}
@SuppressWarnings("unchecked")
protected <T extends Node> T get(String identifier) {
assert nodeMap.containsKey(identifier);
return (T) nodeMap.get(identifier);
}
protected void clear() {
view.getChildren().clear();
nodeMap.clear();
}
public boolean isMainView() { return mainView; }
public Region getView() { return view; }
public abstract void setup();
public void cleanup() {
clear();
}
}

View File

@@ -1,105 +0,0 @@
package org.toop.app.view;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import java.util.Stack;
public final class ViewStack {
private static boolean setup = false;
private static StackPane root;
private static View active;
private static Stack<View> stack;
public static void setup(Scene scene) {
assert scene != null;
if (setup) {
return;
}
root = new StackPane();
active = null;
stack = new Stack<View>();
scene.setRoot(root);
setup = true;
}
public static void cleanup() {
assert setup;
final var count = stack.size();
for (int i = 0; i < count; i++) {
pop();
}
if (active != null) {
active.cleanup();
}
setup = false;
}
public static void reload() {
assert setup;
for (final var view : stack) {
view.cleanup();
}
if (active != null) {
active.cleanup();
active.setup();
}
for (final var view : stack) {
view.setup();
}
}
public static void push(View view) {
assert setup;
assert view != null;
if (view.isMainView()) {
Platform.runLater(() -> {
if (active != null) {
root.getChildren().removeFirst();
active.cleanup();
}
root.getChildren().addFirst(view.getView());
view.setup();
active = view;
});
} else {
Platform.runLater(() -> {
stack.push(view);
root.getChildren().addLast(view.getView());
view.setup();
});
}
}
public static void pop() {
assert setup;
if (stack.isEmpty()) {
return;
}
Platform.runLater(() -> {
final var last = stack.pop();
root.getChildren().removeLast();
last.cleanup();
});
}
}

View File

@@ -1,128 +0,0 @@
package org.toop.app.view.displays;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import org.toop.app.widget.Widget;
import org.toop.framework.audio.AudioEventListener;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import javafx.geometry.Pos;
import javafx.scene.text.Text;
import org.toop.framework.eventbus.GlobalEventBus;
public class SongDisplay extends VBox implements Widget {
private final Text songTitle;
private final ProgressBar progressBar;
private final Text progressText;
private boolean paused = false;
public SongDisplay() {
new EventFlow()
.listen(this::updateTheSong);
setAlignment(Pos.CENTER);
getStyleClass().add("song-display");
songTitle = new Text("song playing");
songTitle.getStyleClass().add("song-title");
progressBar = new ProgressBar(0);
progressBar.getStyleClass().add("progress-bar");
progressText = new Text("0:00/0:00");
progressText.getStyleClass().add("progress-text");
Button skipButton = new Button(">>");
Button pauseButton = new Button("");
Button previousButton = new Button("<<");
skipButton.getStyleClass().setAll("skip-button");
pauseButton.getStyleClass().setAll("pause-button");
previousButton.getStyleClass().setAll("previous-button");
skipButton.setOnAction( event -> {
GlobalEventBus.post(new AudioEvents.SkipMusic());
paused = false;
pauseButton.setText(getPlayString(paused));
});
pauseButton.setOnAction(event -> {
GlobalEventBus.post(new AudioEvents.PauseMusic());
paused = !paused;
pauseButton.setText(getPlayString(paused));
});
previousButton.setOnAction( event -> {
GlobalEventBus.post(new AudioEvents.PreviousMusic());
paused = false;
pauseButton.setText(getPlayString(paused));
});
HBox control = new HBox(10, previousButton, pauseButton, skipButton);
control.setAlignment(Pos.CENTER);
control.getStyleClass().add("controls");
getChildren().addAll(songTitle, progressBar, progressText, control);
}
private void updateTheSong(AudioEvents.PlayingMusic event) {
Platform.runLater(() -> {
String text = event.name();
text = text.substring(0, text.length() - 4);
songTitle.setText(text);
double currentPos = event.currentPosition();
double duration = event.duration();
if (currentPos / duration > 0.05) {
double progress = currentPos / duration;
progressBar.setProgress(progress);
}
else if (currentPos / duration < 0.05) {
progressBar.setProgress(0.05);
}
progressText.setText(getTimeString(event.currentPosition(), event.duration()));
});
}
private String getTimeString(long position, long duration) {
long positionMinutes = position / 60;
long durationMinutes = duration / 60;
long positionSeconds = position % 60;
long durationSeconds = duration % 60;
String positionSecondsStr = String.valueOf(positionSeconds);
String durationSecondsStr = String.valueOf(durationSeconds);
if (positionSeconds < 10) {
positionSecondsStr = "0" + positionSeconds;
}
if (durationSeconds < 10) {
durationSecondsStr = "0" + durationSeconds;
}
String time = positionMinutes + ":" + positionSecondsStr + " / " + durationMinutes + ":" + durationSecondsStr;
return time;
}
@Override
public Node getNode() {
return this;
}
private String getPlayString(boolean paused) {
if (paused) {
return "";
}
else {
return "";
}
}
}

View File

@@ -1,127 +0,0 @@
package org.toop.app.view.views;
import org.toop.app.GameInformation;
import org.toop.app.Server;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.text.Text;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public final class ChallengeView extends View {
private final GameInformation.Player playerInformation;
private final String challenger;
private final String game;
private final Consumer<GameInformation.Player> onAccept;
public ChallengeView(String challenger, String game, Consumer<GameInformation.Player> onAccept) {
super(false, "bg-popup");
playerInformation = new GameInformation.Player();
this.challenger = challenger;
this.game = game;
this.onAccept = onAccept;
}
@Override
public void setup() {
final Text challengeText = text();
challengeText.setText(AppContext.getString("you-were-challenged-by"));
final Text challengerHeader = header();
challengerHeader.setText(challenger);
final Text gameText = text();
gameText.setText(AppContext.getString("to-a-game-of") + " " + game);
final Button acceptButton = button();
acceptButton.setText(AppContext.getString("accept"));
acceptButton.setOnAction(_ -> {
onAccept.accept(playerInformation);
});
final Button denyButton = button();
denyButton.setText(AppContext.getString("deny"));
denyButton.setOnAction(_ -> {
ViewStack.pop();
});
final List<Node> nodes = new ArrayList<>();
if (playerInformation.isHuman) {
final Button playerToggle = button();
playerToggle.setText(AppContext.getString("player"));
playerToggle.setOnAction(_ -> {
playerInformation.isHuman = false;
cleanup();
setup();
});
nodes.add(vbox(playerToggle));
} else {
final Button computerToggle = button();
computerToggle.setText(AppContext.getString("computer"));
computerToggle.setOnAction(_ -> {
playerInformation.isHuman = true;
cleanup();
setup();
});
nodes.add(vbox(computerToggle));
final Text computerDifficultyText = text();
computerDifficultyText.setText(AppContext.getString("computer-difficulty"));
final Slider computerDifficultySlider = slider();
computerDifficultySlider.setMin(0);
computerDifficultySlider.setMax(Server.gameToType(game).getMaxDepth());
computerDifficultySlider.setValue(playerInformation.computerDifficulty);
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
playerInformation.computerDifficulty = newValue.intValue();
});
nodes.add(vbox(computerDifficultyText, computerDifficultySlider));
}
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit(hboxFill(
vboxFill(
challengeText,
challengerHeader,
gameText,
separator(),
hboxFill(
acceptButton,
denyButton
)
),
vboxFill(
nodes.toArray(new Node[0])
)
))
);
}
}

View File

@@ -1,110 +0,0 @@
package org.toop.app.view.views;
import org.toop.app.App;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.util.Duration;
public final class CreditsView extends View {
public CreditsView() {
super(false, "bg-primary");
}
@Override
public void setup() {
final Text scrumMasterHeader = header();
scrumMasterHeader.setText(AppContext.getString("scrum-master") + ": Stef");
final Text productOwnerHeader = header();
productOwnerHeader.setText(AppContext.getString("product-owner") + ": Omar");
final Text mergeCommanderHeader = header();
mergeCommanderHeader.setText(AppContext.getString("merge-commander") + ": Bas");
final Text localizationHeader = header();
localizationHeader.setText(AppContext.getString("localization") + ": Ticho");
final Text aiHeader = header();
aiHeader.setText(AppContext.getString("ai") + ": Michiel");
final Text developersHeader = header();
developersHeader.setText(AppContext.getString("developers") + ": Michiel, Bas, Stef, Omar, Ticho");
final Text moralSupportHeader = header();
moralSupportHeader.setText(AppContext.getString("moral-support") + ": Wesley");
final Text openglHeader = header();
openglHeader.setText(AppContext.getString("opengl") + ": Omar");
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit("credits-fit", vboxFill("credits-container", "credits-container",
vbox("credits-spacer-top", ""),
scrumMasterHeader,
productOwnerHeader,
mergeCommanderHeader,
localizationHeader,
aiHeader,
developersHeader,
moralSupportHeader,
openglHeader,
vbox("credits-spacer-bottom", "")
))
);
final Button backButton = button();
backButton.setText(AppContext.getString("back"));
backButton.setOnAction(_ -> { ViewStack.pop(); });
add(Pos.BOTTOM_LEFT,
vboxFill(
backButton
)
);
playCredits(100, 20);
}
private void playCredits(int lineHeight, int length) {
final ScrollPane creditsFit = get("credits-fit");
creditsFit.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
creditsFit.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
final VBox creditsContainer = get("credits-container");
creditsContainer.setSpacing(lineHeight);
final VBox creditsSpacerTop = get("credits-spacer-top");
creditsSpacerTop.setMinHeight(App.getHeight() - lineHeight);
final VBox creditsSpacerBottom = get("credits-spacer-bottom");
creditsSpacerBottom.setMinHeight(App.getHeight() - lineHeight);
final Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0), new KeyValue(creditsFit.vvalueProperty(), 0.0)),
new KeyFrame(Duration.seconds(length), new KeyValue(creditsFit.vvalueProperty(), 1.0))
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
}

View File

@@ -1,45 +0,0 @@
package org.toop.app.view.views;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.text.Text;
public final class ErrorView extends View {
private final String error;
public ErrorView(String error) {
super(false, "bg-popup");
this.error = error;
}
@Override
public void setup() {
final Text errorHeader = header();
errorHeader.setText(AppContext.getString("error"));
final Text errorText = text();
errorText.setText(error);
final Button okButton = button();
okButton.setText(AppContext.getString("ok"));
okButton.setOnAction(_ -> { ViewStack.pop(); });
add(Pos.CENTER,
vboxFill(
errorHeader,
separator(),
vspacer(),
errorText,
vspacer(),
separator(),
okButton
)
);
}
}

View File

@@ -1,184 +0,0 @@
package org.toop.app.view.views;
import javafx.application.Platform;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.text.Text;
import java.util.function.Consumer;
public final class GameView extends View {
private static class GameOverView extends View {
private final boolean iWon;
private final String winner;
public GameOverView(boolean iWon, String winner) {
super(false, "bg-popup");
this.iWon = iWon;
this.winner = winner;
}
@Override
public void setup() {
final Text gameOverHeader = header();
gameOverHeader.setText(AppContext.getString("game-over"));
final Button okButton = button();
okButton.setText(AppContext.getString("ok"));
okButton.setOnAction(_ -> { ViewStack.pop(); });
Text gameOverText = text();
if (winner.isEmpty()) {
gameOverText.setText(AppContext.getString("the-game-ended-in-a-draw"));
} else {
if (iWon) {
gameOverText.setText(AppContext.getString("you-win") + " " + winner);
} else {
gameOverText.setText(AppContext.getString("you-lost-against") + " " + winner);
}
}
add(Pos.CENTER,
fit(vboxFill(
gameOverHeader,
separator(),
vspacer(),
gameOverText,
vspacer(),
separator(),
okButton
))
);
}
}
private final Button forfeitButton;
private final Button exitButton;
private final Text currentPlayerHeader;
private final Text currentMoveHeader;
private final Text nextPlayerHeader;
private final ListView<Text> chatListView;
private final TextField chatInput;
public GameView(Runnable onForfeit, Runnable onExit, Consumer<String> onMessage) {
assert onExit != null;
super(true, "bg-primary");
if (onForfeit != null) {
forfeitButton = button();
forfeitButton.setText(AppContext.getString("forfeit"));
forfeitButton.setOnAction(_ -> onForfeit.run());
} else {
forfeitButton = null;
}
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
if (onMessage != null) {
chatListView = new ListView<Text>();
chatInput = input();
chatInput.setOnAction(_ -> {
onMessage.accept(chatInput.getText());
chatInput.setText("");
});
} else {
chatListView = null;
chatInput = null;
}
exitButton = button();
exitButton.setText(AppContext.getString("exit"));
exitButton.setOnAction(_ -> onExit.run());
currentPlayerHeader = header("", "current-player");
currentMoveHeader = header();
nextPlayerHeader = header();
}
@Override
public void setup() {
add(Pos.TOP_RIGHT,
fit(vboxFill(
currentPlayerHeader,
hboxFill(
separator(),
currentMoveHeader,
separator()
),
nextPlayerHeader
))
);
add(Pos.BOTTOM_LEFT,
vboxFill(
forfeitButton,
exitButton
)
);
if (chatListView != null) {
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
chatListView,
chatInput
)
));
}
}
public void nextPlayer(boolean isMe, String currentPlayer, String currentMove, String nextPlayer) {
Platform.runLater(() -> {
currentPlayerHeader.setText(currentPlayer);
currentMoveHeader.setText(currentMove);
nextPlayerHeader.setText(nextPlayer);
if (isMe) {
currentPlayerHeader.getStyleClass().add("my-turn");
} else {
currentPlayerHeader.getStyleClass().remove("my-turn");
}
});
}
public void updateChat(String message) {
if (chatListView == null) {
return;
}
final Text messageText = text();
messageText.setText(message);
chatListView.getItems().add(messageText);
}
public void gameOver(boolean iWon, String winner) {
ViewStack.push(new GameOverView(iWon, winner));
}
}

View File

@@ -1,171 +0,0 @@
package org.toop.app.view.views;
import org.toop.app.GameInformation;
import org.toop.app.game.Connect4Game;
import org.toop.app.game.ReversiGame;
import org.toop.app.game.TicTacToeGameThread;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import java.util.ArrayList;
import java.util.List;
public final class LocalMultiplayerView extends View {
private final GameInformation information;
public LocalMultiplayerView(GameInformation information) {
super(true, "bg-primary");
this.information = information;
}
public LocalMultiplayerView(GameInformation.Type type) {
this(new GameInformation(type));
}
@Override
public void setup() {
final Button playButton = button();
playButton.setText(AppContext.getString("play"));
playButton.setOnAction(_ -> {
for (final GameInformation.Player player : information.players) {
if (player.name.isEmpty()) {
ViewStack.push(new ErrorView(AppContext.getString("please-enter-your-name")));
return;
}
}
switch (information.type) {
case TICTACTOE: new TicTacToeGameThread(information); break;
case REVERSI: new ReversiGame(information); break;
case CONNECT4: new Connect4Game(information); break;
// case BATTLESHIP: new BattleshipGame(information); break;
}
});
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit(vboxFill(
hbox(
setupPlayers()
),
separator(),
playButton
))
);
final Button backButton = button();
backButton.setText(AppContext.getString("back"));
backButton.setOnAction(_ -> { ViewStack.push(new MainView()); });
add(Pos.BOTTOM_LEFT,
vboxFill(
backButton
)
);
}
private VBox[] setupPlayers() {
final VBox[] playerBoxes = new VBox[information.type.getPlayerCount()];
for (int i = 0; i < playerBoxes.length; i++) {
final int index = i;
List<Node> nodes = new ArrayList<>();
final Text playerHeader = header();
playerHeader.setText(AppContext.getString("player") + " #" + (i + 1));
nodes.add(playerHeader);
nodes.add(separator());
final Text nameText = text();
nameText.setText(AppContext.getString("name"));
if (information.players[i].isHuman) {
final Button playerToggle = button();
playerToggle.setText(AppContext.getString("player"));
playerToggle.setOnAction(_ -> {
information.players[index].isHuman = false;
cleanup();
setup();
});
nodes.add(vboxFill(playerToggle));
final TextField playerNameInput = input();
playerNameInput.setPromptText(AppContext.getString("enter-your-name"));
playerNameInput.setText(information.players[i].name);
playerNameInput.textProperty().addListener((_, _, newValue) -> {
information.players[index].name = newValue;
});
nodes.add(vboxFill(nameText, playerNameInput));
} else {
final Button computerToggle = button();
computerToggle.setText(AppContext.getString("computer"));
computerToggle.setOnAction(_ -> {
information.players[index].isHuman = true;
cleanup();
setup();
});
nodes.add(vboxFill(computerToggle));
information.players[i].name = "Pism Bot V" + i;
final Text computerNameText = text();
computerNameText.setText(information.players[index].name);
nodes.add(vboxFill(nameText, computerNameText));
final Text computerDifficultyText = text();
computerDifficultyText.setText(AppContext.getString("computer-difficulty"));
final Slider computerDifficultySlider = slider();
computerDifficultySlider.setMin(0);
computerDifficultySlider.setMax(information.type.getMaxDepth());
computerDifficultySlider.setValue(information.players[i].computerDifficulty);
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
information.players[index].computerDifficulty = newValue.intValue();
});
nodes.add(vboxFill(computerDifficultyText, computerDifficultySlider));
final Text computerThinkTimeText = text();
computerThinkTimeText.setText(AppContext.getString("computer-think-time"));
final Slider computerThinkTimeSlider = slider();
computerThinkTimeSlider.setMin(0);
computerThinkTimeSlider.setMax(5);
computerThinkTimeSlider.setValue(information.players[i].computerThinkTime);
computerThinkTimeSlider.valueProperty().addListener((_, _, newValue) -> {
information.players[index].computerThinkTime = newValue.intValue();
});
nodes.add(vboxFill(computerThinkTimeText, computerThinkTimeSlider));
}
playerBoxes[i] = vboxFill(nodes.toArray(new Node[0]));
}
return playerBoxes;
}
}

View File

@@ -1,57 +0,0 @@
package org.toop.app.view.views;
import org.toop.app.GameInformation;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
public final class LocalView extends View {
public LocalView() {
super(true, "bg-primary");
}
@Override
public void setup() {
final Button ticTacToeButton = button();
ticTacToeButton.setText(AppContext.getString("tic-tac-toe"));
ticTacToeButton.setOnAction(_ -> { ViewStack.push(new LocalMultiplayerView(GameInformation.Type.TICTACTOE)); });
final Button reversiButton = button();
reversiButton.setText(AppContext.getString("reversi"));
reversiButton.setOnAction(_ -> { ViewStack.push(new LocalMultiplayerView(GameInformation.Type.REVERSI)); });
final Button connect4Button = button();
connect4Button.setText(AppContext.getString("connect4"));
connect4Button.setOnAction(_ -> { ViewStack.push(new LocalMultiplayerView(GameInformation.Type.CONNECT4)); });
add(Pos.CENTER,
fit(vboxFill(
ticTacToeButton,
reversiButton,
connect4Button
))
);
final Button backButton = button();
backButton.setText(AppContext.getString("back"));
backButton.setOnAction(_ -> { ViewStack.push(new MainView()); });
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.BOTTOM_LEFT,
vboxFill(
backButton
)
);
}
}

View File

@@ -1,55 +0,0 @@
package org.toop.app.view.views;
import org.toop.app.App;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.local.AppContext;
import org.toop.app.view.displays.SongDisplay;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
public final class MainView extends View {
public MainView() {
super(true, "bg-primary");
}
@Override
public void setup() {
final Button localButton = button();
localButton.setText(AppContext.getString("local"));
localButton.setOnAction(_ -> { ViewStack.push(new LocalView()); });
final Button onlineButton = button();
onlineButton.setText(AppContext.getString("online"));
onlineButton.setOnAction(_ -> { ViewStack.push(new OnlineView()); });
final Button creditsButton = button();
creditsButton.setText(AppContext.getString("credits"));
creditsButton.setOnAction(_ -> { ViewStack.push(new CreditsView()); });
final Button optionsButton = button();
optionsButton.setText(AppContext.getString("options"));
optionsButton.setOnAction(_ -> { ViewStack.push(new OptionsView()); });
final Button quitButton = button();
quitButton.setText(AppContext.getString("quit"));
quitButton.setOnAction(_ -> { App.startQuit(); });
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit(vboxFill(
localButton,
onlineButton,
creditsButton,
optionsButton,
quitButton
))
);
}
}

View File

@@ -1,91 +0,0 @@
package org.toop.app.view.views;
import org.toop.app.Server;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.text.Text;
public class OnlineView extends View {
public OnlineView() {
super(true, "bg-primary");
}
@Override
public void setup() {
final Text serverInformationHeader = header();
serverInformationHeader.setText(AppContext.getString("server-information"));
final Text serverIPText = text();
serverIPText.setText(AppContext.getString("ip-address"));
final TextField serverIPInput = input();
serverIPInput.setPromptText(AppContext.getString("enter-the-server-ip"));
final Text serverPortText = text();
serverPortText.setText(AppContext.getString("port"));
final TextField serverPortInput = input();
serverPortInput.setPromptText(AppContext.getString("enter-the-server-port"));
final Text playerNameText = text();
playerNameText.setText(AppContext.getString("player-name"));
final TextField playerNameInput = input();
playerNameInput.setPromptText(AppContext.getString("enter-your-name"));
final Button connectButton = button();
connectButton.setText(AppContext.getString("connect"));
connectButton.setOnAction(_ -> {
new Server(serverIPInput.getText(), serverPortInput.getText(), playerNameInput.getText());
});
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit(vboxFill(
serverInformationHeader,
separator(),
vboxFill(
serverIPText,
serverIPInput
),
vboxFill(
serverPortText,
serverPortInput
),
vboxFill(
playerNameText,
playerNameInput
),
vboxFill(
connectButton
)
))
);
final Button backButton = button();
backButton.setText(AppContext.getString("back"));
backButton.setOnAction(_ -> { ViewStack.push(new MainView()); });
add(Pos.BOTTOM_LEFT,
vboxFill(
backButton
)
);
}
}

View File

@@ -1,258 +0,0 @@
package org.toop.app.view.views;
import org.toop.app.App;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.framework.audio.VolumeControl;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import org.toop.local.AppContext;
import org.toop.local.AppSettings;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Slider;
import javafx.scene.text.Text;
import javafx.util.StringConverter;
import java.util.Locale;
public final class OptionsView extends View {
public OptionsView() {
super(false, "bg-secondary");
}
@Override
public void setup() {
final Text generalHeader = header();
generalHeader.setText(AppContext.getString("general"));
final Text volumeHeader = header();
volumeHeader.setText(AppContext.getString("volume"));
final Text styleHeader = header();
styleHeader.setText(AppContext.getString("style"));
add(Pos.CENTER,
fit(hboxFill(
vboxFill(
generalHeader,
separator(),
vboxFill(
text("language-text"),
combobox("language-combobox")
),
vboxFill(
button("fullscreen-button")
)
),
vboxFill(
volumeHeader,
separator(),
vboxFill(
text("master-volume-text"),
slider("master-volume-slider")
),
vboxFill(
text("effects-volume-text"),
slider("effects-volume-slider")
),
vboxFill(
text("music-volume-text"),
slider("music-volume-slider")
)
),
vboxFill(
styleHeader,
separator(),
vboxFill(
text("theme-text"),
combobox("theme-combobox")
),
vboxFill(
text("layout-text"),
combobox("layout-combobox")
)
)
))
);
setupLanguageOption();
setupMasterVolumeOption();
setupEffectsVolumeOption();
setupMusicVolumeOption();
setupThemeOption();
setupLayoutOption();
setupFullscreenOption();
final Button backButton = button();
backButton.setText(AppContext.getString("back"));
backButton.setOnAction(_ -> { ViewStack.pop(); });
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.BOTTOM_LEFT,
vboxFill(
backButton
)
);
}
private void setupLanguageOption() {
final Text languageText = get("language-text");
languageText.setText(AppContext.getString("language"));
final ComboBox<Locale> languageCombobox = get("language-combobox");
languageCombobox.getItems().addAll(AppContext.getLocalization().getAvailableLocales());
languageCombobox.setValue(AppContext.getLocale());
languageCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
AppSettings.getSettings().setLocale(newValue.toString());
AppContext.setLocale(newValue);
});
languageCombobox.setConverter(new StringConverter<>() {
@Override
public String toString(Locale locale) {
return AppContext.getString(locale.getDisplayName().toLowerCase());
}
@Override
public Locale fromString(String s) {
return null;
}
});
}
private void setupMasterVolumeOption() {
final Text masterVolumeText = get("master-volume-text");
masterVolumeText.setText(AppContext.getString("master-volume"));
final Slider masterVolumeSlider = get("master-volume-slider");
masterVolumeSlider.setMin(0);
masterVolumeSlider.setMax(100);
masterVolumeSlider.setValue(AppSettings.getSettings().getVolume());
masterVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
AppSettings.getSettings().setVolume(newValue.intValue());
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.MASTERVOLUME)).asyncPostEvent();
});
}
private void setupEffectsVolumeOption() {
final Text effectsVolumeText = get("effects-volume-text");
effectsVolumeText.setText(AppContext.getString("effects-volume"));
final Slider effectsVolumeSlider = get("effects-volume-slider");
effectsVolumeSlider.setMin(0);
effectsVolumeSlider.setMax(100);
effectsVolumeSlider.setValue(AppSettings.getSettings().getFxVolume());
effectsVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
AppSettings.getSettings().setFxVolume(newValue.intValue());
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.FX)).asyncPostEvent();
});
}
private void setupMusicVolumeOption() {
final Text musicVolumeText = get("music-volume-text");
musicVolumeText.setText(AppContext.getString("music-volume"));
final Slider musicVolumeSlider = get("music-volume-slider");
musicVolumeSlider.setMin(0);
musicVolumeSlider.setMax(100);
musicVolumeSlider.setValue(AppSettings.getSettings().getMusicVolume());
musicVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
AppSettings.getSettings().setMusicVolume(newValue.intValue());
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.MUSIC)).asyncPostEvent();
});
}
private void setupThemeOption() {
final Text themeText = get("theme-text");
themeText.setText(AppContext.getString("theme"));
final ComboBox<String> themeCombobox = get("theme-combobox");
themeCombobox.getItems().addAll("dark", "light", "high-contrast");
themeCombobox.setValue(AppSettings.getSettings().getTheme());
themeCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
AppSettings.getSettings().setTheme(newValue);
App.setStyle(newValue, AppSettings.getSettings().getLayoutSize());
});
themeCombobox.setConverter(new StringConverter<>() {
@Override
public String toString(String theme) {
return AppContext.getString(theme);
}
@Override
public String fromString(String s) {
return null;
}
});
}
private void setupLayoutOption() {
final Text layoutText = get("layout-text");
layoutText.setText(AppContext.getString("layout-size"));
final ComboBox<String> layoutCombobox = get("layout-combobox");
layoutCombobox.getItems().addAll("small", "medium", "large");
layoutCombobox.setValue(AppSettings.getSettings().getLayoutSize());
layoutCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
AppSettings.getSettings().setLayoutSize(newValue);
App.setStyle(AppSettings.getSettings().getTheme(), newValue);
});
layoutCombobox.setConverter(new StringConverter<>() {
@Override
public String toString(String layout) {
return AppContext.getString(layout);
}
@Override
public String fromString(String s) {
return null;
}
});
}
private void setupFullscreenOption() {
final Button fullscreenButton = get("fullscreen-button");
if (AppSettings.getSettings().getFullscreen()) {
fullscreenButton.setText(AppContext.getString("windowed"));
fullscreenButton.setOnAction(_ -> {
AppSettings.getSettings().setFullscreen(false);
App.setFullscreen(false);
});
} else {
fullscreenButton.setText(AppContext.getString("fullscreen"));
fullscreenButton.setOnAction(_ -> {
AppSettings.getSettings().setFullscreen(true);
App.setFullscreen(true);
});
}
}
}

View File

@@ -1,40 +0,0 @@
package org.toop.app.view.views;
import org.toop.app.App;
import org.toop.app.view.View;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.text.Text;
public final class QuitView extends View {
public QuitView() {
super(false, "bg-popup");
}
@Override
public void setup() {
final Text sureHeader = header();
sureHeader.setText(AppContext.getString("are-you-sure"));
final Button yesButton = button();
yesButton.setText(AppContext.getString("yes"));
yesButton.setOnAction(_ -> { App.quit(); });
final Button noButton = button();
noButton.setText(AppContext.getString("no"));
noButton.setOnAction(_ -> { App.stopQuit(); });
add(Pos.CENTER,
fit(vbox(
sureHeader,
hbox(
yesButton,
noButton
)
))
);
}
}

View File

@@ -1,119 +0,0 @@
package org.toop.app.view.views;
import org.toop.app.GameInformation;
import org.toop.app.Server;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Slider;
import javafx.scene.text.Text;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
public final class SendChallengeView extends View {
private final Server server;
private final String opponent;
private final BiConsumer<GameInformation.Player, String> onSend;
private final GameInformation.Player playerInformation;
public SendChallengeView(Server server, String opponent, BiConsumer<GameInformation.Player, String> onSend) {
super(false, "bg-popup");
this.server = server;
this.opponent = opponent;
this.onSend = onSend;
playerInformation = new GameInformation.Player();
}
@Override
public void setup() {
final Text challengeText = text();
challengeText.setText(AppContext.getString("challenge"));
final Text opponentHeader = header();
opponentHeader.setText(opponent);
final Text gameText = text();
gameText.setText(AppContext.getString("to-a-game-of"));
final ComboBox<String> gamesCombobox = combobox();
gamesCombobox.getItems().addAll(server.getGameList());
gamesCombobox.setValue(gamesCombobox.getItems().getFirst());
final Button sendButton = button();
sendButton.setText(AppContext.getString("send"));
sendButton.setOnAction(_ -> { onSend.accept(playerInformation, gamesCombobox.getValue()); });
final Button cancelButton = button();
cancelButton.setText(AppContext.getString("cancel"));
cancelButton.setOnAction(_ -> {
ViewStack.pop(); });
final List<Node> nodes = new ArrayList<>();
if (playerInformation.isHuman) {
final Button playerToggle = button();
playerToggle.setText(AppContext.getString("player"));
playerToggle.setOnAction(_ -> {
playerInformation.isHuman = false;
cleanup();
setup();
});
nodes.add(vbox(playerToggle));
} else {
final Button computerToggle = button();
computerToggle.setText(AppContext.getString("computer"));
computerToggle.setOnAction(_ -> {
playerInformation.isHuman = true;
cleanup();
setup();
});
nodes.add(vbox(computerToggle));
final Text computerDifficultyText = text();
computerDifficultyText.setText(AppContext.getString("computer-difficulty"));
final Slider computerDifficultySlider = slider();
computerDifficultySlider.setMin(0);
computerDifficultySlider.setMax(Server.gameToType(gamesCombobox.getValue()).getMaxDepth());
computerDifficultySlider.setValue(playerInformation.computerDifficulty);
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
playerInformation.computerDifficulty = newValue.intValue();
});
nodes.add(vbox(computerDifficultyText, computerDifficultySlider));
}
add(Pos.CENTER,
fit(hboxFill(
vboxFill(
challengeText,
opponentHeader,
gameText,
gamesCombobox,
separator(),
hboxFill(
sendButton,
cancelButton
)
),
vboxFill(
nodes.toArray(new Node[0])
)
))
);
}
}

View File

@@ -1,89 +0,0 @@
package org.toop.app.view.views;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.text.Text;
import java.util.List;
import java.util.function.Consumer;
public final class ServerView extends View {
private final String user;
private final Consumer<String> onPlayerClicked;
private final Runnable onDisconnect;
private ListView<Button> listView;
public ServerView(String user, Consumer<String> onPlayerClicked, Runnable onDisconnect) {
super(true, "bg-primary");
this.user = user;
this.onPlayerClicked = onPlayerClicked;
this.onDisconnect = onDisconnect;
}
public void update(List<String> players) {
Platform.runLater(() -> {
listView.getItems().clear();
for (int i = 0; i < players.size(); i++) {
final int finalI = i;
final Button button = button();
button.setText(players.get(i));
button.setOnAction(_ -> {
onPlayerClicked.accept(players.get(finalI));
});
listView.getItems().add(button);
}
});
}
@Override
public void setup() {
final Text playerHeader = header();
playerHeader.setText(user);
listView = new ListView<Button>();
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit(vboxFill(
vbox(
playerHeader,
separator()
),
listView
))
);
final Button disconnectButton = button();
disconnectButton.setText(AppContext.getString("disconnect"));
disconnectButton.setOnAction(_ -> {
onDisconnect.run();
ViewStack.push(new OnlineView());
});
add(Pos.BOTTOM_LEFT,
vboxFill(
disconnectButton
)
);
}
}

View File

@@ -1,7 +1,10 @@
package org.toop.app.widget; package org.toop.app.widget;
import javafx.scene.image.ImageView;
import org.toop.framework.resource.resources.ImageAsset;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import java.io.File;
import java.util.function.Consumer; import java.util.function.Consumer;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
@@ -18,38 +21,55 @@ import javafx.scene.layout.VBox;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import javafx.util.StringConverter; import javafx.util.StringConverter;
public final class Primitive { public final class Primitive {
public static Text header(String key) { public static Text header(String key, boolean localize) {
var header = new Text(); var header = new Text();
header.getStyleClass().add("header"); header.getStyleClass().add("header");
if (!key.isEmpty()) { if (!key.isEmpty()) {
header.setText(AppContext.getString(key)); if (localize) header.setText(AppContext.getString(key)); else header.setText(key);
header.textProperty().bind(AppContext.bindToKey(key)); header.textProperty().bind(AppContext.bindToKey(key, localize));
} }
return header; return header;
} }
public static Text text(String key) { public static Text header(String key) {
return header(key, true);
}
public static Text text(String key, boolean localize) {
var text = new Text(); var text = new Text();
text.getStyleClass().add("text"); text.getStyleClass().add("text");
if (!key.isEmpty()) { if (!key.isEmpty()) {
text.setText(AppContext.getString(key)); if (localize) text.setText(AppContext.getString(key)); else text.setText(key);
text.textProperty().bind(AppContext.bindToKey(key)); text.textProperty().bind(AppContext.bindToKey(key, localize));
} }
return text; return text;
} }
public static Button button(String key, Runnable onAction) { public static Text text(String key) {
return text(key, true);
}
public static ImageView image(ImageAsset imageAsset) {
ImageView imageView = new ImageView(imageAsset.getImage());
imageView.getStyleClass().add("image");
imageView.setPreserveRatio(true);
imageView.setFitWidth(400);
imageView.setFitHeight(400);
return imageView;
}
public static Button button(String key, Runnable onAction, boolean localize) {
var button = new Button(); var button = new Button();
button.getStyleClass().add("button"); button.getStyleClass().add("button");
if (!key.isEmpty()) { if (!key.isEmpty()) {
button.setText(AppContext.getString(key)); if (localize) button.setText(AppContext.getString(key)); else button.setText(key);
button.textProperty().bind(AppContext.bindToKey(key)); button.textProperty().bind(AppContext.bindToKey(key, localize));
} }
if (onAction != null) { if (onAction != null) {
@@ -60,13 +80,17 @@ public final class Primitive {
return button; return button;
} }
public static TextField input(String promptKey, String text, Consumer<String> onValueChanged) { public static Button button(String key, Runnable onAction) {
return button(key, onAction, true);
}
public static TextField input(String promptKey, String text, Consumer<String> onValueChanged, boolean localize) {
var input = new TextField(); var input = new TextField();
input.getStyleClass().add("input"); input.getStyleClass().add("input");
if (!promptKey.isEmpty()) { if (!promptKey.isEmpty()) {
input.setPromptText(AppContext.getString(promptKey)); if (localize) input.setPromptText(AppContext.getString(promptKey)); else input.setPromptText(promptKey);
input.promptTextProperty().bind(AppContext.bindToKey(promptKey)); input.promptTextProperty().bind(AppContext.bindToKey(promptKey, localize));
} }
input.setText(text); input.setText(text);
@@ -79,6 +103,10 @@ public final class Primitive {
return input; return input;
} }
public static TextField input(String promptKey, String text, Consumer<String> onValueChanged) {
return input(promptKey, text, onValueChanged, true);
}
public static Slider slider(int min, int max, int value, Consumer<Integer> onValueChanged) { public static Slider slider(int min, int max, int value, Consumer<Integer> onValueChanged) {
var slider = new Slider(); var slider = new Slider();
slider.getStyleClass().add("slider"); slider.getStyleClass().add("slider");

View File

@@ -0,0 +1,5 @@
package org.toop.app.widget;
public interface Updatable {
void update();
}

View File

@@ -62,4 +62,16 @@ public final class WidgetContainer {
public static ViewWidget getCurrentView() { public static ViewWidget getCurrentView() {
return currentView; return currentView;
} }
public static void setCurrentView(ViewWidget view) {
if (root == null || view == null) {
return;
}
Platform.runLater(() -> {
root.getChildren().clear();
root.getChildren().add(view.getNode());
currentView = view;
});
}
} }

View File

@@ -0,0 +1,75 @@
package org.toop.app.widget.complex;
import javafx.geometry.Pos;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
public class LoadingWidget extends ViewWidget implements Update { // TODO make of widget type
private final ProgressBar progressBar;
private Runnable success = () -> {};
private Runnable failure = () -> {};
private int maxAmount;
private int amount;
private float percentage = 0.0f;
public LoadingWidget(int startAmount, int maxAmount) {
amount = startAmount;
this.maxAmount = maxAmount;
progressBar = new ProgressBar();
HBox box = new HBox(10, progressBar);
add(Pos.CENTER, box);
}
public void setMaxAmount(int maxAmount) {
this.maxAmount = maxAmount;
}
public void setAmount(int amount) {
this.amount = amount;
update();
}
public void setAmount() {
setAmount(this.amount+1);
}
public void setOnSuccess(Runnable onSuccess) {
success = onSuccess;
}
public void setOnFailure(Runnable onFailure) {
failure = onFailure;
}
public void triggerSuccess() {
success.run();
}
public void triggerFailure() {
failure.run();
}
@Override
public void update() {
if (amount >= maxAmount) {
triggerSuccess();
System.out.println("triggered");
this.hide();
return;
} else if (amount < 0) {
triggerFailure();
System.out.println("triggerFailure");
this.hide();
return;
}
percentage = (float) amount / maxAmount;
progressBar.setProgress(percentage);
}
}

View File

@@ -1,7 +1,21 @@
package org.toop.app.widget.complex; package org.toop.app.widget.complex;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
public abstract class PopupWidget extends StackWidget { public abstract class PopupWidget extends StackWidget {
private final Button popButton;
public PopupWidget() { public PopupWidget() {
super("bg-popup"); super("bg-popup");
popButton = new Button("X");
popButton.setOnAction(_ -> hide());
add(Pos.TOP_RIGHT, popButton);
}
protected void setOnPop(Runnable onPop) {
popButton.setOnAction(_ -> onPop.run());
} }
} }

View File

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

View File

@@ -22,7 +22,7 @@ public class SongDisplay extends VBox implements Widget {
public SongDisplay() { public SongDisplay() {
new EventFlow() new EventFlow()
.listen(this::updateTheSong); .listen(AudioEvents.PlayingMusic.class, this::updateTheSong, false);
setAlignment(Pos.CENTER); setAlignment(Pos.CENTER);
setMaxHeight(Region.USE_PREF_SIZE); setMaxHeight(Region.USE_PREF_SIZE);

View File

@@ -20,5 +20,10 @@ public class QuitPopup extends PopupWidget {
}); });
add(Pos.CENTER, confirmWidget); add(Pos.CENTER, confirmWidget);
setOnPop(() -> {
App.stopQuit();
hide();
});
} }
} }

View File

@@ -0,0 +1,115 @@
package org.toop.app.widget.tutorial;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.image.ImageView;
import javafx.scene.text.Text;
import org.apache.maven.surefire.shared.lang3.tuple.ImmutablePair;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.Updatable;
import org.toop.app.widget.WidgetContainer;
import org.toop.app.widget.complex.PopupWidget;
import org.toop.framework.resource.resources.ImageAsset;
import org.toop.local.AppContext;
import java.util.List;
/**
* A widget base for all the tutorial widgets.
*
* <p>Usage example:
*
* <pre>{@code
* public class Connect4TutorialWidget extends BaseTutorialWidget {
* public Connect4TutorialWidget(Runnable nextScreen) {
* super(List.of(
* new ImmutablePair<>("connect4.1", ResourceManager.get("connect41.png")),
* new ImmutablePair<>("connect4.2", ResourceManager.get("connect42.png"))
* ), nextScreen);
* }
* }</pre>
*/
public class BaseTutorialWidget extends PopupWidget implements Updatable {
private final Text tutorialText;
private final ImageView imagery;
private final Button previousButton;
private final Button nextButton;
private final List<ImmutablePair<String, ImageAsset>> pages;
private final Runnable nextScreen;
private int pageIndex = 0;
public BaseTutorialWidget(List<ImmutablePair<String, ImageAsset>> pages, Runnable nextScreen) {
this.tutorialText = Primitive.text(pages.getFirst().getKey());
this.imagery = Primitive.image(pages.getFirst().getValue());
this.pages = pages;
this.nextScreen = nextScreen;
previousButton = Primitive.button("goback", () -> { update(false); this.hide(); });
nextButton = Primitive.button(">", () -> update(true));
var w = Primitive.hbox(
previousButton,
nextButton
);
var x = Primitive.vbox(imagery, tutorialText);
add(Pos.CENTER, Primitive.vbox(x, w));
WidgetContainer.add(Pos.CENTER, this);
}
@Override
public void update() {
update(true);
}
// TODO Refactor if statements to make code easier to read.
public void update(boolean next) {
pageIndex = next ? pageIndex + 1 : pageIndex - 1;
if (pageIndex >= pages.size()) {
pageIndex--;
return;
} else if (pageIndex < 0) {
pageIndex++;
return;
}
if (pageIndex == pages.size()-1) {
nextButton.textProperty().unbind();
nextButton.setText(AppContext.getString("startgame"));
nextButton.setOnAction((_) -> {
this.hide();
nextScreen.run();
});
} else {
nextButton.textProperty().unbind();
nextButton.setText(AppContext.getString(">"));
nextButton.setOnAction((_) -> this.update(true));
}
if (pageIndex == 0) {
previousButton.textProperty().unbind();
previousButton.setText(AppContext.getString("goback"));
previousButton.setOnAction((_) -> this.hide());
} else {
previousButton.textProperty().unbind();
previousButton.setText(AppContext.getString("<"));
previousButton.setOnAction((_) -> this.update(false));
}
var currentPage = pages.get(pageIndex);
var text = currentPage.getKey();
var image = currentPage.getValue();
tutorialText.textProperty().unbind();
tutorialText.setText(AppContext.getString(text));
imagery.setImage(Primitive.image(image).getImage());
}
}

View File

@@ -0,0 +1,15 @@
package org.toop.app.widget.tutorial;
import org.apache.maven.surefire.shared.lang3.tuple.ImmutablePair;
import org.toop.framework.resource.ResourceManager;
import java.util.List;
public class Connect4TutorialWidget extends BaseTutorialWidget {
public Connect4TutorialWidget(Runnable nextScreen) {
super(List.of(
new ImmutablePair<>("connect4.1", ResourceManager.get("connect41.png")),
new ImmutablePair<>("connect4.2", ResourceManager.get("connect42.png"))
), nextScreen);
}
}

View File

@@ -0,0 +1,17 @@
package org.toop.app.widget.tutorial;
import org.apache.maven.surefire.shared.lang3.tuple.ImmutablePair;
import org.toop.framework.resource.ResourceManager;
import java.util.List;
public class ReversiTutorialWidget extends BaseTutorialWidget {
public ReversiTutorialWidget(Runnable nextScreen) {
super(List.of(
new ImmutablePair<>("reversi1", ResourceManager.get("reversi1.png")),
new ImmutablePair<>("reversi2", ResourceManager.get("reversi2.png")),
new ImmutablePair<>("reversi3", ResourceManager.get("cat.jpg")),
new ImmutablePair<>("reversi4", ResourceManager.get("cat.jpg"))
), nextScreen);
}
}

View File

@@ -0,0 +1,22 @@
package org.toop.app.widget.tutorial;
import javafx.geometry.Pos;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.WidgetContainer;
import org.toop.app.widget.complex.PopupWidget;
import org.toop.local.AppSettings;
public class ShowEnableTutorialWidget extends PopupWidget {
public ShowEnableTutorialWidget(Runnable tutorial, Runnable nextScreen, Runnable appSettingsSetter) {
var a = Primitive.hbox(
Primitive.button("ok", () -> { appSettingsSetter.run(); tutorial.run(); this.hide(); }),
Primitive.button("no", () -> { appSettingsSetter.run(); nextScreen.run(); this.hide(); }),
Primitive.button("never", () -> { AppSettings.getSettings().setTutorialFlag(false); nextScreen.run(); this.hide(); })
);
var txt = Primitive.text("tutorial");
add(Pos.CENTER, Primitive.vbox(txt, a));
WidgetContainer.add(Pos.CENTER, this);
}
}

View File

@@ -0,0 +1,16 @@
package org.toop.app.widget.tutorial;
import org.apache.maven.surefire.shared.lang3.tuple.ImmutablePair;
import org.toop.framework.resource.ResourceManager;
import java.util.List;
public class TicTacToeTutorialWidget extends BaseTutorialWidget {
public TicTacToeTutorialWidget(Runnable nextScreen) {
super(List.of(
new ImmutablePair<>("tictactoe1", ResourceManager.get("tictactoe1.png")),
new ImmutablePair<>("tictactoe2", ResourceManager.get("tictactoe2.png"))
), nextScreen);
}
}

View File

@@ -11,18 +11,21 @@ import javafx.geometry.Pos;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.text.Text; 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;
public final class GameView extends ViewWidget { public final class GameView extends ViewWidget {
private final Text currentPlayerHeader; private final Text currentPlayerHeader;
private final Text currentMoveHeader; private final Text currentMoveHeader;
private final Text nextPlayerHeader; private final Text nextPlayerHeader;
private final Button forfeitButton; private final Button forfeitButton;
private final Button exitButton; private final Button exitButton;
private final Button tutorialButton;
private final TextField chatInput;
private final TextField chatInput; public GameView(Runnable onForfeit, Runnable onExit, Consumer<String> onMessage, String gameType) {
public GameView(Runnable onForfeit, Runnable onExit, Consumer<String> onMessage) {
currentPlayerHeader = Primitive.header(""); currentPlayerHeader = Primitive.header("");
currentMoveHeader = Primitive.header(""); currentMoveHeader = Primitive.header("");
nextPlayerHeader = Primitive.header(""); nextPlayerHeader = Primitive.header("");
@@ -48,6 +51,17 @@ public final class GameView extends ViewWidget {
chatInput = null; chatInput = null;
} }
switch(gameType) {
case "TicTacToe":
this.tutorialButton = Primitive.button("tutorialstring", () -> new TicTacToeTutorialWidget(() -> {})); break;
case "Reversi":
this.tutorialButton = Primitive.button("tutorialstring", () -> new ReversiTutorialWidget(() -> {})); break;
case "Connect4":
this.tutorialButton = Primitive.button("tutorialstring", () -> new Connect4TutorialWidget(() -> {})); break;
default:
this.tutorialButton = null; break;
}
setupLayout(); setupLayout();
} }
@@ -74,6 +88,10 @@ public final class GameView extends ViewWidget {
if (chatInput != null) { if (chatInput != null) {
add(Pos.BOTTOM_RIGHT, Primitive.vbox(chatInput)); 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) {

View File

@@ -1,19 +1,22 @@
package org.toop.app.widget.view; package org.toop.app.widget.view;
import javafx.application.Platform;
import org.toop.app.GameInformation; import org.toop.app.GameInformation;
import org.toop.app.game.Connect4Game; import org.toop.app.game.Connect4Game;
import org.toop.app.game.ReversiGame; import org.toop.app.game.ReversiGame;
import org.toop.app.game.TicTacToeGameThread; import org.toop.app.game.TicTacToeGameThread;
import org.toop.app.widget.Primitive; import org.toop.app.widget.Primitive;
import org.toop.app.widget.WidgetContainer;
import org.toop.app.widget.complex.PlayerInfoWidget; import org.toop.app.widget.complex.PlayerInfoWidget;
import org.toop.app.widget.complex.ViewWidget; import org.toop.app.widget.complex.ViewWidget;
import org.toop.app.widget.popup.ErrorPopup; import org.toop.app.widget.popup.ErrorPopup;
import org.toop.app.widget.tutorial.*;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import org.toop.local.AppSettings;
public class LocalMultiplayerView extends ViewWidget { public class LocalMultiplayerView extends ViewWidget {
private final GameInformation information; private final GameInformation information;
@@ -32,12 +35,42 @@ public class LocalMultiplayerView extends ViewWidget {
} }
switch (information.type) { switch (information.type) {
case TICTACTOE -> new TicTacToeGameThread(information); case TICTACTOE:
case REVERSI -> new ReversiGame(information); if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstTTT()) {
case CONNECT4 -> new Connect4Game(information); new ShowEnableTutorialWidget(
() -> new TicTacToeTutorialWidget(() -> new TicTacToeGameThread(information)),
() -> Platform.runLater(() -> new TicTacToeGameThread(information)),
() -> AppSettings.getSettings().setFirstTTT(false)
);
} else {
new TicTacToeGameThread(information);
}
break;
case REVERSI:
if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstReversi()) {
new ShowEnableTutorialWidget(
() -> new ReversiTutorialWidget(() -> new ReversiGame(information)),
() -> Platform.runLater(() -> new ReversiGame(information)),
() -> AppSettings.getSettings().setFirstReversi(false)
);
} else {
new ReversiGame(information);
}
break;
case CONNECT4:
if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstConnect4()) {
new ShowEnableTutorialWidget(
() -> new Connect4TutorialWidget(() -> new Connect4Game(information)),
() -> Platform.runLater(() -> new Connect4Game(information)),
() -> AppSettings.getSettings().setFirstConnect4(false)
);
} else {
new Connect4Game(information);
}
break;
}
// case BATTLESHIP -> new BattleshipGame(information); // case BATTLESHIP -> new BattleshipGame(information);
} });
});
var playerSection = setupPlayerSections(); var playerSection = setupPlayerSections();

View File

@@ -3,7 +3,6 @@ package org.toop.app.widget.view;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.widget.Primitive; import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.ViewWidget; import org.toop.app.widget.complex.ViewWidget;
import javafx.geometry.Pos; import javafx.geometry.Pos;
public class MainView extends ViewWidget { public class MainView extends ViewWidget {

View File

@@ -29,7 +29,7 @@ public final class ServerView extends ViewWidget {
} }
private void setupLayout() { private void setupLayout() {
var playerHeader = Primitive.header(user); var playerHeader = Primitive.header(user, false);
var playerListSection = Primitive.vbox( var playerListSection = Primitive.vbox(
playerHeader, playerHeader,
@@ -52,7 +52,7 @@ public final class ServerView extends ViewWidget {
listView.getItems().clear(); listView.getItems().clear();
for (String player : players) { for (String player : players) {
var playerButton = Primitive.button(player, () -> onPlayerClicked.accept(player)); var playerButton = Primitive.button(player, () -> onPlayerClicked.accept(player), false);
listView.getItems().add(playerButton); listView.getItems().add(playerButton);
} }
}); });

View File

@@ -58,10 +58,19 @@ public class AppContext {
return "MISSING RESOURCE"; return "MISSING RESOURCE";
} }
public static StringBinding bindToKey(String key) { public static StringBinding bindToKey(String key, boolean localize) {
return Bindings.createStringBinding( if (localize) return Bindings.createStringBinding(
() -> localization.getString(key, locale), () -> localization.getString(key, locale),
localeProperty localeProperty
); );
return Bindings.createStringBinding(
() -> key
);
} }
public static StringBinding bindToKey(String key) {
return bindToKey(key, true);
}
} }

View File

@@ -21,6 +21,8 @@ public class AppSettings {
settingsAsset.load(); settingsAsset.load();
} }
checkSettings();
Settings settingsData = settingsAsset.getContent(); Settings settingsData = settingsAsset.getContent();
AppContext.setLocale(Locale.of(settingsData.locale)); AppContext.setLocale(Locale.of(settingsData.locale));
@@ -64,4 +66,43 @@ public class AppSettings {
public static SettingsAsset getSettings() { public static SettingsAsset getSettings() {
return settingsAsset; return settingsAsset;
} }
public static void checkSettings() {
Settings s = settingsAsset.getContent();
boolean changed = false;
if (s.showTutorials == null) {
settingsAsset.setTutorialFlag(true);
changed = true;
}
if (s.firstReversi == null) {
settingsAsset.setFirstReversi(true);
changed = true;
}
if (s.firstTTT == null) {
settingsAsset.setFirstTTT(true);
changed = true;
}
if (s.firstConnect4 == null) {
settingsAsset.setFirstConnect4(true);
changed = true;
}
if (changed) {
getSettings().save();
}
}
public static void doDefaultSettings() {
settingsAsset.setFirstConnect4(true);
settingsAsset.setFirstTTT(true);
settingsAsset.setFirstReversi(true);
settingsAsset.setLocale("en");
settingsAsset.setTheme("dark");
settingsAsset.setFullscreen(false);
settingsAsset.setVolume(100);
settingsAsset.setFxVolume(20);
settingsAsset.setMusicVolume(15);
settingsAsset.setTutorialFlag(true);
settingsAsset.setLayoutSize("medium");
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -5,6 +5,7 @@ are-you-sure=\u0647\u0644 \u0623\u0646\u062a \u0645\u062a\u0623\u0643\u062f?
back=\u0627\u0644\u0631\u062c\u0648\u0639 back=\u0627\u0644\u0631\u062c\u0648\u0639
cancel=\u0625\u0644\u063a\u0627\u0621 cancel=\u0625\u0644\u063a\u0627\u0621
challenge=\u062a\u062d\u062f\u064a challenge=\u062a\u062d\u062f\u064a
connect4=Connect 4
computer-difficulty=\u0635\u0639\u0648\u0628 \u0627\u0644\u0643\u0645\u0628\u064a\u0648\u062a\u0631 computer-difficulty=\u0635\u0639\u0648\u0628 \u0627\u0644\u0643\u0645\u0628\u064a\u0648\u062a\u0631
computer-think-time=\u0648\u0642\u062a \u062a\u0641\u0643\u064a\u0631 \u0627\u0644\u0643\u0645\u0628\u064a\u0648\u062a\u0631 computer-think-time=\u0648\u0642\u062a \u062a\u0641\u0643\u064a\u0631 \u0627\u0644\u0643\u0645\u0628\u064a\u0648\u062a\u0631
computer=\u0643\u0645\u0628\u064a\u0648\u062a\u0631 computer=\u0643\u0645\u0628\u064a\u0648\u062a\u0631
@@ -41,6 +42,7 @@ merge-commander=Merge Commander
moral-support=\u062f\u0639\u0645 \u0623\u0639\u0635\u0627\u0628\u064a moral-support=\u062f\u0639\u0645 \u0623\u0639\u0635\u0627\u0628\u064a
music-volume=\u0635\u0648\u062a \u0627\u0644\u0645\u0648\u0633\u064a\u0642\u0649 music-volume=\u0635\u0648\u062a \u0627\u0644\u0645\u0648\u0633\u064a\u0642\u0649
name=\u0627\u0633\u0645 name=\u0627\u0633\u0645
never=\u0644\u0627\u060c \u0644\u0627 \u0623\u0631\u064A\u062F \u0623\u0646 \u0623\u0631\u0649 \u0623\u064A \u062F\u0631\u0648\u0633 \u0639\u0644\u0649 \u0627\u0644\u0625\u0637\u0644\u0627\u0642.
no=\u0644\u0627 no=\u0644\u0627
ok=\u0645\u0648\u0627\u0641\u0642 ok=\u0645\u0648\u0627\u0641\u0642
online=\u0645\u062a\u0635\u0644 online=\u0645\u062a\u0635\u0644
@@ -62,13 +64,27 @@ style=\u0623\u0633\u0644\u0648\u0628
the-game-ended-in-a-draw=\u0627\u0646\u062a\u0647\u062a \u0627\u0644\u0644\u0639\u0628\u0629 \u0628\u062a\u0639\u0627\u062f\u0644 the-game-ended-in-a-draw=\u0627\u0646\u062a\u0647\u062a \u0627\u0644\u0644\u0639\u0628\u0629 \u0628\u062a\u0639\u0627\u062f\u0644
theme=\u0645\u0648\u0636\u0648\u0639 theme=\u0645\u0648\u0636\u0648\u0639
tic-tac-toe=\u062a\u064a\u0643 \u062a\u0627\u0643 \u062a\u0648 tic-tac-toe=\u062a\u064a\u0643 \u062a\u0627\u0643 \u062a\u0648
tictactoe1 =\u0645\u0631\u062d\u0628\u064b\u0627 \u0628\u0643 \u0641\u064a \u0644\u0639\u0628\u0629 \u0625\u0643\u0633-\u0623\u0648! \u064a\u0645\u0643\u0646\u0643 \u0627\u0644\u0642\u064a\u0627\u0645 \u0628\u062d\u0631\u0643\u062a\u0643 \u0628\u0627\u0644\u0646\u0642\u0631 \u0639\u0644\u0649 \u0623\u064a \u0645\u0646 \u0627\u0644\u0645\u0631\u0628\u0639\u0627\u062a \u0627\u0644\u062a\u0633\u0639\u0629 \u0639\u0644\u0649 \u0627\u0644\u0644\u0648\u062d\u0629. \u0627\u0644\u0645\u062b\u0627\u0644 \u0623\u0639\u0644\u0627\u0647:
tictactoe2 =\u064a\u0641\u0648\u0632 \u0627\u0644\u0644\u0627\u0639\u0628 \u0639\u0646\u062f\u0645\u0627 \u064a\u062d\u0635\u0644 \u0639\u0644\u0649 \u062b\u0644\u0627\u062b \u0642\u0637\u0639 \u0645\u062a\u062a\u0627\u0644\u064a\u0629 ? \u0623\u0641\u0642\u064a\u064b\u0627 \u0623\u0648 \u0639\u0645\u0648\u062f\u064a\u064b\u0627 \u0623\u0648 \u0642\u0637\u0631\u064a\u064b\u0627. \u0641\u064a \u0627\u0644\u0645\u062b\u0627\u0644 \u0623\u0639\u0644\u0627\u0647\u060c \u064a\u0641\u0648\u0632 \u0627\u0644\u0644\u0627\u0639\u0628 \u0628\u0635\u0641 \u0642\u0637\u0631\u064a \u0645\u0646 \u062b\u0644\u0627\u062b \u0642\u0637\u0639
to-a-game-of=\u0625\u0644\u0649 \u0644\u0639\u0628\u0629 \u0645\u0646 to-a-game-of=\u0625\u0644\u0649 \u0644\u0639\u0628\u0629 \u0645\u0646
tutorial=\u0647\u0644 \u062a\u0631\u064a\u062f \u0645\u0634\u0627\u0647\u062f\u0629 \u062f\u0644\u064a\u0644 \u0644\u0647\u0630\u0627 \u0627\u0644\u0644\u0639\u0628\u0629\u061f
volume=\u0635\u0648\u062a volume=\u0635\u0648\u062a
windowed=\u0641\u064a \u0646\u0641\u0630\u0629 windowed=\u0641\u064a \u0646\u0641\u0630\u0629
yes=\u0646\u0639\u0645 yes=\u0646\u0639\u0645
you-lost-against=\u0623\u0646\u062a \u062e\u0633\u0631\u062a \u0636\u062f you-lost-against=\u0623\u0646\u062a \u062e\u0633\u0631\u062a \u0636\u062f
you-were-challenged-by=\u062a\u062d\u062f\u064a\u062a \u0645\u0646 you-were-challenged-by=\u062a\u062d\u062f\u064a\u062a \u0645\u0646
you-win=\u0623\u0646\u062a \u062a\u0641\u0648\u0632 you-win=\u0623\u0646\u062a \u062a\u0641\u0648\u0632
>=>
<=<
connect4.1=\u0645\u0631\u062d\u0628\u0627 \u0628\u0643 \u0641\u064a \u0644\u0639\u0628\u0629 Connect 4! \u064a\u0645\u0643\u0646\u0643 \u062a\u0633\u0645\u064a\u0629 \u0627\u0644\u0644\u0639\u0628 \u0645\u0646 \u0637\u0631\u0641 \u0645\u0646 \u0627\u0644\u0639\u0645\u0648\u062f\u0627\u062a. \u0633\u064a\u062a\u0645 \u0648\u0636\u0639 \u0627\u0644\u0644\u062d\u0629 \u0627\u0644\u0623\u0642\u0644 \u0641\u064a \u062a\u0644\u0643 \u0627\u0644\u0639\u0645\u0648\u062f\u0629 \u0627\u0644\u0645\u0644\u0623\u0629 \u0627\u0644\u062a\u064a \u0644\u064a\u0633\u062a\u0645\u0644\u0623 \u0627\u0644\u0645\u0644\u0623\u0629.
connect4.2=\u064a\u0645\u0643\u0646\u0643 \u0627\u0644\u0641\u0648\u0632 \u0628\u0647\u0632\u0645 \u0623\u0631\u062c\u0639 \u0645\u0646 \u0627\u0644\u0642\u0637\u0639 \u0627\u0644\u0641\u064a \u0627\u0644\u0623\u0641\u0642 \u0623\u0648 \u0627\u0644\u0645\u064a\u0646 \u0623\u0648 \u0623\u0641\u0642 \u0639\u0644\u0649 \u0627\u0644\u0645\u0644\u0623\u0629!
reversi1=\u0645\u0631\u062d\u0628\u0627 \u0628\u0643 \u0641\u064a \u0644\u0639\u0628\u0629 Reversi! \u064a\u0645\u0643\u0646\u0643 \u0627\u0644\u0644\u0639\u0628 \u0628\u0627\u0644\u0646\u0642\u0631 \u0639\u0644\u0649 \u0623\u064a \u0646\u0642\u0637\u0629 \u0645\u0634\u0631\u0641\u0629.
reversi2=\u0639\u0646\u062f\u0645\u0627 \u062a\u0646\u0642\u0631 \u0639\u0644\u0649 \u0646\u0642\u0637\u0629 \u0633\u064a\u062a\u063a\u064a\u0631 \u062c\u0645\u064a\u0639 \u0627\u0644\u0644\u0648\u0639\u0627\u0628 \u0628\u064a\u0646 \u0645\u0643\u0627\u0646 \u062a\u0636\u0639 \u0627\u0644\u0646\u0642\u0637\u0629 \u0648\u0646\u0642\u0637\u0629 \u0627\u0644\u0644\u0648\u0639 \u0627\u0644\u062a\u0627\u0644\u064a \u0627\u0644\u0645\u0648\u062c\u0648\u062f.
reversi3=\u0645\u0631\u062a\u0643 \u0642\u062f \u064a\u062a\u063a\u0627\u0637 \u0625\u0630\u0627 \u0644\u0645 \u064a\u0643\u0646 \u0647\u0646\u0627\u0643 \u062d\u0631\u0643 \u0642\u0627\u0646\u0648\u0646\u064a.
reversi4=\u0627\u0644\u0644\u0627\u0639\u0628 \u0627\u0644\u0630\u064a \u064a\u0641\u0648\u0632 \u0641\u064a \u0646\u0647\u0627\u064a\u0629 \u0627\u0644\u0644\u0639\u0628 \u0647\u0648 \u0627\u0644\u0630\u064a \u064a\u0643\u0648\u0646 \u0644\u062f\u064a\u0647 \u0627\u0644\u0623\u0643\u062b\u0631 \u0645\u0646 \u0627\u0644\u0644\u0648\u0639\u0627\u0628 \u0639\u0644\u0649 \u0627\u0644\u0644\u0648\u062d\u0629.
tutorialstring=\u0627\u0644\u062f\u0631\u0633 \u0627\u0644\u062a\u0648\u0636\u064a\u062d\u064a
startgame=\u0627\u0628\u062f\u0623 \u0627\u0644\u0644\u0639\u0628\u0629!
goback=\u0627\u0631\u062c\u0639
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629
chinese=\u4e2d\u6587 (\u0627\u0644\u0635\u064a\u0646\u064a\u0629) chinese=\u4e2d\u6587 (\u0627\u0644\u0635\u064a\u0646\u064a\u0629)

View File

@@ -9,6 +9,8 @@ computer-difficulty=Computer Schwierigkeit
computer-think-time=Computer Denkzeit computer-think-time=Computer Denkzeit
computer=Computer computer=Computer
connect=Verbinden connect=Verbinden
connect4=Connect 4
credits=Credits credits=Credits
dark=Dunkel dark=Dunkel
deny=Ablehnen deny=Ablehnen
@@ -41,6 +43,7 @@ merge-commander=Merge Commander
moral-support=Mentale Unterst\u00fctzung moral-support=Mentale Unterst\u00fctzung
music-volume=Musiklautst\u00e4rke music-volume=Musiklautst\u00e4rke
name=Name name=Name
never=Nein, ich m\u00f6chte niemals irgendwelche Tutorials sehen.
no=Nein no=Nein
ok=Ok ok=Ok
online=Online online=Online
@@ -62,13 +65,28 @@ style=Stil
the-game-ended-in-a-draw=Das Spiel endete unentschieden the-game-ended-in-a-draw=Das Spiel endete unentschieden
theme=Thema theme=Thema
tic-tac-toe=Tic Tac Toe tic-tac-toe=Tic Tac Toe
tictactoe1 =Willkommen beim Spiel Tic Tac Toe! Du kannst deinen Zug machen, indem du auf eines der 9 Felder auf dem Spielfeld klickst. Beispiel oben:
tictactoe2 =Ein Spieler gewinnt, wenn er drei Steine in einer Reihe hat ? horizontal, vertikal oder diagonal. Im obigen Beispiel gewinnt der Spieler mit einer diagonalen Reihe von drei Steinen.
to-a-game-of=zu einem Spiel von to-a-game-of=zu einem Spiel von
tutorial=M\u00f6chtest du ein Tutorial f\u00fcr dieses Spiel sehen?
volume=Lautst\u00e4rke volume=Lautst\u00e4rke
windowed=Fenstermodus windowed=Fenstermodus
yes=Ja yes=Ja
you-lost-against=Sie haben gegen ... verloren you-lost-against=Sie haben gegen ... verloren
you-were-challenged-by=Sie wurden herausgefordert von ... you-were-challenged-by=Sie wurden herausgefordert von ...
you-win=Sie gewinnen you-win=Sie gewinnen
>=>
<=<
connect4.1=Willkommen beim Spiel Connect 4! Du kannst einen Zug machen, indem du auf eine der Spalten klickst. Der Zug wird in der niedrigsten noch freien Reihe dieser Spalte platziert.
connect4.2=Du kannst gewinnen, indem du 4 Spielsteine deiner Farbe horizontal, diagonal oder vertikal verbindest! Siehe das obige Beispiel.
reversi1=Willkommen beim Spiel Reversi! Du kannst einen Zug machen, indem du auf einen der leicht transparenten Punkte klickst.
reversi2=Wenn du auf einen Punkt klickst, werden alle Spielsteine dazwischen umgedreht, bis zum n<>chsten Punkt. Siehe das Beispiel oben, wo Gelb die zu drehenden Steine ist.
reversi3=Dein Zug kann <20>bersprungen werden, wenn es keinen legalen Zug gibt. Dein Gegner spielt dann weiter, bis du einen legalen Zug machen kannst.
reversi4=Der Spieler, der am Ende die meisten Steine auf dem Brett hat, gewinnt.
tutorialstring=Tutorial
startgame=Spiel starten!
goback=Zur<EFBFBD>ck
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabisch) arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabisch)
chinese=\u4e2d\u6587 (Chinesisch) chinese=\u4e2d\u6587 (Chinesisch)

View File

@@ -42,6 +42,7 @@ moral-support=Moral Support
music-volume=Music Volume music-volume=Music Volume
name=Name name=Name
no=No no=No
never=No, I never want to see any tutorials
ok=Ok ok=Ok
online=Online online=Online
opengl=OpenGL opengl=OpenGL
@@ -62,6 +63,9 @@ style=Style
the-game-ended-in-a-draw=The game ended in a draw the-game-ended-in-a-draw=The game ended in a draw
theme=Theme theme=Theme
tic-tac-toe=Tic Tac Toe tic-tac-toe=Tic Tac Toe
tictactoe1 =Welcome to the game of Tic Tac Toe! You can make your move by clicking on any of the 9 squares on the board. Example above:
tictactoe2 =A player wins by getting 3 pieces in a row - horizontally, vertically or diagonally. In the example above, the player wins with a diagonal row of three pieces.
tutorial=Do you want a tutorial for this game?
connect4=Connect 4 connect4=Connect 4
to-a-game-of=to a game of to-a-game-of=to a game of
volume=Volume volume=Volume
@@ -70,6 +74,19 @@ yes=Yes
you-lost-against=You lost against you-lost-against=You lost against
you-were-challenged-by=You were challenged by you-were-challenged-by=You were challenged by
you-win=You win you-win=You win
>=>
<=<
// tutorial
connect4.1=Welcome to the game of Connect 4! You can make a move by clicking one of the columns. The move will be placed in the lowest row of that column that is not filled yet.
connect4.2=You can win by getting 4 pieces of your row horizontally, diagonally or vertically! For an example, see above.
reversi1=Welcome to the game of Reversi! You can make a move by clicking on one of the slightly transparent dots.
reversi2=Clicking on a dot will flip all the moves between where you place the dot and the next dot it finds. See the example above, where yellow is the moves to be flipped.
reversi3=Your turn may be skipped if there is no legal move. This will let your opponent play again until you get an opportunity at a legal move.
reversi4=The player who wins at the end of the game is the one who has the most pieces on the board.
tutorialstring=Tutorial
startgame=Start game!
goback=Go back
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabic) arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabic)
chinese=\u4e2d\u6587 (Chinese) chinese=\u4e2d\u6587 (Chinese)

View File

@@ -9,6 +9,8 @@ computer-difficulty=Dificultad del ordenador
computer-think-time=Tiempo de pensamiento del ordenador computer-think-time=Tiempo de pensamiento del ordenador
computer=Ordenador computer=Ordenador
connect=Conectar connect=Conectar
connect4=Connect 4
credits=Cr\u00e9ditos credits=Cr\u00e9ditos
dark=Oscuro dark=Oscuro
deny=Denegar deny=Denegar
@@ -41,6 +43,7 @@ merge-commander=Merge Commander
moral-support=Apoyo moral moral-support=Apoyo moral
music-volume=Volumen de m\u00fasica music-volume=Volumen de m\u00fasica
name=Nombre name=Nombre
never=No, no quiero ver ning\u00fan tutorial nunca.
no=No no=No
ok=OK ok=OK
online=En l\u00ednea online=En l\u00ednea
@@ -62,13 +65,28 @@ style=Estilo
the-game-ended-in-a-draw=El juego termin\u00f3 en empate the-game-ended-in-a-draw=El juego termin\u00f3 en empate
theme=Tema theme=Tema
tic-tac-toe=Tres en raya tic-tac-toe=Tres en raya
tictactoe1 =\u00a1Bienvenido al juego del Tres en Raya! Puedes hacer tu jugada haciendo clic en cualquiera de los 9 cuadros del tablero. Ejemplo arriba:
tictactoe2 =Un jugador gana al conseguir 3 fichas en l\u00ednea, ya sea horizontal, vertical o diagonalmente. En el ejemplo de arriba, el jugador gana con una fila diagonal de tres fichas.
to-a-game-of=a un juego de to-a-game-of=a un juego de
tutorial=\u00bfQuieres ver un tutorial para este juego?
volume=Volumen volume=Volumen
windowed=En ventana windowed=En ventana
yes=S\u00ed yes=S\u00ed
you-lost-against=Perdiste contra you-lost-against=Perdiste contra
you-were-challenged-by=Fuiste desafiado por you-were-challenged-by=Fuiste desafiado por
you-win=Ganaste you-win=Ganaste
>=>
<=<
connect4.1=\u00a1Bienvenido al juego de Connect 4! Puedes hacer un movimiento haciendo clic en una de las columnas. El movimiento se colocar<61> en la fila m<>s baja de esa columna que no est<73> llena.
connect4.2=\u00a1Puedes ganar consiguiendo 4 fichas de tu color horizontal, diagonal o verticalmente! Mira el ejemplo de arriba.
reversi1=\u00a1Bienvenido al juego de Reversi! Puedes hacer un movimiento haciendo clic en uno de los puntos ligeramente transparentes.
reversi2=Al hacer clic en un punto, se voltear<61>n todas las fichas entre donde colocas el punto y el siguiente punto que encuentre. Mira el ejemplo de arriba, donde amarillo son las fichas a voltear.
reversi3=Tu turno puede ser saltado si no hay un movimiento legal. Esto permitir<69> que tu oponente juegue nuevamente hasta que tengas una oportunidad legal.
reversi4=El jugador que gane al final del juego es quien tenga m<>s fichas en el tablero.
tutorialstring=Tutorial
startgame=\u00a1Iniciar juego!
goback=Volver
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Ar\u00e1bigo) arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Ar\u00e1bigo)
chinese=\u4e2d\u6587 (Chino) chinese=\u4e2d\u6587 (Chino)

View File

@@ -9,6 +9,8 @@ computer-difficulty=Difficult\u00e9 de l'ordinateur
computer-think-time=Temps de r\u00e9flexion de l'ordinateur computer-think-time=Temps de r\u00e9flexion de l'ordinateur
computer=Ordinateur computer=Ordinateur
connect=Connexion connect=Connexion
connect4=Connect 4
credits=Cr\u00e9dits credits=Cr\u00e9dits
dark=Sombre dark=Sombre
deny=Refuser deny=Refuser
@@ -41,6 +43,7 @@ merge-commander=Merge Commander
moral-support=Soutien moral moral-support=Soutien moral
music-volume=Volume de la musique music-volume=Volume de la musique
name=Nom name=Nom
never=Non, je ne veux jamais voir de tutoriels.
no=Non no=Non
ok=OK ok=OK
online=En ligne online=En ligne
@@ -62,13 +65,27 @@ style=Style
the-game-ended-in-a-draw=La partie s'est termin\u00e9e par un match nul the-game-ended-in-a-draw=La partie s'est termin\u00e9e par un match nul
theme=Th\u00e8me theme=Th\u00e8me
tic-tac-toe=Morpion tic-tac-toe=Morpion
tictactoe1 =Bienvenue dans le jeu du Tic Tac Toe ! Vous pouvez jouer en cliquant sur l\u2019une des 9 cases du plateau. Exemple ci-dessus :
tictactoe2 =Un joueur gagne en alignant 3 pi\u00e8ces \u2013 horizontalement, verticalement ou en diagonale. Dans l\u2019exemple ci-dessus, le joueur gagne avec une diagonale de trois pi\u00e8ces.
to-a-game-of=\u00e0 une partie de to-a-game-of=\u00e0 une partie de
tutorial=Veux-tu voir un tutoriel pour ce jeu\u00a0?
volume=Volume volume=Volume
windowed=Fen\u00eatrr\u00e9 windowed=Fen\u00eatrr\u00e9
yes=Oui yes=Oui
you-lost-against=Vous avez perdu contre you-lost-against=Vous avez perdu contre
you-were-challenged-by=Vous avez \u00e9t\u00e9 d\u00e9fi\u00e9 par you-were-challenged-by=Vous avez \u00e9t\u00e9 d\u00e9fi\u00e9 par
you-win=Vous avez gagn\u00e9 you-win=Vous avez gagn\u00e9
>=>
<=<
connect4.1=Bienvenue dans le jeu Connect 4 ! Vous pouvez effectuer un mouvement en cliquant sur l'une des colonnes. Le mouvement sera plac<61> dans la ligne la plus basse de cette colonne qui n'est pas encore remplie.
connect4.2=Vous pouvez gagner en alignant 4 pions de votre couleur horizontalement, diagonalement ou verticalement ! Voir l'exemple ci-dessus.
reversi1=Bienvenue dans le jeu Reversi ! Vous pouvez jouer en cliquant sur l'un des points l<>g<EFBFBD>rement transparents.
reversi2=Cliquer sur un point retournera tous les pions entre le point plac<61> et le prochain point trouv<75>. Voir l'exemple ci-dessus, o<> le jaune indique les pions <20> retourner.
reversi3=Votre tour peut <20>tre saut<75> s'il n'y a pas de coup l<>gal. Cela permettra <20> votre adversaire de jouer jusqu'<27> ce que vous ayez un coup l<>gal.
reversi4=Le joueur qui a le plus de pions <20> la fin du jeu gagne.
tutorialstring=Tutoriel
startgame=D\u00e9marrer le jeu!
goback=Retour
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabe) arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabe)
chinese=\u4e2d\u6587 (Chinois) chinese=\u4e2d\u6587 (Chinois)

View File

@@ -9,6 +9,8 @@ computer-difficulty=\u0915\u0902\u092a\u094d\u092f\u0942\u091f\u0930 \u0915\u094
computer-think-time=\u0915\u0902\u092a\u094d\u092f\u0942\u091f\u0930 \u091a\u093f\u0902\u0924\u0928 \u0938\u092e\u092f computer-think-time=\u0915\u0902\u092a\u094d\u092f\u0942\u091f\u0930 \u091a\u093f\u0902\u0924\u0928 \u0938\u092e\u092f
computer=\u0915\u0902\u092a\u094d\u092f\u0942\u091f\u0930 computer=\u0915\u0902\u092a\u094d\u092f\u0942\u091f\u0930
connect=\u091c\u094b\u095c\u0947\u0902 connect=\u091c\u094b\u095c\u0947\u0902
connect4=Connect 4
credits=\u0915\u094d\u0930\u0947\u0921\u093f\u091f\u094d\u0938 credits=\u0915\u094d\u0930\u0947\u0921\u093f\u091f\u094d\u0938
dark=\u0917\u0939\u0930\u093e dark=\u0917\u0939\u0930\u093e
deny=\u0907\u0902\u0915\u093e\u0930 deny=\u0907\u0902\u0915\u093e\u0930
@@ -41,6 +43,7 @@ merge-commander=\u092e\u0930\u094d\u091c \u0915\u092e\u093e\u0902\u0921\u0930
moral-support=\u0928\u0948\u0924\u093f\u0915 \u0938\u0939\u093e\u0930\u093e moral-support=\u0928\u0948\u0924\u093f\u0915 \u0938\u0939\u093e\u0930\u093e
music-volume=\u0938\u0902\u0917\u0940\u0924 \u0935\u0949\u0932\u094d\u092f\u0942\u092e music-volume=\u0938\u0902\u0917\u0940\u0924 \u0935\u0949\u0932\u094d\u092f\u0942\u092e
name=\u0928\u093e\u092e name=\u0928\u093e\u092e
never=\u0928\u0939\u0940\u0902, \u092e\u0948\u0902 \u0915\u092c\u094d\u0939\u0940 \u092d\u0940 \u0915\u094b\u0908 \u091f\u094d\u092f\u0942\u091f\u094b\u0930\u093f\u092f\u0932 \u0928\u0939\u0940\u0902 \u0926\u0947\u0916\u0928\u093e \u091a\u093e\u0939\u0924\u093e/\u091a\u093e\u0939\u0924\u0940\u0964
no=\u0928\u0939\u0940\u0902 no=\u0928\u0939\u0940\u0902
ok=\u0920\u0940\u0915 \u0939\u0948 ok=\u0920\u0940\u0915 \u0939\u0948
online=\u0911\u0928\u0932\u093e\u0907\u0928 online=\u0911\u0928\u0932\u093e\u0907\u0928
@@ -62,13 +65,27 @@ style=\u0936\u0948\u0932\u0940
the-game-ended-in-a-draw=\u0916\u0947\u0932 \u091f\u0940\u0915 \u0939\u094b \u0917\u092f\u093e the-game-ended-in-a-draw=\u0916\u0947\u0932 \u091f\u0940\u0915 \u0939\u094b \u0917\u092f\u093e
theme=\u0925\u0940\u092e theme=\u0925\u0940\u092e
tic-tac-toe=\u091f\u093f\u0915-\u091f\u0948\u0915-\u091f\u094b tic-tac-toe=\u091f\u093f\u0915-\u091f\u0948\u0915-\u091f\u094b
tictactoe1 =\u091f\u093f\u0915-\u091f\u0948\u0915-\u091f\u094b \u0915\u0947 \u0916\u0947\u0932 \u092e\u0947\u0902 \u0906\u092a\u0915\u093e \u0938\u094d\u0935\u093e\u0917\u0924 \u0939\u0948! \u0906\u092a \u092c\u094b\u0930\u094d\u0921 \u092a\u0930 \u0915\u093f\u0938\u0940 \u092d\u0940 9 \u0916\u093e\u0928\u0947 \u092a\u0930 \u0915\u094d\u0932\u093f\u0915 \u0915\u0930\u0915\u0947 \u0905\u092a\u0928\u0940 \u091a\u093e\u0932 \u091a\u0932 \u0938\u0915\u0924\u0947 \u0939\u0948\u0902\u0964 \u090a\u092a\u0930 \u0926\u093f\u092f\u093e \u0917\u092f\u093e \u0909\u0926\u093e\u0939\u0930\u0923 \u0926\u0947\u0916\u0947\u0902:
tictactoe2 =\u0915\u094b\u0908 \u0916\u093f\u0932\u093e\u0921\u093c\u0940 \u0924\u092c \u091c\u0940\u0924\u0924\u093e \u0939\u0948 \u091c\u092c \u0935\u0939 \u092a\u0948 \u092a\u0902\u0915\u094d\u0924\u093f \u092e\u0947\u0902 \u0924\u0940\u0928 \u0928\u093f\u0936\u093e\u0928 \u090f\u0915 \u092a\u0902\u0915\u094d\u0924\u093f \u092e\u0947\u0902 \u0932\u0917\u093e \u0926\u0947\u0924\u093e \u0939\u0948 ? \u0915\u094d\u0937\u0948\u0924\u093f\u091c, \u090a\u0930\u094d\u0927\u094d\u0935\u093e\u0927\u0930 \u092f\u093e \u0924\u093f\u0930\u091b\u0947\u0964 \u090a\u092a\u0930 \u0909\u0926\u093e\u0939\u0930\u0923 \u092e\u0947\u0902, \u0916\u093f\u0932\u093e\u0921\u093c\u0940 \u0924\u093f\u0930\u091b\u0940 \u092a\u0902\u0915\u094d\u0924\u093f \u092e\u0947\u0902 \u0924\u093f\u0930\u091b\u0940 \u092a\u0902\u0915\u094d\u0924\u093f \u0938\u0947 \u091c\u0940\u0924\u0924\u093e \u0939\u0948\u0964
to-a-game-of=\u0916\u0947\u0932 \u0915\u0940 \u090f\u0915 \u0916\u0947\u0932 \u0915\u0940 \u0913\u0930 to-a-game-of=\u0916\u0947\u0932 \u0915\u0940 \u090f\u0915 \u0916\u0947\u0932 \u0915\u0940 \u0913\u0930
tutorial=\u0915\u094d\u092f\u093e \u0906\u092a \u0907\u0938 \u0917\u0947\u092e \u0915\u0947 \u0932\u093f\u090f \u0915\u094b\u0908 \u091f\u094d\u092f\u0942\u091f\u094b\u0930\u093f\u092f\u0932 \u0926\u0947\u0916\u0928\u093e \u091a\u093e\u0939\u0924\u0947 \u0939\u0948\u0902?
volume=\u0935\u0949\u0932\u094d\u092f\u0942\u092e volume=\u0935\u0949\u0932\u094d\u092f\u0942\u092e
windowed=\u0935\u093f\u0902\u0921\u094b \u092e\u094b\u0921 windowed=\u0935\u093f\u0902\u0921\u094b \u092e\u094b\u0921
yes=\u0939\u093e\u0901 yes=\u0939\u093e\u0901
you-lost-against=\u0906\u092a \u0939\u093e\u0930 \u0917\u090f you-lost-against=\u0906\u092a \u0939\u093e\u0930 \u0917\u090f
you-were-challenged-by=\u0906\u092a\u0915\u094b \u091a\u0941\u0928\u094c\u0924\u0940 \u0926\u0940 \u0917\u0908 you-were-challenged-by=\u0906\u092a\u0915\u094b \u091a\u0941\u0928\u094c\u0924\u0940 \u0926\u0940 \u0917\u0908
you-win=\u0906\u092a \u091c\u0940\u0924 \u0917\u090f you-win=\u0906\u092a \u091c\u0940\u0924 \u0917\u090f
>=>
<=<
connect4.1=\u0915\u0928\u094d\u0928\u0947 \u0915\u0940 \u091c\u0948 \u0915\u0947 \u091c\u094c\u0915 \u0915\u0928\u0947\u0915\u094d\u091f 4 \u092e\u0947\u0902! \u0906\u092a \u0915\u0940 \u091a\u0948\u0928 \u092a\u0932\u0938 \u092a\u0928\u094d\u0928 \u0928\u093e\u092e\u094d\u092c\u0921\u093e\u0928\u0947 \u0915\u0940 \u092f\u094b\u0917\u0924\u0940 \u092e\u0947\u0902 \u0915\u0940 \u0928\u093f\u091a\u094d\u091a\u0942\u0928 \u0915\u0940 \u0924\u0939 \u091a\u093f\u0928 \u092a\u0924\u093f \u0928\u0939\u0940\u0902 \u0926\u0947 \u092c\u0924\u0940.
connect4.2=\u092a\u093e\u0902\u091a \u092e\u0946\u092c\u0921 \u0915\u0940 \u092a\u0930\u092a\u0930\u092f\u094d\u0928 \u092d\u093e\u0935\u0940 \u0938\u0947 4 \u092a\u093f\u0938 \u0924\u093e\u0924\u093e \u0915\u0940 \u0930\u094f\u0936\u0942 \u0938\u0940\u0926\u094d\u0927 \u092e\u0947\u0902 \u092a\u0940\u0928\u094d\u0928\u094b\u0902 \u0915\u0940 \u092e\u093e\u0928\u094d\u0926\u0930 \u0939\u0940.
reversi1=\u0915\u0928\u094d\u0928\u0947 \u0915\u0940 \u091c\u0948 \u0910 \u0930\u0947\u0935\u0930\u094d\u0938\u0940 \u092e\u0947\u0902 \u0926\u0948\u091a \u092c\u0922\u093e\u0928 \u0915\u0940 \u092a\u0924\u094d\u0928 \u092a\u0930 \u092a\u094d\u0932\u0947 \u0916\u0942\u0928\u0947 \u0915\u0940 \u092a\u094d\u0930\u092f\u0948\u0915\u0944 \u0915\u0930\u0948\u0902.
reversi2=\u0915\u093f\u0938 \u092a\u0930 \u092a\u093f\u0938 \u092a\u0948\u0918 \u0915\u0940 \u091a\u0928 \u092e\u0947\u0902 \u0938\u092e\u093e\u0930\u094e \u092a\u093f\u0938 \u092a\u0940\u0918 \u092e\u0947\u0902 \u092a\u0930\u093f\u0923\u0924 \u0915\u093e \u092a\u0930\u093f\u0928\u0942\u0924\u0940 \u0915\u0940 \u092a\u094d\u0930\u0924\u093f \u092a\u0941\u0928\u0940 \u092a\u0930\u093f\u0928\u094d\u0924 \u0915\u0940 \u092a\u0940\u0938 \u092a\u0930\u094d\u092f\u0928\u0947 \u091c\u093e\u0902\u0918\u0942.
reversi3=\u092f\u0939 \u092a\u0930\u094d\u092f \u0938\u0947 \u0938\u0947\u091a \u0915\u0940 \u091c\u093e\u0902\u091c \u0928\u0939\u0940\u0902 \u0939\u0948 \u0914\u0938\u0924\u0947 \u0915\u0948 \u092a\u0948\u0928 \u092a\u0930\u094d\u092f \u0939\u0948 \u0914\u092a\u0915\u0940 \u0915\u0940 \u092a\u0932\u0947 \u092d\u0942\u0924 \u0915\u0940 \u0906\u0927\u093e \u092a\u0948\u0928 \u091c\u093e\u0902\u091c \u0915\u0930 \u0938\u0915\u0924\u0947 \u0939\u0948\u0902.
reversi4=\u0916\u0941\u092f \u0915\u093f \u0915\u0940 \u0928\u093f\u092e\u0940 \u092e\u0947\u0902 \u091a\u093e\u0932 \u0938\u092c\u0938\u0947 \u091a\u0942\u0928\u094d\u0928\u0947 \u0939\u0948, \u0935\u0949 \u0915\u0947 \u092e\u093e\u0924\u094d\u0930 \u091c\u0940\u0924\u0947 \u0939\u0948.
tutorialstring=\u0924\u0942\u091f\u0949\u0930\u093f\u092f\u0932
startgame=\u0916\u0947\u0932 \u0936\u0941\u0930\u0942 \u0915\u0930\u0947\u0902!
goback=\u0935\u093e\u092a\u0938 \u091c\u093e\u090f\u0901
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u0905\u0930\u092c\u0940) arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u0905\u0930\u092c\u0940)
chinese=\u4e2d\u6587 (\u091a\u0940\u0928\u0940) chinese=\u4e2d\u6587 (\u091a\u0940\u0928\u0940)

View File

@@ -9,6 +9,7 @@ computer-difficulty=Difficolt\u00e0 del computer
computer-think-time=Tempo di riflessione del computer computer-think-time=Tempo di riflessione del computer
computer=Computer computer=Computer
connect=Connetti connect=Connetti
connect4=Connect 4
credits=Crediti credits=Crediti
dark=Scuro dark=Scuro
deny=Nega deny=Nega
@@ -41,6 +42,7 @@ merge-commander=Merge Commander
moral-support=Supporto morale moral-support=Supporto morale
music-volume=Volume della musica music-volume=Volume della musica
name=Nome name=Nome
never=No, non voglio mai vedere alcun tutorial.
no=No no=No
ok=OK ok=OK
online=Online online=Online
@@ -62,13 +64,27 @@ style=Stile
the-game-ended-in-a-draw=La partita \u00e8 terminata in parit\u00e0 the-game-ended-in-a-draw=La partita \u00e8 terminata in parit\u00e0
theme=Tema theme=Tema
tic-tac-toe=Tris tic-tac-toe=Tris
tictactoe1 =Benvenuto nel gioco del Tris! Puoi fare la tua mossa cliccando su uno dei 9 quadrati della griglia. Esempio sopra:
tictactoe2 =Un giocatore vince mettendo 3 simboli in fila ? orizzontalmente, verticalmente o diagonalmente. Nell?esempio sopra, il giocatore vince con una fila diagonale di tre simboli.
to-a-game-of=a una partita di to-a-game-of=a una partita di
tutorial=Vuoi vedere un tutorial per questo gioco?
volume=Volume volume=Volume
windowed=Finestra windowed=Finestra
yes=S\u00ec yes=S\u00ec
you-lost-against=Hai perso contro you-lost-against=Hai perso contro
you-were-challenged-by=Sei stato sfidato da you-were-challenged-by=Sei stato sfidato da
you-win=Hai vinto you-win=Hai vinto
>=>
<=<
connect4.1=Benvenuto nel gioco Connect 4! Puoi fare una mossa cliccando su una delle colonne. La mossa sar<61> posizionata nella riga pi<70> bassa di quella colonna che non <20> ancora piena.
connect4.2=Puoi vincere ottenendo 4 pedine del tuo colore in orizzontale, diagonale o verticale! Guarda l'esempio sopra.
reversi1=Benvenuto nel gioco Reversi! Puoi fare una mossa cliccando su uno dei punti leggermente trasparenti.
reversi2=Cliccando su un punto, tutti i pezzi tra dove metti il punto e il prossimo punto trovato verranno girati. Guarda l'esempio sopra, dove il giallo indica i pezzi da girare.
reversi3=Il tuo turno pu<70> essere saltato se non ci sono mosse legali. Questo permetter<65> al tuo avversario di giocare fino a quando non avrai un'opportunit<69> legale.
reversi4=Il giocatore che alla fine del gioco ha pi<70> pezzi sulla scacchiera vince.
tutorialstring=Tutorial
startgame=Avvia il gioco!
goback=Indietro
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabo) arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabo)
chinese=\u4e2d\u6587 (Cinese) chinese=\u4e2d\u6587 (Cinese)

View File

@@ -9,6 +9,7 @@ computer-difficulty=\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u306e\u96e3\u6613\u5ea6
computer-think-time=\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u306e\u601d\u8003\u6642\u9593 computer-think-time=\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u306e\u601d\u8003\u6642\u9593
computer=\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf computer=\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf
connect=\u63a5\u7d9a connect=\u63a5\u7d9a
connect4=Connect 4
credits=\u30af\u30ec\u30b8\u30c3\u30c8 credits=\u30af\u30ec\u30b8\u30c3\u30c8
dark=\u30c0\u30fc\u30af dark=\u30c0\u30fc\u30af
deny=\u62d2\u5426 deny=\u62d2\u5426
@@ -41,6 +42,7 @@ merge-commander=\u30de\u30fc\u30b8\u30b3\u30de\u30f3\u30c0\u30fc
moral-support=\u30e1\u30f3\u30bf\u30eb\u30b5\u30dd\u30fc\u30c8 moral-support=\u30e1\u30f3\u30bf\u30eb\u30b5\u30dd\u30fc\u30c8
music-volume=\u97f3\u697d\u97f3\u91cf music-volume=\u97f3\u697d\u97f3\u91cf
name=\u540d\u524d name=\u540d\u524d
never=\u3044\u3044\u3048\u3001\u30c1\u30e5\u30fc\u30c8\u30ea\u30a2\u30eb\u306f\u4e8c\u5ea6\u3068\u898b\u305f\u304f\u3042\u308a\u307e\u305b\u3093\u3002
no=\u3044\u3044\u3048 no=\u3044\u3044\u3048
ok=OK ok=OK
online=\u30aa\u30f3\u30e9\u30a4\u30f3 online=\u30aa\u30f3\u30e9\u30a4\u30f3
@@ -62,13 +64,27 @@ style=\u30b9\u30bf\u30a4\u30eb
the-game-ended-in-a-draw=\u30b2\u30fc\u30e0\u306f\u5f15\u304d\u5206\u3051\u306b\u7d42\u308f\u308a\u307e\u3057\u305f the-game-ended-in-a-draw=\u30b2\u30fc\u30e0\u306f\u5f15\u304d\u5206\u3051\u306b\u7d42\u308f\u308a\u307e\u3057\u305f
theme=\u30c6\u30fc\u30de theme=\u30c6\u30fc\u30de
tic-tac-toe=\u4e09\u76ee\u4e26\u3079 tic-tac-toe=\u4e09\u76ee\u4e26\u3079
tictactoe1 =\u4e09\u76ee\u4e26\u3079\u306e\u30b2\u30fc\u30e0\u3078\u3088\u3046\u3053\u305d\uff01\u30dc\u30fc\u30c9\u4e0a\u306e9\u3064\u306e\u30de\u30b9\u306e\u3044\u305a\u308c\u304b\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u624b\u3092\u6253\u3061\u307e\u3057\u3087\u3046\u3002\u4e0a\u306e\u4f8b\u3092\u53c2\u7167\uff1a
tictactoe2 =\u30d7\u30ec\u30a4\u30e4\u30fc\u306f\u3001\u6a2a\u30fb\u7e26\u30fb\u65b9\u5411\u306e\u3044\u305a\u308c\u304b\u3067\u30de\u30fc\u30af\u30923\u3064\u4e26\u3079\u308b\u3068\u52dd\u3061\u3067\u3059\u3002\u4e0a\u306e\u4f8b\u3067\u306f\u3001\u30d7\u30ec\u30a4\u30e4\u30fc\u304c\u659c\u3081\u306b3\u3064\u4e26\u3079\u3066\u52dd\u3063\u3066\u3044\u307e\u3059\u3002
to-a-game-of=\u30b2\u30fc\u30e0\u306b to-a-game-of=\u30b2\u30fc\u30e0\u306b
tutorial=\u3053\u306e\u30b2\u30fc\u30e0\u306e\u30c1\u30e5\u30fc\u30c8\u30ea\u30a2\u30eb\u3092\u898b\u305f\u3044\u3067\u3059\u304b\uff1f
volume=\u97f3\u91cf volume=\u97f3\u91cf
windowed=\u30a6\u30a3\u30f3\u30c9\u30a6\u8868\u793a windowed=\u30a6\u30a3\u30f3\u30c9\u30a6\u8868\u793a
yes=\u306f\u3044 yes=\u306f\u3044
you-lost-against=\u3042\u306a\u305f\u306f ... \u306b\u6557\u308c\u307e\u3057\u305f you-lost-against=\u3042\u306a\u305f\u306f ... \u306b\u6557\u308c\u307e\u3057\u305f
you-were-challenged-by=\u3042\u306a\u305f\u306f ... \u304b\u3089\u6311\u6226\u3055\u308c\u307e\u3057\u305f you-were-challenged-by=\u3042\u306a\u305f\u306f ... \u304b\u3089\u6311\u6226\u3055\u308c\u307e\u3057\u305f
you-win=\u52dd\u5229\u3067\u3059 you-win=\u52dd\u5229\u3067\u3059
>=>
<=<
connect4.1=\u30b3\u30cd\u30af\u30c84\u306e\u30b2\u30fc\u30e0\u3078\u3088\u3046\u3053\u305d! \u30ab\u30e9\u30e0\u306e\u4e0a\u306e\u30ab\u30e9\u30e0\u3092\u30af\u30ea\u30c3\u30af\u3059\u308b\u3068\u52d5\u304b\u3057\u3092\u884c\u3048\u307e\u3059\u3002\u52d5\u304b\u3057\u306f\u3001\u3060\u307e\u308a\u306f\u307e\u3067\u5869\u3067\u306a\u3044\u884c\u306b\u8a2d\u7f6e\u3055\u308c\u307e\u3059\u3002
connect4.2=\u6a2a\u7dda\u3001\u65b9\u5411\u306e\u307f\u3082\u306a\u3057\u3067\u30014\u3064\u306e\u8ca0\u3051\u3092\u7d50\u5408\u3055\u305b\u308b\u3068\u52dd\u3061\u307e\u3059! \u4e0a\u306e\u4f8b\u3092\u898b\u3066\u304f\u3060\u3055\u3044\u3002
reversi1=\u30ea\u30d0\u30fc\u30b7\u30fb\u30b2\u30fc\u30e0\u3078\u3088\u3046\u3053\u305d! \u30ab\u30e9\u30e0\u306e\u30b9\u30dd\u30c3\u30c8\u30c9\u30c3\u30c8\u3092\u30af\u30ea\u30c3\u30af\u3059\u308b\u3068\u52d5\u304b\u3057\u306e\u30d7\u30ec\u30a4\u304c\u3067\u304d\u307e\u3059\u3002
reversi2=\u30af\u30ea\u30c3\u30af\u3059\u308b\u3068\u3001\u3064\u306a\u304c\u308a\u3092\u542b\u3081\u305f\u8ca0\u3051\u304c\u307e\u3067\u306e\u8ca0\u3051\u304c\u5909\u308f\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002
reversi3=\u6b21\u306e\u52d5\u304b\u3057\u304c\u306a\u3044\u5834\u5408\u3001\u8a8d\u5b9a\u3055\u308c\u305f\u52d5\u304b\u3057\u306e\u6642\u9593\u306f\u62d2\u7d76\u3055\u308c\u308b\u3053\u3068\u304c\u3042\u308a\u307e\u3059\u3002
reversi4=\u672c\u6b21\u306b\u30dc\u30fc\u30c9\u4e0a\u3067\u6700\u591a\u306e\u8ca0\u3051\u3092\u6301\u3064\u30d7\u30ec\u30a4\u30e4\u30fc\u304c\u52dd\u3061\u307e\u3059\u3002
tutorialstring=\u30c1\u30e5\u30fc\u30c8\u30ea\u30a2\u30eb
startgame=\u30b2\u30fc\u30e0\u3092\u958b\u59cb\uff01
goback=\u623b\u308b
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u30a2\u30e9\u30d3\u30a2\u8a9e) arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u30a2\u30e9\u30d3\u30a2\u8a9e)
chinese=\u4e2d\u6587 (\u4e2d\u6587) chinese=\u4e2d\u6587 (\u4e2d\u6587)

View File

@@ -9,6 +9,7 @@ computer-difficulty=\uCEF4\uD4E8\uD130 \uC5B4\uB9AC\uAE30
computer-think-time=\uCEF4\uD4E8\uD130 \uC0DD\uAC01 \uC2DC\uAC04 computer-think-time=\uCEF4\uD4E8\uD130 \uC0DD\uAC01 \uC2DC\uAC04
computer=\uCEF4\uD4E8\uD130 computer=\uCEF4\uD4E8\uD130
connect=\uC5F0\uACB0 connect=\uC5F0\uACB0
connect4=Connect 4
credits=\uD06C\uB808\uB527 credits=\uD06C\uB808\uB527
dark=\uC5B4\uB460 dark=\uC5B4\uB460
deny=\uAC70\uBD80 deny=\uAC70\uBD80
@@ -42,6 +43,7 @@ moral-support=\uC815\uC2E0\uC801 \uC9C0\uC6D0
music-volume=\uC74C\uC545 \uBCFC\uB968 music-volume=\uC74C\uC545 \uBCFC\uB968
name=\uC774\uB984 name=\uC774\uB984
no=\uC544\uB2C8\uC624 no=\uC544\uB2C8\uC624
never=\uc544\ub2c8\uc694, \uc800\ub294 \ud29c\ud1a0\ub9ac\uc5bc\uc744 \ub2e4\uc2dc\ub294 \ubcf4\uace0 \uc2f6\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.
ok=\uD655\uC778 ok=\uD655\uC778
online=\uC628\uB77C\uC778 online=\uC628\uB77C\uC778
opengl=OpenGL opengl=OpenGL
@@ -62,13 +64,27 @@ style=\uC2A4\uD0C0\uC77C
the-game-ended-in-a-draw=\uAC8C\uC784\uC774 \uBB34\uC2B9\uBD80\uB85C \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4 the-game-ended-in-a-draw=\uAC8C\uC784\uC774 \uBB34\uC2B9\uBD80\uB85C \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4
theme=\uC8FC\uC81C theme=\uC8FC\uC81C
tic-tac-toe=\uD2F0\uD06C\uD0D1\uD1A0 tic-tac-toe=\uD2F0\uD06C\uD0D1\uD1A0
tictactoe1 =\ud2f1\ud0dd\ud1a0 \uac8c\uc784\uc5d0 \uc624\uc2e0 \uac83\uc744 \ud658\uc601\ud569\ub2c8\ub2e4! \ubcf4\ub4dc\uc758 9\uac1c \uce78 \uc911 \ud558\ub098\ub97c \ud074\ub9ad\ud558\uc5ec \uc6c0\uc9c1\uc77c \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc704\uc758 \uc608\uc2dc\ub97c \ucc38\uace0\ud558\uc138\uc694:
tictactoe2 =\ud50c\ub808\uc774\uc5b4\ub294 \uac00\ub85c, \uc138\ub85c \ub610\ub294 \ub300\uac01\uc120\uc73c\ub85c \ub9d0 3\uac1c\ub97c \uc77c\ub82c\ub85c \ub193\uc73c\uba74 \uc2b9\ub9ac\ud569\ub2c8\ub2e4. \uc704\uc758 \uc608\uc5d0\uc11c \ud50c\ub808\uc774\uc5b4\ub294 \ub300\uac01\uc120\uc73c\ub85c \uc138 \uac1c\ub97c \uc5f0\uacb0\ud558\uc5ec \uc774\uacbc\uc2b5\ub2c8\ub2e4.
to-a-game-of=... \uAC8C\uC784\uC5D0 to-a-game-of=... \uAC8C\uC784\uC5D0
tutorial=\uc774 \uac8c\uc784\uc758 \ud29c\ud1a0\ub9ac\uc5bc\uc744 \ubcf4\uace0 \uc2f6\ub098\uc694?
volume=\uBCFC\uB968 volume=\uBCFC\uB968
windowed=\uCC3D \uBAA9\uB85D windowed=\uCC3D \uBAA9\uB85D
yes=\uB124 yes=\uB124
you-lost-against=... \uC5D0\uAC8C \uC9C0\uC600\uC2B5\uB2C8\uB2E4 you-lost-against=... \uC5D0\uAC8C \uC9C0\uC600\uC2B5\uB2C8\uB2E4
you-were-challenged-by=... \uB85C\uBD80\uD130 \uCC38\uC5EC \uC694\uCCAD\uC744 \uBC1B\uC558\uC2B5\uB2C8\uB2E4 you-were-challenged-by=... \uB85C\uBD80\uD130 \uCC38\uC5EC \uC694\uCCAD\uC744 \uBC1B\uC558\uC2B5\uB2C8\uB2E4
you-win=\uC774\uACBC\uC2B5\uB2C8\uB2E4 you-win=\uC774\uACBC\uC2B5\uB2C8\uB2E4
>=>
<=<
connect4.1=Connect 4 \uacbd\uc6b0\uc5d0 \uc81c\uc2dc\ud569\ub2c8\ub2e4! \ud648\ub825\uc744 \ub2e4\ub978 \uc0c1\uc704\ub85c \ud074\ub9ad\ud558\uc2dc\uba70 \ub2e4\uc74c \uc815\uc758 \ud648\ub825\uc744 \ub610\ub294 \uc704\ub85c \uc0ac\uc6a9\ud558\uc2dc\uba70 \ud648\ub825\uc744 \uc124\uc815\ud569\ub2c8\ub2e4.
connect4.2=\uc0ac\uc6a9\uc790\uc758 \ud648\uc744 \uc54c\ub824 \ud574\uc8fc\uba70 \ud574\ub2f9 \ud648\uc758 4\uae38\uc744 \ud574\uc8fc\uba70 \ud655\uc9c0, \ub354\ub7ec \ubc29\uacfc \ub610\ub294 \uc0ac\uc6a9\uc790 \ud648\uc758 \uc5f4\ub9b0 \ucd5c\ub300 \ubc29\ud574\uc5d0 \uc5c6\uc74c\uc774\ub2e4!
reversi1=Reversi \uacbd\uc6b0\uc5d0 \uc81c\uc2dc\ud569\ub2c8\ub2e4! \ub2e4\ub978 \ud615\uc2dd\uc758 \ud648\uc744 \ud074\ub9ad\ud558\uc2dc\uba70 \ub2e4\uc74c \uc815\uc758 \ud648\uc744 \uc124\uc815\ud569\ub2c8\ub2e4.
reversi2=\ud074\ub9ad \ud558\uba70, \ub2e4\ub978 \ud648 \uc704\ub85c \ucd5c\uc2e0 \ubc1b\ub294 \ud648\uc5d0 \ub300\ud574 \ub2e4\uc774\ubc84\ub77c\uc758 \ud648\uc744 \ubcc0\uacbd\ud569\ub2c8\ub2e4.
reversi3=\uc0ac\uc6a9\uc790\uc758 \ud648\uc744 \ud074 \uc218 \uc5c6\uc2b5\uc2b5\ub2c8\ub2e4. \uc0ac\uc6a9\uc790 \ub2f5\uc5d0 \ub300\ud574 \uc811\ub2c8\ub2e4.
reversi4=\uacbd\uc6b0 \uc5d0\uc11c \ucd5c\ub300 \ud648\uc744 \uac00\uc838\ub294 \uc0ac\uc6a9\uc790\uc774 \uc52c\uc544\uc624\uba70 \uc0ac\uc6a9\uc790\uc758 \ud648\uc744 \uc54c\ub824\ud569\ub2c8\ub2e4.
tutorialstring=\ud14c\ud2b8\ub9ad
startgame=\uac8c\uc784 \uc2dc\uc791!
goback=\ub4a4\ub85c \uac00\uae30
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u0639\u0631\u0628\u064a\u0629) arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u0639\u0631\u0628\u064a\u0629)
chinese=\u4e2d\u6587 (\u4e2d\u6587) chinese=\u4e2d\u6587 (\u4e2d\u6587)

View File

@@ -41,6 +41,7 @@ merge-commander=Merge Commander
moral-support=Morele steun moral-support=Morele steun
music-volume=Muziekvolume music-volume=Muziekvolume
name=Naam name=Naam
never=Nee, ik wil nooit tutorials zien.
no=Nee no=Nee
ok=Ok<EFBFBD> ok=Ok<EFBFBD>
online=Online online=Online
@@ -62,14 +63,28 @@ style=Stijl
the-game-ended-in-a-draw=Het spel eindigde in een gelijkspel the-game-ended-in-a-draw=Het spel eindigde in een gelijkspel
theme=Thema theme=Thema
tic-tac-toe=Boter Kaas en Eieren tic-tac-toe=Boter Kaas en Eieren
tictactoe1 =Welkom bij het spel Boter, Kaas en Eieren! Je kunt je zet doen door op een van de 9 vakjes op het bord te klikken. Voorbeeld hierboven:
tictactoe2 =Een speler wint door 3 stukken op een rij te krijgen ? horizontaal, verticaal of diagonaal. In het voorbeeld hierboven wint de speler met een diagonale rij van drie stukken.
connect4=Vier op een rij connect4=Vier op een rij
to-a-game-of=voor een spelletje to-a-game-of=voor een spelletje
tutorial=Wil je een tutorial voor dit spel zien?
volume=Volume volume=Volume
windowed=Venstermodus windowed=Venstermodus
yes=Ja yes=Ja
you-lost-against=Je hebt verloren van you-lost-against=Je hebt verloren van
you-were-challenged-by=Je bent uitgedaagd door you-were-challenged-by=Je bent uitgedaagd door
you-win=Je wint you-win=Je wint
>=>
<=<
connect4.1=Welkom bij het spel Connect 4! Je kunt een zet doen door op een van de kolommen te klikken. De zet wordt geplaatst in de laagste nog lege rij van die kolom.
connect4.2=Je kunt winnen door 4 van je stukken horizontaal, diagonaal of verticaal op een rij te krijgen! Zie het voorbeeld hierboven.
reversi1=Welkom bij het spel Reversi! Je kunt een zet doen door op een van de licht transparante stippen te klikken.
reversi2=Door op een stip te klikken draai je alle stukken om tussen de plaats waar je de stip zet en de volgende stip die wordt gevonden. Zie het voorbeeld hierboven, waar geel de stukken zijn die omgedraaid worden.
reversi3=Je beurt kan worden overgeslagen als er geen legale zet is. Hierdoor kan je tegenstander doorgaan tot jij een legale zet kunt doen.
reversi4=De speler die aan het einde van het spel de meeste stukken op het bord heeft, wint.
tutorialstring=Tutorial
startgame=Spel starten!
goback=Ga terug
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabisch) arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabisch)
chinese=\u4e2d\u6587 (Chinees) chinese=\u4e2d\u6587 (Chinees)

View File

@@ -5,6 +5,7 @@ are-you-sure=\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b?
back=\u041d\u0430\u0437\u0430\u0434 back=\u041d\u0430\u0437\u0430\u0434
cancel=\u041e\u0442\u043c\u0435\u043d\u0430 cancel=\u041e\u0442\u043c\u0435\u043d\u0430
challenge=\u0412\u044b\u0437\u043e\u0432 challenge=\u0412\u044b\u0437\u043e\u0432
connect4=Connect 4
computer-difficulty=\u0421\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0430 computer-difficulty=\u0421\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0430
computer-think-time=\u0412\u0440\u0435\u043c\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0430 computer-think-time=\u0412\u0440\u0435\u043c\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0430
computer=\u041a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440 computer=\u041a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440
@@ -41,6 +42,7 @@ merge-commander=Merge Commander
moral-support=\u041c\u043e\u0440\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 moral-support=\u041c\u043e\u0440\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430
music-volume=\u0413\u0440\u043e\u043c\u043a\u043e\u0441\u0442\u044c \u043c\u0443\u0437\u044b\u043a\u0438 music-volume=\u0413\u0440\u043e\u043c\u043a\u043e\u0441\u0442\u044c \u043c\u0443\u0437\u044b\u043a\u0438
name=\u0418\u043c\u044f name=\u0418\u043c\u044f
never=\u041d\u0435\u0442, \u044f \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0445\u043e\u0447\u0443 \u0432\u0438\u0434\u0435\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u043b\u0438\u0431\u043e \u0443\u0447\u0435\u0431\u043d\u0438\u043a\u0438.
no=\u041d\u0435\u0442 no=\u041d\u0435\u0442
ok=OK ok=OK
online=\u0412 \u0441\u0435\u0442\u0438 online=\u0412 \u0441\u0435\u0442\u0438
@@ -62,13 +64,27 @@ style=\u0421\u0442\u0438\u043b\u044c
the-game-ended-in-a-draw=\u0418\u0433\u0440\u0430 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u0430\u0441\u044c \u043d\u0438\u0447\u044c\u0435\u0439 the-game-ended-in-a-draw=\u0418\u0433\u0440\u0430 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u0430\u0441\u044c \u043d\u0438\u0447\u044c\u0435\u0439
theme=\u0422\u0435\u043c\u0430 theme=\u0422\u0435\u043c\u0430
tic-tac-toe=\u041a\u0440\u0435\u0441\u0442\u0438\u043a\u043e tic-tac-toe=\u041a\u0440\u0435\u0441\u0442\u0438\u043a\u043e
tictactoe1 =\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c \u0432 \u0438\u0433\u0440\u0443 \u041a\u0440\u0435\u0441\u0442\u0438\u043a\u0438-\u043d\u043e\u043b\u0438\u043a\u0438! \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0445\u043e\u0434, \u043d\u0430\u0436\u0430\u0432 \u043d\u0430 \u043b\u044e\u0431\u043e\u0439 \u0438\u0437 9 \u043a\u0432\u0430\u0434\u0440\u0430\u0442\u043e\u0432 \u043d\u0430 \u043f\u043e\u043b\u0435. \u041f\u0440\u0438\u043c\u0435\u0440 \u0432\u044b\u0448\u0435:
tictactoe2 =\u0418\u0433\u0440\u043e\u043a \u0432\u044b\u0438\u0433\u0440\u044b\u0432\u0430\u0435\u0442, \u0435\u0441\u043b\u0438 \u0441\u0442\u0430\u0432\u0438\u0442 3 \u0441\u0438\u043c\u0432\u043e\u043b\u0430 \u043f\u043e\u0434\u0440\u044f\u0434 ? \u043f\u043e \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u0438, \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0438 \u0438\u043b\u0438 \u0434\u0438\u0430\u0433\u043e\u043d\u0430\u043b\u0438. \u0412 \u043f\u0440\u0438\u0432\u0435\u0434\u0451\u043d\u043d\u043e\u043c \u0432\u044b\u0448\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0438\u0433\u0440\u043e\u043a \u0432\u044b\u0438\u0433\u0440\u044b\u0432\u0430\u0435\u0442 \u043f\u043e \u0434\u0438\u0430\u0433\u043e\u043d\u0430\u043b\u0438
to-a-game-of=\u043a \u0438\u0433\u0440\u0435 \u0432 to-a-game-of=\u043a \u0438\u0433\u0440\u0435 \u0432
tutorial=\u0425\u043e\u0447\u0435\u0448\u044c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0443\u0447\u0435\u0431\u043d\u0438\u043a \u043f\u043e \u044d\u0442\u043e\u0439 \u0438\u0433\u0440\u0435?
volume=\u0413\u0440\u043e\u043c\u043a\u043e\u0441\u0442\u044c volume=\u0413\u0440\u043e\u043c\u043a\u043e\u0441\u0442\u044c
windowed=\u041e\u043a\u043d\u043e windowed=\u041e\u043a\u043d\u043e
yes=\u0414\u0430 yes=\u0414\u0430
you-lost-against=\u0412\u044b \u043f\u0440\u043e\u0438\u0433\u0440\u0430\u043b\u0438 \u043a\u043e\u043c\u0443 you-lost-against=\u0412\u044b \u043f\u0440\u043e\u0438\u0433\u0440\u0430\u043b\u0438 \u043a\u043e\u043c\u0443
you-were-challenged-by=\u0412\u0430\u0441 \u0432\u044b\u0437\u0432\u0430\u043b \u043d\u0430 \u0441\u043e\u0440\u0435\u0432\u043d\u0438\u043a you-were-challenged-by=\u0412\u0430\u0441 \u0432\u044b\u0437\u0432\u0430\u043b \u043d\u0430 \u0441\u043e\u0440\u0435\u0432\u043d\u0438\u043a
you-win=\u0412\u044b \u0432\u044b\u0438\u0433\u0440\u044b\u0432\u0430\u0435\u0442\u0435 you-win=\u0412\u044b \u0432\u044b\u0438\u0433\u0440\u044b\u0432\u0430\u0435\u0442\u0435
>=>
<=<
connect4.1=\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c \u0432 \u0438\u0433\u0440\u0443 Connect 4! \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0445\u043e\u0434, \u043a\u043b\u0438\u043a\u043d\u0443\u044f \u043f\u043e \u043e\u0434\u043d\u043e\u0439 \u0438\u0437 \u0441\u0442\u043e\u043b\u0431\u0446\u043e\u0432. \u0425\u043e\u0434 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d \u0432 \u043d\u0438\u0436\u0430\u0439 \u043d\u0435\u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435 \u0432 \u044d\u0442\u043e\u0439 \u043a\u043e\u043b\u043e\u043d\u043a\u0435.
connect4.2=\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u044b\u0438\u0433\u0440\u0430\u0442\u044c, \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0432 4 \u0444\u0438\u0448\u043a\u0438 \u0432\u0430\u0448\u0435\u0433\u043e \u0446\u0432\u0435\u0442\u0430 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e, \u0434\u0438\u0430\u0433\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u0438\u043b\u0438 \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u043e!
reversi1=\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c \u0432 \u0438\u0433\u0440\u0443 Reversi! \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0445\u043e\u0434, \u043a\u043b\u0438\u043a\u043d\u0443\u044f \u043f043 \u043f043 \u043d043 \u043e043 \u043d043 \u043e043 \u043a043 \u0430043 \u043a043 \u0430043.
reversi2=\u041d043 \u043d043 \u0430043 \u043a043 \u0430043 \u043a043 \u043e043 \u043c043 \u0435043 \u0436043 \u0435043 \u0434043 \u0435043 \u043d043 \u0430043.
reversi3=\u0412043 \u0430043 \u0436043 \u0434043 \u0430043 \u043d043 \u0438043 \u043d043 \u0435043 \u0435043 \u0432043 \u0430043.
reversi4=\u0418043 \u0433043 \u0440043 \u043e043 \u043a043 \u043e043 \u0442043 \u043e043 \u0442043 \u043e043 \u0435043 \u0435043 \u0430043 \u0435043 \u043d043 \u0438043 \u0435043 \u0435043 \u043c043 \u0430043.
tutorialstring=\u0423\u0447\u0435\u0431\u043d\u0438\u043a
startgame=\u041d\u0430\u0447\u0430\u0442\u044c \u0438\u0433\u0440\u0443!
goback=\u041d\u0430\u0437\u0430\u0434
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u0410\u0440\u0430\u0431\u0441\u043a\u0438\u0439) arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u0410\u0440\u0430\u0431\u0441\u043a\u0438\u0439)
chinese=\u4e2d\u6587 (\u041a\u0438\u0442\u0430\u0439\u0441\u043a\u0438\u0439) chinese=\u4e2d\u6587 (\u041a\u0438\u0442\u0430\u0439\u0441\u043a\u0438\u0439)

View File

@@ -9,6 +9,7 @@ computer-difficulty=\u8ba1\u7b97\u673a\u96be\u5ea6
computer-think-time=\u8ba1\u7b97\u673a\u601d\u8003\u65f6\u95f4 computer-think-time=\u8ba1\u7b97\u673a\u601d\u8003\u65f6\u95f4
computer=\u8ba1\u7b97\u673a computer=\u8ba1\u7b97\u673a
connect=\u8fde\u63a5 connect=\u8fde\u63a5
connect4=Connect 4
credits=\u81f4\u8c22 credits=\u81f4\u8c22
dark=\u6697\u8272 dark=\u6697\u8272
deny=\u62d2\u7edd deny=\u62d2\u7edd
@@ -41,6 +42,7 @@ merge-commander=\u5408\u5e76\u6307\u6325
moral-support=\u7cbe\u795e\u652f\u6301 moral-support=\u7cbe\u795e\u652f\u6301
music-volume=\u97f3\u4e50\u97f3\u91cf music-volume=\u97f3\u4e50\u97f3\u91cf
name=\u540d\u5b57 name=\u540d\u5b57
never=\u4e0d\uff0c\u6211\u5b8c\u5168\u4e0d\u60f3\u518d\u770b\u5230\u4efb\u4f55\u6559\u7a0b\u3002
no=\u4e0d no=\u4e0d
ok=\u786e\u5b9a ok=\u786e\u5b9a
online=\u5728\u7ebf online=\u5728\u7ebf
@@ -62,13 +64,27 @@ style=\u98ce\u683c
the-game-ended-in-a-draw=\u6e38\u620f\u4ee5\u548c\u5c40\u7ed3\u675f the-game-ended-in-a-draw=\u6e38\u620f\u4ee5\u548c\u5c40\u7ed3\u675f
theme=\u4e3b\u9898 theme=\u4e3b\u9898
tic-tac-toe=\u4e09\u5b9a\u7ebf tic-tac-toe=\u4e09\u5b9a\u7ebf
tictactoe1 =\u6b22\u8fce\u6765\u5230\u4e95\u5b57\u68cb\u6e38\u620f\uff01\u4f60\u53ef\u4ee5\u901a\u8fc7\u70b9\u51fb\u68cb\u76d8\u4e0a\u4efb\u610f\u4e00\u4e2a\u4e5d\u4e2a\u65b9\u683c\u6765\u843d\u5b50\u3002\u4e0a\u65b9\u793a\u4f8b\uff1a
tictactoe2 =\u73a9\u5bb6\u5728\u6a2a\u3001\u7ad6\u6216\u659c\u7ebf\u4e0a\u8fde\u7eed\u653e\u7f6e\u4e09\u4e2a\u68cb\u5b50\u5373\u53ef\u83b7\u80dc\u3002\u5728\u4e0a\u65b9\u7684\u793a\u4f8b\u4e2d\uff0c\u73a9\u5bb6\u901a\u8fc7\u659c\u7ebf\u4e0a\u7684\u4e09\u4e2a\u68cb\u5b50\u83b7\u80dc\u4e86\u6bd4\u8d5b\u3002
to-a-game-of=\u6311\u6218\u4e00\u573a to-a-game-of=\u6311\u6218\u4e00\u573a
tutorial=\u4f60\u60f3\u770b\u8fd9\u4e2a\u6e38\u620f\u7684\u6559\u7a0b\u5417\uff1f
volume=\u97f3\u91cf volume=\u97f3\u91cf
windowed=\u7a97\u53e3\u6a21\u5f0f windowed=\u7a97\u53e3\u6a21\u5f0f
yes=\u662f yes=\u662f
you-lost-against=\u60a8\u8f93\u7ed9\u4e86 you-lost-against=\u60a8\u8f93\u7ed9\u4e86
you-were-challenged-by=\u60a8\u88ab\u6311\u6218\u81ea you-were-challenged-by=\u60a8\u88ab\u6311\u6218\u81ea
you-win=\u60a8\u83b7\u80dc\u4e86 you-win=\u60a8\u83b7\u80dc\u4e86
>=>
<=<
connect4.1=\u6b22\u8fce\u6765\u5230 Connect 4 \u6e38\u620f! \u4f60\u53ef\u4ee5\u70b9\u51fb\u4e00\u5217\u6761\u76ee\u64cd\u4f5c. \u64cd\u4f5c\u5c06\u88c5\u7f6e\u5728\u672a\u88c5\u5165\u7684\u6700\u4f4e\u884c.
connect4.2=\u5982\u679c\u5f97\u52304\u4e2a\u5bf9\u5e94\u7684\u4ee3\u7406\u7ec4\u6210\u6c34\u5e73\u3001\u5347\u5e26\u6216\u5782\u76f4\u5373\u53ef\u80dc. \u770b\u4e0a\u65b9\u793a\u4f8b.
reversi1=\u6b22\u8fce\u6765\u5230 Reversi \u6e38\u620f! \u4f60\u53ef\u4ee5\u70b9\u51fb\u4e00\u4e2a\u9ed8\u8272\u5149\u900f\u7a7a\u70b9\u64cd\u4f5c.
reversi2=\u70b9\u51fb\u4e00\u4e2a\u70b9\u65f6\u5c06\u5c06\u6240\u6709\u4e2d\u95f4\u7684\u4ee3\u7406\u7ffb\u8f6c\u3002 \u770b\u4e0a\u65b9\u793a\u4f8b\uff0c\u9ec4\u8272\u662f\u5bf9\u4ee3\u7406\u9700\u64ad\u7684\u4ee3\u7406.
reversi3=\u5982\u679c\u6ca1\u6709\u5408\u6cd5\u64cd\u4f5c\u4f60\u7684\u8fdb\u6b65\u53ef\u80fd\u88ab\u5ffd\u7565. \u8fd9\u4f1a\u8ba9\u5bf9\u624b\u518d\u6b21\u64cd\u4f5c\u5230\u4f60\u6709\u5408\u6cd5\u64cd\u4f5c\u65f6.
reversi4=\u672c\u6e38\u620f\u7ed3\u675f\u65f6\u8d62\u5f97\u6ee1\u8fc7\u76d8\u9762\u7684\u4ee3\u7406\u6570\u6700\u591a\u7684\u4eba\u5c31\u80dc.
tutorialstring=\u6559\u7a0b
startgame=\u5f00\u59cb\u6e38\u620f\uff01
goback=\u8fd4\u56de
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u963f\u62c9\u4f2f\u8bed) arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u963f\u62c9\u4f2f\u8bed)
chinese=\u4e2d\u6587 chinese=\u4e2d\u6587

View File

@@ -48,4 +48,9 @@
.text { .text {
-fx-font-size: 22px; -fx-font-size: 22px;
-fx-font-weight: normal; -fx-font-weight: normal;
}
.image {
-fx-max-width: 200px;
-fx-max-height: 200px;
} }

View File

@@ -48,4 +48,9 @@
.text { .text {
-fx-font-size: 16px; -fx-font-size: 16px;
-fx-font-weight: normal; -fx-font-weight: normal;
}
.image {
-fx-max-width: 200px;
-fx-max-height: 200px;
} }

View File

@@ -48,4 +48,9 @@
.text { .text {
-fx-font-size: 12px; -fx-font-size: 12px;
-fx-font-weight: normal; -fx-font-weight: normal;
} }
.image {
-fx-max-width: 200px;
-fx-max-height: 200px;
}

View File

@@ -25,17 +25,16 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
public AudioEventListener<?, ?> initListeners(String buttonSoundToPlay) { public AudioEventListener<?, ?> initListeners(String buttonSoundToPlay) {
new EventFlow() new EventFlow()
.listen(this::handleStopMusicManager) .listen(AudioEvents.StopAudioManager.class, this::handleStopMusicManager, false)
.listen(this::handlePlaySound) .listen(AudioEvents.PlayEffect.class, this::handlePlaySound, false)
.listen(this::handleSkipSong) .listen(AudioEvents.SkipMusic.class, this::handleSkipSong, false)
.listen(this::handlePauseSong) .listen(AudioEvents.PauseMusic.class, this::handlePauseSong, false)
.listen(this::handlePreviousSong) .listen(AudioEvents.PreviousMusic.class, this::handlePreviousSong, false)
.listen(this::handleStopSound) .listen(AudioEvents.StopEffect.class, this::handleStopSound, false)
.listen(this::handleMusicStart) .listen(AudioEvents.StartBackgroundMusic.class, this::handleMusicStart, false)
.listen(this::handleVolumeChange) .listen(AudioEvents.ChangeVolume.class, this::handleVolumeChange, false)
.listen(this::handleGetVolume) .listen(AudioEvents.GetVolume.class, this::handleGetVolume,false)
.listen(AudioEvents.ClickButton.class, _ -> .listen(AudioEvents.ClickButton.class, _ -> soundEffectManager.play(buttonSoundToPlay, false), false);
soundEffectManager.play(buttonSoundToPlay, false));
return this; return this;
} }

View File

@@ -38,7 +38,7 @@ public class EventFlow {
private EventType event = null; private EventType event = null;
/** The listener returned by GlobalEventBus subscription. Used for unsubscription. */ /** The listener returned by GlobalEventBus subscription. Used for unsubscription. */
private final List<ListenerHandler> listeners = new ArrayList<>(); private final List<ListenerHandler<?>> listeners = new ArrayList<>();
/** Holds the results returned from the subscribed event, if any. */ /** Holds the results returned from the subscribed event, if any. */
private Map<String, ?> result = null; private Map<String, ?> result = null;
@@ -46,16 +46,15 @@ public class EventFlow {
/** Empty constructor (event must be added via {@link #addPostEvent(Class, Object...)}). */ /** Empty constructor (event must be added via {@link #addPostEvent(Class, Object...)}). */
public EventFlow() {} public EventFlow() {}
public EventFlow addPostEvent(EventType event) { /**
this.event = event; *
return this; * Add an event that will be triggered when {@link #postEvent()} or {@link #asyncPostEvent()} is called.
} *
* @param eventClass The event that will be posted.
public EventFlow addPostEvent(Supplier<? extends EventType> eventSupplier) { * @param args The event arguments, see the added event record for more information.
this.event = eventSupplier.get(); * @return {@link #EventFlow}
return this; *
} */
public <T extends EventType> EventFlow addPostEvent(Class<T> eventClass, Object... args) { public <T extends EventType> EventFlow addPostEvent(Class<T> eventClass, Object... args) {
try { try {
boolean isUuidEvent = UniqueEvent.class.isAssignableFrom(eventClass); boolean isUuidEvent = UniqueEvent.class.isAssignableFrom(eventClass);
@@ -98,155 +97,421 @@ public class EventFlow {
} }
} }
/** Subscribe by ID: only fires if UUID matches this publisher's eventId. */ /**
public <TT extends ResponseToUniqueEvent> EventFlow onResponse( *
Class<TT> eventClass, Consumer<TT> action, boolean unsubscribeAfterSuccess) { * Add an event that will be triggered when {@link #postEvent()} or {@link #asyncPostEvent()} is called.
ListenerHandler[] listenerHolder = new ListenerHandler[1]; *
listenerHolder[0] = * @param event The event to be posted.
new ListenerHandler( * @return {@link #EventFlow}
GlobalEventBus.subscribe( *
eventClass, */
event -> { public EventFlow addPostEvent(EventType event) {
if (event.getIdentifier() != this.eventSnowflake) return; this.event = event;
action.accept(event);
if (unsubscribeAfterSuccess && listenerHolder[0] != null) {
GlobalEventBus.unsubscribe(listenerHolder[0]);
this.listeners.remove(listenerHolder[0]);
}
this.result = event.result();
}));
this.listeners.add(listenerHolder[0]);
return this; return this;
} }
/** Subscribe by ID: only fires if UUID matches this publisher's eventId. */ /**
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(Class<TT> eventClass, Consumer<TT> action) { *
return this.onResponse(eventClass, action, true); * Add an event that will be triggered when {@link #postEvent()} or {@link #asyncPostEvent()} is called.
*
* @param eventSupplier The event that will be posted through a Supplier.
* @return {@link #EventFlow}
*
*/
public EventFlow addPostEvent(Supplier<? extends EventType> eventSupplier) {
this.event = eventSupplier.get();
return this;
} }
/** Subscribe by ID without explicit class. */ /**
*
* Start listening for an event and trigger when ID correlates.
*
* @param event The {@link ResponseToUniqueEvent} to trigger the lambda.
* @param action The lambda to run when triggered.
* @param unsubscribeAfterSuccess Enable/disable auto unsubscribing to event after being triggered.
* @param name A name given to the event, can later be used to unsubscribe.
* @return {@link #EventFlow}
*
*/
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(
Class<TT> event, Consumer<TT> action, boolean unsubscribeAfterSuccess, String name
) {
final long id = SnowflakeGenerator.nextId();
Consumer<TT> newAction = eventClass -> {
if (eventClass.getIdentifier() != this.eventSnowflake) return;
action.accept(eventClass);
if (unsubscribeAfterSuccess) unsubscribe(id);
this.result = eventClass.result();
};
// TODO Remove casts
var listener = new ListenerHandler<>(
id,
name,
(Class<ResponseToUniqueEvent>) event,
(Consumer<ResponseToUniqueEvent>) newAction
);
GlobalEventBus.subscribe(listener);
this.listeners.add(listener);
return this;
}
/**
*
* Start listening for an event and trigger when ID correlates, auto unsubscribes after being triggered and adds an empty name.
*
* @param event The {@link ResponseToUniqueEvent} to trigger the lambda.
* @param action The lambda to run when triggered.
* @return {@link #EventFlow}
*
*/
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(Class<TT> event, Consumer<TT> action) {
return this.onResponse(event, action, true, "");
}
/**
*
* Start listening for an event and trigger when ID correlates, auto adds an empty name.
*
* @param event The {@link ResponseToUniqueEvent} to trigger the lambda.
* @param action The lambda to run when triggered.
* @param unsubscribeAfterSuccess Enable/disable auto unsubscribing to event after being triggered.
* @return {@link #EventFlow}
*
*/
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(Class<TT> event, Consumer<TT> action, boolean unsubscribeAfterSuccess) {
return this.onResponse(event, action, unsubscribeAfterSuccess, "");
}
/**
*
* Start listening for an event and trigger when ID correlates, auto unsubscribes after being triggered.
*
* @param event The {@link ResponseToUniqueEvent} to trigger the lambda.
* @param action The lambda to run when triggered.
* @param name A name given to the event, can later be used to unsubscribe.
* @return {@link #EventFlow}
*
*/
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(Class<TT> event, Consumer<TT> action, String name) {
return this.onResponse(event, action, true, name);
}
/**
*
* Subscribe by ID without explicit class.
*
* @param action The lambda to run when triggered.
* @return {@link #EventFlow}
*
* @deprecated use {@link #onResponse(Class, Consumer, boolean, String)} instead.
*/
@Deprecated
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <TT extends ResponseToUniqueEvent> EventFlow onResponse( public <TT extends ResponseToUniqueEvent> EventFlow onResponse(
Consumer<TT> action, boolean unsubscribeAfterSuccess) { Consumer<TT> action, boolean unsubscribeAfterSuccess, String name) {
ListenerHandler[] listenerHolder = new ListenerHandler[1];
listenerHolder[0] = final long id = SnowflakeGenerator.nextId();
new ListenerHandler(
GlobalEventBus.subscribe( Consumer<TT> newAction = event -> {
event -> { if (!(event instanceof UniqueEvent uuidEvent)) return;
if (!(event instanceof UniqueEvent uuidEvent)) return; if (uuidEvent.getIdentifier() == this.eventSnowflake) {
if (uuidEvent.getIdentifier() == this.eventSnowflake) { try {
try { TT typedEvent = (TT) uuidEvent;
TT typedEvent = (TT) uuidEvent; action.accept(typedEvent);
action.accept(typedEvent);
if (unsubscribeAfterSuccess if (unsubscribeAfterSuccess) unsubscribe(id);
&& listenerHolder[0] != null) {
GlobalEventBus.unsubscribe(listenerHolder[0]); this.result = typedEvent.result();
this.listeners.remove(listenerHolder[0]); } catch (ClassCastException _) {
} throw new ClassCastException(
this.result = typedEvent.result(); "Cannot cast "
} catch (ClassCastException _) { + event.getClass().getName()
throw new ClassCastException( + " to UniqueEvent");
"Cannot cast " }
+ event.getClass().getName() }
+ " to UniqueEvent"); };
}
} var listener = new ListenerHandler<>(
})); id,
this.listeners.add(listenerHolder[0]); name,
(Class<TT>) action.getClass().getDeclaredMethods()[0].getParameterTypes()[0],
newAction
);
GlobalEventBus.subscribe(listener);
this.listeners.add(listener);
return this; return this;
} }
/**
*
* Subscribe by ID without explicit class.
*
* @param action The lambda to run when triggered.
* @return {@link #EventFlow}
*
* @deprecated use {@link #onResponse(Class, Consumer)} instead.
*/
@Deprecated
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(Consumer<TT> action) { public <TT extends ResponseToUniqueEvent> EventFlow onResponse(Consumer<TT> action) {
return this.onResponse(action, true); return this.onResponse(action, true, "");
} }
/**
*
* Start listening for an event, and run a lambda when triggered.
*
* @param event The {@link EventType} to trigger the lambda.
* @param action The lambda to run when triggered.
* @param unsubscribeAfterSuccess Enable/disable auto unsubscribing to event after being triggered.
* @param name A name given to the event, can later be used to unsubscribe.
* @return {@link #EventFlow}
*
*/
public <TT extends EventType> EventFlow listen( public <TT extends EventType> EventFlow listen(
Class<TT> eventClass, Consumer<TT> action, boolean unsubscribeAfterSuccess) { Class<TT> event, Consumer<TT> action, boolean unsubscribeAfterSuccess, String name) {
ListenerHandler[] listenerHolder = new ListenerHandler[1];
listenerHolder[0] =
new ListenerHandler(
GlobalEventBus.subscribe(
eventClass,
event -> {
action.accept(event);
if (unsubscribeAfterSuccess && listenerHolder[0] != null) { long id = SnowflakeGenerator.nextId();
GlobalEventBus.unsubscribe(listenerHolder[0]);
this.listeners.remove(listenerHolder[0]); Consumer<TT> newAction = eventc -> {
} action.accept(eventc);
}));
this.listeners.add(listenerHolder[0]); if (unsubscribeAfterSuccess) unsubscribe(id);
};
var listener = new ListenerHandler<>(
id,
name,
event,
newAction
);
GlobalEventBus.subscribe(listener);
this.listeners.add(listener);
return this; return this;
} }
public <TT extends EventType> EventFlow listen(Class<TT> eventClass, Consumer<TT> action) { /**
return this.listen(eventClass, action, true); *
* Start listening for an event, and run a lambda when triggered, auto unsubscribes.
*
* @param event The {@link EventType} to trigger the lambda.
* @param action The lambda to run when triggered.
* @param name A name given to the event, can later be used to unsubscribe.
* @return {@link #EventFlow}
*
*/
public <TT extends EventType> EventFlow listen(Class<TT> event, Consumer<TT> action, String name) {
return this.listen(event, action, true, name);
} }
/**
*
* Start listening for an event, and run a lambda when triggered, auto unsubscribe and gives it an empty name.
*
* @param event The {@link EventType} to trigger the lambda.
* @param action The lambda to run when triggered.
* @return {@link #EventFlow}
*
*/
public <TT extends EventType> EventFlow listen(Class<TT> event, Consumer<TT> action) {
return this.listen(event, action, true, "");
}
/**
*
* Start listening for an event, and run a lambda when triggered, adds an empty name.
*
* @param event The {@link EventType} to trigger the lambda.
* @param action The lambda to run when triggered.
* @param unsubscribeAfterSuccess Enable/disable auto unsubscribing to event after being triggered.
* @return {@link #EventFlow}
*
*/
public <TT extends EventType> EventFlow listen(Class<TT> event, Consumer<TT> action, boolean unsubscribeAfterSuccess) {
return this.listen(event, action, unsubscribeAfterSuccess, "");
}
/**
*
* Start listening to an event.
*
* @param action The lambda to run when triggered.
* @return {@link EventFlow}
*
* @deprecated use {@link #listen(Class, Consumer, boolean, String)} instead.
*/
@Deprecated
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <TT extends EventType> EventFlow listen( public <TT extends EventType> EventFlow listen(
Consumer<TT> action, boolean unsubscribeAfterSuccess) { Consumer<TT> action, boolean unsubscribeAfterSuccess, String name) {
ListenerHandler[] listenerHolder = new ListenerHandler[1]; long id = SnowflakeGenerator.nextId();
listenerHolder[0] =
new ListenerHandler( Class<TT> eventClass = (Class<TT>) action.getClass().getDeclaredMethods()[0].getParameterTypes()[0];
GlobalEventBus.subscribe(
event -> { Consumer<TT> newAction = event -> {
if (!(event instanceof EventType nonUuidEvent)) return; if (!(event instanceof EventType nonUuidEvent)) return;
try { try {
TT typedEvent = (TT) nonUuidEvent; TT typedEvent = (TT) nonUuidEvent;
action.accept(typedEvent); action.accept(typedEvent);
if (unsubscribeAfterSuccess && listenerHolder[0] != null) { if (unsubscribeAfterSuccess) unsubscribe(id);
GlobalEventBus.unsubscribe(listenerHolder[0]); } catch (ClassCastException _) {
this.listeners.remove(listenerHolder[0]); throw new ClassCastException(
} "Cannot cast "
} catch (ClassCastException _) { + event.getClass().getName()
throw new ClassCastException( + " to UniqueEvent");
"Cannot cast " }
+ event.getClass().getName() };
+ " to UniqueEvent");
} var listener = new ListenerHandler<>(
})); id,
this.listeners.add(listenerHolder[0]); name,
eventClass,
newAction
);
GlobalEventBus.subscribe(listener);
this.listeners.add(listener);
return this; return this;
} }
/**
*
* Start listening to an event.
*
* @param action The lambda to run when triggered.
* @return {@link EventFlow}
*
* @deprecated use {@link #listen(Class, Consumer)} instead.
*/
@Deprecated
public <TT extends EventType> EventFlow listen(Consumer<TT> action) { public <TT extends EventType> EventFlow listen(Consumer<TT> action) {
return this.listen(action, true); return this.listen(action, true, "");
} }
/** Post synchronously */ /**
* Posts the event added through {@link #addPostEvent}.
*/
public EventFlow postEvent() { public EventFlow postEvent() {
GlobalEventBus.post(this.event); GlobalEventBus.post(this.event);
return this; return this;
} }
/** Post asynchronously */ /**
* Posts the event added through {@link #addPostEvent} asynchronously.
*/
public EventFlow asyncPostEvent() { public EventFlow asyncPostEvent() {
GlobalEventBus.postAsync(this.event); GlobalEventBus.postAsync(this.event);
return this; return this;
} }
/**
*
* Unsubscribe from an event.
*
* @param listenerObject The listener object to remove and unsubscribe.
*/
public void unsubscribe(Object listenerObject) {
this.listeners.removeIf(handler -> {
if (handler.getListener() == listenerObject) {
GlobalEventBus.unsubscribe(handler);
return true;
}
return false;
});
}
/**
*
* Unsubscribe from an event.
*
* @param listenerId The id given to the {@link ListenerHandler}.
*/
public void unsubscribe(long listenerId) {
this.listeners.removeIf(handler -> {
if (handler.getId() == listenerId) {
GlobalEventBus.unsubscribe(handler);
return true;
}
return false;
});
}
/**
* Unsubscribe from an event.
*
* @param name The name given to the listener.
*/
public void unsubscribe(String name) {
this.listeners.removeIf(handler -> {
if (handler.getName().equals(name)) {
GlobalEventBus.unsubscribe(handler);
return true;
}
return false;
});
}
/**
* Unsubscribe all events.
*/
public void unsubscribeAll() {
listeners.removeIf(handler -> {
GlobalEventBus.unsubscribe(handler);
return true;
});
}
/**
* Clean and remove everything inside {@link EventFlow}.
*/
private void clean() { private void clean() {
this.listeners.clear(); unsubscribeAll();
this.event = null; this.event = null;
this.result = null; this.result = null;
} // TODO } // TODO
/**
* TODO
*
* @return TODO
*/
public Map<String, ?> getResult() { public Map<String, ?> getResult() {
return this.result; return this.result;
} }
/**
* TODO
*
* @return TODO
*/
public EventType getEvent() { public EventType getEvent() {
return event; return event;
} }
/**
*
* Returns a copy of the list of listeners.
*
* @return Copy of the list of listeners.
*/
public ListenerHandler[] getListeners() { public ListenerHandler[] getListeners() {
return listeners.toArray(new ListenerHandler[0]); return listeners.toArray(new ListenerHandler[0]);
} }
/**
* Returns the generated snowflake for the {@link EventFlow}
*
* @return The generated snowflake for this {@link EventFlow}
*/
public long getEventSnowflake() { public long getEventSnowflake() {
return eventSnowflake; return eventSnowflake;
} }

View File

@@ -6,6 +6,9 @@ import com.lmax.disruptor.dsl.ProducerType;
import java.util.Map; import java.util.Map;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.eventbus.events.EventType; import org.toop.framework.eventbus.events.EventType;
import org.toop.framework.eventbus.events.UniqueEvent; import org.toop.framework.eventbus.events.UniqueEvent;
@@ -14,9 +17,10 @@ import org.toop.framework.eventbus.events.UniqueEvent;
* publishing. * publishing.
*/ */
public final class GlobalEventBus { public final class GlobalEventBus {
private static final Logger logger = LogManager.getLogger(GlobalEventBus.class);
/** Map of event class to type-specific listeners. */ /** Map of event class to type-specific listeners. */
private static final Map<Class<?>, CopyOnWriteArrayList<Consumer<? super EventType>>> private static final Map<Class<?>, CopyOnWriteArrayList<ListenerHandler<?>>>
LISTENERS = new ConcurrentHashMap<>(); LISTENERS = new ConcurrentHashMap<>();
/** Map of event class to Snowflake-ID-specific listeners. */ /** Map of event class to Snowflake-ID-specific listeners. */
@@ -49,7 +53,6 @@ public final class GlobalEventBus {
ProducerType.MULTI, ProducerType.MULTI,
new BusySpinWaitStrategy()); new BusySpinWaitStrategy());
// Single consumer that dispatches to subscribers
DISRUPTOR.handleEventsWith( DISRUPTOR.handleEventsWith(
(holder, seq, endOfBatch) -> { (holder, seq, endOfBatch) -> {
if (holder.event != null) { if (holder.event != null) {
@@ -73,23 +76,12 @@ public final class GlobalEventBus {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Subscription // Subscription
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
public static <T extends EventType> Consumer<? super EventType> subscribe( public static <T extends EventType> void subscribe(ListenerHandler<T> listener) {
Class<T> eventClass, Consumer<T> listener) { logger.debug("Subscribing to {}: {}", listener.getListenerClass().getSimpleName(), listener.getListener().getClass().getSimpleName());
LISTENERS.computeIfAbsent(listener.getListenerClass(), _ -> new CopyOnWriteArrayList<>()).add(listener);
CopyOnWriteArrayList<Consumer<? super EventType>> list =
LISTENERS.computeIfAbsent(eventClass, k -> new CopyOnWriteArrayList<>());
Consumer<? super EventType> wrapper = event -> listener.accept(eventClass.cast(event));
list.add(wrapper);
return wrapper;
}
public static Consumer<? super EventType> subscribe(Consumer<Object> listener) {
Consumer<? super EventType> wrapper = event -> listener.accept(event);
LISTENERS.computeIfAbsent(Object.class, _ -> new CopyOnWriteArrayList<>()).add(wrapper);
return wrapper;
} }
// TODO
public static <T extends UniqueEvent> void subscribeById( public static <T extends UniqueEvent> void subscribeById(
Class<T> eventClass, long eventId, Consumer<T> listener) { Class<T> eventClass, long eventId, Consumer<T> listener) {
UUID_LISTENERS UUID_LISTENERS
@@ -97,10 +89,14 @@ public final class GlobalEventBus {
.put(eventId, listener); .put(eventId, listener);
} }
public static void unsubscribe(Object listener) { public static void unsubscribe(ListenerHandler<?> listener) {
LISTENERS.values().forEach(list -> list.remove(listener)); logger.debug("Unsubscribing from {}: {}", listener.getListenerClass().getSimpleName(), listener.getListener().getClass().getSimpleName());
LISTENERS.getOrDefault(listener.getListenerClass(), new CopyOnWriteArrayList<>())
.remove(listener);
LISTENERS.entrySet().removeIf(entry -> entry.getValue().isEmpty());
} }
// TODO
public static <T extends UniqueEvent> void unsubscribeById( public static <T extends UniqueEvent> void unsubscribeById(
Class<T> eventClass, long eventId) { Class<T> eventClass, long eventId) {
Map<Long, Consumer<? extends UniqueEvent>> map = UUID_LISTENERS.get(eventClass); Map<Long, Consumer<? extends UniqueEvent>> map = UUID_LISTENERS.get(eventClass);
@@ -124,36 +120,44 @@ public final class GlobalEventBus {
} }
} }
@SuppressWarnings("unchecked")
private static <T extends EventType> void callListener(ListenerHandler<?> raw, EventType event) {
ListenerHandler<T> handler = (ListenerHandler<T>) raw;
Consumer<T> listener = handler.getListener();
T casted = (T) event;
listener.accept(casted);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static void dispatchEvent(EventType event) { private static void dispatchEvent(EventType event) {
Class<?> clazz = event.getClass(); Class<?> clazz = event.getClass();
// class-specific listeners logger.debug("Triggered event: {}", event.getClass().getSimpleName());
CopyOnWriteArrayList<Consumer<? super EventType>> classListeners = LISTENERS.get(clazz);
CopyOnWriteArrayList<ListenerHandler<?>> classListeners = LISTENERS.get(clazz);
if (classListeners != null) { if (classListeners != null) {
for (Consumer<? super EventType> listener : classListeners) { for (ListenerHandler<?> listener : classListeners) {
try { try {
listener.accept(event); callListener(listener, event);
} catch (Throwable e) { } catch (Throwable e) {
// e.printStackTrace(); // e.printStackTrace();
} }
} }
} }
// generic listeners CopyOnWriteArrayList<ListenerHandler<?>> genericListeners = LISTENERS.get(Object.class);
CopyOnWriteArrayList<Consumer<? super EventType>> genericListeners =
LISTENERS.get(Object.class);
if (genericListeners != null) { if (genericListeners != null) {
for (Consumer<? super EventType> listener : genericListeners) { for (ListenerHandler<?> listener : genericListeners) {
try { try {
listener.accept(event); callListener(listener, event);
} catch (Throwable e) { } catch (Throwable e) {
// e.printStackTrace(); // e.printStackTrace();
} }
} }
} }
// snowflake listeners
if (event instanceof UniqueEvent snowflakeEvent) { if (event instanceof UniqueEvent snowflakeEvent) {
Map<Long, Consumer<? extends UniqueEvent>> map = UUID_LISTENERS.get(clazz); Map<Long, Consumer<? extends UniqueEvent>> map = UUID_LISTENERS.get(clazz);
if (map != null) { if (map != null) {
@@ -182,4 +186,8 @@ public final class GlobalEventBus {
LISTENERS.clear(); LISTENERS.clear();
UUID_LISTENERS.clear(); UUID_LISTENERS.clear();
} }
public static Map<Class<?>, CopyOnWriteArrayList<ListenerHandler<?>>> getAllListeners() {
return LISTENERS;
}
} }

View File

@@ -1,25 +1,48 @@
package org.toop.framework.eventbus; package org.toop.framework.eventbus;
public class ListenerHandler { import org.toop.framework.SnowflakeGenerator;
private Object listener; import org.toop.framework.eventbus.events.EventType;
// private boolean unsubscribeAfterSuccess = true; import java.util.function.Consumer;
// public ListenerHandler(Object listener, boolean unsubAfterSuccess) { public class ListenerHandler<T extends EventType> {
// this.listener = listener; private final long id;
// this.unsubscribeAfterSuccess = unsubAfterSuccess; private final String name;
// } private final Class<T> clazz;
private final Consumer<T> listener;
public ListenerHandler(Object listener) { public ListenerHandler(long id, String name, Class<T> clazz, Consumer<T> listener) {
this.id = id;
this.name = name;
this.clazz = clazz;
this.listener = listener; this.listener = listener;
} }
public Object getListener() { public ListenerHandler(String name, Class<T> clazz, Consumer<T> listener) {
return this.listener; this(SnowflakeGenerator.nextId(), name, clazz, listener);
} }
// public boolean isUnsubscribeAfterSuccess() { public ListenerHandler(long id, Class<T> clazz, Consumer<T> listener) {
// return this.unsubscribeAfterSuccess; this(id, String.valueOf(id), clazz, listener);
// } }
public ListenerHandler(Class<T> clazz, Consumer<T> listener) {
this(SnowflakeGenerator.nextId(), clazz, listener);
}
public long getId() {
return id;
}
public Consumer<T> getListener() {
return listener;
}
public Class<T> getListenerClass() {
return clazz;
}
public String getName() {
return name;
}
} }

View File

@@ -17,25 +17,25 @@ public class NetworkingClientEventListener {
public NetworkingClientEventListener(NetworkingClientManager clientManager) { public NetworkingClientEventListener(NetworkingClientManager clientManager) {
this.clientManager = clientManager; this.clientManager = clientManager;
new EventFlow() new EventFlow()
.listen(this::handleStartClient) .listen(NetworkEvents.StartClient.class, this::handleStartClient, false)
.listen(this::handleCommand) .listen(NetworkEvents.SendCommand.class, this::handleCommand, false)
.listen(this::handleSendLogin) .listen(NetworkEvents.SendLogin.class, this::handleSendLogin, false)
.listen(this::handleSendLogout) .listen(NetworkEvents.SendLogout.class, this::handleSendLogout, false)
.listen(this::handleSendGetPlayerlist) .listen(NetworkEvents.SendGetPlayerlist.class, this::handleSendGetPlayerlist, false)
.listen(this::handleSendGetGamelist) .listen(NetworkEvents.SendGetGamelist.class, this::handleSendGetGamelist, false)
.listen(this::handleSendSubscribe) .listen(NetworkEvents.SendSubscribe.class, this::handleSendSubscribe, false)
.listen(this::handleSendMove) .listen(NetworkEvents.SendMove.class, this::handleSendMove, false)
.listen(this::handleSendChallenge) .listen(NetworkEvents.SendChallenge.class, this::handleSendChallenge, false)
.listen(this::handleSendAcceptChallenge) .listen(NetworkEvents.SendAcceptChallenge.class, this::handleSendAcceptChallenge, false)
.listen(this::handleSendForfeit) .listen(NetworkEvents.SendForfeit.class, this::handleSendForfeit, false)
.listen(this::handleSendMessage) .listen(NetworkEvents.SendMessage.class, this::handleSendMessage, false)
.listen(this::handleSendHelp) .listen(NetworkEvents.SendHelp.class, this::handleSendHelp, false)
.listen(this::handleSendHelpForCommand) .listen(NetworkEvents.SendHelpForCommand.class, this::handleSendHelpForCommand, false)
.listen(this::handleCloseClient) .listen(NetworkEvents.CloseClient.class, this::handleCloseClient, false)
.listen(this::handleReconnect) .listen(NetworkEvents.Reconnect.class, this::handleReconnect, false)
.listen(this::handleChangeAddress) .listen(NetworkEvents.ChangeAddress.class, this::handleChangeAddress, false)
.listen(this::handleGetAllConnections) .listen(NetworkEvents.RequestsAllClients.class, this::handleGetAllConnections, false)
.listen(this::handleShutdownAll); .listen(NetworkEvents.ForceCloseAllClients.class, this::handleShutdownAll, false);
} }
void handleStartClient(NetworkEvents.StartClient event) { void handleStartClient(NetworkEvents.StartClient event) {

View File

@@ -8,6 +8,8 @@ import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.toop.framework.eventbus.GlobalEventBus;
import org.toop.framework.networking.events.NetworkEvents;
import org.toop.framework.networking.exceptions.ClientNotFoundException; import org.toop.framework.networking.exceptions.ClientNotFoundException;
import org.toop.framework.networking.exceptions.CouldNotConnectException; import org.toop.framework.networking.exceptions.CouldNotConnectException;
import org.toop.framework.networking.interfaces.NetworkingClient; import org.toop.framework.networking.interfaces.NetworkingClient;
@@ -44,6 +46,7 @@ public class NetworkingClientManager implements org.toop.framework.networking.in
nClient.connect(id, nConnector.host(), nConnector.port()); nClient.connect(id, nConnector.host(), nConnector.port());
networkClients.put(id, nClient); networkClients.put(id, nClient);
logger.info("New client started successfully for {}:{}", nConnector.host(), nConnector.port()); logger.info("New client started successfully for {}:{}", nConnector.host(), nConnector.port());
GlobalEventBus.post(new NetworkEvents.ConnectTry(id, attempts, nConnector.reconnectAttempts(), true));
onSuccess.run(); onSuccess.run();
scheduler.shutdown(); scheduler.shutdown();
} catch (CouldNotConnectException e) { } catch (CouldNotConnectException e) {
@@ -51,14 +54,17 @@ public class NetworkingClientManager implements org.toop.framework.networking.in
if (attempts < nConnector.reconnectAttempts()) { if (attempts < nConnector.reconnectAttempts()) {
logger.warn("Could not connect to {}:{}. Retrying in {} {}", logger.warn("Could not connect to {}:{}. Retrying in {} {}",
nConnector.host(), nConnector.port(), nConnector.timeout(), nConnector.timeUnit()); nConnector.host(), nConnector.port(), nConnector.timeout(), nConnector.timeUnit());
GlobalEventBus.post(new NetworkEvents.ConnectTry(id, attempts, nConnector.reconnectAttempts(), false));
scheduler.schedule(this, nConnector.timeout(), nConnector.timeUnit()); scheduler.schedule(this, nConnector.timeout(), nConnector.timeUnit());
} else { } else {
logger.error("Failed to start client for {}:{} after {} attempts", nConnector.host(), nConnector.port(), attempts); logger.error("Failed to start client for {}:{} after {} attempts", nConnector.host(), nConnector.port(), attempts);
GlobalEventBus.post(new NetworkEvents.ConnectTry(id, -1, nConnector.reconnectAttempts(), false));
onFailure.run(); onFailure.run();
scheduler.shutdown(); scheduler.shutdown();
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("Unexpected exception during startClient", e); logger.error("Unexpected exception during startClient", e);
GlobalEventBus.post(new NetworkEvents.ConnectTry(id, -1, nConnector.reconnectAttempts(), false));
onFailure.run(); onFailure.run();
scheduler.shutdown(); scheduler.shutdown();
} }

View File

@@ -181,6 +181,8 @@ public class NetworkEvents extends EventsBase {
public record StartClientResponse(long clientId, boolean successful, long identifier) public record StartClientResponse(long clientId, boolean successful, long identifier)
implements ResponseToUniqueEvent {} implements ResponseToUniqueEvent {}
public record ConnectTry(long clientId, int amount, int maxAmount, boolean success) implements GenericEvent {}
/** /**
* Requests reconnection of an existing client using its previous configuration. * Requests reconnection of an existing client using its previous configuration.
* <p> * <p>

View File

@@ -8,7 +8,7 @@ import org.toop.framework.resource.types.LoadableResource;
@FileExtension({"png", "jpg", "jpeg"}) @FileExtension({"png", "jpg", "jpeg"})
public class ImageAsset extends BaseResource implements LoadableResource { public class ImageAsset extends BaseResource implements LoadableResource {
private Image image; private Image image = null;
public ImageAsset(final File file) { public ImageAsset(final File file) {
super(file); super(file);
@@ -40,8 +40,7 @@ public class ImageAsset extends BaseResource implements LoadableResource {
public Image getImage() { public Image getImage() {
if (!this.isLoaded) { if (!this.isLoaded) {
this.load(); this.load();
return image;
} }
return null; return image;
} }
} }

View File

@@ -14,7 +14,7 @@ public class JsonAsset<T> extends BaseResource implements LoadableResource {
private T content; private T content;
private Class<T> type; private Class<T> type;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); private final Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
public JsonAsset(File file, Class<T> type) { public JsonAsset(File file, Class<T> type) {
super(file); super(file);

View File

@@ -38,6 +38,22 @@ public class SettingsAsset extends JsonAsset<Settings> {
return getContent().layoutSize; return getContent().layoutSize;
} }
public Boolean getTutorialFlag() {
return getContent().showTutorials;
}
public Boolean getFirstTTT() {
return getContent().firstTTT;
}
public Boolean getFirstConnect4() {
return getContent().firstConnect4;
}
public Boolean getFirstReversi() {
return getContent().firstReversi;
}
public void setVolume(int volume) { public void setVolume(int volume) {
getContent().volume = volume; getContent().volume = volume;
save(); save();
@@ -72,4 +88,24 @@ public class SettingsAsset extends JsonAsset<Settings> {
getContent().layoutSize = layoutSize; getContent().layoutSize = layoutSize;
save(); save();
} }
public void setTutorialFlag(boolean tutorialFlag) {
getContent().showTutorials = tutorialFlag;
save();
}
public void setFirstTTT(boolean firstTTT) {
getContent().firstTTT = firstTTT;
save();
}
public void setFirstConnect4(boolean firstConnect4) {
getContent().firstConnect4 = firstConnect4;
save();
}
public void setFirstReversi(boolean firstReversi) {
getContent().firstReversi = firstReversi;
save();
}
} }

View File

@@ -8,4 +8,9 @@ public class Settings {
public int volume = 100; public int volume = 100;
public int fxVolume = 20; public int fxVolume = 20;
public int musicVolume = 15; public int musicVolume = 15;
public Boolean showTutorials;
public Boolean firstReversi;
public Boolean firstTTT;
public Boolean firstConnect4;
} }

View File

@@ -1,235 +1,222 @@
// package org.toop.framework.eventbus; package org.toop.framework.eventbus;
//
// import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Tag;
// import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
// import org.toop.framework.eventbus.events.UniqueEvent; import org.toop.framework.eventbus.events.ResponseToUniqueEvent;
//
// import java.math.BigInteger; import java.math.BigInteger;
// import java.util.concurrent.*; import java.util.concurrent.*;
// import java.util.concurrent.atomic.LongAdder; import java.util.concurrent.atomic.LongAdder;
//
// import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
//
// class EventFlowStressTest { class EventFlowStressTest {
//
// /** Top-level record to ensure runtime type matches subscription */ public record HeavyEvent(String payload, long eventSnowflake) implements ResponseToUniqueEvent {
// public record HeavyEvent(String payload, long eventSnowflake) implements UniqueEvent { @Override
// @Override public long getIdentifier() {
// public java.util.Map<String, Object> result() { return eventSnowflake;
// return java.util.Map.of("payload", payload, "eventId", eventSnowflake); }
// } }
//
// @Override public record HeavyEventSuccess(String payload, long eventSnowflake) implements ResponseToUniqueEvent {
// public long eventSnowflake() { @Override
// return this.eventSnowflake; public long getIdentifier() {
// } return eventSnowflake;
// } }
// }
// public record HeavyEventSuccess(String payload, long eventSnowflake) implements
// UniqueEvent { private static final int THREADS = 32;
// @Override private static final long EVENTS_PER_THREAD = 10_000_000;
// public java.util.Map<String, Object> result() {
// return java.util.Map.of("payload", payload, "eventId", eventSnowflake); @Tag("stress")
// } @Test
// void extremeConcurrencySendTest_progressWithMemory() throws InterruptedException {
// @Override LongAdder counter = new LongAdder();
// public long eventSnowflake() { ExecutorService executor = Executors.newFixedThreadPool(THREADS);
// return eventSnowflake;
// } BigInteger totalEvents = BigInteger.valueOf(THREADS)
// } .multiply(BigInteger.valueOf(EVENTS_PER_THREAD));
//
// private static final int THREADS = 32; long startTime = System.currentTimeMillis();
// private static final long EVENTS_PER_THREAD = 10_000_000;
// Thread monitor = new Thread(() -> {
// @Tag("stress") long lastCount = 0;
// @Test long lastTime = System.currentTimeMillis();
// void extremeConcurrencySendTest_progressWithMemory() throws InterruptedException { Runtime runtime = Runtime.getRuntime();
// LongAdder counter = new LongAdder();
// ExecutorService executor = Executors.newFixedThreadPool(THREADS); while (counter.sum() < totalEvents.longValue()) {
// try { Thread.sleep(200); } catch (InterruptedException ignored) {}
// BigInteger totalEvents = BigInteger.valueOf(THREADS)
// .multiply(BigInteger.valueOf(EVENTS_PER_THREAD)); long now = System.currentTimeMillis();
// long completed = counter.sum();
// long startTime = System.currentTimeMillis(); long eventsThisPeriod = completed - lastCount;
// double eps = eventsThisPeriod / ((now - lastTime) / 1000.0);
// // Monitor thread for EPS and memory
// Thread monitor = new Thread(() -> { long usedMemory = runtime.totalMemory() - runtime.freeMemory();
// long lastCount = 0; double usedPercent = usedMemory * 100.0 / runtime.maxMemory();
// long lastTime = System.currentTimeMillis();
// Runtime runtime = Runtime.getRuntime(); System.out.printf(
// "Progress: %d/%d (%.2f%%), EPS: %.0f, Memory Used: %.2f MB (%.2f%%)%n",
// while (counter.sum() < totalEvents.longValue()) { completed,
// try { Thread.sleep(200); } catch (InterruptedException ignored) {} totalEvents.longValue(),
// completed * 100.0 / totalEvents.doubleValue(),
// long now = System.currentTimeMillis(); eps,
// long completed = counter.sum(); usedMemory / 1024.0 / 1024.0,
// long eventsThisPeriod = completed - lastCount; usedPercent
// double eps = eventsThisPeriod / ((now - lastTime) / 1000.0); );
//
// long usedMemory = runtime.totalMemory() - runtime.freeMemory(); lastCount = completed;
// double usedPercent = usedMemory * 100.0 / runtime.maxMemory(); lastTime = now;
// }
// System.out.printf( });
// "Progress: %d/%d (%.2f%%), EPS: %.0f, Memory Used: %.2f MB (%.2f%%)%n", monitor.setDaemon(true);
// completed, monitor.start();
// totalEvents.longValue(),
// completed * 100.0 / totalEvents.doubleValue(), var listener = new EventFlow().listen(HeavyEvent.class, _ -> counter.increment());
// eps,
// usedMemory / 1024.0 / 1024.0, for (int t = 0; t < THREADS; t++) {
// usedPercent executor.submit(() -> {
// ); for (int i = 0; i < EVENTS_PER_THREAD; i++) {
// var _ = new EventFlow().addPostEvent(HeavyEvent.class, "payload-" + i)
// lastCount = completed; .asyncPostEvent();
// lastTime = now; }
// } });
// }); }
// monitor.setDaemon(true);
// monitor.start(); executor.shutdown();
// executor.awaitTermination(10, TimeUnit.MINUTES);
// var listener = new EventFlow().listen(HeavyEvent.class, _ -> counter.increment());
// listener.getResult();
// // Submit events asynchronously
// for (int t = 0; t < THREADS; t++) { long endTime = System.currentTimeMillis();
// executor.submit(() -> { double durationSeconds = (endTime - startTime) / 1000.0;
// for (int i = 0; i < EVENTS_PER_THREAD; i++) {
// var _ = new EventFlow().addPostEvent(HeavyEvent.class, "payload-" + i) System.out.println("Posted " + totalEvents + " events in " + durationSeconds + " seconds");
// .asyncPostEvent(); double averageEps = totalEvents.doubleValue() / durationSeconds;
// } System.out.printf("Average EPS: %.0f%n", averageEps);
// });
// } assertEquals(totalEvents.longValue(), counter.sum());
// }
// executor.shutdown();
// executor.awaitTermination(10, TimeUnit.MINUTES); @Tag("stress")
// @Test
// listener.getResult(); void extremeConcurrencySendAndReturnTest_progressWithMemory() throws InterruptedException {
// LongAdder counter = new LongAdder();
// long endTime = System.currentTimeMillis(); ExecutorService executor = Executors.newFixedThreadPool(THREADS);
// double durationSeconds = (endTime - startTime) / 1000.0;
// BigInteger totalEvents = BigInteger.valueOf(THREADS)
// System.out.println("Posted " + totalEvents + " events in " + durationSeconds + " .multiply(BigInteger.valueOf(EVENTS_PER_THREAD));
// seconds");
// double averageEps = totalEvents.doubleValue() / durationSeconds; long startTime = System.currentTimeMillis();
// System.out.printf("Average EPS: %.0f%n", averageEps);
// Thread monitor = new Thread(() -> {
// assertEquals(totalEvents.longValue(), counter.sum()); long lastCount = 0;
// } long lastTime = System.currentTimeMillis();
// Runtime runtime = Runtime.getRuntime();
// @Tag("stress")
// @Test while (counter.sum() < totalEvents.longValue()) {
// void extremeConcurrencySendAndReturnTest_progressWithMemory() throws InterruptedException { try { Thread.sleep(500); } catch (InterruptedException ignored) {}
// LongAdder counter = new LongAdder();
// ExecutorService executor = Executors.newFixedThreadPool(THREADS); long now = System.currentTimeMillis();
// long completed = counter.sum();
// BigInteger totalEvents = BigInteger.valueOf(THREADS) long eventsThisPeriod = completed - lastCount;
// .multiply(BigInteger.valueOf(EVENTS_PER_THREAD)); double eps = eventsThisPeriod / ((now - lastTime) / 1000.0);
//
// long startTime = System.currentTimeMillis(); long usedMemory = runtime.totalMemory() - runtime.freeMemory();
// double usedPercent = usedMemory * 100.0 / runtime.maxMemory();
// // Monitor thread for EPS and memory
// Thread monitor = new Thread(() -> { System.out.printf(
// long lastCount = 0; "Progress: %d/%d (%.2f%%), EPS: %.0f, Memory Used: %.2f MB (%.2f%%)%n",
// long lastTime = System.currentTimeMillis(); completed,
// Runtime runtime = Runtime.getRuntime(); totalEvents.longValue(),
// completed * 100.0 / totalEvents.doubleValue(),
// while (counter.sum() < totalEvents.longValue()) { eps,
// try { Thread.sleep(200); } catch (InterruptedException ignored) {} usedMemory / 1024.0 / 1024.0,
// usedPercent
// long now = System.currentTimeMillis(); );
// long completed = counter.sum();
// long eventsThisPeriod = completed - lastCount; lastCount = completed;
// double eps = eventsThisPeriod / ((now - lastTime) / 1000.0); lastTime = now;
// }
// long usedMemory = runtime.totalMemory() - runtime.freeMemory(); });
// double usedPercent = usedMemory * 100.0 / runtime.maxMemory(); monitor.setDaemon(true);
// monitor.start();
// System.out.printf(
// "Progress: %d/%d (%.2f%%), EPS: %.0f, Memory Used: %.2f MB (%.2f%%)%n", EventFlow sharedFlow = new EventFlow();
// completed, sharedFlow.listen(HeavyEventSuccess.class, _ -> counter.increment(), false, "heavyEventSuccessListener");
// totalEvents.longValue(),
// completed * 100.0 / totalEvents.doubleValue(), for (int t = 0; t < THREADS; t++) {
// eps, executor.submit(() -> {
// usedMemory / 1024.0 / 1024.0, EventFlow threadFlow = new EventFlow();
// usedPercent
// ); for (int i = 0; i < EVENTS_PER_THREAD; i++) {
// var heavyEvent = threadFlow.addPostEvent(HeavyEvent.class, "payload-" + i)
// lastCount = completed; .postEvent();
// lastTime = now;
// } threadFlow.addPostEvent(HeavyEventSuccess.class, "payload-" + i, heavyEvent.getEventSnowflake())
// }); .postEvent();
// monitor.setDaemon(true); }
// monitor.start(); });
// }
// // Submit events asynchronously
// for (int t = 0; t < THREADS; t++) { executor.shutdown();
// executor.submit(() -> { executor.awaitTermination(10, TimeUnit.MINUTES);
// for (int i = 0; i < EVENTS_PER_THREAD; i++) {
// var a = new EventFlow().addPostEvent(HeavyEvent.class, "payload-" + i) long endTime = System.currentTimeMillis();
// .onResponse(HeavyEventSuccess.class, _ -> counter.increment()) double durationSeconds = (endTime - startTime) / 1000.0;
// .postEvent();
// System.out.println("Posted " + totalEvents + " events in " + durationSeconds + " seconds");
// new EventFlow().addPostEvent(HeavyEventSuccess.class, "payload-" + i, double averageEps = totalEvents.doubleValue() / durationSeconds;
// a.getEventSnowflake()) System.out.printf("Average EPS: %.0f%n", averageEps);
// .postEvent();
// } assertEquals(totalEvents.longValue(), counter.sum());
// }); }
// }
// @Tag("stress")
// executor.shutdown(); @Test
// executor.awaitTermination(10, TimeUnit.MINUTES); void efficientExtremeConcurrencyTest() throws InterruptedException {
// final int THREADS = Runtime.getRuntime().availableProcessors();
// long endTime = System.currentTimeMillis(); final int EVENTS_PER_THREAD = 1_000_000;
// double durationSeconds = (endTime - startTime) / 1000.0;
// ExecutorService executor = Executors.newFixedThreadPool(THREADS);
// System.out.println("Posted " + totalEvents + " events in " + durationSeconds + " ConcurrentLinkedQueue<HeavyEvent> processedEvents = new ConcurrentLinkedQueue<>();
// seconds");
// double averageEps = totalEvents.doubleValue() / durationSeconds; long start = System.nanoTime();
// System.out.printf("Average EPS: %.0f%n", averageEps);
// EventFlow sharedFlow = new EventFlow();
// assertEquals(totalEvents.longValue(), counter.sum()); sharedFlow.listen(HeavyEvent.class, processedEvents::add, false, "heavyEventListener");
// }
// for (int t = 0; t < THREADS; t++) {
// executor.submit(() -> {
// @Tag("stress") EventFlow threadFlow = new EventFlow();
// @Test
// void efficientExtremeConcurrencyTest() throws InterruptedException { for (int i = 0; i < EVENTS_PER_THREAD; i++) {
// final int THREADS = Runtime.getRuntime().availableProcessors(); threadFlow.addPostEvent(HeavyEvent.class, "payload-" + i)
// final int EVENTS_PER_THREAD = 5000; .postEvent();
// }
// ExecutorService executor = Executors.newFixedThreadPool(THREADS); });
// ConcurrentLinkedQueue<HeavyEvent> processedEvents = new ConcurrentLinkedQueue<>(); }
//
// long start = System.nanoTime(); executor.shutdown();
// executor.awaitTermination(10, TimeUnit.MINUTES);
// for (int t = 0; t < THREADS; t++) {
// executor.submit(() -> { long end = System.nanoTime();
// for (int i = 0; i < EVENTS_PER_THREAD; i++) { double durationSeconds = (end - start) / 1_000_000_000.0;
// new EventFlow().addPostEvent(HeavyEvent.class, "payload-" + i)
// .onResponse(HeavyEvent.class, processedEvents::add) BigInteger totalEvents = BigInteger.valueOf(THREADS)
// .postEvent(); .multiply(BigInteger.valueOf(EVENTS_PER_THREAD));
// } double eps = totalEvents.doubleValue() / durationSeconds;
// });
// } System.out.printf("Posted %s events in %.3f seconds%n", totalEvents, durationSeconds);
// System.out.printf("Throughput: %.0f events/sec%n", eps);
// executor.shutdown();
// executor.awaitTermination(10, TimeUnit.MINUTES); Runtime rt = Runtime.getRuntime();
// System.out.printf("Used memory: %.2f MB%n", (rt.totalMemory() - rt.freeMemory()) / 1024.0 / 1024.0);
// long end = System.nanoTime();
// double durationSeconds = (end - start) / 1_000_000_000.0; assertEquals(totalEvents.intValue(), processedEvents.size());
// }
// BigInteger totalEvents = BigInteger.valueOf((long)
// THREADS).multiply(BigInteger.valueOf(EVENTS_PER_THREAD));
// double eps = totalEvents.doubleValue() / durationSeconds;
//
// System.out.printf("Posted %s events in %.3f seconds%n", totalEvents, durationSeconds);
// System.out.printf("Throughput: %.0f events/sec%n", eps);
//
// Runtime rt = Runtime.getRuntime();
// System.out.printf("Used memory: %.2f MB%n", (rt.totalMemory() - rt.freeMemory()) / 1024.0
// / 1024.0);
//
// assertEquals(totalEvents.intValue(), processedEvents.size());
// }
//
// @Tag("stress") // @Tag("stress")
// @Test // @Test
// void constructorCacheVsReflection() throws Throwable { // void constructorCacheVsReflection() throws Throwable {
@@ -247,7 +234,6 @@
// long endHandle = System.nanoTime(); // long endHandle = System.nanoTime();
// //
// System.out.println("Reflection: " + (endReflect - startReflect) / 1_000_000 + " ms"); // System.out.println("Reflection: " + (endReflect - startReflect) / 1_000_000 + " ms");
// System.out.println("MethodHandle Cache: " + (endHandle - startHandle) / 1_000_000 + " // System.out.println("MethodHandle Cache: " + (endHandle - startHandle) / 1_000_000 + " ms");
// ms");
// } // }
// } }