diff --git a/REFERENCE.md b/REFERENCE.md new file mode 100644 index 0000000..509c4e8 --- /dev/null +++ b/REFERENCE.md @@ -0,0 +1,69 @@ +# Reference Implementation + +This library includes a reference implementation that can be used as a starting point for integrating Achievables into your own project. The reference implementation provides a CLI (Command Line Interface) for registering Achievables, submitting Events, and tracking achievement progress. + +## Components + +The reference implementation consists of the following components: + +1. `ReferencePlayer` - A simple implementation of the `AchievablePlayer` interface +2. `ReferenceAchievableManager` - An implementation of the `AchievableManager` interface with in-memory storage +3. `TestEvent` - A simple event for testing +4. `AchievablesCLI` - A CLI application for interacting with the Achievables system + +## Using the CLI + +To run the reference implementation: + +``` +java -cp achievables.jar us.mcparks.achievables.reference.AchievablesCLI +``` + +### Available Commands + +- `help` - Show the help message +- `exit`, `quit` - Exit the application +- `register ` - Register an achievable from a BIGAL file +- `list` - List all registered achievables +- `player add ` - Add a player +- `player list` - List all players +- `player remove ` - Remove a player +- `event ` - Submit an event +- `status ` - Show achievement status for a player +- `state ` - Show state for a player and achievable + +### Example Usage + +1. Start the CLI +2. Add a player: + ``` + > player add player1 Alice + ``` +3. Register an achievable from a BIGAL file: + ``` + > register example.bigal + ``` +4. Submit events: + ``` + > event TestEvent player1 "Test data" + ``` +5. Check player's achievement status: + ``` + > status player1 + ``` +6. Check player's state for a specific achievable: + ``` + > state player1 + ``` + +## Using in Your Own Project + +You can use the reference implementation as a starting point for your own implementation: + +1. Extend `ReferencePlayer` or create your own `AchievablePlayer` implementation +2. Use `ReferenceAchievableManager` directly or as a reference for implementing your own manager +3. Create your own event types for your specific application needs + +## Example BIGAL Script + +An example BIGAL script is included at `src/main/java/us/mcparks/achievables/reference/example.bigal`. This script defines a simple achievable that tracks counters for events. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 19e562b..55acb0a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,6 @@ plugins { id 'java-library' id 'groovy' - id "io.freefair.lombok" version "6.6.3" } tasks.withType(JavaCompile) { @@ -15,6 +14,13 @@ tasks.withType(GroovyCompile) { options.encoding = 'UTF-8' } +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } +} + java { toolchain { languageVersion = JavaLanguageVersion.of(8) @@ -36,6 +42,14 @@ sourceSets { srcDirs = ['src/main/groovy', 'src/main/java'] } } + test { + java { + srcDirs = ['src/test/java'] + } + groovy { + srcDirs = ['src/test/groovy'] + } + } } dependencies { @@ -45,4 +59,12 @@ dependencies { compileOnly 'org.jetbrains:annotations:24.0.0' implementation 'org.codehaus.groovy:groovy-all:3.0.12' implementation 'com.google.guava:guava:32.0.0-jre' + + // Testing dependencies + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + testImplementation 'org.junit.platform:junit-platform-suite-api:1.8.2' + testRuntimeOnly 'org.junit.platform:junit-platform-suite-engine:1.8.2' + testImplementation 'org.mockito:mockito-core:4.5.1' + testImplementation 'org.mockito:mockito-junit-jupiter:4.5.1' } \ No newline at end of file diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 0000000..b95a8a0 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Compile the test files directly +echo "Compiling tests..." +javac -d /tmp/reference-tests -cp \ + "/home/runner/work/Achievables/Achievables/build/classes/groovy/main:/home/runner/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-api/5.8.2/4c21029217adf07e4c0d0c5e192b6bf610c94bdc/junit-jupiter-api-5.8.2.jar:/home/runner/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-commons/1.8.2/32c8b8617c1342376fd5af2053da6410d8866861/junit-platform-commons-1.8.2.jar" \ + /home/runner/work/Achievables/Achievables/src/test/java/us/mcparks/achievables/reference/ReferenceCoreTests.java + +# Run the tests +echo "Running tests..." +java -cp "/tmp/reference-tests:/home/runner/work/Achievables/Achievables/build/classes/groovy/main:/home/runner/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-api/5.8.2/4c21029217adf07e4c0d0c5e192b6bf610c94bdc/junit-jupiter-api-5.8.2.jar:/home/runner/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-commons/1.8.2/32c8b8617c1342376fd5af2053da6410d8866861/junit-platform-commons-1.8.2.jar:/home/runner/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-engine/5.8.2/c598b4328d2f397194d11df3b1648d68d7d990e3/junit-jupiter-engine-5.8.2.jar:/home/runner/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-engine/1.8.2/b737de09f19864bd136805c84df7999a142fec29/junit-platform-engine-1.8.2.jar:/home/runner/.gradle/caches/modules-2/files-2.1/org.opentest4j/opentest4j/1.2.0/28c11eb91f9b6d8e200631d46e20a7f407f2a046/opentest4j-1.2.0.jar" \ + org.junit.platform.console.ConsoleLauncher --class-path /tmp/reference-tests --scan-class-path \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..397ebf9 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'Achievables' +include 'standalone-tests' \ No newline at end of file diff --git a/src/main/groovy/us/mcparks/achievables/dsl/BigalsIntegratedGroovyAchievementLanguage.groovy b/src/main/groovy/us/mcparks/achievables/dsl/BigalsIntegratedGroovyAchievementLanguage.groovy index 9127884..8a44f92 100644 --- a/src/main/groovy/us/mcparks/achievables/dsl/BigalsIntegratedGroovyAchievementLanguage.groovy +++ b/src/main/groovy/us/mcparks/achievables/dsl/BigalsIntegratedGroovyAchievementLanguage.groovy @@ -5,6 +5,7 @@ import org.codehaus.groovy.control.CompilerConfiguration import org.codehaus.groovy.control.customizers.ImportCustomizer import us.mcparks.achievables.dsl.meta.AchievableWithMeta import us.mcparks.achievables.dsl.meta.MetaBuilder +import us.mcparks.achievables.dsl.meta.DefaultMetaBuilder import us.mcparks.achievables.groovy.BigAlAchievable import us.mcparks.achievables.dsl.v1_0.AchievementDslV0 import us.mcparks.achievables.utils.GroovyEvaluator @@ -98,7 +99,7 @@ public final class BigalsIntegratedGroovyAchievementLanguage { static GroovyEvaluator bigalEvaluator = createEvaluator("${AchievementDslV0.class.getName()}") static GroovyEvaluator versionEvaluator = createEvaluator("${VersionDsl.class.getName()}") - static Supplier> metaBuilderSupplier = MetaBuilder::new + static Supplier> metaBuilderSupplier = DefaultMetaBuilder::new static GroovyEvaluator createEvaluator(String... classNames) { def importCustomizer = new ImportCustomizer() diff --git a/src/main/groovy/us/mcparks/achievables/dsl/meta/AchievableMeta.groovy b/src/main/groovy/us/mcparks/achievables/dsl/meta/AchievableMeta.groovy index 591c113..3f74363 100644 --- a/src/main/groovy/us/mcparks/achievables/dsl/meta/AchievableMeta.groovy +++ b/src/main/groovy/us/mcparks/achievables/dsl/meta/AchievableMeta.groovy @@ -1,5 +1,6 @@ package us.mcparks.achievables.dsl.meta interface AchievableMeta { - + String getName() + String getDescription() } \ No newline at end of file diff --git a/src/main/groovy/us/mcparks/achievables/dsl/meta/MetaBuilder.groovy b/src/main/groovy/us/mcparks/achievables/dsl/meta/MetaBuilder.groovy index bbd4d17..27923ab 100644 --- a/src/main/groovy/us/mcparks/achievables/dsl/meta/MetaBuilder.groovy +++ b/src/main/groovy/us/mcparks/achievables/dsl/meta/MetaBuilder.groovy @@ -2,4 +2,42 @@ package us.mcparks.achievables.dsl.meta interface MetaBuilder { T build(); +} + +class DefaultMetaBuilder implements MetaBuilder { + private String name = "" + private String description = "" + + void name(String name) { + this.name = name + } + + void description(String description) { + this.description = description + } + + @Override + AchievableMeta build() { + return new SimpleAchievableMeta(name, description) + } + + static class SimpleAchievableMeta implements AchievableMeta { + private final String name + private final String description + + SimpleAchievableMeta(String name, String description) { + this.name = name + this.description = description + } + + @Override + String getName() { + return name + } + + @Override + String getDescription() { + return description + } + } } \ No newline at end of file diff --git a/src/main/java/us/mcparks/achievables/reference/AchievablesCLI.java b/src/main/java/us/mcparks/achievables/reference/AchievablesCLI.java new file mode 100644 index 0000000..465741d --- /dev/null +++ b/src/main/java/us/mcparks/achievables/reference/AchievablesCLI.java @@ -0,0 +1,321 @@ +package us.mcparks.achievables.reference; + +import us.mcparks.achievables.Achievables; +import us.mcparks.achievables.dsl.BigalsIntegratedGroovyAchievementLanguage; +import us.mcparks.achievables.dsl.meta.AchievableWithMeta; +import us.mcparks.achievables.events.Event; +import us.mcparks.achievables.framework.Achievable; +import us.mcparks.achievables.framework.AchievablePlayer; +import us.mcparks.achievables.triggers.AchievableTrigger; +import us.mcparks.achievables.triggers.EventAchievableTrigger; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A CLI application for interacting with the Achievables library. + * Allows registering achievables, submitting events, and testing achievables. + */ +public class AchievablesCLI { + private static final Logger logger = Logger.getLogger(AchievablesCLI.class.getName()); + private static final ReferenceAchievableManager manager = new ReferenceAchievableManager(); + private static final Map players = new HashMap<>(); + + public static void main(String[] args) { + // Initialize the Achievables system with our manager + Achievables.initialize(manager); + + // Register the TestEvent class + manager.registerEventClass("TestEvent", TestEvent.class); + + // Welcome message + System.out.println("Achievables Reference Implementation CLI"); + System.out.println("Type 'help' for a list of commands"); + + // Read commands from standard input + Scanner scanner = new Scanner(System.in); + boolean running = true; + + while (running) { + System.out.print("> "); + String line = scanner.nextLine().trim(); + + if (line.isEmpty()) { + continue; + } + + String[] parts = line.split("\\s+", 2); + String command = parts[0].toLowerCase(); + String args1 = parts.length > 1 ? parts[1] : ""; + + try { + switch (command) { + case "help": + printHelp(); + break; + case "exit": + case "quit": + running = false; + break; + case "register": + registerAchievable(args1); + break; + case "list": + listAchievables(); + break; + case "player": + handlePlayerCommand(args1); + break; + case "event": + submitEvent(args1); + break; + case "status": + showStatus(args1); + break; + case "state": + showState(args1); + break; + default: + System.out.println("Unknown command: " + command); + System.out.println("Type 'help' for a list of commands"); + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error executing command: " + line, e); + System.out.println("Error: " + e.getMessage()); + } + } + + System.out.println("Goodbye!"); + } + + private static void printHelp() { + System.out.println("Available commands:"); + System.out.println(" help - Show this help message"); + System.out.println(" exit, quit - Exit the application"); + System.out.println(" register - Register an achievable from a BIGAL file"); + System.out.println(" list - List all registered achievables"); + System.out.println(" player add - Add a player"); + System.out.println(" player list - List all players"); + System.out.println(" player remove - Remove a player"); + System.out.println(" event - Submit an event"); + System.out.println(" status - Show achievement status for a player"); + System.out.println(" state - Show state for a player and achievable"); + } + + private static void registerAchievable(String fileName) throws IOException { + if (fileName.isEmpty()) { + System.out.println("Please specify a file name"); + return; + } + + File file = new File(fileName); + if (!file.exists()) { + System.out.println("File not found: " + fileName); + return; + } + + String content = new String(Files.readAllBytes(Paths.get(fileName))); + AchievableWithMeta achievableWithMeta = BigalsIntegratedGroovyAchievementLanguage.interpret(content); + Achievable achievable = achievableWithMeta.getAchievable(); + + manager.registerAchievable(achievable); + System.out.println("Registered achievable: " + achievable.getUUID()); + System.out.println(" Name: " + achievableWithMeta.getMeta().getName()); + System.out.println(" Description: " + achievableWithMeta.getMeta().getDescription()); + } + + private static void listAchievables() { + Collection achievables = manager.getAchievables(); + if (achievables.isEmpty()) { + System.out.println("No achievables registered"); + return; + } + + System.out.println("Registered achievables:"); + for (Achievable achievable : achievables) { + System.out.println(" " + achievable.getUUID()); + } + } + + private static void handlePlayerCommand(String args) { + if (args.isEmpty()) { + System.out.println("Missing player command. Use 'player add', 'player list', or 'player remove'"); + return; + } + + String[] parts = args.split("\\s+", 3); + String subCommand = parts[0].toLowerCase(); + + switch (subCommand) { + case "add": + if (parts.length < 3) { + System.out.println("Usage: player add "); + return; + } + addPlayer(parts[1], parts[2]); + break; + case "list": + listPlayers(); + break; + case "remove": + if (parts.length < 2) { + System.out.println("Usage: player remove "); + return; + } + removePlayer(parts[1]); + break; + default: + System.out.println("Unknown player command: " + subCommand); + System.out.println("Use 'player add', 'player list', or 'player remove'"); + } + } + + private static void addPlayer(String id, String name) { + ReferencePlayer player = new ReferencePlayer(id, name); + players.put(id, player); + manager.addPlayer(player); + System.out.println("Added player: " + player); + } + + private static void listPlayers() { + if (players.isEmpty()) { + System.out.println("No players"); + return; + } + + System.out.println("Players:"); + for (ReferencePlayer player : players.values()) { + System.out.println(" " + player); + } + } + + private static void removePlayer(String id) { + ReferencePlayer player = players.get(id); + if (player == null) { + System.out.println("Player not found: " + id); + return; + } + + players.remove(id); + manager.removePlayer(player); + System.out.println("Removed player: " + player); + } + + private static void submitEvent(String args) { + if (args.isEmpty()) { + System.out.println("Usage: event "); + return; + } + + String[] parts = args.split("\\s+", 3); + if (parts.length < 3) { + System.out.println("Usage: event "); + return; + } + + String type = parts[0]; + String playerId = parts[1]; + String data = parts[2]; + + ReferencePlayer player = players.get(playerId); + if (player == null) { + System.out.println("Player not found: " + playerId); + return; + } + + // Create and submit the event + TestEvent event = new TestEvent(type, player, data); + AchievableTrigger trigger = new EventAchievableTrigger(event); + + manager.processTrigger(trigger); + System.out.println("Submitted event: " + event); + } + + private static void showStatus(String playerId) { + if (playerId.isEmpty()) { + System.out.println("Usage: status "); + return; + } + + ReferencePlayer player = players.get(playerId); + if (player == null) { + System.out.println("Player not found: " + playerId); + return; + } + + System.out.println("Achievement status for player: " + player); + boolean found = false; + + for (Achievable achievable : manager.getAchievables()) { + boolean completed = manager.isCompleted(achievable, player); + boolean satisfied = achievable.isSatisfied(player); + + System.out.println(" " + achievable.getUUID() + ":"); + System.out.println(" Completed: " + completed); + System.out.println(" Currently satisfied: " + satisfied); + found = true; + } + + if (!found) { + System.out.println("No achievables registered"); + } + } + + private static void showState(String args) { + if (args.isEmpty()) { + System.out.println("Usage: state "); + return; + } + + String[] parts = args.split("\\s+", 2); + if (parts.length < 2) { + System.out.println("Usage: state "); + return; + } + + String playerId = parts[0]; + String uuidStr = parts[1]; + + ReferencePlayer player = players.get(playerId); + if (player == null) { + System.out.println("Player not found: " + playerId); + return; + } + + UUID uuid; + try { + uuid = UUID.fromString(uuidStr); + } catch (IllegalArgumentException e) { + System.out.println("Invalid UUID format: " + uuidStr); + return; + } + + Achievable achievable = manager.getAchievable(uuid); + if (achievable == null) { + System.out.println("Achievable not found: " + uuid); + return; + } + + if (!(achievable instanceof us.mcparks.achievables.framework.StatefulAchievable)) { + System.out.println("Achievable is not stateful: " + uuid); + return; + } + + us.mcparks.achievables.framework.StatefulAchievable statefulAchievable = + (us.mcparks.achievables.framework.StatefulAchievable) achievable; + + Map state = statefulAchievable.getPlayerState(player); + + System.out.println("State for player " + player + " and achievable " + uuid + ":"); + for (Map.Entry entry : state.entrySet()) { + System.out.println(" " + entry.getKey() + ": " + entry.getValue()); + } + } +} \ No newline at end of file diff --git a/src/main/java/us/mcparks/achievables/reference/ReferenceAchievableManager.java b/src/main/java/us/mcparks/achievables/reference/ReferenceAchievableManager.java new file mode 100644 index 0000000..fd7607c --- /dev/null +++ b/src/main/java/us/mcparks/achievables/reference/ReferenceAchievableManager.java @@ -0,0 +1,198 @@ +package us.mcparks.achievables.reference; + +import us.mcparks.achievables.AchievableManager; +import us.mcparks.achievables.events.Event; +import us.mcparks.achievables.framework.Achievable; +import us.mcparks.achievables.framework.AchievablePlayer; +import us.mcparks.achievables.framework.StatefulAchievable; +import us.mcparks.achievables.triggers.AchievableTrigger; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; + +/** + * A reference implementation of AchievableManager that provides in-memory storage + * of achievement state and progress. + */ +public class ReferenceAchievableManager implements AchievableManager { + private final Logger logger = Logger.getLogger(ReferenceAchievableManager.class.getName()); + + // Map of registered achievements + private final Map achievables = new ConcurrentHashMap<>(); + + // Map of achievement completion status for players + private final Map> completedAchievables = new ConcurrentHashMap<>(); + + // Map of player-specific achievement state + private final Map>> playerState = new ConcurrentHashMap<>(); + + // Map of static state for stateful achievements + private final Map> staticState = new ConcurrentHashMap<>(); + + // Collection of current players + private final Collection currentPlayers = new HashSet<>(); + + // Map of event class names to actual event classes + private final Map> eventClasses = new ConcurrentHashMap<>(); + + /** + * Registers an achievable with the manager + * @param achievable The achievable to register + */ + public void registerAchievable(Achievable achievable) { + achievables.put(achievable.getUUID(), achievable); + logger.info("Registered achievable: " + achievable.getUUID()); + } + + /** + * Registers an event class + * @param eventClassName The name of the event class + * @param eventClass The event class + */ + public void registerEventClass(String eventClassName, Class eventClass) { + eventClasses.put(eventClassName, eventClass); + logger.info("Registered event class: " + eventClassName); + } + + /** + * Adds a player to the current players collection + * @param player The player to add + */ + public void addPlayer(AchievablePlayer player) { + currentPlayers.add(player); + logger.info("Added player: " + player); + } + + /** + * Removes a player from the current players collection + * @param player The player to remove + */ + public void removePlayer(AchievablePlayer player) { + currentPlayers.remove(player); + logger.info("Removed player: " + player); + } + + @Override + public void processTrigger(AchievableTrigger trigger) { + logger.info("Processing trigger: " + trigger.getType()); + + // Find all achievables that respond to this trigger + for (Achievable achievable : achievables.values()) { + if (achievable.getTriggers().contains(trigger.getType())) { + achievable.process(trigger); + } + } + } + + @Override + public boolean isCompleted(Achievable achievable, AchievablePlayer player) { + if (!(player instanceof ReferencePlayer)) { + return false; + } + + String playerId = ((ReferencePlayer) player).getId(); + return completedAchievables.containsKey(playerId) && + completedAchievables.get(playerId).contains(achievable.getUUID()); + } + + @Override + public void completeAchievable(Achievable achievable, AchievablePlayer player) { + if (!(player instanceof ReferencePlayer)) { + return; + } + + String playerId = ((ReferencePlayer) player).getId(); + completedAchievables.computeIfAbsent(playerId, k -> new HashSet<>()).add(achievable.getUUID()); + logger.info("Player " + player + " completed achievable: " + achievable.getUUID()); + } + + @Override + public Collection getCurrentPlayers() { + return Collections.unmodifiableCollection(currentPlayers); + } + + @Override + public Map getPlayerState(AchievablePlayer player, StatefulAchievable achievable) { + if (!(player instanceof ReferencePlayer)) { + return null; + } + + String playerId = ((ReferencePlayer) player).getId(); + UUID achievableId = achievable.getUUID(); + + if (!playerState.containsKey(playerId) || !playerState.get(playerId).containsKey(achievableId)) { + return null; + } + + return new HashMap<>(playerState.get(playerId).get(achievableId)); + } + + @Override + public void setPlayerState(AchievablePlayer player, StatefulAchievable achievable, Map state, boolean persist) throws ExecutionException { + if (!(player instanceof ReferencePlayer)) { + return; + } + + String playerId = ((ReferencePlayer) player).getId(); + UUID achievableId = achievable.getUUID(); + + playerState.computeIfAbsent(playerId, k -> new ConcurrentHashMap<>()) + .put(achievableId, new HashMap<>(state)); + + logger.fine("Set player state for " + player + " on achievable " + achievableId); + } + + @Override + public Map getStaticState(StatefulAchievable achievable) { + UUID achievableId = achievable.getUUID(); + + if (!staticState.containsKey(achievableId)) { + return null; + } + + return new HashMap<>(staticState.get(achievableId)); + } + + @Override + public void setStaticState(StatefulAchievable achievable, Map state) throws ExecutionException { + UUID achievableId = achievable.getUUID(); + staticState.put(achievableId, new HashMap<>(state)); + logger.fine("Set static state for achievable " + achievableId); + } + + @Override + public void initializePlayerState(AchievablePlayer player, StatefulAchievable achievable) throws ExecutionException { + setPlayerState(player, achievable, achievable.getInitialPlayerState(), true); + logger.fine("Initialized player state for " + player + " on achievable " + achievable.getUUID()); + } + + @Override + public void initializeStaticState(StatefulAchievable achievable) throws ExecutionException { + setStaticState(achievable, achievable.getInitialStaticState()); + logger.fine("Initialized static state for achievable " + achievable.getUUID()); + } + + @Override + public Class getEventClass(String eventClassName) { + return eventClasses.get(eventClassName); + } + + /** + * Gets all registered achievables + * @return A collection of registered achievables + */ + public Collection getAchievables() { + return Collections.unmodifiableCollection(achievables.values()); + } + + /** + * Gets an achievable by its UUID + * @param uuid The UUID of the achievable + * @return The achievable, or null if not found + */ + public Achievable getAchievable(UUID uuid) { + return achievables.get(uuid); + } +} \ No newline at end of file diff --git a/src/main/java/us/mcparks/achievables/reference/ReferencePlayer.java b/src/main/java/us/mcparks/achievables/reference/ReferencePlayer.java new file mode 100644 index 0000000..4c69463 --- /dev/null +++ b/src/main/java/us/mcparks/achievables/reference/ReferencePlayer.java @@ -0,0 +1,28 @@ +package us.mcparks.achievables.reference; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import us.mcparks.achievables.framework.AchievablePlayer; + +/** + * A simple implementation of AchievablePlayer for the reference implementation. + * Represents a player by a unique ID and name. + */ +@EqualsAndHashCode +public class ReferencePlayer implements AchievablePlayer { + @Getter + private final String id; + + @Getter + private final String name; + + public ReferencePlayer(String id, String name) { + this.id = id; + this.name = name; + } + + @Override + public String toString() { + return "Player{id='" + id + "', name='" + name + "'}"; + } +} \ No newline at end of file diff --git a/src/main/java/us/mcparks/achievables/reference/TestEvent.java b/src/main/java/us/mcparks/achievables/reference/TestEvent.java new file mode 100644 index 0000000..8a79759 --- /dev/null +++ b/src/main/java/us/mcparks/achievables/reference/TestEvent.java @@ -0,0 +1,31 @@ +package us.mcparks.achievables.reference; + +import lombok.Getter; +import us.mcparks.achievables.events.Event; +import us.mcparks.achievables.events.PlayerEvent; +import us.mcparks.achievables.framework.AchievablePlayer; + +/** + * A simple event implementation for testing achievables + */ +public class TestEvent implements Event, PlayerEvent { + @Getter + private final String type; + + @Getter + private final AchievablePlayer applicablePlayer; + + @Getter + private final String data; + + public TestEvent(String type, AchievablePlayer player, String data) { + this.type = type; + this.applicablePlayer = player; + this.data = data; + } + + @Override + public String toString() { + return "TestEvent{type='" + type + "', player=" + applicablePlayer + ", data='" + data + "'}"; + } +} \ No newline at end of file diff --git a/src/main/java/us/mcparks/achievables/reference/example.bigal b/src/main/java/us/mcparks/achievables/reference/example.bigal new file mode 100644 index 0000000..da26de5 --- /dev/null +++ b/src/main/java/us/mcparks/achievables/reference/example.bigal @@ -0,0 +1,38 @@ +// Example BIGAL script for the reference implementation +// This is a simple achievable that tracks counters for different events + +syntaxVersion 0 + +achievement { + name "Event Counter" + description "Track the number of events a player has participated in" + difficulty 1 + reward "None" + + state { + eventCount = 0 + testEventCount = 0 + } + + staticState { + totalEventsProcessed = 0 + } + + activators { + state.eventCount >= 5 + } + + events { + on("TestEvent") { + // Increment counters + state.eventCount++ + state.testEventCount++ + + // Also track in static state + staticState.totalEventsProcessed++ + + println "Processed TestEvent: ${event.data} for player ${event.applicablePlayer}" + println "Player has now processed ${state.eventCount} total events" + } + } +} \ No newline at end of file diff --git a/src/test/java/us/mcparks/achievables/reference/ReferenceAchievableManagerAdvancedTests.java b/src/test/java/us/mcparks/achievables/reference/ReferenceAchievableManagerAdvancedTests.java new file mode 100644 index 0000000..8bae63f --- /dev/null +++ b/src/test/java/us/mcparks/achievables/reference/ReferenceAchievableManagerAdvancedTests.java @@ -0,0 +1,189 @@ +package us.mcparks.achievables.reference; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import us.mcparks.achievables.framework.Achievable; +import us.mcparks.achievables.framework.AchievablePlayer; +import us.mcparks.achievables.framework.StatefulAchievable; +import us.mcparks.achievables.triggers.AchievableTrigger; +import us.mcparks.achievables.triggers.EventAchievableTrigger; + +import static org.mockito.Mockito.*; + +public class ReferenceAchievableManagerAdvancedTests { + + private ReferenceAchievableManager manager; + private ReferencePlayer player; + private StatefulAchievable mockAchievable; + private UUID achievableUuid; + + @BeforeEach + public void setUp() { + manager = new ReferenceAchievableManager(); + player = new ReferencePlayer("test-player", "Test Player"); + manager.addPlayer(player); + + // Create mock achievable + achievableUuid = UUID.randomUUID(); + mockAchievable = mock(StatefulAchievable.class); + when(mockAchievable.getUUID()).thenReturn(achievableUuid); + + Map initialPlayerState = new HashMap<>(); + initialPlayerState.put("counter", 0); + when(mockAchievable.getInitialPlayerState()).thenReturn(initialPlayerState); + + Map initialStaticState = new HashMap<>(); + initialStaticState.put("globalCounter", 0); + when(mockAchievable.getInitialStaticState()).thenReturn(initialStaticState); + + manager.registerAchievable(mockAchievable); + } + + @Test + public void testAchievableRegistration() { + // Verify achievable was registered + Collection achievables = manager.getAchievables(); + assertEquals(1, achievables.size(), "Should have 1 registered achievable"); + assertTrue(achievables.contains(mockAchievable), "Should contain registered achievable"); + + // Verify retrieving achievable by UUID + Achievable retrievedAchievable = manager.getAchievable(achievableUuid); + assertEquals(mockAchievable, retrievedAchievable, "Should retrieve registered achievable by UUID"); + assertNull(manager.getAchievable(UUID.randomUUID()), "Should return null for unregistered UUID"); + } + + @Test + public void testAchievableCompletion() { + // Initial state + assertFalse(manager.isCompleted(mockAchievable, player), + "Achievable should not be completed initially"); + + // Complete achievable + manager.completeAchievable(mockAchievable, player); + + // Verify completion status + assertTrue(manager.isCompleted(mockAchievable, player), + "Achievable should be marked as completed after completion"); + } + + @Test + public void testPlayerStateManagement() throws ExecutionException { + // Initial state should be null + assertNull(manager.getPlayerState(player, mockAchievable), + "Player state should be null initially"); + + // Initialize player state + manager.initializePlayerState(player, mockAchievable); + + // Verify state after initialization + Map playerState = manager.getPlayerState(player, mockAchievable); + assertNotNull(playerState, "Player state should not be null after initialization"); + assertEquals(0, playerState.get("counter"), "Counter should be initialized to 0"); + + // Update player state + Map newState = new HashMap<>(); + newState.put("counter", 5); + newState.put("newField", "value"); + manager.setPlayerState(player, mockAchievable, newState, true); + + // Verify updated state + playerState = manager.getPlayerState(player, mockAchievable); + assertEquals(5, playerState.get("counter"), "Counter should be updated to 5"); + assertEquals("value", playerState.get("newField"), "New field should be added"); + } + + @Test + public void testStaticStateManagement() throws ExecutionException { + // Initial static state should be null + assertNull(manager.getStaticState(mockAchievable), + "Static state should be null initially"); + + // Initialize static state + manager.initializeStaticState(mockAchievable); + + // Verify state after initialization + Map staticState = manager.getStaticState(mockAchievable); + assertNotNull(staticState, "Static state should not be null after initialization"); + assertEquals(0, staticState.get("globalCounter"), "Global counter should be initialized to 0"); + + // Update static state + Map newState = new HashMap<>(); + newState.put("globalCounter", 10); + newState.put("newGlobalField", "global-value"); + manager.setStaticState(mockAchievable, newState); + + // Verify updated state + staticState = manager.getStaticState(mockAchievable); + assertEquals(10, staticState.get("globalCounter"), "Global counter should be updated to 10"); + assertEquals("global-value", staticState.get("newGlobalField"), "New global field should be added"); + } + + @Test + public void testTriggerProcessing() { + // Setup trigger types for mock achievable + AchievableTrigger.Type triggerType = new AchievableTrigger.Type("test-trigger"); + when(mockAchievable.getTriggers()).thenReturn(java.util.Collections.singleton(triggerType)); + + // Create test trigger + AchievableTrigger trigger = mock(AchievableTrigger.class); + when(trigger.getType()).thenReturn(triggerType); + + // Process trigger + manager.processTrigger(trigger); + + // Verify achievable.process was called + verify(mockAchievable).process(trigger); + + // Test with non-matching trigger + AchievableTrigger nonMatchingTrigger = mock(AchievableTrigger.class); + when(nonMatchingTrigger.getType()).thenReturn(new AchievableTrigger.Type("other-trigger")); + + // Reset mock + reset(mockAchievable); + + // Process non-matching trigger + manager.processTrigger(nonMatchingTrigger); + + // Verify achievable.process was not called + verify(mockAchievable, never()).process(nonMatchingTrigger); + } + + @Test + public void testNonReferencePlayerHandling() throws ExecutionException { + // Create a non-ReferencePlayer + AchievablePlayer nonReferencePlayer = mock(AchievablePlayer.class); + + // Test isCompleted + assertFalse(manager.isCompleted(mockAchievable, nonReferencePlayer), + "isCompleted should return false for non-ReferencePlayer"); + + // Test completeAchievable + manager.completeAchievable(mockAchievable, nonReferencePlayer); + assertFalse(manager.isCompleted(mockAchievable, nonReferencePlayer), + "completeAchievable should have no effect for non-ReferencePlayer"); + + // Test getPlayerState + assertNull(manager.getPlayerState(nonReferencePlayer, mockAchievable), + "getPlayerState should return null for non-ReferencePlayer"); + + // Test setPlayerState + Map state = new HashMap<>(); + state.put("test", "value"); + manager.setPlayerState(nonReferencePlayer, mockAchievable, state, true); + assertNull(manager.getPlayerState(nonReferencePlayer, mockAchievable), + "setPlayerState should have no effect for non-ReferencePlayer"); + + // Test initializePlayerState + manager.initializePlayerState(nonReferencePlayer, mockAchievable); + assertNull(manager.getPlayerState(nonReferencePlayer, mockAchievable), + "initializePlayerState should have no effect for non-ReferencePlayer"); + } +} \ No newline at end of file diff --git a/src/test/java/us/mcparks/achievables/reference/ReferenceAchievableManagerTest.java b/src/test/java/us/mcparks/achievables/reference/ReferenceAchievableManagerTest.java new file mode 100644 index 0000000..8342e61 --- /dev/null +++ b/src/test/java/us/mcparks/achievables/reference/ReferenceAchievableManagerTest.java @@ -0,0 +1,64 @@ +package us.mcparks.achievables.reference; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import us.mcparks.achievables.framework.AchievablePlayer; + +class ReferenceAchievableManagerTest { + + private ReferenceAchievableManager manager; + private ReferencePlayer player1; + private ReferencePlayer player2; + + @BeforeEach + void setUp() { + manager = new ReferenceAchievableManager(); + player1 = new ReferencePlayer("player1", "TestPlayer1"); + player2 = new ReferencePlayer("player2", "TestPlayer2"); + } + + @Test + void testPlayerManagement() { + // Test initial state + assertTrue(manager.getCurrentPlayers().isEmpty(), "Should start with no players"); + + // Add players + manager.addPlayer(player1); + manager.addPlayer(player2); + + // Verify players were added + Collection currentPlayers = manager.getCurrentPlayers(); + assertEquals(2, currentPlayers.size(), "Should have 2 players after adding"); + assertTrue(currentPlayers.contains(player1), "Should contain player1"); + assertTrue(currentPlayers.contains(player2), "Should contain player2"); + + // Remove a player + manager.removePlayer(player1); + + // Verify player was removed + currentPlayers = manager.getCurrentPlayers(); + assertEquals(1, currentPlayers.size(), "Should have 1 player after removing"); + assertFalse(currentPlayers.contains(player1), "Should not contain removed player"); + assertTrue(currentPlayers.contains(player2), "Should still contain other player"); + } + + @Test + void testRegisterEventClass() { + // Register event class + manager.registerEventClass("TestEvent", TestEvent.class); + + // Verify registration + assertEquals(TestEvent.class, manager.getEventClass("TestEvent"), + "Should return registered event class"); + assertNull(manager.getEventClass("UnknownEvent"), + "Should return null for unregistered event class"); + } +} \ No newline at end of file diff --git a/src/test/java/us/mcparks/achievables/reference/ReferenceCoreTest.java b/src/test/java/us/mcparks/achievables/reference/ReferenceCoreTest.java new file mode 100644 index 0000000..d214e7d --- /dev/null +++ b/src/test/java/us/mcparks/achievables/reference/ReferenceCoreTest.java @@ -0,0 +1,37 @@ +package us.mcparks.achievables.reference; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +class ReferenceCoreTest { + + @Test + void testReferencePlayer() { + // Arrange + String id = "player1"; + String name = "TestPlayer"; + + // Act + ReferencePlayer player = new ReferencePlayer(id, name); + + // Assert + assertEquals(id, player.getId(), "Player ID should match"); + assertEquals(name, player.getName(), "Player name should match"); + } + + @Test + void testTestEvent() { + // Arrange + String type = "test_event"; + ReferencePlayer player = new ReferencePlayer("player1", "TestPlayer"); + String data = "event_data"; + + // Act + TestEvent event = new TestEvent(type, player, data); + + // Assert + assertEquals(type, event.getType(), "Event type should match"); + assertEquals(player, event.getApplicablePlayer(), "Player should match"); + assertEquals(data, event.getData(), "Event data should match"); + } +} \ No newline at end of file diff --git a/src/test/java/us/mcparks/achievables/reference/ReferenceCoreTests.java b/src/test/java/us/mcparks/achievables/reference/ReferenceCoreTests.java new file mode 100644 index 0000000..345bed2 --- /dev/null +++ b/src/test/java/us/mcparks/achievables/reference/ReferenceCoreTests.java @@ -0,0 +1,76 @@ +package us.mcparks.achievables.reference; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class ReferenceCoreTests { + + @Test + public void testReferencePlayer() { + // Create a player + ReferencePlayer player = new ReferencePlayer("test-id", "Test Player"); + + // Verify properties + assertEquals("test-id", player.getId()); + assertEquals("Test Player", player.getName()); + + // Test toString + String playerString = player.toString(); + assertTrue(playerString.contains("test-id")); + assertTrue(playerString.contains("Test Player")); + + // Test equals and hashCode + ReferencePlayer samePayer = new ReferencePlayer("test-id", "Test Player"); + ReferencePlayer differentPlayer = new ReferencePlayer("other-id", "Other Player"); + + assertEquals(player, samePayer); + assertNotEquals(player, differentPlayer); + assertEquals(player.hashCode(), samePayer.hashCode()); + assertNotEquals(player.hashCode(), differentPlayer.hashCode()); + } + + @Test + public void testTestEvent() { + // Create a player and event + ReferencePlayer player = new ReferencePlayer("test-id", "Test Player"); + TestEvent event = new TestEvent("test-type", player, "test-data"); + + // Verify properties + assertEquals("test-type", event.getType()); + assertEquals(player, event.getApplicablePlayer()); + assertEquals("test-data", event.getData()); + + // Test toString + String eventString = event.toString(); + assertTrue(eventString.contains("test-type")); + assertTrue(eventString.contains("test-id")); + assertTrue(eventString.contains("test-data")); + } + + @Test + public void testReferenceAchievableManager() { + // Create manager and players + ReferenceAchievableManager manager = new ReferenceAchievableManager(); + ReferencePlayer player1 = new ReferencePlayer("player1", "Player One"); + ReferencePlayer player2 = new ReferencePlayer("player2", "Player Two"); + + // Test player management + assertTrue(manager.getCurrentPlayers().isEmpty()); + + manager.addPlayer(player1); + manager.addPlayer(player2); + assertEquals(2, manager.getCurrentPlayers().size()); + assertTrue(manager.getCurrentPlayers().contains(player1)); + assertTrue(manager.getCurrentPlayers().contains(player2)); + + manager.removePlayer(player1); + assertEquals(1, manager.getCurrentPlayers().size()); + assertFalse(manager.getCurrentPlayers().contains(player1)); + assertTrue(manager.getCurrentPlayers().contains(player2)); + + // Test event registration + manager.registerEventClass("TestEvent", TestEvent.class); + assertEquals(TestEvent.class, manager.getEventClass("TestEvent")); + assertNull(manager.getEventClass("UnknownEvent")); + } +} \ No newline at end of file diff --git a/src/test/java/us/mcparks/achievables/reference/ReferencePlayerTest.java b/src/test/java/us/mcparks/achievables/reference/ReferencePlayerTest.java new file mode 100644 index 0000000..1e16759 --- /dev/null +++ b/src/test/java/us/mcparks/achievables/reference/ReferencePlayerTest.java @@ -0,0 +1,52 @@ +package us.mcparks.achievables.reference; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class ReferencePlayerTest { + + @Test + void testConstructorAndGetters() { + // Arrange + String id = "player1"; + String name = "TestPlayer"; + + // Act + ReferencePlayer player = new ReferencePlayer(id, name); + + // Assert + assertEquals(id, player.getId(), "Player ID should match the one provided in constructor"); + assertEquals(name, player.getName(), "Player name should match the one provided in constructor"); + } + + @Test + void testToString() { + // Arrange + ReferencePlayer player = new ReferencePlayer("player1", "TestPlayer"); + + // Act + String result = player.toString(); + + // Assert + assertTrue(result.contains("player1"), "toString should contain player ID"); + assertTrue(result.contains("TestPlayer"), "toString should contain player name"); + } + + @Test + void testEqualsAndHashCode() { + // Arrange + ReferencePlayer player1 = new ReferencePlayer("player1", "TestPlayer"); + ReferencePlayer player2 = new ReferencePlayer("player1", "TestPlayer"); + ReferencePlayer player3 = new ReferencePlayer("player2", "OtherPlayer"); + + // Assert + assertEquals(player1, player2, "Players with the same ID and name should be equal"); + assertNotEquals(player1, player3, "Players with different ID should not be equal"); + assertNotEquals(player1, null, "Player should not be equal to null"); + assertNotEquals(player1, "Not a player", "Player should not be equal to other types"); + + // HashCode + assertEquals(player1.hashCode(), player2.hashCode(), "Hash codes should be equal for equal objects"); + assertNotEquals(player1.hashCode(), player3.hashCode(), "Hash codes should differ for different objects"); + } +} \ No newline at end of file diff --git a/src/test/java/us/mcparks/achievables/reference/ReferenceTestSuite.java b/src/test/java/us/mcparks/achievables/reference/ReferenceTestSuite.java new file mode 100644 index 0000000..c8b06e7 --- /dev/null +++ b/src/test/java/us/mcparks/achievables/reference/ReferenceTestSuite.java @@ -0,0 +1,11 @@ +package us.mcparks.achievables.reference; + +import org.junit.jupiter.api.Test; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectPackages("us.mcparks.achievables.reference") +public class ReferenceTestSuite { + // This is a test suite that will run all test classes in the us.mcparks.achievables.reference package +} \ No newline at end of file diff --git a/src/test/java/us/mcparks/achievables/reference/TestEventTest.java b/src/test/java/us/mcparks/achievables/reference/TestEventTest.java new file mode 100644 index 0000000..891f5c9 --- /dev/null +++ b/src/test/java/us/mcparks/achievables/reference/TestEventTest.java @@ -0,0 +1,38 @@ +package us.mcparks.achievables.reference; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class TestEventTest { + + @Test + void testConstructorAndGetters() { + // Arrange + String type = "test_event"; + ReferencePlayer player = new ReferencePlayer("player1", "TestPlayer"); + String data = "event_data"; + + // Act + TestEvent event = new TestEvent(type, player, data); + + // Assert + assertEquals(type, event.getType(), "Event type should match the one provided in constructor"); + assertEquals(player, event.getApplicablePlayer(), "Player should match the one provided in constructor"); + assertEquals(data, event.getData(), "Event data should match the one provided in constructor"); + } + + @Test + void testToString() { + // Arrange + ReferencePlayer player = new ReferencePlayer("player1", "TestPlayer"); + TestEvent event = new TestEvent("test_event", player, "event_data"); + + // Act + String result = event.toString(); + + // Assert + assertTrue(result.contains("test_event"), "toString should contain event type"); + assertTrue(result.contains("player1"), "toString should contain player information"); + assertTrue(result.contains("event_data"), "toString should contain event data"); + } +} \ No newline at end of file diff --git a/standalone-tests/build.gradle b/standalone-tests/build.gradle new file mode 100644 index 0000000..f696f48 --- /dev/null +++ b/standalone-tests/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':') + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + testImplementation 'org.mockito:mockito-core:4.5.1' + testImplementation 'org.mockito:mockito-junit-jupiter:4.5.1' +} + +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } +} \ No newline at end of file diff --git a/standalone-tests/build/reports/tests/test/classes/us.mcparks.achievables.reference.ReferenceAchievableManagerTest.html b/standalone-tests/build/reports/tests/test/classes/us.mcparks.achievables.reference.ReferenceAchievableManagerTest.html new file mode 100644 index 0000000..97e7576 --- /dev/null +++ b/standalone-tests/build/reports/tests/test/classes/us.mcparks.achievables.reference.ReferenceAchievableManagerTest.html @@ -0,0 +1,118 @@ + + + + + +Test results - ReferenceAchievableManagerTest + + + + + +
+

ReferenceAchievableManagerTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.045s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + + + + + + +
TestDurationResult
testPlayerManagement()0.042spassed
testRegisterEventClass()0.003spassed
+
+
+

Standard error

+ +
May 20, 2025 7:35:30 PM us.mcparks.achievables.reference.ReferenceAchievableManager addPlayer
+INFO: Added player: Player{id='player1', name='TestPlayer1'}
+May 20, 2025 7:35:30 PM us.mcparks.achievables.reference.ReferenceAchievableManager addPlayer
+INFO: Added player: Player{id='player2', name='TestPlayer2'}
+May 20, 2025 7:35:30 PM us.mcparks.achievables.reference.ReferenceAchievableManager removePlayer
+INFO: Removed player: Player{id='player1', name='TestPlayer1'}
+May 20, 2025 7:35:30 PM us.mcparks.achievables.reference.ReferenceAchievableManager registerEventClass
+INFO: Registered event class: TestEvent
+
+
+
+
+ +
+ + diff --git a/standalone-tests/build/reports/tests/test/classes/us.mcparks.achievables.reference.ReferencePlayerTest.html b/standalone-tests/build/reports/tests/test/classes/us.mcparks.achievables.reference.ReferencePlayerTest.html new file mode 100644 index 0000000..2091629 --- /dev/null +++ b/standalone-tests/build/reports/tests/test/classes/us.mcparks.achievables.reference.ReferencePlayerTest.html @@ -0,0 +1,106 @@ + + + + + +Test results - ReferencePlayerTest + + + + + +
+

ReferencePlayerTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
3
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.003s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + + + + + + + + + + + +
TestDurationResult
testConstructorAndGetters()0.001spassed
testEqualsAndHashCode()0.001spassed
testToString()0.001spassed
+
+
+ +
+ + diff --git a/standalone-tests/build/reports/tests/test/classes/us.mcparks.achievables.reference.TestEventTest.html b/standalone-tests/build/reports/tests/test/classes/us.mcparks.achievables.reference.TestEventTest.html new file mode 100644 index 0000000..8045c33 --- /dev/null +++ b/standalone-tests/build/reports/tests/test/classes/us.mcparks.achievables.reference.TestEventTest.html @@ -0,0 +1,101 @@ + + + + + +Test results - TestEventTest + + + + + +
+

TestEventTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + + + + + + +
TestDurationResult
testConstructorAndGetters()0.001spassed
testToString()0.001spassed
+
+
+ +
+ + diff --git a/standalone-tests/build/reports/tests/test/css/base-style.css b/standalone-tests/build/reports/tests/test/css/base-style.css new file mode 100644 index 0000000..3ae6c58 --- /dev/null +++ b/standalone-tests/build/reports/tests/test/css/base-style.css @@ -0,0 +1,174 @@ + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 12pt; +} + +body, a, a:visited { + color: #303030; +} + +#content { + padding: 30px 50px; +} + +#content h1 { + font-size: 160%; + margin-bottom: 10px; +} + +#footer { + margin-top: 100px; + font-size: 80%; + white-space: nowrap; +} + +#footer, #footer a { + color: #a0a0a0; +} + +#line-wrapping-toggle { + vertical-align: middle; +} + +#label-for-line-wrapping-toggle { + vertical-align: middle; +} + +ul { + margin-left: 0; +} + +h1, h2, h3 { + white-space: nowrap; +} + +h2 { + font-size: 120%; +} + +.tab-container .tab-container { + margin-left: 8px; +} + +ul.tabLinks { + padding: 0; + margin-bottom: 0; + overflow: auto; + min-width: 800px; + width: auto; + border-bottom: solid 1px #aaa; +} + +ul.tabLinks li { + float: left; + height: 100%; + list-style: none; + padding: 5px 10px; + border-radius: 7px 7px 0 0; + border: solid 1px transparent; + border-bottom: none; + margin-right: 6px; + background-color: #f0f0f0; +} + +ul.tabLinks li.deselected > a { + color: #6d6d6d; +} + +ul.tabLinks li:hover { + background-color: #fafafa; +} + +ul.tabLinks li.selected { + background-color: #c5f0f5; + border-color: #aaa; +} + +ul.tabLinks a { + font-size: 120%; + display: block; + outline: none; + text-decoration: none; + margin: 0; + padding: 0; +} + +ul.tabLinks li h2 { + margin: 0; + padding: 0; +} + +div.tab { +} + +div.selected { + display: block; +} + +div.deselected { + display: none; +} + +div.tab table { + min-width: 350px; + width: auto; + border-collapse: collapse; +} + +div.tab th, div.tab table { + border-bottom: solid 1px #d0d0d0; +} + +div.tab th { + text-align: left; + white-space: nowrap; + padding-left: 6em; +} + +div.tab th:first-child { + padding-left: 0; +} + +div.tab td { + white-space: nowrap; + padding-left: 6em; + padding-top: 5px; + padding-bottom: 5px; +} + +div.tab td:first-child { + padding-left: 0; +} + +div.tab td.numeric, div.tab th.numeric { + text-align: right; +} + +span.code { + display: inline-block; + margin-top: 0; + margin-bottom: 1em; +} + +span.code pre { + font-size: 11pt; + padding: 10px; + margin: 0; + background-color: #f7f7f7; + border: solid 1px #d0d0d0; + min-width: 700px; + width: auto; +} + +span.wrapped pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-all; +} + +label.hidden { + display: none; +} diff --git a/standalone-tests/build/reports/tests/test/css/style.css b/standalone-tests/build/reports/tests/test/css/style.css new file mode 100644 index 0000000..3dc4913 --- /dev/null +++ b/standalone-tests/build/reports/tests/test/css/style.css @@ -0,0 +1,84 @@ + +#summary { + margin-top: 30px; + margin-bottom: 40px; +} + +#summary table { + border-collapse: collapse; +} + +#summary td { + vertical-align: top; +} + +.breadcrumbs, .breadcrumbs a { + color: #606060; +} + +.infoBox { + width: 110px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; +} + +.infoBox p { + margin: 0; +} + +.counter, .percent { + font-size: 120%; + font-weight: bold; + margin-bottom: 8px; +} + +#duration { + width: 125px; +} + +#successRate, .summaryGroup { + border: solid 2px #d0d0d0; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#successRate { + width: 140px; + margin-left: 35px; +} + +#successRate .percent { + font-size: 180%; +} + +.success, .success a { + color: #008000; +} + +div.success, #successRate.success { + background-color: #bbd9bb; + border-color: #008000; +} + +.failures, .failures a { + color: #b60808; +} + +.skipped, .skipped a { + color: #c09853; +} + +div.failures, #successRate.failures { + background-color: #ecdada; + border-color: #b60808; +} + +ul.linkList { + padding-left: 0; +} + +ul.linkList li { + list-style: none; + margin-bottom: 5px; +} diff --git a/standalone-tests/build/reports/tests/test/index.html b/standalone-tests/build/reports/tests/test/index.html new file mode 100644 index 0000000..91033a7 --- /dev/null +++ b/standalone-tests/build/reports/tests/test/index.html @@ -0,0 +1,153 @@ + + + + + +Test results - Test Summary + + + + + +
+

Test Summary

+
+ + + + + +
+
+ + + + + + + +
+
+
7
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.050s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Packages

+ + + + + + + + + + + + + + + + + + + + + +
PackageTestsFailuresIgnoredDurationSuccess rate
+us.mcparks.achievables.reference +7000.050s100%
+
+
+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+us.mcparks.achievables.reference.ReferenceAchievableManagerTest +2000.045s100%
+us.mcparks.achievables.reference.ReferencePlayerTest +3000.003s100%
+us.mcparks.achievables.reference.TestEventTest +2000.002s100%
+
+
+ +
+ + diff --git a/standalone-tests/build/reports/tests/test/js/report.js b/standalone-tests/build/reports/tests/test/js/report.js new file mode 100644 index 0000000..e246eba --- /dev/null +++ b/standalone-tests/build/reports/tests/test/js/report.js @@ -0,0 +1,218 @@ +(function (window, document) { + "use strict"; + + function changeElementClass(element, classValue) { + if (element.getAttribute("className")) { + element.setAttribute("className", classValue); + } else { + element.setAttribute("class", classValue); + } + } + + function getClassAttribute(element) { + if (element.getAttribute("className")) { + return element.getAttribute("className"); + } else { + return element.getAttribute("class"); + } + } + + function addClass(element, classValue) { + changeElementClass(element, getClassAttribute(element) + " " + classValue); + } + + function removeClass(element, classValue) { + changeElementClass(element, getClassAttribute(element).replace(classValue, "")); + } + + function getCheckBox() { + return document.getElementById("line-wrapping-toggle"); + } + + function getLabelForCheckBox() { + return document.getElementById("label-for-line-wrapping-toggle"); + } + + function findCodeBlocks() { + const codeBlocks = []; + const tabContainers = getTabContainers(); + for (let i = 0; i < tabContainers.length; i++) { + const spans = tabContainers[i].getElementsByTagName("span"); + for (let i = 0; i < spans.length; ++i) { + if (spans[i].className.indexOf("code") >= 0) { + codeBlocks.push(spans[i]); + } + } + } + return codeBlocks; + } + + function forAllCodeBlocks(operation) { + const codeBlocks = findCodeBlocks(); + + for (let i = 0; i < codeBlocks.length; ++i) { + operation(codeBlocks[i], "wrapped"); + } + } + + function toggleLineWrapping() { + const checkBox = getCheckBox(); + + if (checkBox.checked) { + forAllCodeBlocks(addClass); + } else { + forAllCodeBlocks(removeClass); + } + } + + function initControls() { + if (findCodeBlocks().length > 0) { + const checkBox = getCheckBox(); + const label = getLabelForCheckBox(); + + checkBox.onclick = toggleLineWrapping; + checkBox.checked = false; + + removeClass(label, "hidden"); + } + } + + class TabManager { + baseId; + tabs; + titles; + headers; + + constructor(baseId, tabs, titles, headers) { + this.baseId = baseId; + this.tabs = tabs; + this.titles = titles; + this.headers = headers; + } + + select(i) { + this.deselectAll(); + + changeElementClass(this.tabs[i], "tab selected"); + changeElementClass(this.headers[i], "selected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + const a = document.createElement("a"); + + a.appendChild(document.createTextNode(this.titles[i])); + this.headers[i].appendChild(a); + } + + deselectAll() { + for (let i = 0; i < this.tabs.length; i++) { + changeElementClass(this.tabs[i], "tab deselected"); + changeElementClass(this.headers[i], "deselected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + const a = document.createElement("a"); + + const id = this.baseId + "-tab" + i; + a.setAttribute("id", id); + a.setAttribute("href", "#tab" + i); + a.onclick = () => { + this.select(i); + return false; + }; + a.appendChild(document.createTextNode(this.titles[i])); + + this.headers[i].appendChild(a); + } + } + } + + function getTabContainers() { + const tabContainers = Array.from(document.getElementsByClassName("tab-container")); + + // Used by existing TabbedPageRenderer users, which have not adjusted to use TabsRenderer yet. + const legacyContainer = document.getElementById("tabs"); + if (legacyContainer) { + tabContainers.push(legacyContainer); + } + + return tabContainers; + } + + function initTabs() { + let tabGroups = 0; + + function createTab(num, container) { + const tabElems = findTabs(container); + const tabManager = new TabManager("tabs" + num, tabElems, findTitles(tabElems), findHeaders(container)); + tabManager.select(0); + } + + const tabContainers = getTabContainers(); + + for (let i = 0; i < tabContainers.length; i++) { + createTab(tabGroups, tabContainers[i]); + tabGroups++; + } + + return true; + } + + function findTabs(container) { + return findChildElements(container, "DIV", "tab"); + } + + function findHeaders(container) { + const owner = findChildElements(container, "UL", "tabLinks"); + return findChildElements(owner[0], "LI", null); + } + + function findTitles(tabs) { + const titles = []; + + for (let i = 0; i < tabs.length; i++) { + const tab = tabs[i]; + const header = findChildElements(tab, "H2", null)[0]; + + header.parentNode.removeChild(header); + + if (header.innerText) { + titles.push(header.innerText); + } else { + titles.push(header.textContent); + } + } + + return titles; + } + + function findChildElements(container, name, targetClass) { + const elements = []; + const children = container.childNodes; + + for (let i = 0; i < children.length; i++) { + const child = children.item(i); + + if (child.nodeType === 1 && child.nodeName === name) { + if (targetClass && child.className.indexOf(targetClass) < 0) { + continue; + } + + elements.push(child); + } + } + + return elements; + } + + // Entry point. + + window.onload = function() { + initTabs(); + initControls(); + }; +} (window, window.document)); diff --git a/standalone-tests/build/reports/tests/test/packages/us.mcparks.achievables.reference.html b/standalone-tests/build/reports/tests/test/packages/us.mcparks.achievables.reference.html new file mode 100644 index 0000000..2672a1e --- /dev/null +++ b/standalone-tests/build/reports/tests/test/packages/us.mcparks.achievables.reference.html @@ -0,0 +1,123 @@ + + + + + +Test results - Package us.mcparks.achievables.reference + + + + + +
+

Package us.mcparks.achievables.reference

+ +
+ + + + + +
+
+ + + + + + + +
+
+
7
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.050s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+ReferenceAchievableManagerTest +2000.045s100%
+ReferencePlayerTest +3000.003s100%
+TestEventTest +2000.002s100%
+
+
+ +
+ + diff --git a/standalone-tests/build/test-results/test/TEST-us.mcparks.achievables.reference.ReferenceAchievableManagerTest.xml b/standalone-tests/build/test-results/test/TEST-us.mcparks.achievables.reference.ReferenceAchievableManagerTest.xml new file mode 100644 index 0000000..42efc85 --- /dev/null +++ b/standalone-tests/build/test-results/test/TEST-us.mcparks.achievables.reference.ReferenceAchievableManagerTest.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/standalone-tests/build/test-results/test/TEST-us.mcparks.achievables.reference.ReferencePlayerTest.xml b/standalone-tests/build/test-results/test/TEST-us.mcparks.achievables.reference.ReferencePlayerTest.xml new file mode 100644 index 0000000..5115866 --- /dev/null +++ b/standalone-tests/build/test-results/test/TEST-us.mcparks.achievables.reference.ReferencePlayerTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/standalone-tests/build/test-results/test/TEST-us.mcparks.achievables.reference.TestEventTest.xml b/standalone-tests/build/test-results/test/TEST-us.mcparks.achievables.reference.TestEventTest.xml new file mode 100644 index 0000000..e00e1c7 --- /dev/null +++ b/standalone-tests/build/test-results/test/TEST-us.mcparks.achievables.reference.TestEventTest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/standalone-tests/build/test-results/test/binary/output.bin b/standalone-tests/build/test-results/test/binary/output.bin new file mode 100644 index 0000000..564c885 Binary files /dev/null and b/standalone-tests/build/test-results/test/binary/output.bin differ diff --git a/standalone-tests/build/test-results/test/binary/output.bin.idx b/standalone-tests/build/test-results/test/binary/output.bin.idx new file mode 100644 index 0000000..8bed1ea Binary files /dev/null and b/standalone-tests/build/test-results/test/binary/output.bin.idx differ diff --git a/standalone-tests/build/test-results/test/binary/results.bin b/standalone-tests/build/test-results/test/binary/results.bin new file mode 100644 index 0000000..8edb245 Binary files /dev/null and b/standalone-tests/build/test-results/test/binary/results.bin differ diff --git a/standalone-tests/build/tmp/compileTestJava/previous-compilation-data.bin b/standalone-tests/build/tmp/compileTestJava/previous-compilation-data.bin new file mode 100644 index 0000000..6ca5223 Binary files /dev/null and b/standalone-tests/build/tmp/compileTestJava/previous-compilation-data.bin differ diff --git a/standalone-tests/src/test/java/us/mcparks/achievables/reference/ReferenceAchievableManagerTest.java b/standalone-tests/src/test/java/us/mcparks/achievables/reference/ReferenceAchievableManagerTest.java new file mode 100644 index 0000000..b36ddb6 --- /dev/null +++ b/standalone-tests/src/test/java/us/mcparks/achievables/reference/ReferenceAchievableManagerTest.java @@ -0,0 +1,59 @@ +package us.mcparks.achievables.reference; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Collection; +import us.mcparks.achievables.framework.AchievablePlayer; + +class ReferenceAchievableManagerTest { + + private ReferenceAchievableManager manager; + private ReferencePlayer player1; + private ReferencePlayer player2; + + @BeforeEach + void setUp() { + manager = new ReferenceAchievableManager(); + player1 = new ReferencePlayer("player1", "TestPlayer1"); + player2 = new ReferencePlayer("player2", "TestPlayer2"); + } + + @Test + void testPlayerManagement() { + // Test initial state + assertTrue(manager.getCurrentPlayers().isEmpty(), "Should start with no players"); + + // Add players + manager.addPlayer(player1); + manager.addPlayer(player2); + + // Verify players were added + Collection currentPlayers = manager.getCurrentPlayers(); + assertEquals(2, currentPlayers.size(), "Should have 2 players after adding"); + assertTrue(currentPlayers.contains(player1), "Should contain player1"); + assertTrue(currentPlayers.contains(player2), "Should contain player2"); + + // Remove a player + manager.removePlayer(player1); + + // Verify player was removed + currentPlayers = manager.getCurrentPlayers(); + assertEquals(1, currentPlayers.size(), "Should have 1 player after removing"); + assertFalse(currentPlayers.contains(player1), "Should not contain removed player"); + assertTrue(currentPlayers.contains(player2), "Should still contain other player"); + } + + @Test + void testRegisterEventClass() { + // Register event class + manager.registerEventClass("TestEvent", TestEvent.class); + + // Verify registration + assertEquals(TestEvent.class, manager.getEventClass("TestEvent"), + "Should return registered event class"); + assertNull(manager.getEventClass("UnknownEvent"), + "Should return null for unregistered event class"); + } +} \ No newline at end of file diff --git a/standalone-tests/src/test/java/us/mcparks/achievables/reference/ReferencePlayerTest.java b/standalone-tests/src/test/java/us/mcparks/achievables/reference/ReferencePlayerTest.java new file mode 100644 index 0000000..1e16759 --- /dev/null +++ b/standalone-tests/src/test/java/us/mcparks/achievables/reference/ReferencePlayerTest.java @@ -0,0 +1,52 @@ +package us.mcparks.achievables.reference; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class ReferencePlayerTest { + + @Test + void testConstructorAndGetters() { + // Arrange + String id = "player1"; + String name = "TestPlayer"; + + // Act + ReferencePlayer player = new ReferencePlayer(id, name); + + // Assert + assertEquals(id, player.getId(), "Player ID should match the one provided in constructor"); + assertEquals(name, player.getName(), "Player name should match the one provided in constructor"); + } + + @Test + void testToString() { + // Arrange + ReferencePlayer player = new ReferencePlayer("player1", "TestPlayer"); + + // Act + String result = player.toString(); + + // Assert + assertTrue(result.contains("player1"), "toString should contain player ID"); + assertTrue(result.contains("TestPlayer"), "toString should contain player name"); + } + + @Test + void testEqualsAndHashCode() { + // Arrange + ReferencePlayer player1 = new ReferencePlayer("player1", "TestPlayer"); + ReferencePlayer player2 = new ReferencePlayer("player1", "TestPlayer"); + ReferencePlayer player3 = new ReferencePlayer("player2", "OtherPlayer"); + + // Assert + assertEquals(player1, player2, "Players with the same ID and name should be equal"); + assertNotEquals(player1, player3, "Players with different ID should not be equal"); + assertNotEquals(player1, null, "Player should not be equal to null"); + assertNotEquals(player1, "Not a player", "Player should not be equal to other types"); + + // HashCode + assertEquals(player1.hashCode(), player2.hashCode(), "Hash codes should be equal for equal objects"); + assertNotEquals(player1.hashCode(), player3.hashCode(), "Hash codes should differ for different objects"); + } +} \ No newline at end of file diff --git a/standalone-tests/src/test/java/us/mcparks/achievables/reference/TestEventTest.java b/standalone-tests/src/test/java/us/mcparks/achievables/reference/TestEventTest.java new file mode 100644 index 0000000..891f5c9 --- /dev/null +++ b/standalone-tests/src/test/java/us/mcparks/achievables/reference/TestEventTest.java @@ -0,0 +1,38 @@ +package us.mcparks.achievables.reference; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class TestEventTest { + + @Test + void testConstructorAndGetters() { + // Arrange + String type = "test_event"; + ReferencePlayer player = new ReferencePlayer("player1", "TestPlayer"); + String data = "event_data"; + + // Act + TestEvent event = new TestEvent(type, player, data); + + // Assert + assertEquals(type, event.getType(), "Event type should match the one provided in constructor"); + assertEquals(player, event.getApplicablePlayer(), "Player should match the one provided in constructor"); + assertEquals(data, event.getData(), "Event data should match the one provided in constructor"); + } + + @Test + void testToString() { + // Arrange + ReferencePlayer player = new ReferencePlayer("player1", "TestPlayer"); + TestEvent event = new TestEvent("test_event", player, "event_data"); + + // Act + String result = event.toString(); + + // Assert + assertTrue(result.contains("test_event"), "toString should contain event type"); + assertTrue(result.contains("player1"), "toString should contain player information"); + assertTrue(result.contains("event_data"), "toString should contain event data"); + } +} \ No newline at end of file