diff --git a/.gitignore b/.gitignore index 592a652..bfad6e0 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index dcffce8..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index 46f4d3b..c6bca1c 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -13,6 +13,7 @@ toop vmoptions xplugin + yourturn \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index c168b80..e917175 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,7 +2,8 @@ \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 64c32f6..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java index 05a34a4..ed1297a 100644 --- a/app/src/main/java/org/toop/Main.java +++ b/app/src/main/java/org/toop/Main.java @@ -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(); - } -} \ No newline at end of file + 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(); + } +} diff --git a/app/src/main/java/org/toop/local/AppContext.java b/app/src/main/java/org/toop/local/AppContext.java index 4a50029..4d96fb4 100644 --- a/app/src/main/java/org/toop/local/AppContext.java +++ b/app/src/main/java/org/toop/local/AppContext.java @@ -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); - } -} \ No newline at end of file + public static String getString(String key) { + assert localization != null; + return localization.getString(key, locale); + } +} diff --git a/app/src/main/java/org/toop/local/AppSettings.java b/app/src/main/java/org/toop/local/AppSettings.java index 6bb782c..4c367c5 100644 --- a/app/src/main/java/org/toop/local/AppSettings.java +++ b/app/src/main/java/org/toop/local/AppSettings.java @@ -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() { diff --git a/app/src/main/resources/assets/audio/music/formerseas.mp3 b/app/src/main/resources/assets/audio/music/formerseas.mp3 new file mode 100644 index 0000000..7e7bf38 Binary files /dev/null and b/app/src/main/resources/assets/audio/music/formerseas.mp3 differ diff --git a/framework/src/main/java/org/toop/framework/Logging.java b/framework/src/main/java/org/toop/framework/Logging.java index abd13cc..ad28f0b 100644 --- a/framework/src/main/java/org/toop/framework/Logging.java +++ b/framework/src/main/java/org/toop/framework/Logging.java @@ -12,191 +12,190 @@ import org.apache.logging.log4j.core.config.LoggerConfig; *

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 type of the class - */ - public static void disableLogsForClass(Class class_) { - disableLogsForClassInternal(class_.getName()); - } + /** + * Disables logs for a specific class. + * + * @param class_ the class for which logs should be disabled + * @param type of the class + */ + public static void disableLogsForClass(Class 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 type of the class - */ - public static void enableLogsForClass(Class 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 type of the class + */ + public static void enableLogsForClass(Class 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 void enableAllLogsForClass(Class class_) { - enableLogsForClass(class_, Level.ALL); - } + /** Convenience methods for enabling logs at specific levels for classes. */ + public static void enableAllLogsForClass(Class 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 void enableDebugLogsForClass(Class class_) { - enableLogsForClass(class_, Level.DEBUG); - } + public static void enableDebugLogsForClass(Class 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 void enableErrorLogsForClass(Class class_) { - enableLogsForClass(class_, Level.ERROR); - } + public static void enableErrorLogsForClass(Class 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 void enableFatalLogsForClass(Class class_) { - enableLogsForClass(class_, Level.FATAL); - } + public static void enableFatalLogsForClass(Class 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 void enableInfoLogsForClass(Class class_) { - enableLogsForClass(class_, Level.INFO); - } + public static void enableInfoLogsForClass(Class 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 void enableTraceLogsForClass(Class class_) { - enableLogsForClass(class_, Level.TRACE); - } + public static void enableTraceLogsForClass(Class class_) { + enableLogsForClass(class_, Level.TRACE); + } - public static void enableTraceLogsForClass(String className) { - enableLogsForClass(className, Level.TRACE); - } -} \ No newline at end of file + public static void enableTraceLogsForClass(String className) { + enableLogsForClass(className, Level.TRACE); + } +} diff --git a/framework/src/main/java/org/toop/framework/SnowflakeGenerator.java b/framework/src/main/java/org/toop/framework/SnowflakeGenerator.java index 7fbb946..a6d9ab3 100644 --- a/framework/src/main/java/org/toop/framework/SnowflakeGenerator.java +++ b/framework/src/main/java/org/toop/framework/SnowflakeGenerator.java @@ -7,26 +7,27 @@ import java.util.concurrent.atomic.AtomicLong; /** * A thread-safe, distributed unique ID generator following the Snowflake pattern. - *

- * Each generated 64-bit ID encodes: + * + *

Each generated 64-bit ID encodes: + * *

- *

* *

This implementation ensures: + * *

- *

* - *

Custom epoch is set to {@code 2025-01-01T00:00:00Z}.

+ *

Custom epoch is set to {@code 2025-01-01T00:00:00Z}. + * + *

Usage example: * - *

Usage example:

*
{@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.
-     * 

- * If multiple IDs are generated in the same millisecond, a sequence number - * is incremented. If the sequence overflows, waits until the next millisecond. - *

+ * + *

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(); } diff --git a/framework/src/main/java/org/toop/framework/asset/types/BundledResource.java b/framework/src/main/java/org/toop/framework/asset/types/BundledResource.java deleted file mode 100644 index ceb0f5f..0000000 --- a/framework/src/main/java/org/toop/framework/asset/types/BundledResource.java +++ /dev/null @@ -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. - * - *

Implementing classes allow an {@link ResourceLoader} - * to automatically merge multiple related files into a single resource instance.

- * - *

Typical use cases include:

- * - * - *

Implementing classes must provide:

- * - * - *

Example usage:

- *
{@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;
- *     }
- * }
- * }
- * - *

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(); -} \ No newline at end of file diff --git a/framework/src/main/java/org/toop/framework/asset/types/FileExtension.java b/framework/src/main/java/org/toop/framework/asset/types/FileExtension.java deleted file mode 100644 index b3c42d5..0000000 --- a/framework/src/main/java/org/toop/framework/asset/types/FileExtension.java +++ /dev/null @@ -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. - * - *

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.

- * - *

Usage example:

- *
{@code
- * @FileExtension({"png", "jpg"})
- * public class ImageAsset extends BaseResource implements LoadableResource {
- *     ...
- * }
- * }
- * - *

Key points:

- * - */ -@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(); -} diff --git a/framework/src/main/java/org/toop/framework/asset/types/LoadableResource.java b/framework/src/main/java/org/toop/framework/asset/types/LoadableResource.java deleted file mode 100644 index d25ba9e..0000000 --- a/framework/src/main/java/org/toop/framework/asset/types/LoadableResource.java +++ /dev/null @@ -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. - *

- * 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. - *

- * - *

Implementing classes must define the following behaviors:

- * - * - *

Typical usage:

- *
{@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;
- *     }
- * }
- * }
- * - *

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(); -} diff --git a/framework/src/main/java/org/toop/framework/audio/AudioVolumeManager.java b/framework/src/main/java/org/toop/framework/audio/AudioVolumeManager.java index 7d256cc..e97574a 100644 --- a/framework/src/main/java/org/toop/framework/audio/AudioVolumeManager.java +++ b/framework/src/main/java/org/toop/framework/audio/AudioVolumeManager.java @@ -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(); } } diff --git a/framework/src/main/java/org/toop/framework/audio/SoundManager.java b/framework/src/main/java/org/toop/framework/audio/SoundManager.java index 51c31ad..13a98cb 100644 --- a/framework/src/main/java/org/toop/framework/audio/SoundManager.java +++ b/framework/src/main/java/org/toop/framework/audio/SoundManager.java @@ -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 backgroundMusicQueue = new LinkedList<>(); private final Map activeSoundEffects = new HashMap<>(); private final HashMap 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 asset : ResourceManager.getAllOfType(SoundEffectAsset.class)) { + for (ResourceMeta 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 shuffledArray = new ArrayList<>(ResourceManager.getAllOfType(MusicAsset.class) - .stream() - .map(ResourceMeta::getResource) - .toList()); + List 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 getActiveSoundEffects(){ return this.activeSoundEffects; } + public Map getActiveSoundEffects() { + return this.activeSoundEffects; + } - public List getActiveMusic() { return activeMusic; } + public List getActiveMusic() { + return activeMusic; + } } diff --git a/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java b/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java index 5cb596d..aed23ee 100644 --- a/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java +++ b/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java @@ -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 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 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 result() { return Map.of(); @@ -90,5 +95,4 @@ public class AudioEvents extends EventsBase { } public record ClickButton() implements EventWithoutSnowflake {} - - } +} diff --git a/framework/src/main/java/org/toop/framework/eventbus/ListenerHandler.java b/framework/src/main/java/org/toop/framework/eventbus/ListenerHandler.java index 8daa274..cc5fbc4 100644 --- a/framework/src/main/java/org/toop/framework/eventbus/ListenerHandler.java +++ b/framework/src/main/java/org/toop/framework/eventbus/ListenerHandler.java @@ -1,7 +1,7 @@ package org.toop.framework.eventbus; public class ListenerHandler { - private Object listener = null; + private Object listener; // private boolean unsubscribeAfterSuccess = true; diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClient.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClient.java index 74f8a9f..fd99bf7 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClient.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClient.java @@ -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); } } diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java index 9c313d1..d8ed2b9 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java @@ -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()); } diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingGameClientHandler.java b/framework/src/main/java/org/toop/framework/networking/NetworkingGameClientHandler.java index 0b6dcaa..8c97f60 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingGameClientHandler.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingGameClientHandler.java @@ -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()) diff --git a/framework/src/main/java/org/toop/framework/asset/ResourceLoader.java b/framework/src/main/java/org/toop/framework/resource/ResourceLoader.java similarity index 61% rename from framework/src/main/java/org/toop/framework/asset/ResourceLoader.java rename to framework/src/main/java/org/toop/framework/resource/ResourceLoader.java index 4e69a50..dcada62 100644 --- a/framework/src/main/java/org/toop/framework/asset/ResourceLoader.java +++ b/framework/src/main/java/org/toop/framework/resource/ResourceLoader.java @@ -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. - *

- * 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). - *

* - *

Assets are stored in a static, thread-safe list and can be retrieved - * through {@link ResourceManager}.

+ *

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). + * + *

Assets are stored in a static, thread-safe list and can be retrieved through {@link + * ResourceManager}. + * + *

Features: * - *

Features:

*
    - *
  • Recursive directory scanning for assets.
  • - *
  • Automatic registration of resource classes via reflection.
  • - *
  • Bundled resource support: multiple files merged into a single resource instance.
  • - *
  • Preload resources automatically invoke {@link PreloadResource#load()}.
  • - *
  • Progress tracking via {@link AssetLoaderEvents.LoadingProgressUpdate} events.
  • + *
  • Recursive directory scanning for assets. + *
  • Automatic registration of resource classes via reflection. + *
  • Bundled resource support: multiple files merged into a single resource instance. + *
  • Preload resources automatically invoke {@link PreloadResource#load()}. + *
  • Progress tracking via {@link AssetLoaderEvents.LoadingProgressUpdate} events. *
* - *

Usage example:

+ *

Usage example: + * *

{@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> assets = new CopyOnWriteArrayList<>();
-    private final Map> registry = new ConcurrentHashMap<>();
+    private static final List> assets =
+            new CopyOnWriteArrayList<>();
+    private final Map> 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> 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  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 resourceMapper(File file, Class type) {
+    /** Maps a file to a resource instance based on its extension and registered factories. */
+    private  T resourceMapper(File file)
+            throws CouldNotCreateResourceFactoryException, IllegalArgumentException {
         String ext = getExtension(file.getName());
         Function 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) 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 files) {
         Map 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 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> classes = reflections.getSubTypesOf(BaseResource.class);
 
         for (Class 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) : "";
diff --git a/framework/src/main/java/org/toop/framework/asset/ResourceManager.java b/framework/src/main/java/org/toop/framework/resource/ResourceManager.java
similarity index 61%
rename from framework/src/main/java/org/toop/framework/asset/ResourceManager.java
rename to framework/src/main/java/org/toop/framework/resource/ResourceManager.java
index 983dc02..9641733 100644
--- a/framework/src/main/java/org/toop/framework/asset/ResourceManager.java
+++ b/framework/src/main/java/org/toop/framework/resource/ResourceManager.java
@@ -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.
- * 

- * {@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. - *

* - *

Key responsibilities:

+ *

{@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. + * + *

Key responsibilities: + * *

    - *
  • Storing all loaded assets in a concurrent map.
  • - *
  • Providing typed access to asset resources.
  • - *
  • Allowing lookup by asset name or ID.
  • - *
  • Supporting retrieval of all assets of a specific {@link BaseResource} subclass.
  • + *
  • Storing all loaded assets in a concurrent map. + *
  • Providing typed access to asset resources. + *
  • Allowing lookup by asset name or ID. + *
  • Supporting retrieval of all assets of a specific {@link BaseResource} subclass. *
* - *

Example usage:

+ *

Example usage: + * *

{@code
  * // Load assets from a loader
  * ResourceLoader loader = new ResourceLoader(new File("RootFolder"));
@@ -40,36 +41,29 @@ import java.util.concurrent.ConcurrentHashMap;
  * Optional> maybeAsset = ResourceManager.findByName("menu.css");
  * }
* - *

Notes:

+ *

Notes: + * *

    - *
  • All retrieval methods are static and thread-safe.
  • - *
  • The {@link #get(String)} method may require casting if the asset type is not known at compile time.
  • - *
  • Assets should be loaded via {@link ResourceLoader} before retrieval.
  • + *
  • All retrieval methods are static and thread-safe. + *
  • The {@link #get(String)} method may require casting if the asset type is not known at + * compile time. + *
  • Assets should be loaded via {@link ResourceLoader} before retrieval. *
*/ public class ResourceManager { - private static final Logger logger = LogManager.getLogger(ResourceManager.class); - private static final ResourceManager INSTANCE = new ResourceManager(); - private static final Map> assets = new ConcurrentHashMap<>(); + private static final Logger logger = LogManager.getLogger(ResourceManager.class); + private static final Map> 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 asset : loader.getAssets()) { assets.put(asset.getName(), asset); } } @@ -85,15 +79,15 @@ public class ResourceManager { public static T get(String name) { ResourceMeta asset = (ResourceMeta) 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 ArrayList> getAllOfType() { -// return (ArrayList>) (ArrayList) new ArrayList<>(assets.values()); -// } + // @SuppressWarnings("unchecked") + // public static ArrayList> getAllOfType() { + // return (ArrayList>) (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 asset) { assets.put(asset.getName(), asset); + logger.info("Successfully added asset: {}, to the asset list", asset.getName()); } } diff --git a/framework/src/main/java/org/toop/framework/asset/ResourceMeta.java b/framework/src/main/java/org/toop/framework/resource/ResourceMeta.java similarity index 85% rename from framework/src/main/java/org/toop/framework/asset/ResourceMeta.java rename to framework/src/main/java/org/toop/framework/resource/ResourceMeta.java index 972b635..4312b84 100644 --- a/framework/src/main/java/org/toop/framework/asset/ResourceMeta.java +++ b/framework/src/main/java/org/toop/framework/resource/ResourceMeta.java @@ -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 { private final Long id; @@ -25,5 +25,4 @@ public class ResourceMeta { public T getResource() { return this.resource; } - } diff --git a/framework/src/main/java/org/toop/framework/asset/events/AssetLoaderEvents.java b/framework/src/main/java/org/toop/framework/resource/events/AssetLoaderEvents.java similarity index 60% rename from framework/src/main/java/org/toop/framework/asset/events/AssetLoaderEvents.java rename to framework/src/main/java/org/toop/framework/resource/events/AssetLoaderEvents.java index 91a296e..04ef018 100644 --- a/framework/src/main/java/org/toop/framework/asset/events/AssetLoaderEvents.java +++ b/framework/src/main/java/org/toop/framework/resource/events/AssetLoaderEvents.java @@ -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 {} } diff --git a/framework/src/main/java/org/toop/framework/resource/exceptions/CouldNotCreateResourceFactoryException.java b/framework/src/main/java/org/toop/framework/resource/exceptions/CouldNotCreateResourceFactoryException.java new file mode 100644 index 0000000..aa7b3c0 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/resource/exceptions/CouldNotCreateResourceFactoryException.java @@ -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())); + } +} diff --git a/framework/src/main/java/org/toop/framework/resource/exceptions/IsNotAResourceException.java b/framework/src/main/java/org/toop/framework/resource/exceptions/IsNotAResourceException.java new file mode 100644 index 0000000..41abbde --- /dev/null +++ b/framework/src/main/java/org/toop/framework/resource/exceptions/IsNotAResourceException.java @@ -0,0 +1,7 @@ +package org.toop.framework.resource.exceptions; + +public class IsNotAResourceException extends RuntimeException { + public IsNotAResourceException(Class clazz, String message) { + super(clazz.getName() + " does not implement BaseResource"); + } +} diff --git a/framework/src/main/java/org/toop/framework/resource/exceptions/ResourceNotFoundException.java b/framework/src/main/java/org/toop/framework/resource/exceptions/ResourceNotFoundException.java new file mode 100644 index 0000000..c75de1e --- /dev/null +++ b/framework/src/main/java/org/toop/framework/resource/exceptions/ResourceNotFoundException.java @@ -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); + } +} diff --git a/framework/src/main/java/org/toop/framework/asset/resources/BaseResource.java b/framework/src/main/java/org/toop/framework/resource/resources/BaseResource.java similarity index 84% rename from framework/src/main/java/org/toop/framework/asset/resources/BaseResource.java rename to framework/src/main/java/org/toop/framework/resource/resources/BaseResource.java index c1aa040..72da47c 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/BaseResource.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/BaseResource.java @@ -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; } - } diff --git a/framework/src/main/java/org/toop/framework/asset/resources/CssAsset.java b/framework/src/main/java/org/toop/framework/resource/resources/CssAsset.java similarity index 73% rename from framework/src/main/java/org/toop/framework/asset/resources/CssAsset.java rename to framework/src/main/java/org/toop/framework/resource/resources/CssAsset.java index 6f8cd19..0d056ae 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/CssAsset.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/CssAsset.java @@ -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 { diff --git a/framework/src/main/java/org/toop/framework/asset/resources/FontAsset.java b/framework/src/main/java/org/toop/framework/resource/resources/FontAsset.java similarity index 91% rename from framework/src/main/java/org/toop/framework/asset/resources/FontAsset.java rename to framework/src/main/java/org/toop/framework/resource/resources/FontAsset.java index 1054d03..da9c709 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/FontAsset.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/FontAsset.java @@ -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; } -} \ No newline at end of file +} diff --git a/framework/src/main/java/org/toop/framework/asset/resources/ImageAsset.java b/framework/src/main/java/org/toop/framework/resource/resources/ImageAsset.java similarity index 86% rename from framework/src/main/java/org/toop/framework/asset/resources/ImageAsset.java rename to framework/src/main/java/org/toop/framework/resource/resources/ImageAsset.java index ed2c87e..2e6b417 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/ImageAsset.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/ImageAsset.java @@ -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; } -} \ No newline at end of file +} diff --git a/framework/src/main/java/org/toop/framework/asset/resources/JsonAsset.java b/framework/src/main/java/org/toop/framework/resource/resources/JsonAsset.java similarity index 83% rename from framework/src/main/java/org/toop/framework/asset/resources/JsonAsset.java rename to framework/src/main/java/org/toop/framework/resource/resources/JsonAsset.java index 5f9e1ba..c13849c 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/JsonAsset.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/JsonAsset.java @@ -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 extends BaseResource implements LoadableResource { @@ -25,7 +26,8 @@ public class JsonAsset 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 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 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); } diff --git a/framework/src/main/java/org/toop/framework/asset/resources/LocalizationAsset.java b/framework/src/main/java/org/toop/framework/resource/resources/LocalizationAsset.java similarity index 64% rename from framework/src/main/java/org/toop/framework/asset/resources/LocalizationAsset.java rename to framework/src/main/java/org/toop/framework/resource/resources/LocalizationAsset.java index cf18f14..2bd2333 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/LocalizationAsset.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/LocalizationAsset.java @@ -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. - *

- * This class implements {@link LoadableResource} to support loading/unloading - * and {@link BundledResource} to represent resources that can contain multiple - * localized bundles. - *

- *

- * Files handled by this class must have the {@code .properties} extension, - * optionally with a locale suffix, e.g., {@code messages_en_US.properties}. - *

+ * Represents a localization resource asset that loads and manages property files containing + * key-value pairs for different locales. + * + *

This class implements {@link LoadableResource} to support loading/unloading and {@link + * BundledResource} to represent resources that can contain multiple localized bundles. + * + *

Files handled by this class must have the {@code .properties} extension, optionally with a + * locale suffix, e.g., {@code messages_en_US.properties}. + * + *

Example usage: * - *

- * Example usage: *

{@code
  * LocalizationAsset asset = new LocalizationAsset(new File("messages_en_US.properties"));
  * asset.load();
  * String greeting = asset.getString("hello", Locale.US);
  * }
- *

*/ @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". diff --git a/framework/src/main/java/org/toop/framework/asset/resources/MusicAsset.java b/framework/src/main/java/org/toop/framework/resource/resources/MusicAsset.java similarity index 81% rename from framework/src/main/java/org/toop/framework/asset/resources/MusicAsset.java rename to framework/src/main/java/org/toop/framework/resource/resources/MusicAsset.java index fc6eec7..d60b6bc 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/MusicAsset.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/MusicAsset.java @@ -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; } -} \ No newline at end of file +} diff --git a/framework/src/main/java/org/toop/framework/asset/resources/SettingsAsset.java b/framework/src/main/java/org/toop/framework/resource/resources/SettingsAsset.java similarity index 74% rename from framework/src/main/java/org/toop/framework/asset/resources/SettingsAsset.java rename to framework/src/main/java/org/toop/framework/resource/resources/SettingsAsset.java index c496213..7728c9a 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/SettingsAsset.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/SettingsAsset.java @@ -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 { @@ -32,13 +30,13 @@ public class SettingsAsset extends JsonAsset { 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 { 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(); - } -} \ No newline at end of file + public void setLayoutSize(String layoutSize) { + getContent().layoutSize = layoutSize; + save(); + } +} diff --git a/framework/src/main/java/org/toop/framework/asset/resources/SoundEffectAsset.java b/framework/src/main/java/org/toop/framework/resource/resources/SoundEffectAsset.java similarity index 58% rename from framework/src/main/java/org/toop/framework/asset/resources/SoundEffectAsset.java rename to framework/src/main/java/org/toop/framework/resource/resources/SoundEffectAsset.java index d207077..c55306a 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/SoundEffectAsset.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/SoundEffectAsset.java @@ -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); } diff --git a/framework/src/main/java/org/toop/framework/asset/resources/TextAsset.java b/framework/src/main/java/org/toop/framework/resource/resources/TextAsset.java similarity index 85% rename from framework/src/main/java/org/toop/framework/asset/resources/TextAsset.java rename to framework/src/main/java/org/toop/framework/resource/resources/TextAsset.java index e6acc3c..9d91405 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/TextAsset.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/TextAsset.java @@ -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; } -} \ No newline at end of file +} diff --git a/framework/src/main/java/org/toop/framework/resource/types/BundledResource.java b/framework/src/main/java/org/toop/framework/resource/types/BundledResource.java new file mode 100644 index 0000000..ca63407 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/resource/types/BundledResource.java @@ -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. + * + *

Implementing classes allow an {@link ResourceLoader} to automatically merge multiple related + * files into a single resource instance. + * + *

Typical use cases include: + * + *

    + *
  • Localization assets, where multiple `.properties` files (e.g., `messages_en.properties`, + * `messages_nl.properties`) are grouped under the same logical resource. + *
  • Sprite sheets, tile sets, or other multi-file resources that logically belong together. + *
+ * + *

Implementing classes must provide: + * + *

    + *
  • {@link #loadFile(File)}: Logic to load or merge an individual file into the resource. + *
  • {@link #getBaseName()}: A consistent base name used to group multiple files into this + * resource. + *
+ * + *

Example usage: + * + *

{@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;
+ *     }
+ * }
+ * }
+ * + *

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(); +} diff --git a/framework/src/main/java/org/toop/framework/resource/types/FileExtension.java b/framework/src/main/java/org/toop/framework/resource/types/FileExtension.java new file mode 100644 index 0000000..df30efd --- /dev/null +++ b/framework/src/main/java/org/toop/framework/resource/types/FileExtension.java @@ -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. + * + *

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. + * + *

Usage example: + * + *

{@code
+ * @FileExtension({"png", "jpg"})
+ * public class ImageAsset extends BaseResource implements LoadableResource {
+ *     ...
+ * }
+ * }
+ * + *

Key points: + * + *

    + *
  • The annotation is retained at runtime for reflection-based registration. + *
  • Can only be applied to types (classes) that extend {@link BaseResource}. + *
  • Multiple extensions can be specified in the {@code value()} array. + *
+ */ +@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(); +} diff --git a/framework/src/main/java/org/toop/framework/resource/types/LoadableResource.java b/framework/src/main/java/org/toop/framework/resource/types/LoadableResource.java new file mode 100644 index 0000000..32fb0bc --- /dev/null +++ b/framework/src/main/java/org/toop/framework/resource/types/LoadableResource.java @@ -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. + * + *

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. + * + *

Implementing classes must define the following behaviors: + * + *

    + *
  • {@link #load()}: Load the resource into memory or perform necessary initialization. + *
  • {@link #unload()}: Release any held resources or memory when the resource is no longer + * needed. + *
  • {@link #isLoaded()}: Return {@code true} if the resource has been successfully loaded and + * is ready for use, {@code false} otherwise. + *
+ * + *

Typical usage: + * + *

{@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;
+ *     }
+ * }
+ * }
+ * + *

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(); +} diff --git a/framework/src/main/java/org/toop/framework/asset/types/PreloadResource.java b/framework/src/main/java/org/toop/framework/resource/types/PreloadResource.java similarity index 67% rename from framework/src/main/java/org/toop/framework/asset/types/PreloadResource.java rename to framework/src/main/java/org/toop/framework/resource/types/PreloadResource.java index 07d213d..28f8734 100644 --- a/framework/src/main/java/org/toop/framework/asset/types/PreloadResource.java +++ b/framework/src/main/java/org/toop/framework/resource/types/PreloadResource.java @@ -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}. * - *

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.

+ *

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. * *

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.

+ * without requiring manual loading by the user. + * + *

Typical usage: * - *

Typical usage:

*
{@code
  * public class MyFontAsset extends BaseResource implements PreloadResource {
  *     @Override
@@ -34,6 +36,6 @@ import org.toop.framework.asset.ResourceLoader;
  * }
* *

Note: Only use this interface for resources that are safe to load at startup, as it may - * increase memory usage or startup time.

+ * increase memory usage or startup time. */ public interface PreloadResource extends LoadableResource {} diff --git a/framework/src/main/java/org/toop/framework/settings/Settings.java b/framework/src/main/java/org/toop/framework/settings/Settings.java index b1ed334..052107c 100644 --- a/framework/src/main/java/org/toop/framework/settings/Settings.java +++ b/framework/src/main/java/org/toop/framework/settings/Settings.java @@ -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; diff --git a/framework/src/test/java/org/toop/framework/networking/events/NetworkEventsTest.java b/framework/src/test/java/org/toop/framework/networking/events/NetworkEventsTest.java index 3641549..f2a129d 100644 --- a/framework/src/test/java/org/toop/framework/networking/events/NetworkEventsTest.java +++ b/framework/src/test/java/org/toop/framework/networking/events/NetworkEventsTest.java @@ -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 diff --git a/game/src/main/java/org/toop/game/Game.java b/game/src/main/java/org/toop/game/Game.java index 71b2214..9b4259c 100644 --- a/game/src/main/java/org/toop/game/Game.java +++ b/game/src/main/java/org/toop/game/Game.java @@ -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); -} \ No newline at end of file + public abstract Move[] getLegalMoves(); + + public abstract State play(Move move); +} diff --git a/game/src/main/java/org/toop/game/TurnBasedGame.java b/game/src/main/java/org/toop/game/TurnBasedGame.java index 2da6337..b4eb1d3 100644 --- a/game/src/main/java/org/toop/game/TurnBasedGame.java +++ b/game/src/main/java/org/toop/game/TurnBasedGame.java @@ -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; } -} \ No newline at end of file + public int getCurrentTurn() { + return currentTurn; + } +} diff --git a/game/src/main/java/org/toop/game/othello/Othello.java b/game/src/main/java/org/toop/game/othello/Othello.java index 3012eac..435527a 100644 --- a/game/src/main/java/org/toop/game/othello/Othello.java +++ b/game/src/main/java/org/toop/game/othello/Othello.java @@ -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; - } -} \ No newline at end of file + @Override + public State play(Move move) { + return null; + } +} diff --git a/game/src/main/java/org/toop/game/othello/OthelloAI.java b/game/src/main/java/org/toop/game/othello/OthelloAI.java index 8957387..40f147c 100644 --- a/game/src/main/java/org/toop/game/othello/OthelloAI.java +++ b/game/src/main/java/org/toop/game/othello/OthelloAI.java @@ -4,8 +4,8 @@ import org.toop.game.AI; import org.toop.game.Game; public final class OthelloAI extends AI { - @Override - public Game.Move findBestMove(Othello game, int depth) { - return null; - } + @Override + public Game.Move findBestMove(Othello game, int depth) { + return null; + } } diff --git a/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java b/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java index ee2a5b9..0fa6ca8 100644 --- a/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java +++ b/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java @@ -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 legalMoves = new ArrayList<>(); - final char currentValue = getCurrentValue(); + final ArrayList 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'; - } -} \ No newline at end of file + private char getCurrentValue() { + return currentTurn == 0 ? 'X' : 'O'; + } +} diff --git a/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java b/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java index 6be92f5..325b8ee 100644 --- a/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java +++ b/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java @@ -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()); - } -} \ No newline at end of file + assertNotNull(move); + assertEquals('O', move.value()); + assertEquals(2, move.position()); + } +}