Merge pull request #164 from 2OOP/Development

Development update, demo 2
This commit is contained in:
Bas Antonius de Jong
2025-10-07 19:57:50 +02:00
committed by GitHub
114 changed files with 5893 additions and 1262 deletions

10
.gitignore vendored
View File

@@ -47,6 +47,9 @@ shelf/
*.iml
*.ipr
*.iws
misc.xml
uiDesigner.xml
##############################
## Eclipse
@@ -103,4 +106,9 @@ build
newgamesver-release-V1.jar
server.properties
gameserver.log.*
gameserver.log
gameserver.log
##############################
## JPackage
##############################
jpackage-input

View File

@@ -6,6 +6,7 @@
<w>dcompile</w>
<w>errorprone</w>
<w>flushnl</w>
<w>gaaf</w>
<w>gamelist</w>
<w>playerlist</w>
<w>tictactoe</w>

View File

@@ -0,0 +1,8 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.lang.foreign.Arena,ofAuto,java.lang.foreign.Arena,global,org.toop.framework.audio.AudioPlayer,play" />
</inspection_tool>
</profile>
</component>

41
.idea/resourceBundles.xml generated Normal file
View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ResourceBundleManager">
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_ar.properties" />
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_de.properties" />
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_en.properties" />
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_es.properties" />
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_fr.properties" />
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_hi.properties" />
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_it.properties" />
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_ja.properties" />
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_ko.properties" />
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_nl.properties" />
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_ru.properties" />
<file url="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_zh.properties" />
<custom-resource-bundle>
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/Localization_de.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/Localization_es.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/Localization_fr.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/Localization_it.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/Localization_nl.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/Localization_zh.properties" />
<base-name>localization</base-name>
</custom-resource-bundle>
<custom-resource-bundle>
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_ar.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_de.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_en.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_es.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_fr.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_hi.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_it.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_ja.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_ko.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_nl.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_ru.properties" />
<file value="file://$PROJECT_DIR$/app/src/main/resources/assets/localization/localization_zh.properties" />
<base-name>localization</base-name>
</custom-resource-bundle>
</component>
</project>

View File

@@ -9,16 +9,21 @@
<main-class>org.toop.Main</main-class>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.46.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.toop</groupId>
<artifactId>pism_framework</artifactId>
@@ -31,38 +36,46 @@
<version>0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>25</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
<configuration>
<source>25</source>
<target>25</target>
<release>25</release>
<encoding>UTF-8</encoding>
<!-- <compilerArgs>-->
<!-- <arg>-XDcompilePolicy=simple</arg>-->
<!-- <arg>&#45;&#45;should-stop=ifError=FLOW</arg>-->
<!-- <arg>-Xplugin:ErrorProne</arg>-->
<!-- </compilerArgs>-->
<!-- <annotationProcessorPaths>-->
<!-- <path>-->
<!-- <groupId>com.google.errorprone</groupId>-->
<!-- <artifactId>error_prone_core</artifactId>-->
<!-- <version>2.42.0</version>-->
<!-- </path>-->
<!-- &lt;!&ndash; Other annotation processors go here.-->
<!-- If 'annotationProcessorPaths' is set, processors will no longer be-->
<!-- discovered on the regular -classpath; see also 'Using Error Prone-->
<!-- together with other annotation processors' below. &ndash;&gt;-->
<!-- </annotationProcessorPaths>-->
<!-- <fork>true</fork>-->
</configuration>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.toop.Main</mainClass>
</transformer>
</transformers>
<finalName>pism</finalName>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
@@ -99,6 +112,14 @@
</java>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>25</source>
<target>25</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,16 +1,21 @@
package org.toop;
import org.toop.app.gui.LocalServerSelector;
import org.toop.app.App;
import org.toop.framework.asset.ResourceLoader;
import org.toop.framework.asset.ResourceManager;
import org.toop.framework.audio.SoundManager;
import org.toop.framework.networking.NetworkingClientManager;
import org.toop.framework.networking.NetworkingInitializationException;
public class Main {
static void main(String[] args) {
public final class Main {
public static void main(String[] args) {
initSystems();
javax.swing.SwingUtilities.invokeLater(LocalServerSelector::new);
App.run(args);
}
private static void initSystems() throws NetworkingInitializationException {
new NetworkingClientManager();
ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets"));
new Thread(NetworkingClientManager::new).start();
new Thread(SoundManager::new).start();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,21 +0,0 @@
package org.toop.app.gui;
import java.awt.*;
import javax.swing.*;
public class BackgroundPanel extends JPanel {
private Image backgroundImage;
public void setBackgroundImage(Image image) {
this.backgroundImage = image;
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (backgroundImage != null) {
g.drawImage(backgroundImage, 0, 0, getWidth(), getHeight(), this);
}
}
}

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.toop.app.gui.LocalGameSelector">
<grid id="27dc6" binding="panel1" default-binding="true" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="780" height="600"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="3f7f6" class="javax.swing.JComboBox" binding="gameSelectionComboBox">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<model/>
</properties>
</component>
<component id="4700f" class="javax.swing.JButton" binding="startGame">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Start game"/>
</properties>
</component>
<component id="41f79" class="javax.swing.JComboBox" binding="playerTypeSelectionBox">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<editable value="false"/>
</properties>
</component>
</children>
</grid>
</form>

View File

@@ -1,107 +0,0 @@
package org.toop.app.gui;
import java.awt.*;
import javax.swing.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.tictactoe.LocalTicTacToe;
import org.toop.tictactoe.gui.UIGameBoard;
public class LocalGameSelector extends JFrame {
private static final Logger logger = LogManager.getLogger(LocalGameSelector.class);
private JPanel panel1;
private JComboBox gameSelectionComboBox;
private JButton startGame;
private JComboBox playerTypeSelectionBox;
private JButton deleteSave;
private JPanel cards; // CardLayout panel
private CardLayout cardLayout;
private UIGameBoard tttBoard;
public LocalGameSelector() {
setTitle("Local Game Selector");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(1920, 1080);
setLocationRelativeTo(null);
// Setup CardLayout
cardLayout = new CardLayout();
cards = new JPanel(cardLayout);
setContentPane(cards);
// --- Main menu panel ---
panel1 = new JPanel();
panel1.setLayout(new FlowLayout());
gameSelectionComboBox = new JComboBox<>();
gameSelectionComboBox.addItem("Tic Tac Toe");
gameSelectionComboBox.addItem("Reversi");
playerTypeSelectionBox = new JComboBox<>();
playerTypeSelectionBox.addItem("Player vs Player");
playerTypeSelectionBox.addItem("Player vs AI");
playerTypeSelectionBox.addItem("AI vs Player");
panel1.add(gameSelectionComboBox);
panel1.add(playerTypeSelectionBox);
startGame = new JButton("Start Game");
panel1.add(startGame);
deleteSave = new JButton("Delete Save");
panel1.add(deleteSave);
deleteSave.setEnabled(false);
deleteSave.addActionListener(
e -> {
tttBoard = null;
deleteSave.setEnabled(false);
});
cards.add(panel1, "MainMenu");
// Start button action
startGame.addActionListener(e -> startGameClicked());
setVisible(true);
}
private void startGameClicked() {
String playerTypes = (String) playerTypeSelectionBox.getSelectedItem();
String selectedGame = (String) gameSelectionComboBox.getSelectedItem();
LocalTicTacToe lttt = null;
if (playerTypes.equals("Player vs Player")) {
logger.info("Player vs Player");
lttt = LocalTicTacToe.createLocal(new boolean[] {false, false});
} else {
if (playerTypes.equals("Player vs AI")) {
logger.info("Player vs AI");
lttt = LocalTicTacToe.createLocal(new boolean[] {false, true});
} else {
logger.info("AI vs Player");
lttt = LocalTicTacToe.createLocal(new boolean[] {true, false});
}
}
if ("Tic Tac Toe".equalsIgnoreCase(selectedGame)) {
if (tttBoard == null) {
tttBoard = new UIGameBoard(lttt, this);
cards.add(tttBoard.getTTTPanel(), "TicTacToe");
}
cardLayout.show(cards, "TicTacToe");
}
lttt.startThreads();
}
public void showMainMenu() {
cardLayout.show(cards, "MainMenu");
gameSelectionComboBox.setSelectedIndex(0);
playerTypeSelectionBox.setSelectedIndex(0);
if (tttBoard != null) {
deleteSave.setEnabled(true);
}
}
}

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.toop.app.gui.LocalServerSelector">
<grid id="27dc6" binding="panel1" default-binding="true" layout-manager="GridLayoutManager" row-count="1" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="7774a" class="javax.swing.JButton" binding="serverButton" default-binding="true">
<constraints>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Server"/>
</properties>
</component>
<component id="ca0c8" class="javax.swing.JButton" binding="localButton">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Local"/>
</properties>
</component>
<hspacer id="74dd2">
<constraints>
<grid row="0" column="3" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<hspacer id="50fd">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
</children>
</grid>
</form>

View File

@@ -1,32 +0,0 @@
package org.toop.app.gui;
import javax.swing.*;
public class LocalServerSelector {
private JPanel panel1;
private JButton serverButton;
private JButton localButton;
private final JFrame frame;
public LocalServerSelector() {
frame = new JFrame("Server Selector");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(panel1);
frame.setSize(1920, 1080);
frame.setLocationRelativeTo(null); // Sets to center
frame.setVisible(true);
serverButton.addActionListener(e -> onServerClicked());
localButton.addActionListener(e -> onLocalClicked());
}
private void onServerClicked() {
frame.dispose();
new RemoteGameSelector();
}
private void onLocalClicked() {
frame.dispose();
new LocalGameSelector();
}
}

View File

@@ -1,147 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.toop.app.gui.RemoteGameSelector">
<grid id="27dc6" binding="mainMenu" layout-manager="GridLayoutManager" row-count="10" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="10" left="10" bottom="10" right="10"/>
<constraints>
<xy x="20" y="20" width="741" height="625"/>
</constraints>
<properties>
<opaque value="true"/>
<preferredSize width="800" height="600"/>
</properties>
<border type="none"/>
<children>
<component id="ed019" class="javax.swing.JTextField" binding="nameTextField">
<constraints>
<grid row="2" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties>
<text value=""/>
</properties>
</component>
<vspacer id="4e05c">
<constraints>
<grid row="9" column="2" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="1d2f1" class="javax.swing.JLabel">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Name:"/>
</properties>
</component>
<component id="70d8c" class="javax.swing.JLabel">
<constraints>
<grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Welcome to ISY games selector!"/>
</properties>
</component>
<component id="5cc82" class="javax.swing.JTextField" binding="name2TextField">
<constraints>
<grid row="3" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties>
<text value=""/>
</properties>
</component>
<hspacer id="27f2a">
<constraints>
<grid row="3" column="3" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<component id="3874e" class="javax.swing.JLabel" binding="fillAllFields">
<constraints>
<grid row="8" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<enabled value="true"/>
<foreground color="-65536"/>
<text value="Please fill all fields"/>
<visible value="false"/>
</properties>
</component>
<component id="a2f0" class="javax.swing.JButton" binding="connectButton" default-binding="true">
<constraints>
<grid row="7" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Connect"/>
</properties>
</component>
<component id="6a4c6" class="javax.swing.JTextField" binding="ipTextField">
<constraints>
<grid row="4" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="4214e" class="javax.swing.JComboBox" binding="gameSelectorBox">
<constraints>
<grid row="6" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<model/>
</properties>
</component>
<component id="4225c" class="javax.swing.JTextField" binding="portTextField">
<constraints>
<grid row="5" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="43a89" class="javax.swing.JLabel">
<constraints>
<grid row="6" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Select game:"/>
</properties>
</component>
<component id="3abc3" class="javax.swing.JLabel">
<constraints>
<grid row="5" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Port:"/>
</properties>
</component>
<component id="514db" class="javax.swing.JLabel">
<constraints>
<grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="IP:"/>
</properties>
</component>
<component id="5723c" class="javax.swing.JLabel">
<constraints>
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Name 2:"/>
</properties>
</component>
<hspacer id="2e34c">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<vspacer id="93fc6">
<constraints>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
</children>
</grid>
</form>

View File

@@ -1,114 +0,0 @@
package org.toop.app.gui;
import java.awt.event.ActionEvent;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import javax.swing.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.networking.NetworkingGameClientHandler;
import org.toop.framework.networking.events.NetworkEvents;
import org.toop.tictactoe.LocalTicTacToe;
import org.toop.tictactoe.gui.UIGameBoard;
public class RemoteGameSelector {
private static final Logger logger = LogManager.getLogger(RemoteGameSelector.class);
private JPanel mainMenu;
private JTextField nameTextField;
private JTextField name2TextField;
private JTextField ipTextField;
private JTextField portTextField;
private JButton connectButton;
private JComboBox gameSelectorBox;
private JPanel cards;
private JPanel gameSelector;
private JFrame frame;
private JLabel fillAllFields;
private LocalTicTacToe localTicTacToe;
public RemoteGameSelector() {
gameSelectorBox.addItem("Tic Tac Toe");
gameSelectorBox.addItem("Reversi");
// todo get supported games from server and add to gameSelectorBox
frame = new JFrame("Game Selector");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1920, 1080);
frame.setResizable(true);
init();
frame.add(mainMenu);
frame.setVisible(true);
// GlobalEventBus.subscribeAndRegister() Todo add game panel to frame when connection
// succeeds
}
private void init() {
connectButton.addActionListener(
(ActionEvent e) -> {
if (!nameTextField.getText().isEmpty()
&& !name2TextField.getText().isEmpty()
&& !ipTextField.getText().isEmpty()
&& !portTextField.getText().isEmpty()) {
AtomicReference<Long> clientId = new AtomicReference<>();
new EventFlow()
.addPostEvent(
NetworkEvents.StartClient.class,
(Supplier<NetworkingGameClientHandler>)
new NetworkingGameClientHandler(clientId.get()),
"127.0.0.1",
5001)
.onResponse(
NetworkEvents.StartClientResponse.class,
(response) -> {
clientId.set(response.clientId());
})
.asyncPostEvent();
// GlobalEventBus.subscribeAndRegister(
// NetworkEvents.ReceivedMessage.class,
// event -> {
// if
// (event.message().equalsIgnoreCase("ok")) {
// logger.info("received ok from
// server.");
// } else if
// (event.message().toLowerCase().startsWith("gameid")) {
// String gameId =
// event.message()
// .toLowerCase()
// .replace("gameid
// ", "");
// GlobalEventBus.post(
// new
// NetworkEvents.SendCommand(
// "start_game " +
// gameId));
// } else {
// logger.info("{}",
// event.message());
// }
// });
frame.remove(mainMenu);
UIGameBoard ttt = new UIGameBoard(localTicTacToe, this);
localTicTacToe.startThreads();
frame.add(ttt.getTTTPanel()); // TODO: Fix later
frame.revalidate();
frame.repaint();
} else {
fillAllFields.setVisible(true);
}
});
}
public void showMainMenu() {
frame.removeAll();
frame.add(mainMenu);
frame.revalidate();
frame.repaint();
}
}

View File

@@ -0,0 +1,12 @@
package org.toop.app.layer;
import javafx.scene.Node;
import javafx.scene.layout.Region;
public abstract class Container {
public abstract Region getContainer();
public abstract void addNodes(Node... nodes);
public abstract void addContainer(Container container, boolean fill);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,235 @@
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.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import org.toop.app.App;
import org.toop.app.GameInformation;
import org.toop.app.layer.Container;
import org.toop.app.layer.Layer;
import org.toop.app.layer.NodeBuilder;
import org.toop.app.layer.Popup;
import org.toop.app.layer.containers.HorizontalContainer;
import org.toop.app.layer.containers.VerticalContainer;
import org.toop.app.layer.layers.game.TicTacToeLayer;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.networking.events.NetworkEvents;
import org.toop.local.AppContext;
public final class ConnectedLayer extends Layer {
private static Timer pollTimer = new Timer();
private static class ChallengePopup extends Popup {
private final GameInformation information;
private final String challenger;
private final String game;
private final long clientID;
private final int challengeID;
public ChallengePopup(
GameInformation information,
String challenger,
String game,
long clientID,
String challengeID) {
super(false, "bg-popup");
this.information = information;
this.challenger = challenger;
this.game = game;
this.clientID = clientID;
this.challengeID =
Integer.parseInt(challengeID.substring(18, challengeID.length() - 2));
reload();
}
@Override
public void reload() {
popAll();
final var challengeText = NodeBuilder.header(AppContext.getString("challengeText"));
final var challengerNameText = NodeBuilder.header(challenger);
final var gameText = NodeBuilder.text(AppContext.getString("gameIsText"));
final var gameNameText = NodeBuilder.text(game);
final var acceptButton =
NodeBuilder.button(
AppContext.getString("accept"),
() -> {
pollTimer.cancel();
new EventFlow()
.addPostEvent(
new NetworkEvents.SendAcceptChallenge(
clientID, challengeID))
.postEvent();
App.activate(new TicTacToeLayer(information, clientID));
});
final var denyButton =
NodeBuilder.button(
AppContext.getString("deny"),
() -> {
App.pop();
});
final Container controlContainer = new HorizontalContainer(30);
controlContainer.addNodes(acceptButton, denyButton);
final Container mainContainer = new VerticalContainer(30);
mainContainer.addNodes(challengeText, challengerNameText);
mainContainer.addNodes(gameText, gameNameText);
mainContainer.addContainer(controlContainer, false);
addContainer(mainContainer, Pos.CENTER, 0, 0, 30, 30);
}
}
GameInformation information;
long clientId;
String user;
List<String> onlinePlayers = new CopyOnWriteArrayList<>();
public ConnectedLayer(GameInformation information) {
super("bg-primary");
this.information = information;
new EventFlow()
.addPostEvent(
NetworkEvents.StartClient.class,
information.serverIP(),
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();
Thread popThread = new Thread(this::populatePlayerList);
popThread.setDaemon(false);
popThread.start();
})
.postEvent();
new EventFlow().listen(this::handleReceivedChallenge);
reload();
}
private void populatePlayerList() {
EventFlow sendGetPlayerList =
new EventFlow().addPostEvent(new NetworkEvents.SendGetPlayerlist(this.clientId));
new EventFlow()
.listen(
NetworkEvents.PlayerlistResponse.class,
e -> {
if (e.clientId() == this.clientId) {
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() {
public void run() {
sendGetPlayerList.postEvent();
Platform.runLater(() -> reload());
}
};
pollTimer.schedule(task, 0L, 5000L); // TODO: Block app exit, fix later
}
private void sendChallenge(String oppUsername, String gameType) {
final AtomicInteger challengeId = new AtomicInteger(-1);
if (onlinePlayers.contains(oppUsername)) {
new EventFlow()
.addPostEvent(
new NetworkEvents.SendChallenge(this.clientId, oppUsername, gameType))
.listen(
NetworkEvents.ChallengeResponse.class,
e -> {
challengeId.set(
Integer.parseInt(
e.challengeId()
.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) {
App.push(
new ChallengePopup(
information,
response.challengerName(),
response.gameType(),
clientId,
response.challengeId()));
}
@Override
public void reload() {
popAll();
ListView<Label> players = new ListView<>();
for (int i = 0; i < onlinePlayers.size(); i++) {
int finalI = i;
players.getItems()
.add(
NodeBuilder.button(
onlinePlayers.get(i),
() -> {
String clickedPlayer = onlinePlayers.get(finalI);
sendChallenge(clickedPlayer, "tic-tac-toe");
}));
}
final Container playersContainer = new VerticalContainer(10);
playersContainer.addNodes(players);
addContainer(playersContainer, Pos.CENTER, 0, 0, 0, 0);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +0,0 @@
package org.toop.events;
import org.toop.framework.eventbus.events.EventWithoutSnowflake;
import org.toop.framework.eventbus.events.EventsBase;
public class WindowEvents extends EventsBase {
/** Triggers when a cell is clicked in one of the game boards. */
public record CellClicked(int cell) implements EventWithoutSnowflake {}
/** Triggers when the window wants to quit. */
public record OnQuitRequested() implements EventWithoutSnowflake {}
}

View File

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

View File

@@ -0,0 +1,58 @@
package org.toop.local;
import java.io.File;
import java.util.Locale;
import org.toop.app.App;
import org.toop.framework.asset.resources.SettingsAsset;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.settings.Settings;
public class AppSettings {
private SettingsAsset settingsAsset;
public void applySettings() {
SettingsAsset settings = getPath();
if (!settings.isLoaded()) {
settings.load();
}
Settings settingsData = settings.getContent();
AppContext.setLocale(Locale.of(settingsData.locale));
App.setFullscreen(settingsData.fullScreen);
new EventFlow()
.addPostEvent(new AudioEvents.ChangeVolume(settingsData.volume))
.asyncPostEvent();
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() {
if (this.settingsAsset == null) {
String os = System.getProperty("os.name").toLowerCase();
String basePath;
if (os.contains("win")) {
basePath = System.getenv("APPDATA");
if (basePath == null) {
basePath = System.getProperty("user.home");
}
} else if (os.contains("mac")) {
basePath = System.getProperty("user.home") + "/Library/Application Support";
} else {
basePath = System.getProperty("user.home") + "/.config";
}
File settingsFile =
new File(basePath + File.separator + "ISY1" + File.separator + "settings.json");
this.settingsAsset = new SettingsAsset(settingsFile);
}
return this.settingsAsset;
}
}

View File

@@ -1,233 +0,0 @@
package org.toop.tictactoe;
import java.util.concurrent.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.networking.events.NetworkEvents;
import org.toop.game.Game;
import org.toop.game.tictactoe.TicTacToe;
import org.toop.game.tictactoe.TicTacToeAI;
import org.toop.tictactoe.gui.UIGameBoard;
/**
* A representation of a local tic-tac-toe game. Calls are made to a server for information about
* current game state. MOST OF THIS CODE IS TRASH, THROW IT OUT OF THE WINDOW AFTER DEMO.
*/
// Todo: refactor
public class LocalTicTacToe { // TODO: Implement runnable
private static final Logger logger = LogManager.getLogger(LocalTicTacToe.class);
private final ExecutorService executor = Executors.newFixedThreadPool(3);
private final BlockingQueue<String> receivedQueue = new LinkedBlockingQueue<>();
private final BlockingQueue<Game.Move> moveQueuePlayerA = new LinkedBlockingQueue<>();
private final BlockingQueue<Game.Move> moveQueuePlayerB = new LinkedBlockingQueue<>();
private Object receivedMessageListener = null;
private boolean isLocal;
private String gameId;
private long clientId = -1;
private String serverId = null;
private boolean[] isAiPlayer = new boolean[2];
private TicTacToeAI ai = new TicTacToeAI();
private TicTacToe ticTacToe;
private UIGameBoard ui;
/** Is either 0 or 1. */
private int playersTurn = 0;
/**
* @return The current players turn.
*/
public int getCurrentPlayersTurn() {
return this.playersTurn;
}
// LocalTicTacToe(String gameId, String connectionId, String serverId) {
// this.gameId = gameId;
// this.connectionId = connectionId;
// this.serverId = serverId;
// this.receivedMessageListener =
// GlobalEventBus.subscribe(Events.ServerEvents.ReceivedMessage.class,
// this::receiveMessageAction);
// GlobalEventBus.register(this.receivedMessageListener);
//
//
// this.executor.submit(this::gameThread);
// } TODO: If remote server
/**
* Starts a connection with a remote server.
*
* @param ip The IP of the server to connect to.
* @param port The port of the server to connect to.
*/
private LocalTicTacToe(String ip, int port) {
// this.receivedMessageListener =
// GlobalEventBus.subscribe(this::receiveMessageAction);
// GlobalEventBus.subscribe(this.receivedMessageListener);
// this.connectionId = this.createConnection(ip, port); TODO: Refactor this
this.createGame("X", "O");
this.isLocal = false;
// this.executor.submit(this::remoteGameThread);
}
private LocalTicTacToe(boolean[] aiFlags) {
this.isAiPlayer = aiFlags; // store who is AI
this.isLocal = true;
// this.executor.submit(this::localGameThread);
}
public void startThreads() {
if (isLocal) {
this.executor.submit(this::localGameThread);
} else {
this.executor.submit(this::remoteGameThread);
}
}
public static LocalTicTacToe createLocal(boolean[] aiPlayers) {
return new LocalTicTacToe(aiPlayers);
}
public static LocalTicTacToe createRemote(String ip, int port) {
return new LocalTicTacToe(ip, port);
}
private void createGame(String nameA, String nameB) {
nameA = nameA.trim().replace(" ", "-");
nameB = nameB.trim().replace(" ", "-");
this.sendCommand("create_game", nameA, nameB);
}
private void startGame() {
if (this.gameId == null) {
return;
}
this.sendCommand("start_game", this.gameId);
}
private void localGameThread() {
boolean running = true;
this.ticTacToe = new TicTacToe("X", "O");
while (running) {
try {
Game.State state;
if (!isAiPlayer[0]) {
state = this.ticTacToe.play(this.moveQueuePlayerA.take());
} else {
Game.Move bestMove = ai.findBestMove(this.ticTacToe, 9);
assert bestMove != null;
state = this.ticTacToe.play(bestMove);
ui.setCell(bestMove.position(), "X");
}
if (state == Game.State.WIN || state == Game.State.DRAW) {
ui.setState(state, "X");
running = false;
}
this.setNextPlayersTurn();
if (!isAiPlayer[1]) {
state = this.ticTacToe.play(this.moveQueuePlayerB.take());
} else {
Game.Move bestMove = ai.findBestMove(this.ticTacToe, 9);
assert bestMove != null;
state = this.ticTacToe.play(bestMove);
ui.setCell(bestMove.position(), "O");
}
if (state == Game.State.WIN || state == Game.State.DRAW) {
ui.setState(state, "O");
running = false;
}
this.setNextPlayersTurn();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private void remoteGameThread() {
// TODO: If server start this.
}
public void setNextPlayersTurn() {
if (this.playersTurn == 0) {
this.playersTurn += 1;
} else {
this.playersTurn -= 1;
}
}
public char[] getCurrentBoard() {
// return ticTacToe.getGrid();
return new char[2];
}
/** End the current game. */
public void endGame() {
sendCommand("gameid", "end_game"); // TODO: Command is a bit wrong.
}
/**
* @param moveIndex The index of the move to make.
*/
public void move(int moveIndex) {
this.executor.submit(
() -> {
try {
if (this.playersTurn == 0 && !isAiPlayer[0]) {
this.moveQueuePlayerA.put(new Game.Move(moveIndex, 'X'));
logger.info(
"Adding player's {}, move: {} to queue A",
this.playersTurn,
moveIndex);
} else if (this.playersTurn == 1 && !isAiPlayer[1]) {
this.moveQueuePlayerB.put(new Game.Move(moveIndex, 'O'));
logger.info(
"Adding player's {}, move: {} to queue B",
this.playersTurn,
moveIndex);
}
} catch (InterruptedException e) {
logger.error(
"Could not add player: {}'s, move {}",
this.playersTurn,
moveIndex); // TODO: Error handling instead of crash.
}
});
}
private void endTheGame() {
this.sendCommand("end_game", this.gameId);
// this.endListeners();
}
private void receiveMessageAction(NetworkEvents.ReceivedMessage receivedMessage) {
if (receivedMessage.clientId() != this.clientId) {
return;
}
try {
logger.info("Received message from {}: {}", this.clientId, receivedMessage.message());
this.receivedQueue.put(receivedMessage.message());
} catch (InterruptedException e) {
logger.error("Error waiting for received Message", e);
}
}
private void sendCommand(String... args) {
new EventFlow()
.addPostEvent(NetworkEvents.SendCommand.class, this.clientId, args)
.asyncPostEvent();
}
// private void endListeners() {
// GlobalEventBus.unregister(this.receivedMessageListener);
// } TODO
public void setUIReference(UIGameBoard uiGameBoard) {
this.ui = uiGameBoard;
}
}

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.toop.tictactoe.gui.UIGameBoard">
<grid id="27dc6" binding="tttPanel" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="10" left="10" bottom="10" right="10"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties>
<preferredSize width="800" height="600"/>
</properties>
<border type="none"/>
<children>
<component id="e1a78" class="javax.swing.JButton" binding="backToMainMenuButton" default-binding="true">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="1" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Back to main menu"/>
</properties>
</component>
</children>
</grid>
</form>

View File

@@ -1,144 +0,0 @@
package org.toop.tictactoe.gui;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Locale;
import javax.swing.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.app.gui.LocalGameSelector;
import org.toop.app.gui.RemoteGameSelector;
import org.toop.game.Game;
import org.toop.tictactoe.LocalTicTacToe;
public class UIGameBoard {
private static final int TICTACTOE_SIZE = 3;
private static final Logger logger = LogManager.getLogger(LocalGameSelector.class);
private JPanel tttPanel; // Root panel for this game
private JButton backToMainMenuButton;
private JButton[] cells;
private String currentPlayer = "X";
private int currentPlayerIndex = 0;
private Object parentSelector;
private boolean parentLocal;
private LocalTicTacToe localTicTacToe;
private boolean gameOver = false;
public UIGameBoard(LocalTicTacToe lttt, Object parent) {
if (!(parent == null)) {
if (parent instanceof LocalGameSelector) {
parentLocal = true;
} else if (parent instanceof RemoteGameSelector) {
parentLocal = false;
}
}
this.parentSelector = parent;
this.localTicTacToe = lttt;
lttt.setUIReference(this);
// Root panel
tttPanel = new JPanel(new BorderLayout());
// Back button
backToMainMenuButton = new JButton("Back to Main Menu");
tttPanel.add(backToMainMenuButton, BorderLayout.SOUTH);
backToMainMenuButton.addActionListener(
_ -> {
// TODO reset game and connections
// Game now gets reset in local
if (parentLocal) {
((LocalGameSelector) parent).showMainMenu();
} else {
((RemoteGameSelector) parent).showMainMenu();
}
});
// Game grid
JPanel gameGrid = createGridPanel(TICTACTOE_SIZE, TICTACTOE_SIZE);
tttPanel.add(gameGrid, BorderLayout.CENTER);
// localTicTacToe.setMoveListener((playerIndex, moveIndex, symbol) -> {
// SwingUtilities.invokeLater(() -> {
// cells[moveIndex].setText(String.valueOf(symbol));
// });
// });
}
private JPanel createGridPanel(int sizeX, int sizeY) {
JPanel panel = new JPanel(new GridLayout(sizeX, sizeY));
cells = new JButton[sizeX * sizeY];
for (int i = 0; i < sizeX * sizeY; i++) {
cells[i] = new JButton(" ");
cells[i].setFont(new Font("Arial", Font.BOLD, 400 / sizeX));
panel.add(cells[i]);
cells[i].setFocusable(false);
final int index = i;
cells[i].addActionListener(
(ActionEvent _) -> {
if (!gameOver) {
if (cells[index].getText().equals(" ")) {
int cp = this.localTicTacToe.getCurrentPlayersTurn();
if (cp == 0) {
this.currentPlayer = "X";
currentPlayerIndex = 0;
} else if (cp == 1) {
this.currentPlayer = "O";
currentPlayerIndex = 1;
}
this.localTicTacToe.move(index);
cells[index].setText(currentPlayer);
} else {
logger.info(
"Player "
+ currentPlayerIndex
+ " attempted invalid move at: "
+ cells[index].getText());
}
} else {
logger.info(
"Player "
+ currentPlayerIndex
+ " attempted to move after the game has ended.");
}
});
}
return panel;
}
public void setCell(int index, String move) {
System.out.println(cells[index].getText());
cells[index].setText(move);
}
public void setState(Game.State state, String playerMove) {
Color color;
if (state == Game.State.WIN && playerMove.equals(currentPlayer)) {
color = new Color(160, 220, 160);
} else if (state == Game.State.WIN) {
color = new Color(220, 160, 160);
} else if (state == Game.State.DRAW) {
color = new Color(220, 220, 160);
} else {
color = new Color(220, 220, 220);
}
for (JButton cell : cells) {
cell.setBackground(color);
}
if (state == Game.State.DRAW || state == Game.State.WIN) {
gameOver = true;
}
}
public JPanel getTTTPanel() {
return tttPanel;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,63 @@
ai=\u0627\u0644\u0630\u0643\u0627\u0621 \u0627\u0644\u0635\u0646\u0627\u0639\u064a
appTitle=\u0645\u062e\u062a\u0627\u0631 \u0623\u0644\u0639\u0627\u0628 ISY
back=\u0631\u062c\u0648\u0639
backToMainMenu=\u0627\u0644\u0639\u0648\u062F\u0629 \u0625\u0644\u0649 \u0627\u0644\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629
computer=\u0627\u0644\u062d\u0627\u0633\u0648\u0628
computerDifficulty=\u0635\u0639\u0648\u0628\u0629 \u0627\u0644\u062d\u0627\u0633\u0648\u0628
computerThinkTime=\u0632\u0645\u0646 \u062a\u0641\u0643\u064a\u0631 \u0627\u0644\u0643\u0645\u0628\u064a\u0648\u062a\u0631
congratulations=\u0645\u0628\u0631\u0648\u0643
connect=\u0627\u062a\u0635\u0644
connectionType=\u0646\u0648\u0639 \u0627\u0644\u0627\u062A\u0635\u0627\u0644
credits=\u0627\u0644\u0634\u0643\u0631 \u0648\u0627\u0644\u062a\u0642\u062f\u064a\u0631
dark-hc=\u063A\u0627\u0645\u0642 (\u062A\u0646\u0627\u0642\u0636 \u0639\u0627\u0644\u064D)
dark=\u063A\u0627\u0645\u0642
developers=\u0627\u0644\u0645\u0637\u0648\u0631\u0648\u0646
drawText=\u0627\u0646\u062A\u0647\u062A \u0627\u0644\u0644\u0639\u0628\u0629 \u0628\u062A\u0639\u0627\u062F\u0644
effectsVolume=\u062d\u062c\u0645 \u0627\u0644\u062a\u0623\u062b\u064a\u0631\u0627\u062a
musicVolume=Music Volume (translate me)
fullscreen=\u0643\u0627\u0645\u0644 \u0627\u0644\u0634\u0627\u0634\u0629
goodGameText=\u0644\u0639\u0628\u0629 \u0631\u0627\u0626\u0639\u0629. \u0623\u062D\u0633\u0646\u062A.
human=\u0627\u0644\u0625\u0646\u0633\u0627\u0646
language=\u0627\u0644\u0644\u063a\u0629
large=\u0643\u0628\u064A\u0631
layoutSize=\u062D\u062C\u0645 \u0627\u0644\u062A\u0635\u0645\u064A\u0645
light-hc=\u0641\u0627\u062A\u062D (\u062A\u0646\u0627\u0642\u0636 \u0639\u0627\u0644\u064D)
light=\u0641\u0627\u062A\u062D
local=\u0645\u062d\u0644\u064a
localization=\u062a\u0648\u0645\u064a\u0645 \u0627\u0644\u0644\u063a\u0629
medium=\u0645\u062A\u0648\u0633\u0637
mergeCommander=\u0642\u0627\u0626\u062f \u0627\u0644\u062f\u0645\u062c
moralSupport=\u062f\u0639\u0645 \u0645\u0639\u0646\u0648\u064a
no=\u0644\u0627
opengl=OpenGL
options=\u0627\u0644\u062e\u064a\u0627\u0631\u0627\u062a
othello=\u0623\u0648\u062a\u064a\u0644\u0648
playerName=\u0627\u0633\u0645 \u0627\u0644\u0644\u0627\u0639\u0628
productOwner=\u0645\u0627\u0644\u0643 \u0627\u0644\u0645\u0646\u062a\u062c
quit=\u062e\u0631\u0648\u062c
quitSure=\u0647\u0644 \u0623\u0646\u062a \u0645\u062a\u0623\u0643\u062f\u061f
scrumMaster=\u0645\u062f\u064a\u0631 \u0627\u0644\u0633\u0643\u0631\u0645
server=\u062e\u0627\u062f\u0645
serverIP=IP \u0627\u0644\u062e\u0627\u062f\u0645
serverPort=\u0645\u0646\u0641\u0630 \u0627\u0644\u062e\u0627\u062f\u0645
small=\u0635\u063A\u064A\u0631
start=\u0627\u0628\u062f\u0623
theme=\u0627\u0644\u0645\u0648\u0636\u0648\u0639
tictactoe=\u062a\u064a\u0643 \u062a\u0627\u0643 \u062a\u0648
volume=\u0627\u0644\u062d\u062c\u0645 \u0627\u0644\u0631\u0626\u064a\u0633\u064a
windowed=\u0646\u0627\u0641\u0630\u064a
yes=\u0646\u0639\u0645
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629
chinese=\u4e2d\u6587 (\u0627\u0644\u0635\u064a\u0646\u064a\u0629)
dutch=Nederlands (\u0627\u0644\u0647\u0648\u0644\u0646\u062f\u064a\u0629)
english=English (\u0627\u0644\u0625\u0646\u062c\u0644\u064a\u0632\u064a\u0629)
french=Fran\u00e7ais (\u0627\u0644\u0641\u0631\u0646\u0633\u064a\u0629)
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (\u0627\u0644\u062c\u0648\u0631\u062c\u064a\u0629)
german=Deutsch (\u0627\u0644\u0623\u0644\u0645\u0627\u0646\u064a\u0629)
hindi=\u0939\u093f\u0928\u094d\u0926\u0940 (\u0627\u0644\u0647\u0646\u062f\u064a\u0629)
italian=Italiano (\u0627\u0644\u0625\u064a\u0637\u0627\u0644\u064a\u0629)
japanese=\u65e5\u672c\u8a9e (\u0627\u0644\u064a\u0627\u0628\u0627\u0646\u064a\u0629)
korean=\ud55c\uad6d\uc5b4 (\u0627\u0644\u0643\u0648\u0631\u064a\u0629)
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (\u0627\u0644\u0631\u0648\u0633\u064a\u0629)
spanish=Espa\u00f1ol (\u0627\u0644\u0625\u0633\u0628\u0627\u0646\u064a\u0629)

View File

@@ -0,0 +1,63 @@
ai=K\u00fcnstliche Intelligenz
appTitle=ISY Spieleauswahl
back=Zur\u00fcck
backToMainMenu=Zur\u00FCck zum Hauptmen\u00FC
computer=Computer
computerDifficulty=Computerschwierigkeit
computerThinkTime=Computer Denkzeit
congratulations=Gl\u00FCckwunsch
connect=Verbinden
connectionType=Verbindungstyp
credits=Credits
dark-hc=Dunkel (Hoher Kontrast)
dark=Dunkel
developers=Entwickler
drawText=Das Spiel endete unentschieden
effectsVolume=Effektlautst\u00e4rke
musicVolume=Music Volume (translate me)
fullscreen=Vollbild
goodGameText=Gutes Spiel. Gut gespielt.
human=Mensch
language=Sprache
large=Gro\u00DF
layoutSize=Layout-Gr\u00F6\u00DFe
light-hc=Hell (Hoher Kontrast)
light=Hell
local=Lokal
localization=Lokalisierung
medium=Mittel
mergeCommander=Merge-Kommandant
moralSupport=Mentale Unterst\u00fctzung
no=Nein
opengl=OpenGL
options=Optionen
othello=Othello
playerName=Spielername
productOwner=Produktverantwortlicher
quit=Beenden
quitSure=Bist du dir sicher?
scrumMaster=Scrum Master
server=Server
serverIP=Server-IP
serverPort=Server-Port
small=Klein
start=Start
theme=Thema
tictactoe=Tic Tac Toe
volume=Hauptlautst<EFBFBD>rke
windowed=Fenstermodus
yes=Ja
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabisch)
chinese=\u4e2d\u6587 (Chinesisch)
dutch=Nederlands (Niederl\u00e4ndisch)
english=English (Englisch)
french=Fran\u00e7ais (Franz\u00f6sisch)
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (Georgisch)
german=Deutsch
hindi=\u0939\u093f\u0928\u094d\u0926\u0940 (Hindi)
italian=Italiano (Italienisch)
japanese=\u65e5\u672c\u8a9e (Japanisch)
korean=\ud55c\uad6d\uc5b4 (Koreanisch)
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (Russisch)
spanish=Espa\u00f1ol (Spanisch)

View File

@@ -0,0 +1,67 @@
accept=Accept
ai=Artificial Intelligence
appTitle=ISY Games Selector
back=Back
backToMainMenu=Back to main menu
challengeText=You were challenged by
computer=Computer
computerDifficulty=Computer Difficulty
computerThinkTime=Computer Think Time
congratulations=Congratulations
connect=Connect
connectionType=Connection Type
credits=Credits
dark=Dark
dark-hc=Dark (High Contrast)
deny=Deny
developers=Developers
drawText=The game ended in a draw
effectsVolume=Effects Volume
musicVolume=Music Volume
fullscreen=Fullscreen
gameIsText=To a game of
goodGameText=Good game. Well played.
human=Human
language=Language
large=Large
layoutSize=Layout Size
light=Light
light-hc=Light (High Contrast)
local=Local
localization=Localization
medium=Medium
mergeCommander=Merge Commander
moralSupport=Moral Support
no=No
opengl=OpenGL
options=Options
othello=Othello
playerName=Player Name
productOwner=Product Owner
quit=Quit
quitSure=Are you sure?
scrumMaster=Scrum Master
server=Server
serverIP=Server IP
serverPort=Server Port
small=Small
start=Start
theme=Theme
tictactoe=Tic Tac Toe
volume=Master Volume
windowed=Windowed
yes=Yes
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabic)
chinese=\u4e2d\u6587 (Chinese)
dutch=Nederlands (Dutch)
english=English
french=Fran\u00e7ais (French)
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (Georgian)
german=Deutsch (German)
hindi=\u0939\u093f\u0928\u094d\u0926\u0940 (Hindi)
italian=Italiano (Italian)
japanese=\u65e5\u672c\u8a9e (Japanese)
korean=\ud55c\uad6d\uc5b4 (Korean)
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (Russian)
spanish=Espa\u00f1ol (Spanish)

View File

@@ -0,0 +1,63 @@
ai=Inteligencia Artificial
appTitle=Selector de Juegos ISY
back=Atr\u00e1s
backToMainMenu=Volver al men\u00FA principal
computer=Ordenador
computerDifficulty=Dificultad del Ordenador
computerThinkTime=Tiempo de pensamiento de la computadora
congratulations=Felicitaciones
connect=Conectar
connectionType=Tipo de conexi\u00F3n
credits=Cr\u00e9ditos
dark-hc=Oscuro (Alto Contraste)
dark=Oscuro
developers=Desarrolladores
drawText=El juego termin\u00F3 en empate
effectsVolume=Volumen de efectos
musicVolume=Music Volume (translate me)
fullscreen=Pantalla completa
goodGameText=Buen juego. Bien jugado.
human=Humano
language=Idioma
large=Grande
layoutSize=Tama\u00F1o del dise\u00F1o
light-hc=Claro (Alto Contraste)
light=Claro
local=Local
localization=Localizaci\u00f3n
medium=Mediano
mergeCommander=Comandante de Merge
moralSupport=Apoyo moral
no=No
opengl=OpenGL
options=Opciones
othello=Othello
playerName=Nombre del Jugador
productOwner=Propietario del Producto
quit=Salir
quitSure=\u00BFAst\u00e1s seguro?
scrumMaster=Scrum Master
server=Servidor
serverIP=Servidor-IP
serverPort=Servidor-puerto
small=Peque\u00F1o
start=Iniciar
theme=Tema
tictactoe=Tres en Raya
volume=Volumen principal
windowed=Ventana
yes=S\u00ed
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Ar\u00e1bigo)
chinese=\u4e2d\u6587 (Chino)
dutch=Nederlands (Neerland\u00e9s)
english=English (Ingl\u00e9s)
french=Fran\u00e7ais (Franc\u00e9s)
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (Georgiano)
german=Deutsch (Alem\u00e1n)
hindi=\u0939\u093f\u0928\u094d\u0926\u0940 (Hindi)
italian=Italiano (Italiano)
japanese=\u65e5\u672c\u8a9e (Japon\u00e9s)
korean=\ud55c\uad6d\uc5b4 (Coreano)
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (Ruso)
spanish=Espa\u00f1ol

View File

@@ -0,0 +1,63 @@
ai=Intelligence Artificielle
appTitle=S\u00e9lecteur de Jeux ISY
back=Retour
backToMainMenu=Retour au menu principal
computer=Ordinateur
computerDifficulty=Difficult\u00e9 de l'Ordinateur
computerThinkTime=Temps de r\u00e9flexion de l'ordinateur
congratulations=F\u00E9licitations
connect=Connecter
connectionType=Type de connexion
credits=Cr\u00e9dits
dark-hc=Sombre (Contraste \u00E9lev\u00E9)
dark=Sombre
developers=D\u00e9veloppeurs
drawText=La partie s'est termin\u00E9e par un match nul
effectsVolume=Volume des effets
musicVolume=Music Volume (translate me)
fullscreen=Plein \u00e9cran
goodGameText=Bien jou\u00E9. Bonne partie.
human=Humain
language=Langue
large=Grand
layoutSize=Taille de la disposition
light-hc=Clair (Contraste \u00E9lev\u00E9)
light=Clair
local=Local
localization=Localisation
medium=Moyen
mergeCommander=Commandant de Merge
moralSupport=Soutien moral
no=Non
opengl=OpenGL
options=Options
othello=Othello
playerName=Nom du joueur
productOwner=Responsable du produit
quit=Quitter
quitSure=Es-tu s\u00fbr ?
scrumMaster=Scrum Master
server=Serveur
serverIP=Serveur-IP
serverPort=Serveur-Port
small=Petit
start=D\u00e9marrer
theme=Th\u00E8me
tictactoe=Morpion
volume=Volume principal
windowed=Fen\u00eatre
yes=Oui
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabe)
chinese=\u4e2d\u6587 (Chinois)
dutch=Nederlands (N\u00e9erlandais)
english=English (Anglais)
french=Fran\u00e7ais
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (G\u00e9orgien)
german=Deutsch (Allemand)
hindi=\u0939\u093f\u0928\u094d\u0926\u0940 (Hindi)
italian=Italiano (Italien)
japanese=\u65e5\u672c\u8a9e (Japonais)
korean=\ud55c\uad6d\uc5b4 (Cor\u00e9en)
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (Russe)
spanish=Espa\u00f1ol (Espagnol)

View File

@@ -0,0 +1,63 @@
ai=\u092a\u094d\u0930\u0924\u093f\u092a\u094d\u0930\u0923\u093e\u0924\u094d\u092e\u093e\u0928 \u092a\u094d\u0930\u092c\u094d\u0939\u093e\u0935\u0924\u094d\u0924\u093e
appTitle=ISY \u0917\u0947\u092e \u0938\u0947\u0932\u0947\u0915\u094d\u091f\u0930
back=\u092a\u093f\u091a\u093e\u0932
backToMainMenu=\u092E\u0947\u0928 \u092E\u0947\u0928\u0942 \u092A\u0930 \u0935\u093E\u092A\u0938 \u091C\u093E\u090F\u0902
computer=\u0915\u092e\u092a\u094d\u092f\u0942\u091f\u0930
computerDifficulty=\u0915\u092e\u092a\u094d\u092f\u0942\u091f\u0930 \u092a\u094d\u0930\u092f\u093e\u0938
computerThinkTime=\u0915\u0902\u092a\u094d\u092f\u0942\u091f\u0930 \u0915\u0940 \u092a\u0930 \u092b\u0930 \u0938\u092e\u092f
congratulations=\u092C\u0927\u093E\u0908
connect=\u091c\u0942\u0921\u094d\u0921 \u0915\u0930\u0947\u0902
connectionType=\u0915\u0928\u0947\u0915\u094D\u0936\u0928 \u0915\u093E \u092A\u094D\u0930\u0915\u093E\u0930
credits=\u0916\u094d\u092f\u093e\u0924\u0947
dark-hc=\u0915\u093E\u0932\u093E (\u090A\u091A\u094D\u091A \u0915\u0949\u0928\u094D\u091F\u094D\u0930\u093E\u0938\u094D\u091F)
dark=\u0915\u093E\u0932\u093E
developers=\u0935\u093f\u0915\u0938\u093f\u0915\u0930
drawText=\u0916\u0947\u0932 \u091F\u0940\u091A \u092A\u0930 \u0916\u0924\u094D\u092E \u0939\u094B \u0917\u092F\u0940
effectsVolume=\u092a\u0631\u094d\u092a\u093e\u0935 \u0935\u094b\u0932\u094d\u092e
musicVolume=Music Volume (translate me)
fullscreen=\u092a\u0942\u0930\u094d\u0923 \u0938\u0915\u0940\u0928\u093e
goodGameText=\u0905\u091A\u094D\u091B\u0940 \u0916\u0947\u0932 \u0925\u0940\u0964 \u091B\u0940 \u0916\u0942\u092C \u0916\u0947\u0932\u093E.
human=\u092e\u093e\u0928\u0935
language=\u092d\u093e\u0937\u093e
large=\u092C\u0921\u093C\u093E
layoutSize=\u0930\u0942\u092A\u0930\u0947\u0916 \u0915\u093E \u0906\u0915\u093E\u0930
light-hc=\u091A\u094D\u092E\u092C\u0940\u0932\u093E (\u090A\u091A\u094D\u091A \u0915\u0949\u0928\u094D\u091F\u094D\u0930\u093E\u0938\u094D\u091F)
light=\u091A\u094D\u092E\u092C\u0940\u0932\u093E
local=\u0938\u094d\u0925\u093e\u0928\u093f\u092f
localization=\u0938\u094d\u0925\u093e\u0928\u093f\u092f\u0915\u0930\u0923
medium=\u092E\u0927\u094D\u092F\u092E
mergeCommander=\u092e\u0930\u094d\u091c \u0915\u092e\u0902\u0921\u0930
moralSupport=\u0928\u094d\u092e\u093e\u0928\u093f\u0915 \u0938\u092e\u0930\u094d\u0925\u0928
no=\u0928\u0939\u0940\u0902
opengl=OpenGL
options=\u0935\u093f\u0915\u0932\u094d\u092a
othello=\u0913\u0925\u0940\u0932\u094b
playerName=\u0915\u0941\u0930\u093e\u0930\u0940 \u0928\u093e\u092e
productOwner=\u0906\u092f\u0947\u0915\u093e \u092e\u093e\u0932\u093f\u0915
quit=\u0938\u094e\u091c\u094d
quitSure=\u0915\u094d\u092f\u093e \u0915\u094d\u092f\u093e \u091f\u0940\u091f \u0939\u0948\u0902?
scrumMaster=\u0938\u094d\u0915\u094d\u0930\u0941\u092e \u092e\u093e\u0938\u094d\u091f\u0930
server=\u0938\u0930\u094d\u0935\u0930
serverIP=\u0938\u0930\u094d\u0935\u0930 IP
serverPort=\u0938\u0930\u094d\u0935\u0930 \u092a\u094b\u0930\u094d\u091f
small=\u091B\u094B\u091F\u093E
start=\u092b\u093f\u0930\u0942
theme=\u0925\u0940\u092E
tictactoe=\u091f\u093f\u0915 \u091f\u0948\u0915 \u091f\u094b
volume=\u092e\u0941\u0916\u094d\u092f \u0906\u0935\u093e\u091c
windowed=\u0915\u094d\u0930\u094d\u0939 \u092e\u0947\u0902
yes=\u0939\u093e\u0907
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u0905\u0930\u092c\u0940)
chinese=\u4e2d\u6587 (\u091a\u0940\u0928\u0940)
dutch=Nederlands (\u0921\u091a)
english=English (\u0905\u0902\u0917\u094d\u0930\u0947\u091c\u0940)
french=Fran\u00e7ais (\u092b\u094d\u0930\u0947\u0902\u091a)
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (\u091c\u094d\u091c\u094b\u0930\u094d\u091c\u093f\u092f\u0928)
german=Deutsch (\u091c\u0930\u094d\u092e\u0928)
hindi=\u0939\u093f\u0928\u094d\u0926\u0940
italian=Italiano (\u0907\u091f\u093e\u0932\u093f\u092f\u0928)
japanese=\u65e5\u672c\u8a9e (\u091c\u093e\u092a\u093e\u0928\u0940)
korean=\ud55c\uad6d\uc5b4 (\u0915\u094b\u0930\u093f\u092f\u0928)
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (\u0930\u0942\u0938\u0940)
spanish=Espa\u00f1ol (\u0938\u094d\u092a\u0947\u0928\u093f\u0936)

View File

@@ -0,0 +1,63 @@
ai=Intelligenza Artificiale
appTitle=Selettore di Giochi ISY
back=Indietro
backToMainMenu=Ritorna al menu principale
computer=Computer
computerDifficulty=Difficolt\u00e0 del computer
computerThinkTime=Tempo di pensiero del computer
congratulations=Congratulazioni
connect=Connetti
connectionType=Tipo di connessione
credits=Crediti
dark-hc=Scuro (Alto Contrasto)
dark=Scuro
developers=Sviluppatori
drawText=La partita \u00E8 terminata in parit\u00E0
effectsVolume=Volume effetti
musicVolume=Music Volume (translate me)
fullscreen=Schermo intero
goodGameText=Bel gioco. Ben giocato.
human=Umano
language=Lingua
large=Grande
layoutSize=Dimensione Layout
light-hc=Chiaro (Alto Contrasto)
light=Chiaro
local=Locale
localization=Localizzazione
medium=Medio
mergeCommander=Comandante di Merge
moralSupport=Supporto morale
no=No
opengl=OpenGL
options=Opzioni
othello=Othello
playerName=Nome del giocatore
productOwner=Proprietario del prodotto
quit=Uscire
quitSure=Sei sicuro?
scrumMaster=Scrum Master
server=Server
serverIP=Server-IP
serverPort=Porta del server
small=Piccolo
start=Inizia
theme=Tema
tictactoe=Tic Tac Toe
volume=Volume principale
windowed=Finestra
yes=S\u00ec
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabo)
chinese=\u4e2d\u6587 (Cinese)
dutch=Nederlands (Olandese)
english=English (Inglese)
french=Fran\u00e7ais (Francese)
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (Georgiano)
german=Deutsch (Tedesco)
hindi=\u0939\u093f\u0928\u094d\u0926\u0940 (Hindi)
italian=Italiano
japanese=\u65e5\u672c\u8a9e (Giapponese)
korean=\ud55c\uad6d\uc5b4 (Coreano)
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (Russo)
spanish=Espa\u00f1ol (Spagnolo)

View File

@@ -0,0 +1,63 @@
ai=\u4eba\u5de5\u77e5\u80fd
appTitle=ISY \u30b2\u30fc\u30e0\u30bb\u30ec\u30af\u30bf\u30fc
back=\u623b\u308b
backToMainMenu=\u30E1\u30A4\u30F3\u30E1\u30CB\u30E5\u30FC\u306B\u623B\u308B
computer=\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc
computerDifficulty=\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u96e3\u6613\u5ea6
computerThinkTime=\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u306e\u8003\u3048\u6642\u9593
congratulations=\u304A\u3081\u3067\u3068\u3046\u3054\u3056\u3044\u307E\u3059
connect=\u63a5\u7d9a
connectionType=\u63A5\u7D9A\u30BF\u30A4\u30D7
credits=\u30af\u30ec\u30b8\u30c3\u30c8
dark-hc=\u30C0\u30FC\u30AF (\u9AD8\u30B3\u30F3\u30C8\u30E9\u30B9\u30C8)
dark=\u30C0\u30FC\u30AF
developers=\u958b\u767a\u8005
drawText=\u30B2\u30FC\u30E0\u306F\u5E73\u7B49\u306B\u7D42\u4E86\u3057\u307E\u3057\u305F
effectsVolume=\u30a8\u30d5\u30a7\u30af\u30c8\u30dc\u30ea\u30e5\u30fc\u30e0
musicVolume=Music Volume (translate me)
fullscreen=\u5168\u753b\u9762
goodGameText=\u3044\u3044\u30B2\u30FC\u30E0\u3067\u3057\u305F\u3002\u3088\u304F\u6226\u3044\u307E\u3057\u305F\u3002
human=\u4eba\u9593
language=\u8a00\u8a9e
large=\u5927
layoutSize=\u30EC\u30A4\u30A2\u30A6\u30C8\u30B5\u30A4\u30BA
light-hc=\u30E9\u30A4\u30C8 (\u9AD8\u30B3\u30F3\u30C8\u30E9\u30B9\u30C8)
light=\u30E9\u30A4\u30C8
local=\u5730\u57df
localization=\u30ed\u30fc\u30ab\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3
medium=\u4E2D
mergeCommander=\u30de\u30fc\u30b8\u30b3\u30de\u30f3\u30c0\u30fc
moralSupport=\u6c17\u529b\u652f\u63f4
no=\u3044\u3044\u3048
opengl=OpenGL
options=\u30aa\u30d7\u30b7\u30e7\u30f3
othello=\u30aa\u30bb\u30ed
playerName=\u30d7\u30ec\u30a4\u30e4\u30fc\u540d
productOwner=\u30d7\u30ed\u30c0\u30af\u30c8\u30aa\u30fc\u30ca\u30fc
quit=\u7d42\u4e86
quitSure=\u672c\u5f53\u306b\u7d42\u4e86\u3057\u307e\u3059\u304b\uff1f
scrumMaster=\u30b9\u30af\u30e9\u30e0\u30de\u30b9\u30bf\u30fc
server=\u30b5\u30fc\u30d0\u30fc
serverIP=\u30b5\u30fc\u30d0\u30fc IP
serverPort=\u30b5\u30fc\u30d0\u30fc \u30dd\u30fc\u30c8
small=\u5C0F
start=\u59cb\u307e\u308a
theme=\u30C6\u30FC\u30DE
tictactoe=\u30bf\u30a4\u30af\u30bf\u30c3\u30c8\u30c8\u30a6
volume=\u30de\u30b9\u30bf\u30fc\u30dc\u30ea\u30e5\u30fc\u30e0
windowed=\u30a6\u30a3\u30f3\u30c9\u30a6
yes=\u306f\u3044
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u30a2\u30e9\u30d3\u30a2\u8a9e)
chinese=\u4e2d\u6587 (\u4e2d\u6587)
dutch=Nederlands (\u30aa\u30e9\u30f3\u30c0\u8a9e)
english=English (\u82f1\u8a9e)
french=Fran\u00e7ais (\u30d5\u30e9\u30f3\u30b9\u8a9e)
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (\u30b0\u30eb\u30b8\u30a2\u8a9e)
german=Deutsch (\u30c9\u30a4\u30c4\u8a9e)
hindi=\u0939\u093f\u0928\u094d\u0926\u0940 (\u30d2\u30f3\u30c7\u30a3\u8a9e)
italian=Italiano (\u30a4\u30bf\u30ea\u30a2\u8a9e)
japanese=\u65e5\u672c\u8a9e
korean=\ud55c\uad6d\uc5b4 (\u97d3\u56fd\u8a9e)
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (\u30ed\u30b7\u30a2\u8a9e)
spanish=Espa\u00f1ol (\u30b9\u30da\u30a4\u30f3\u8a9e)

View File

@@ -0,0 +1,63 @@
ai=\uc778\uacf5 \uc9c0\ub2a5
appTitle=ISY \uac8c\uc784 \uc120\ud0dd\uae30
back=\ub4a4\ub85c
backToMainMenu=\uBA54\uC778 \uBA54\uB274\uB85C \uB3CC\uC544\uAC00\uAE30
computer=\uce74\ud14c\uae4c
computerDifficulty=\uce74\ud14c\uae4c \ub2e8\uacc4
computerThinkTime=\uc870\ub9ac\ud558\ub294 \uc2dc\uac04
congratulations=\uCD95\uD558\uD569\uB2C8\uB2E4
connect=\uc5f0\uacb0
connectionType=\uC5F0\uACB0 \uC720\uD615
credits=\uac10\uc0ac
dark-hc=\uC5B4\uB460 (\uACE0 \uB300\uBE44)
dark=\uC5B4\uB460
developers=\uac1c\ubc1c\uc790
drawText=\uAC8C\uC784\uC740 \uBB34\uC2B9\uBD84\uC73C\uB85C \uB05D\uB0AC\uC2B5\uB2C8\uB2E4
effectsVolume=\ud654\uac70 \ubc84\uc804
musicVolume=Music Volume (translate me)
fullscreen=\uc804\uccb4 \ud654\uba74
goodGameText=\uC88B\uC740 \uAC8C\uC784\uC774\uC600\uC2B5\uB2C8\uB2E4. \uC798 \uD50C\uB808\uC774\uD588\uC2B5\uB2C8\uB2E4.
human=\uc778\uac04
language=\uc5b8\uc5b4
large=\uD070
layoutSize=\uB808\uC774\uC544\uC6C3 \uD06C\uAE30
light-hc=\uBC1D\uC74C (\uACE0 \uB300\uBE44)
light=\uBC1D\uC74C
local=\ub85c\uceec
localization=\uc5b8\uc5b4\ud654
medium=\uBCF4\uD1B5
mergeCommander=\uba54\uc9c0 \ucea0\ub9ac\ub354
moralSupport=\uc815\uc2e0\uc801 \uc9c0\uc6d0
no=\uc544\ub2c8\uc624
opengl=OpenGL
options=\uc635\uc158
othello=\uc624\ud14c\ub85c
playerName=\ud50c\ub808\uc774\uc5b4 \uc774\ub984
productOwner=\uc81c\ud488 \uad00\ub9ac\uc790
quit=\uc885\ub8cc
quitSure=\uc815\ub9d0 \uc885\ub8cc\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
scrumMaster=\uc2a4\ud06c\ub7fc \ub9c8\uc2a4\ud130
server=\uc11c\ubc84
serverIP=\uc11c\ubc84 IP
serverPort=\uc11c\ubc84 \ud3ec\ud2b8
small=\uC791\uC74C
start=\uc2dc\uc791
theme=\uC8FC\uC81C
tictactoe=\ud2f0\ud06c\ud0d0\ud1a0
volume=\ub9c8\uc2a4\ud130 \ubcfc\ub80c
windowed=\ucc3d \ubaa8\ub4dc
yes=\ub124
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u0639\u0631\u0628\u064a\u0629)
chinese=\u4e2d\u6587 (\u4e2d\u6587)
dutch=Nederlands (\ub3c4\ucc99)
english=English (\uc601\uad6d\uc5b4)
french=Fran\u00e7ais (\ud504\ub791\uc81c\uc2a4\ucf54)
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (\uac8c\uc774\uc874)
german=Deutsch (\ub3c4\ucf54)
hindi=\u0939\u093f\u0928\u094d\u0926\u0940 (\ud567\ub9ac)
italian=Italiano (\uc774\ud0c0\ub9ac\uc5b4\ub098)
japanese=\u65e5\u672c\u8a9e (\uc65c\uc790\ub9ac\uc5b4)
korean=\ud55c\uad6d\uc5b4
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (\ub85c\uc6b0\uc2a4\uc544\uc774\ucf58)
spanish=Espa\u00f1ol (\uc2a4\ud398\ub974\uc2a4\uc544\uc774\ucf58)

View File

@@ -0,0 +1,63 @@
ai=Kunstmatige Intelligentie
appTitle=ISY Spel Kiezer
back=Terug
backToMainMenu=Terug naar hoofdmenu
computer=Computer
computerDifficulty=Computermoeilijkheid
computerThinkTime=Computer Denk Tijd
congratulations=Gefeliciteerd
connect=Verbinden
connectionType=Verbindingstype
credits=Credits
dark-hc=Donker (Hoog Contrast)
dark=Donker
developers=Ontwikkelaars
drawText=Het spel eindigde in een gelijkspel
effectsVolume=Effecten Volume
musicVolume=Music Volume (translate me)
fullscreen=Volledig scherm
goodGameText=Goed gespeeld. Mooie wedstrijd.
human=Mens
language=Taal
large=Groot
layoutSize=Lay-outgrootte
light-hc=Licht (Hoog Contrast)
light=Licht
local=Lokaal
localization=Lokalisatie
medium=Middel
mergeCommander=Merge-commandant
moralSupport=Moraalsteun
no=Nee
opengl=OpenGL
options=Opties
othello=Othello
playerName=Spelernaam
productOwner=Producteigenaar
quit=Afsluiten
quitSure=Weet je het zeker?
scrumMaster=Scrum Master
server=Server
serverIP=Server-IP
serverPort=Serverpoort
small=Klein
start=Start
theme=Thema
tictactoe=Boter Kaas en Eieren
volume=Hoofdvolume
windowed=Venstermodus
yes=Ja
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabisch)
chinese=\u4e2d\u6587 (Chinees)
dutch=Nederlands
english=English (Engels)
french=Fran\u00e7ais (Frans)
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (Georgisch)
german=Deutsch (Duits)
hindi=\u0939\u093f\u0928\u094d\u0926\u0940 (Hindi)
italian=Italiano (Italiaans)
japanese=\u65e5\u672c\u8a9e (Japans)
korean=\ud55c\uad6d\uc5b4 (Koreaans)
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (Russisch)
spanish=Espa\u00f1ol (Spaans)

View File

@@ -0,0 +1,63 @@
ai=\u0418\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442
appTitle=ISY \u0412\u044b\u0431\u043e\u0440 \u0438\u0433\u0440
back=\u041d\u0430\u0437\u0430\u0434
backToMainMenu=\u041D\u0430\u0437\u0430\u0434 \u0432 \u0433\u043B\u0430\u0432\u043D\u043E\u0435 \u043C\u0435\u043D\u044E
computer=\u041a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440
computerDifficulty=\u0421\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0430
computerThinkTime=\u0412\u0440\u0435\u043c\u044f \u043e\u0431\u0434\u0443\u043c\u044b \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0430
congratulations=\u041F\u043E\u0437\u0434\u0440\u0430\u0432\u043B\u044F\u0435\u043C
connect=\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f
connectionType=\u0442\u0438\u043F \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u044F
credits=\u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u043d\u043e\u0441\u0442\u0438
dark-hc=\u0442\u0451\u043C\u043D\u044B\u0439 (\u0432\u044B\u0441\u043E\u043A\u0438\u0439 \u043A\u043E\u043D\u0442\u0440\u0430\u0441\u0442)
dark=\u0442\u0451\u043C\u043D\u044B\u0439
developers=\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438
drawText=\u0418\u0433\u0440\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u043D\u0438\u0447\u044C\u0435\u0439
effectsVolume=\u042d\u0444\u0444\u0435\u043a\u0442\u044b \u0433\u0440\u043e\u043c\u043a\u043e\u0441\u0442\u044c
musicVolume=Music Volume (translate me)
fullscreen=\u041f\u043e\u043b\u043d\u043e\u044d\u043a\u0440\u0430\u043d\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c
goodGameText=\u0425\u043E\u0440\u043E\u0448\u0430\u044F \u0438\u0433\u0440\u0430. \u0425\u043E\u0440\u043E\u0448\u043E \u0441\u044B\u0433\u0440\u0430\u043D\u043E.
human=\u0427\u0435\u043b\u043e\u0432\u0435\u043a
language=\u042f\u0437\u044b\u043a
large=\u0431\u043E\u043B\u044C\u0448\u043E\u0439
layoutSize=\u0440\u0430\u0437\u043C\u0435\u0440 \u043C\u0430\u043A\u0435\u0442\u0430
light-hc=\u0441\u0432\u0435\u0442\u043B\u044B\u0439 (\u0432\u044B\u0441\u043E\u043A\u0438\u0439 \u043A\u043E\u043D\u0442\u0440\u0430\u0441\u0442)
light=\u0441\u0432\u0435\u0442\u043B\u044B\u0439
local=\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439
localization=\u041b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f
medium=\u0441\u0440\u0435\u0434\u043D\u0438\u0439
mergeCommander=\u041a\u043e\u043c\u0430\u043d\u0434\u0435\u0440 \u0441\u043b\u0438\u044f\u043d\u0438\u044f
moralSupport=\u041c\u043e\u0440\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430
no=\u041d\u0435\u0442
opengl=OpenGL
options=\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438
othello=\u041e\u0442\u0435\u043b\u043b\u043e
playerName=\u0418\u043c\u044f \u0438\u0433\u0440\u043e\u043a\u0430
productOwner=\u0412\u043b\u0430\u0434\u0435\u043b\u0435\u0446 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430
quit=\u0412\u044b\u0445\u043e\u0434
quitSure=\u0423\u0432\u0435\u0440\u0435\u043d\u044b \u043b\u0438?
scrumMaster=\u041c\u0430\u0441\u0442\u0435\u0440 Scrum
server=\u0421\u0435\u0440\u0432\u0435\u0440
serverIP=\u0418\u043f\u0440\u0435\u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430
serverPort=\u041f\u043e\u0440\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430
small=\u043C\u0430\u043B\u0435\u043D\u044C\u043A\u0438\u0439
start=\u0421\u0442\u0430\u0440\u0442
theme=\u0442\u0435\u043C\u0430
tictactoe=\u041a\u0440\u0435\u0441\u0442\u0438\u043a\u0438
volume=\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0433\u0440\u043e\u043c\u043a\u043e\u0441\u0442\u044c
windowed=\u041e\u043a\u043d\u043e
yes=\u0414\u0430
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u0410\u0440\u0430\u0431\u0441\u043a\u0438\u0439)
chinese=\u4e2d\u6587 (\u041a\u0438\u0442\u0430\u0439\u0441\u043a\u0438\u0439)
dutch=Nederlands (\u041d\u0438\u0434\u0435\u0440\u043b\u0430\u043d\u0434\u0441\u043a\u0438\u0439)
english=English (\u0410\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439)
french=Fran\u00e7ais (\u0424\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438\u0439)
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (\u0413\u0440\u0443\u0437\u0438\u043d\u0441\u043a\u0438\u0439)
german=Deutsch (\u041d\u0435\u043c\u0435\u0446\u043a\u0438\u0439)
hindi=\u0939\u093f\u0928\u094d\u0926\u0940 (\u0425\u0438\u043d\u0434\u0438)
italian=Italiano (\u0418\u0442\u0430\u043b\u044c\u044f\u043d\u0441\u043a\u0438\u0439)
japanese=\u65e5\u672c\u8a9e (\u042f\u043f\u043e\u043d\u0441\u043a\u0438\u0439)
korean=\ud55c\uad6d\uc5b4 (\u041a\u043e\u0440\u0435\u0439\u0441\u043a\u0438\u0439)
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439
spanish=Espa\u00f1ol (\u0418\u0441\u043f\u0430\u043d\u0441\u043a\u0438\u0439)

View File

@@ -0,0 +1,63 @@
ai=\u4eba\u5de5\u667a\u80fd
appTitle=ISY \u6e38\u620f\u9009\u62e9\u5668
back=\u8fd4\u56de
backToMainMenu=\u8FD4\u56DE\u4E3B\u83DC\u5355
computer=\u8ba1\u7b97\u673a
computerDifficulty=\u8ba1\u7b97\u673a\u96be\u5ea6
computerThinkTime=\u7535\u8111\u8003\u616e\u65f6\u95f4
congratulations=\u606D\u559C
connect=\u8fde\u63a5
connectionType=\u8FDE\u63A5\u7C7B\u578B
credits=\u6b23\u8d4f
dark-hc=\u6697 (\u9AD8\u5BF9\u6BD4)
dark=\u6697
developers=\u5f00\u53d1\u8005
drawText=\u6E38\u620F\u4EE5\u5E73\u5C40\u7ED3\u675F
effectsVolume=\u6548\u679c\u91cf
musicVolume=Music Volume (translate me)
fullscreen=\u5168\u5c4f
goodGameText=\u6E38\u620F\u5F88\u597D. \u8868\u73B0\u4F18\u79C0.
human=\u4eba
language=\u8bed\u8a00
large=\u5927
layoutSize=\u5E03\u5C40\u5927\u5C0F
light-hc=\u4EAE (\u9AD8\u5BF9\u6BD4)
light=\u4EAE
local=\u672c\u5730
localization=\u6d88\u606f\u5c55\u793a / \u672c\u5730\u5316
medium=\u4E2D
mergeCommander=Merge \u63a7\u4e3b
moralSupport=\u795e\u7cbe\u652f\u6301
no=\u5426
opengl=OpenGL
options=\u9009\u9879
othello=Othello
playerName=\u73a9\u5bb6\u540d\u79f0
productOwner=\u54c1\u76d8\u4ea7\u54c1\u4eba
quit=\u9000\u51fa
quitSure=\u4f60\u786e\u5b9a\u5417?
scrumMaster=Scrum \u4f1a\u5458
server=\u670d\u52a1\u5668
serverIP=\u670d\u52a1\u5668 IP
serverPort=\u670d\u52a1\u5668 \u7aef\u53e3
small=\u5C0F
start=\u5f00\u59cb
theme=\u4E3B\u9898
tictactoe=Tic Tac Toe
volume=\u4e3b\u97f3\u91cf
windowed=\u7a97\u53e3\u6a21\u5f0f
yes=\u662f
arabic=\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (\u963f\u62c9\u4f2f\u8bed)
chinese=\u4e2d\u6587
dutch=Nederlands (\u8377\u5170\u8bed)
english=English (\u82f1\u8bed)
french=Fran\u00e7ais (\u6cd5\u8bed)
georgian=\u10e5\u10d0\u10e0\u10d4\u10e1\u10d8 (\u683c\u9c81\u5409\u4e9a\u8bed)
german=Deutsch (\u5fb7\u8bed)
hindi=\u0939\u093f\u0928\u094d\u0926\u0940 (\u5370\u5ea6\u8bed)
italian=Italiano (\u610f\u5927\u5229\u8bed)
japanese=\u65e5\u672c\u8a9e (\u65e5\u8bed)
korean=\ud55c\uad6d\uc5b4 (\u97e9\u8bed)
russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (\u4fc4\u8bed)
spanish=Espa\u00f1ol (\u897f\u73ed\u7259\u8bed)

View File

@@ -0,0 +1,215 @@
/* ----------------------------
.background
----------------------------- */
.bg-primary {
-fx-background-color: #0a0a0a;
}
.bg-secondary {
-fx-background-color: #1a1a1a;
}
.bg-popup {
-fx-background-color: #0a0a0acc;
}
/* ----------------------------
.button
----------------------------- */
.button {
/* Layout */
-fx-padding: 10 20;
-fx-background-radius: 6;
-fx-cursor: hand;
/* Color */
-fx-background-color: #1a1a1a;
-fx-text-fill: #ffffff;
-fx-border-color: #ffffff;
-fx-border-width: 1;
/* Effects */
-fx-effect: dropshadow(gaussian, #00000099, 4, 0, 0, 1);
}
.button:hover {
-fx-background-color: #2a2a2a;
-fx-border-color: #00ff00;
}
/* ----------------------------
.choice-box
----------------------------- */
.choice-box {
/* Layout */
-fx-padding: 6;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #1a1a1a;
-fx-border-color: #ffffff;
-fx-border-width: 1;
-fx-mark-color: #ffffff;
}
.choice-box:hover {
-fx-border-color: #00ff00;
}
.choice-box:focused {
-fx-border-color: #00cc66;
}
.choice-box .label {
-fx-text-fill: #ffffff;
}
/* ----------------------------
.choice-box popup styling
----------------------------- */
.choice-box .context-menu {
/* Layout */
-fx-padding: 4;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #1a1a1a;
-fx-border-color: #ffffff;
-fx-border-width: 1;
}
.choice-box .menu-item {
/* Layout */
-fx-padding: 6 12;
}
.choice-box .menu-item .label {
-fx-text-fill: #ffffff;
}
.choice-box .menu-item:hover {
-fx-background-color: #2a2a2a;
}
.choice-box .menu-item:focused {
-fx-background-color: #00ff00;
-fx-text-fill: #000000;
}
/* ----------------------------
.container
----------------------------- */
.container {
/* Layout */
-fx-padding: 10;
-fx-alignment: center;
/* Color */
-fx-background-color: #1a1a1a;
}
/* ----------------------------
.input
----------------------------- */
.input {
/* Layout */
-fx-padding: 8;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #1a1a1a;
-fx-text-fill: #ffffff;
-fx-border-color: #ffffff;
-fx-border-width: 1;
}
.input:hover {
-fx-border-color: #00ff00;
}
.input:focused {
-fx-border-color: #00cc66;
}
/* ----------------------------
.separator
----------------------------- */
.separator {
/* Layout */
-fx-padding: 10 0;
}
.separator .line {
/* Color */
-fx-border-color: #ffffff;
-fx-border-width: 0 0 1 0;
}
/* ----------------------------
.slider
----------------------------- */
.slider {
/* Layout */
-fx-padding: 6 0;
/* Color */
-fx-background-color: transparent;
}
.slider .track {
/* Color */
-fx-background-color: linear-gradient(to left, #00ff00, #ff0000);
-fx-background-insets: 0;
-fx-background-radius: 2;
-fx-pref-height: 4;
}
.slider .thumb {
/* Color */
-fx-background-color: #ffffff;
-fx-background-radius: 50%;
/* Effects */
-fx-effect: dropshadow(gaussian, #000000aa, 4, 0, 0, 1);
}
.slider .thumb:hover {
-fx-scale-x: 1.2;
-fx-scale-y: 1.2;
}
/* ----------------------------
.text-header
----------------------------- */
.text-header {
-fx-fill: #ffffff;
-fx-text-fill: #ffffff;
}
/* ----------------------------
.text-normal
----------------------------- */
.text-normal {
-fx-fill: #f0f0f0;
-fx-text-fill: #f0f0f0;
}
/* ----------------------------
.toggle-button
----------------------------- */
.toggle {
/* Layout */
-fx-padding: 8 16;
-fx-background-radius: 6;
/* Color */
-fx-background-color: #1f1f1f;
-fx-text-fill: #ffffff;
-fx-border-color: #ffffff;
}
.toggle:hover {
-fx-background-color: #00ff00;
-fx-text-fill: #000000;
-fx-border-color: #00ff00;
}

View File

@@ -0,0 +1,215 @@
/* ----------------------------
.background
----------------------------- */
.bg-primary {
-fx-background-color: #181818;
}
.bg-secondary {
-fx-background-color: #2a2a2a;
}
.bg-popup {
-fx-background-color: #1818187f;
}
/* ----------------------------
.button
----------------------------- */
.button {
/* Layout */
-fx-padding: 10 20;
-fx-background-radius: 6;
-fx-cursor: hand;
/* Color */
-fx-background-color: #2a2a2a;
-fx-text-fill: #f0f0f0;
-fx-border-color: #3a3a3a;
-fx-border-width: 1;
/* Effects */
-fx-effect: dropshadow(gaussian, #0000004d, 4, 0, 0, 1);
}
.button:hover {
-fx-background-color: #3a3a3a;
-fx-border-color: #4caf50;
}
/* ----------------------------
.choice-box
----------------------------- */
.choice-box {
/* Layout */
-fx-padding: 6;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #2a2a2a;
-fx-border-color: #444444;
-fx-border-width: 1;
-fx-mark-color: #f0f0f0;
}
.choice-box:hover {
-fx-border-color: #4caf50;
}
.choice-box:focused {
-fx-border-color: #81c784;
}
.choice-box .label {
-fx-text-fill: #f0f0f0;
}
/* ----------------------------
.choice-box popup styling
----------------------------- */
.choice-box .context-menu {
/* Layout */
-fx-padding: 4;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #2a2a2a;
-fx-border-color: #444444;
-fx-border-width: 1;
}
.choice-box .menu-item {
/* Layout */
-fx-padding: 6 12;
}
.choice-box .menu-item .label {
-fx-text-fill: #dddddd;
}
.choice-box .menu-item:hover {
-fx-background-color: #3a3a3a;
}
.choice-box .menu-item:focused {
-fx-background-color: #4caf50;
-fx-text-fill: #ffffff;
}
/* ----------------------------
.container
----------------------------- */
.container {
/* Layout */
-fx-padding: 10;
-fx-alignment: center;
/* Color */
-fx-background-color: #2a2a2a;
}
/* ----------------------------
.input
----------------------------- */
.input {
/* Layout */
-fx-padding: 8;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #2a2a2a;
-fx-text-fill: #f0f0f0;
-fx-border-color: #444444;
-fx-border-width: 1;
}
.input:hover {
-fx-border-color: #4caf50;
}
.input:focused {
-fx-border-color: #81c784;
}
/* ----------------------------
.separator
----------------------------- */
.separator {
/* Layout */
-fx-padding: 10 0;
}
.separator .line {
/* Color */
-fx-border-color: #3a3a3a;
-fx-border-width: 0 0 1 0;
}
/* ----------------------------
.slider
----------------------------- */
.slider {
/* Layout */
-fx-padding: 6 0;
/* Color */
-fx-background-color: transparent;
}
.slider .track {
/* Color */
-fx-background-color: linear-gradient(to left, #4caf50, #f44336);
-fx-background-insets: 0;
-fx-background-radius: 2;
-fx-pref-height: 4;
}
.slider .thumb {
/* Color */
-fx-background-color: #f0f0f0;
-fx-background-radius: 50%;
/* Effects */
-fx-effect: dropshadow(gaussian, #00000066, 4, 0, 0, 1);
}
.slider .thumb:hover {
-fx-scale-x: 1.2;
-fx-scale-y: 1.2;
}
/* ----------------------------
.text-header
----------------------------- */
.text-header {
-fx-fill: #f0f0f0;
-fx-text-fill: #f0f0f0;
}
/* ----------------------------
.text-normal
----------------------------- */
.text-normal {
-fx-fill: #dddddd;
-fx-text-fill: #dddddd;
}
/* ----------------------------
.toggle-button
----------------------------- */
.toggle {
/* Layout */
-fx-padding: 8 16;
-fx-background-radius: 6;
/* Color */
-fx-background-color: #333333;
-fx-text-fill: #cccccc;
-fx-border-color: #4a4a4a;
}
.toggle:hover {
-fx-background-color: #4caf50;
-fx-text-fill: #ffffff;
-fx-border-color: #4caf50;
}

View File

@@ -0,0 +1,11 @@
.text-header {
-fx-font-family: "Arial";
-fx-font-size: 24px;
-fx-font-weight: bold;
}
.text-normal {
-fx-font-family: "Arial";
-fx-font-size: 20px;
-fx-font-weight: normal;
}

View File

@@ -0,0 +1,215 @@
/* ----------------------------
.background
----------------------------- */
.bg-primary {
-fx-background-color: #ffffff;
}
.bg-secondary {
-fx-background-color: #f2f2f2;
}
.bg-popup {
-fx-background-color: #ffffffcc;
}
/* ----------------------------
.button
----------------------------- */
.button {
/* Layout */
-fx-padding: 10 20;
-fx-background-radius: 6;
-fx-cursor: hand;
/* Color */
-fx-background-color: #f2f2f2;
-fx-text-fill: #000000;
-fx-border-color: #000000;
-fx-border-width: 1;
/* Effects */
-fx-effect: dropshadow(gaussian, #00000033, 4, 0, 0, 1);
}
.button:hover {
-fx-background-color: #e0e0e0;
-fx-border-color: #008000;
}
/* ----------------------------
.choice-box
----------------------------- */
.choice-box {
/* Layout */
-fx-padding: 6;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #ffffff;
-fx-border-color: #000000;
-fx-border-width: 1;
-fx-mark-color: #000000;
}
.choice-box:hover {
-fx-border-color: #008000;
}
.choice-box:focused {
-fx-border-color: #009900;
}
.choice-box .label {
-fx-text-fill: #000000;
}
/* ----------------------------
.choice-box popup styling
----------------------------- */
.choice-box .context-menu {
/* Layout */
-fx-padding: 4;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #ffffff;
-fx-border-color: #000000;
-fx-border-width: 1;
}
.choice-box .menu-item {
/* Layout */
-fx-padding: 6 12;
}
.choice-box .menu-item .label {
-fx-text-fill: #000000;
}
.choice-box .menu-item:hover {
-fx-background-color: #e0e0e0;
}
.choice-box .menu-item:focused {
-fx-background-color: #008000;
-fx-text-fill: #ffffff;
}
/* ----------------------------
.container
----------------------------- */
.container {
/* Layout */
-fx-padding: 10;
-fx-alignment: center;
/* Color */
-fx-background-color: #f9f9f9;
}
/* ----------------------------
.input
----------------------------- */
.input {
/* Layout */
-fx-padding: 8;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #ffffff;
-fx-text-fill: #000000;
-fx-border-color: #000000;
-fx-border-width: 1;
}
.input:hover {
-fx-border-color: #008000;
}
.input:focused {
-fx-border-color: #009900;
}
/* ----------------------------
.separator
----------------------------- */
.separator {
/* Layout */
-fx-padding: 10 0;
}
.separator .line {
/* Color */
-fx-border-color: #000000;
-fx-border-width: 0 0 1 0;
}
/* ----------------------------
.slider
----------------------------- */
.slider {
/* Layout */
-fx-padding: 6 0;
/* Color */
-fx-background-color: transparent;
}
.slider .track {
/* Color */
-fx-background-color: linear-gradient(to left, #00cc00, #cc0000);
-fx-background-insets: 0;
-fx-background-radius: 2;
-fx-pref-height: 4;
}
.slider .thumb {
/* Color */
-fx-background-color: #000000;
-fx-background-radius: 50%;
/* Effects */
-fx-effect: dropshadow(gaussian, #00000066, 4, 0, 0, 1);
}
.slider .thumb:hover {
-fx-scale-x: 1.2;
-fx-scale-y: 1.2;
}
/* ----------------------------
.text-header
----------------------------- */
.text-header {
-fx-fill: #000000;
-fx-text-fill: #000000;
}
/* ----------------------------
.text-normal
----------------------------- */
.text-normal {
-fx-fill: #111111;
-fx-text-fill: #111111;
}
/* ----------------------------
.toggle-button
----------------------------- */
.toggle {
/* Layout */
-fx-padding: 8 16;
-fx-background-radius: 6;
/* Color */
-fx-background-color: #e6e6e6;
-fx-text-fill: #000000;
-fx-border-color: #000000;
}
.toggle:hover {
-fx-background-color: #00cc00;
-fx-text-fill: #ffffff;
-fx-border-color: #00cc00;
}

View File

@@ -0,0 +1,215 @@
/* ----------------------------
.background
----------------------------- */
.bg-primary {
-fx-background-color: #f5f5f5;
}
.bg-secondary {
-fx-background-color: #e0e0e0;
}
.bg-popup {
-fx-background-color: #f5f5f57f;
}
/* ----------------------------
.button
----------------------------- */
.button {
/* Layout */
-fx-padding: 10 20;
-fx-background-radius: 6;
-fx-cursor: hand;
/* Color */
-fx-background-color: #e0e0e0;
-fx-text-fill: #1a1a1a;
-fx-border-color: #cccccc;
-fx-border-width: 1;
/* Effects */
-fx-effect: dropshadow(gaussian, #00000026, 4, 0, 0, 1);
}
.button:hover {
-fx-background-color: #d5d5d5;
-fx-border-color: #4caf50;
}
/* ----------------------------
.choice-box
----------------------------- */
.choice-box {
/* Layout */
-fx-padding: 6;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #e0e0e0;
-fx-border-color: #cccccc;
-fx-border-width: 1;
-fx-mark-color: #1a1a1a;
}
.choice-box:hover {
-fx-border-color: #4caf50;
}
.choice-box:focused {
-fx-border-color: #81c784;
}
.choice-box .label {
-fx-text-fill: #1a1a1a;
}
/* ----------------------------
.choice-box popup styling
----------------------------- */
.choice-box .context-menu {
/* Layout */
-fx-padding: 4;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #ffffff;
-fx-border-color: #cccccc;
-fx-border-width: 1;
}
.choice-box .menu-item {
/* Layout */
-fx-padding: 6 12;
}
.choice-box .menu-item .label {
-fx-text-fill: #333333;
}
.choice-box .menu-item:hover {
-fx-background-color: #eeeeee;
}
.choice-box .menu-item:focused {
-fx-background-color: #4caf50;
-fx-text-fill: #ffffff;
}
/* ----------------------------
.container
----------------------------- */
.container {
/* Layout */
-fx-padding: 10;
-fx-alignment: center;
/* Color */
-fx-background-color: #e0e0e0;
}
/* ----------------------------
.input
----------------------------- */
.input {
/* Layout */
-fx-padding: 8;
-fx-background-radius: 4;
/* Color */
-fx-background-color: #ffffff;
-fx-text-fill: #1a1a1a;
-fx-border-color: #cccccc;
-fx-border-width: 1;
}
.input:hover {
-fx-border-color: #4caf50;
}
.input:focused {
-fx-border-color: #81c784;
}
/* ----------------------------
.separator
----------------------------- */
.separator {
/* Layout */
-fx-padding: 10 0;
}
.separator .line {
/* Color */
-fx-border-color: #cccccc;
-fx-border-width: 0 0 1 0;
}
/* ----------------------------
.slider
----------------------------- */
.slider {
/* Layout */
-fx-padding: 6 0;
/* Color */
-fx-background-color: transparent;
}
.slider .track {
/* Color */
-fx-background-color: linear-gradient(to left, #4caf50, #f44336);
-fx-background-insets: 0;
-fx-background-radius: 2;
-fx-pref-height: 4;
}
.slider .thumb {
/* Color */
-fx-background-color: #1a1a1a;
-fx-background-radius: 50%;
/* Effects */
-fx-effect: dropshadow(gaussian, #00000033, 4, 0, 0, 1);
}
.slider .thumb:hover {
-fx-scale-x: 1.2;
-fx-scale-y: 1.2;
}
/* ----------------------------
.text-header
----------------------------- */
.text-header {
-fx-fill: #1a1a1a;
-fx-text-fill: #1a1a1a;
}
/* ----------------------------
.text-normal
----------------------------- */
.text-normal {
-fx-fill: #333333;
-fx-text-fill: #333333;
}
/* ----------------------------
.toggle-button
----------------------------- */
.toggle {
/* Layout */
-fx-padding: 8 16;
-fx-background-radius: 6;
/* Color */
-fx-background-color: #d0d0d0;
-fx-text-fill: #1a1a1a;
-fx-border-color: #b0b0b0;
}
.toggle:hover {
-fx-background-color: #4caf50;
-fx-text-fill: #ffffff;
-fx-border-color: #4caf50;
}

View File

@@ -0,0 +1,11 @@
.text-header {
-fx-font-family: "Arial";
-fx-font-size: 20px;
-fx-font-weight: bold;
}
.text-normal {
-fx-font-family: "Arial";
-fx-font-size: 16px;
-fx-font-weight: normal;
}

View File

@@ -0,0 +1,11 @@
.text-header {
-fx-font-family: "Arial";
-fx-font-size: 16px;
-fx-font-weight: bold;
}
.text-normal {
-fx-font-family: "Arial";
-fx-font-size: 12px;
-fx-font-weight: normal;
}

View File

@@ -0,0 +1 @@
Super gaaf!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 956 B

View File

@@ -96,7 +96,34 @@
<version>4.0.0</version>
</dependency>
</dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>25</version>
</dependency>
<!-- JavaFX Media -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
@@ -109,24 +136,6 @@
<target>25</target>
<release>25</release>
<encoding>UTF-8</encoding>
<!-- <compilerArgs>-->
<!-- <arg>-XDcompilePolicy=simple</arg>-->
<!-- <arg>&#45;&#45;should-stop=ifError=FLOW</arg>-->
<!-- <arg>-Xplugin:ErrorProne</arg>-->
<!-- </compilerArgs>-->
<!-- <annotationProcessorPaths>-->
<!-- <path>-->
<!-- <groupId>com.google.errorprone</groupId>-->
<!-- <artifactId>error_prone_core</artifactId>-->
<!-- <version>2.42.0</version>-->
<!-- </path>-->
<!-- &lt;!&ndash; Other annotation processors go here.-->
<!-- If 'annotationProcessorPaths' is set, processors will no longer be-->
<!-- discovered on the regular -classpath; see also 'Using Error Prone-->
<!-- together with other annotation processors' below. &ndash;&gt;-->
<!-- </annotationProcessorPaths>-->
<!-- <fork>true</fork>-->
</configuration>
</plugin>
<plugin>

View File

@@ -5,7 +5,37 @@ import java.time.Instant;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicLong;
/**
* A thread-safe, distributed unique ID generator following the Snowflake pattern.
*
* <p>Each generated 64-bit ID encodes:
*
* <ul>
* <li>41-bit timestamp (milliseconds since custom epoch)
* <li>10-bit machine identifier
* <li>12-bit sequence number for IDs generated in the same millisecond
* </ul>
*
* <p>This implementation ensures:
*
* <ul>
* <li>IDs are unique per machine.
* <li>Monotonicity within the same machine.
* <li>Safe concurrent generation via synchronized {@link #nextId()}.
* </ul>
*
* <p>Custom epoch is set to {@code 2025-01-01T00:00:00Z}.
*
* <p>Usage example:
*
* <pre>{@code
* SnowflakeGenerator generator = new SnowflakeGenerator();
* long id = generator.nextId();
* }</pre>
*/
public class SnowflakeGenerator {
/** Custom epoch in milliseconds (2025-01-01T00:00:00Z). */
private static final long EPOCH = Instant.parse("2025-01-01T00:00:00Z").toEpochMilli();
// Bit allocations
@@ -13,19 +43,25 @@ public class SnowflakeGenerator {
private static final long MACHINE_BITS = 10;
private static final long SEQUENCE_BITS = 12;
// Max values
// Maximum values for each component
private static final long MAX_MACHINE_ID = (1L << MACHINE_BITS) - 1;
private static final long MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1;
private static final long MAX_TIMESTAMP = (1L << TIMESTAMP_BITS) - 1;
// Bit shifts
// Bit shifts for composing the ID
private static final long MACHINE_SHIFT = SEQUENCE_BITS;
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_BITS;
/** Unique machine identifier derived from network interfaces (10 bits). */
private static final long machineId = SnowflakeGenerator.genMachineId();
private final AtomicLong lastTimestamp = new AtomicLong(-1L);
private long sequence = 0L;
/**
* Generates a 10-bit machine identifier based on MAC addresses of network interfaces. Falls
* back to a random value if MAC cannot be determined.
*/
private static long genMachineId() {
try {
StringBuilder sb = new StringBuilder();
@@ -35,17 +71,26 @@ public class SnowflakeGenerator {
for (byte b : mac) sb.append(String.format("%02X", b));
}
}
// limit to 10 bits (01023)
return sb.toString().hashCode() & 0x3FF;
return sb.toString().hashCode() & 0x3FF; // limit to 10 bits
} catch (Exception e) {
return (long) (Math.random() * 1024); // fallback
}
}
/**
* For testing: manually set the last generated timestamp.
*
* @param l timestamp in milliseconds
*/
void setTime(long l) {
this.lastTimestamp.set(l);
}
/**
* Constructs a SnowflakeGenerator. Validates that the machine ID is within allowed range.
*
* @throws IllegalArgumentException if machine ID is invalid
*/
public SnowflakeGenerator() {
if (machineId < 0 || machineId > MAX_MACHINE_ID) {
throw new IllegalArgumentException(
@@ -53,6 +98,15 @@ public class SnowflakeGenerator {
}
}
/**
* Generates the next unique ID.
*
* <p>If multiple IDs are generated in the same millisecond, a sequence number is incremented.
* If the sequence overflows, waits until the next millisecond.
*
* @return a unique 64-bit ID
* @throws IllegalStateException if clock moves backwards or timestamp exceeds 41-bit limit
*/
public synchronized long nextId() {
long currentTimestamp = timestamp();
@@ -67,7 +121,6 @@ public class SnowflakeGenerator {
if (currentTimestamp == lastTimestamp.get()) {
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0) {
// Sequence overflow, wait for next millisecond
currentTimestamp = waitNextMillis(currentTimestamp);
}
} else {
@@ -81,6 +134,12 @@ public class SnowflakeGenerator {
| sequence;
}
/**
* Waits until the next millisecond if sequence overflows.
*
* @param lastTimestamp previous timestamp
* @return new timestamp
*/
private long waitNextMillis(long lastTimestamp) {
long ts = timestamp();
while (ts <= lastTimestamp) {
@@ -89,6 +148,7 @@ public class SnowflakeGenerator {
return ts;
}
/** Returns current system timestamp in milliseconds. */
private long timestamp() {
return System.currentTimeMillis();
}

View File

@@ -0,0 +1,252 @@
package org.toop.framework.asset;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
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.
*
* <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>Assets are stored in a static, thread-safe list and can be retrieved through {@link
* ResourceManager}.
*
* <p>Features:
*
* <ul>
* <li>Recursive directory scanning for assets.
* <li>Automatic registration of resource classes via reflection.
* <li>Bundled resource support: multiple files merged into a single resource instance.
* <li>Preload resources automatically invoke {@link PreloadResource#load()}.
* <li>Progress tracking via {@link AssetLoaderEvents.LoadingProgressUpdate} events.
* </ul>
*
* <p>Usage example:
*
* <pre>{@code
* ResourceLoader loader = new ResourceLoader("assets");
* double progress = loader.getProgress();
* List<Asset<? extends BaseResource>> loadedAssets = loader.getAssets();
* }</pre>
*/
public class ResourceLoader {
private static final Logger logger = LogManager.getLogger(ResourceLoader.class);
private static final List<ResourceMeta<? extends BaseResource>> assets =
new CopyOnWriteArrayList<>();
private final Map<String, Function<File, ? extends BaseResource>> registry =
new ConcurrentHashMap<>();
private final AtomicInteger loadedCount = new AtomicInteger(0);
private int totalCount = 0;
/**
* Constructs an ResourceLoader and loads assets from the given root folder.
*
* @param rootFolder the folder containing asset files
*/
public ResourceLoader(File rootFolder) {
autoRegisterResources();
List<File> foundFiles = new ArrayList<>();
fileSearcher(rootFolder, foundFiles);
this.totalCount = foundFiles.size();
// measure memory before loading
long before = getUsedMemory();
loader(foundFiles);
// ~measure memory after loading
long after = getUsedMemory();
long used = after - before;
logger.info("Total files loaded: {}", this.totalCount);
logger.info("Memory used by assets: ~{} MB", used / (1024 * 1024));
}
private static long getUsedMemory() {
Runtime runtime = Runtime.getRuntime();
return runtime.totalMemory() - runtime.freeMemory();
}
/**
* Constructs an ResourceLoader from a folder path.
*
* @param rootFolder the folder path containing assets
*/
public ResourceLoader(String rootFolder) {
this(new File(rootFolder));
}
/**
* Returns the current progress of loading assets (0.0 to 1.0).
*
* @return progress as a double
*/
public double getProgress() {
return (totalCount == 0) ? 1.0 : (loadedCount.get() / (double) totalCount);
}
/**
* Returns the number of assets loaded so far.
*
* @return loaded count
*/
public int getLoadedCount() {
return loadedCount.get();
}
/**
* Returns the total number of files found to load.
*
* @return total asset count
*/
public int getTotalCount() {
return totalCount;
}
/**
* Returns a snapshot list of all assets loaded by this loader.
*
* @return list of loaded assets
*/
public List<ResourceMeta<? extends BaseResource>> getAssets() {
return new ArrayList<>(assets);
}
/**
* Registers a factory for a specific file extension.
*
* @param extension the file extension (without dot)
* @param factory a function mapping a File to a resource instance
* @param <T> the type of resource
*/
public <T extends BaseResource> void register(String extension, Function<File, T> factory) {
this.registry.put(extension, factory);
}
/** 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) {
String ext = getExtension(file.getName());
Function<File, ? extends BaseResource> factory = registry.get(ext);
if (factory == null) return null;
BaseResource resource = factory.apply(file);
if (!type.isInstance(resource)) {
throw new IllegalArgumentException(
"File " + file.getName() + " is not of type " + type.getSimpleName());
}
return type.cast(resource);
}
/** Loads the given list of files into assets, handling bundled and preload resources. */
private void loader(List<File> files) {
Map<String, BundledResource> bundledResources = new HashMap<>();
for (File file : files) {
boolean skipAdd = false;
BaseResource resource = resourceMapper(file, BaseResource.class);
switch (resource) {
case null -> {
continue;
}
case BundledResource br -> {
String key = resource.getClass().getName() + ":" + br.getBaseName();
if (!bundledResources.containsKey(key)) {
bundledResources.put(key, br);
}
bundledResources.get(key).loadFile(file);
resource = (BaseResource) bundledResources.get(key);
assets.add(new ResourceMeta<>(br.getBaseName(), resource));
skipAdd = true;
}
case PreloadResource pr -> pr.load();
default -> {}
}
BaseResource finalResource = resource;
boolean alreadyAdded = assets.stream().anyMatch(a -> a.getResource() == finalResource);
if (!alreadyAdded && !skipAdd) {
assets.add(new ResourceMeta<>(file.getName(), resource));
}
logger.info(
"Loaded {} from {}",
resource.getClass().getSimpleName(),
file.getAbsolutePath());
loadedCount.incrementAndGet();
new EventFlow()
.addPostEvent(
new AssetLoaderEvents.LoadingProgressUpdate(
loadedCount.get(), totalCount))
.postEvent();
}
}
/** Recursively searches a folder and adds all files to the foundFiles list. */
private void fileSearcher(final File folder, List<File> foundFiles) {
for (File fileEntry : Objects.requireNonNull(folder.listFiles())) {
if (fileEntry.isDirectory()) {
fileSearcher(fileEntry, foundFiles);
} else {
foundFiles.add(fileEntry);
}
}
}
/**
* Uses reflection to automatically register all {@link BaseResource} subclasses annotated with
* {@link FileExtension}.
*/
private void autoRegisterResources() {
Reflections reflections = new Reflections("org.toop.framework.asset.resources");
Set<Class<? extends BaseResource>> classes = reflections.getSubTypesOf(BaseResource.class);
for (Class<? extends BaseResource> cls : classes) {
if (!cls.isAnnotationPresent(FileExtension.class)) continue;
FileExtension annotation = cls.getAnnotation(FileExtension.class);
for (String ext : annotation.value()) {
registry.put(
ext,
file -> {
try {
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. */
private static String getBaseName(String fileName) {
int underscoreIndex = fileName.indexOf('_');
int dotIndex = fileName.lastIndexOf('.');
if (underscoreIndex > 0) return fileName.substring(0, underscoreIndex);
return fileName.substring(0, dotIndex);
}
/** Returns the file extension of a given file name (without dot). */
public static String getExtension(String name) {
int i = name.lastIndexOf('.');
return (i > 0) ? name.substring(i + 1) : "";
}
}

View File

@@ -0,0 +1,147 @@
package org.toop.framework.asset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.asset.resources.*;
/**
* 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>Key responsibilities:
*
* <ul>
* <li>Storing all loaded assets in a concurrent map.
* <li>Providing typed access to asset resources.
* <li>Allowing lookup by asset name or ID.
* <li>Supporting retrieval of all assets of a specific {@link BaseResource} subclass.
* </ul>
*
* <p>Example usage:
*
* <pre>{@code
* // Load assets from a loader
* ResourceLoader loader = new ResourceLoader(new File("RootFolder"));
* ResourceManager.loadAssets(loader);
*
* // Retrieve a single resource
* ImageAsset background = ResourceManager.get("background.jpg");
*
* // Retrieve all fonts
* List<Asset<FontAsset>> fonts = ResourceManager.getAllOfType(FontAsset.class);
*
* // Retrieve by asset name or optional lookup
* Optional<Asset<? extends BaseResource>> maybeAsset = ResourceManager.findByName("menu.css");
* }</pre>
*
* <p>Notes:
*
* <ul>
* <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>Assets should be loaded via {@link ResourceLoader} before retrieval.
* </ul>
*/
public class ResourceManager {
private static final Logger logger = LogManager.getLogger(ResourceManager.class);
private static final ResourceManager INSTANCE = new ResourceManager();
private static final Map<String, ResourceMeta<? extends BaseResource>> assets =
new ConcurrentHashMap<>();
private ResourceManager() {}
/**
* Returns the singleton instance of {@code ResourceManager}.
*
* @return the shared instance
*/
public static ResourceManager getInstance() {
return INSTANCE;
}
/**
* Loads all assets from a given {@link ResourceLoader} into the manager.
*
* @param loader the loader that has already loaded assets
*/
public static synchronized void loadAssets(ResourceLoader loader) {
for (var asset : loader.getAssets()) {
assets.put(asset.getName(), asset);
}
}
/**
* Retrieve the resource of a given name, cast to the expected type.
*
* @param name the asset name
* @param <T> the expected resource type
* @return the resource, or null if not found
*/
@SuppressWarnings("unchecked")
public static <T extends BaseResource> T get(String name) {
ResourceMeta<T> asset = (ResourceMeta<T>) assets.get(name);
if (asset == null) {
throw new TypeNotPresentException(
name,
new RuntimeException(
String.format(
"Type %s not present",
name))); // TODO: Create own exception, BAM
}
return asset.getResource();
}
// @SuppressWarnings("unchecked")
// public static <T extends BaseResource> ArrayList<ResourceMeta<T>> getAllOfType() {
// return (ArrayList<ResourceMeta<T>>) (ArrayList<?>) new ArrayList<>(assets.values());
// }
/**
* Retrieve all assets of a specific resource type.
*
* @param type the class type to filter
* @param <T> the resource type
* @return a list of assets matching the type
*/
public static <T extends BaseResource> ArrayList<ResourceMeta<T>> getAllOfType(Class<T> type) {
ArrayList<ResourceMeta<T>> list = new ArrayList<>();
for (ResourceMeta<? extends BaseResource> asset : assets.values()) {
if (type.isInstance(asset.getResource())) {
@SuppressWarnings("unchecked")
ResourceMeta<T> typed = (ResourceMeta<T>) asset;
list.add(typed);
}
}
return list;
}
/**
* Retrieve an asset by its unique ID.
*
* @param id the asset ID
* @return the asset, or null if not found
*/
public static ResourceMeta<? extends BaseResource> getById(String id) {
for (ResourceMeta<? extends BaseResource> asset : assets.values()) {
if (asset.getId().toString().equals(id)) {
return asset;
}
}
return null;
}
/**
* Add a new asset to the manager.
*
* @param asset the asset to add
*/
public static void addAsset(ResourceMeta<? extends BaseResource> asset) {
assets.put(asset.getName(), asset);
}
}

View File

@@ -0,0 +1,28 @@
package org.toop.framework.asset;
import org.toop.framework.SnowflakeGenerator;
import org.toop.framework.asset.resources.BaseResource;
public class ResourceMeta<T extends BaseResource> {
private final Long id;
private final String name;
private final T resource;
public ResourceMeta(String name, T resource) {
this.id = new SnowflakeGenerator().nextId();
this.name = name;
this.resource = resource;
}
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
public T getResource() {
return this.resource;
}
}

View File

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

View File

@@ -0,0 +1,17 @@
package org.toop.framework.asset.resources;
import java.io.*;
public abstract class BaseResource {
final File file;
boolean isLoaded = false;
protected BaseResource(final File file) {
this.file = file;
}
public File getFile() {
return this.file;
}
}

View File

@@ -0,0 +1,18 @@
package org.toop.framework.asset.resources;
import java.io.File;
import org.toop.framework.asset.types.FileExtension;
@FileExtension({"css"})
public class CssAsset extends BaseResource {
private final String url;
public CssAsset(File file) {
super(file);
this.url = file.toURI().toString();
}
public String getUrl() {
return url;
}
}

View File

@@ -0,0 +1,62 @@
package org.toop.framework.asset.resources;
import java.io.File;
import java.io.FileInputStream;
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"})
public class FontAsset extends BaseResource implements PreloadResource {
private String family;
public FontAsset(final File fontFile) {
super(fontFile);
}
@Override
public void load() {
if (!this.isLoaded) {
try (FileInputStream fis = new FileInputStream(this.file)) {
// Register font with JavaFX
Font font = Font.loadFont(fis, 12); // Default preview size
if (font == null) {
throw new RuntimeException("Failed to load font: " + this.file);
}
this.family = font.getFamily(); // Save family name for CSS / future use
this.isLoaded = true;
} catch (IOException e) {
throw new RuntimeException("Error reading font file: " + this.file, e);
}
}
}
@Override
public void unload() {
// Font remains globally registered with JavaFX, but we just forget it locally
this.family = null;
this.isLoaded = false;
}
@Override
public boolean isLoaded() {
return this.isLoaded;
}
/** Get a new font instance with the given size */
public Font getFont(double size) {
if (!this.isLoaded) {
load();
}
return Font.font(this.family, size);
}
/** Get the family name (for CSS usage) */
public String getFamily() {
if (!this.isLoaded) {
load();
}
return this.family;
}
}

View File

@@ -0,0 +1,47 @@
package org.toop.framework.asset.resources;
import java.io.File;
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"})
public class ImageAsset extends BaseResource implements LoadableResource {
private Image image;
public ImageAsset(final File file) {
super(file);
}
@Override
public void load() {
if (!this.isLoaded) {
try (FileInputStream fis = new FileInputStream(this.file)) {
this.image = new Image(fis);
this.isLoaded = true;
} catch (Exception e) {
throw new RuntimeException("Failed to load image: " + this.file, e);
}
}
}
@Override
public void unload() {
this.image = null;
this.isLoaded = false;
}
@Override
public boolean isLoaded() {
return this.isLoaded;
}
public Image getImage() {
if (!this.isLoaded) {
this.load();
return image;
}
return null;
}
}

View File

@@ -0,0 +1,77 @@
package org.toop.framework.asset.resources;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
@FileExtension({"json"})
public class JsonAsset<T> extends BaseResource implements LoadableResource {
private T content;
private Class<T> type;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public JsonAsset(File file, Class<T> type) {
super(file);
this.type = type;
}
@Override
public void load() {
File file = getFile();
if (!file.exists()) {
try {
// make a new file with the declared constructor (example: settings) if it doesn't
// exist
content = type.getDeclaredConstructor().newInstance();
save();
} catch (Exception e) {
throw new RuntimeException("Could not make default JSON settings for" + file, e);
}
} else {
// else open the file, try reading it using gson, and set it to loaded
try (FileReader reader = new FileReader(file)) {
content = gson.fromJson(reader, type);
this.isLoaded = true;
} catch (Exception e) {
throw new RuntimeException("Failed to load JSON asset" + getFile(), e);
}
}
}
@Override
public void unload() {
this.content = null;
this.isLoaded = false;
}
public T getContent() {
if (!isLoaded()) {
load();
}
return content;
}
public void save() {
File file = getFile();
File parent = file.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
try (FileWriter writer = new FileWriter(file)) {
gson.toJson(content, writer);
} catch (IOException e) {
throw new RuntimeException("Failed to save JSON asset" + getFile(), e);
}
}
@Override
public boolean isLoaded() {
return this.isLoaded;
}
}

View File

@@ -0,0 +1,181 @@
package org.toop.framework.asset.resources;
import java.io.*;
import java.nio.charset.StandardCharsets;
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 containing
* key-value pairs for different locales.
*
* <p>This class implements {@link LoadableResource} to support loading/unloading and {@link
* BundledResource} to represent resources that can contain multiple localized bundles.
*
* <p>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:
*
* <pre>{@code
* LocalizationAsset asset = new LocalizationAsset(new File("messages_en_US.properties"));
* asset.load();
* String greeting = asset.getString("hello", Locale.US);
* }</pre>
*/
@FileExtension({"properties"})
public class LocalizationAsset extends BaseResource implements LoadableResource, BundledResource {
/** Map of loaded resource bundles, keyed by locale. */
private final Map<Locale, ResourceBundle> bundles = new HashMap<>();
/** Flag indicating whether this asset has been loaded. */
private boolean isLoaded = false;
/** Basename of the given asset */
private final String baseName = "localization";
/** Fallback locale used when no matching locale is found. */
private final Locale fallback = Locale.forLanguageTag("en_US");
/**
* Constructs a new LocalizationAsset for the specified file.
*
* @param file the property file to load
*/
public LocalizationAsset(File file) {
super(file);
}
/** Loads the resource file into memory and prepares localized bundles. */
@Override
public void load() {
loadFile(getFile());
isLoaded = true;
}
/** Unloads all loaded resource bundles, freeing memory. */
@Override
public void unload() {
bundles.clear();
isLoaded = false;
}
/**
* Checks whether this asset has been loaded.
*
* @return {@code true} if the asset is loaded, {@code false} otherwise
*/
@Override
public boolean isLoaded() {
return isLoaded;
}
/**
* Retrieves a localized string for the given key and locale. If an exact match for the locale
* is not found, a fallback matching the language or the default locale will be used.
*
* @param key the key of the string
* @param locale the desired locale
* @return the localized string
* @throws MissingResourceException if no resource bundle is available for the locale
*/
public String getString(String key, Locale locale) {
Locale target = findBestLocale(locale);
ResourceBundle bundle = bundles.get(target);
if (bundle == null)
throw new MissingResourceException(
"No bundle for locale: " + target, getClass().getName(), key);
return bundle.getString(key);
}
/**
* Finds the best matching locale among loaded bundles. Prefers an exact match, then
* language-only match, then fallback.
*
* @param locale the desired locale
* @return the best matching locale
*/
private Locale findBestLocale(Locale locale) {
if (bundles.containsKey(locale)) return locale;
for (Locale l : bundles.keySet()) {
if (l.getLanguage().equals(locale.getLanguage())) return l;
}
return fallback;
}
/**
* Returns an unmodifiable set of all locales for which bundles are loaded.
*
* @return available locales
*/
public Set<Locale> getAvailableLocales() {
return Collections.unmodifiableSet(bundles.keySet());
}
/**
* Loads a specific property file as a resource bundle. The locale is extracted from the file
* name if present.
*
* @param file the property file to load
* @throws RuntimeException if the file cannot be read
*/
@Override
public void loadFile(File file) {
try (InputStreamReader reader =
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
Locale locale = extractLocale(file.getName(), baseName);
bundles.put(locale, new PropertyResourceBundle(reader));
} catch (IOException e) {
throw new RuntimeException("Failed to load localization file: " + file, e);
}
isLoaded = true;
}
/**
* Returns the base name of the underlying file (without locale or extension).
*
* @return the base name
*/
@Override
public String getBaseName() {
return this.baseName;
}
// /**
// * Extracts the base name from a file name.
// *
// * @param fileName the file name
// * @return base name without locale or extension
// */
// private String getBaseName(String fileName) {
// int dotIndex = fileName.lastIndexOf('.');
// String nameWithoutExtension = (dotIndex > 0) ? fileName.substring(0, dotIndex) :
// fileName;
//
// int underscoreIndex = nameWithoutExtension.indexOf('_');
// if (underscoreIndex > 0) {
// return nameWithoutExtension.substring(0, underscoreIndex);
// }
// return nameWithoutExtension;
// }
/**
* Extracts a locale from a file name based on the pattern "base_LOCALE.properties".
*
* @param fileName the file name
* @param baseName the base name
* @return extracted locale, or fallback if none found
*/
private Locale extractLocale(String fileName, String baseName) {
int underscoreIndex = fileName.indexOf('_');
int dotIndex = fileName.lastIndexOf('.');
if (underscoreIndex > 0 && dotIndex > underscoreIndex) {
String localePart = fileName.substring(underscoreIndex + 1, dotIndex);
return Locale.forLanguageTag(localePart.replace('_', '-'));
}
return fallback;
}
}

View File

@@ -0,0 +1,39 @@
package org.toop.framework.asset.resources;
import java.io.*;
import javafx.scene.media.Media;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
@FileExtension({"mp3"})
public class MusicAsset extends BaseResource implements LoadableResource {
private Media media;
public MusicAsset(final File audioFile) {
super(audioFile);
}
public Media getMedia() {
if (media == null) {
media = new Media(file.toURI().toString());
}
return media;
}
@Override
public void load() {
if (media == null) media = new Media(file.toURI().toString());
this.isLoaded = true;
}
@Override
public void unload() {
media = null;
isLoaded = false;
}
@Override
public boolean isLoaded() {
return isLoaded;
}
}

View File

@@ -0,0 +1,75 @@
package org.toop.framework.asset.resources;
import java.io.File;
import java.util.Locale;
import org.toop.framework.settings.Settings;
public class SettingsAsset extends JsonAsset<Settings> {
public SettingsAsset(File file) {
super(file, Settings.class);
}
public int getVolume() {
return getContent().volume;
}
public int getFxVolume() {
return getContent().fxVolume;
}
public int getMusicVolume() {
return getContent().musicVolume;
}
public Locale getLocale() {
return Locale.forLanguageTag(getContent().locale);
}
public boolean getFullscreen() {
return getContent().fullScreen;
}
public String getTheme() {
return getContent().theme;
}
public String getLayoutSize() {
return getContent().layoutSize;
}
public void setVolume(int volume) {
getContent().volume = volume;
save();
}
public void setFxVolume(int fxVolume) {
getContent().fxVolume = fxVolume;
save();
}
public void setMusicVolume(int musicVolume) {
getContent().musicVolume = musicVolume;
save();
}
public void setLocale(String locale) {
getContent().locale = locale;
save();
}
public void setFullscreen(boolean fullscreen) {
getContent().fullScreen = fullscreen;
save();
}
public void setTheme(String theme) {
getContent().theme = theme;
save();
}
public void setLayoutSize(String layoutSize) {
getContent().layoutSize = layoutSize;
save();
}
}

View File

@@ -0,0 +1,80 @@
package org.toop.framework.asset.resources;
import java.io.*;
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"})
public class SoundEffectAsset extends BaseResource implements LoadableResource {
private byte[] rawData;
public SoundEffectAsset(final File audioFile) {
super(audioFile);
}
// Gets a new clip to play
public Clip getNewClip()
throws LineUnavailableException, UnsupportedAudioFileException, IOException {
// Get a new clip from audio system
Clip clip = AudioSystem.getClip();
// Insert a new audio stream into the clip
AudioInputStream inputStream = this.getAudioStream();
AudioFormat baseFormat = inputStream.getFormat();
if (baseFormat.getSampleSizeInBits() > 16)
inputStream = downSampleAudio(inputStream, baseFormat);
clip.open(
inputStream); // ^ Clip can only run 16 bit and lower, thus downsampling necessary.
return clip;
}
// Generates a new audio stream from byte array
private AudioInputStream getAudioStream() throws UnsupportedAudioFileException, IOException {
// Check if raw data is loaded into memory
if (!this.isLoaded()) {
this.load();
}
// Turn rawData into an input stream and turn that into an audio input stream;
return AudioSystem.getAudioInputStream(new ByteArrayInputStream(this.rawData));
}
private AudioInputStream downSampleAudio(
AudioInputStream audioInputStream, AudioFormat baseFormat) {
AudioFormat decodedFormat =
new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getSampleRate(),
16, // force 16-bit
baseFormat.getChannels(),
baseFormat.getChannels() * 2,
baseFormat.getSampleRate(),
false // little-endian
);
return AudioSystem.getAudioInputStream(decodedFormat, audioInputStream);
}
@Override
public void load() {
try {
this.rawData = Files.readAllBytes(file.toPath());
this.isLoaded = true;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void unload() {
this.rawData = null;
this.isLoaded = false;
}
@Override
public boolean isLoaded() {
return this.isLoaded;
}
}

View File

@@ -0,0 +1,43 @@
package org.toop.framework.asset.resources;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import org.toop.framework.asset.types.FileExtension;
import org.toop.framework.asset.types.LoadableResource;
@FileExtension({"txt", "json", "xml"})
public class TextAsset extends BaseResource implements LoadableResource {
private String content;
public TextAsset(File file) {
super(file);
}
@Override
public void load() {
try {
byte[] bytes = Files.readAllBytes(getFile().toPath());
this.content = new String(bytes, StandardCharsets.UTF_8);
this.isLoaded = true;
} catch (IOException e) {
throw new RuntimeException("Failed to load text asset: " + getFile(), e);
}
}
@Override
public void unload() {
this.content = null;
this.isLoaded = false;
}
@Override
public boolean isLoaded() {
return this.isLoaded;
}
public String getContent() {
return this.content;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,69 @@
package org.toop.framework.asset.types;
import org.toop.framework.asset.ResourceLoader;
/**
* 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>Implementing classes must define the following behaviors:
*
* <ul>
* <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>{@link #isLoaded()}: Return {@code true} if the resource has been successfully loaded and
* is ready for use, {@code false} otherwise.
* </ul>
*
* <p>Typical usage:
*
* <pre>{@code
* public class MyFontAsset extends BaseResource implements LoadableResource {
* private boolean loaded = false;
*
* @Override
* public void load() {
* // Load font file into memory
* loaded = true;
* }
*
* @Override
* public void unload() {
* // Release resources if needed
* loaded = false;
* }
*
* @Override
* public boolean isLoaded() {
* return loaded;
* }
* }
* }</pre>
*
* <p>This interface is commonly used with {@link PreloadResource} to allow automatic loading by an
* {@link ResourceLoader} if desired.
*/
public interface LoadableResource {
/**
* Load the resource into memory or initialize it. This method may throw runtime exceptions if
* loading fails.
*/
void load();
/**
* Unload the resource and free any associated resources. After this call, {@link #isLoaded()}
* should return false.
*/
void unload();
/**
* Check whether the resource has been successfully loaded.
*
* @return true if the resource is loaded and ready for use, false otherwise
*/
boolean isLoaded();
}

View File

@@ -0,0 +1,41 @@
package org.toop.framework.asset.types;
import org.toop.framework.asset.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 {@link
* LoadableResource#load()} and {@link LoadableResource#unload()} methods, as well as the {@link
* LoadableResource#isLoaded()} check.
*
* <p>When a resource implements {@code PreloadResource}, the {@code ResourceLoader} will invoke
* {@link LoadableResource#load()} automatically after the resource is discovered and instantiated,
* without requiring manual loading by the user.
*
* <p>Typical usage:
*
* <pre>{@code
* public class MyFontAsset extends BaseResource implements PreloadResource {
* @Override
* public void load() {
* // load the font into memory
* }
*
* @Override
* public void unload() {
* // release resources if needed
* }
*
* @Override
* public boolean isLoaded() {
* return loaded;
* }
* }
* }</pre>
*
* <p>Note: Only use this interface for resources that are safe to load at startup, as it may
* increase memory usage or startup time.
*/
public interface PreloadResource extends LoadableResource {}

View File

@@ -0,0 +1,100 @@
package org.toop.framework.audio;
import javafx.scene.media.MediaPlayer;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
public class AudioVolumeManager {
private final SoundManager sM;
private double volume = 1.0;
private double fxVolume = 1.0;
private double musicVolume = 1.0;
public AudioVolumeManager(SoundManager soundManager) {
this.sM = soundManager;
new EventFlow()
.listen(this::handleVolumeChange)
.listen(this::handleFxVolumeChange)
.listen(this::handleMusicVolumeChange)
.listen(this::handleGetCurrentVolume)
.listen(this::handleGetCurrentFxVolume)
.listen(this::handleGetCurrentMusicVolume);
}
public void updateMusicVolume(MediaPlayer mediaPlayer) {
mediaPlayer.setVolume(this.musicVolume * this.volume);
}
public void updateSoundEffectVolume(Clip clip) {
if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
FloatControl volumeControl =
(FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float min = volumeControl.getMinimum();
float max = volumeControl.getMaximum();
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));
volumeControl.setValue(dB);
}
}
private double limitVolume(double volume) {
if (volume > 1.0) return 1.0;
else return Math.max(volume, 0.0);
}
private void handleFxVolumeChange(AudioEvents.ChangeFxVolume event) {
this.fxVolume = limitVolume(event.newVolume() / 100);
for (Clip clip : sM.getActiveSoundEffects().values()) {
updateSoundEffectVolume(clip);
}
}
private void handleVolumeChange(AudioEvents.ChangeVolume event) {
this.volume = limitVolume(event.newVolume() / 100);
for (MediaPlayer mediaPlayer : sM.getActiveMusic()) {
this.updateMusicVolume(mediaPlayer);
}
for (Clip clip : sM.getActiveSoundEffects().values()) {
updateSoundEffectVolume(clip);
}
}
private void handleMusicVolumeChange(AudioEvents.ChangeMusicVolume event) {
this.musicVolume = limitVolume(event.newVolume() / 100);
System.out.println(this.musicVolume);
System.out.println(this.volume);
for (MediaPlayer mediaPlayer : sM.getActiveMusic()) {
this.updateMusicVolume(mediaPlayer);
}
}
private void handleGetCurrentVolume(AudioEvents.GetCurrentVolume event) {
new EventFlow()
.addPostEvent(
new AudioEvents.GetCurrentVolumeResponse(volume * 100, event.snowflakeId()))
.asyncPostEvent();
}
private void handleGetCurrentFxVolume(AudioEvents.GetCurrentFxVolume event) {
new EventFlow()
.addPostEvent(
new AudioEvents.GetCurrentFxVolumeResponse(
fxVolume * 100, event.snowflakeId()))
.asyncPostEvent();
}
private void handleGetCurrentMusicVolume(AudioEvents.GetCurrentMusicVolume event) {
new EventFlow()
.addPostEvent(
new AudioEvents.GetCurrentMusicVolumeResponse(
musicVolume * 100, event.snowflakeId()))
.asyncPostEvent();
}
}

Some files were not shown because too many files have changed in this diff Show More