diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/TutorialLoader.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/TutorialLoader.java new file mode 100644 index 0000000..7ebabcb --- /dev/null +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/TutorialLoader.java @@ -0,0 +1,63 @@ +package thb.jeanluc.adventure.loader; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import thb.jeanluc.adventure.loader.dto.TutorialDto; +import thb.jeanluc.adventure.loader.dto.TutorialStepDto; +import thb.jeanluc.adventure.model.Tutorial; +import thb.jeanluc.adventure.model.TutorialStep; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Loads the optional onboarding tutorial from {@code /tutorial.yaml}. + * Kept separate from {@link WorldLoader}: the tutorial is onboarding, not world + * content. Returns {@link Tutorial#none()} when the file is absent. + */ +public class TutorialLoader { + + /** Default classpath base directory. */ + public static final String DEFAULT_BASE = "/world"; + + private final ObjectMapper yaml = new ObjectMapper(new YAMLFactory()); + private final String basePath; + + public TutorialLoader() { + this(DEFAULT_BASE); + } + + public TutorialLoader(String basePath) { + this.basePath = basePath; + } + + /** Reads the tutorial; absent file → {@link Tutorial#none()}. */ + public Tutorial load() { + String resource = basePath + "/tutorial.yaml"; + try (InputStream in = getClass().getResourceAsStream(resource)) { + if (in == null) { + return Tutorial.none(); + } + TutorialDto dto = yaml.readValue(in, TutorialDto.class); + if (dto == null) { + return Tutorial.none(); + } + List steps = new ArrayList<>(); + if (dto.steps() != null) { + for (TutorialStepDto s : dto.steps()) { + steps.add(new TutorialStep( + s.instruction(), + s.expect(), + s.minArgs() == null ? 0 : s.minArgs(), + s.confirm(), + s.hint())); + } + } + return new Tutorial(dto.intro(), steps, dto.closingTips()); + } catch (IOException e) { + throw new WorldLoadException("Failed to parse " + resource, e); + } + } +} diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialDto.java new file mode 100644 index 0000000..ab113eb --- /dev/null +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialDto.java @@ -0,0 +1,7 @@ +package thb.jeanluc.adventure.loader.dto; + +import java.util.List; + +/** YAML representation of the tutorial document. */ +public record TutorialDto(String intro, List steps, String closingTips) { +} diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialStepDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialStepDto.java new file mode 100644 index 0000000..e3f622b --- /dev/null +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialStepDto.java @@ -0,0 +1,11 @@ +package thb.jeanluc.adventure.loader.dto; + +/** YAML representation of a tutorial step. */ +public record TutorialStepDto( + String instruction, + String expect, + Integer minArgs, + String confirm, + String hint +) { +} diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Tutorial.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Tutorial.java new file mode 100644 index 0000000..338b2e2 --- /dev/null +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Tutorial.java @@ -0,0 +1,20 @@ +package thb.jeanluc.adventure.model; + +import java.util.List; + +/** Onboarding walkthrough: an intro, ordered steps, and closing tips. */ +public record Tutorial(String intro, List steps, String closingTips) { + + public Tutorial { + steps = steps == null ? List.of() : List.copyOf(steps); + } + + public boolean isEmpty() { + return steps.isEmpty(); + } + + /** An absent tutorial (no steps). */ + public static Tutorial none() { + return new Tutorial(null, List.of(), null); + } +} diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/TutorialStep.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/TutorialStep.java new file mode 100644 index 0000000..e32b587 --- /dev/null +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/TutorialStep.java @@ -0,0 +1,16 @@ +package thb.jeanluc.adventure.model; + +/** + * One interactive tutorial step: an instruction, the command that satisfies it + * ({@code expect} is a verb name like "look", or "go_direction"/"go_to_room"), + * a minimum argument count, a success confirmation, and a hint for a valid but + * wrong command. + */ +public record TutorialStep( + String instruction, + String expect, + int minArgs, + String confirm, + String hint +) { +} diff --git a/Semesterprojekt/src/main/resources/world/tutorial.yaml b/Semesterprojekt/src/main/resources/world/tutorial.yaml new file mode 100644 index 0000000..1dd7573 --- /dev/null +++ b/Semesterprojekt/src/main/resources/world/tutorial.yaml @@ -0,0 +1,40 @@ +intro: | + Welcome to the manor. Let me show you the ropes — just type the commands as + I describe them. (Type 'skip' at any time to jump straight into the game.) +steps: + - instruction: "First, look around the room: type 'look'." + expect: look + confirm: "Good — 'look' (or 'l') re-describes wherever you are." + hint: "When you're ready, just type: look" + - instruction: "Examine something you noticed: 'examine ' (or 'x ')." + expect: examine + minArgs: 1 + confirm: "That's how you inspect items, exits, and people up close." + hint: "Try examining something, e.g.: examine lamp" + - instruction: "Pick something up: 'take '." + expect: take + minArgs: 1 + confirm: "Taken. Things you carry go into your inventory." + hint: "Pick something up, e.g.: take lamp" + - instruction: "Check what you're carrying: 'inventory' (or 'i')." + expect: inventory + confirm: "That's your inventory." + hint: "Type: inventory" + - instruction: "Use an item: 'use ' — try lighting the lamp." + expect: use + minArgs: 1 + confirm: "Some items toggle, read, or react when you use them." + hint: "Use something, e.g.: use lamp" + - instruction: "Now move to another room: 'go ' (try 'go north')." + expect: go_direction + confirm: "You can move north / south / east / west between connected rooms." + hint: "Head to a connected room, e.g.: go north" + - instruction: "Travel back to a room you've already seen: 'go to '." + expect: go_to_room + confirm: "Nice — 'go to ' auto-walks to any room you've already visited." + hint: "Try: go to " +closingTips: | + That's the core. A few more you'll want: + read · drop · talk to · give to · + use on (combine things) · map · quests · save · menu. + Type 'help' anytime. Good luck. diff --git a/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/TutorialLoaderTest.java b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/TutorialLoaderTest.java new file mode 100644 index 0000000..937306b --- /dev/null +++ b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/TutorialLoaderTest.java @@ -0,0 +1,27 @@ +package thb.jeanluc.adventure.loader; + +import org.junit.jupiter.api.Test; +import thb.jeanluc.adventure.model.Tutorial; + +import static org.assertj.core.api.Assertions.assertThat; + +class TutorialLoaderTest { + + @Test + void loadsTheTestFixture() { + Tutorial t = new TutorialLoader().load(); // test-classpath /world/tutorial.yaml shadows prod + assertThat(t.isEmpty()).isFalse(); + assertThat(t.intro()).isNotBlank(); + assertThat(t.closingTips()).isNotBlank(); + assertThat(t.steps()).hasSize(2); + assertThat(t.steps().getFirst().expect()).isEqualTo("look"); + assertThat(t.steps().getFirst().minArgs()).isZero(); + } + + @Test + void missingFileReturnsEmptyTutorial() { + Tutorial t = new TutorialLoader("/no-such-base").load(); + assertThat(t.isEmpty()).isTrue(); + assertThat(t.steps()).isEmpty(); + } +} diff --git a/Semesterprojekt/src/test/resources/world/tutorial.yaml b/Semesterprojekt/src/test/resources/world/tutorial.yaml new file mode 100644 index 0000000..950bf44 --- /dev/null +++ b/Semesterprojekt/src/test/resources/world/tutorial.yaml @@ -0,0 +1,14 @@ +intro: | + Test intro. +steps: + - instruction: "Look: type 'look'." + expect: look + confirm: "Looked." + hint: "Type look." + - instruction: "Take: type 'take '." + expect: take + minArgs: 1 + confirm: "Took it." + hint: "Take something." +closingTips: | + Test tips.