mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 02:44:50 +00:00
Merge remote-tracking branch 'origin/UI' into UI
# Conflicts: # app/src/main/java/org/toop/app/App.java # app/src/main/java/org/toop/app/GameInformation.java # app/src/main/java/org/toop/app/canvas/GameCanvas.java # app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java # app/src/main/java/org/toop/app/layer/Container.java # app/src/main/java/org/toop/app/layer/Layer.java # app/src/main/java/org/toop/app/layer/NodeBuilder.java # app/src/main/java/org/toop/app/layer/Popup.java # app/src/main/java/org/toop/app/layer/containers/HorizontalContainer.java # app/src/main/java/org/toop/app/layer/containers/VerticalContainer.java # app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java # app/src/main/java/org/toop/app/layer/layers/CreditsPopup.java # app/src/main/java/org/toop/app/layer/layers/MainLayer.java # app/src/main/java/org/toop/app/layer/layers/MultiplayerLayer.java # app/src/main/java/org/toop/app/layer/layers/OptionsPopup.java # app/src/main/java/org/toop/app/layer/layers/QuitPopup.java # app/src/main/java/org/toop/app/layer/layers/game/GameFinishedPopup.java # app/src/main/java/org/toop/app/layer/layers/game/TicTacToeLayer.java # app/src/main/java/org/toop/local/AppSettings.java
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -48,6 +48,8 @@ shelf/
|
||||
*.ipr
|
||||
*.iws
|
||||
misc.xml
|
||||
uiDesigner.xml
|
||||
|
||||
|
||||
##############################
|
||||
## Eclipse
|
||||
@@ -76,6 +78,8 @@ dist/
|
||||
nbdist/
|
||||
nbactions.xml
|
||||
nb-configuration.xml
|
||||
misc.xml
|
||||
compiler.xml
|
||||
|
||||
##############################
|
||||
## Visual Studio Code
|
||||
|
||||
22
.idea/compiler.xml
generated
22
.idea/compiler.xml
generated
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="pism_framework" />
|
||||
<module name="pism_game" />
|
||||
<module name="pism_app" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="pism_app" options="" />
|
||||
<module name="pism_framework" options="" />
|
||||
<module name="pism_game" options="" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
1
.idea/dictionaries/project.xml
generated
1
.idea/dictionaries/project.xml
generated
@@ -13,6 +13,7 @@
|
||||
<w>toop</w>
|
||||
<w>vmoptions</w>
|
||||
<w>xplugin</w>
|
||||
<w>yourturn</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
3
.idea/inspectionProfiles/Project_Default.xml
generated
3
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -2,7 +2,8 @@
|
||||
<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" />
|
||||
<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,java.util.Map,remove" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="WriteOnlyObject" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
19
.idea/misc.xml
generated
19
.idea/misc.xml
generated
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EntryPointsManager">
|
||||
<list size="1">
|
||||
<item index="0" class="java.lang.String" itemvalue="com.google.common.eventbus.Subscribe" />
|
||||
</list>
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,21 +1,21 @@
|
||||
package org.toop;
|
||||
|
||||
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;
|
||||
import org.toop.framework.resource.ResourceLoader;
|
||||
import org.toop.framework.resource.ResourceManager;
|
||||
|
||||
public final class Main {
|
||||
public static void main(String[] args) {
|
||||
initSystems();
|
||||
App.run(args);
|
||||
}
|
||||
static void main(String[] args) {
|
||||
initSystems();
|
||||
App.run(args);
|
||||
}
|
||||
|
||||
private static void initSystems() throws NetworkingInitializationException {
|
||||
ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets"));
|
||||
new Thread(NetworkingClientManager::new).start();
|
||||
new Thread(SoundManager::new).start();
|
||||
}
|
||||
}
|
||||
private static void initSystems() throws NetworkingInitializationException {
|
||||
ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets"));
|
||||
new Thread(NetworkingClientManager::new).start();
|
||||
new Thread(SoundManager::new).start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
package org.toop.local;
|
||||
|
||||
import org.toop.framework.asset.ResourceManager;
|
||||
import org.toop.framework.asset.resources.LocalizationAsset;
|
||||
|
||||
import java.util.Locale;
|
||||
import org.toop.framework.resource.ResourceManager;
|
||||
import org.toop.framework.resource.resources.LocalizationAsset;
|
||||
|
||||
public class AppContext {
|
||||
private static final LocalizationAsset localization = ResourceManager.get("localization");
|
||||
private static Locale locale = Locale.forLanguageTag("en");
|
||||
private static final LocalizationAsset localization = ResourceManager.get("localization");
|
||||
private static Locale locale = Locale.forLanguageTag("en");
|
||||
|
||||
public static LocalizationAsset getLocalization() {
|
||||
return localization;
|
||||
}
|
||||
public static LocalizationAsset getLocalization() {
|
||||
return localization;
|
||||
}
|
||||
|
||||
public static void setLocale(Locale locale) {
|
||||
AppContext.locale = locale;
|
||||
}
|
||||
public static void setLocale(Locale locale) {
|
||||
AppContext.locale = locale;
|
||||
}
|
||||
|
||||
public static Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
public static Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public static String getString(String key) {
|
||||
assert localization != null;
|
||||
return localization.getString(key, locale);
|
||||
}
|
||||
}
|
||||
public static String getString(String key) {
|
||||
assert localization != null;
|
||||
return localization.getString(key, locale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
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.resource.ResourceManager;
|
||||
import org.toop.framework.resource.ResourceMeta;
|
||||
import org.toop.framework.resource.resources.SettingsAsset;
|
||||
import org.toop.framework.settings.Settings;
|
||||
|
||||
import java.io.File;
|
||||
@@ -12,19 +17,32 @@ import java.util.Locale;
|
||||
public class AppSettings {
|
||||
private static SettingsAsset settingsAsset;
|
||||
|
||||
private SettingsAsset settingsAsset;
|
||||
|
||||
public void applySettings() {
|
||||
this.settingsAsset = getPath();
|
||||
if (!this.settingsAsset.isLoaded()) {
|
||||
this.settingsAsset.load();
|
||||
public static void applySettings() {
|
||||
SettingsAsset settings = getPath();
|
||||
if (!settings.isLoaded()) {
|
||||
settings.load();
|
||||
}
|
||||
Settings settingsData = settings.getContent();
|
||||
|
||||
Settings settingsData = this.settingsAsset.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());
|
||||
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 static SettingsAsset getPath() {
|
||||
@@ -43,10 +61,12 @@ public class AppSettings {
|
||||
basePath = System.getProperty("user.home") + "/.config";
|
||||
}
|
||||
|
||||
File settingsFile = new File(basePath + File.separator + "ISY1" + File.separator + "settings.json");
|
||||
settingsAsset = new SettingsAsset(settingsFile);
|
||||
File settingsFile =
|
||||
new File(basePath + File.separator + "ISY1" + File.separator + "settings.json");
|
||||
// this.settingsAsset = new SettingsAsset(settingsFile);
|
||||
ResourceManager.addAsset(new ResourceMeta<>("settings.json", new SettingsAsset(settingsFile)));
|
||||
}
|
||||
return settingsAsset;
|
||||
return ResourceManager.get("settings.json");
|
||||
}
|
||||
|
||||
public static SettingsAsset getSettings() {
|
||||
|
||||
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.
@@ -12,191 +12,190 @@ import org.apache.logging.log4j.core.config.LoggerConfig;
|
||||
* <p>Provides methods to enable or disable logs globally or per class, with support for specifying
|
||||
* log levels either via {@link Level} enums or string names.
|
||||
*/
|
||||
// Todo: refactor
|
||||
public final class Logging {
|
||||
|
||||
/** Disables all logging globally by setting the root logger level to {@link Level#OFF}. */
|
||||
public static void disableAllLogs() {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig rootLoggerConfig = config.getRootLogger();
|
||||
rootLoggerConfig.setLevel(Level.OFF);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
/** Disables all logging globally by setting the root logger level to {@link Level#OFF}. */
|
||||
public static void disableAllLogs() {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig rootLoggerConfig = config.getRootLogger();
|
||||
rootLoggerConfig.setLevel(Level.OFF);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
|
||||
/** Enables all logging globally by setting the root logger level to {@link Level#ALL}. */
|
||||
public static void enableAllLogs() {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig rootLoggerConfig = config.getRootLogger();
|
||||
rootLoggerConfig.setLevel(Level.ALL);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
/** Enables all logging globally by setting the root logger level to {@link Level#ALL}. */
|
||||
public static void enableAllLogs() {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig rootLoggerConfig = config.getRootLogger();
|
||||
rootLoggerConfig.setLevel(Level.ALL);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables global logging at a specific level by setting the root logger.
|
||||
*
|
||||
* @param level the logging level to enable for all logs
|
||||
*/
|
||||
public static void enableAllLogs(Level level) {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig rootLoggerConfig = config.getRootLogger();
|
||||
rootLoggerConfig.setLevel(level);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
/**
|
||||
* Enables global logging at a specific level by setting the root logger.
|
||||
*
|
||||
* @param level the logging level to enable for all logs
|
||||
*/
|
||||
public static void enableAllLogs(Level level) {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig rootLoggerConfig = config.getRootLogger();
|
||||
rootLoggerConfig.setLevel(level);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies whether the provided string corresponds to a valid class name.
|
||||
*
|
||||
* @param className fully-qualified class name to check
|
||||
* @return true if the class exists, false otherwise
|
||||
*/
|
||||
private static boolean verifyStringIsActualClass(String className) {
|
||||
try {
|
||||
Class.forName(className);
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Verifies whether the provided string corresponds to a valid class name.
|
||||
*
|
||||
* @param className fully-qualified class name to check
|
||||
* @return true if the class exists, false otherwise
|
||||
*/
|
||||
private static boolean verifyStringIsActualClass(String className) {
|
||||
try {
|
||||
Class.forName(className);
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper to disable logs for a specific class by name.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
*/
|
||||
private static void disableLogsForClassInternal(String className) {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
config.removeLogger(className);
|
||||
LoggerConfig specificConfig = new LoggerConfig(className, Level.OFF, false);
|
||||
config.addLogger(className, specificConfig);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
/**
|
||||
* Internal helper to disable logs for a specific class by name.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
*/
|
||||
private static void disableLogsForClassInternal(String className) {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
config.removeLogger(className);
|
||||
LoggerConfig specificConfig = new LoggerConfig(className, Level.OFF, false);
|
||||
config.addLogger(className, specificConfig);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables logs for a specific class.
|
||||
*
|
||||
* @param class_ the class for which logs should be disabled
|
||||
* @param <T> type of the class
|
||||
*/
|
||||
public static <T> void disableLogsForClass(Class<T> class_) {
|
||||
disableLogsForClassInternal(class_.getName());
|
||||
}
|
||||
/**
|
||||
* Disables logs for a specific class.
|
||||
*
|
||||
* @param class_ the class for which logs should be disabled
|
||||
* @param <T> type of the class
|
||||
*/
|
||||
public static <T> void disableLogsForClass(Class<T> class_) {
|
||||
disableLogsForClassInternal(class_.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables logs for a class specified by fully-qualified name, if the class exists.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
*/
|
||||
public static void disableLogsForClass(String className) {
|
||||
if (verifyStringIsActualClass(className)) {
|
||||
disableLogsForClassInternal(className);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Disables logs for a class specified by fully-qualified name, if the class exists.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
*/
|
||||
public static void disableLogsForClass(String className) {
|
||||
if (verifyStringIsActualClass(className)) {
|
||||
disableLogsForClassInternal(className);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper to enable logs for a specific class at a specific level.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
* @param level logging level to set
|
||||
*/
|
||||
private static void enableLogsForClassInternal(String className, Level level) {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig loggerConfig = config.getLoggers().get(className);
|
||||
if (loggerConfig == null) {
|
||||
loggerConfig = new LoggerConfig(className, level, false);
|
||||
config.addLogger(className, loggerConfig);
|
||||
} else {
|
||||
loggerConfig.setLevel(level);
|
||||
}
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
/**
|
||||
* Internal helper to enable logs for a specific class at a specific level.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
* @param level logging level to set
|
||||
*/
|
||||
private static void enableLogsForClassInternal(String className, Level level) {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig loggerConfig = config.getLoggers().get(className);
|
||||
if (loggerConfig == null) {
|
||||
loggerConfig = new LoggerConfig(className, level, false);
|
||||
config.addLogger(className, loggerConfig);
|
||||
} else {
|
||||
loggerConfig.setLevel(level);
|
||||
}
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables logging for a class at a specific level.
|
||||
*
|
||||
* @param class_ class to configure
|
||||
* @param levelToLog the logging level to set
|
||||
* @param <T> type of the class
|
||||
*/
|
||||
public static <T> void enableLogsForClass(Class<T> class_, Level levelToLog) {
|
||||
enableLogsForClassInternal(class_.getName(), levelToLog);
|
||||
}
|
||||
/**
|
||||
* Enables logging for a class at a specific level.
|
||||
*
|
||||
* @param class_ class to configure
|
||||
* @param levelToLog the logging level to set
|
||||
* @param <T> type of the class
|
||||
*/
|
||||
public static <T> void enableLogsForClass(Class<T> class_, Level levelToLog) {
|
||||
enableLogsForClassInternal(class_.getName(), levelToLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables logging for a class specified by name at a specific level, if the class exists.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
* @param levelToLog the logging level to set
|
||||
*/
|
||||
public static void enableLogsForClass(String className, Level levelToLog) {
|
||||
if (verifyStringIsActualClass(className)) {
|
||||
enableLogsForClassInternal(className, levelToLog);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Enables logging for a class specified by name at a specific level, if the class exists.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
* @param levelToLog the logging level to set
|
||||
*/
|
||||
public static void enableLogsForClass(String className, Level levelToLog) {
|
||||
if (verifyStringIsActualClass(className)) {
|
||||
enableLogsForClassInternal(className, levelToLog);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables logging for a class specified by name at a specific level using a string.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
* @param levelToLog name of the logging level (e.g., "DEBUG", "INFO")
|
||||
*/
|
||||
public static void enableLogsForClass(String className, String levelToLog) {
|
||||
Level level = Level.valueOf(levelToLog.trim().toUpperCase());
|
||||
if (level != null && verifyStringIsActualClass(className)) {
|
||||
enableLogsForClassInternal(className, level);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Enables logging for a class specified by name at a specific level using a string.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
* @param levelToLog name of the logging level (e.g., "DEBUG", "INFO")
|
||||
*/
|
||||
public static void enableLogsForClass(String className, String levelToLog) {
|
||||
Level level = Level.valueOf(levelToLog.trim().toUpperCase());
|
||||
if (level != null && verifyStringIsActualClass(className)) {
|
||||
enableLogsForClassInternal(className, level);
|
||||
}
|
||||
}
|
||||
|
||||
/** Convenience methods for enabling logs at specific levels for classes. */
|
||||
public static <T> void enableAllLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.ALL);
|
||||
}
|
||||
/** Convenience methods for enabling logs at specific levels for classes. */
|
||||
public static <T> void enableAllLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.ALL);
|
||||
}
|
||||
|
||||
public static void enableAllLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.ALL);
|
||||
}
|
||||
public static void enableAllLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.ALL);
|
||||
}
|
||||
|
||||
public static <T> void enableDebugLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.DEBUG);
|
||||
}
|
||||
public static <T> void enableDebugLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.DEBUG);
|
||||
}
|
||||
|
||||
public static void enableDebugLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.DEBUG);
|
||||
}
|
||||
public static void enableDebugLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.DEBUG);
|
||||
}
|
||||
|
||||
public static <T> void enableErrorLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.ERROR);
|
||||
}
|
||||
public static <T> void enableErrorLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.ERROR);
|
||||
}
|
||||
|
||||
public static void enableErrorLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.ERROR);
|
||||
}
|
||||
public static void enableErrorLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.ERROR);
|
||||
}
|
||||
|
||||
public static <T> void enableFatalLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.FATAL);
|
||||
}
|
||||
public static <T> void enableFatalLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.FATAL);
|
||||
}
|
||||
|
||||
public static void enableFatalLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.FATAL);
|
||||
}
|
||||
public static void enableFatalLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.FATAL);
|
||||
}
|
||||
|
||||
public static <T> void enableInfoLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.INFO);
|
||||
}
|
||||
public static <T> void enableInfoLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.INFO);
|
||||
}
|
||||
|
||||
public static void enableInfoLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.INFO);
|
||||
}
|
||||
public static void enableInfoLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.INFO);
|
||||
}
|
||||
|
||||
public static <T> void enableTraceLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.TRACE);
|
||||
}
|
||||
public static <T> void enableTraceLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.TRACE);
|
||||
}
|
||||
|
||||
public static void enableTraceLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.TRACE);
|
||||
}
|
||||
}
|
||||
public static void enableTraceLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.TRACE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,26 +7,27 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* A thread-safe, distributed unique ID generator following the Snowflake pattern.
|
||||
* <p>
|
||||
* Each generated 64-bit ID encodes:
|
||||
*
|
||||
* <p>Each generated 64-bit ID encodes:
|
||||
*
|
||||
* <ul>
|
||||
* <li>41-bit timestamp (milliseconds since custom epoch)</li>
|
||||
* <li>10-bit machine identifier</li>
|
||||
* <li>12-bit sequence number for IDs generated in the same millisecond</li>
|
||||
* <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>
|
||||
*
|
||||
* <p>This implementation ensures:
|
||||
*
|
||||
* <ul>
|
||||
* <li>IDs are unique per machine.</li>
|
||||
* <li>Monotonicity within the same machine.</li>
|
||||
* <li>Safe concurrent generation via synchronized {@link #nextId()}.</li>
|
||||
* <li>IDs are unique per machine.
|
||||
* <li>Monotonicity within the same machine.
|
||||
* <li>Safe concurrent generation via synchronized {@link #nextId()}.
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>Custom epoch is set to {@code 2025-01-01T00:00:00Z}.</p>
|
||||
* <p>Custom epoch is set to {@code 2025-01-01T00:00:00Z}.
|
||||
*
|
||||
* <p>Usage example:
|
||||
*
|
||||
* <p>Usage example:</p>
|
||||
* <pre>{@code
|
||||
* SnowflakeGenerator generator = new SnowflakeGenerator();
|
||||
* long id = generator.nextId();
|
||||
@@ -34,9 +35,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
*/
|
||||
public class SnowflakeGenerator {
|
||||
|
||||
/**
|
||||
* Custom epoch in milliseconds (2025-01-01T00:00:00Z).
|
||||
*/
|
||||
/** Custom epoch in milliseconds (2025-01-01T00:00:00Z). */
|
||||
private static final long EPOCH = Instant.parse("2025-01-01T00:00:00Z").toEpochMilli();
|
||||
|
||||
// Bit allocations
|
||||
@@ -53,17 +52,15 @@ public class SnowflakeGenerator {
|
||||
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).
|
||||
*/
|
||||
/** 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.
|
||||
* 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 {
|
||||
@@ -82,6 +79,7 @@ public class SnowflakeGenerator {
|
||||
|
||||
/**
|
||||
* For testing: manually set the last generated timestamp.
|
||||
*
|
||||
* @param l timestamp in milliseconds
|
||||
*/
|
||||
void setTime(long l) {
|
||||
@@ -89,8 +87,8 @@ public class SnowflakeGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SnowflakeGenerator.
|
||||
* Validates that the machine ID is within allowed range.
|
||||
* Constructs a SnowflakeGenerator. Validates that the machine ID is within allowed range.
|
||||
*
|
||||
* @throws IllegalArgumentException if machine ID is invalid
|
||||
*/
|
||||
public SnowflakeGenerator() {
|
||||
@@ -102,10 +100,9 @@ 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.
|
||||
* </p>
|
||||
*
|
||||
* <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
|
||||
@@ -139,6 +136,7 @@ public class SnowflakeGenerator {
|
||||
|
||||
/**
|
||||
* Waits until the next millisecond if sequence overflows.
|
||||
*
|
||||
* @param lastTimestamp previous timestamp
|
||||
* @return new timestamp
|
||||
*/
|
||||
@@ -150,9 +148,7 @@ public class SnowflakeGenerator {
|
||||
return ts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current system timestamp in milliseconds.
|
||||
*/
|
||||
/** Returns current system timestamp in milliseconds. */
|
||||
private long timestamp() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
package org.toop.framework.asset.types;
|
||||
|
||||
import org.toop.framework.asset.ResourceLoader;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*
|
||||
* <p>Typical use cases include:</p>
|
||||
* <ul>
|
||||
* <li>Localization assets, where multiple `.properties` files (e.g., `messages_en.properties`,
|
||||
* `messages_nl.properties`) are grouped under the same logical resource.</li>
|
||||
* <li>Sprite sheets, tile sets, or other multi-file resources that logically belong together.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Implementing classes must provide:</p>
|
||||
* <ul>
|
||||
* <li>{@link #loadFile(File)}: Logic to load or merge an individual file into the resource.</li>
|
||||
* <li>{@link #getBaseName()}: A consistent base name used to group multiple files into this resource.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Example usage:</p>
|
||||
* <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.</p>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.toop.framework.asset.types;
|
||||
|
||||
import org.toop.framework.asset.ResourceLoader;
|
||||
import org.toop.framework.asset.resources.BaseResource;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*
|
||||
* <p>Usage example:</p>
|
||||
* <pre>{@code
|
||||
* @FileExtension({"png", "jpg"})
|
||||
* public class ImageAsset extends BaseResource implements LoadableResource {
|
||||
* ...
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Key points:</p>
|
||||
* <ul>
|
||||
* <li>The annotation is retained at runtime for reflection-based registration.</li>
|
||||
* <li>Can only be applied to types (classes) that extend {@link BaseResource}.</li>
|
||||
* <li>Multiple extensions can be specified in the {@code value()} array.</li>
|
||||
* </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();
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
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>
|
||||
*
|
||||
* <p>Implementing classes must define the following behaviors:</p>
|
||||
* <ul>
|
||||
* <li>{@link #load()}: Load the resource into memory or perform necessary initialization.</li>
|
||||
* <li>{@link #unload()}: Release any held resources or memory when the resource is no longer needed.</li>
|
||||
* <li>{@link #isLoaded()}: Return {@code true} if the resource has been successfully loaded and is ready for use, {@code false} otherwise.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Typical usage:</p>
|
||||
* <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.</p>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
package org.toop.framework.audio;
|
||||
|
||||
import com.sun.scenario.Settings;
|
||||
import javafx.scene.media.MediaPlayer;
|
||||
import org.toop.framework.audio.events.AudioEvents;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
|
||||
import javax.sound.sampled.Clip;
|
||||
import javax.sound.sampled.FloatControl;
|
||||
import org.toop.framework.audio.events.AudioEvents;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
|
||||
public class AudioVolumeManager {
|
||||
private final SoundManager sM;
|
||||
@@ -15,7 +13,7 @@ public class AudioVolumeManager {
|
||||
private double fxVolume = 1.0;
|
||||
private double musicVolume = 1.0;
|
||||
|
||||
public AudioVolumeManager(SoundManager soundManager){
|
||||
public AudioVolumeManager(SoundManager soundManager) {
|
||||
this.sM = soundManager;
|
||||
|
||||
new EventFlow()
|
||||
@@ -25,19 +23,22 @@ public class AudioVolumeManager {
|
||||
.listen(this::handleGetCurrentVolume)
|
||||
.listen(this::handleGetCurrentFxVolume)
|
||||
.listen(this::handleGetCurrentMusicVolume);
|
||||
|
||||
}
|
||||
|
||||
public void updateMusicVolume(MediaPlayer mediaPlayer){
|
||||
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);
|
||||
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
|
||||
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);
|
||||
}
|
||||
@@ -50,7 +51,7 @@ public class AudioVolumeManager {
|
||||
|
||||
private void handleFxVolumeChange(AudioEvents.ChangeFxVolume event) {
|
||||
this.fxVolume = limitVolume(event.newVolume() / 100);
|
||||
for (Clip clip : sM.getActiveSoundEffects().values()){
|
||||
for (Clip clip : sM.getActiveSoundEffects().values()) {
|
||||
updateSoundEffectVolume(clip);
|
||||
}
|
||||
}
|
||||
@@ -60,32 +61,38 @@ public class AudioVolumeManager {
|
||||
for (MediaPlayer mediaPlayer : sM.getActiveMusic()) {
|
||||
this.updateMusicVolume(mediaPlayer);
|
||||
}
|
||||
for (Clip clip : sM.getActiveSoundEffects().values()){
|
||||
for (Clip clip : sM.getActiveSoundEffects().values()) {
|
||||
updateSoundEffectVolume(clip);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMusicVolumeChange(AudioEvents.ChangeMusicVolume event){
|
||||
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()){
|
||||
for (MediaPlayer mediaPlayer : sM.getActiveMusic()) {
|
||||
this.updateMusicVolume(mediaPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGetCurrentVolume(AudioEvents.GetCurrentVolume event) {
|
||||
new EventFlow().addPostEvent(new AudioEvents.GetCurrentVolumeResponse(volume * 100, event.snowflakeId()))
|
||||
new EventFlow()
|
||||
.addPostEvent(
|
||||
new AudioEvents.GetCurrentVolumeResponse(volume * 100, event.snowflakeId()))
|
||||
.asyncPostEvent();
|
||||
}
|
||||
|
||||
private void handleGetCurrentFxVolume(AudioEvents.GetCurrentFxVolume event) {
|
||||
new EventFlow().addPostEvent(new AudioEvents.GetCurrentFxVolumeResponse(fxVolume * 100, event.snowflakeId()))
|
||||
new EventFlow()
|
||||
.addPostEvent(
|
||||
new AudioEvents.GetCurrentFxVolumeResponse(
|
||||
fxVolume * 100, event.snowflakeId()))
|
||||
.asyncPostEvent();
|
||||
}
|
||||
|
||||
private void handleGetCurrentMusicVolume(AudioEvents.GetCurrentMusicVolume event){
|
||||
new EventFlow().addPostEvent(new AudioEvents.GetCurrentMusicVolumeResponse(musicVolume * 100, event.snowflakeId()))
|
||||
private void handleGetCurrentMusicVolume(AudioEvents.GetCurrentMusicVolume event) {
|
||||
new EventFlow()
|
||||
.addPostEvent(
|
||||
new AudioEvents.GetCurrentMusicVolumeResponse(
|
||||
musicVolume * 100, event.snowflakeId()))
|
||||
.asyncPostEvent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
package org.toop.framework.audio;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.toop.framework.SnowflakeGenerator;
|
||||
import org.toop.framework.asset.ResourceManager;
|
||||
import org.toop.framework.asset.ResourceMeta;
|
||||
import org.toop.framework.asset.resources.MusicAsset;
|
||||
import org.toop.framework.asset.resources.SoundEffectAsset;
|
||||
import org.toop.framework.audio.events.AudioEvents;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
|
||||
import javafx.scene.media.MediaPlayer;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import javafx.scene.media.MediaPlayer;
|
||||
import javax.sound.sampled.*;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.toop.framework.SnowflakeGenerator;
|
||||
import org.toop.framework.audio.events.AudioEvents;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.toop.framework.resource.ResourceManager;
|
||||
import org.toop.framework.resource.ResourceMeta;
|
||||
import org.toop.framework.resource.resources.MusicAsset;
|
||||
import org.toop.framework.resource.resources.SoundEffectAsset;
|
||||
|
||||
public class SoundManager {
|
||||
private static final Logger logger = LogManager.getLogger(SoundManager.class);
|
||||
@@ -22,13 +20,12 @@ public class SoundManager {
|
||||
private final Queue<MusicAsset> backgroundMusicQueue = new LinkedList<>();
|
||||
private final Map<Long, Clip> activeSoundEffects = new HashMap<>();
|
||||
private final HashMap<String, SoundEffectAsset> audioResources = new HashMap<>();
|
||||
private final SnowflakeGenerator idGenerator = new SnowflakeGenerator(); // TODO: Don't create a new generator
|
||||
private final AudioVolumeManager audioVolumeManager = new AudioVolumeManager(this);
|
||||
|
||||
|
||||
public SoundManager() {
|
||||
// Get all Audio Resources and add them to a list.
|
||||
for (ResourceMeta<SoundEffectAsset> asset : ResourceManager.getAllOfType(SoundEffectAsset.class)) {
|
||||
for (ResourceMeta<SoundEffectAsset> asset :
|
||||
ResourceManager.getAllOfType(SoundEffectAsset.class)) {
|
||||
try {
|
||||
this.addAudioResource(asset);
|
||||
} catch (IOException | LineUnavailableException | UnsupportedAudioFileException e) {
|
||||
@@ -39,13 +36,17 @@ public class SoundManager {
|
||||
.listen(this::handlePlaySound)
|
||||
.listen(this::handleStopSound)
|
||||
.listen(this::handleMusicStart)
|
||||
.listen(AudioEvents.ClickButton.class, _ -> {
|
||||
try {
|
||||
playSound("medium-button-click.wav", false);
|
||||
} catch (UnsupportedAudioFileException | LineUnavailableException | IOException e) {
|
||||
logger.error(e);
|
||||
}
|
||||
});
|
||||
.listen(
|
||||
AudioEvents.ClickButton.class,
|
||||
_ -> {
|
||||
try {
|
||||
playSound("medium-button-click.wav", false);
|
||||
} catch (UnsupportedAudioFileException
|
||||
| LineUnavailableException
|
||||
| IOException e) {
|
||||
logger.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handlePlaySound(AudioEvents.PlayEffect event) {
|
||||
@@ -68,14 +69,13 @@ public class SoundManager {
|
||||
|
||||
private void handleMusicStart(AudioEvents.StartBackgroundMusic e) {
|
||||
backgroundMusicQueue.clear();
|
||||
List<MusicAsset> shuffledArray = new ArrayList<>(ResourceManager.getAllOfType(MusicAsset.class)
|
||||
.stream()
|
||||
.map(ResourceMeta::getResource)
|
||||
.toList());
|
||||
List<MusicAsset> shuffledArray =
|
||||
new ArrayList<>(
|
||||
ResourceManager.getAllOfType(MusicAsset.class).stream()
|
||||
.map(ResourceMeta::getResource)
|
||||
.toList());
|
||||
Collections.shuffle(shuffledArray);
|
||||
backgroundMusicQueue.addAll(
|
||||
shuffledArray
|
||||
);
|
||||
backgroundMusicQueue.addAll(shuffledArray);
|
||||
backgroundMusicPlayer();
|
||||
}
|
||||
|
||||
@@ -89,34 +89,42 @@ public class SoundManager {
|
||||
|
||||
MediaPlayer mediaPlayer = new MediaPlayer(ma.getMedia());
|
||||
|
||||
mediaPlayer.setOnEndOfMedia(() -> {
|
||||
addBackgroundMusic(ma);
|
||||
activeMusic.remove(mediaPlayer);
|
||||
mediaPlayer.dispose();
|
||||
ma.unload();
|
||||
backgroundMusicPlayer(); // play next
|
||||
});
|
||||
mediaPlayer.setOnEndOfMedia(
|
||||
() -> {
|
||||
addBackgroundMusic(ma);
|
||||
activeMusic.remove(mediaPlayer);
|
||||
mediaPlayer.dispose();
|
||||
ma.unload();
|
||||
backgroundMusicPlayer(); // play next
|
||||
});
|
||||
|
||||
mediaPlayer.setOnStopped(() -> {
|
||||
addBackgroundMusic(ma);
|
||||
activeMusic.remove(mediaPlayer);
|
||||
ma.unload();
|
||||
});
|
||||
mediaPlayer.setOnStopped(
|
||||
() -> {
|
||||
addBackgroundMusic(ma);
|
||||
activeMusic.remove(mediaPlayer);
|
||||
ma.unload();
|
||||
});
|
||||
|
||||
mediaPlayer.setOnError(() -> {
|
||||
addBackgroundMusic(ma);
|
||||
activeMusic.remove(mediaPlayer);
|
||||
ma.unload();
|
||||
});
|
||||
mediaPlayer.setOnError(
|
||||
() -> {
|
||||
addBackgroundMusic(ma);
|
||||
activeMusic.remove(mediaPlayer);
|
||||
ma.unload();
|
||||
});
|
||||
|
||||
audioVolumeManager.updateMusicVolume(mediaPlayer);
|
||||
mediaPlayer.play();
|
||||
activeMusic.add(mediaPlayer);
|
||||
logger.info("Playing background music: {}", ma.getFile().getName());
|
||||
logger.info("Background music next in line: {}", backgroundMusicQueue.peek().getFile().getName());
|
||||
logger.info(
|
||||
"Background music next in line: {}",
|
||||
backgroundMusicQueue.peek() != null
|
||||
? backgroundMusicQueue.peek().getFile().getName()
|
||||
: null);
|
||||
}
|
||||
|
||||
private long playSound(String audioFileName, boolean loop) throws UnsupportedAudioFileException, LineUnavailableException, IOException {
|
||||
private long playSound(String audioFileName, boolean loop)
|
||||
throws UnsupportedAudioFileException, LineUnavailableException, IOException {
|
||||
SoundEffectAsset asset = audioResources.get(audioFileName);
|
||||
|
||||
// Return -1 which indicates resource wasn't available
|
||||
@@ -134,26 +142,26 @@ public class SoundManager {
|
||||
// If supposed to loop make it loop, else just start it once
|
||||
if (loop) {
|
||||
clip.loop(Clip.LOOP_CONTINUOUSLY);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
clip.start();
|
||||
}
|
||||
|
||||
logger.debug("Playing sound: {}", asset.getFile().getName());
|
||||
|
||||
// Generate id for clip
|
||||
long clipId = idGenerator.nextId();
|
||||
long clipId = new SnowflakeGenerator().nextId();
|
||||
|
||||
// store it so we can stop it later
|
||||
activeSoundEffects.put(clipId, clip); // TODO: Do on snowflake for specific sound to stop
|
||||
activeSoundEffects.put(clipId, clip);
|
||||
|
||||
// remove when finished (only for non-looping sounds)
|
||||
clip.addLineListener(event -> {
|
||||
if (event.getType() == LineEvent.Type.STOP && !clip.isRunning()) {
|
||||
activeSoundEffects.remove(clipId);
|
||||
clip.close();
|
||||
}
|
||||
});
|
||||
clip.addLineListener(
|
||||
event -> {
|
||||
if (event.getType() == LineEvent.Type.STOP && !clip.isRunning()) {
|
||||
activeSoundEffects.remove(clipId);
|
||||
clip.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Return id so it can be stopped
|
||||
return clipId;
|
||||
@@ -179,7 +187,11 @@ public class SoundManager {
|
||||
activeSoundEffects.clear();
|
||||
}
|
||||
|
||||
public Map<Long, Clip> getActiveSoundEffects(){ return this.activeSoundEffects; }
|
||||
public Map<Long, Clip> getActiveSoundEffects() {
|
||||
return this.activeSoundEffects;
|
||||
}
|
||||
|
||||
public List<MediaPlayer> getActiveMusic() { return activeMusic; }
|
||||
public List<MediaPlayer> getActiveMusic() {
|
||||
return activeMusic;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
package org.toop.framework.audio.events;
|
||||
|
||||
import java.util.Map;
|
||||
import org.toop.framework.eventbus.events.EventWithSnowflake;
|
||||
import org.toop.framework.eventbus.events.EventWithoutSnowflake;
|
||||
import org.toop.framework.eventbus.events.EventsBase;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class AudioEvents extends EventsBase {
|
||||
/** Starts playing a sound. */
|
||||
public record PlayEffect(String fileName, boolean loop)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record PlayEffect(String fileName, boolean loop) implements EventWithoutSnowflake {}
|
||||
|
||||
public record StopEffect(long clipId) implements EventWithoutSnowflake {}
|
||||
|
||||
public record StartBackgroundMusic() implements EventWithoutSnowflake {}
|
||||
|
||||
public record ChangeVolume(double newVolume) implements EventWithoutSnowflake {}
|
||||
|
||||
public record ChangeFxVolume(double newVolume) implements EventWithoutSnowflake {}
|
||||
|
||||
public record ChangeMusicVolume(double newVolume) implements EventWithoutSnowflake {}
|
||||
|
||||
public record GetCurrentVolume(long snowflakeId) implements EventWithSnowflake {
|
||||
@@ -29,7 +30,9 @@ public class AudioEvents extends EventsBase {
|
||||
return snowflakeId;
|
||||
}
|
||||
}
|
||||
public record GetCurrentVolumeResponse(double currentVolume, long snowflakeId) implements EventWithSnowflake {
|
||||
|
||||
public record GetCurrentVolumeResponse(double currentVolume, long snowflakeId)
|
||||
implements EventWithSnowflake {
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Map.of();
|
||||
@@ -65,7 +68,8 @@ public class AudioEvents extends EventsBase {
|
||||
}
|
||||
}
|
||||
|
||||
public record GetCurrentFxVolumeResponse(double currentVolume, long snowflakeId) implements EventWithSnowflake {
|
||||
public record GetCurrentFxVolumeResponse(double currentVolume, long snowflakeId)
|
||||
implements EventWithSnowflake {
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Map.of();
|
||||
@@ -77,7 +81,8 @@ public class AudioEvents extends EventsBase {
|
||||
}
|
||||
}
|
||||
|
||||
public record GetCurrentMusicVolumeResponse(double currentVolume, long snowflakeId) implements EventWithSnowflake {
|
||||
public record GetCurrentMusicVolumeResponse(double currentVolume, long snowflakeId)
|
||||
implements EventWithSnowflake {
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Map.of();
|
||||
@@ -90,5 +95,4 @@ public class AudioEvents extends EventsBase {
|
||||
}
|
||||
|
||||
public record ClickButton() implements EventWithoutSnowflake {}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.toop.framework.eventbus;
|
||||
|
||||
public class ListenerHandler {
|
||||
private Object listener = null;
|
||||
private Object listener;
|
||||
|
||||
// private boolean unsubscribeAfterSuccess = true;
|
||||
|
||||
|
||||
@@ -84,9 +84,9 @@ public class NetworkingClient {
|
||||
if (isChannelActive()) {
|
||||
this.channel.writeAndFlush(msg);
|
||||
logger.info(
|
||||
"Connection {} sent message: '{}'", this.channel.remoteAddress(), literalMsg);
|
||||
"Connection {} sent message: '{}' ", this.channel.remoteAddress(), literalMsg);
|
||||
} else {
|
||||
logger.warn("Cannot send message: '{}', connection inactive.", literalMsg);
|
||||
logger.warn("Cannot send message: '{}', connection inactive. ", literalMsg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ public class NetworkingClientManager {
|
||||
}
|
||||
|
||||
long startClientRequest(String ip, int port) {
|
||||
long connectionId = new SnowflakeGenerator().nextId(); // TODO: Maybe use the one generated
|
||||
try { // With EventFlow
|
||||
long connectionId = new SnowflakeGenerator().nextId();
|
||||
try {
|
||||
NetworkingClient client =
|
||||
new NetworkingClient(
|
||||
() -> new NetworkingGameClientHandler(connectionId),
|
||||
@@ -81,19 +81,13 @@ public class NetworkingClientManager {
|
||||
void handleStartClient(NetworkEvents.StartClient event) {
|
||||
long id = this.startClientRequest(event.ip(), event.port());
|
||||
new Thread(
|
||||
() -> {
|
||||
try {
|
||||
Thread.sleep(100); // TODO: Is this a good idea?
|
||||
() ->
|
||||
new EventFlow()
|
||||
.addPostEvent(
|
||||
NetworkEvents.StartClientResponse.class,
|
||||
id,
|
||||
event.eventSnowflake())
|
||||
.asyncPostEvent();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.asyncPostEvent())
|
||||
.start();
|
||||
}
|
||||
|
||||
@@ -185,7 +179,7 @@ public class NetworkingClientManager {
|
||||
|
||||
void handleCloseClient(NetworkEvents.CloseClient event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
client.closeConnection(); // TODO: Check if not blocking, what if error, mb not remove?
|
||||
client.closeConnection();
|
||||
this.networkClients.remove(event.clientId());
|
||||
logger.info("Client {} closed successfully.", event.clientId());
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
|
||||
gameWinConditionHandler(recSrvRemoved);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
// return
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -93,10 +93,10 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
|
||||
helpHandler(recSrvRemoved);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
// return
|
||||
}
|
||||
} else {
|
||||
return; // TODO: Should be an error.
|
||||
logger.error("Could not parse: {}", rec);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
|
||||
try {
|
||||
String[] msg =
|
||||
Pattern.compile(
|
||||
"(?:CHALLENGER|GAMETYPE|CHALLENGENUMBER):\\s*\"?(.*?)\"?\\s*(?:,|})")
|
||||
"(?:CHALLENGER|GAMETYPE|CHALLENGENUMBER):\\s*\"?(.*?)\"?\\s*[,}]")
|
||||
.matcher(rec)
|
||||
.results()
|
||||
.map(m -> m.group().trim())
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
package org.toop.framework.asset;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.toop.framework.asset.events.AssetLoaderEvents;
|
||||
import org.toop.framework.asset.resources.*;
|
||||
import org.toop.framework.asset.types.BundledResource;
|
||||
import org.toop.framework.asset.types.FileExtension;
|
||||
import org.toop.framework.asset.types.PreloadResource;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.reflections.Reflections;
|
||||
package org.toop.framework.resource;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
@@ -16,30 +6,40 @@ 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.eventbus.EventFlow;
|
||||
import org.toop.framework.resource.events.AssetLoaderEvents;
|
||||
import org.toop.framework.resource.exceptions.CouldNotCreateResourceFactoryException;
|
||||
import org.toop.framework.resource.resources.*;
|
||||
import org.toop.framework.resource.types.BundledResource;
|
||||
import org.toop.framework.resource.types.FileExtension;
|
||||
import org.toop.framework.resource.types.PreloadResource;
|
||||
|
||||
/**
|
||||
* Responsible for loading assets from a file system directory into memory.
|
||||
* <p>
|
||||
* The {@code ResourceLoader} scans a root folder recursively, identifies files,
|
||||
* and maps them to registered resource types based on file extensions and
|
||||
* {@link FileExtension} annotations.
|
||||
* It supports multiple resource types including {@link PreloadResource} (automatically loaded)
|
||||
* and {@link BundledResource} (merged across multiple files).
|
||||
* </p>
|
||||
*
|
||||
* <p>Assets are stored in a static, thread-safe list and can be retrieved
|
||||
* through {@link ResourceManager}.</p>
|
||||
* <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:
|
||||
*
|
||||
* <p>Features:</p>
|
||||
* <ul>
|
||||
* <li>Recursive directory scanning for assets.</li>
|
||||
* <li>Automatic registration of resource classes via reflection.</li>
|
||||
* <li>Bundled resource support: multiple files merged into a single resource instance.</li>
|
||||
* <li>Preload resources automatically invoke {@link PreloadResource#load()}.</li>
|
||||
* <li>Progress tracking via {@link AssetLoaderEvents.LoadingProgressUpdate} events.</li>
|
||||
* <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:</p>
|
||||
* <p>Usage example:
|
||||
*
|
||||
* <pre>{@code
|
||||
* ResourceLoader loader = new ResourceLoader("assets");
|
||||
* double progress = loader.getProgress();
|
||||
@@ -48,14 +48,17 @@ import java.util.function.Function;
|
||||
*/
|
||||
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 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) {
|
||||
@@ -84,6 +87,7 @@ public class ResourceLoader {
|
||||
|
||||
/**
|
||||
* Constructs an ResourceLoader from a folder path.
|
||||
*
|
||||
* @param rootFolder the folder path containing assets
|
||||
*/
|
||||
public ResourceLoader(String rootFolder) {
|
||||
@@ -92,6 +96,7 @@ public class ResourceLoader {
|
||||
|
||||
/**
|
||||
* Returns the current progress of loading assets (0.0 to 1.0).
|
||||
*
|
||||
* @return progress as a double
|
||||
*/
|
||||
public double getProgress() {
|
||||
@@ -100,6 +105,7 @@ public class ResourceLoader {
|
||||
|
||||
/**
|
||||
* Returns the number of assets loaded so far.
|
||||
*
|
||||
* @return loaded count
|
||||
*/
|
||||
public int getLoadedCount() {
|
||||
@@ -108,6 +114,7 @@ public class ResourceLoader {
|
||||
|
||||
/**
|
||||
* Returns the total number of files found to load.
|
||||
*
|
||||
* @return total asset count
|
||||
*/
|
||||
public int getTotalCount() {
|
||||
@@ -116,6 +123,7 @@ public class ResourceLoader {
|
||||
|
||||
/**
|
||||
* Returns a snapshot list of all assets loaded by this loader.
|
||||
*
|
||||
* @return list of loaded assets
|
||||
*/
|
||||
public List<ResourceMeta<? extends BaseResource>> getAssets() {
|
||||
@@ -124,6 +132,7 @@ public class ResourceLoader {
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -132,69 +141,79 @@ public class ResourceLoader {
|
||||
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) {
|
||||
/** Maps a file to a resource instance based on its extension and registered factories. */
|
||||
private <T extends BaseResource> T resourceMapper(File file)
|
||||
throws CouldNotCreateResourceFactoryException, IllegalArgumentException {
|
||||
String ext = getExtension(file.getName());
|
||||
Function<File, ? extends BaseResource> factory = registry.get(ext);
|
||||
if (factory == null) return null;
|
||||
if (factory == null)
|
||||
throw new CouldNotCreateResourceFactoryException(registry, file.getName());
|
||||
|
||||
BaseResource resource = factory.apply(file);
|
||||
|
||||
if (!type.isInstance(resource)) {
|
||||
if (resource == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"File " + file.getName() + " is not of type " + type.getSimpleName()
|
||||
);
|
||||
"File "
|
||||
+ file.getName()
|
||||
+ " is not of type "
|
||||
+ BaseResource.class.getSimpleName());
|
||||
}
|
||||
|
||||
return type.cast(resource);
|
||||
return ((Class<T>) BaseResource.class).cast(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given list of files into assets, handling bundled and preload resources.
|
||||
*/
|
||||
/** Loads the given list of files into assets, handling bundled and preload resources. */
|
||||
private void loader(List<File> files) {
|
||||
Map<String, BundledResource> bundledResources = new HashMap<>();
|
||||
|
||||
for (File file : files) {
|
||||
boolean skipAdd = false;
|
||||
BaseResource resource = resourceMapper(file, BaseResource.class);
|
||||
BaseResource resource = null;
|
||||
try {
|
||||
resource = resourceMapper(file);
|
||||
} catch (CouldNotCreateResourceFactoryException _) {
|
||||
logger.warn("Could not create resource for: {}", file);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error(e);
|
||||
}
|
||||
switch (resource) {
|
||||
case null -> {
|
||||
continue;
|
||||
}
|
||||
case BundledResource br -> {
|
||||
String key = resource.getClass().getName() + ":" + br.getBaseName();
|
||||
if (!bundledResources.containsKey(key)) {bundledResources.put(key, br);}
|
||||
if (!bundledResources.containsKey(key)) {
|
||||
bundledResources.put(key, br);
|
||||
}
|
||||
bundledResources.get(key).loadFile(file);
|
||||
resource = (BaseResource) bundledResources.get(key);
|
||||
assets.add(new ResourceMeta<>(br.getBaseName(), resource));
|
||||
skipAdd = true;
|
||||
}
|
||||
case PreloadResource pr -> pr.load();
|
||||
default -> {
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
|
||||
BaseResource finalResource = resource;
|
||||
boolean alreadyAdded = assets.stream()
|
||||
.anyMatch(a -> a.getResource() == finalResource);
|
||||
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());
|
||||
logger.info(
|
||||
"Loaded {} from {}",
|
||||
resource.getClass().getSimpleName(),
|
||||
file.getAbsolutePath());
|
||||
loadedCount.incrementAndGet();
|
||||
new EventFlow()
|
||||
.addPostEvent(new AssetLoaderEvents.LoadingProgressUpdate(loadedCount.get(), totalCount))
|
||||
.addPostEvent(
|
||||
new AssetLoaderEvents.LoadingProgressUpdate(
|
||||
loadedCount.get(), totalCount))
|
||||
.postEvent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively searches a folder and adds all files to the foundFiles list.
|
||||
*/
|
||||
/** Recursively searches a folder and adds all files to the foundFiles list. */
|
||||
private void fileSearcher(final File folder, List<File> foundFiles) {
|
||||
for (File fileEntry : Objects.requireNonNull(folder.listFiles())) {
|
||||
if (fileEntry.isDirectory()) {
|
||||
@@ -206,31 +225,31 @@ public class ResourceLoader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses reflection to automatically register all {@link BaseResource} subclasses
|
||||
* annotated with {@link FileExtension}.
|
||||
* 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");
|
||||
Reflections reflections = new Reflections("org.toop.framework.resource.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);
|
||||
}
|
||||
});
|
||||
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.
|
||||
*/
|
||||
/** 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('.');
|
||||
@@ -238,9 +257,7 @@ public class ResourceLoader {
|
||||
return fileName.substring(0, dotIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file extension of a given file name (without dot).
|
||||
*/
|
||||
/** Returns the file extension of a given file name (without dot). */
|
||||
public static String getExtension(String name) {
|
||||
int i = name.lastIndexOf('.');
|
||||
return (i > 0) ? name.substring(i + 1) : "";
|
||||
@@ -1,30 +1,31 @@
|
||||
package org.toop.framework.asset;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.toop.framework.asset.resources.*;
|
||||
package org.toop.framework.resource;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.toop.framework.resource.exceptions.ResourceNotFoundException;
|
||||
import org.toop.framework.resource.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>
|
||||
*
|
||||
* <p>Key responsibilities:</p>
|
||||
* <p>{@code ResourceManager} maintains a thread-safe registry of {@link ResourceMeta} 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>
|
||||
* <li>Providing typed access to asset resources.</li>
|
||||
* <li>Allowing lookup by asset name or ID.</li>
|
||||
* <li>Supporting retrieval of all assets of a specific {@link BaseResource} subclass.</li>
|
||||
* <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:</p>
|
||||
* <p>Example usage:
|
||||
*
|
||||
* <pre>{@code
|
||||
* // Load assets from a loader
|
||||
* ResourceLoader loader = new ResourceLoader(new File("RootFolder"));
|
||||
@@ -40,36 +41,29 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* Optional<Asset<? extends BaseResource>> maybeAsset = ResourceManager.findByName("menu.css");
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Notes:</p>
|
||||
* <p>Notes:
|
||||
*
|
||||
* <ul>
|
||||
* <li>All retrieval methods are static and thread-safe.</li>
|
||||
* <li>The {@link #get(String)} method may require casting if the asset type is not known at compile time.</li>
|
||||
* <li>Assets should be loaded via {@link ResourceLoader} before retrieval.</li>
|
||||
* <li>All retrieval methods are static and thread-safe.
|
||||
* <li>The {@link #get(String)} method may require casting if the asset type is not known at
|
||||
* compile time.
|
||||
* <li>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 static final Logger logger = LogManager.getLogger(ResourceManager.class);
|
||||
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 synchronized static void loadAssets(ResourceLoader loader) {
|
||||
for (var asset : loader.getAssets()) {
|
||||
public static synchronized void loadAssets(ResourceLoader loader) {
|
||||
for (ResourceMeta<? extends BaseResource> asset : loader.getAssets()) {
|
||||
assets.put(asset.getName(), asset);
|
||||
}
|
||||
}
|
||||
@@ -85,15 +79,15 @@ public class ResourceManager {
|
||||
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
|
||||
throw new ResourceNotFoundException(name);
|
||||
}
|
||||
return asset.getResource();
|
||||
}
|
||||
|
||||
// @SuppressWarnings("unchecked")
|
||||
// public static <T extends BaseResource> ArrayList<ResourceMeta<T>> getAllOfType() {
|
||||
// return (ArrayList<ResourceMeta<T>>) (ArrayList<?>) new ArrayList<>(assets.values());
|
||||
// }
|
||||
// @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.
|
||||
@@ -136,5 +130,6 @@ public class ResourceManager {
|
||||
*/
|
||||
public static void addAsset(ResourceMeta<? extends BaseResource> asset) {
|
||||
assets.put(asset.getName(), asset);
|
||||
logger.info("Successfully added asset: {}, to the asset list", asset.getName());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.toop.framework.asset;
|
||||
package org.toop.framework.resource;
|
||||
|
||||
import org.toop.framework.SnowflakeGenerator;
|
||||
import org.toop.framework.asset.resources.BaseResource;
|
||||
import org.toop.framework.resource.resources.BaseResource;
|
||||
|
||||
public class ResourceMeta<T extends BaseResource> {
|
||||
private final Long id;
|
||||
@@ -25,5 +25,4 @@ public class ResourceMeta<T extends BaseResource> {
|
||||
public T getResource() {
|
||||
return this.resource;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package org.toop.framework.asset.events;
|
||||
package org.toop.framework.resource.events;
|
||||
|
||||
import org.toop.framework.eventbus.events.EventWithoutSnowflake;
|
||||
|
||||
public class AssetLoaderEvents {
|
||||
public record LoadingProgressUpdate(int hasLoadedAmount, int isLoadingAmount) implements EventWithoutSnowflake {}
|
||||
public record LoadingProgressUpdate(int hasLoadedAmount, int isLoadingAmount)
|
||||
implements EventWithoutSnowflake {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.toop.framework.resource.exceptions;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class CouldNotCreateResourceFactoryException extends RuntimeException {
|
||||
public CouldNotCreateResourceFactoryException(Map<?, ?> registry, String fileName) {
|
||||
super(
|
||||
String.format(
|
||||
"Could not create resource factory for: %s, isRegistryEmpty: %b",
|
||||
fileName, registry.isEmpty()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.toop.framework.resource.exceptions;
|
||||
|
||||
public class IsNotAResourceException extends RuntimeException {
|
||||
public <T> IsNotAResourceException(Class<T> clazz, String message) {
|
||||
super(clazz.getName() + " does not implement BaseResource");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.toop.framework.resource.exceptions;
|
||||
|
||||
public class ResourceNotFoundException extends RuntimeException {
|
||||
public ResourceNotFoundException(String name) {
|
||||
super("Could not find resource: " + name);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.toop.framework.asset.resources;
|
||||
package org.toop.framework.resource.resources;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
@@ -14,5 +14,4 @@ public abstract class BaseResource {
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package org.toop.framework.asset.resources;
|
||||
|
||||
import org.toop.framework.asset.types.FileExtension;
|
||||
package org.toop.framework.resource.resources;
|
||||
|
||||
import java.io.File;
|
||||
import org.toop.framework.resource.types.FileExtension;
|
||||
|
||||
@FileExtension({"css"})
|
||||
public class CssAsset extends BaseResource {
|
||||
@@ -1,12 +1,11 @@
|
||||
package org.toop.framework.asset.resources;
|
||||
|
||||
import javafx.scene.text.Font;
|
||||
import org.toop.framework.asset.types.FileExtension;
|
||||
import org.toop.framework.asset.types.PreloadResource;
|
||||
package org.toop.framework.resource.resources;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import javafx.scene.text.Font;
|
||||
import org.toop.framework.resource.types.FileExtension;
|
||||
import org.toop.framework.resource.types.PreloadResource;
|
||||
|
||||
@FileExtension({"ttf", "otf"})
|
||||
public class FontAsset extends BaseResource implements PreloadResource {
|
||||
@@ -60,4 +59,4 @@ public class FontAsset extends BaseResource implements PreloadResource {
|
||||
}
|
||||
return this.family;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
package org.toop.framework.asset.resources;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import org.toop.framework.asset.types.FileExtension;
|
||||
import org.toop.framework.asset.types.LoadableResource;
|
||||
package org.toop.framework.resource.resources;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import javafx.scene.image.Image;
|
||||
import org.toop.framework.resource.types.FileExtension;
|
||||
import org.toop.framework.resource.types.LoadableResource;
|
||||
|
||||
@FileExtension({"png", "jpg", "jpeg"})
|
||||
public class ImageAsset extends BaseResource implements LoadableResource {
|
||||
@@ -45,4 +44,4 @@ public class ImageAsset extends BaseResource implements LoadableResource {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package org.toop.framework.asset.resources;
|
||||
package org.toop.framework.resource.resources;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.toop.framework.asset.types.FileExtension;
|
||||
import org.toop.framework.asset.types.LoadableResource;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import org.toop.framework.resource.types.FileExtension;
|
||||
import org.toop.framework.resource.types.LoadableResource;
|
||||
|
||||
@FileExtension({"json"})
|
||||
public class JsonAsset<T> extends BaseResource implements LoadableResource {
|
||||
@@ -25,7 +26,8 @@ public class JsonAsset<T> extends BaseResource implements LoadableResource {
|
||||
File file = getFile();
|
||||
if (!file.exists()) {
|
||||
try {
|
||||
// make a new file with the declared constructor (example: settings) if it doesn't exist
|
||||
// make a new file with the declared constructor (example: settings) if it doesn't
|
||||
// exist
|
||||
content = type.getDeclaredConstructor().newInstance();
|
||||
save();
|
||||
} catch (Exception e) {
|
||||
@@ -36,7 +38,7 @@ public class JsonAsset<T> extends BaseResource implements LoadableResource {
|
||||
try (FileReader reader = new FileReader(file)) {
|
||||
content = gson.fromJson(reader, type);
|
||||
this.isLoaded = true;
|
||||
} catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to load JSON asset" + getFile(), e);
|
||||
}
|
||||
}
|
||||
@@ -59,10 +61,11 @@ public class JsonAsset<T> extends BaseResource implements LoadableResource {
|
||||
File file = getFile();
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null && !parent.exists()) {
|
||||
parent.mkdirs();
|
||||
boolean isDirectoryMade = parent.mkdirs();
|
||||
assert isDirectoryMade;
|
||||
}
|
||||
try (FileWriter writer = new FileWriter(file)) {
|
||||
gson.toJson(content, writer);
|
||||
gson.toJson(content, writer);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to save JSON asset" + getFile(), e);
|
||||
}
|
||||
@@ -1,34 +1,29 @@
|
||||
package org.toop.framework.asset.resources;
|
||||
|
||||
import org.toop.framework.asset.types.BundledResource;
|
||||
import org.toop.framework.asset.types.FileExtension;
|
||||
import org.toop.framework.asset.types.LoadableResource;
|
||||
package org.toop.framework.resource.resources;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import org.toop.framework.resource.types.BundledResource;
|
||||
import org.toop.framework.resource.types.FileExtension;
|
||||
import org.toop.framework.resource.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>
|
||||
* <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>
|
||||
* 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:
|
||||
*
|
||||
* <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>
|
||||
* </p>
|
||||
*/
|
||||
@FileExtension({"properties"})
|
||||
public class LocalizationAsset extends BaseResource implements LoadableResource, BundledResource {
|
||||
@@ -43,7 +38,7 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
||||
private final String baseName = "localization";
|
||||
|
||||
/** Fallback locale used when no matching locale is found. */
|
||||
private final Locale fallback = Locale.forLanguageTag("en_US");
|
||||
private final Locale fallback = Locale.forLanguageTag("en");
|
||||
|
||||
/**
|
||||
* Constructs a new LocalizationAsset for the specified file.
|
||||
@@ -54,18 +49,14 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
||||
super(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the resource file into memory and prepares localized bundles.
|
||||
*/
|
||||
/** 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.
|
||||
*/
|
||||
/** Unloads all loaded resource bundles, freeing memory. */
|
||||
@Override
|
||||
public void unload() {
|
||||
bundles.clear();
|
||||
@@ -83,11 +74,10 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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 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
|
||||
@@ -95,14 +85,15 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
||||
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);
|
||||
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.
|
||||
* 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
|
||||
@@ -125,8 +116,8 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a specific property file as a resource bundle.
|
||||
* The locale is extracted from the file name if present.
|
||||
* 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
|
||||
@@ -134,7 +125,7 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
||||
@Override
|
||||
public void loadFile(File file) {
|
||||
try (InputStreamReader reader =
|
||||
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
|
||||
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
|
||||
Locale locale = extractLocale(file.getName(), baseName);
|
||||
bundles.put(locale, new PropertyResourceBundle(reader));
|
||||
} catch (IOException e) {
|
||||
@@ -153,22 +144,23 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
||||
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 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".
|
||||
@@ -1,10 +1,9 @@
|
||||
package org.toop.framework.asset.resources;
|
||||
|
||||
import javafx.scene.media.Media;
|
||||
import org.toop.framework.asset.types.FileExtension;
|
||||
import org.toop.framework.asset.types.LoadableResource;
|
||||
package org.toop.framework.resource.resources;
|
||||
|
||||
import java.io.*;
|
||||
import javafx.scene.media.Media;
|
||||
import org.toop.framework.resource.types.FileExtension;
|
||||
import org.toop.framework.resource.types.LoadableResource;
|
||||
|
||||
@FileExtension({"mp3"})
|
||||
public class MusicAsset extends BaseResource implements LoadableResource {
|
||||
@@ -37,4 +36,4 @@ public class MusicAsset extends BaseResource implements LoadableResource {
|
||||
public boolean isLoaded() {
|
||||
return isLoaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
package org.toop.framework.asset.resources;
|
||||
|
||||
|
||||
import org.toop.framework.settings.Settings;
|
||||
package org.toop.framework.resource.resources;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
import org.toop.framework.settings.Settings;
|
||||
|
||||
public class SettingsAsset extends JsonAsset<Settings> {
|
||||
|
||||
@@ -32,13 +30,13 @@ public class SettingsAsset extends JsonAsset<Settings> {
|
||||
return getContent().fullScreen;
|
||||
}
|
||||
|
||||
public String getTheme() {
|
||||
return getContent().theme;
|
||||
}
|
||||
public String getTheme() {
|
||||
return getContent().theme;
|
||||
}
|
||||
|
||||
public String getLayoutSize() {
|
||||
return getContent().layoutSize;
|
||||
}
|
||||
public String getLayoutSize() {
|
||||
return getContent().layoutSize;
|
||||
}
|
||||
|
||||
public void setVolume(int volume) {
|
||||
getContent().volume = volume;
|
||||
@@ -65,13 +63,13 @@ public class SettingsAsset extends JsonAsset<Settings> {
|
||||
save();
|
||||
}
|
||||
|
||||
public void setTheme(String theme) {
|
||||
getContent().theme = theme;
|
||||
save();
|
||||
}
|
||||
public void setTheme(String theme) {
|
||||
getContent().theme = theme;
|
||||
save();
|
||||
}
|
||||
|
||||
public void setLayoutSize(String layoutSize) {
|
||||
getContent().layoutSize = layoutSize;
|
||||
save();
|
||||
}
|
||||
}
|
||||
public void setLayoutSize(String layoutSize) {
|
||||
getContent().layoutSize = layoutSize;
|
||||
save();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
package org.toop.framework.asset.resources;
|
||||
package org.toop.framework.resource.resources;
|
||||
|
||||
import org.toop.framework.asset.types.FileExtension;
|
||||
import org.toop.framework.asset.types.LoadableResource;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import javax.sound.sampled.*;
|
||||
import org.toop.framework.resource.types.FileExtension;
|
||||
import org.toop.framework.resource.types.LoadableResource;
|
||||
|
||||
@FileExtension({"wav"})
|
||||
public class SoundEffectAsset extends BaseResource implements LoadableResource {
|
||||
@@ -16,22 +15,25 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource {
|
||||
}
|
||||
|
||||
// Gets a new clip to play
|
||||
public Clip getNewClip() throws LineUnavailableException, UnsupportedAudioFileException, IOException {
|
||||
public Clip getNewClip()
|
||||
throws LineUnavailableException, UnsupportedAudioFileException, IOException {
|
||||
// Get a new clip from audio system
|
||||
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.
|
||||
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()){
|
||||
if (!this.isLoaded()) {
|
||||
this.load();
|
||||
}
|
||||
|
||||
@@ -39,16 +41,18 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource {
|
||||
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
|
||||
);
|
||||
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);
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
package org.toop.framework.asset.resources;
|
||||
|
||||
import org.toop.framework.asset.types.FileExtension;
|
||||
import org.toop.framework.asset.types.LoadableResource;
|
||||
package org.toop.framework.resource.resources;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import org.toop.framework.resource.types.FileExtension;
|
||||
import org.toop.framework.resource.types.LoadableResource;
|
||||
|
||||
@FileExtension({"txt", "json", "xml"})
|
||||
public class TextAsset extends BaseResource implements LoadableResource {
|
||||
@@ -41,4 +40,4 @@ public class TextAsset extends BaseResource implements LoadableResource {
|
||||
public String getContent() {
|
||||
return this.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.toop.framework.resource.types;
|
||||
|
||||
import java.io.File;
|
||||
import org.toop.framework.resource.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.resource.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.resource.ResourceLoader;
|
||||
import org.toop.framework.resource.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.resource.types;
|
||||
|
||||
import org.toop.framework.resource.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();
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
package org.toop.framework.asset.types;
|
||||
package org.toop.framework.resource.types;
|
||||
|
||||
import org.toop.framework.asset.ResourceLoader;
|
||||
import org.toop.framework.resource.ResourceLoader;
|
||||
|
||||
/**
|
||||
* Marker interface for resources that should be **automatically loaded** by the {@link ResourceLoader}.
|
||||
* Marker interface for resources that should be **automatically loaded** by the {@link
|
||||
* ResourceLoader}.
|
||||
*
|
||||
* <p>Extends {@link LoadableResource}, so any implementing class must provide the standard
|
||||
* {@link LoadableResource#load()} and {@link LoadableResource#unload()} methods, as well as the
|
||||
* {@link LoadableResource#isLoaded()} check.</p>
|
||||
* <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>
|
||||
* without requiring manual loading by the user.
|
||||
*
|
||||
* <p>Typical usage:
|
||||
*
|
||||
* <p>Typical usage:</p>
|
||||
* <pre>{@code
|
||||
* public class MyFontAsset extends BaseResource implements PreloadResource {
|
||||
* @Override
|
||||
@@ -34,6 +36,6 @@ import org.toop.framework.asset.ResourceLoader;
|
||||
* }</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.</p>
|
||||
* increase memory usage or startup time.
|
||||
*/
|
||||
public interface PreloadResource extends LoadableResource {}
|
||||
@@ -3,8 +3,8 @@ package org.toop.framework.settings;
|
||||
public class Settings {
|
||||
public boolean fullScreen = false;
|
||||
public String locale = "en";
|
||||
public String theme = "dark";
|
||||
public String layoutSize = "medium";
|
||||
public String theme = "dark";
|
||||
public String layoutSize = "medium";
|
||||
public int volume = 100;
|
||||
public int fxVolume = 20;
|
||||
public int musicVolume = 15;
|
||||
|
||||
@@ -35,10 +35,10 @@ class NetworkEventsTest {
|
||||
@Test
|
||||
void testChallengeResponse() {
|
||||
NetworkEvents.ChallengeResponse event =
|
||||
new NetworkEvents.ChallengeResponse(1L, "Alice", "Chess", "ch001");
|
||||
assertEquals("Alice", event.challengerName());
|
||||
assertEquals("Chess", event.gameType());
|
||||
assertEquals("ch001", event.challengeId());
|
||||
new NetworkEvents.ChallengeResponse(1L, "John", "1", "tic-tac-toe");
|
||||
assertEquals("John", event.challengerName());
|
||||
assertEquals("1", event.challengeId());
|
||||
assertEquals("tic-tac-toe", event.gameType());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -3,34 +3,37 @@ package org.toop.game;
|
||||
import java.util.Arrays;
|
||||
|
||||
public abstract class Game {
|
||||
public enum State {
|
||||
NORMAL, DRAW, WIN,
|
||||
}
|
||||
public enum State {
|
||||
NORMAL,
|
||||
DRAW,
|
||||
WIN,
|
||||
}
|
||||
|
||||
public record Move(int position, char value) {}
|
||||
public record Move(int position, char value) {}
|
||||
|
||||
public static final char EMPTY = (char)0;
|
||||
public static final char EMPTY = (char) 0;
|
||||
|
||||
public final int rowSize;
|
||||
public final int columnSize;
|
||||
public final char[] board;
|
||||
public final int rowSize;
|
||||
public final int columnSize;
|
||||
public final char[] board;
|
||||
|
||||
protected Game(int rowSize, int columnSize) {
|
||||
assert rowSize > 0 && columnSize > 0;
|
||||
protected Game(int rowSize, int columnSize) {
|
||||
assert rowSize > 0 && columnSize > 0;
|
||||
|
||||
this.rowSize = rowSize;
|
||||
this.columnSize = columnSize;
|
||||
this.rowSize = rowSize;
|
||||
this.columnSize = columnSize;
|
||||
|
||||
board = new char[rowSize * columnSize];
|
||||
Arrays.fill(board, EMPTY);
|
||||
}
|
||||
board = new char[rowSize * columnSize];
|
||||
Arrays.fill(board, EMPTY);
|
||||
}
|
||||
|
||||
protected Game(Game other) {
|
||||
rowSize = other.rowSize;
|
||||
columnSize = other.columnSize;
|
||||
board = Arrays.copyOf(other.board, other.board.length);
|
||||
}
|
||||
protected Game(Game other) {
|
||||
rowSize = other.rowSize;
|
||||
columnSize = other.columnSize;
|
||||
board = Arrays.copyOf(other.board, other.board.length);
|
||||
}
|
||||
|
||||
public abstract Move[] getLegalMoves();
|
||||
public abstract State play(Move move);
|
||||
}
|
||||
public abstract Move[] getLegalMoves();
|
||||
|
||||
public abstract State play(Move move);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
package org.toop.game;
|
||||
|
||||
public abstract class TurnBasedGame extends Game {
|
||||
public final int turns;
|
||||
public final int turns;
|
||||
|
||||
protected int currentTurn;
|
||||
protected int currentTurn;
|
||||
|
||||
protected TurnBasedGame(int rowSize, int columnSize, int turns) {
|
||||
super(rowSize, columnSize);
|
||||
protected TurnBasedGame(int rowSize, int columnSize, int turns) {
|
||||
super(rowSize, columnSize);
|
||||
assert turns >= 2;
|
||||
this.turns = turns;
|
||||
}
|
||||
}
|
||||
|
||||
protected TurnBasedGame(TurnBasedGame other) {
|
||||
super(other);
|
||||
turns = other.turns;
|
||||
currentTurn = other.currentTurn;
|
||||
}
|
||||
protected TurnBasedGame(TurnBasedGame other) {
|
||||
super(other);
|
||||
turns = other.turns;
|
||||
currentTurn = other.currentTurn;
|
||||
}
|
||||
|
||||
protected void nextTurn() {
|
||||
currentTurn = (currentTurn + 1) % turns;
|
||||
}
|
||||
protected void nextTurn() {
|
||||
currentTurn = (currentTurn + 1) % turns;
|
||||
}
|
||||
|
||||
public int getCurrentTurn() { return currentTurn; }
|
||||
}
|
||||
public int getCurrentTurn() {
|
||||
return currentTurn;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ package org.toop.game.othello;
|
||||
import org.toop.game.TurnBasedGame;
|
||||
|
||||
public final class Othello extends TurnBasedGame {
|
||||
Othello() {
|
||||
super(8, 8, 2);
|
||||
}
|
||||
Othello() {
|
||||
super(8, 8, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Move[] getLegalMoves() {
|
||||
return new Move[0];
|
||||
}
|
||||
@Override
|
||||
public Move[] getLegalMoves() {
|
||||
return new Move[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public State play(Move move) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public State play(Move move) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import org.toop.game.AI;
|
||||
import org.toop.game.Game;
|
||||
|
||||
public final class OthelloAI extends AI<Othello> {
|
||||
@Override
|
||||
public Game.Move findBestMove(Othello game, int depth) {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public Game.Move findBestMove(Othello game, int depth) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package org.toop.game.tictactoe;
|
||||
|
||||
import org.toop.game.TurnBasedGame;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import org.toop.game.TurnBasedGame;
|
||||
|
||||
public final class TicTacToe extends TurnBasedGame {
|
||||
private int movesLeft;
|
||||
@@ -19,8 +18,8 @@ public final class TicTacToe extends TurnBasedGame {
|
||||
|
||||
@Override
|
||||
public Move[] getLegalMoves() {
|
||||
final ArrayList<Move> legalMoves = new ArrayList<>();
|
||||
final char currentValue = getCurrentValue();
|
||||
final ArrayList<Move> legalMoves = new ArrayList<>();
|
||||
final char currentValue = getCurrentValue();
|
||||
|
||||
for (int i = 0; i < board.length; i++) {
|
||||
if (board[i] == EMPTY) {
|
||||
@@ -44,12 +43,12 @@ public final class TicTacToe extends TurnBasedGame {
|
||||
return State.WIN;
|
||||
}
|
||||
|
||||
nextTurn();
|
||||
nextTurn();
|
||||
|
||||
if (movesLeft <= 2) {
|
||||
if (movesLeft <= 0 || checkForEarlyDraw(this)) {
|
||||
return State.DRAW;
|
||||
}
|
||||
if (movesLeft <= 0 || checkForEarlyDraw(this)) {
|
||||
return State.DRAW;
|
||||
}
|
||||
}
|
||||
|
||||
return State.NORMAL;
|
||||
@@ -60,7 +59,9 @@ public final class TicTacToe extends TurnBasedGame {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
final int index = i * 3;
|
||||
|
||||
if (board[index] != EMPTY && board[index] == board[index + 1] && board[index] == board[index + 2]) {
|
||||
if (board[index] != EMPTY
|
||||
&& board[index] == board[index + 1]
|
||||
&& board[index] == board[index + 2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -83,7 +84,7 @@ public final class TicTacToe extends TurnBasedGame {
|
||||
|
||||
private boolean checkForEarlyDraw(TicTacToe game) {
|
||||
for (final Move move : game.getLegalMoves()) {
|
||||
final TicTacToe copy = new TicTacToe(game);
|
||||
final TicTacToe copy = new TicTacToe(game);
|
||||
|
||||
if (copy.play(move) == State.WIN || !checkForEarlyDraw(copy)) {
|
||||
return false;
|
||||
@@ -93,7 +94,7 @@ public final class TicTacToe extends TurnBasedGame {
|
||||
return true;
|
||||
}
|
||||
|
||||
private char getCurrentValue() {
|
||||
return currentTurn == 0? 'X' : 'O';
|
||||
}
|
||||
}
|
||||
private char getCurrentValue() {
|
||||
return currentTurn == 0 ? 'X' : 'O';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +1,81 @@
|
||||
package org.toop.game.tictactoe;
|
||||
|
||||
import org.toop.game.Game;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.toop.game.Game;
|
||||
|
||||
class TicTacToeAITest {
|
||||
private TicTacToe game;
|
||||
private TicTacToeAI ai;
|
||||
private TicTacToe game;
|
||||
private TicTacToeAI ai;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
game = new TicTacToe();
|
||||
ai = new TicTacToeAI();
|
||||
}
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
game = new TicTacToe();
|
||||
ai = new TicTacToeAI();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBestMove_returnWinningMoveWithDepth1() {
|
||||
// X X -
|
||||
// O O -
|
||||
// - - -
|
||||
game.play(new Game.Move(0, 'X'));
|
||||
game.play(new Game.Move(3, 'O'));
|
||||
game.play(new Game.Move(1, 'X'));
|
||||
game.play(new Game.Move(4, 'O'));
|
||||
@Test
|
||||
void testBestMove_returnWinningMoveWithDepth1() {
|
||||
// X X -
|
||||
// O O -
|
||||
// - - -
|
||||
game.play(new Game.Move(0, 'X'));
|
||||
game.play(new Game.Move(3, 'O'));
|
||||
game.play(new Game.Move(1, 'X'));
|
||||
game.play(new Game.Move(4, 'O'));
|
||||
|
||||
final Game.Move move = ai.findBestMove(game, 1);
|
||||
final Game.Move move = ai.findBestMove(game, 1);
|
||||
|
||||
assertNotNull(move);
|
||||
assertEquals('X', move.value());
|
||||
assertEquals(2, move.position());
|
||||
}
|
||||
assertNotNull(move);
|
||||
assertEquals('X', move.value());
|
||||
assertEquals(2, move.position());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBestMove_blockOpponentWinDepth1() {
|
||||
// - - -
|
||||
// O - -
|
||||
// X X -
|
||||
game.play(new Game.Move(6, 'X'));
|
||||
game.play(new Game.Move(3, 'O'));
|
||||
game.play(new Game.Move(7, 'X'));
|
||||
@Test
|
||||
void testBestMove_blockOpponentWinDepth1() {
|
||||
// - - -
|
||||
// O - -
|
||||
// X X -
|
||||
game.play(new Game.Move(6, 'X'));
|
||||
game.play(new Game.Move(3, 'O'));
|
||||
game.play(new Game.Move(7, 'X'));
|
||||
|
||||
final Game.Move move = ai.findBestMove(game, 1);
|
||||
final Game.Move move = ai.findBestMove(game, 1);
|
||||
|
||||
assertNotNull(move);
|
||||
assertEquals('O', move.value());
|
||||
assertEquals(8, move.position());
|
||||
}
|
||||
assertNotNull(move);
|
||||
assertEquals('O', move.value());
|
||||
assertEquals(8, move.position());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBestMove_preferCornerOnEmpty() {
|
||||
final Game.Move move = ai.findBestMove(game, 0);
|
||||
@Test
|
||||
void testBestMove_preferCornerOnEmpty() {
|
||||
final Game.Move move = ai.findBestMove(game, 0);
|
||||
|
||||
assertNotNull(move);
|
||||
assertEquals('X', move.value());
|
||||
assertTrue(Set.of(0, 2, 6, 8).contains(move.position()));
|
||||
}
|
||||
assertNotNull(move);
|
||||
assertEquals('X', move.value());
|
||||
assertTrue(Set.of(0, 2, 6, 8).contains(move.position()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBestMove_findBestMoveDraw() {
|
||||
// O X -
|
||||
// - O X
|
||||
// X O X
|
||||
game.play(new Game.Move(1, 'X'));
|
||||
game.play(new Game.Move(0, 'O'));
|
||||
game.play(new Game.Move(5, 'X'));
|
||||
game.play(new Game.Move(4, 'O'));
|
||||
game.play(new Game.Move(6, 'X'));
|
||||
game.play(new Game.Move(7, 'O'));
|
||||
game.play(new Game.Move(8, 'X'));
|
||||
@Test
|
||||
void testBestMove_findBestMoveDraw() {
|
||||
// O X -
|
||||
// - O X
|
||||
// X O X
|
||||
game.play(new Game.Move(1, 'X'));
|
||||
game.play(new Game.Move(0, 'O'));
|
||||
game.play(new Game.Move(5, 'X'));
|
||||
game.play(new Game.Move(4, 'O'));
|
||||
game.play(new Game.Move(6, 'X'));
|
||||
game.play(new Game.Move(7, 'O'));
|
||||
game.play(new Game.Move(8, 'X'));
|
||||
|
||||
final Game.Move move = ai.findBestMove(game, game.getLegalMoves().length);
|
||||
final Game.Move move = ai.findBestMove(game, game.getLegalMoves().length);
|
||||
|
||||
assertNotNull(move);
|
||||
assertEquals('O', move.value());
|
||||
assertEquals(2, move.position());
|
||||
}
|
||||
}
|
||||
assertNotNull(move);
|
||||
assertEquals('O', move.value());
|
||||
assertEquals(2, move.position());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user