diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/EndingEngine.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/EndingEngine.java new file mode 100644 index 0000000..e70a46d --- /dev/null +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/EndingEngine.java @@ -0,0 +1,46 @@ +package thb.jeanluc.adventure.game; + +import thb.jeanluc.adventure.io.text.StyledText; +import thb.jeanluc.adventure.model.Ending; + +/** Detects the first matching ending and renders the end-of-game screen. */ +public final class EndingEngine { + + private EndingEngine() { + } + + /** First ending whose conditions hold, in list order; null if none. */ + public static Ending triggered(GameContext ctx) { + for (Ending e : ctx.getWorld().getEndings()) { + if (Conditions.all(e.when(), ctx)) { + return e; + } + } + return null; + } + + /** Builds the ending screen: title banner, text, and a run summary. */ + public static StyledText render(Ending e, GameContext ctx, int turns) { + int total = ctx.getWorld().getQuests().size(); + int done = ctx.getQuestLog().completed().size(); + String bar = "═".repeat(Math.max(12, e.title().length() + 6)); + + StyledText.Builder b = StyledText.builder(); + b.heading(bar + "\n").heading(" " + e.title() + "\n").heading(bar + "\n"); + b.plain(e.text().stripTrailing()).plain("\n\n"); + b.dim("Turns: " + turns + "\n"); + b.dim("Quests completed: " + done + " / " + total + "\n"); + b.heading("Rank: " + rank(e, total, done)); + return b.build(); + } + + private static String rank(Ending e, int total, int done) { + if (e.victory() && total > 0 && done == total) { + return "Master of the Manor"; + } + if (e.victory()) { + return "Manor Reclaimed"; + } + return "Escaped — the manor keeps its secrets"; + } +} diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Game.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Game.java index 0d5a5cb..20d0f02 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Game.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Game.java @@ -8,6 +8,7 @@ import thb.jeanluc.adventure.command.ParsedCommand; import thb.jeanluc.adventure.io.text.Hud; import thb.jeanluc.adventure.io.text.MapView; import thb.jeanluc.adventure.map.MapLayout; +import thb.jeanluc.adventure.model.Ending; import java.util.Optional; @@ -39,6 +40,7 @@ public class Game { */ public void run() { publishHud(); + maybeEnd(); while (running) { String input = ctx.getIo().readLine(); if (input == null) { @@ -56,6 +58,16 @@ public class Game { turn++; } publishHud(); + maybeEnd(); + } + } + + /** Ends the game if any ending's conditions are met, printing its screen. */ + private void maybeEnd() { + Ending e = EndingEngine.triggered(ctx); + if (e != null) { + ctx.getIo().print(EndingEngine.render(e, ctx, turn)); + stop(); } } diff --git a/Semesterprojekt/src/test/java/thb/jeanluc/adventure/game/EndingEngineTest.java b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/game/EndingEngineTest.java new file mode 100644 index 0000000..f40859c --- /dev/null +++ b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/game/EndingEngineTest.java @@ -0,0 +1,50 @@ +package thb.jeanluc.adventure.game; + +import org.junit.jupiter.api.Test; +import thb.jeanluc.adventure.io.TestIO; +import thb.jeanluc.adventure.model.Condition; +import thb.jeanluc.adventure.model.Ending; +import thb.jeanluc.adventure.model.Player; +import thb.jeanluc.adventure.model.Room; +import thb.jeanluc.adventure.model.World; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class EndingEngineTest { + private GameContext ctx(List endings) { + Player p = new Player(new Room("k", "K", "d"), 0); + World w = new World(Map.of(), Map.of(), Map.of(), "t", "w", Map.of(), endings); + return new GameContext(w, p, new TestIO()); + } + + private Ending ending(String id, boolean victory, String flag) { + return new Ending(id, id + "-title", victory, + List.of(new Condition(Condition.Type.FLAG, flag)), id + " text"); + } + + @Test + void firstMatchingEndingWinsByOrder() { + GameContext ctx = ctx(List.of(ending("victory", true, "won"), ending("fled", false, "fled"))); + ctx.getState().set("won"); + ctx.getState().set("fled"); + assertThat(EndingEngine.triggered(ctx).id()).isEqualTo("victory"); + } + + @Test + void noEndingWhenNoConditionHolds() { + GameContext ctx = ctx(List.of(ending("victory", true, "won"))); + assertThat(EndingEngine.triggered(ctx)).isNull(); + } + + @Test + void renderIncludesTitleTextAndSummary() { + GameContext ctx = ctx(List.of(ending("victory", true, "won"))); + ctx.getState().set("won"); + String out = EndingEngine.render(EndingEngine.triggered(ctx), ctx, 7).plainText(); + assertThat(out).contains("victory-title").contains("victory text"); + assertThat(out).contains("Turns: 7").contains("Quests completed: 0 / 0"); + } +}