Files
Jander_Semester2/Semesterprojekt/docs/commands.md
Jean-Luc Makiola 83643a192f semesterprojekt: implement full text adventure (phases 1-7)
Walking skeleton through Swing GUI: YAML-driven world (4 rooms,
4 items, 1 NPC), HashMap command dispatch with parser, three-tier
item hierarchy (readable / switchable / plain), and end-to-end
NPC give/receive flow. 67 tests green.
2026-05-25 21:37:59 +02:00

3.6 KiB

Befehle

Command-Pattern mit Registry — passt zum Aufgaben-Tipp „switch, if-else, HashMap" und macht die HashMap-Variante explizit.

Interface

public interface Command {
    /** Wird vom Parser nach Tokenisierung aufgerufen. */
    void execute(GameContext ctx, List<String> args);

    /** Short help text for the 'help' command. */
    String help();
}

Registry

public class CommandRegistry {
    private final Map<String, Command> commands = new HashMap<>();

    public void register(Command cmd, String... names) {
        for (String n : names) {
            commands.put(n.toLowerCase(), cmd);
        }
    }

    public Optional<Command> find(String verb) {
        return Optional.ofNullable(commands.get(verb.toLowerCase()));
    }
}

Aliase per Mehrfach-Registrierung:

registry.register(new GoCommand(), "go", "move");
registry.register(new TakeCommand(), "take", "pick");

Parser

Einfacher tokenisierender Parser. Erst-Token = Verb, Rest = Argumente.

Spezialfall: Präpositionen / Artikel wegfiltern, damit go to north und go north beide funktionieren.

public record ParsedCommand(String verb, List<String> args) {}

public class CommandParser {
    private static final Set<String> FILLERS = Set.of("to", "with", "at", "the", "a", "an");

    public ParsedCommand parse(String input) {
        String[] tokens = input.trim().toLowerCase().split("\\s+");
        if (tokens.length == 0 || tokens[0].isEmpty()) {
            return new ParsedCommand("", List.of());
        }
        String verb = tokens[0];
        List<String> args = Arrays.stream(tokens, 1, tokens.length)
            .filter(t -> !FILLERS.contains(t))
            .toList();
        return new ParsedCommand(verb, args);
    }
}

Befehlsliste (Pflicht + Optional)

Verb Aliase Wirkung Quelle
go <direction> move, walk Spieler wechselt Raum Pflicht
take <item> pick, get Item aus Raum ins Inventar Pflicht
drop <item> put Item aus Inventar in Raum sinnvoll
use <item> Item-spezifische Aktion Pflicht
read <item> Spezialfall von use für readable Pflicht
inventory inv, i Inventar anzeigen sinnvoll
look l Raumbeschreibung wiederholen sinnvoll
examine <item> x Item-Beschreibung anzeigen sinnvoll
talk <npc> speak NPC-Greeting ausgeben NPC-Bonus
give <item> <npc> Item übergeben, Reaktion auslösen NPC-Bonus
help ? Befehlsübersicht sinnvoll
quit exit Spiel beenden sinnvoll

Behandlung unbekannter Befehle

parser.parse(input);
registry.find(parsed.verb())
    .ifPresentOrElse(
        cmd -> cmd.execute(ctx, parsed.args()),
        () -> ctx.io().write("I don't understand '" + parsed.verb() + "'. Type 'help'.")
    );

GameContext

Wird allen Commands gereicht, kapselt was sie ändern dürfen:

public class GameContext {
    private final World world;
    private final Player player;
    private final GameIO io;
    // Lombok @Getter, kein Setter
}

So vermeidest du, dass jeder Command 5 Konstruktor-Parameter braucht.

Tests

Pro Command ein Testfall, der GameContext mit Mockito mockt (oder als Fake-IO baut):

@Test
void goCommand_movesPlayerToConnectedRoom() {
    Room kitchen = ...; Room hallway = ...;
    kitchen.getExits().put(Direction.NORTH, hallway);
    Player p = new Player(kitchen, 0);
    GameContext ctx = new GameContext(world, p, new TestIO());

    new GoCommand().execute(ctx, List.of("north"));

    assertThat(p.getCurrentRoom()).isEqualTo(hallway);
}