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 2cf1058..68f5bb1 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Game.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Game.java @@ -32,9 +32,6 @@ public class Game { /** Loop flag. Flipped to false by {@link #stop()}. */ private boolean running = true; - /** Number of commands processed this session; shown in the HUD. */ - private int turn = 0; - /** * Runs the loop until {@link #stop()} is called or input is exhausted. */ @@ -55,7 +52,10 @@ public class Game { ctx.getIo().write("I don't understand '" + parsed.verb() + "'. Type 'help'."); } else { cmd.get().execute(ctx, parsed.args()); - turn++; + ctx.getSession().incrementTurn(); + if (ctx.getSession().getTurn() % 10 == 0) { + ctx.save(); + } } publishHud(); maybeEnd(); @@ -66,7 +66,7 @@ public class Game { private void maybeEnd() { Ending e = EndingEngine.triggered(ctx); if (e != null) { - ctx.getIo().print(EndingEngine.render(e, ctx, turn)); + ctx.getIo().print(EndingEngine.render(e, ctx, ctx.getSession().getTurn())); stop(); } } @@ -76,7 +76,7 @@ public class Game { ctx.getIo().setHud(new Hud( ctx.getPlayer().getCurrentRoom().getName(), ctx.getPlayer().getGold(), - turn, + ctx.getSession().getTurn(), Light.carryingLight(ctx))); MapView map = MapLayout.compute( ctx.getWorld(), diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameContext.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameContext.java index a552b92..9f41d13 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameContext.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameContext.java @@ -1,32 +1,63 @@ package thb.jeanluc.adventure.game; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import thb.jeanluc.adventure.io.GameIO; import thb.jeanluc.adventure.model.Player; import thb.jeanluc.adventure.model.World; /** - * Bundle of everything a command needs to do its work: the world, the - * player, and the IO channel. Passing this single object keeps command - * signatures small and consistent. + * Bundle of everything a command needs: the active {@link GameSession} + * (world, player, flags, quests, turn) and the IO channel. Getters delegate + * to the session so existing command code keeps working unchanged. */ -@Getter -@RequiredArgsConstructor public class GameContext { - /** Loaded world (rooms, items, npcs). */ - private final World world; - - /** The player avatar. */ - private final Player player; - - /** IO channel for reading input and writing output. */ + private final GameSession session; private final GameIO io; - /** Mutable world flags. Created fresh per context; not a constructor arg. */ - private final GameState state = new GameState(); + /** Silent autosave hook, wired by the app; no-op by default (tests). */ + private Runnable saveCallback = () -> { }; - /** Runtime quest progress. Created fresh per context; not a constructor arg. */ - private final QuestLog questLog = new QuestLog(); + public GameContext(GameSession session, GameIO io) { + this.session = session; + this.io = io; + } + + /** Convenience for tests/legacy callers: wraps a fresh single-use session. */ + public GameContext(World world, Player player, GameIO io) { + this(new GameSession(world, player, "session"), io); + } + + public GameSession getSession() { + return session; + } + + public World getWorld() { + return session.getWorld(); + } + + public Player getPlayer() { + return session.getPlayer(); + } + + public GameState getState() { + return session.getState(); + } + + public QuestLog getQuestLog() { + return session.getQuestLog(); + } + + public GameIO getIo() { + return io; + } + + /** Sets the silent autosave hook (called by interval/quest autosave). */ + public void setSaveCallback(Runnable saveCallback) { + this.saveCallback = saveCallback; + } + + /** Runs the silent autosave hook. */ + public void save() { + saveCallback.run(); + } } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameSession.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameSession.java new file mode 100644 index 0000000..fda1bfb --- /dev/null +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameSession.java @@ -0,0 +1,36 @@ +package thb.jeanluc.adventure.game; + +import lombok.Getter; +import thb.jeanluc.adventure.model.Player; +import thb.jeanluc.adventure.model.World; + +/** + * Bundle of the mutable, savable game state for one playthrough: the + * loaded {@link World}, the {@link Player}, world {@link GameState} flags, + * {@link QuestLog} progress, the turn counter, and the bound save-slot name. + * Save/load read and write exactly this object. + */ +@Getter +public class GameSession { + + private final World world; + private final Player player; + private final GameState state = new GameState(); + private final QuestLog questLog = new QuestLog(); + private final String slotName; + private int turn; + + public GameSession(World world, Player player, String slotName) { + this.world = world; + this.player = player; + this.slotName = slotName; + } + + public void setTurn(int turn) { + this.turn = turn; + } + + public void incrementTurn() { + this.turn++; + } +} diff --git a/Semesterprojekt/src/test/java/thb/jeanluc/adventure/game/GameSessionTest.java b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/game/GameSessionTest.java new file mode 100644 index 0000000..9c729f0 --- /dev/null +++ b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/game/GameSessionTest.java @@ -0,0 +1,52 @@ +package thb.jeanluc.adventure.game; + +import org.junit.jupiter.api.Test; +import thb.jeanluc.adventure.io.TestIO; +import thb.jeanluc.adventure.model.Player; +import thb.jeanluc.adventure.model.Room; +import thb.jeanluc.adventure.model.World; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class GameSessionTest { + + private GameSession session() { + Room k = new Room("k", "Kitchen", "d"); + World w = new World(Map.of("k", k), Map.of(), Map.of(), "t", "w"); + return new GameSession(w, new Player(k, 0), "slot-a"); + } + + @Test + void exposesStateAndIncrementsTurn() { + GameSession s = session(); + assertThat(s.getSlotName()).isEqualTo("slot-a"); + assertThat(s.getTurn()).isZero(); + s.incrementTurn(); + s.incrementTurn(); + assertThat(s.getTurn()).isEqualTo(2); + s.setTurn(10); + assertThat(s.getTurn()).isEqualTo(10); + } + + @Test + void contextDelegatesToSession() { + GameSession s = session(); + GameContext ctx = new GameContext(s, new TestIO()); + assertThat(ctx.getWorld()).isSameAs(s.getWorld()); + assertThat(ctx.getPlayer()).isSameAs(s.getPlayer()); + assertThat(ctx.getState()).isSameAs(s.getState()); + assertThat(ctx.getQuestLog()).isSameAs(s.getQuestLog()); + assertThat(ctx.getSession()).isSameAs(s); + } + + @Test + void legacyConstructorBuildsFreshSession() { + Room k = new Room("k", "Kitchen", "d"); + World w = new World(Map.of("k", k), Map.of(), Map.of(), "t", "w"); + GameContext ctx = new GameContext(w, new Player(k, 0), new TestIO()); + assertThat(ctx.getSession()).isNotNull(); + assertThat(ctx.getSession().getTurn()).isZero(); + } +}