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.
3.6 KiB
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);
}