mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 10:54:51 +00:00
Merge pull request #164 from 2OOP/Development
Development update, demo 2
This commit is contained in:
10
.gitignore
vendored
10
.gitignore
vendored
@@ -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
|
||||
1
.idea/dictionaries/project.xml
generated
1
.idea/dictionaries/project.xml
generated
@@ -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>
|
||||
|
||||
8
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
8
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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
41
.idea/resourceBundles.xml
generated
Normal 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>
|
||||
77
app/pom.xml
77
app/pom.xml
@@ -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>--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>-->
|
||||
<!-- <!– 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. –>-->
|
||||
<!-- </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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
167
app/src/main/java/org/toop/app/App.java
Normal file
167
app/src/main/java/org/toop/app/App.java
Normal 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;
|
||||
}
|
||||
}
|
||||
10
app/src/main/java/org/toop/app/GameInformation.java
Normal file
10
app/src/main/java/org/toop/app/GameInformation.java
Normal 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) {}
|
||||
130
app/src/main/java/org/toop/app/canvas/GameCanvas.java
Normal file
130
app/src/main/java/org/toop/app/canvas/GameCanvas.java
Normal 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;
|
||||
}
|
||||
}
|
||||
37
app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java
Normal file
37
app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
12
app/src/main/java/org/toop/app/layer/Container.java
Normal file
12
app/src/main/java/org/toop/app/layer/Container.java
Normal 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);
|
||||
}
|
||||
86
app/src/main/java/org/toop/app/layer/Layer.java
Normal file
86
app/src/main/java/org/toop/app/layer/Layer.java
Normal 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();
|
||||
}
|
||||
140
app/src/main/java/org/toop/app/layer/NodeBuilder.java
Normal file
140
app/src/main/java/org/toop/app/layer/NodeBuilder.java
Normal 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;
|
||||
}
|
||||
}
|
||||
20
app/src/main/java/org/toop/app/layer/Popup.java
Normal file
20
app/src/main/java/org/toop/app/layer/Popup.java
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
235
app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java
Normal file
235
app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
65
app/src/main/java/org/toop/app/layer/layers/MainLayer.java
Normal file
65
app/src/main/java/org/toop/app/layer/layers/MainLayer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
222
app/src/main/java/org/toop/app/layer/layers/OptionsPopup.java
Normal file
222
app/src/main/java/org/toop/app/layer/layers/OptionsPopup.java
Normal 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;
|
||||
}
|
||||
}
|
||||
47
app/src/main/java/org/toop/app/layer/layers/QuitPopup.java
Normal file
47
app/src/main/java/org/toop/app/layer/layers/QuitPopup.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
27
app/src/main/java/org/toop/local/AppContext.java
Normal file
27
app/src/main/java/org/toop/local/AppContext.java
Normal 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);
|
||||
}
|
||||
}
|
||||
58
app/src/main/java/org/toop/local/AppSettings.java
Normal file
58
app/src/main/java/org/toop/local/AppSettings.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/resources/assets/audio/fx/dramatic.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/dramatic.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/fx/harsh-button-click.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/harsh-button-click.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/fx/hitsound0.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/hitsound0.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/fx/hitsound1.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/hitsound1.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/fx/mainmenu.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/mainmenu.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/fx/medium-button-click.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/medium-button-click.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/fx/sadtrombone.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/sadtrombone.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/fx/scawymusic.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/scawymusic.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/fx/soft-button-click.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/soft-button-click.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/fx/suspensful.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/suspensful.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/fx/testsound.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/testsound.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/fx/winsound.wav
Normal file
BIN
app/src/main/resources/assets/audio/fx/winsound.wav
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/music/damned.mp3
Normal file
BIN
app/src/main/resources/assets/audio/music/damned.mp3
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/music/extraction-point.mp3
Normal file
BIN
app/src/main/resources/assets/audio/music/extraction-point.mp3
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/music/formerseas.mp3
Normal file
BIN
app/src/main/resources/assets/audio/music/formerseas.mp3
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/music/gladius.mp3
Normal file
BIN
app/src/main/resources/assets/audio/music/gladius.mp3
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/music/godfrey.mp3
Normal file
BIN
app/src/main/resources/assets/audio/music/godfrey.mp3
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/music/mw2-main-menu.mp3
Normal file
BIN
app/src/main/resources/assets/audio/music/mw2-main-menu.mp3
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/fonts/GroovyManiac.ttf
Normal file
BIN
app/src/main/resources/assets/fonts/GroovyManiac.ttf
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/fonts/Roboto-Regular.ttf
Normal file
BIN
app/src/main/resources/assets/fonts/Roboto-Regular.ttf
Normal file
Binary file not shown.
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
215
app/src/main/resources/assets/style/dark-hc.css
Normal file
215
app/src/main/resources/assets/style/dark-hc.css
Normal 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;
|
||||
}
|
||||
215
app/src/main/resources/assets/style/dark.css
Normal file
215
app/src/main/resources/assets/style/dark.css
Normal 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;
|
||||
}
|
||||
11
app/src/main/resources/assets/style/large.css
Normal file
11
app/src/main/resources/assets/style/large.css
Normal 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;
|
||||
}
|
||||
215
app/src/main/resources/assets/style/light-hc.css
Normal file
215
app/src/main/resources/assets/style/light-hc.css
Normal 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;
|
||||
}
|
||||
215
app/src/main/resources/assets/style/light.css
Normal file
215
app/src/main/resources/assets/style/light.css
Normal 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;
|
||||
}
|
||||
11
app/src/main/resources/assets/style/medium.css
Normal file
11
app/src/main/resources/assets/style/medium.css
Normal 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;
|
||||
}
|
||||
11
app/src/main/resources/assets/style/small.css
Normal file
11
app/src/main/resources/assets/style/small.css
Normal 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;
|
||||
}
|
||||
1
app/src/main/resources/assets/text/test.txt
Normal file
1
app/src/main/resources/assets/text/test.txt
Normal 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 |
@@ -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>--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>-->
|
||||
<!-- <!– 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. –>-->
|
||||
<!-- </annotationProcessorPaths>-->
|
||||
<!-- <fork>true</fork>-->
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
@@ -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 (0–1023)
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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) : "";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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
Reference in New Issue
Block a user