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.7 KiB
Item-Modell
Hierarchie aus abstraktem Item und drei konkreten Subtypen. Liegt im Subpackage thb.jeanluc.adventure.model.item.
Hierarchie
classDiagram
class Item {
<<abstract>>
#String id
#String name
#String description
+use(GameContext)* void
}
class ReadableItem {
-String readText
+use(GameContext) void
}
class SwitchableItem {
-boolean state
-boolean initialState
-String onText
-String offText
+use(GameContext) void
+isOn() boolean
}
class PlainItem {
+use(GameContext) void
}
Item <|-- ReadableItem
Item <|-- SwitchableItem
Item <|-- PlainItem
Felder pro Klasse
Item (abstract)
| Feld | Typ | Hinweis |
|---|---|---|
id |
String |
unique slug, Lookup-Key |
name |
String |
Anzeigename |
description |
String |
examine-Output |
Abstrakte Methode: public abstract void use(GameContext ctx)
ReadableItem
| Feld | Typ | Hinweis |
|---|---|---|
readText |
String |
wird beim read/use ausgegeben |
use() schreibt readText über ctx.io().
SwitchableItem
| Feld | Typ | Hinweis |
|---|---|---|
state |
boolean |
aktueller Zustand (mutable) |
initialState |
boolean |
aus YAML, im Konstruktor an state durchgereicht |
onText |
String |
Nachricht beim Einschalten |
offText |
String |
Nachricht beim Ausschalten |
use() toggelt state und schreibt den entsprechenden Text.
Bewusste Entscheidung: boolean statt enum SwitchState. Wenn später BROKEN o.ä. nötig wird, refactoren.
PlainItem
Keine eigenen Felder. use() schreibt eine generische Nachricht („You can't use the X by itself."). Existiert, damit alle Items polymorph use() haben und das YAML einen konsistenten type:-Discriminator hat.
Lombok-Setup
@SuperBuilder ist Pflicht, weil normales @Builder Vererbung nicht beherrscht.
@Getter
@SuperBuilder
@RequiredArgsConstructor
public abstract class Item {
protected final String id;
protected final String name;
protected final String description;
public abstract void use(GameContext ctx);
}
@Getter
@SuperBuilder
public class ReadableItem extends Item {
private final String readText;
@Override
public void use(GameContext ctx) {
ctx.getIo().write(readText);
}
}
Konstruktion:
ReadableItem.builder()
.id("letter").name("Letter").description("A crumpled paper.")
.readText("Meet me at midnight.")
.build();
Jackson Polymorphism
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = ReadableItem.class, name = "readable"),
@JsonSubTypes.Type(value = SwitchableItem.class, name = "switchable"),
@JsonSubTypes.Type(value = PlainItem.class, name = "plain")
})
public abstract class Item { ... }
YAML-Pflichtfeld pro Item: type: readable|switchable|plain.
Verworfen / bewusst nicht im MVP
use X on Y(targeted use) — Items kennen ihren Kontext überGameContext, kein zweites Item als Parameter.- Hidden Items (sichtbar erst nach Aktion) — keine
visible-Flag, alle Items sofort sichtbar. - Item kennt seinen Standort — Items sind „dumm", nur Room/Player wissen wer sie hält.
- Item-State beeinflusst Raumbeschreibung (z.B. „Cellar dark unless lamp on") — wenn nötig, später per Conditions-System.
hasBeenRead-Tracking — Lesen bleibt idempotent.
Player-Input-Matching
Player tippt take lamp → Match gegen id. Multi-Word-Items haben snake_case ids, also take oil_lamp.
Alias-Feld (aliases: [lamp, oil]) ist YAGNI bis ein konkreter Bedarf entsteht.