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.
140 lines
3.7 KiB
Markdown
140 lines
3.7 KiB
Markdown
# Item-Modell
|
|
|
|
Hierarchie aus abstraktem `Item` und drei konkreten Subtypen. Liegt im Subpackage `thb.jeanluc.adventure.model.item`.
|
|
|
|
## Hierarchie
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
```java
|
|
@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:
|
|
```java
|
|
ReadableItem.builder()
|
|
.id("letter").name("Letter").description("A crumpled paper.")
|
|
.readText("Meet me at midnight.")
|
|
.build();
|
|
```
|
|
|
|
## Jackson Polymorphism
|
|
|
|
```java
|
|
@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 über `GameContext`, 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.
|