feat(command): accept German command and direction aliases

The assignment's examples are German ("Gehe nach Norden", "Nimm Brief",
"Lies Brief", "Benutze Schaufel") but every verb was English-only, so none of
them parsed.

- Register German aliases alongside the English ones (gehe, nimm, lies, benutze,
  lege, gib, rede, schau, karte, inventar, hilfe, beenden)
- Direction.fromString accepts norden/sueden/süden/osten/westen and the n/s/e/w
  shorthands; YAML exits keep the canonical English names
- Treat "nach", "den", "die", "das" as filler words so "Gehe nach Norden" parses
This commit is contained in:
2026-07-14 12:38:45 +02:00
parent 8f6ee80f30
commit 73fc6eab68
4 changed files with 118 additions and 20 deletions

View File

@@ -47,10 +47,15 @@ import java.util.List;
@Slf4j
public final class App {
/** Utility class; not instantiable. */
private App() {
}
/** Standard JVM entry point. @param args ignored */
/**
* Standard JVM entry point.
*
* @param args ignored
*/
public static void main(String[] args) {
GameIO io = new ConsoleIO();
try {
@@ -62,12 +67,22 @@ public final class App {
}
}
/** Runs the menu shell on the given IO until the player quits. */
/**
* Runs the menu shell on the given IO until the player quits.
*
* @param io frontend the menu and the game are rendered on
*/
public static void run(GameIO io) {
run(io, new SaveService(), new SettingsStore());
}
/** Runs the menu shell with injectable persistence (for testing). */
/**
* Runs the menu shell with injectable persistence (for testing).
*
* @param io frontend the menu and the game are rendered on
* @param saves save-slot backend
* @param settingsStore settings backend
*/
static void run(GameIO io, SaveService saves, SettingsStore settingsStore) {
Settings settings = settingsStore.load();
SettingsMenu.apply(settings, io);
@@ -112,27 +127,29 @@ public final class App {
}
});
// German aliases mirror the examples in the assignment ("Gehe nach
// Norden", "Nimm Brief", "Lies Brief", "Benutze Schaufel").
CommandRegistry registry = new CommandRegistry();
registry.register(new GoCommand(), "go", "move", "walk");
registry.register(new LookCommand(), "look", "l");
registry.register(new InventoryCommand(), "inventory", "inv", "i");
registry.register(new TakeCommand(), "take", "pick", "get");
registry.register(new DropCommand(), "drop", "put");
registry.register(new UseCommand(), "use");
registry.register(new ReadCommand(), "read");
registry.register(new ExamineCommand(), "examine", "x", "inspect");
registry.register(new MapCommand(), "map", "m");
registry.register(new QuestsCommand(), "quests", "log", "journal");
registry.register(new TalkCommand(), "talk", "speak");
registry.register(new GiveCommand(), "give");
registry.register(new SaveCommand(saves), "save");
registry.register(new GoCommand(), "go", "move", "walk", "gehe", "geh");
registry.register(new LookCommand(), "look", "l", "umsehen", "schau");
registry.register(new InventoryCommand(), "inventory", "inv", "i", "inventar");
registry.register(new TakeCommand(), "take", "pick", "get", "nimm", "nehme");
registry.register(new DropCommand(), "drop", "put", "lege", "ablegen");
registry.register(new UseCommand(), "use", "benutze", "benutz");
registry.register(new ReadCommand(), "read", "lies", "lese");
registry.register(new ExamineCommand(), "examine", "x", "inspect", "untersuche");
registry.register(new MapCommand(), "map", "m", "karte");
registry.register(new QuestsCommand(), "quests", "log", "journal", "aufgaben");
registry.register(new TalkCommand(), "talk", "speak", "rede", "sprich");
registry.register(new GiveCommand(), "give", "gib");
registry.register(new SaveCommand(saves), "save", "speichern");
Game game = new Game(ctx, registry, new CommandParser());
MenuCommand menu = new MenuCommand(saves);
menu.bind(game);
registry.register(menu, "menu", "quit", "exit");
registry.register(new HelpCommand(registry), "help", "?");
registry.register(menu, "menu", "quit", "exit", "beenden");
registry.register(new HelpCommand(registry), "help", "?", "hilfe");
if (withTutorial) {
Tutorial tutorial = new TutorialLoader().load();

View File

@@ -16,7 +16,8 @@ public class CommandParser {
* small — when adding more, watch out for words that might be valid
* item ids ({@code key}, {@code lamp}, etc.).
*/
private static final Set<String> FILLERS = Set.of("to", "with", "at", "the", "a", "an", "on");
private static final Set<String> FILLERS =
Set.of("to", "with", "at", "the", "a", "an", "on", "nach", "den", "die", "das");
/**
* Parses an input line into a verb and its arguments.

View File

@@ -1,5 +1,7 @@
package thb.jeanluc.adventure.model;
import java.util.Map;
/**
* Compass directions used for navigating between rooms.
* Each direction has an opposite, which is used when describing
@@ -7,9 +9,13 @@ package thb.jeanluc.adventure.model;
* entering the next room from the SOUTH).
*/
public enum Direction {
/** Towards the top of the map; opposite of {@link #SOUTH}. */
NORTH,
/** Towards the bottom of the map; opposite of {@link #NORTH}. */
SOUTH,
/** Towards the right of the map; opposite of {@link #WEST}. */
EAST,
/** Towards the left of the map; opposite of {@link #EAST}. */
WEST;
/**
@@ -38,13 +44,40 @@ public enum Direction {
if (s == null) {
throw new IllegalArgumentException("Direction must not be null");
}
String key = s.trim().toLowerCase();
Direction alias = ALIASES.get(key);
if (alias != null) {
return alias;
}
try {
return Direction.valueOf(s.trim().toUpperCase());
return Direction.valueOf(key.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Unknown direction: '" + s + "'");
}
}
/**
* Alternative spellings accepted from player input: single-letter
* shorthands and the German direction names used in the assignment's
* examples ("Gehe nach Norden"). YAML exits still use the canonical
* English names.
*/
private static final Map<String, Direction> ALIASES = Map.ofEntries(
Map.entry("n", NORTH),
Map.entry("s", SOUTH),
Map.entry("e", EAST),
Map.entry("w", WEST),
Map.entry("norden", NORTH),
Map.entry("sueden", SOUTH),
Map.entry("süden", SOUTH),
Map.entry("osten", EAST),
Map.entry("westen", WEST),
Map.entry("nord", NORTH),
Map.entry("sued", SOUTH),
Map.entry("süd", SOUTH),
Map.entry("ost", EAST),
Map.entry("west", WEST));
/**
* Returns the human-readable lowercase label, suitable for display
* to the player (e.g. "north"). Intentionally not a toString override

View File

@@ -0,0 +1,47 @@
package thb.jeanluc.adventure.command;
import org.junit.jupiter.api.Test;
import thb.jeanluc.adventure.model.Direction;
import static org.assertj.core.api.Assertions.assertThat;
/**
* The assignment's examples are German ("Gehe nach Norden", "Nimm Brief"), so
* those inputs must parse. English stays the canonical form used in YAML.
*/
class GermanAliasTest {
private final CommandParser parser = new CommandParser();
@Test
void parse_gehNachNorden_yieldsGoNorth() {
ParsedCommand c = parser.parse("Gehe nach Norden");
assertThat(c.verb()).isEqualTo("gehe");
assertThat(c.args()).containsExactly("norden");
assertThat(Direction.fromString(c.args().getFirst())).isEqualTo(Direction.NORTH);
}
@Test
void parse_nimmBrief_yieldsTakeWithItemArg() {
ParsedCommand c = parser.parse("Nimm Brief");
assertThat(c.verb()).isEqualTo("nimm");
assertThat(c.args()).containsExactly("brief");
}
@Test
void fromString_withGermanDirectionNames_resolves() {
assertThat(Direction.fromString("norden")).isEqualTo(Direction.NORTH);
assertThat(Direction.fromString("sueden")).isEqualTo(Direction.SOUTH);
assertThat(Direction.fromString("süden")).isEqualTo(Direction.SOUTH);
assertThat(Direction.fromString("osten")).isEqualTo(Direction.EAST);
assertThat(Direction.fromString("westen")).isEqualTo(Direction.WEST);
}
@Test
void fromString_withEnglishNames_stillResolves() {
assertThat(Direction.fromString("north")).isEqualTo(Direction.NORTH);
assertThat(Direction.fromString("n")).isEqualTo(Direction.NORTH);
}
}