Formatting

This commit is contained in:
Bas de Jong
2025-10-07 19:47:45 +02:00
parent 783cfd22e1
commit 3c385e27b0
50 changed files with 2017 additions and 1749 deletions

View File

@@ -8,14 +8,14 @@ import org.toop.framework.networking.NetworkingClientManager;
import org.toop.framework.networking.NetworkingInitializationException; import org.toop.framework.networking.NetworkingInitializationException;
public final class Main { public final class Main {
public static void main(String[] args) { public static void main(String[] args) {
initSystems(); initSystems();
App.run(args); App.run(args);
} }
private static void initSystems() throws NetworkingInitializationException { private static void initSystems() throws NetworkingInitializationException {
ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets")); ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets"));
new Thread(NetworkingClientManager::new).start(); new Thread(NetworkingClientManager::new).start();
new Thread(SoundManager::new).start(); new Thread(SoundManager::new).start();
} }
} }

View File

@@ -1,6 +1,11 @@
package org.toop.app; package org.toop.app;
import java.util.Stack;
import javafx.application.Application;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.toop.app.layer.Layer; import org.toop.app.layer.Layer;
import org.toop.app.layer.layers.MainLayer; import org.toop.app.layer.layers.MainLayer;
import org.toop.app.layer.layers.QuitPopup; import org.toop.app.layer.layers.QuitPopup;
@@ -9,155 +14,154 @@ import org.toop.framework.asset.resources.CssAsset;
import org.toop.framework.audio.events.AudioEvents; import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.toop.local.AppSettings; import org.toop.local.AppSettings;
import java.util.Stack;
public final class App extends Application { public final class App extends Application {
private static Stage stage; private static Stage stage;
private static Scene scene; private static Scene scene;
private static StackPane root; private static StackPane root;
private static Stack<Layer> stack; private static Stack<Layer> stack;
private static int height; private static int height;
private static int width; private static int width;
private static boolean isQuitting; private static boolean isQuitting;
public static void run(String[] args) { public static void run(String[] args) {
launch(args); launch(args);
} }
@Override @Override
public void start(Stage stage) throws Exception { public void start(Stage stage) throws Exception {
final StackPane root = new StackPane(); final StackPane root = new StackPane();
final Scene scene = new Scene(root); final Scene scene = new Scene(root);
stage.setTitle(AppContext.getString("appTitle")); stage.setTitle(AppContext.getString("appTitle"));
stage.setWidth(1080); stage.setWidth(1080);
stage.setHeight(720); stage.setHeight(720);
stage.setOnCloseRequest(event -> { stage.setOnCloseRequest(
event.consume(); event -> {
event.consume();
if (!isQuitting) { if (!isQuitting) {
quitPopup(); quitPopup();
} }
}); });
stage.setScene(scene); stage.setScene(scene);
stage.setResizable(false); stage.setResizable(false);
stage.show(); stage.show();
App.stage = stage; App.stage = stage;
App.scene = scene; App.scene = scene;
App.root = root; App.root = root;
App.stack = new Stack<>(); App.stack = new Stack<>();
App.width = (int) stage.getWidth(); App.width = (int) stage.getWidth();
App.height = (int) stage.getHeight(); App.height = (int) stage.getHeight();
App.isQuitting = false; App.isQuitting = false;
final AppSettings settings = new AppSettings(); final AppSettings settings = new AppSettings();
settings.applySettings(); settings.applySettings();
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent(); new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent();
activate(new MainLayer()); activate(new MainLayer());
} }
public static void activate(Layer layer) { public static void activate(Layer layer) {
Platform.runLater(() -> { Platform.runLater(
popAll(); () -> {
push(layer); popAll();
}); push(layer);
} });
}
public static void push(Layer layer) { public static void push(Layer layer) {
Platform.runLater(() -> { Platform.runLater(
root.getChildren().addLast(layer.getLayer()); () -> {
stack.push(layer); root.getChildren().addLast(layer.getLayer());
}); stack.push(layer);
} });
}
public static void pop() { public static void pop() {
Platform.runLater(() -> { Platform.runLater(
root.getChildren().removeLast(); () -> {
stack.pop(); root.getChildren().removeLast();
stack.pop();
isQuitting = false; isQuitting = false;
}); });
} }
public static void popAll() { public static void popAll() {
Platform.runLater(() -> { Platform.runLater(
final int childrenCount = root.getChildren().size(); () -> {
final int childrenCount = root.getChildren().size();
for (int i = 0; i < childrenCount; i++) { for (int i = 0; i < childrenCount; i++) {
try { try {
root.getChildren().removeLast(); root.getChildren().removeLast();
} catch (Exception e) { } catch (Exception e) {
IO.println(e); IO.println(e);
} }
} }
stack.removeAllElements(); stack.removeAllElements();
}); });
} }
public static void quitPopup() { public static void quitPopup() {
Platform.runLater(() -> { Platform.runLater(
push(new QuitPopup()); () -> {
isQuitting = true; push(new QuitPopup());
}); isQuitting = true;
} });
}
public static void quit() { public static void quit() {
stage.close(); stage.close();
} }
public static void reloadAll() { public static void reloadAll() {
stage.setTitle(AppContext.getString("appTitle")); stage.setTitle(AppContext.getString("appTitle"));
for (final Layer layer : stack) { for (final Layer layer : stack) {
layer.reload(); layer.reload();
} }
} }
public static void setFullscreen(boolean fullscreen) { public static void setFullscreen(boolean fullscreen) {
stage.setFullScreen(fullscreen); stage.setFullScreen(fullscreen);
width = (int) stage.getWidth(); width = (int) stage.getWidth();
height = (int) stage.getHeight(); height = (int) stage.getHeight();
reloadAll(); reloadAll();
} }
public static void setStyle(String theme, String layoutSize) { public static void setStyle(String theme, String layoutSize) {
final int stylesCount = scene.getStylesheets().size(); final int stylesCount = scene.getStylesheets().size();
for (int i = 0; i < stylesCount; i++) { for (int i = 0; i < stylesCount; i++) {
scene.getStylesheets().removeLast(); scene.getStylesheets().removeLast();
} }
scene.getStylesheets().add(ResourceManager.<CssAsset>get(theme + ".css").getUrl()); scene.getStylesheets().add(ResourceManager.<CssAsset>get(theme + ".css").getUrl());
scene.getStylesheets().add(ResourceManager.<CssAsset>get(layoutSize + ".css").getUrl()); scene.getStylesheets().add(ResourceManager.<CssAsset>get(layoutSize + ".css").getUrl());
reloadAll(); reloadAll();
} }
public static int getWidth() { public static int getWidth() {
return width; return width;
} }
public static int getHeight() { public static int getHeight() {
return height; return height;
} }
} }

View File

@@ -1,6 +1,10 @@
package org.toop.app; package org.toop.app;
public record GameInformation(String[] playerName, boolean[] isPlayerHuman, public record GameInformation(
int[] computerDifficulty, int[] computerThinkTime, String[] playerName,
boolean isConnectionLocal, String serverIP, String serverPort) { boolean[] isPlayerHuman,
} int[] computerDifficulty,
int[] computerThinkTime,
boolean isConnectionLocal,
String serverIP,
String serverPort) {}

View File

@@ -1,123 +1,130 @@
package org.toop.app.canvas; package org.toop.app.canvas;
import java.util.function.Consumer;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext; import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseButton; import javafx.scene.input.MouseButton;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import java.util.function.Consumer;
public abstract class GameCanvas { public abstract class GameCanvas {
protected record Cell(float x, float y, float width, float height) { protected record Cell(float x, float y, float width, float height) {}
}
protected final Canvas canvas; protected final Canvas canvas;
protected final GraphicsContext graphics; protected final GraphicsContext graphics;
protected final Color color; protected final Color color;
protected int width; protected int width;
protected int height; protected int height;
protected final int rows; protected final int rows;
protected final int columns; protected final int columns;
protected final int gapSize; protected final int gapSize;
protected final boolean edges; protected final boolean edges;
protected final Cell[] cells; protected final Cell[] cells;
protected GameCanvas(Color color, int width, int height, int rows, int columns, int gapSize, boolean edges, Consumer<Integer> onCellClicked) { protected GameCanvas(
canvas = new Canvas(width, height); Color color,
graphics = canvas.getGraphicsContext2D(); int width,
int height,
int rows,
int columns,
int gapSize,
boolean edges,
Consumer<Integer> onCellClicked) {
canvas = new Canvas(width, height);
graphics = canvas.getGraphicsContext2D();
this.color = color; this.color = color;
this.width = width; this.width = width;
this.height = height; this.height = height;
this.rows = rows; this.rows = rows;
this.columns = columns; this.columns = columns;
this.gapSize = gapSize; this.gapSize = gapSize;
this.edges = edges; this.edges = edges;
cells = new Cell[rows * columns]; cells = new Cell[rows * columns];
final float cellWidth = ((float) width - (rows - 1) * gapSize) / rows; final float cellWidth = ((float) width - (rows - 1) * gapSize) / rows;
final float cellHeight = ((float) height - (columns - 1) * gapSize) / columns; final float cellHeight = ((float) height - (columns - 1) * gapSize) / columns;
for (int y = 0; y < columns; y++) { for (int y = 0; y < columns; y++) {
final float startY = y * cellHeight + y * gapSize; final float startY = y * cellHeight + y * gapSize;
for (int x = 0; x < rows; x++) { for (int x = 0; x < rows; x++) {
final float startX = x * cellWidth + x * gapSize; final float startX = x * cellWidth + x * gapSize;
cells[y * rows + x] = new Cell(startX, startY, cellWidth, cellHeight); cells[y * rows + x] = new Cell(startX, startY, cellWidth, cellHeight);
} }
} }
canvas.setOnMouseClicked(event -> { canvas.setOnMouseClicked(
if (event.getButton() != MouseButton.PRIMARY) { event -> {
return; if (event.getButton() != MouseButton.PRIMARY) {
} return;
}
final int column = (int) ((event.getX() / width) * rows); final int column = (int) ((event.getX() / width) * rows);
final int row = (int) ((event.getY() / height) * columns); final int row = (int) ((event.getY() / height) * columns);
event.consume(); event.consume();
onCellClicked.accept(row * rows + column); onCellClicked.accept(row * rows + column);
}); });
render(); render();
} }
public void clear() { public void clear() {
graphics.clearRect(0, 0, width, height); graphics.clearRect(0, 0, width, height);
} }
public void render() { public void render() {
graphics.setFill(color); graphics.setFill(color);
for (int x = 1; x < rows; x++) { for (int x = 1; x < rows; x++) {
graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height); graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height);
} }
for (int y = 1; y < columns; y++) { for (int y = 1; y < columns; y++) {
graphics.fillRect(0, cells[y * rows].y() - gapSize, width, gapSize); graphics.fillRect(0, cells[y * rows].y() - gapSize, width, gapSize);
} }
if (edges) { if (edges) {
graphics.fillRect(-gapSize, 0, gapSize, height); graphics.fillRect(-gapSize, 0, gapSize, height);
graphics.fillRect(0, -gapSize, width, gapSize); graphics.fillRect(0, -gapSize, width, gapSize);
graphics.fillRect(width - gapSize, 0, gapSize, height); graphics.fillRect(width - gapSize, 0, gapSize, height);
graphics.fillRect(0, height - gapSize, width, gapSize); graphics.fillRect(0, height - gapSize, width, gapSize);
} }
} }
public void draw(Color color, int cell) { public void draw(Color color, int cell) {
final float x = cells[cell].x() + gapSize; final float x = cells[cell].x() + gapSize;
final float y = cells[cell].y() + gapSize; final float y = cells[cell].y() + gapSize;
final float width = cells[cell].width() - gapSize * 2; final float width = cells[cell].width() - gapSize * 2;
final float height = cells[cell].height() - gapSize * 2; final float height = cells[cell].height() - gapSize * 2;
graphics.setFill(color); graphics.setFill(color);
graphics.fillRect(x, y, width, height); graphics.fillRect(x, y, width, height);
} }
public void resize(int width, int height) { public void resize(int width, int height) {
canvas.setWidth(width); canvas.setWidth(width);
canvas.setHeight(height); canvas.setHeight(height);
this.width = width; this.width = width;
this.height = height; this.height = height;
clear(); clear();
render(); render();
} }
public Canvas getCanvas() { public Canvas getCanvas() {
return canvas; return canvas;
} }
} }

View File

@@ -1,38 +1,37 @@
package org.toop.app.canvas; package org.toop.app.canvas;
import java.util.function.Consumer;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import java.util.function.Consumer;
public class TicTacToeCanvas extends GameCanvas { public class TicTacToeCanvas extends GameCanvas {
public TicTacToeCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) { public TicTacToeCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) {
super(color, width, height, 3, 3, 10, false, onCellClicked); super(color, width, height, 3, 3, 10, false, onCellClicked);
} }
public void drawX(Color color, int cell) { public void drawX(Color color, int cell) {
graphics.setStroke(color); graphics.setStroke(color);
graphics.setLineWidth(gapSize); graphics.setLineWidth(gapSize);
final float x = cells[cell].x() + gapSize; final float x = cells[cell].x() + gapSize;
final float y = cells[cell].y() + gapSize; final float y = cells[cell].y() + gapSize;
final float width = cells[cell].width() - gapSize * 2; final float width = cells[cell].width() - gapSize * 2;
final float height = cells[cell].height() - gapSize * 2; final float height = cells[cell].height() - gapSize * 2;
graphics.strokeLine(x, y, x + width, y + height); graphics.strokeLine(x, y, x + width, y + height);
graphics.strokeLine(x + width, y, x, y + height); graphics.strokeLine(x + width, y, x, y + height);
} }
public void drawO(Color color, int cell) { public void drawO(Color color, int cell) {
graphics.setStroke(color); graphics.setStroke(color);
graphics.setLineWidth(gapSize); graphics.setLineWidth(gapSize);
final float x = cells[cell].x() + gapSize; final float x = cells[cell].x() + gapSize;
final float y = cells[cell].y() + gapSize; final float y = cells[cell].y() + gapSize;
final float width = cells[cell].width() - gapSize * 2; final float width = cells[cell].width() - gapSize * 2;
final float height = cells[cell].height() - gapSize * 2; final float height = cells[cell].height() - gapSize * 2;
graphics.strokeOval(x, y, width, height); graphics.strokeOval(x, y, width, height);
} }
} }

View File

@@ -4,8 +4,9 @@ import javafx.scene.Node;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
public abstract class Container { public abstract class Container {
public abstract Region getContainer(); public abstract Region getContainer();
public abstract void addNodes(Node... nodes); public abstract void addNodes(Node... nodes);
public abstract void addContainer(Container container, boolean fill);
} public abstract void addContainer(Container container, boolean fill);
}

View File

@@ -1,81 +1,86 @@
package org.toop.app.layer; package org.toop.app.layer;
import org.toop.app.App;
import org.toop.app.canvas.GameCanvas;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import org.toop.app.App;
import org.toop.app.canvas.GameCanvas;
public abstract class Layer { public abstract class Layer {
protected StackPane layer; protected StackPane layer;
protected Region background; protected Region background;
protected Layer(String... backgroundStyles) { protected Layer(String... backgroundStyles) {
layer = new StackPane(); layer = new StackPane();
background = new Region(); background = new Region();
background.getStyleClass().addAll(backgroundStyles); background.getStyleClass().addAll(backgroundStyles);
background.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE); background.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
layer.getChildren().addLast(background); layer.getChildren().addLast(background);
} }
protected void addContainer(Container container, Pos position, int xOffset, int yOffset, int widthPercent, int heightPercent) { protected void addContainer(
StackPane.setAlignment(container.getContainer(), position); Container container,
Pos position,
int xOffset,
int yOffset,
int widthPercent,
int heightPercent) {
StackPane.setAlignment(container.getContainer(), position);
final double widthUnit = App.getWidth() / 100.0; final double widthUnit = App.getWidth() / 100.0;
final double heightUnit = App.getHeight() / 100.0; final double heightUnit = App.getHeight() / 100.0;
if (widthPercent > 0) { if (widthPercent > 0) {
container.getContainer().setMaxWidth(widthPercent * widthUnit); container.getContainer().setMaxWidth(widthPercent * widthUnit);
} else { } else {
container.getContainer().setMaxWidth(Region.USE_PREF_SIZE); container.getContainer().setMaxWidth(Region.USE_PREF_SIZE);
} }
if (heightPercent > 0) { if (heightPercent > 0) {
container.getContainer().setMaxHeight(heightPercent * heightUnit); container.getContainer().setMaxHeight(heightPercent * heightUnit);
} else { } else {
container.getContainer().setMaxHeight(Region.USE_PREF_SIZE); container.getContainer().setMaxHeight(Region.USE_PREF_SIZE);
} }
container.getContainer().setTranslateX(xOffset * widthUnit); container.getContainer().setTranslateX(xOffset * widthUnit);
container.getContainer().setTranslateY(yOffset * heightUnit); container.getContainer().setTranslateY(yOffset * heightUnit);
layer.getChildren().addLast(container.getContainer()); layer.getChildren().addLast(container.getContainer());
} }
protected void addGameCanvas(GameCanvas canvas, Pos position, int xOffset, int yOffset) { protected void addGameCanvas(GameCanvas canvas, Pos position, int xOffset, int yOffset) {
StackPane.setAlignment(canvas.getCanvas(), position); StackPane.setAlignment(canvas.getCanvas(), position);
final double widthUnit = App.getWidth() / 100.0; final double widthUnit = App.getWidth() / 100.0;
final double heightUnit = App.getHeight() / 100.0; final double heightUnit = App.getHeight() / 100.0;
canvas.getCanvas().setTranslateX(xOffset * widthUnit); canvas.getCanvas().setTranslateX(xOffset * widthUnit);
canvas.getCanvas().setTranslateY(yOffset * heightUnit); canvas.getCanvas().setTranslateY(yOffset * heightUnit);
layer.getChildren().addLast(canvas.getCanvas()); layer.getChildren().addLast(canvas.getCanvas());
} }
protected void pop() { protected void pop() {
if (layer.getChildren().size() <= 1) { if (layer.getChildren().size() <= 1) {
return; return;
} }
layer.getChildren().removeLast(); layer.getChildren().removeLast();
} }
protected void popAll() { protected void popAll() {
final int containers = layer.getChildren().size(); final int containers = layer.getChildren().size();
for (int i = 1; i < containers; i++) { for (int i = 1; i < containers; i++) {
layer.getChildren().removeLast(); layer.getChildren().removeLast();
} }
} }
public StackPane getLayer() { public StackPane getLayer() {
return layer; return layer;
} }
public abstract void reload(); public abstract void reload();
} }

View File

@@ -1,131 +1,140 @@
package org.toop.app.layer; package org.toop.app.layer;
import org.toop.framework.audio.events.AudioEvents; import java.util.function.Consumer;
import org.toop.framework.eventbus.EventFlow;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import org.toop.framework.audio.events.AudioEvents;
import java.util.function.Consumer; import org.toop.framework.eventbus.EventFlow;
public final class NodeBuilder { public final class NodeBuilder {
public static void addCss(Node node, String... cssClasses) { public static void addCss(Node node, String... cssClasses) {
node.getStyleClass().addAll(cssClasses); node.getStyleClass().addAll(cssClasses);
} }
public static void setCss(Node node, String... cssClasses) { public static void setCss(Node node, String... cssClasses) {
node.getStyleClass().removeAll(); node.getStyleClass().removeAll();
node.getStyleClass().addAll(cssClasses); node.getStyleClass().addAll(cssClasses);
} }
public static Text header(String x) { public static Text header(String x) {
final Text element = new Text(x); final Text element = new Text(x);
setCss(element, "text-primary", "text-header"); setCss(element, "text-primary", "text-header");
return element; return element;
} }
public static Text text(String x) { public static Text text(String x) {
final Text element = new Text(x); final Text element = new Text(x);
setCss(element, "text-secondary", "text-normal"); setCss(element, "text-secondary", "text-normal");
return element; return element;
} }
public static Label button(String x, Runnable runnable) { public static Label button(String x, Runnable runnable) {
final Label element = new Label(x); final Label element = new Label(x);
setCss(element, "button", "text-normal"); setCss(element, "button", "text-normal");
element.setOnMouseClicked(_ -> { element.setOnMouseClicked(
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); _ -> {
runnable.run(); new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
}); runnable.run();
});
return element; return element;
} }
public static Label toggle(String x1, String x2, boolean toggled, Consumer<Boolean> consumer) { public static Label toggle(String x1, String x2, boolean toggled, Consumer<Boolean> consumer) {
final Label element = new Label(toggled ? x2 : x1); final Label element = new Label(toggled ? x2 : x1);
setCss(element, "toggle", "text-normal"); setCss(element, "toggle", "text-normal");
final BooleanProperty checked = new SimpleBooleanProperty(toggled); final BooleanProperty checked = new SimpleBooleanProperty(toggled);
element.setOnMouseClicked(_ -> { element.setOnMouseClicked(
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); _ -> {
checked.set(!checked.get()); new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
checked.set(!checked.get());
if (checked.get()) { if (checked.get()) {
element.setText(x1); element.setText(x1);
} else { } else {
element.setText(x2); element.setText(x2);
} }
consumer.accept(checked.get()); consumer.accept(checked.get());
}); });
return element; return element;
} }
public static Slider slider(int max, int initial, Consumer<Integer> consumer) { public static Slider slider(int max, int initial, Consumer<Integer> consumer) {
final Slider element = new Slider(0, max, initial); final Slider element = new Slider(0, max, initial);
setCss(element, "bg-slider-track"); setCss(element, "bg-slider-track");
element.setMinorTickCount(0); element.setMinorTickCount(0);
element.setMajorTickUnit(1); element.setMajorTickUnit(1);
element.setBlockIncrement(1); element.setBlockIncrement(1);
element.setSnapToTicks(true); element.setSnapToTicks(true);
element.setShowTickLabels(true); element.setShowTickLabels(true);
element.setOnMouseClicked(_ -> { element.setOnMouseClicked(
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); _ -> {
}); new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
});
element.valueProperty().addListener((_, _, newValue) -> { element.valueProperty()
consumer.accept(newValue.intValue()); .addListener(
}); (_, _, newValue) -> {
consumer.accept(newValue.intValue());
});
return element; return element;
} }
public static TextField input(String x, Consumer<String> consumer) { public static TextField input(String x, Consumer<String> consumer) {
final TextField element = new TextField(x); final TextField element = new TextField(x);
setCss(element, "input", "text-normal"); setCss(element, "input", "text-normal");
element.setOnMouseClicked(_ -> { element.setOnMouseClicked(
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); _ -> {
}); new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
});
element.textProperty().addListener((_, _, newValue) -> { element.textProperty()
consumer.accept(newValue); .addListener(
}); (_, _, newValue) -> {
consumer.accept(newValue);
});
return element; return element;
} }
public static <T> ChoiceBox<T> choiceBox(Consumer<T> consumer) { public static <T> ChoiceBox<T> choiceBox(Consumer<T> consumer) {
final ChoiceBox<T> element = new ChoiceBox<>(); final ChoiceBox<T> element = new ChoiceBox<>();
setCss(element, "choice-box", "text-normal"); setCss(element, "choice-box", "text-normal");
element.setOnMouseClicked(_ -> { element.setOnMouseClicked(
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); _ -> {
}); new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
});
element.valueProperty().addListener((_, _, newValue) -> { element.valueProperty()
consumer.accept(newValue); .addListener(
}); (_, _, newValue) -> {
consumer.accept(newValue);
});
return element; return element;
} }
public static Separator separator() { public static Separator separator() {
final Separator element = new Separator(Orientation.HORIZONTAL); final Separator element = new Separator(Orientation.HORIZONTAL);
setCss(element, "separator"); setCss(element, "separator");
return element; return element;
} }
} }

View File

@@ -3,17 +3,18 @@ package org.toop.app.layer;
import org.toop.app.App; import org.toop.app.App;
public abstract class Popup extends Layer { public abstract class Popup extends Layer {
protected Popup(boolean popOnBackground, String... backgroundStyles) { protected Popup(boolean popOnBackground, String... backgroundStyles) {
super(backgroundStyles); super(backgroundStyles);
if (popOnBackground) { if (popOnBackground) {
background.setOnMouseClicked(_ -> { background.setOnMouseClicked(
App.pop(); _ -> {
}); App.pop();
} });
} }
}
protected Popup(boolean popOnBackground) { protected Popup(boolean popOnBackground) {
this(popOnBackground, "bg-popup"); this(popOnBackground, "bg-popup");
} }
} }

View File

@@ -1,60 +1,59 @@
package org.toop.app.layer.containers; package org.toop.app.layer.containers;
import org.toop.app.layer.Container;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import org.toop.app.layer.Container;
public final class HorizontalContainer extends Container { public final class HorizontalContainer extends Container {
private final HBox container; private final HBox container;
public HorizontalContainer(int spacing, String... cssClasses) { public HorizontalContainer(int spacing, String... cssClasses) {
container = new HBox(spacing); container = new HBox(spacing);
container.getStyleClass().addAll(cssClasses); container.getStyleClass().addAll(cssClasses);
} }
public HorizontalContainer(int spacing) { public HorizontalContainer(int spacing) {
this(spacing, "container"); this(spacing, "container");
} }
@Override @Override
public Region getContainer() { public Region getContainer() {
return container; return container;
} }
@Override @Override
public void addNodes(Node... nodes) { public void addNodes(Node... nodes) {
container.getChildren().addAll(nodes); container.getChildren().addAll(nodes);
} }
@Override @Override
public void addContainer(Container container, boolean fill) { public void addContainer(Container container, boolean fill) {
if (fill) { if (fill) {
container.getContainer().setMinSize(0, 0); container.getContainer().setMinSize(0, 0);
container.getContainer().setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); container.getContainer().setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
HBox.setHgrow(container.getContainer(), Priority.ALWAYS); HBox.setHgrow(container.getContainer(), Priority.ALWAYS);
} else { } else {
container.getContainer().setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); container.getContainer().setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
} }
this.container.getChildren().add(container.getContainer()); this.container.getChildren().add(container.getContainer());
if (fill) { if (fill) {
balanceChildWidths(); balanceChildWidths();
} }
} }
private void balanceChildWidths() { private void balanceChildWidths() {
final ObservableList<Node> children = container.getChildren(); final ObservableList<Node> children = container.getChildren();
final double widthPerChild = container.getWidth() / children.size(); final double widthPerChild = container.getWidth() / children.size();
for (final Node child : children) { for (final Node child : children) {
if (child instanceof Region) { if (child instanceof Region) {
((Region) child).setPrefWidth(widthPerChild); ((Region) child).setPrefWidth(widthPerChild);
} }
} }
} }
} }

View File

@@ -1,60 +1,59 @@
package org.toop.app.layer.containers; package org.toop.app.layer.containers;
import org.toop.app.layer.Container;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import org.toop.app.layer.Container;
public final class VerticalContainer extends Container { public final class VerticalContainer extends Container {
private final VBox container; private final VBox container;
public VerticalContainer(int spacing, String... cssClasses) { public VerticalContainer(int spacing, String... cssClasses) {
container = new VBox(spacing); container = new VBox(spacing);
container.getStyleClass().addAll(cssClasses); container.getStyleClass().addAll(cssClasses);
} }
public VerticalContainer(int spacing) { public VerticalContainer(int spacing) {
this(spacing, "container"); this(spacing, "container");
} }
@Override @Override
public Region getContainer() { public Region getContainer() {
return container; return container;
} }
@Override @Override
public void addNodes(Node... nodes) { public void addNodes(Node... nodes) {
container.getChildren().addAll(nodes); container.getChildren().addAll(nodes);
} }
@Override @Override
public void addContainer(Container container, boolean fill) { public void addContainer(Container container, boolean fill) {
if (fill) { if (fill) {
container.getContainer().setMinSize(0, 0); container.getContainer().setMinSize(0, 0);
container.getContainer().setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); container.getContainer().setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
VBox.setVgrow(container.getContainer(), Priority.ALWAYS); VBox.setVgrow(container.getContainer(), Priority.ALWAYS);
} else { } else {
container.getContainer().setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); container.getContainer().setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
} }
this.container.getChildren().add(container.getContainer()); this.container.getChildren().add(container.getContainer());
if (fill) { if (fill) {
balanceChildHeights(); balanceChildHeights();
} }
} }
private void balanceChildHeights() { private void balanceChildHeights() {
final ObservableList<Node> children = container.getChildren(); final ObservableList<Node> children = container.getChildren();
final double heightPerChild = container.getHeight() / children.size(); final double heightPerChild = container.getHeight() / children.size();
for (final Node child : children) { for (final Node child : children) {
if (child instanceof Region) { if (child instanceof Region) {
((Region) child).setPrefHeight(heightPerChild); ((Region) child).setPrefHeight(heightPerChild);
} }
} }
} }
} }

View File

@@ -1,6 +1,14 @@
package org.toop.app.layer.layers; package org.toop.app.layer.layers;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.GameInformation; import org.toop.app.GameInformation;
import org.toop.app.layer.Container; import org.toop.app.layer.Container;
@@ -14,169 +22,214 @@ import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.networking.events.NetworkEvents; import org.toop.framework.networking.events.NetworkEvents;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
public final class ConnectedLayer extends Layer { public final class ConnectedLayer extends Layer {
private static Timer pollTimer = new Timer(); private static Timer pollTimer = new Timer();
private static class ChallengePopup extends Popup { private static class ChallengePopup extends Popup {
private final GameInformation information; private final GameInformation information;
private final String challenger; private final String challenger;
private final String game; private final String game;
private final long clientID; private final long clientID;
private final int challengeID; private final int challengeID;
public ChallengePopup(GameInformation information, String challenger, String game, long clientID, String challengeID) { public ChallengePopup(
super(false, "bg-popup"); GameInformation information,
String challenger,
String game,
long clientID,
String challengeID) {
super(false, "bg-popup");
this.information = information; this.information = information;
this.challenger = challenger; this.challenger = challenger;
this.game = game; this.game = game;
this.clientID = clientID; this.clientID = clientID;
this.challengeID = Integer.parseInt(challengeID.substring(18, challengeID.length() - 2)); this.challengeID =
Integer.parseInt(challengeID.substring(18, challengeID.length() - 2));
reload(); reload();
} }
@Override @Override
public void reload() { public void reload() {
popAll(); popAll();
final var challengeText = NodeBuilder.header(AppContext.getString("challengeText")); final var challengeText = NodeBuilder.header(AppContext.getString("challengeText"));
final var challengerNameText = NodeBuilder.header(challenger); final var challengerNameText = NodeBuilder.header(challenger);
final var gameText = NodeBuilder.text(AppContext.getString("gameIsText")); final var gameText = NodeBuilder.text(AppContext.getString("gameIsText"));
final var gameNameText = NodeBuilder.text(game); final var gameNameText = NodeBuilder.text(game);
final var acceptButton = NodeBuilder.button(AppContext.getString("accept"), () -> { final var acceptButton =
pollTimer.cancel(); NodeBuilder.button(
AppContext.getString("accept"),
() -> {
pollTimer.cancel();
new EventFlow().addPostEvent(new NetworkEvents.SendAcceptChallenge(clientID, challengeID)).postEvent(); new EventFlow()
App.activate(new TicTacToeLayer(information, clientID)); .addPostEvent(
}); new NetworkEvents.SendAcceptChallenge(
clientID, challengeID))
.postEvent();
App.activate(new TicTacToeLayer(information, clientID));
});
final var denyButton = NodeBuilder.button(AppContext.getString("deny"), () -> { final var denyButton =
App.pop(); NodeBuilder.button(
}); AppContext.getString("deny"),
() -> {
App.pop();
});
final Container controlContainer = new HorizontalContainer(30); final Container controlContainer = new HorizontalContainer(30);
controlContainer.addNodes(acceptButton, denyButton); controlContainer.addNodes(acceptButton, denyButton);
final Container mainContainer = new VerticalContainer(30); final Container mainContainer = new VerticalContainer(30);
mainContainer.addNodes(challengeText, challengerNameText); mainContainer.addNodes(challengeText, challengerNameText);
mainContainer.addNodes(gameText, gameNameText); mainContainer.addNodes(gameText, gameNameText);
mainContainer.addContainer(controlContainer, false); mainContainer.addContainer(controlContainer, false);
addContainer(mainContainer, Pos.CENTER, 0, 0, 30, 30); addContainer(mainContainer, Pos.CENTER, 0, 0, 30, 30);
} }
} }
GameInformation information; GameInformation information;
long clientId; long clientId;
String user; String user;
List<String> onlinePlayers = new CopyOnWriteArrayList<>(); List<String> onlinePlayers = new CopyOnWriteArrayList<>();
public ConnectedLayer(GameInformation information) { public ConnectedLayer(GameInformation information) {
super("bg-primary"); super("bg-primary");
this.information = information; this.information = information;
new EventFlow() new EventFlow()
.addPostEvent(NetworkEvents.StartClient.class, information.serverIP(), Integer.parseInt(information.serverPort())) .addPostEvent(
.onResponse(NetworkEvents.StartClientResponse.class, e -> { NetworkEvents.StartClient.class,
clientId = e.clientId(); information.serverIP(),
user = information.playerName()[0].replaceAll("\\s+", ""); Integer.parseInt(information.serverPort()))
.onResponse(
NetworkEvents.StartClientResponse.class,
e -> {
clientId = e.clientId();
user = information.playerName()[0].replaceAll("\\s+", "");
new EventFlow().addPostEvent(new NetworkEvents.SendLogin(this.clientId, this.user)).postEvent(); new EventFlow()
.addPostEvent(
new NetworkEvents.SendLogin(this.clientId, this.user))
.postEvent();
Thread popThread = new Thread(this::populatePlayerList); Thread popThread = new Thread(this::populatePlayerList);
popThread.setDaemon(false); popThread.setDaemon(false);
popThread.start(); popThread.start();
}).postEvent(); })
.postEvent();
new EventFlow().listen(this::handleReceivedChallenge); new EventFlow().listen(this::handleReceivedChallenge);
reload(); reload();
} }
private void populatePlayerList() { private void populatePlayerList() {
EventFlow sendGetPlayerList = new EventFlow().addPostEvent(new NetworkEvents.SendGetPlayerlist(this.clientId)); EventFlow sendGetPlayerList =
new EventFlow().listen(NetworkEvents.PlayerlistResponse.class, e -> { new EventFlow().addPostEvent(new NetworkEvents.SendGetPlayerlist(this.clientId));
if (e.clientId() == this.clientId) { new EventFlow()
List<String> playerList = new java.util.ArrayList<>(List.of(e.playerlist())); // TODO: Garbage, but works .listen(
playerList.removeIf(name -> name.equalsIgnoreCase(user)); NetworkEvents.PlayerlistResponse.class,
if (this.onlinePlayers != playerList) { e -> {
this.onlinePlayers.clear(); if (e.clientId() == this.clientId) {
this.onlinePlayers.addAll(playerList); List<String> playerList =
} new java.util.ArrayList<>(
} List.of(e.playerlist())); // TODO: Garbage,
}); // but works
playerList.removeIf(name -> name.equalsIgnoreCase(user));
if (this.onlinePlayers != playerList) {
this.onlinePlayers.clear();
this.onlinePlayers.addAll(playerList);
}
}
});
TimerTask task = new TimerTask() { TimerTask task =
public void run() { new TimerTask() {
sendGetPlayerList.postEvent(); public void run() {
Platform.runLater(() -> reload()); sendGetPlayerList.postEvent();
} Platform.runLater(() -> reload());
}; }
};
pollTimer.schedule(task, 0L, 5000L); // TODO: Block app exit, fix later pollTimer.schedule(task, 0L, 5000L); // TODO: Block app exit, fix later
} }
private void sendChallenge(String oppUsername, String gameType) { private void sendChallenge(String oppUsername, String gameType) {
final AtomicInteger challengeId = new AtomicInteger(-1); final AtomicInteger challengeId = new AtomicInteger(-1);
if (onlinePlayers.contains(oppUsername)) { if (onlinePlayers.contains(oppUsername)) {
new EventFlow().addPostEvent(new NetworkEvents.SendChallenge(this.clientId, oppUsername, gameType)) new EventFlow()
.listen(NetworkEvents.ChallengeResponse.class, e -> { .addPostEvent(
challengeId.set(Integer.parseInt(e.challengeId().substring(18, e.challengeId().length() - 2))); new NetworkEvents.SendChallenge(this.clientId, oppUsername, gameType))
}) .listen(
.listen(NetworkEvents.GameMatchResponse.class, e -> { NetworkEvents.ChallengeResponse.class,
if (e.clientId() == this.clientId) { e -> {
pollTimer.cancel(); challengeId.set(
App.activate(new TicTacToeLayer(information, this.clientId)); Integer.parseInt(
} e.challengeId()
}, false).postEvent(); .substring(
// ^ 18, e.challengeId().length() - 2)));
// | })
// | .listen(
// | NetworkEvents.GameMatchResponse.class,
} e -> {
} if (e.clientId() == this.clientId) {
pollTimer.cancel();
App.activate(new TicTacToeLayer(information, this.clientId));
}
},
false)
.postEvent();
// ^
// |
// |
// |
}
}
private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) { private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) {
App.push(new ChallengePopup(information, response.challengerName(), response.gameType(), clientId, response.challengeId())); App.push(
} new ChallengePopup(
information,
response.challengerName(),
response.gameType(),
clientId,
response.challengeId()));
}
@Override @Override
public void reload() { public void reload() {
popAll(); popAll();
ListView<Label> players = new ListView<>(); ListView<Label> players = new ListView<>();
for (int i = 0; i < onlinePlayers.size(); i++) { for (int i = 0; i < onlinePlayers.size(); i++) {
int finalI = i; int finalI = i;
players.getItems().add(NodeBuilder.button(onlinePlayers.get(i), () -> { players.getItems()
String clickedPlayer = onlinePlayers.get(finalI); .add(
sendChallenge(clickedPlayer, "tic-tac-toe"); NodeBuilder.button(
})); onlinePlayers.get(i),
} () -> {
String clickedPlayer = onlinePlayers.get(finalI);
sendChallenge(clickedPlayer, "tic-tac-toe");
}));
}
final Container playersContainer = new VerticalContainer(10); final Container playersContainer = new VerticalContainer(10);
playersContainer.addNodes(players); playersContainer.addNodes(players);
addContainer(playersContainer, Pos.CENTER, 0, 0, 0, 0); addContainer(playersContainer, Pos.CENTER, 0, 0, 0, 0);
} }
} }

View File

@@ -1,5 +1,10 @@
package org.toop.app.layer.layers; package org.toop.app.layer.layers;
import javafx.animation.PauseTransition;
import javafx.animation.TranslateTransition;
import javafx.geometry.Pos;
import javafx.scene.text.Text;
import javafx.util.Duration;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.layer.Container; import org.toop.app.layer.Container;
import org.toop.app.layer.NodeBuilder; import org.toop.app.layer.NodeBuilder;
@@ -8,65 +13,61 @@ import org.toop.app.layer.containers.HorizontalContainer;
import org.toop.app.layer.containers.VerticalContainer; import org.toop.app.layer.containers.VerticalContainer;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.animation.PauseTransition;
import javafx.animation.TranslateTransition;
import javafx.geometry.Pos;
import javafx.scene.text.Text;
import javafx.util.Duration;
public final class CreditsPopup extends Popup { public final class CreditsPopup extends Popup {
private final int lineHeight = 100; private final int lineHeight = 100;
public CreditsPopup() { public CreditsPopup() {
super(true, "bg-primary"); super(true, "bg-primary");
reload(); reload();
} }
@Override @Override
public void reload() { public void reload() {
popAll(); popAll();
final String[] credits = { final String[] credits = {
AppContext.getString("scrumMaster") + ": Stef", AppContext.getString("scrumMaster") + ": Stef",
AppContext.getString("productOwner") + ": Omar", AppContext.getString("productOwner") + ": Omar",
AppContext.getString("mergeCommander") + ": Bas", AppContext.getString("mergeCommander") + ": Bas",
AppContext.getString("localization") + ": Ticho", AppContext.getString("localization") + ": Ticho",
AppContext.getString("ai") + ": Michiel", AppContext.getString("ai") + ": Michiel",
AppContext.getString("developers") + ": Michiel, Bas, Stef, Omar, Ticho", AppContext.getString("developers") + ": Michiel, Bas, Stef, Omar, Ticho",
AppContext.getString("moralSupport") + ": Wesley", AppContext.getString("moralSupport") + ": Wesley",
AppContext.getString("opengl") + ": Omar" AppContext.getString("opengl") + ": Omar"
}; };
final Text[] creditsHeaders = new Text[credits.length]; final Text[] creditsHeaders = new Text[credits.length];
for (int i = 0; i < credits.length; i++) { for (int i = 0; i < credits.length; i++) {
creditsHeaders[i] = NodeBuilder.header(credits[i]); creditsHeaders[i] = NodeBuilder.header(credits[i]);
} }
final Container creditsContainer = new HorizontalContainer(0); final Container creditsContainer = new HorizontalContainer(0);
final Container animatedContainer = new VerticalContainer(lineHeight); final Container animatedContainer = new VerticalContainer(lineHeight);
creditsContainer.addContainer(animatedContainer, true); creditsContainer.addContainer(animatedContainer, true);
animatedContainer.addNodes(creditsHeaders); animatedContainer.addNodes(creditsHeaders);
addContainer(creditsContainer, Pos.CENTER, 0, 0, 50, 100); addContainer(creditsContainer, Pos.CENTER, 0, 0, 50, 100);
playCredits(animatedContainer, App.getHeight()); playCredits(animatedContainer, App.getHeight());
} }
private void playCredits(Container container, double sceneLength) { private void playCredits(Container container, double sceneLength) {
container.getContainer().setTranslateY(-sceneLength); container.getContainer().setTranslateY(-sceneLength);
final TranslateTransition scrollCredits = new TranslateTransition(Duration.seconds(20), container.getContainer()); final TranslateTransition scrollCredits =
scrollCredits.setFromY(-sceneLength - lineHeight); new TranslateTransition(Duration.seconds(20), container.getContainer());
scrollCredits.setToY(sceneLength + lineHeight); scrollCredits.setFromY(-sceneLength - lineHeight);
scrollCredits.setToY(sceneLength + lineHeight);
scrollCredits.setOnFinished(_ -> { scrollCredits.setOnFinished(
final PauseTransition pauseCredits = new PauseTransition(Duration.seconds(3)); _ -> {
pauseCredits.setOnFinished(_ -> playCredits(container, sceneLength)); final PauseTransition pauseCredits = new PauseTransition(Duration.seconds(3));
pauseCredits.play(); pauseCredits.setOnFinished(_ -> playCredits(container, sceneLength));
}); pauseCredits.play();
});
scrollCredits.play(); scrollCredits.play();
} }
} }

View File

@@ -1,5 +1,6 @@
package org.toop.app.layer.layers; package org.toop.app.layer.layers;
import javafx.geometry.Pos;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.layer.Container; import org.toop.app.layer.Container;
import org.toop.app.layer.Layer; import org.toop.app.layer.Layer;
@@ -7,45 +8,58 @@ import org.toop.app.layer.NodeBuilder;
import org.toop.app.layer.containers.VerticalContainer; import org.toop.app.layer.containers.VerticalContainer;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.geometry.Pos;
public final class MainLayer extends Layer { public final class MainLayer extends Layer {
public MainLayer() { public MainLayer() {
super("bg-primary"); super("bg-primary");
reload(); reload();
} }
@Override @Override
public void reload() { public void reload() {
popAll(); popAll();
final var tictactoeButton = NodeBuilder.button(AppContext.getString("tictactoe"), () -> { final var tictactoeButton =
App.activate(new MultiplayerLayer()); NodeBuilder.button(
}); AppContext.getString("tictactoe"),
() -> {
App.activate(new MultiplayerLayer());
});
final var othelloButton = NodeBuilder.button(AppContext.getString("othello"), () -> { final var othelloButton =
App.activate(new MultiplayerLayer()); NodeBuilder.button(
}); AppContext.getString("othello"),
() -> {
App.activate(new MultiplayerLayer());
});
final var creditsButton = NodeBuilder.button(AppContext.getString("credits"), () -> { final var creditsButton =
App.push(new CreditsPopup()); NodeBuilder.button(
}); AppContext.getString("credits"),
() -> {
App.push(new CreditsPopup());
});
final var optionsButton = NodeBuilder.button(AppContext.getString("options"), () -> { final var optionsButton =
App.push(new OptionsPopup()); NodeBuilder.button(
}); AppContext.getString("options"),
() -> {
App.push(new OptionsPopup());
});
final var quitButton = NodeBuilder.button(AppContext.getString("quit"), () -> { final var quitButton =
App.quitPopup(); NodeBuilder.button(
}); AppContext.getString("quit"),
() -> {
App.quitPopup();
});
final Container gamesContainer = new VerticalContainer(5); final Container gamesContainer = new VerticalContainer(5);
gamesContainer.addNodes(tictactoeButton, othelloButton); gamesContainer.addNodes(tictactoeButton, othelloButton);
final Container controlContainer = new VerticalContainer(5); final Container controlContainer = new VerticalContainer(5);
controlContainer.addNodes(creditsButton, optionsButton, quitButton); controlContainer.addNodes(creditsButton, optionsButton, quitButton);
addContainer(gamesContainer, Pos.TOP_LEFT, 2, 2, 20, 0); addContainer(gamesContainer, Pos.TOP_LEFT, 2, 2, 20, 0);
addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 20, 0); addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 20, 0);
} }
} }

View File

@@ -1,5 +1,7 @@
package org.toop.app.layer.layers; package org.toop.app.layer.layers;
import java.time.LocalDateTime;
import javafx.geometry.Pos;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.GameInformation; import org.toop.app.GameInformation;
import org.toop.app.layer.Container; import org.toop.app.layer.Container;
@@ -10,167 +12,230 @@ import org.toop.app.layer.containers.VerticalContainer;
import org.toop.app.layer.layers.game.TicTacToeLayer; import org.toop.app.layer.layers.game.TicTacToeLayer;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.geometry.Pos;
import java.time.LocalDateTime;
public final class MultiplayerLayer extends Layer { public final class MultiplayerLayer extends Layer {
private boolean isConnectionLocal = true; private boolean isConnectionLocal = true;
private boolean isPlayer1Human = true; private boolean isPlayer1Human = true;
private String player1Name = ""; private String player1Name = "";
private int computer1Difficulty = 0; private int computer1Difficulty = 0;
private int computer1ThinkTime = 0; private int computer1ThinkTime = 0;
private boolean isPlayer2Human = true; private boolean isPlayer2Human = true;
private String player2Name = ""; private String player2Name = "";
private int computer2Difficulty = 0; private int computer2Difficulty = 0;
private int computer2ThinkTime = 0; private int computer2ThinkTime = 0;
private String serverIP = ""; private String serverIP = "";
private String serverPort = ""; private String serverPort = "";
public MultiplayerLayer() { public MultiplayerLayer() {
super("bg-primary"); super("bg-primary");
reload(); reload();
} }
@Override @Override
public void reload() { public void reload() {
popAll(); popAll();
final Container player1Container = new VerticalContainer(20); final Container player1Container = new VerticalContainer(20);
final Container player2Container = new VerticalContainer(20); final Container player2Container = new VerticalContainer(20);
final var isPlayer1HumanToggle = NodeBuilder.toggle(AppContext.getString("human"), AppContext.getString("computer"), !isPlayer1Human, (computer) -> { final var isPlayer1HumanToggle =
isPlayer1Human = !computer; NodeBuilder.toggle(
reload(); AppContext.getString("human"),
}); AppContext.getString("computer"),
!isPlayer1Human,
(computer) -> {
isPlayer1Human = !computer;
reload();
});
player1Container.addNodes(isPlayer1HumanToggle); player1Container.addNodes(isPlayer1HumanToggle);
if (isPlayer1Human) { if (isPlayer1Human) {
final var playerNameText = NodeBuilder.text(AppContext.getString("playerName")); final var playerNameText = NodeBuilder.text(AppContext.getString("playerName"));
final var playerNameInput = NodeBuilder.input(player1Name, (name) -> { final var playerNameInput =
player1Name = name; NodeBuilder.input(
}); player1Name,
(name) -> {
player1Name = name;
});
player1Container.addNodes(playerNameText, playerNameInput); player1Container.addNodes(playerNameText, playerNameInput);
} else { } else {
player1Name = "Pism Bot V" + LocalDateTime.now().getSecond(); player1Name = "Pism Bot V" + LocalDateTime.now().getSecond();
final var computerNameText = NodeBuilder.text(player1Name); final var computerNameText = NodeBuilder.text(player1Name);
final var computerNameSeparator = NodeBuilder.separator(); final var computerNameSeparator = NodeBuilder.separator();
final var computerDifficultyText = NodeBuilder.text(AppContext.getString("computerDifficulty")); final var computerDifficultyText =
final var computerDifficultySeparator = NodeBuilder.separator(); NodeBuilder.text(AppContext.getString("computerDifficulty"));
final var computerDifficultySlider = NodeBuilder.slider(10, computer1Difficulty, (difficulty) -> final var computerDifficultySeparator = NodeBuilder.separator();
computer1Difficulty = difficulty); final var computerDifficultySlider =
NodeBuilder.slider(
10,
computer1Difficulty,
(difficulty) -> computer1Difficulty = difficulty);
final var computerThinkTimeText = NodeBuilder.text(AppContext.getString("computerThinkTime")); final var computerThinkTimeText =
final var computerThinkTimeSlider = NodeBuilder.slider(5, computer1ThinkTime, (thinkTime) -> NodeBuilder.text(AppContext.getString("computerThinkTime"));
computer1ThinkTime = thinkTime); final var computerThinkTimeSlider =
NodeBuilder.slider(
5, computer1ThinkTime, (thinkTime) -> computer1ThinkTime = thinkTime);
player1Container.addNodes(computerNameText, computerNameSeparator, player1Container.addNodes(
computerDifficultyText, computerDifficultySlider, computerDifficultySeparator, computerNameText,
computerThinkTimeText, computerThinkTimeSlider); computerNameSeparator,
} computerDifficultyText,
computerDifficultySlider,
computerDifficultySeparator,
computerThinkTimeText,
computerThinkTimeSlider);
}
if (isConnectionLocal) { if (isConnectionLocal) {
final var isPlayer2HumanToggle = NodeBuilder.toggle(AppContext.getString("human"), AppContext.getString("computer"), !isPlayer2Human, (computer) -> { final var isPlayer2HumanToggle =
isPlayer2Human = !computer; NodeBuilder.toggle(
reload(); AppContext.getString("human"),
}); AppContext.getString("computer"),
!isPlayer2Human,
(computer) -> {
isPlayer2Human = !computer;
reload();
});
player2Container.addNodes(isPlayer2HumanToggle); player2Container.addNodes(isPlayer2HumanToggle);
if (isPlayer2Human) { if (isPlayer2Human) {
final var playerNameText = NodeBuilder.text(AppContext.getString("playerName")); final var playerNameText = NodeBuilder.text(AppContext.getString("playerName"));
final var playerNameInput = NodeBuilder.input(player2Name, (name) -> { final var playerNameInput =
player2Name = name; NodeBuilder.input(
}); player2Name,
(name) -> {
player2Name = name;
});
player2Container.addNodes(playerNameText, playerNameInput); player2Container.addNodes(playerNameText, playerNameInput);
} else { } else {
player2Name = "Pism Bot V" + LocalDateTime.now().getSecond(); player2Name = "Pism Bot V" + LocalDateTime.now().getSecond();
final var computerNameText = NodeBuilder.text(player2Name); final var computerNameText = NodeBuilder.text(player2Name);
final var computerNameSeparator = NodeBuilder.separator(); final var computerNameSeparator = NodeBuilder.separator();
final var computerDifficultyText = NodeBuilder.text(AppContext.getString("computerDifficulty")); final var computerDifficultyText =
final var computerDifficultySeparator = NodeBuilder.separator(); NodeBuilder.text(AppContext.getString("computerDifficulty"));
final var computerDifficultySlider = NodeBuilder.slider(10, computer2Difficulty, (difficulty) -> final var computerDifficultySeparator = NodeBuilder.separator();
computer2Difficulty = difficulty); final var computerDifficultySlider =
NodeBuilder.slider(
10,
computer2Difficulty,
(difficulty) -> computer2Difficulty = difficulty);
final var computerThinkTimeText = NodeBuilder.text(AppContext.getString("computerThinkTime")); final var computerThinkTimeText =
final var computerThinkTimeSlider = NodeBuilder.slider(5, computer2ThinkTime, (thinkTime) -> NodeBuilder.text(AppContext.getString("computerThinkTime"));
computer2ThinkTime = thinkTime); final var computerThinkTimeSlider =
NodeBuilder.slider(
5,
computer2ThinkTime,
(thinkTime) -> computer2ThinkTime = thinkTime);
player2Container.addNodes(computerNameText, computerNameSeparator, player2Container.addNodes(
computerDifficultyText, computerDifficultySlider, computerDifficultySeparator, computerNameText,
computerThinkTimeText, computerThinkTimeSlider); computerNameSeparator,
} computerDifficultyText,
} else { computerDifficultySlider,
final var serverIPText = NodeBuilder.text(AppContext.getString("serverIP")); computerDifficultySeparator,
final var serverIPSeparator = NodeBuilder.separator(); computerThinkTimeText,
final var serverIPInput = NodeBuilder.input(serverIP, (ip) -> { computerThinkTimeSlider);
serverIP = ip; }
}); } else {
final var serverIPText = NodeBuilder.text(AppContext.getString("serverIP"));
final var serverIPSeparator = NodeBuilder.separator();
final var serverIPInput =
NodeBuilder.input(
serverIP,
(ip) -> {
serverIP = ip;
});
final var serverPortText = NodeBuilder.text(AppContext.getString("serverPort")); final var serverPortText = NodeBuilder.text(AppContext.getString("serverPort"));
final var serverPortInput = NodeBuilder.input(serverPort, (port) -> { final var serverPortInput =
serverPort = port; NodeBuilder.input(
}); serverPort,
(port) -> {
serverPort = port;
});
player2Container.addNodes(serverIPText, serverIPInput, serverIPSeparator, player2Container.addNodes(
serverPortText, serverPortInput); serverIPText,
} serverIPInput,
serverIPSeparator,
serverPortText,
serverPortInput);
}
final var versusText = NodeBuilder.header("VS"); final var versusText = NodeBuilder.header("VS");
final var connectionTypeText = NodeBuilder.text(AppContext.getString("connectionType") + ":"); final var connectionTypeText =
final var connectionTypeToggle = NodeBuilder.toggle(AppContext.getString("local"), AppContext.getString("server"), !isConnectionLocal, (server) -> { NodeBuilder.text(AppContext.getString("connectionType") + ":");
isConnectionLocal = !server; final var connectionTypeToggle =
reload(); NodeBuilder.toggle(
}); AppContext.getString("local"),
AppContext.getString("server"),
!isConnectionLocal,
(server) -> {
isConnectionLocal = !server;
reload();
});
final var playButton = NodeBuilder.button(isConnectionLocal ? AppContext.getString("start") : AppContext.getString("connect"), () -> { final var playButton =
final var information = new GameInformation( NodeBuilder.button(
new String[]{player1Name, player2Name}, isConnectionLocal
new boolean[]{isPlayer1Human, isPlayer2Human}, ? AppContext.getString("start")
new int[]{computer1Difficulty, computer2Difficulty}, : AppContext.getString("connect"),
new int[]{computer1ThinkTime, computer2ThinkTime}, () -> {
isConnectionLocal, serverIP, serverPort); final var information =
new GameInformation(
new String[] {player1Name, player2Name},
new boolean[] {isPlayer1Human, isPlayer2Human},
new int[] {computer1Difficulty, computer2Difficulty},
new int[] {computer1ThinkTime, computer2ThinkTime},
isConnectionLocal,
serverIP,
serverPort);
if (isConnectionLocal) { if (isConnectionLocal) {
App.activate(new TicTacToeLayer(information)); App.activate(new TicTacToeLayer(information));
} else { } else {
App.activate(new ConnectedLayer(information)); App.activate(new ConnectedLayer(information));
} }
}); });
final Container mainContainer = new VerticalContainer(10); final Container mainContainer = new VerticalContainer(10);
final Container playersContainer = new HorizontalContainer(5); final Container playersContainer = new HorizontalContainer(5);
final Container connectionTypeContainer = new HorizontalContainer(10); final Container connectionTypeContainer = new HorizontalContainer(10);
mainContainer.addContainer(playersContainer, true); mainContainer.addContainer(playersContainer, true);
mainContainer.addContainer(connectionTypeContainer, false); mainContainer.addContainer(connectionTypeContainer, false);
mainContainer.addNodes(playButton); mainContainer.addNodes(playButton);
connectionTypeContainer.addNodes(connectionTypeText, connectionTypeToggle); connectionTypeContainer.addNodes(connectionTypeText, connectionTypeToggle);
playersContainer.addContainer(player1Container, true); playersContainer.addContainer(player1Container, true);
playersContainer.addNodes(versusText); playersContainer.addNodes(versusText);
playersContainer.addContainer(player2Container, true); playersContainer.addContainer(player2Container, true);
final var backButton = NodeBuilder.button(AppContext.getString("back"), () -> { final var backButton =
App.activate(new MainLayer()); NodeBuilder.button(
}); AppContext.getString("back"),
() -> {
App.activate(new MainLayer());
});
final Container controlContainer = new VerticalContainer(0); final Container controlContainer = new VerticalContainer(0);
controlContainer.addNodes(backButton); controlContainer.addNodes(backButton);
addContainer(mainContainer, Pos.CENTER, 0, 0, 75, 75); addContainer(mainContainer, Pos.CENTER, 0, 0, 75, 75);
addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 0, 0); addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 0, 0);
} }
} }

View File

@@ -1,5 +1,10 @@
package org.toop.app.layer.layers; package org.toop.app.layer.layers;
import java.util.Locale;
import javafx.geometry.Pos;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.layer.Container; import org.toop.app.layer.Container;
import org.toop.app.layer.NodeBuilder; import org.toop.app.layer.NodeBuilder;
@@ -11,32 +16,25 @@ import org.toop.framework.eventbus.EventFlow;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import org.toop.local.AppSettings; import org.toop.local.AppSettings;
import javafx.geometry.Pos;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import java.util.Locale;
public final class OptionsPopup extends Popup { public final class OptionsPopup extends Popup {
AppSettings appSettings = new AppSettings(); AppSettings appSettings = new AppSettings();
SettingsAsset settings = appSettings.getPath(); SettingsAsset settings = appSettings.getPath();
private boolean isWindowed = !(settings.getFullscreen()); private boolean isWindowed = !(settings.getFullscreen());
public OptionsPopup() { public OptionsPopup() {
super(true, "bg-primary"); super(true, "bg-primary");
reload(); reload();
} }
@Override @Override
public void reload() { public void reload() {
popAll(); popAll();
final var languageHeader = NodeBuilder.header(AppContext.getString("language")); final var languageHeader = NodeBuilder.header(AppContext.getString("language"));
final var languageSeparator = NodeBuilder.separator(); final var languageSeparator = NodeBuilder.separator();
final var volumeHeader = NodeBuilder.header(AppContext.getString("volume")); final var volumeHeader = NodeBuilder.header(AppContext.getString("volume"));
final var volumeSeparator = NodeBuilder.separator(); final var volumeSeparator = NodeBuilder.separator();
final var fxVolumeHeader = NodeBuilder.header(AppContext.getString("effectsVolume")); final var fxVolumeHeader = NodeBuilder.header(AppContext.getString("effectsVolume"));
final var fxVolumeSeparator = NodeBuilder.separator(); final var fxVolumeSeparator = NodeBuilder.separator();
@@ -44,151 +42,181 @@ public final class OptionsPopup extends Popup {
final var musicVolumeHeader = NodeBuilder.header(AppContext.getString("musicVolume")); final var musicVolumeHeader = NodeBuilder.header(AppContext.getString("musicVolume"));
final var musicVolumeSeparator = NodeBuilder.separator(); final var musicVolumeSeparator = NodeBuilder.separator();
final var themeHeader = NodeBuilder.header(AppContext.getString("theme")); final var themeHeader = NodeBuilder.header(AppContext.getString("theme"));
final var themeSeparator = NodeBuilder.separator(); final var themeSeparator = NodeBuilder.separator();
final var layoutSizeHeader = NodeBuilder.header(AppContext.getString("layoutSize")); final var layoutSizeHeader = NodeBuilder.header(AppContext.getString("layoutSize"));
final var layoutSizeSeparator = NodeBuilder.separator(); final var layoutSizeSeparator = NodeBuilder.separator();
final var optionsContainer = new VerticalContainer(5); final var optionsContainer = new VerticalContainer(5);
optionsContainer.addNodes(languageHeader, languageChoiceBox(), languageSeparator); optionsContainer.addNodes(languageHeader, languageChoiceBox(), languageSeparator);
optionsContainer.addNodes(volumeHeader, volumeSlider(), volumeSeparator); optionsContainer.addNodes(volumeHeader, volumeSlider(), volumeSeparator);
optionsContainer.addNodes(fxVolumeHeader, fxVolumeSlider(), fxVolumeSeparator); optionsContainer.addNodes(fxVolumeHeader, fxVolumeSlider(), fxVolumeSeparator);
optionsContainer.addNodes(musicVolumeHeader, musicVolumeSlider(), musicVolumeSeparator); optionsContainer.addNodes(musicVolumeHeader, musicVolumeSlider(), musicVolumeSeparator);
optionsContainer.addNodes(themeHeader, themeChoiceBox(), themeSeparator); optionsContainer.addNodes(themeHeader, themeChoiceBox(), themeSeparator);
optionsContainer.addNodes(layoutSizeHeader, layoutSizeChoiceBox(), layoutSizeSeparator); optionsContainer.addNodes(layoutSizeHeader, layoutSizeChoiceBox(), layoutSizeSeparator);
optionsContainer.addNodes(fullscreenToggle()); optionsContainer.addNodes(fullscreenToggle());
final Container mainContainer = new VerticalContainer(50, ""); final Container mainContainer = new VerticalContainer(50, "");
mainContainer.addContainer(optionsContainer, true); mainContainer.addContainer(optionsContainer, true);
final var backButton = NodeBuilder.button(AppContext.getString("back"), () -> { final var backButton =
App.pop(); NodeBuilder.button(
}); AppContext.getString("back"),
() -> {
App.pop();
});
final Container controlContainer = new VerticalContainer(5); final Container controlContainer = new VerticalContainer(5);
controlContainer.addNodes(backButton); controlContainer.addNodes(backButton);
addContainer(mainContainer, Pos.CENTER, 0, 0, 0, 0); addContainer(mainContainer, Pos.CENTER, 0, 0, 0, 0);
addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 0, 0); addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 0, 0);
} }
private ChoiceBox<Locale> languageChoiceBox() { private ChoiceBox<Locale> languageChoiceBox() {
assert AppContext.getLocalization() != null; assert AppContext.getLocalization() != null;
final ChoiceBox<Locale> languageChoiceBox = NodeBuilder.choiceBox((locale) -> { final ChoiceBox<Locale> languageChoiceBox =
if (locale == AppContext.getLocale()) { NodeBuilder.choiceBox(
return; (locale) -> {
} if (locale == AppContext.getLocale()) {
return;
}
settings.setLocale(locale.toString()); settings.setLocale(locale.toString());
AppContext.setLocale(locale); AppContext.setLocale(locale);
App.reloadAll(); App.reloadAll();
}); });
languageChoiceBox.setConverter(new javafx.util.StringConverter<>() { languageChoiceBox.setConverter(
@Override new javafx.util.StringConverter<>() {
public String toString(Locale locale) { @Override
return AppContext.getString(locale.getDisplayName().toLowerCase()); public String toString(Locale locale) {
} return AppContext.getString(locale.getDisplayName().toLowerCase());
}
@Override @Override
public Locale fromString(String string) { public Locale fromString(String string) {
return null; return null;
} }
}); });
languageChoiceBox.getItems().addAll(AppContext.getLocalization().getAvailableLocales()); languageChoiceBox.getItems().addAll(AppContext.getLocalization().getAvailableLocales());
languageChoiceBox.setValue(AppContext.getLocale()); languageChoiceBox.setValue(AppContext.getLocale());
return languageChoiceBox; return languageChoiceBox;
} }
private Slider volumeSlider() { private Slider volumeSlider() {
return NodeBuilder.slider(100, settings.getVolume(), (volume) -> { return NodeBuilder.slider(
settings.setVolume(volume); 100,
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(volume.doubleValue())).asyncPostEvent(); settings.getVolume(),
}); (volume) -> {
} settings.setVolume(volume);
new EventFlow()
.addPostEvent(new AudioEvents.ChangeVolume(volume.doubleValue()))
.asyncPostEvent();
});
}
private Slider fxVolumeSlider() { private Slider fxVolumeSlider() {
return NodeBuilder.slider(100, settings.getFxVolume(), (volume) -> { return NodeBuilder.slider(
settings.setFxVolume(volume); 100,
new EventFlow().addPostEvent(new AudioEvents.ChangeFxVolume(volume.doubleValue())).asyncPostEvent(); settings.getFxVolume(),
}); (volume) -> {
settings.setFxVolume(volume);
new EventFlow()
.addPostEvent(new AudioEvents.ChangeFxVolume(volume.doubleValue()))
.asyncPostEvent();
});
} }
private Slider musicVolumeSlider() { private Slider musicVolumeSlider() {
return NodeBuilder.slider(100, settings.getMusicVolume(), (volume) -> { return NodeBuilder.slider(
settings.setMusicVolume(volume); 100,
new EventFlow().addPostEvent(new AudioEvents.ChangeMusicVolume(volume.doubleValue())).asyncPostEvent(); settings.getMusicVolume(),
}); (volume) -> {
settings.setMusicVolume(volume);
new EventFlow()
.addPostEvent(new AudioEvents.ChangeMusicVolume(volume.doubleValue()))
.asyncPostEvent();
});
} }
private Label fullscreenToggle() {
return NodeBuilder.toggle(
AppContext.getString("windowed"),
AppContext.getString("fullscreen"),
!isWindowed,
(fullscreen) -> {
isWindowed = !fullscreen;
private Label fullscreenToggle() { settings.setFullscreen(fullscreen);
return NodeBuilder.toggle(AppContext.getString("windowed"), AppContext.getString("fullscreen"), !isWindowed, (fullscreen) -> { App.setFullscreen(fullscreen);
isWindowed = !fullscreen; });
}
settings.setFullscreen(fullscreen); private ChoiceBox<String> themeChoiceBox() {
App.setFullscreen(fullscreen); final ChoiceBox<String> themeChoiceBox =
}); NodeBuilder.choiceBox(
} (theme) -> {
if (theme.equalsIgnoreCase(settings.getTheme())) {
return;
}
private ChoiceBox<String> themeChoiceBox() { settings.setTheme(theme);
final ChoiceBox<String> themeChoiceBox = NodeBuilder.choiceBox((theme) -> { App.setStyle(theme, settings.getLayoutSize());
if (theme.equalsIgnoreCase(settings.getTheme())) { });
return;
}
settings.setTheme(theme); themeChoiceBox.setConverter(
App.setStyle(theme, settings.getLayoutSize()); new javafx.util.StringConverter<>() {
}); @Override
public String toString(String theme) {
return AppContext.getString(theme);
}
themeChoiceBox.setConverter(new javafx.util.StringConverter<>() { @Override
@Override public String fromString(String string) {
public String toString(String theme) { return null;
return AppContext.getString(theme); }
} });
@Override themeChoiceBox.getItems().addAll("dark", "light", "dark-hc", "light-hc");
public String fromString(String string) { themeChoiceBox.setValue(settings.getTheme());
return null;
}
});
themeChoiceBox.getItems().addAll("dark", "light", "dark-hc", "light-hc"); return themeChoiceBox;
themeChoiceBox.setValue(settings.getTheme()); }
return themeChoiceBox; private ChoiceBox<String> layoutSizeChoiceBox() {
} final ChoiceBox<String> layoutSizeChoiceBox =
NodeBuilder.choiceBox(
(layoutSize) -> {
if (layoutSize.equalsIgnoreCase(settings.getLayoutSize())) {
return;
}
private ChoiceBox<String> layoutSizeChoiceBox() { settings.setLayoutSize(layoutSize);
final ChoiceBox<String> layoutSizeChoiceBox = NodeBuilder.choiceBox((layoutSize) -> { App.setStyle(settings.getTheme(), layoutSize);
if (layoutSize.equalsIgnoreCase(settings.getLayoutSize())) { });
return;
}
settings.setLayoutSize(layoutSize); layoutSizeChoiceBox.setConverter(
App.setStyle(settings.getTheme(), layoutSize); new javafx.util.StringConverter<>() {
}); @Override
public String toString(String layoutSize) {
return AppContext.getString(layoutSize);
}
layoutSizeChoiceBox.setConverter(new javafx.util.StringConverter<>() { @Override
@Override public String fromString(String string) {
public String toString(String layoutSize) { return null;
return AppContext.getString(layoutSize); }
} });
@Override layoutSizeChoiceBox.getItems().addAll("small", "medium", "large");
public String fromString(String string) { layoutSizeChoiceBox.setValue(settings.getLayoutSize());
return null;
}
});
layoutSizeChoiceBox.getItems().addAll("small", "medium", "large"); return layoutSizeChoiceBox;
layoutSizeChoiceBox.setValue(settings.getLayoutSize()); }
}
return layoutSizeChoiceBox;
}
}

View File

@@ -1,5 +1,6 @@
package org.toop.app.layer.layers; package org.toop.app.layer.layers;
import javafx.geometry.Pos;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.layer.Container; import org.toop.app.layer.Container;
import org.toop.app.layer.NodeBuilder; import org.toop.app.layer.NodeBuilder;
@@ -8,35 +9,39 @@ import org.toop.app.layer.containers.HorizontalContainer;
import org.toop.app.layer.containers.VerticalContainer; import org.toop.app.layer.containers.VerticalContainer;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.geometry.Pos;
public final class QuitPopup extends Popup { public final class QuitPopup extends Popup {
public QuitPopup() { public QuitPopup() {
super(true); super(true);
reload(); reload();
} }
@Override @Override
public void reload() { public void reload() {
popAll(); popAll();
final var sureText = NodeBuilder.header(AppContext.getString("quitSure")); final var sureText = NodeBuilder.header(AppContext.getString("quitSure"));
final var yesButton = NodeBuilder.button(AppContext.getString("yes"), () -> { final var yesButton =
App.quit(); NodeBuilder.button(
}); AppContext.getString("yes"),
() -> {
App.quit();
});
final var noButton = NodeBuilder.button(AppContext.getString("no"), () -> { final var noButton =
App.pop(); NodeBuilder.button(
}); AppContext.getString("no"),
() -> {
App.pop();
});
final Container controlContainer = new HorizontalContainer(30); final Container controlContainer = new HorizontalContainer(30);
controlContainer.addNodes(yesButton, noButton); controlContainer.addNodes(yesButton, noButton);
final Container mainContainer = new VerticalContainer(30); final Container mainContainer = new VerticalContainer(30);
mainContainer.addNodes(sureText); mainContainer.addNodes(sureText);
mainContainer.addContainer(controlContainer, false); mainContainer.addContainer(controlContainer, false);
addContainer(mainContainer, Pos.CENTER, 0, 0, 30, 30); addContainer(mainContainer, Pos.CENTER, 0, 0, 30, 30);
} }
} }

View File

@@ -1,5 +1,6 @@
package org.toop.app.layer.layers.game; package org.toop.app.layer.layers.game;
import javafx.geometry.Pos;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.layer.Container; import org.toop.app.layer.Container;
import org.toop.app.layer.NodeBuilder; import org.toop.app.layer.NodeBuilder;
@@ -8,45 +9,47 @@ import org.toop.app.layer.containers.VerticalContainer;
import org.toop.app.layer.layers.MainLayer; import org.toop.app.layer.layers.MainLayer;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.geometry.Pos;
public class GameFinishedPopup extends Popup { public class GameFinishedPopup extends Popup {
private final boolean isDraw; private final boolean isDraw;
private final String winner; private final String winner;
public GameFinishedPopup(boolean isDraw, String winner) { public GameFinishedPopup(boolean isDraw, String winner) {
super(true, "bg-popup"); super(true, "bg-popup");
this.isDraw = isDraw; this.isDraw = isDraw;
this.winner = winner; this.winner = winner;
reload(); reload();
} }
@Override @Override
public void reload() { public void reload() {
popAll(); popAll();
final Container mainContainer = new VerticalContainer(30); final Container mainContainer = new VerticalContainer(30);
if (isDraw) { if (isDraw) {
final var drawHeader = NodeBuilder.header(AppContext.getString("drawText")); final var drawHeader = NodeBuilder.header(AppContext.getString("drawText"));
final var goodGameText = NodeBuilder.text(AppContext.getString("goodGameText")); final var goodGameText = NodeBuilder.text(AppContext.getString("goodGameText"));
mainContainer.addNodes(drawHeader, goodGameText); mainContainer.addNodes(drawHeader, goodGameText);
} else { } else {
final var winHeader = NodeBuilder.header(AppContext.getString("congratulations") + ": " + winner); final var winHeader =
final var goodGameText = NodeBuilder.text(AppContext.getString("goodGameText")); NodeBuilder.header(AppContext.getString("congratulations") + ": " + winner);
final var goodGameText = NodeBuilder.text(AppContext.getString("goodGameText"));
mainContainer.addNodes(winHeader, goodGameText); mainContainer.addNodes(winHeader, goodGameText);
} }
final var backToMainMenuButton = NodeBuilder.button(AppContext.getString("backToMainMenu"), () -> { final var backToMainMenuButton =
App.activate(new MainLayer()); NodeBuilder.button(
}); AppContext.getString("backToMainMenu"),
() -> {
App.activate(new MainLayer());
});
mainContainer.addNodes(backToMainMenuButton); mainContainer.addNodes(backToMainMenuButton);
addContainer(mainContainer, Pos.CENTER, 0, 0, 30, 30); addContainer(mainContainer, Pos.CENTER, 0, 0, 30, 30);
} }
} }

View File

@@ -1,5 +1,11 @@
package org.toop.app.layer.layers.game; package org.toop.app.layer.layers.game;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javafx.geometry.Pos;
import javafx.scene.paint.Color;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.GameInformation; import org.toop.app.GameInformation;
@@ -17,280 +23,307 @@ import org.toop.game.tictactoe.TicTacToe;
import org.toop.game.tictactoe.TicTacToeAI; import org.toop.game.tictactoe.TicTacToeAI;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.paint.Color;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public final class TicTacToeLayer extends Layer { public final class TicTacToeLayer extends Layer {
private TicTacToeCanvas canvas; private TicTacToeCanvas canvas;
private AtomicReference<TicTacToe> ticTacToe; private AtomicReference<TicTacToe> ticTacToe;
private TicTacToeAI ticTacToeAI; private TicTacToeAI ticTacToeAI;
private GameInformation information; private GameInformation information;
private final Text currentPlayerNameText; private final Text currentPlayerNameText;
private final Text currentPlayerMoveText; private final Text currentPlayerMoveText;
private final BlockingQueue<Game.Move> playerMoveQueue = new LinkedBlockingQueue<>(); private final BlockingQueue<Game.Move> playerMoveQueue = new LinkedBlockingQueue<>();
// Todo: set these from the server // Todo: set these from the server
private char currentPlayerMove = Game.EMPTY; private char currentPlayerMove = Game.EMPTY;
private String player2Name = ""; private String player2Name = "";
final AtomicBoolean firstPlayerIsMe = new AtomicBoolean(true); final AtomicBoolean firstPlayerIsMe = new AtomicBoolean(true);
public TicTacToeLayer(GameInformation information) { public TicTacToeLayer(GameInformation information) {
super("bg-primary"); super("bg-primary");
canvas = new TicTacToeCanvas(Color.LIME, (App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75, (cell) -> { canvas =
try { new TicTacToeCanvas(
if (information.isConnectionLocal()) { Color.LIME,
if (ticTacToe.get().getCurrentTurn() == 0) { (App.getHeight() / 100) * 75,
playerMoveQueue.put(new Game.Move(cell, 'X')); (App.getHeight() / 100) * 75,
} else { (cell) -> {
playerMoveQueue.put(new Game.Move(cell, 'O')); try {
} if (information.isConnectionLocal()) {
} else { if (ticTacToe.get().getCurrentTurn() == 0) {
if (information.isPlayerHuman()[0] && currentPlayerMove != Game.EMPTY) { playerMoveQueue.put(new Game.Move(cell, 'X'));
playerMoveQueue.put(new Game.Move(cell, firstPlayerIsMe.get()? 'X' : 'O')); } else {
} playerMoveQueue.put(new Game.Move(cell, 'O'));
} }
} catch (InterruptedException _) {} } else {
}); if (information.isPlayerHuman()[0]
&& currentPlayerMove != Game.EMPTY) {
playerMoveQueue.put(
new Game.Move(
cell, firstPlayerIsMe.get() ? 'X' : 'O'));
}
}
} catch (InterruptedException _) {
}
});
ticTacToe = new AtomicReference<>(new TicTacToe()); ticTacToe = new AtomicReference<>(new TicTacToe());
ticTacToeAI = new TicTacToeAI(); ticTacToeAI = new TicTacToeAI();
this.information = information; this.information = information;
if (information.isConnectionLocal()) { if (information.isConnectionLocal()) {
new Thread(this::localGameThread).start(); new Thread(this::localGameThread).start();
} }
currentPlayerNameText = NodeBuilder.header(""); currentPlayerNameText = NodeBuilder.header("");
currentPlayerMoveText = NodeBuilder.header(""); currentPlayerMoveText = NodeBuilder.header("");
reload(); reload();
} }
public TicTacToeLayer(GameInformation information, long clientID) { public TicTacToeLayer(GameInformation information, long clientID) {
this(information); this(information);
Thread a = new Thread(this::serverGameThread); Thread a = new Thread(this::serverGameThread);
a.setDaemon(false); a.setDaemon(false);
a.start(); a.start();
reload(); reload();
} }
@Override @Override
public void reload() { public void reload() {
popAll(); popAll();
canvas.resize((App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75); canvas.resize((App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75);
for (int i = 0; i < ticTacToe.get().board.length; i++) { for (int i = 0; i < ticTacToe.get().board.length; i++) {
final char value = ticTacToe.get().board[i]; final char value = ticTacToe.get().board[i];
if (value == 'X') { if (value == 'X') {
canvas.drawX(Color.RED, i); canvas.drawX(Color.RED, i);
} else if (value == 'O') { } else if (value == 'O') {
canvas.drawO(Color.BLUE, i); canvas.drawO(Color.BLUE, i);
} }
} }
final var backButton = NodeBuilder.button(AppContext.getString("back"), () -> { final var backButton =
App.activate(new MainLayer()); NodeBuilder.button(
}); AppContext.getString("back"),
() -> {
App.activate(new MainLayer());
});
final Container controlContainer = new VerticalContainer(5); final Container controlContainer = new VerticalContainer(5);
controlContainer.addNodes(backButton); controlContainer.addNodes(backButton);
final Container informationContainer = new HorizontalContainer(15); final Container informationContainer = new HorizontalContainer(15);
informationContainer.addNodes(currentPlayerNameText, currentPlayerMoveText); informationContainer.addNodes(currentPlayerNameText, currentPlayerMoveText);
addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 0, 0); addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 0, 0);
addContainer(informationContainer, Pos.TOP_LEFT, 2, 2, 0, 0); addContainer(informationContainer, Pos.TOP_LEFT, 2, 2, 0, 0);
addGameCanvas(canvas, Pos.CENTER, 0, 0); addGameCanvas(canvas, Pos.CENTER, 0, 0);
} }
private int compurterDifficultyToDepth(int maxDifficulty, int difficulty) { private int compurterDifficultyToDepth(int maxDifficulty, int difficulty) {
return (int) (((float) maxDifficulty / difficulty) * 9); return (int) (((float) maxDifficulty / difficulty) * 9);
} }
private void localGameThread() { private void localGameThread() {
boolean running = true; boolean running = true;
while (running) { while (running) {
final int currentPlayer = ticTacToe.get().getCurrentTurn(); final int currentPlayer = ticTacToe.get().getCurrentTurn();
currentPlayerNameText.setText(information.playerName()[currentPlayer]); currentPlayerNameText.setText(information.playerName()[currentPlayer]);
currentPlayerMoveText.setText(ticTacToe.get().getCurrentTurn() == 0? "X" : "O"); currentPlayerMoveText.setText(ticTacToe.get().getCurrentTurn() == 0 ? "X" : "O");
Game.Move move = null; Game.Move move = null;
if (information.isPlayerHuman()[currentPlayer]) { if (information.isPlayerHuman()[currentPlayer]) {
try { try {
final Game.Move wants = playerMoveQueue.take(); final Game.Move wants = playerMoveQueue.take();
final Game.Move[] legalMoves = ticTacToe.get().getLegalMoves(); final Game.Move[] legalMoves = ticTacToe.get().getLegalMoves();
for (final Game.Move legalMove : legalMoves) { for (final Game.Move legalMove : legalMoves) {
if (legalMove.position() == wants.position() && legalMove.value() == wants.value()) { if (legalMove.position() == wants.position()
move = wants; && legalMove.value() == wants.value()) {
} move = wants;
} }
} catch (InterruptedException _) {} }
} else { } catch (InterruptedException _) {
final long start = System.currentTimeMillis(); }
} else {
final long start = System.currentTimeMillis();
move = ticTacToeAI.findBestMove(ticTacToe.get(), compurterDifficultyToDepth(10, move =
information.computerDifficulty()[currentPlayer])); ticTacToeAI.findBestMove(
ticTacToe.get(),
compurterDifficultyToDepth(
10, information.computerDifficulty()[currentPlayer]));
if (information.computerThinkTime()[currentPlayer] > 0) { if (information.computerThinkTime()[currentPlayer] > 0) {
final long elapsedTime = System.currentTimeMillis() - start; final long elapsedTime = System.currentTimeMillis() - start;
final long sleepTime = information.computerThinkTime()[currentPlayer] * 1000L - elapsedTime; final long sleepTime =
information.computerThinkTime()[currentPlayer] * 1000L - elapsedTime;
try { try {
Thread.sleep(sleepTime); Thread.sleep(sleepTime);
} catch (InterruptedException _) {} } catch (InterruptedException _) {
} }
} }
}
if (move == null) { if (move == null) {
continue; continue;
} }
final Game.State state = ticTacToe.get().play(move); final Game.State state = ticTacToe.get().play(move);
if (move.value() == 'X') { if (move.value() == 'X') {
canvas.drawX(Color.RED, move.position()); canvas.drawX(Color.RED, move.position());
} else if (move.value() == 'O') { } else if (move.value() == 'O') {
canvas.drawO(Color.BLUE, move.position()); canvas.drawO(Color.BLUE, move.position());
} }
if (state != Game.State.NORMAL) { if (state != Game.State.NORMAL) {
if (state == Game.State.WIN) { if (state == Game.State.WIN) {
App.push(new GameFinishedPopup(false, information.playerName()[ticTacToe.get().getCurrentTurn()])); App.push(
} else if (state == Game.State.DRAW) { new GameFinishedPopup(
App.push(new GameFinishedPopup(true, "")); false,
} information.playerName()[ticTacToe.get().getCurrentTurn()]));
} else if (state == Game.State.DRAW) {
App.push(new GameFinishedPopup(true, ""));
}
running = false; running = false;
} }
} }
} }
private void serverGameThread() { private void serverGameThread() {
new EventFlow() new EventFlow()
.listen(this::handleServerGameStart) // <----------- .listen(this::handleServerGameStart) // <-----------
.listen(this::yourTurnResponse) .listen(this::yourTurnResponse)
.listen(this::onMoveResponse) .listen(this::onMoveResponse)
.listen(this::handleReceivedMessage); .listen(this::handleReceivedMessage);
} }
private void handleServerGameStart(NetworkEvents.GameMatchResponse resp) { private void handleServerGameStart(NetworkEvents.GameMatchResponse resp) {
// Meneer Bas de Jong. Dit functie wordt niet aangeroepen als je de challenger bent. // Meneer Bas de Jong. Dit functie wordt niet aangeroepen als je de challenger bent.
// Ik heb veel dingen geprobeert. FUCKING veel dingen. Hij doet het niet. // Ik heb veel dingen geprobeert. FUCKING veel dingen. Hij doet het niet.
// Ik heb zelfs in jou code gekeken en unsubscribeAfterSuccess op false gezet. (zie ConnectedLayer). // Ik heb zelfs in jou code gekeken en unsubscribeAfterSuccess op false gezet. (zie
// Alle andere functies worden wel gecalt. Behalve dit. // ConnectedLayer).
// Alle andere functies worden wel gecalt. Behalve dit.
// Ben jij gehandicapt of ik? Want het moet 1 van de 2 zijn. Ik ben dit al 2 uur aan het debuggen. // Ben jij gehandicapt of ik? Want het moet 1 van de 2 zijn. Ik ben dit al 2 uur aan het
// Ik ga nu slapen (04:46). // debuggen.
// Ik ga nu slapen (04:46).
// ⠀⠀⠀⠀⠀⠀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⠀⠀⠀⠀⠀⠀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⢀⣴⣿⣿⠿⣟⢷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⠀⠀⠀⢀⣴⣿⣿⠿⣟⢷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⢸⣏⡏⠀⠀⠀⢣⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⠀⠀⠀⢸⣏⡏⠀⠀⠀⢣⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⢸⣟⠧⠤⠤⠔⠋⠀⢿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⠀⠀⠀⢸⣟⠧⠤⠤⠔⠋⠀⢿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⣿⡆⠀⠀⠀⠀⠀⠸⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⠀⠀⠀⠀⣿⡆⠀⠀⠀⠀⠀⠸⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠘⣿⡀⢀⣶⠤⠒⠀⢻⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⠀⠀⠀⠀⠘⣿⡀⢀⣶⠤⠒⠀⢻⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⢹⣧⠀⠀⠀⠀⠀⠈⢿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⠀⠀⠀⠀⠀⢹⣧⠀⠀⠀⠀⠀⠈⢿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⣿⡆⠀⠀⠀⠀⠀⠈⢿⣆⣠⣤⣤⣤⣤⣴⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀ // ⠀⠀⠀⠀⠀⠀⣿⡆⠀⠀⠀⠀⠀⠈⢿⣆⣠⣤⣤⣤⣤⣴⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⢀⣾⢿⢿⠀⠀⠀⢀⣀⣀⠘⣿⠋⠁⠀⠙⢇⠀⠀⠙⢿⣦⡀⠀⠀⠀⠀⠀ // ⠀⠀⠀⠀⢀⣾⢿⢿⠀⠀⠀⢀⣀⣀⠘⣿⠋⠁⠀⠙⢇⠀⠀⠙⢿⣦⡀⠀⠀⠀⠀⠀
// ⠀⠀⠀⢀⣾⢇⡞⠘⣧⠀⢖⡭⠞⢛⡄⠘⣆⠀⠀⠀⠈⢧⠀⠀⠀⠙⢿⣄⠀⠀⠀⠀ // ⠀⠀⠀⢀⣾⢇⡞⠘⣧⠀⢖⡭⠞⢛⡄⠘⣆⠀⠀⠀⠈⢧⠀⠀⠀⠙⢿⣄⠀⠀⠀⠀
// ⠀⠀⣠⣿⣛⣥⠤⠤⢿⡄⠀⠀⠈⠉⠀⠀⠹⡄⠀⠀⠀⠈⢧⠀⠀⠀⠈⠻⣦⠀⠀⠀ // ⠀⠀⣠⣿⣛⣥⠤⠤⢿⡄⠀⠀⠈⠉⠀⠀⠹⡄⠀⠀⠀⠈⢧⠀⠀⠀⠈⠻⣦⠀⠀⠀
// ⠀⣼⡟⡱⠛⠙⠀⠀⠘⢷⡀⠀⠀⠀⠀⠀⠀⠹⡀⠀⠀⠀⠈⣧⠀⠀⠀⠀⠹⣧⡀⠀ // ⠀⣼⡟⡱⠛⠙⠀⠀⠘⢷⡀⠀⠀⠀⠀⠀⠀⠹⡀⠀⠀⠀⠈⣧⠀⠀⠀⠀⠹⣧⡀⠀
// ⢸⡏⢠⠃⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠘⣧⠀⠀⠀⠀⠸⣷⡀ // ⢸⡏⢠⠃⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠘⣧⠀⠀⠀⠀⠸⣷⡀
// ⠸⣧⠘⡇⠀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⢹⡇⠀⠀⠀⠀⣿⠇ // ⠸⣧⠘⡇⠀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⢹⡇⠀⠀⠀⠀⣿⠇
// ⠀⣿⡄⢳⠀⠀⠀⠀⠀⠀⠀⠈⣷⠀⠀⠀⠀⠀⠀⠈⠆⠀⠀⠀⠀⠀⠀⠀⠀⣼⡟⠀ // ⠀⣿⡄⢳⠀⠀⠀⠀⠀⠀⠀⠈⣷⠀⠀⠀⠀⠀⠀⠈⠆⠀⠀⠀⠀⠀⠀⠀⠀⣼⡟⠀
// ⠀⢹⡇⠘⣇⠀⠀⠀⠀⠀⠀⠰⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⠀⣼⡟⠀⠀ // ⠀⢹⡇⠘⣇⠀⠀⠀⠀⠀⠀⠰⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⠀⣼⡟⠀⠀
// ⠀⢸⡇⠀⢹⡆⠀⠀⠀⠀⠀⠀⠙⠁⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⢳⣼⠟⠀⠀⠀ // ⠀⢸⡇⠀⢹⡆⠀⠀⠀⠀⠀⠀⠙⠁⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⢳⣼⠟⠀⠀⠀
// ⠀⠸⣧⣀⠀⢳⡀⠀⠀⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠀⠀⠀⢃⠀⢀⣴⡿⠁⠀⠀⠀⠀ // ⠀⠸⣧⣀⠀⢳⡀⠀⠀⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠀⠀⠀⢃⠀⢀⣴⡿⠁⠀⠀⠀⠀
// ⠀⠀⠈⠙⢷⣄⢳⡀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⣠⡿⠟⠛⠉⠀⠀⠀⠀⠀⠀ // ⠀⠀⠈⠙⢷⣄⢳⡀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⣠⡿⠟⠛⠉⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠈⠻⢿⣷⣦⣄⣀⣀⣠⣤⠾⠷⣦⣤⣤⡶⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⠀⠀⠀⠀⠈⠻⢿⣷⣦⣄⣀⣀⣠⣤⠾⠷⣦⣤⣤⡶⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
player2Name = resp.opponent(); player2Name = resp.opponent();
System.out.println(player2Name); System.out.println(player2Name);
currentPlayerMoveText.setText("X"); currentPlayerMoveText.setText("X");
if(!resp.playerToMove().equalsIgnoreCase(resp.opponent())) { if (!resp.playerToMove().equalsIgnoreCase(resp.opponent())) {
currentPlayerNameText.setText(information.playerName()[0]); currentPlayerNameText.setText(information.playerName()[0]);
firstPlayerIsMe.set(true); firstPlayerIsMe.set(true);
System.out.printf("I am starting: My client id is %d\n", resp.clientId()); System.out.printf("I am starting: My client id is %d\n", resp.clientId());
} else { } else {
currentPlayerNameText.setText(player2Name); currentPlayerNameText.setText(player2Name);
firstPlayerIsMe.set(false); firstPlayerIsMe.set(false);
System.out.printf("I am NOT starting: My client id is %d\n", resp.clientId()); System.out.printf("I am NOT starting: My client id is %d\n", resp.clientId());
} }
} }
private void onMoveResponse(NetworkEvents.GameMoveResponse resp) { private void onMoveResponse(NetworkEvents.GameMoveResponse resp) {
char playerChar; char playerChar;
if (!resp.player().equalsIgnoreCase(player2Name)) { if (!resp.player().equalsIgnoreCase(player2Name)) {
playerChar = firstPlayerIsMe.get()? 'X' : 'O'; playerChar = firstPlayerIsMe.get() ? 'X' : 'O';
} else { } else {
playerChar = firstPlayerIsMe.get()? 'O' : 'X'; playerChar = firstPlayerIsMe.get() ? 'O' : 'X';
} }
final Game.Move move = new Game.Move(Integer.parseInt(resp.move()), playerChar); final Game.Move move = new Game.Move(Integer.parseInt(resp.move()), playerChar);
final Game.State state = ticTacToe.get().play(move); final Game.State state = ticTacToe.get().play(move);
if (state != Game.State.NORMAL) { //todo differentiate between future draw guaranteed and is currently a draw if (state
if (state == Game.State.WIN) { != Game.State.NORMAL) { // todo differentiate between future draw guaranteed and is
App.push(new GameFinishedPopup(false, information.playerName()[ticTacToe.get().getCurrentTurn()])); // currently a draw
} else if (state == Game.State.DRAW) { if (state == Game.State.WIN) {
App.push(new GameFinishedPopup(true, "")); App.push(
} new GameFinishedPopup(
} false, information.playerName()[ticTacToe.get().getCurrentTurn()]));
} else if (state == Game.State.DRAW) {
App.push(new GameFinishedPopup(true, ""));
}
}
if (move.value() == 'X') { if (move.value() == 'X') {
canvas.drawX(Color.RED, move.position()); canvas.drawX(Color.RED, move.position());
} else if (move.value() == 'O') { } else if (move.value() == 'O') {
canvas.drawO(Color.BLUE, move.position()); canvas.drawO(Color.BLUE, move.position());
} }
currentPlayerNameText.setText(ticTacToe.get().getCurrentTurn() == (firstPlayerIsMe.get()? 0 : 1)? information.playerName()[0] : player2Name); currentPlayerNameText.setText(
currentPlayerMoveText.setText(ticTacToe.get().getCurrentTurn() == 0? "X" : "O"); ticTacToe.get().getCurrentTurn() == (firstPlayerIsMe.get() ? 0 : 1)
} ? information.playerName()[0]
: player2Name);
currentPlayerMoveText.setText(ticTacToe.get().getCurrentTurn() == 0 ? "X" : "O");
}
private void yourTurnResponse(NetworkEvents.YourTurnResponse response) { private void yourTurnResponse(NetworkEvents.YourTurnResponse response) {
int position = -1; int position = -1;
if (information.isPlayerHuman()[0]) { if (information.isPlayerHuman()[0]) {
try { try {
position = playerMoveQueue.take().position(); position = playerMoveQueue.take().position();
} catch (InterruptedException _) {} } catch (InterruptedException _) {
} else { }
final Game.Move move = ticTacToeAI.findBestMove(ticTacToe.get(), compurterDifficultyToDepth(10, } else {
information.computerDifficulty()[0])); final Game.Move move =
ticTacToeAI.findBestMove(
ticTacToe.get(),
compurterDifficultyToDepth(10, information.computerDifficulty()[0]));
position = move.position(); position = move.position();
} }
new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short)position)) new EventFlow()
.postEvent(); .addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short) position))
} .postEvent();
}
private void handleReceivedMessage(NetworkEvents.ReceivedMessage msg) { private void handleReceivedMessage(NetworkEvents.ReceivedMessage msg) {
System.out.println("Received Message: " + msg.message()); //todo add chat window System.out.println("Received Message: " + msg.message()); // todo add chat window
} }
} }

View File

@@ -1,28 +1,27 @@
package org.toop.local; package org.toop.local;
import java.util.Locale;
import org.toop.framework.asset.ResourceManager; import org.toop.framework.asset.ResourceManager;
import org.toop.framework.asset.resources.LocalizationAsset; import org.toop.framework.asset.resources.LocalizationAsset;
import java.util.Locale;
public class AppContext { public class AppContext {
private static final LocalizationAsset localization = ResourceManager.get("localization"); private static final LocalizationAsset localization = ResourceManager.get("localization");
private static Locale locale = Locale.forLanguageTag("en"); private static Locale locale = Locale.forLanguageTag("en");
public static LocalizationAsset getLocalization() { public static LocalizationAsset getLocalization() {
return localization; return localization;
} }
public static void setLocale(Locale locale) { public static void setLocale(Locale locale) {
AppContext.locale = locale; AppContext.locale = locale;
} }
public static Locale getLocale() { public static Locale getLocale() {
return locale; return locale;
} }
public static String getString(String key) { public static String getString(String key) {
assert localization != null; assert localization != null;
return localization.getString(key, locale); return localization.getString(key, locale);
} }
} }

View File

@@ -1,15 +1,13 @@
package org.toop.local; package org.toop.local;
import jdk.jfr.Event; import java.io.File;
import java.util.Locale;
import org.toop.app.App; import org.toop.app.App;
import org.toop.framework.asset.resources.SettingsAsset; import org.toop.framework.asset.resources.SettingsAsset;
import org.toop.framework.audio.events.AudioEvents; import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.settings.Settings; import org.toop.framework.settings.Settings;
import java.io.File;
import java.util.Locale;
public class AppSettings { public class AppSettings {
private SettingsAsset settingsAsset; private SettingsAsset settingsAsset;
@@ -23,10 +21,16 @@ public class AppSettings {
AppContext.setLocale(Locale.of(settingsData.locale)); AppContext.setLocale(Locale.of(settingsData.locale));
App.setFullscreen(settingsData.fullScreen); App.setFullscreen(settingsData.fullScreen);
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(settingsData.volume)).asyncPostEvent(); new EventFlow()
new EventFlow().addPostEvent(new AudioEvents.ChangeFxVolume(settingsData.fxVolume)).asyncPostEvent(); .addPostEvent(new AudioEvents.ChangeVolume(settingsData.volume))
new EventFlow().addPostEvent(new AudioEvents.ChangeMusicVolume(settingsData.musicVolume)).asyncPostEvent(); .asyncPostEvent();
App.setStyle(settingsAsset.getTheme(), settingsAsset.getLayoutSize()); new EventFlow()
.addPostEvent(new AudioEvents.ChangeFxVolume(settingsData.fxVolume))
.asyncPostEvent();
new EventFlow()
.addPostEvent(new AudioEvents.ChangeMusicVolume(settingsData.musicVolume))
.asyncPostEvent();
App.setStyle(settingsAsset.getTheme(), settingsAsset.getLayoutSize());
} }
public SettingsAsset getPath() { public SettingsAsset getPath() {
@@ -45,7 +49,8 @@ public class AppSettings {
basePath = System.getProperty("user.home") + "/.config"; basePath = System.getProperty("user.home") + "/.config";
} }
File settingsFile = new File(basePath + File.separator + "ISY1" + File.separator + "settings.json"); File settingsFile =
new File(basePath + File.separator + "ISY1" + File.separator + "settings.json");
this.settingsAsset = new SettingsAsset(settingsFile); this.settingsAsset = new SettingsAsset(settingsFile);
} }
return this.settingsAsset; return this.settingsAsset;

View File

@@ -7,26 +7,27 @@ import java.util.concurrent.atomic.AtomicLong;
/** /**
* A thread-safe, distributed unique ID generator following the Snowflake pattern. * A thread-safe, distributed unique ID generator following the Snowflake pattern.
* <p> *
* Each generated 64-bit ID encodes: * <p>Each generated 64-bit ID encodes:
*
* <ul> * <ul>
* <li>41-bit timestamp (milliseconds since custom epoch)</li> * <li>41-bit timestamp (milliseconds since custom epoch)
* <li>10-bit machine identifier</li> * <li>10-bit machine identifier
* <li>12-bit sequence number for IDs generated in the same millisecond</li> * <li>12-bit sequence number for IDs generated in the same millisecond
* </ul> * </ul>
* </p>
* *
* <p>This implementation ensures: * <p>This implementation ensures:
*
* <ul> * <ul>
* <li>IDs are unique per machine.</li> * <li>IDs are unique per machine.
* <li>Monotonicity within the same machine.</li> * <li>Monotonicity within the same machine.
* <li>Safe concurrent generation via synchronized {@link #nextId()}.</li> * <li>Safe concurrent generation via synchronized {@link #nextId()}.
* </ul> * </ul>
* </p>
* *
* <p>Custom epoch is set to {@code 2025-01-01T00:00:00Z}.</p> * <p>Custom epoch is set to {@code 2025-01-01T00:00:00Z}.
*
* <p>Usage example:
* *
* <p>Usage example:</p>
* <pre>{@code * <pre>{@code
* SnowflakeGenerator generator = new SnowflakeGenerator(); * SnowflakeGenerator generator = new SnowflakeGenerator();
* long id = generator.nextId(); * long id = generator.nextId();
@@ -34,9 +35,7 @@ import java.util.concurrent.atomic.AtomicLong;
*/ */
public class SnowflakeGenerator { public class SnowflakeGenerator {
/** /** Custom epoch in milliseconds (2025-01-01T00:00:00Z). */
* Custom epoch in milliseconds (2025-01-01T00:00:00Z).
*/
private static final long EPOCH = Instant.parse("2025-01-01T00:00:00Z").toEpochMilli(); private static final long EPOCH = Instant.parse("2025-01-01T00:00:00Z").toEpochMilli();
// Bit allocations // Bit allocations
@@ -53,17 +52,15 @@ public class SnowflakeGenerator {
private static final long MACHINE_SHIFT = SEQUENCE_BITS; private static final long MACHINE_SHIFT = SEQUENCE_BITS;
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_BITS; private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_BITS;
/** /** Unique machine identifier derived from network interfaces (10 bits). */
* Unique machine identifier derived from network interfaces (10 bits).
*/
private static final long machineId = SnowflakeGenerator.genMachineId(); private static final long machineId = SnowflakeGenerator.genMachineId();
private final AtomicLong lastTimestamp = new AtomicLong(-1L); private final AtomicLong lastTimestamp = new AtomicLong(-1L);
private long sequence = 0L; private long sequence = 0L;
/** /**
* Generates a 10-bit machine identifier based on MAC addresses of network interfaces. * Generates a 10-bit machine identifier based on MAC addresses of network interfaces. Falls
* Falls back to a random value if MAC cannot be determined. * back to a random value if MAC cannot be determined.
*/ */
private static long genMachineId() { private static long genMachineId() {
try { try {
@@ -82,6 +79,7 @@ public class SnowflakeGenerator {
/** /**
* For testing: manually set the last generated timestamp. * For testing: manually set the last generated timestamp.
*
* @param l timestamp in milliseconds * @param l timestamp in milliseconds
*/ */
void setTime(long l) { void setTime(long l) {
@@ -89,8 +87,8 @@ public class SnowflakeGenerator {
} }
/** /**
* Constructs a SnowflakeGenerator. * Constructs a SnowflakeGenerator. Validates that the machine ID is within allowed range.
* Validates that the machine ID is within allowed range. *
* @throws IllegalArgumentException if machine ID is invalid * @throws IllegalArgumentException if machine ID is invalid
*/ */
public SnowflakeGenerator() { public SnowflakeGenerator() {
@@ -102,10 +100,9 @@ public class SnowflakeGenerator {
/** /**
* Generates the next unique ID. * Generates the next unique ID.
* <p> *
* If multiple IDs are generated in the same millisecond, a sequence number * <p>If multiple IDs are generated in the same millisecond, a sequence number is incremented.
* is incremented. If the sequence overflows, waits until the next millisecond. * If the sequence overflows, waits until the next millisecond.
* </p>
* *
* @return a unique 64-bit ID * @return a unique 64-bit ID
* @throws IllegalStateException if clock moves backwards or timestamp exceeds 41-bit limit * @throws IllegalStateException if clock moves backwards or timestamp exceeds 41-bit limit
@@ -139,6 +136,7 @@ public class SnowflakeGenerator {
/** /**
* Waits until the next millisecond if sequence overflows. * Waits until the next millisecond if sequence overflows.
*
* @param lastTimestamp previous timestamp * @param lastTimestamp previous timestamp
* @return new timestamp * @return new timestamp
*/ */
@@ -150,9 +148,7 @@ public class SnowflakeGenerator {
return ts; return ts;
} }
/** /** Returns current system timestamp in milliseconds. */
* Returns current system timestamp in milliseconds.
*/
private long timestamp() { private long timestamp() {
return System.currentTimeMillis(); return System.currentTimeMillis();
} }

View File

@@ -1,45 +1,44 @@
package org.toop.framework.asset; package org.toop.framework.asset;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.asset.events.AssetLoaderEvents;
import org.toop.framework.asset.resources.*;
import org.toop.framework.asset.types.BundledResource;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.PreloadResource;
import org.toop.framework.eventbus.EventFlow;
import org.reflections.Reflections;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function; import java.util.function.Function;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.reflections.Reflections;
import org.toop.framework.asset.events.AssetLoaderEvents;
import org.toop.framework.asset.resources.*;
import org.toop.framework.asset.types.BundledResource;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.PreloadResource;
import org.toop.framework.eventbus.EventFlow;
/** /**
* Responsible for loading assets from a file system directory into memory. * Responsible for loading assets from a file system directory into memory.
* <p>
* The {@code ResourceLoader} scans a root folder recursively, identifies files,
* and maps them to registered resource types based on file extensions and
* {@link FileExtension} annotations.
* It supports multiple resource types including {@link PreloadResource} (automatically loaded)
* and {@link BundledResource} (merged across multiple files).
* </p>
* *
* <p>Assets are stored in a static, thread-safe list and can be retrieved * <p>The {@code ResourceLoader} scans a root folder recursively, identifies files, and maps them to
* through {@link ResourceManager}.</p> * registered resource types based on file extensions and {@link FileExtension} annotations. It
* supports multiple resource types including {@link PreloadResource} (automatically loaded) and
* {@link BundledResource} (merged across multiple files).
*
* <p>Assets are stored in a static, thread-safe list and can be retrieved through {@link
* ResourceManager}.
*
* <p>Features:
* *
* <p>Features:</p>
* <ul> * <ul>
* <li>Recursive directory scanning for assets.</li> * <li>Recursive directory scanning for assets.
* <li>Automatic registration of resource classes via reflection.</li> * <li>Automatic registration of resource classes via reflection.
* <li>Bundled resource support: multiple files merged into a single resource instance.</li> * <li>Bundled resource support: multiple files merged into a single resource instance.
* <li>Preload resources automatically invoke {@link PreloadResource#load()}.</li> * <li>Preload resources automatically invoke {@link PreloadResource#load()}.
* <li>Progress tracking via {@link AssetLoaderEvents.LoadingProgressUpdate} events.</li> * <li>Progress tracking via {@link AssetLoaderEvents.LoadingProgressUpdate} events.
* </ul> * </ul>
* *
* <p>Usage example:</p> * <p>Usage example:
*
* <pre>{@code * <pre>{@code
* ResourceLoader loader = new ResourceLoader("assets"); * ResourceLoader loader = new ResourceLoader("assets");
* double progress = loader.getProgress(); * double progress = loader.getProgress();
@@ -48,14 +47,17 @@ import java.util.function.Function;
*/ */
public class ResourceLoader { public class ResourceLoader {
private static final Logger logger = LogManager.getLogger(ResourceLoader.class); private static final Logger logger = LogManager.getLogger(ResourceLoader.class);
private static final List<ResourceMeta<? extends BaseResource>> assets = new CopyOnWriteArrayList<>(); private static final List<ResourceMeta<? extends BaseResource>> assets =
private final Map<String, Function<File, ? extends BaseResource>> registry = new ConcurrentHashMap<>(); new CopyOnWriteArrayList<>();
private final Map<String, Function<File, ? extends BaseResource>> registry =
new ConcurrentHashMap<>();
private final AtomicInteger loadedCount = new AtomicInteger(0); private final AtomicInteger loadedCount = new AtomicInteger(0);
private int totalCount = 0; private int totalCount = 0;
/** /**
* Constructs an ResourceLoader and loads assets from the given root folder. * Constructs an ResourceLoader and loads assets from the given root folder.
*
* @param rootFolder the folder containing asset files * @param rootFolder the folder containing asset files
*/ */
public ResourceLoader(File rootFolder) { public ResourceLoader(File rootFolder) {
@@ -84,6 +86,7 @@ public class ResourceLoader {
/** /**
* Constructs an ResourceLoader from a folder path. * Constructs an ResourceLoader from a folder path.
*
* @param rootFolder the folder path containing assets * @param rootFolder the folder path containing assets
*/ */
public ResourceLoader(String rootFolder) { public ResourceLoader(String rootFolder) {
@@ -92,6 +95,7 @@ public class ResourceLoader {
/** /**
* Returns the current progress of loading assets (0.0 to 1.0). * Returns the current progress of loading assets (0.0 to 1.0).
*
* @return progress as a double * @return progress as a double
*/ */
public double getProgress() { public double getProgress() {
@@ -100,6 +104,7 @@ public class ResourceLoader {
/** /**
* Returns the number of assets loaded so far. * Returns the number of assets loaded so far.
*
* @return loaded count * @return loaded count
*/ */
public int getLoadedCount() { public int getLoadedCount() {
@@ -108,6 +113,7 @@ public class ResourceLoader {
/** /**
* Returns the total number of files found to load. * Returns the total number of files found to load.
*
* @return total asset count * @return total asset count
*/ */
public int getTotalCount() { public int getTotalCount() {
@@ -116,6 +122,7 @@ public class ResourceLoader {
/** /**
* Returns a snapshot list of all assets loaded by this loader. * Returns a snapshot list of all assets loaded by this loader.
*
* @return list of loaded assets * @return list of loaded assets
*/ */
public List<ResourceMeta<? extends BaseResource>> getAssets() { public List<ResourceMeta<? extends BaseResource>> getAssets() {
@@ -124,6 +131,7 @@ public class ResourceLoader {
/** /**
* Registers a factory for a specific file extension. * Registers a factory for a specific file extension.
*
* @param extension the file extension (without dot) * @param extension the file extension (without dot)
* @param factory a function mapping a File to a resource instance * @param factory a function mapping a File to a resource instance
* @param <T> the type of resource * @param <T> the type of resource
@@ -132,9 +140,7 @@ public class ResourceLoader {
this.registry.put(extension, factory); this.registry.put(extension, factory);
} }
/** /** Maps a file to a resource instance based on its extension and registered factories. */
* Maps a file to a resource instance based on its extension and registered factories.
*/
private <T extends BaseResource> T resourceMapper(File file, Class<T> type) { private <T extends BaseResource> T resourceMapper(File file, Class<T> type) {
String ext = getExtension(file.getName()); String ext = getExtension(file.getName());
Function<File, ? extends BaseResource> factory = registry.get(ext); Function<File, ? extends BaseResource> factory = registry.get(ext);
@@ -144,16 +150,13 @@ public class ResourceLoader {
if (!type.isInstance(resource)) { if (!type.isInstance(resource)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"File " + file.getName() + " is not of type " + type.getSimpleName() "File " + file.getName() + " is not of type " + type.getSimpleName());
);
} }
return type.cast(resource); return type.cast(resource);
} }
/** /** Loads the given list of files into assets, handling bundled and preload resources. */
* Loads the given list of files into assets, handling bundled and preload resources.
*/
private void loader(List<File> files) { private void loader(List<File> files) {
Map<String, BundledResource> bundledResources = new HashMap<>(); Map<String, BundledResource> bundledResources = new HashMap<>();
@@ -166,35 +169,38 @@ public class ResourceLoader {
} }
case BundledResource br -> { case BundledResource br -> {
String key = resource.getClass().getName() + ":" + br.getBaseName(); String key = resource.getClass().getName() + ":" + br.getBaseName();
if (!bundledResources.containsKey(key)) {bundledResources.put(key, br);} if (!bundledResources.containsKey(key)) {
bundledResources.put(key, br);
}
bundledResources.get(key).loadFile(file); bundledResources.get(key).loadFile(file);
resource = (BaseResource) bundledResources.get(key); resource = (BaseResource) bundledResources.get(key);
assets.add(new ResourceMeta<>(br.getBaseName(), resource)); assets.add(new ResourceMeta<>(br.getBaseName(), resource));
skipAdd = true; skipAdd = true;
} }
case PreloadResource pr -> pr.load(); case PreloadResource pr -> pr.load();
default -> { default -> {}
}
} }
BaseResource finalResource = resource; BaseResource finalResource = resource;
boolean alreadyAdded = assets.stream() boolean alreadyAdded = assets.stream().anyMatch(a -> a.getResource() == finalResource);
.anyMatch(a -> a.getResource() == finalResource);
if (!alreadyAdded && !skipAdd) { if (!alreadyAdded && !skipAdd) {
assets.add(new ResourceMeta<>(file.getName(), resource)); assets.add(new ResourceMeta<>(file.getName(), resource));
} }
logger.info("Loaded {} from {}", resource.getClass().getSimpleName(), file.getAbsolutePath()); logger.info(
"Loaded {} from {}",
resource.getClass().getSimpleName(),
file.getAbsolutePath());
loadedCount.incrementAndGet(); loadedCount.incrementAndGet();
new EventFlow() new EventFlow()
.addPostEvent(new AssetLoaderEvents.LoadingProgressUpdate(loadedCount.get(), totalCount)) .addPostEvent(
new AssetLoaderEvents.LoadingProgressUpdate(
loadedCount.get(), totalCount))
.postEvent(); .postEvent();
} }
} }
/** /** Recursively searches a folder and adds all files to the foundFiles list. */
* Recursively searches a folder and adds all files to the foundFiles list.
*/
private void fileSearcher(final File folder, List<File> foundFiles) { private void fileSearcher(final File folder, List<File> foundFiles) {
for (File fileEntry : Objects.requireNonNull(folder.listFiles())) { for (File fileEntry : Objects.requireNonNull(folder.listFiles())) {
if (fileEntry.isDirectory()) { if (fileEntry.isDirectory()) {
@@ -206,8 +212,8 @@ public class ResourceLoader {
} }
/** /**
* Uses reflection to automatically register all {@link BaseResource} subclasses * Uses reflection to automatically register all {@link BaseResource} subclasses annotated with
* annotated with {@link FileExtension}. * {@link FileExtension}.
*/ */
private void autoRegisterResources() { private void autoRegisterResources() {
Reflections reflections = new Reflections("org.toop.framework.asset.resources"); Reflections reflections = new Reflections("org.toop.framework.asset.resources");
@@ -217,20 +223,20 @@ public class ResourceLoader {
if (!cls.isAnnotationPresent(FileExtension.class)) continue; if (!cls.isAnnotationPresent(FileExtension.class)) continue;
FileExtension annotation = cls.getAnnotation(FileExtension.class); FileExtension annotation = cls.getAnnotation(FileExtension.class);
for (String ext : annotation.value()) { for (String ext : annotation.value()) {
registry.put(ext, file -> { registry.put(
try { ext,
return cls.getConstructor(File.class).newInstance(file); file -> {
} catch (Exception e) { try {
throw new RuntimeException(e); return cls.getConstructor(File.class).newInstance(file);
} } catch (Exception e) {
}); throw new RuntimeException(e);
}
});
} }
} }
} }
/** /** Extracts the base name from a file name, used for bundling multiple files. */
* Extracts the base name from a file name, used for bundling multiple files.
*/
private static String getBaseName(String fileName) { private static String getBaseName(String fileName) {
int underscoreIndex = fileName.indexOf('_'); int underscoreIndex = fileName.indexOf('_');
int dotIndex = fileName.lastIndexOf('.'); int dotIndex = fileName.lastIndexOf('.');
@@ -238,9 +244,7 @@ public class ResourceLoader {
return fileName.substring(0, dotIndex); return fileName.substring(0, dotIndex);
} }
/** /** Returns the file extension of a given file name (without dot). */
* Returns the file extension of a given file name (without dot).
*/
public static String getExtension(String name) { public static String getExtension(String name) {
int i = name.lastIndexOf('.'); int i = name.lastIndexOf('.');
return (i > 0) ? name.substring(i + 1) : ""; return (i > 0) ? name.substring(i + 1) : "";

View File

@@ -1,30 +1,29 @@
package org.toop.framework.asset; package org.toop.framework.asset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
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.asset.resources.*; import org.toop.framework.asset.resources.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* Centralized manager for all loaded assets in the application. * Centralized manager for all loaded assets in the application.
* <p>
* {@code ResourceManager} maintains a thread-safe registry of {@link Asset} objects
* and provides utility methods to retrieve assets by name, ID, or type.
* It works together with {@link ResourceLoader} to register assets automatically
* when they are loaded from the file system.
* </p>
* *
* <p>Key responsibilities:</p> * <p>{@code ResourceManager} maintains a thread-safe registry of {@link Asset} objects and provides
* utility methods to retrieve assets by name, ID, or type. It works together with {@link
* ResourceLoader} to register assets automatically when they are loaded from the file system.
*
* <p>Key responsibilities:
*
* <ul> * <ul>
* <li>Storing all loaded assets in a concurrent map.</li> * <li>Storing all loaded assets in a concurrent map.
* <li>Providing typed access to asset resources.</li> * <li>Providing typed access to asset resources.
* <li>Allowing lookup by asset name or ID.</li> * <li>Allowing lookup by asset name or ID.
* <li>Supporting retrieval of all assets of a specific {@link BaseResource} subclass.</li> * <li>Supporting retrieval of all assets of a specific {@link BaseResource} subclass.
* </ul> * </ul>
* *
* <p>Example usage:</p> * <p>Example usage:
*
* <pre>{@code * <pre>{@code
* // Load assets from a loader * // Load assets from a loader
* ResourceLoader loader = new ResourceLoader(new File("RootFolder")); * ResourceLoader loader = new ResourceLoader(new File("RootFolder"));
@@ -40,17 +39,20 @@ import java.util.concurrent.ConcurrentHashMap;
* Optional<Asset<? extends BaseResource>> maybeAsset = ResourceManager.findByName("menu.css"); * Optional<Asset<? extends BaseResource>> maybeAsset = ResourceManager.findByName("menu.css");
* }</pre> * }</pre>
* *
* <p>Notes:</p> * <p>Notes:
*
* <ul> * <ul>
* <li>All retrieval methods are static and thread-safe.</li> * <li>All retrieval methods are static and thread-safe.
* <li>The {@link #get(String)} method may require casting if the asset type is not known at compile time.</li> * <li>The {@link #get(String)} method may require casting if the asset type is not known at
* <li>Assets should be loaded via {@link ResourceLoader} before retrieval.</li> * compile time.
* <li>Assets should be loaded via {@link ResourceLoader} before retrieval.
* </ul> * </ul>
*/ */
public class ResourceManager { public class ResourceManager {
private static final Logger logger = LogManager.getLogger(ResourceManager.class); private static final Logger logger = LogManager.getLogger(ResourceManager.class);
private static final ResourceManager INSTANCE = new ResourceManager(); private static final ResourceManager INSTANCE = new ResourceManager();
private static final Map<String, ResourceMeta<? extends BaseResource>> assets = new ConcurrentHashMap<>(); private static final Map<String, ResourceMeta<? extends BaseResource>> assets =
new ConcurrentHashMap<>();
private ResourceManager() {} private ResourceManager() {}
@@ -68,7 +70,7 @@ public class ResourceManager {
* *
* @param loader the loader that has already loaded assets * @param loader the loader that has already loaded assets
*/ */
public synchronized static void loadAssets(ResourceLoader loader) { public static synchronized void loadAssets(ResourceLoader loader) {
for (var asset : loader.getAssets()) { for (var asset : loader.getAssets()) {
assets.put(asset.getName(), asset); assets.put(asset.getName(), asset);
} }
@@ -85,15 +87,20 @@ public class ResourceManager {
public static <T extends BaseResource> T get(String name) { public static <T extends BaseResource> T get(String name) {
ResourceMeta<T> asset = (ResourceMeta<T>) assets.get(name); ResourceMeta<T> asset = (ResourceMeta<T>) assets.get(name);
if (asset == null) { if (asset == null) {
throw new TypeNotPresentException(name, new RuntimeException(String.format("Type %s not present", name))); // TODO: Create own exception, BAM throw new TypeNotPresentException(
name,
new RuntimeException(
String.format(
"Type %s not present",
name))); // TODO: Create own exception, BAM
} }
return asset.getResource(); return asset.getResource();
} }
// @SuppressWarnings("unchecked") // @SuppressWarnings("unchecked")
// public static <T extends BaseResource> ArrayList<ResourceMeta<T>> getAllOfType() { // public static <T extends BaseResource> ArrayList<ResourceMeta<T>> getAllOfType() {
// return (ArrayList<ResourceMeta<T>>) (ArrayList<?>) new ArrayList<>(assets.values()); // return (ArrayList<ResourceMeta<T>>) (ArrayList<?>) new ArrayList<>(assets.values());
// } // }
/** /**
* Retrieve all assets of a specific resource type. * Retrieve all assets of a specific resource type.

View File

@@ -25,5 +25,4 @@ public class ResourceMeta<T extends BaseResource> {
public T getResource() { public T getResource() {
return this.resource; return this.resource;
} }
} }

View File

@@ -3,5 +3,6 @@ package org.toop.framework.asset.events;
import org.toop.framework.eventbus.events.EventWithoutSnowflake; import org.toop.framework.eventbus.events.EventWithoutSnowflake;
public class AssetLoaderEvents { public class AssetLoaderEvents {
public record LoadingProgressUpdate(int hasLoadedAmount, int isLoadingAmount) implements EventWithoutSnowflake {} public record LoadingProgressUpdate(int hasLoadedAmount, int isLoadingAmount)
implements EventWithoutSnowflake {}
} }

View File

@@ -14,5 +14,4 @@ public abstract class BaseResource {
public File getFile() { public File getFile() {
return this.file; return this.file;
} }
} }

View File

@@ -1,8 +1,7 @@
package org.toop.framework.asset.resources; package org.toop.framework.asset.resources;
import org.toop.framework.asset.types.FileExtension;
import java.io.File; import java.io.File;
import org.toop.framework.asset.types.FileExtension;
@FileExtension({"css"}) @FileExtension({"css"})
public class CssAsset extends BaseResource { public class CssAsset extends BaseResource {

View File

@@ -1,12 +1,11 @@
package org.toop.framework.asset.resources; package org.toop.framework.asset.resources;
import javafx.scene.text.Font;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.PreloadResource;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import javafx.scene.text.Font;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.PreloadResource;
@FileExtension({"ttf", "otf"}) @FileExtension({"ttf", "otf"})
public class FontAsset extends BaseResource implements PreloadResource { public class FontAsset extends BaseResource implements PreloadResource {
@@ -60,4 +59,4 @@ public class FontAsset extends BaseResource implements PreloadResource {
} }
return this.family; return this.family;
} }
} }

View File

@@ -1,11 +1,10 @@
package org.toop.framework.asset.resources; package org.toop.framework.asset.resources;
import javafx.scene.image.Image;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import javafx.scene.image.Image;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
@FileExtension({"png", "jpg", "jpeg"}) @FileExtension({"png", "jpg", "jpeg"})
public class ImageAsset extends BaseResource implements LoadableResource { public class ImageAsset extends BaseResource implements LoadableResource {
@@ -45,4 +44,4 @@ public class ImageAsset extends BaseResource implements LoadableResource {
} }
return null; return null;
} }
} }

View File

@@ -1,12 +1,13 @@
package org.toop.framework.asset.resources; package org.toop.framework.asset.resources;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
@FileExtension({"json"}) @FileExtension({"json"})
public class JsonAsset<T> extends BaseResource implements LoadableResource { public class JsonAsset<T> extends BaseResource implements LoadableResource {
@@ -25,7 +26,8 @@ public class JsonAsset<T> extends BaseResource implements LoadableResource {
File file = getFile(); File file = getFile();
if (!file.exists()) { if (!file.exists()) {
try { try {
// make a new file with the declared constructor (example: settings) if it doesn't exist // make a new file with the declared constructor (example: settings) if it doesn't
// exist
content = type.getDeclaredConstructor().newInstance(); content = type.getDeclaredConstructor().newInstance();
save(); save();
} catch (Exception e) { } catch (Exception e) {
@@ -36,7 +38,7 @@ public class JsonAsset<T> extends BaseResource implements LoadableResource {
try (FileReader reader = new FileReader(file)) { try (FileReader reader = new FileReader(file)) {
content = gson.fromJson(reader, type); content = gson.fromJson(reader, type);
this.isLoaded = true; this.isLoaded = true;
} catch(Exception e) { } catch (Exception e) {
throw new RuntimeException("Failed to load JSON asset" + getFile(), e); throw new RuntimeException("Failed to load JSON asset" + getFile(), e);
} }
} }
@@ -62,7 +64,7 @@ public class JsonAsset<T> extends BaseResource implements LoadableResource {
parent.mkdirs(); parent.mkdirs();
} }
try (FileWriter writer = new FileWriter(file)) { try (FileWriter writer = new FileWriter(file)) {
gson.toJson(content, writer); gson.toJson(content, writer);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Failed to save JSON asset" + getFile(), e); throw new RuntimeException("Failed to save JSON asset" + getFile(), e);
} }

View File

@@ -1,34 +1,29 @@
package org.toop.framework.asset.resources; package org.toop.framework.asset.resources;
import org.toop.framework.asset.types.BundledResource;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import org.toop.framework.asset.types.BundledResource;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
/** /**
* Represents a localization resource asset that loads and manages property files * Represents a localization resource asset that loads and manages property files containing
* containing key-value pairs for different locales. * key-value pairs for different locales.
* <p> *
* This class implements {@link LoadableResource} to support loading/unloading * <p>This class implements {@link LoadableResource} to support loading/unloading and {@link
* and {@link BundledResource} to represent resources that can contain multiple * BundledResource} to represent resources that can contain multiple localized bundles.
* localized bundles. *
* </p> * <p>Files handled by this class must have the {@code .properties} extension, optionally with a
* <p> * locale suffix, e.g., {@code messages_en_US.properties}.
* Files handled by this class must have the {@code .properties} extension, *
* optionally with a locale suffix, e.g., {@code messages_en_US.properties}. * <p>Example usage:
* </p>
* *
* <p>
* Example usage:
* <pre>{@code * <pre>{@code
* LocalizationAsset asset = new LocalizationAsset(new File("messages_en_US.properties")); * LocalizationAsset asset = new LocalizationAsset(new File("messages_en_US.properties"));
* asset.load(); * asset.load();
* String greeting = asset.getString("hello", Locale.US); * String greeting = asset.getString("hello", Locale.US);
* }</pre> * }</pre>
* </p>
*/ */
@FileExtension({"properties"}) @FileExtension({"properties"})
public class LocalizationAsset extends BaseResource implements LoadableResource, BundledResource { public class LocalizationAsset extends BaseResource implements LoadableResource, BundledResource {
@@ -54,18 +49,14 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
super(file); super(file);
} }
/** /** Loads the resource file into memory and prepares localized bundles. */
* Loads the resource file into memory and prepares localized bundles.
*/
@Override @Override
public void load() { public void load() {
loadFile(getFile()); loadFile(getFile());
isLoaded = true; isLoaded = true;
} }
/** /** Unloads all loaded resource bundles, freeing memory. */
* Unloads all loaded resource bundles, freeing memory.
*/
@Override @Override
public void unload() { public void unload() {
bundles.clear(); bundles.clear();
@@ -83,11 +74,10 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
} }
/** /**
* Retrieves a localized string for the given key and locale. * Retrieves a localized string for the given key and locale. If an exact match for the locale
* If an exact match for the locale is not found, a fallback * is not found, a fallback matching the language or the default locale will be used.
* matching the language or the default locale will be used.
* *
* @param key the key of the string * @param key the key of the string
* @param locale the desired locale * @param locale the desired locale
* @return the localized string * @return the localized string
* @throws MissingResourceException if no resource bundle is available for the locale * @throws MissingResourceException if no resource bundle is available for the locale
@@ -95,14 +85,15 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
public String getString(String key, Locale locale) { public String getString(String key, Locale locale) {
Locale target = findBestLocale(locale); Locale target = findBestLocale(locale);
ResourceBundle bundle = bundles.get(target); ResourceBundle bundle = bundles.get(target);
if (bundle == null) throw new MissingResourceException( if (bundle == null)
"No bundle for locale: " + target, getClass().getName(), key); throw new MissingResourceException(
"No bundle for locale: " + target, getClass().getName(), key);
return bundle.getString(key); return bundle.getString(key);
} }
/** /**
* Finds the best matching locale among loaded bundles. * Finds the best matching locale among loaded bundles. Prefers an exact match, then
* Prefers an exact match, then language-only match, then fallback. * language-only match, then fallback.
* *
* @param locale the desired locale * @param locale the desired locale
* @return the best matching locale * @return the best matching locale
@@ -125,8 +116,8 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
} }
/** /**
* Loads a specific property file as a resource bundle. * Loads a specific property file as a resource bundle. The locale is extracted from the file
* The locale is extracted from the file name if present. * name if present.
* *
* @param file the property file to load * @param file the property file to load
* @throws RuntimeException if the file cannot be read * @throws RuntimeException if the file cannot be read
@@ -134,7 +125,7 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
@Override @Override
public void loadFile(File file) { public void loadFile(File file) {
try (InputStreamReader reader = try (InputStreamReader reader =
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) { new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
Locale locale = extractLocale(file.getName(), baseName); Locale locale = extractLocale(file.getName(), baseName);
bundles.put(locale, new PropertyResourceBundle(reader)); bundles.put(locale, new PropertyResourceBundle(reader));
} catch (IOException e) { } catch (IOException e) {
@@ -153,22 +144,23 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
return this.baseName; return this.baseName;
} }
// /** // /**
// * Extracts the base name from a file name. // * Extracts the base name from a file name.
// * // *
// * @param fileName the file name // * @param fileName the file name
// * @return base name without locale or extension // * @return base name without locale or extension
// */ // */
// private String getBaseName(String fileName) { // private String getBaseName(String fileName) {
// int dotIndex = fileName.lastIndexOf('.'); // int dotIndex = fileName.lastIndexOf('.');
// String nameWithoutExtension = (dotIndex > 0) ? fileName.substring(0, dotIndex) : fileName; // String nameWithoutExtension = (dotIndex > 0) ? fileName.substring(0, dotIndex) :
// // fileName;
// int underscoreIndex = nameWithoutExtension.indexOf('_'); //
// if (underscoreIndex > 0) { // int underscoreIndex = nameWithoutExtension.indexOf('_');
// return nameWithoutExtension.substring(0, underscoreIndex); // if (underscoreIndex > 0) {
// } // return nameWithoutExtension.substring(0, underscoreIndex);
// return nameWithoutExtension; // }
// } // return nameWithoutExtension;
// }
/** /**
* Extracts a locale from a file name based on the pattern "base_LOCALE.properties". * Extracts a locale from a file name based on the pattern "base_LOCALE.properties".

View File

@@ -1,11 +1,10 @@
package org.toop.framework.asset.resources; package org.toop.framework.asset.resources;
import java.io.*;
import javafx.scene.media.Media; import javafx.scene.media.Media;
import org.toop.framework.asset.types.FileExtension; import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource; import org.toop.framework.asset.types.LoadableResource;
import java.io.*;
@FileExtension({"mp3"}) @FileExtension({"mp3"})
public class MusicAsset extends BaseResource implements LoadableResource { public class MusicAsset extends BaseResource implements LoadableResource {
private Media media; private Media media;
@@ -37,4 +36,4 @@ public class MusicAsset extends BaseResource implements LoadableResource {
public boolean isLoaded() { public boolean isLoaded() {
return isLoaded; return isLoaded;
} }
} }

View File

@@ -1,10 +1,8 @@
package org.toop.framework.asset.resources; package org.toop.framework.asset.resources;
import org.toop.framework.settings.Settings;
import java.io.File; import java.io.File;
import java.util.Locale; import java.util.Locale;
import org.toop.framework.settings.Settings;
public class SettingsAsset extends JsonAsset<Settings> { public class SettingsAsset extends JsonAsset<Settings> {
@@ -32,13 +30,13 @@ public class SettingsAsset extends JsonAsset<Settings> {
return getContent().fullScreen; return getContent().fullScreen;
} }
public String getTheme() { public String getTheme() {
return getContent().theme; return getContent().theme;
} }
public String getLayoutSize() { public String getLayoutSize() {
return getContent().layoutSize; return getContent().layoutSize;
} }
public void setVolume(int volume) { public void setVolume(int volume) {
getContent().volume = volume; getContent().volume = volume;
@@ -65,13 +63,13 @@ public class SettingsAsset extends JsonAsset<Settings> {
save(); save();
} }
public void setTheme(String theme) { public void setTheme(String theme) {
getContent().theme = theme; getContent().theme = theme;
save(); save();
} }
public void setLayoutSize(String layoutSize) { public void setLayoutSize(String layoutSize) {
getContent().layoutSize = layoutSize; getContent().layoutSize = layoutSize;
save(); save();
} }
} }

View File

@@ -1,11 +1,10 @@
package org.toop.framework.asset.resources; package org.toop.framework.asset.resources;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
import javax.sound.sampled.*;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
import javax.sound.sampled.*;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
@FileExtension({"wav"}) @FileExtension({"wav"})
public class SoundEffectAsset extends BaseResource implements LoadableResource { public class SoundEffectAsset extends BaseResource implements LoadableResource {
@@ -16,22 +15,25 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource {
} }
// Gets a new clip to play // Gets a new clip to play
public Clip getNewClip() throws LineUnavailableException, UnsupportedAudioFileException, IOException { public Clip getNewClip()
throws LineUnavailableException, UnsupportedAudioFileException, IOException {
// Get a new clip from audio system // Get a new clip from audio system
Clip clip = AudioSystem.getClip(); Clip clip = AudioSystem.getClip();
// Insert a new audio stream into the clip // Insert a new audio stream into the clip
AudioInputStream inputStream = this.getAudioStream(); AudioInputStream inputStream = this.getAudioStream();
AudioFormat baseFormat = inputStream.getFormat(); AudioFormat baseFormat = inputStream.getFormat();
if (baseFormat.getSampleSizeInBits() > 16) inputStream = downSampleAudio(inputStream, baseFormat); if (baseFormat.getSampleSizeInBits() > 16)
clip.open(inputStream); // ^ Clip can only run 16 bit and lower, thus downsampling necessary. inputStream = downSampleAudio(inputStream, baseFormat);
clip.open(
inputStream); // ^ Clip can only run 16 bit and lower, thus downsampling necessary.
return clip; return clip;
} }
// Generates a new audio stream from byte array // Generates a new audio stream from byte array
private AudioInputStream getAudioStream() throws UnsupportedAudioFileException, IOException { private AudioInputStream getAudioStream() throws UnsupportedAudioFileException, IOException {
// Check if raw data is loaded into memory // Check if raw data is loaded into memory
if(!this.isLoaded()){ if (!this.isLoaded()) {
this.load(); this.load();
} }
@@ -39,16 +41,18 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource {
return AudioSystem.getAudioInputStream(new ByteArrayInputStream(this.rawData)); return AudioSystem.getAudioInputStream(new ByteArrayInputStream(this.rawData));
} }
private AudioInputStream downSampleAudio(AudioInputStream audioInputStream, AudioFormat baseFormat) { private AudioInputStream downSampleAudio(
AudioFormat decodedFormat = new AudioFormat( AudioInputStream audioInputStream, AudioFormat baseFormat) {
AudioFormat.Encoding.PCM_SIGNED, AudioFormat decodedFormat =
baseFormat.getSampleRate(), new AudioFormat(
16, // force 16-bit AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getChannels(), baseFormat.getSampleRate(),
baseFormat.getChannels() * 2, 16, // force 16-bit
baseFormat.getSampleRate(), baseFormat.getChannels(),
false // little-endian baseFormat.getChannels() * 2,
); baseFormat.getSampleRate(),
false // little-endian
);
return AudioSystem.getAudioInputStream(decodedFormat, audioInputStream); return AudioSystem.getAudioInputStream(decodedFormat, audioInputStream);
} }

View File

@@ -1,12 +1,11 @@
package org.toop.framework.asset.resources; package org.toop.framework.asset.resources;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
@FileExtension({"txt", "json", "xml"}) @FileExtension({"txt", "json", "xml"})
public class TextAsset extends BaseResource implements LoadableResource { public class TextAsset extends BaseResource implements LoadableResource {
@@ -41,4 +40,4 @@ public class TextAsset extends BaseResource implements LoadableResource {
public String getContent() { public String getContent() {
return this.content; return this.content;
} }
} }

View File

@@ -1,30 +1,33 @@
package org.toop.framework.asset.types; package org.toop.framework.asset.types;
import java.io.File;
import org.toop.framework.asset.ResourceLoader; import org.toop.framework.asset.ResourceLoader;
import java.io.File;
/** /**
* Represents a resource that can be composed of multiple files, or "bundled" together * Represents a resource that can be composed of multiple files, or "bundled" together under a
* under a common base name. * common base name.
* *
* <p>Implementing classes allow an {@link ResourceLoader} * <p>Implementing classes allow an {@link ResourceLoader} to automatically merge multiple related
* to automatically merge multiple related files into a single resource instance.</p> * files into a single resource instance.
*
* <p>Typical use cases include:
* *
* <p>Typical use cases include:</p>
* <ul> * <ul>
* <li>Localization assets, where multiple `.properties` files (e.g., `messages_en.properties`, * <li>Localization assets, where multiple `.properties` files (e.g., `messages_en.properties`,
* `messages_nl.properties`) are grouped under the same logical resource.</li> * `messages_nl.properties`) are grouped under the same logical resource.
* <li>Sprite sheets, tile sets, or other multi-file resources that logically belong together.</li> * <li>Sprite sheets, tile sets, or other multi-file resources that logically belong together.
* </ul> * </ul>
* *
* <p>Implementing classes must provide:</p> * <p>Implementing classes must provide:
*
* <ul> * <ul>
* <li>{@link #loadFile(File)}: Logic to load or merge an individual file into the resource.</li> * <li>{@link #loadFile(File)}: Logic to load or merge an individual file into the resource.
* <li>{@link #getBaseName()}: A consistent base name used to group multiple files into this resource.</li> * <li>{@link #getBaseName()}: A consistent base name used to group multiple files into this
* resource.
* </ul> * </ul>
* *
* <p>Example usage:</p> * <p>Example usage:
*
* <pre>{@code * <pre>{@code
* public class LocalizationAsset extends BaseResource implements BundledResource { * public class LocalizationAsset extends BaseResource implements BundledResource {
* private final String baseName; * private final String baseName;
@@ -47,8 +50,8 @@ import java.io.File;
* } * }
* }</pre> * }</pre>
* *
* <p>When used with an asset loader, all files sharing the same base name are * <p>When used with an asset loader, all files sharing the same base name are automatically merged
* automatically merged into a single resource instance.</p> * into a single resource instance.
*/ */
public interface BundledResource { public interface BundledResource {
@@ -60,15 +63,15 @@ public interface BundledResource {
void loadFile(File file); void loadFile(File file);
/** /**
* Return a base name for grouping multiple files into this single resource. * Return a base name for grouping multiple files into this single resource. Files with the same
* Files with the same base name are automatically merged by the loader. * base name are automatically merged by the loader.
* *
* @return the base name used to identify this bundled resource * @return the base name used to identify this bundled resource
*/ */
String getBaseName(); String getBaseName();
// /** // /**
// Returns the name // Returns the name
// */ // */
// String getDefaultName(); // String getDefaultName();
} }

View File

@@ -1,23 +1,21 @@
package org.toop.framework.asset.types; package org.toop.framework.asset.types;
import org.toop.framework.asset.ResourceLoader; import java.lang.annotation.ElementType;
import org.toop.framework.asset.resources.BaseResource;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.annotation.ElementType; import org.toop.framework.asset.ResourceLoader;
import org.toop.framework.asset.resources.BaseResource;
/** /**
* Annotation to declare which file extensions a {@link BaseResource} subclass * Annotation to declare which file extensions a {@link BaseResource} subclass can handle.
* can handle.
* *
* <p>This annotation is processed by the {@link ResourceLoader} * <p>This annotation is processed by the {@link ResourceLoader} to automatically register resource
* to automatically register resource types for specific file extensions. * types for specific file extensions. Each extension listed will be mapped to the annotated
* Each extension listed will be mapped to the annotated resource class, * resource class, allowing the loader to instantiate the correct type when scanning files.
* allowing the loader to instantiate the correct type when scanning files.</p> *
* <p>Usage example:
* *
* <p>Usage example:</p>
* <pre>{@code * <pre>{@code
* @FileExtension({"png", "jpg"}) * @FileExtension({"png", "jpg"})
* public class ImageAsset extends BaseResource implements LoadableResource { * public class ImageAsset extends BaseResource implements LoadableResource {
@@ -25,18 +23,20 @@ import java.lang.annotation.ElementType;
* } * }
* }</pre> * }</pre>
* *
* <p>Key points:</p> * <p>Key points:
*
* <ul> * <ul>
* <li>The annotation is retained at runtime for reflection-based registration.</li> * <li>The annotation is retained at runtime for reflection-based registration.
* <li>Can only be applied to types (classes) that extend {@link BaseResource}.</li> * <li>Can only be applied to types (classes) that extend {@link BaseResource}.
* <li>Multiple extensions can be specified in the {@code value()} array.</li> * <li>Multiple extensions can be specified in the {@code value()} array.
* </ul> * </ul>
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
public @interface FileExtension { public @interface FileExtension {
/** /**
* The list of file extensions (without leading dot) that the annotated resource class can handle. * The list of file extensions (without leading dot) that the annotated resource class can
* handle.
* *
* @return array of file extensions * @return array of file extensions
*/ */

View File

@@ -4,20 +4,23 @@ import org.toop.framework.asset.ResourceLoader;
/** /**
* Represents a resource that can be explicitly loaded and unloaded. * Represents a resource that can be explicitly loaded and unloaded.
* <p>
* Any class implementing {@code LoadableResource} is responsible for managing its own
* loading and unloading logic, such as reading files, initializing data structures,
* or allocating external resources.
* </p>
* *
* <p>Implementing classes must define the following behaviors:</p> * <p>Any class implementing {@code LoadableResource} is responsible for managing its own loading
* and unloading logic, such as reading files, initializing data structures, or allocating external
* resources.
*
* <p>Implementing classes must define the following behaviors:
*
* <ul> * <ul>
* <li>{@link #load()}: Load the resource into memory or perform necessary initialization.</li> * <li>{@link #load()}: Load the resource into memory or perform necessary initialization.
* <li>{@link #unload()}: Release any held resources or memory when the resource is no longer needed.</li> * <li>{@link #unload()}: Release any held resources or memory when the resource is no longer
* <li>{@link #isLoaded()}: Return {@code true} if the resource has been successfully loaded and is ready for use, {@code false} otherwise.</li> * needed.
* <li>{@link #isLoaded()}: Return {@code true} if the resource has been successfully loaded and
* is ready for use, {@code false} otherwise.
* </ul> * </ul>
* *
* <p>Typical usage:</p> * <p>Typical usage:
*
* <pre>{@code * <pre>{@code
* public class MyFontAsset extends BaseResource implements LoadableResource { * public class MyFontAsset extends BaseResource implements LoadableResource {
* private boolean loaded = false; * private boolean loaded = false;
@@ -41,19 +44,19 @@ import org.toop.framework.asset.ResourceLoader;
* } * }
* }</pre> * }</pre>
* *
* <p>This interface is commonly used with {@link PreloadResource} to allow automatic * <p>This interface is commonly used with {@link PreloadResource} to allow automatic loading by an
* loading by an {@link ResourceLoader} if desired.</p> * {@link ResourceLoader} if desired.
*/ */
public interface LoadableResource { public interface LoadableResource {
/** /**
* Load the resource into memory or initialize it. * Load the resource into memory or initialize it. This method may throw runtime exceptions if
* This method may throw runtime exceptions if loading fails. * loading fails.
*/ */
void load(); void load();
/** /**
* Unload the resource and free any associated resources. * Unload the resource and free any associated resources. After this call, {@link #isLoaded()}
* After this call, {@link #isLoaded()} should return false. * should return false.
*/ */
void unload(); void unload();

View File

@@ -3,17 +3,19 @@ package org.toop.framework.asset.types;
import org.toop.framework.asset.ResourceLoader; import org.toop.framework.asset.ResourceLoader;
/** /**
* Marker interface for resources that should be **automatically loaded** by the {@link ResourceLoader}. * Marker interface for resources that should be **automatically loaded** by the {@link
* ResourceLoader}.
* *
* <p>Extends {@link LoadableResource}, so any implementing class must provide the standard * <p>Extends {@link LoadableResource}, so any implementing class must provide the standard {@link
* {@link LoadableResource#load()} and {@link LoadableResource#unload()} methods, as well as the * LoadableResource#load()} and {@link LoadableResource#unload()} methods, as well as the {@link
* {@link LoadableResource#isLoaded()} check.</p> * LoadableResource#isLoaded()} check.
* *
* <p>When a resource implements {@code PreloadResource}, the {@code ResourceLoader} will invoke * <p>When a resource implements {@code PreloadResource}, the {@code ResourceLoader} will invoke
* {@link LoadableResource#load()} automatically after the resource is discovered and instantiated, * {@link LoadableResource#load()} automatically after the resource is discovered and instantiated,
* without requiring manual loading by the user.</p> * without requiring manual loading by the user.
*
* <p>Typical usage:
* *
* <p>Typical usage:</p>
* <pre>{@code * <pre>{@code
* public class MyFontAsset extends BaseResource implements PreloadResource { * public class MyFontAsset extends BaseResource implements PreloadResource {
* @Override * @Override
@@ -34,6 +36,6 @@ import org.toop.framework.asset.ResourceLoader;
* }</pre> * }</pre>
* *
* <p>Note: Only use this interface for resources that are safe to load at startup, as it may * <p>Note: Only use this interface for resources that are safe to load at startup, as it may
* increase memory usage or startup time.</p> * increase memory usage or startup time.
*/ */
public interface PreloadResource extends LoadableResource {} public interface PreloadResource extends LoadableResource {}

View File

@@ -1,12 +1,10 @@
package org.toop.framework.audio; package org.toop.framework.audio;
import com.sun.scenario.Settings;
import javafx.scene.media.MediaPlayer; import javafx.scene.media.MediaPlayer;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import javax.sound.sampled.Clip; import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl; import javax.sound.sampled.FloatControl;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
public class AudioVolumeManager { public class AudioVolumeManager {
private final SoundManager sM; private final SoundManager sM;
@@ -15,7 +13,7 @@ public class AudioVolumeManager {
private double fxVolume = 1.0; private double fxVolume = 1.0;
private double musicVolume = 1.0; private double musicVolume = 1.0;
public AudioVolumeManager(SoundManager soundManager){ public AudioVolumeManager(SoundManager soundManager) {
this.sM = soundManager; this.sM = soundManager;
new EventFlow() new EventFlow()
@@ -25,19 +23,22 @@ public class AudioVolumeManager {
.listen(this::handleGetCurrentVolume) .listen(this::handleGetCurrentVolume)
.listen(this::handleGetCurrentFxVolume) .listen(this::handleGetCurrentFxVolume)
.listen(this::handleGetCurrentMusicVolume); .listen(this::handleGetCurrentMusicVolume);
} }
public void updateMusicVolume(MediaPlayer mediaPlayer){ public void updateMusicVolume(MediaPlayer mediaPlayer) {
mediaPlayer.setVolume(this.musicVolume * this.volume); mediaPlayer.setVolume(this.musicVolume * this.volume);
} }
public void updateSoundEffectVolume(Clip clip){ public void updateSoundEffectVolume(Clip clip) {
if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)){ if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
FloatControl volumeControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN); FloatControl volumeControl =
(FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float min = volumeControl.getMinimum(); float min = volumeControl.getMinimum();
float max = volumeControl.getMaximum(); float max = volumeControl.getMaximum();
float dB = (float) (Math.log10(Math.max(this.fxVolume * this.volume, 0.0001)) * 20.0); // convert linear to dB float dB =
(float)
(Math.log10(Math.max(this.fxVolume * this.volume, 0.0001))
* 20.0); // convert linear to dB
dB = Math.max(min, Math.min(max, dB)); dB = Math.max(min, Math.min(max, dB));
volumeControl.setValue(dB); volumeControl.setValue(dB);
} }
@@ -50,7 +51,7 @@ public class AudioVolumeManager {
private void handleFxVolumeChange(AudioEvents.ChangeFxVolume event) { private void handleFxVolumeChange(AudioEvents.ChangeFxVolume event) {
this.fxVolume = limitVolume(event.newVolume() / 100); this.fxVolume = limitVolume(event.newVolume() / 100);
for (Clip clip : sM.getActiveSoundEffects().values()){ for (Clip clip : sM.getActiveSoundEffects().values()) {
updateSoundEffectVolume(clip); updateSoundEffectVolume(clip);
} }
} }
@@ -60,32 +61,40 @@ public class AudioVolumeManager {
for (MediaPlayer mediaPlayer : sM.getActiveMusic()) { for (MediaPlayer mediaPlayer : sM.getActiveMusic()) {
this.updateMusicVolume(mediaPlayer); this.updateMusicVolume(mediaPlayer);
} }
for (Clip clip : sM.getActiveSoundEffects().values()){ for (Clip clip : sM.getActiveSoundEffects().values()) {
updateSoundEffectVolume(clip); updateSoundEffectVolume(clip);
} }
} }
private void handleMusicVolumeChange(AudioEvents.ChangeMusicVolume event){ private void handleMusicVolumeChange(AudioEvents.ChangeMusicVolume event) {
this.musicVolume = limitVolume(event.newVolume() / 100); this.musicVolume = limitVolume(event.newVolume() / 100);
System.out.println(this.musicVolume); System.out.println(this.musicVolume);
System.out.println(this.volume); System.out.println(this.volume);
for (MediaPlayer mediaPlayer : sM.getActiveMusic()){ for (MediaPlayer mediaPlayer : sM.getActiveMusic()) {
this.updateMusicVolume(mediaPlayer); this.updateMusicVolume(mediaPlayer);
} }
} }
private void handleGetCurrentVolume(AudioEvents.GetCurrentVolume event) { private void handleGetCurrentVolume(AudioEvents.GetCurrentVolume event) {
new EventFlow().addPostEvent(new AudioEvents.GetCurrentVolumeResponse(volume * 100, event.snowflakeId())) new EventFlow()
.addPostEvent(
new AudioEvents.GetCurrentVolumeResponse(volume * 100, event.snowflakeId()))
.asyncPostEvent(); .asyncPostEvent();
} }
private void handleGetCurrentFxVolume(AudioEvents.GetCurrentFxVolume event) { private void handleGetCurrentFxVolume(AudioEvents.GetCurrentFxVolume event) {
new EventFlow().addPostEvent(new AudioEvents.GetCurrentFxVolumeResponse(fxVolume * 100, event.snowflakeId())) new EventFlow()
.addPostEvent(
new AudioEvents.GetCurrentFxVolumeResponse(
fxVolume * 100, event.snowflakeId()))
.asyncPostEvent(); .asyncPostEvent();
} }
private void handleGetCurrentMusicVolume(AudioEvents.GetCurrentMusicVolume event){ private void handleGetCurrentMusicVolume(AudioEvents.GetCurrentMusicVolume event) {
new EventFlow().addPostEvent(new AudioEvents.GetCurrentMusicVolumeResponse(musicVolume * 100, event.snowflakeId())) new EventFlow()
.addPostEvent(
new AudioEvents.GetCurrentMusicVolumeResponse(
musicVolume * 100, event.snowflakeId()))
.asyncPostEvent(); .asyncPostEvent();
} }
} }

View File

@@ -1,5 +1,9 @@
package org.toop.framework.audio; package org.toop.framework.audio;
import java.io.*;
import java.util.*;
import javafx.scene.media.MediaPlayer;
import javax.sound.sampled.*;
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.SnowflakeGenerator; import org.toop.framework.SnowflakeGenerator;
@@ -10,25 +14,20 @@ import org.toop.framework.asset.resources.SoundEffectAsset;
import org.toop.framework.audio.events.AudioEvents; import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
import javafx.scene.media.MediaPlayer;
import java.io.*;
import java.util.*;
import javax.sound.sampled.*;
public class SoundManager { public class SoundManager {
private static final Logger logger = LogManager.getLogger(SoundManager.class); private static final Logger logger = LogManager.getLogger(SoundManager.class);
private final List<MediaPlayer> activeMusic = new ArrayList<>(); private final List<MediaPlayer> activeMusic = new ArrayList<>();
private final Queue<MusicAsset> backgroundMusicQueue = new LinkedList<>(); private final Queue<MusicAsset> backgroundMusicQueue = new LinkedList<>();
private final Map<Long, Clip> activeSoundEffects = new HashMap<>(); private final Map<Long, Clip> activeSoundEffects = new HashMap<>();
private final HashMap<String, SoundEffectAsset> audioResources = new HashMap<>(); private final HashMap<String, SoundEffectAsset> audioResources = new HashMap<>();
private final SnowflakeGenerator idGenerator = new SnowflakeGenerator(); // TODO: Don't create a new generator private final SnowflakeGenerator idGenerator =
new SnowflakeGenerator(); // TODO: Don't create a new generator
private final AudioVolumeManager audioVolumeManager = new AudioVolumeManager(this); private final AudioVolumeManager audioVolumeManager = new AudioVolumeManager(this);
public SoundManager() { public SoundManager() {
// Get all Audio Resources and add them to a list. // Get all Audio Resources and add them to a list.
for (ResourceMeta<SoundEffectAsset> asset : ResourceManager.getAllOfType(SoundEffectAsset.class)) { for (ResourceMeta<SoundEffectAsset> asset :
ResourceManager.getAllOfType(SoundEffectAsset.class)) {
try { try {
this.addAudioResource(asset); this.addAudioResource(asset);
} catch (IOException | LineUnavailableException | UnsupportedAudioFileException e) { } catch (IOException | LineUnavailableException | UnsupportedAudioFileException e) {
@@ -39,13 +38,17 @@ public class SoundManager {
.listen(this::handlePlaySound) .listen(this::handlePlaySound)
.listen(this::handleStopSound) .listen(this::handleStopSound)
.listen(this::handleMusicStart) .listen(this::handleMusicStart)
.listen(AudioEvents.ClickButton.class, _ -> { .listen(
try { AudioEvents.ClickButton.class,
playSound("medium-button-click.wav", false); _ -> {
} catch (UnsupportedAudioFileException | LineUnavailableException | IOException e) { try {
logger.error(e); playSound("medium-button-click.wav", false);
} } catch (UnsupportedAudioFileException
}); | LineUnavailableException
| IOException e) {
logger.error(e);
}
});
} }
private void handlePlaySound(AudioEvents.PlayEffect event) { private void handlePlaySound(AudioEvents.PlayEffect event) {
@@ -68,14 +71,13 @@ public class SoundManager {
private void handleMusicStart(AudioEvents.StartBackgroundMusic e) { private void handleMusicStart(AudioEvents.StartBackgroundMusic e) {
backgroundMusicQueue.clear(); backgroundMusicQueue.clear();
List<MusicAsset> shuffledArray = new ArrayList<>(ResourceManager.getAllOfType(MusicAsset.class) List<MusicAsset> shuffledArray =
.stream() new ArrayList<>(
.map(ResourceMeta::getResource) ResourceManager.getAllOfType(MusicAsset.class).stream()
.toList()); .map(ResourceMeta::getResource)
.toList());
Collections.shuffle(shuffledArray); Collections.shuffle(shuffledArray);
backgroundMusicQueue.addAll( backgroundMusicQueue.addAll(shuffledArray);
shuffledArray
);
backgroundMusicPlayer(); backgroundMusicPlayer();
} }
@@ -89,34 +91,40 @@ public class SoundManager {
MediaPlayer mediaPlayer = new MediaPlayer(ma.getMedia()); MediaPlayer mediaPlayer = new MediaPlayer(ma.getMedia());
mediaPlayer.setOnEndOfMedia(() -> { mediaPlayer.setOnEndOfMedia(
addBackgroundMusic(ma); () -> {
activeMusic.remove(mediaPlayer); addBackgroundMusic(ma);
mediaPlayer.dispose(); activeMusic.remove(mediaPlayer);
ma.unload(); mediaPlayer.dispose();
backgroundMusicPlayer(); // play next ma.unload();
}); backgroundMusicPlayer(); // play next
});
mediaPlayer.setOnStopped(() -> { mediaPlayer.setOnStopped(
addBackgroundMusic(ma); () -> {
activeMusic.remove(mediaPlayer); addBackgroundMusic(ma);
ma.unload(); activeMusic.remove(mediaPlayer);
}); ma.unload();
});
mediaPlayer.setOnError(() -> { mediaPlayer.setOnError(
addBackgroundMusic(ma); () -> {
activeMusic.remove(mediaPlayer); addBackgroundMusic(ma);
ma.unload(); activeMusic.remove(mediaPlayer);
}); ma.unload();
});
audioVolumeManager.updateMusicVolume(mediaPlayer); audioVolumeManager.updateMusicVolume(mediaPlayer);
mediaPlayer.play(); mediaPlayer.play();
activeMusic.add(mediaPlayer); activeMusic.add(mediaPlayer);
logger.info("Playing background music: {}", ma.getFile().getName()); logger.info("Playing background music: {}", ma.getFile().getName());
logger.info("Background music next in line: {}", backgroundMusicQueue.peek().getFile().getName()); logger.info(
"Background music next in line: {}",
backgroundMusicQueue.peek().getFile().getName());
} }
private long playSound(String audioFileName, boolean loop) throws UnsupportedAudioFileException, LineUnavailableException, IOException { private long playSound(String audioFileName, boolean loop)
throws UnsupportedAudioFileException, LineUnavailableException, IOException {
SoundEffectAsset asset = audioResources.get(audioFileName); SoundEffectAsset asset = audioResources.get(audioFileName);
// Return -1 which indicates resource wasn't available // Return -1 which indicates resource wasn't available
@@ -134,8 +142,7 @@ public class SoundManager {
// If supposed to loop make it loop, else just start it once // If supposed to loop make it loop, else just start it once
if (loop) { if (loop) {
clip.loop(Clip.LOOP_CONTINUOUSLY); clip.loop(Clip.LOOP_CONTINUOUSLY);
} } else {
else {
clip.start(); clip.start();
} }
@@ -148,12 +155,13 @@ public class SoundManager {
activeSoundEffects.put(clipId, clip); // TODO: Do on snowflake for specific sound to stop activeSoundEffects.put(clipId, clip); // TODO: Do on snowflake for specific sound to stop
// remove when finished (only for non-looping sounds) // remove when finished (only for non-looping sounds)
clip.addLineListener(event -> { clip.addLineListener(
if (event.getType() == LineEvent.Type.STOP && !clip.isRunning()) { event -> {
activeSoundEffects.remove(clipId); if (event.getType() == LineEvent.Type.STOP && !clip.isRunning()) {
clip.close(); activeSoundEffects.remove(clipId);
} clip.close();
}); }
});
// Return id so it can be stopped // Return id so it can be stopped
return clipId; return clipId;
@@ -179,7 +187,11 @@ public class SoundManager {
activeSoundEffects.clear(); activeSoundEffects.clear();
} }
public Map<Long, Clip> getActiveSoundEffects(){ return this.activeSoundEffects; } public Map<Long, Clip> getActiveSoundEffects() {
return this.activeSoundEffects;
}
public List<MediaPlayer> getActiveMusic() { return activeMusic; } public List<MediaPlayer> getActiveMusic() {
return activeMusic;
}
} }

View File

@@ -1,21 +1,22 @@
package org.toop.framework.audio.events; package org.toop.framework.audio.events;
import java.util.Map;
import org.toop.framework.eventbus.events.EventWithSnowflake; import org.toop.framework.eventbus.events.EventWithSnowflake;
import org.toop.framework.eventbus.events.EventWithoutSnowflake; import org.toop.framework.eventbus.events.EventWithoutSnowflake;
import org.toop.framework.eventbus.events.EventsBase; import org.toop.framework.eventbus.events.EventsBase;
import java.util.Map;
public class AudioEvents extends EventsBase { public class AudioEvents extends EventsBase {
/** Starts playing a sound. */ /** Starts playing a sound. */
public record PlayEffect(String fileName, boolean loop) public record PlayEffect(String fileName, boolean loop) implements EventWithoutSnowflake {}
implements EventWithoutSnowflake {}
public record StopEffect(long clipId) implements EventWithoutSnowflake {} public record StopEffect(long clipId) implements EventWithoutSnowflake {}
public record StartBackgroundMusic() implements EventWithoutSnowflake {} public record StartBackgroundMusic() implements EventWithoutSnowflake {}
public record ChangeVolume(double newVolume) implements EventWithoutSnowflake {} public record ChangeVolume(double newVolume) implements EventWithoutSnowflake {}
public record ChangeFxVolume(double newVolume) implements EventWithoutSnowflake {} public record ChangeFxVolume(double newVolume) implements EventWithoutSnowflake {}
public record ChangeMusicVolume(double newVolume) implements EventWithoutSnowflake {} public record ChangeMusicVolume(double newVolume) implements EventWithoutSnowflake {}
public record GetCurrentVolume(long snowflakeId) implements EventWithSnowflake { public record GetCurrentVolume(long snowflakeId) implements EventWithSnowflake {
@@ -29,7 +30,9 @@ public class AudioEvents extends EventsBase {
return snowflakeId; return snowflakeId;
} }
} }
public record GetCurrentVolumeResponse(double currentVolume, long snowflakeId) implements EventWithSnowflake {
public record GetCurrentVolumeResponse(double currentVolume, long snowflakeId)
implements EventWithSnowflake {
@Override @Override
public Map<String, Object> result() { public Map<String, Object> result() {
return Map.of(); return Map.of();
@@ -65,7 +68,8 @@ public class AudioEvents extends EventsBase {
} }
} }
public record GetCurrentFxVolumeResponse(double currentVolume, long snowflakeId) implements EventWithSnowflake { public record GetCurrentFxVolumeResponse(double currentVolume, long snowflakeId)
implements EventWithSnowflake {
@Override @Override
public Map<String, Object> result() { public Map<String, Object> result() {
return Map.of(); return Map.of();
@@ -77,7 +81,8 @@ public class AudioEvents extends EventsBase {
} }
} }
public record GetCurrentMusicVolumeResponse(double currentVolume, long snowflakeId) implements EventWithSnowflake { public record GetCurrentMusicVolumeResponse(double currentVolume, long snowflakeId)
implements EventWithSnowflake {
@Override @Override
public Map<String, Object> result() { public Map<String, Object> result() {
return Map.of(); return Map.of();
@@ -90,5 +95,4 @@ public class AudioEvents extends EventsBase {
} }
public record ClickButton() implements EventWithoutSnowflake {} public record ClickButton() implements EventWithoutSnowflake {}
}
}

View File

@@ -3,8 +3,8 @@ package org.toop.framework.settings;
public class Settings { public class Settings {
public boolean fullScreen = false; public boolean fullScreen = false;
public String locale = "en"; public String locale = "en";
public String theme = "dark"; public String theme = "dark";
public String layoutSize = "medium"; public String layoutSize = "medium";
public int volume = 100; public int volume = 100;
public int fxVolume = 20; public int fxVolume = 20;
public int musicVolume = 15; public int musicVolume = 15;

View File

@@ -3,34 +3,37 @@ package org.toop.game;
import java.util.Arrays; import java.util.Arrays;
public abstract class Game { public abstract class Game {
public enum State { public enum State {
NORMAL, DRAW, WIN, NORMAL,
} DRAW,
WIN,
}
public record Move(int position, char value) {} public record Move(int position, char value) {}
public static final char EMPTY = (char)0; public static final char EMPTY = (char) 0;
public final int rowSize; public final int rowSize;
public final int columnSize; public final int columnSize;
public final char[] board; public final char[] board;
protected Game(int rowSize, int columnSize) { protected Game(int rowSize, int columnSize) {
assert rowSize > 0 && columnSize > 0; assert rowSize > 0 && columnSize > 0;
this.rowSize = rowSize; this.rowSize = rowSize;
this.columnSize = columnSize; this.columnSize = columnSize;
board = new char[rowSize * columnSize]; board = new char[rowSize * columnSize];
Arrays.fill(board, EMPTY); Arrays.fill(board, EMPTY);
} }
protected Game(Game other) { protected Game(Game other) {
rowSize = other.rowSize; rowSize = other.rowSize;
columnSize = other.columnSize; columnSize = other.columnSize;
board = Arrays.copyOf(other.board, other.board.length); board = Arrays.copyOf(other.board, other.board.length);
} }
public abstract Move[] getLegalMoves(); public abstract Move[] getLegalMoves();
public abstract State play(Move move);
} public abstract State play(Move move);
}

View File

@@ -1,25 +1,27 @@
package org.toop.game; package org.toop.game;
public abstract class TurnBasedGame extends Game { public abstract class TurnBasedGame extends Game {
public final int turns; public final int turns;
protected int currentTurn; protected int currentTurn;
protected TurnBasedGame(int rowSize, int columnSize, int turns) { protected TurnBasedGame(int rowSize, int columnSize, int turns) {
super(rowSize, columnSize); super(rowSize, columnSize);
assert turns >= 2; assert turns >= 2;
this.turns = turns; this.turns = turns;
} }
protected TurnBasedGame(TurnBasedGame other) { protected TurnBasedGame(TurnBasedGame other) {
super(other); super(other);
turns = other.turns; turns = other.turns;
currentTurn = other.currentTurn; currentTurn = other.currentTurn;
} }
protected void nextTurn() { protected void nextTurn() {
currentTurn = (currentTurn + 1) % turns; currentTurn = (currentTurn + 1) % turns;
} }
public int getCurrentTurn() { return currentTurn; } public int getCurrentTurn() {
} return currentTurn;
}
}

View File

@@ -3,17 +3,17 @@ package org.toop.game.othello;
import org.toop.game.TurnBasedGame; import org.toop.game.TurnBasedGame;
public final class Othello extends TurnBasedGame { public final class Othello extends TurnBasedGame {
Othello() { Othello() {
super(8, 8, 2); super(8, 8, 2);
} }
@Override @Override
public Move[] getLegalMoves() { public Move[] getLegalMoves() {
return new Move[0]; return new Move[0];
} }
@Override @Override
public State play(Move move) { public State play(Move move) {
return null; return null;
} }
} }

View File

@@ -4,8 +4,8 @@ import org.toop.game.AI;
import org.toop.game.Game; import org.toop.game.Game;
public final class OthelloAI extends AI<Othello> { public final class OthelloAI extends AI<Othello> {
@Override @Override
public Game.Move findBestMove(Othello game, int depth) { public Game.Move findBestMove(Othello game, int depth) {
return null; return null;
} }
} }

View File

@@ -1,8 +1,7 @@
package org.toop.game.tictactoe; package org.toop.game.tictactoe;
import org.toop.game.TurnBasedGame;
import java.util.ArrayList; import java.util.ArrayList;
import org.toop.game.TurnBasedGame;
public final class TicTacToe extends TurnBasedGame { public final class TicTacToe extends TurnBasedGame {
private int movesLeft; private int movesLeft;
@@ -19,8 +18,8 @@ public final class TicTacToe extends TurnBasedGame {
@Override @Override
public Move[] getLegalMoves() { public Move[] getLegalMoves() {
final ArrayList<Move> legalMoves = new ArrayList<>(); final ArrayList<Move> legalMoves = new ArrayList<>();
final char currentValue = getCurrentValue(); final char currentValue = getCurrentValue();
for (int i = 0; i < board.length; i++) { for (int i = 0; i < board.length; i++) {
if (board[i] == EMPTY) { if (board[i] == EMPTY) {
@@ -44,12 +43,12 @@ public final class TicTacToe extends TurnBasedGame {
return State.WIN; return State.WIN;
} }
nextTurn(); nextTurn();
if (movesLeft <= 2) { if (movesLeft <= 2) {
if (movesLeft <= 0 || checkForEarlyDraw(this)) { if (movesLeft <= 0 || checkForEarlyDraw(this)) {
return State.DRAW; return State.DRAW;
} }
} }
return State.NORMAL; return State.NORMAL;
@@ -60,7 +59,9 @@ public final class TicTacToe extends TurnBasedGame {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
final int index = i * 3; final int index = i * 3;
if (board[index] != EMPTY && board[index] == board[index + 1] && board[index] == board[index + 2]) { if (board[index] != EMPTY
&& board[index] == board[index + 1]
&& board[index] == board[index + 2]) {
return true; return true;
} }
} }
@@ -83,7 +84,7 @@ public final class TicTacToe extends TurnBasedGame {
private boolean checkForEarlyDraw(TicTacToe game) { private boolean checkForEarlyDraw(TicTacToe game) {
for (final Move move : game.getLegalMoves()) { for (final Move move : game.getLegalMoves()) {
final TicTacToe copy = new TicTacToe(game); final TicTacToe copy = new TicTacToe(game);
if (copy.play(move) == State.WIN || !checkForEarlyDraw(copy)) { if (copy.play(move) == State.WIN || !checkForEarlyDraw(copy)) {
return false; return false;
@@ -93,7 +94,7 @@ public final class TicTacToe extends TurnBasedGame {
return true; return true;
} }
private char getCurrentValue() { private char getCurrentValue() {
return currentTurn == 0? 'X' : 'O'; return currentTurn == 0 ? 'X' : 'O';
} }
} }

View File

@@ -1,83 +1,81 @@
package org.toop.game.tictactoe; package org.toop.game.tictactoe;
import org.toop.game.Game;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.toop.game.Game;
class TicTacToeAITest { class TicTacToeAITest {
private TicTacToe game; private TicTacToe game;
private TicTacToeAI ai; private TicTacToeAI ai;
@BeforeEach @BeforeEach
void setup() { void setup() {
game = new TicTacToe(); game = new TicTacToe();
ai = new TicTacToeAI(); ai = new TicTacToeAI();
} }
@Test @Test
void testBestMove_returnWinningMoveWithDepth1() { void testBestMove_returnWinningMoveWithDepth1() {
// X X - // X X -
// O O - // O O -
// - - - // - - -
game.play(new Game.Move(0, 'X')); game.play(new Game.Move(0, 'X'));
game.play(new Game.Move(3, 'O')); game.play(new Game.Move(3, 'O'));
game.play(new Game.Move(1, 'X')); game.play(new Game.Move(1, 'X'));
game.play(new Game.Move(4, 'O')); game.play(new Game.Move(4, 'O'));
final Game.Move move = ai.findBestMove(game, 1); final Game.Move move = ai.findBestMove(game, 1);
assertNotNull(move); assertNotNull(move);
assertEquals('X', move.value()); assertEquals('X', move.value());
assertEquals(2, move.position()); assertEquals(2, move.position());
} }
@Test @Test
void testBestMove_blockOpponentWinDepth1() { void testBestMove_blockOpponentWinDepth1() {
// - - - // - - -
// O - - // O - -
// X X - // X X -
game.play(new Game.Move(6, 'X')); game.play(new Game.Move(6, 'X'));
game.play(new Game.Move(3, 'O')); game.play(new Game.Move(3, 'O'));
game.play(new Game.Move(7, 'X')); game.play(new Game.Move(7, 'X'));
final Game.Move move = ai.findBestMove(game, 1); final Game.Move move = ai.findBestMove(game, 1);
assertNotNull(move); assertNotNull(move);
assertEquals('O', move.value()); assertEquals('O', move.value());
assertEquals(8, move.position()); assertEquals(8, move.position());
} }
@Test @Test
void testBestMove_preferCornerOnEmpty() { void testBestMove_preferCornerOnEmpty() {
final Game.Move move = ai.findBestMove(game, 0); final Game.Move move = ai.findBestMove(game, 0);
assertNotNull(move); assertNotNull(move);
assertEquals('X', move.value()); assertEquals('X', move.value());
assertTrue(Set.of(0, 2, 6, 8).contains(move.position())); assertTrue(Set.of(0, 2, 6, 8).contains(move.position()));
} }
@Test @Test
void testBestMove_findBestMoveDraw() { void testBestMove_findBestMoveDraw() {
// O X - // O X -
// - O X // - O X
// X O X // X O X
game.play(new Game.Move(1, 'X')); game.play(new Game.Move(1, 'X'));
game.play(new Game.Move(0, 'O')); game.play(new Game.Move(0, 'O'));
game.play(new Game.Move(5, 'X')); game.play(new Game.Move(5, 'X'));
game.play(new Game.Move(4, 'O')); game.play(new Game.Move(4, 'O'));
game.play(new Game.Move(6, 'X')); game.play(new Game.Move(6, 'X'));
game.play(new Game.Move(7, 'O')); game.play(new Game.Move(7, 'O'));
game.play(new Game.Move(8, 'X')); game.play(new Game.Move(8, 'X'));
final Game.Move move = ai.findBestMove(game, game.getLegalMoves().length); final Game.Move move = ai.findBestMove(game, game.getLegalMoves().length);
assertNotNull(move); assertNotNull(move);
assertEquals('O', move.value()); assertEquals('O', move.value());
assertEquals(2, move.position()); assertEquals(2, move.position());
} }
} }