feat(game): Combinations engine for use X on Y
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
package thb.jeanluc.adventure.game;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import thb.jeanluc.adventure.io.TestIO;
|
||||
import thb.jeanluc.adventure.model.Combination;
|
||||
import thb.jeanluc.adventure.model.Condition;
|
||||
import thb.jeanluc.adventure.model.Effect;
|
||||
import thb.jeanluc.adventure.model.Player;
|
||||
import thb.jeanluc.adventure.model.Room;
|
||||
import thb.jeanluc.adventure.model.World;
|
||||
import thb.jeanluc.adventure.model.item.Item;
|
||||
import thb.jeanluc.adventure.model.item.PlainItem;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CombinationsTest {
|
||||
|
||||
private Item plain(String id, boolean light) {
|
||||
return PlainItem.builder().id(id).name(id).description("d").light(light).build();
|
||||
}
|
||||
|
||||
/** World with matches/torch/lit_torch/shovel and the given recipes; player in an empty room. */
|
||||
private GameContext ctx(Combination... recipes) {
|
||||
Map<String, Item> items = new LinkedHashMap<>();
|
||||
items.put("matches", plain("matches", false));
|
||||
items.put("torch", plain("torch", false));
|
||||
items.put("lit_torch", plain("lit_torch", true));
|
||||
items.put("shovel", plain("shovel", false));
|
||||
Map<String, Combination> combos = new HashMap<>();
|
||||
for (Combination c : recipes) {
|
||||
combos.put(c.key(), c);
|
||||
}
|
||||
Room start = new Room("start", "Start", "d");
|
||||
World w = new World(Map.of("start", start), items, Map.of(), "t", "w",
|
||||
Map.of(), List.of(), combos);
|
||||
return new GameContext(w, new Player(start, 0), new TestIO());
|
||||
}
|
||||
|
||||
private void give(GameContext ctx, String id) {
|
||||
ctx.getPlayer().addItem(ctx.getWorld().getItems().get(id));
|
||||
}
|
||||
|
||||
private void putInRoom(GameContext ctx, String id) {
|
||||
ctx.getPlayer().getCurrentRoom().addItem(ctx.getWorld().getItems().get(id));
|
||||
}
|
||||
|
||||
private String out(GameContext ctx) {
|
||||
return ((TestIO) ctx.getIo()).allOutput();
|
||||
}
|
||||
|
||||
private Combination torchRecipe() {
|
||||
return new Combination("matches", "torch", List.of(),
|
||||
List.of("matches", "torch"), "lit_torch", List.of(),
|
||||
"You light the torch.", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void combinesFromInventory() {
|
||||
GameContext ctx = ctx(torchRecipe());
|
||||
give(ctx, "matches");
|
||||
give(ctx, "torch");
|
||||
Combinations.tryUse(ctx, "matches", "torch");
|
||||
assertThat(ctx.getPlayer().hasItem("lit_torch")).isTrue();
|
||||
assertThat(ctx.getPlayer().hasItem("matches")).isFalse();
|
||||
assertThat(ctx.getPlayer().hasItem("torch")).isFalse();
|
||||
assertThat(out(ctx)).contains("light the torch");
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchIsOrderIndependent() {
|
||||
GameContext ctx = ctx(torchRecipe());
|
||||
give(ctx, "matches");
|
||||
give(ctx, "torch");
|
||||
Combinations.tryUse(ctx, "torch", "matches");
|
||||
assertThat(ctx.getPlayer().hasItem("lit_torch")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void consumesOperandFromCurrentRoom() {
|
||||
GameContext ctx = ctx(torchRecipe());
|
||||
give(ctx, "matches");
|
||||
putInRoom(ctx, "torch");
|
||||
Combinations.tryUse(ctx, "matches", "torch");
|
||||
assertThat(ctx.getPlayer().hasItem("lit_torch")).isTrue();
|
||||
assertThat(ctx.getPlayer().getCurrentRoom().findItem("torch")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingOperandReportsNoSuch() {
|
||||
GameContext ctx = ctx(torchRecipe());
|
||||
give(ctx, "matches"); // torch absent
|
||||
Combinations.tryUse(ctx, "matches", "torch");
|
||||
assertThat(out(ctx)).contains("no 'torch'");
|
||||
assertThat(ctx.getPlayer().hasItem("lit_torch")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void noRecipeForPairSaysNothingHappens() {
|
||||
GameContext ctx = ctx(torchRecipe());
|
||||
give(ctx, "shovel");
|
||||
give(ctx, "torch");
|
||||
Combinations.tryUse(ctx, "shovel", "torch");
|
||||
assertThat(out(ctx)).contains("Nothing happens.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unmetRequiresShowsFailTextAndDoesNotProduce() {
|
||||
Combination gated = new Combination("matches", "torch",
|
||||
List.of(new Condition(Condition.Type.FLAG, "dry")),
|
||||
List.of("matches", "torch"), "lit_torch", List.of(),
|
||||
"It catches.", "The torch is too damp to light.");
|
||||
GameContext ctx = ctx(gated);
|
||||
give(ctx, "matches");
|
||||
give(ctx, "torch");
|
||||
Combinations.tryUse(ctx, "matches", "torch");
|
||||
assertThat(out(ctx)).contains("too damp");
|
||||
assertThat(ctx.getPlayer().hasItem("lit_torch")).isFalse();
|
||||
assertThat(ctx.getPlayer().hasItem("matches")).isTrue(); // not consumed
|
||||
}
|
||||
|
||||
@Test
|
||||
void metRequiresAppliesEffectsAndProduces() {
|
||||
Combination gated = new Combination("matches", "torch",
|
||||
List.of(new Condition(Condition.Type.FLAG, "dry")),
|
||||
List.of("matches", "torch"), "lit_torch",
|
||||
List.of(new Effect(Effect.Type.SET_FLAG, "torch_lit")),
|
||||
"It catches.", "too damp");
|
||||
GameContext ctx = ctx(gated);
|
||||
ctx.getState().set("dry");
|
||||
give(ctx, "matches");
|
||||
give(ctx, "torch");
|
||||
Combinations.tryUse(ctx, "matches", "torch");
|
||||
assertThat(ctx.getPlayer().hasItem("lit_torch")).isTrue();
|
||||
assertThat(ctx.getState().isSet("torch_lit")).isTrue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user