feat(menu): MainMenu, SettingsMenu, MenuAction (frontend-agnostic)
This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
package thb.jeanluc.adventure.menu;
|
||||||
|
|
||||||
|
import thb.jeanluc.adventure.io.GameIO;
|
||||||
|
import thb.jeanluc.adventure.save.SaveService;
|
||||||
|
import thb.jeanluc.adventure.save.SaveSlotInfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Frontend-agnostic main menu, slot picker, and new-game name prompt. */
|
||||||
|
public final class MainMenu {
|
||||||
|
|
||||||
|
private MainMenu() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shows the main menu and returns the chosen action. */
|
||||||
|
public static MenuAction show(GameIO io) {
|
||||||
|
int i = io.choose("HAUNTED MANOR",
|
||||||
|
List.of("New Game", "Load Game", "Settings", "Quit"));
|
||||||
|
return MenuAction.values()[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prompts for a new save name; blank input yields {@code defaultName}. */
|
||||||
|
public static String promptName(GameIO io, String defaultName) {
|
||||||
|
io.write("Name your save (Enter for \"" + defaultName + "\"):");
|
||||||
|
String line = io.readLine();
|
||||||
|
return (line == null || line.isBlank()) ? defaultName : line.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lets the player pick a save slot to load. Returns the chosen
|
||||||
|
* {@link SaveSlotInfo}, or {@code null} if there are none or the player
|
||||||
|
* picks "Back".
|
||||||
|
*/
|
||||||
|
public static SaveSlotInfo pickSlot(GameIO io, SaveService saves) {
|
||||||
|
List<SaveSlotInfo> slots = saves.list();
|
||||||
|
if (slots.isEmpty()) {
|
||||||
|
io.write("No saved games.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<String> labels = new ArrayList<>();
|
||||||
|
for (SaveSlotInfo s : slots) {
|
||||||
|
labels.add(s.slotName() + " (" + s.roomId() + ", turn " + s.turn() + ")");
|
||||||
|
}
|
||||||
|
labels.add("Back");
|
||||||
|
int i = io.choose("LOAD GAME", labels);
|
||||||
|
return i < slots.size() ? slots.get(i) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package thb.jeanluc.adventure.menu;
|
||||||
|
|
||||||
|
/** Top-level main-menu choices, in display order. */
|
||||||
|
public enum MenuAction {
|
||||||
|
NEW_GAME, LOAD, SETTINGS, QUIT
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package thb.jeanluc.adventure.menu;
|
||||||
|
|
||||||
|
import thb.jeanluc.adventure.io.ConsoleIO;
|
||||||
|
import thb.jeanluc.adventure.io.GameIO;
|
||||||
|
import thb.jeanluc.adventure.save.Settings;
|
||||||
|
import thb.jeanluc.adventure.save.SettingsStore;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Minimal settings screen: toggle colour and glyph mode; persists + applies. */
|
||||||
|
public final class SettingsMenu {
|
||||||
|
|
||||||
|
private SettingsMenu() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loops the settings screen until the player picks "Back", persisting and
|
||||||
|
* applying each change. Returns the final settings.
|
||||||
|
*
|
||||||
|
* @param io active IO
|
||||||
|
* @param current starting settings
|
||||||
|
* @param store where to persist
|
||||||
|
* @param consoleIo the console IO to apply changes to live, or null if not console
|
||||||
|
*/
|
||||||
|
public static Settings show(GameIO io, Settings current, SettingsStore store, ConsoleIO consoleIo) {
|
||||||
|
Settings s = current;
|
||||||
|
while (true) {
|
||||||
|
int i = io.choose("SETTINGS", List.of(
|
||||||
|
"Colour: " + s.colorMode(),
|
||||||
|
"Glyphs: " + s.glyphMode(),
|
||||||
|
"Back"));
|
||||||
|
if (i == 0) {
|
||||||
|
s = new Settings(nextColor(s.colorMode()), s.glyphMode());
|
||||||
|
} else if (i == 1) {
|
||||||
|
s = new Settings(s.colorMode(), nextGlyph(s.glyphMode()));
|
||||||
|
} else {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
store.save(s);
|
||||||
|
apply(s, consoleIo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Applies colour/glyph settings to the console IO (no-op if null). */
|
||||||
|
public static void apply(Settings s, ConsoleIO consoleIo) {
|
||||||
|
if (consoleIo != null) {
|
||||||
|
consoleIo.setColorMode(s.colorMode());
|
||||||
|
consoleIo.setGlyphMode(s.glyphMode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConsoleIO.ColorMode nextColor(ConsoleIO.ColorMode m) {
|
||||||
|
return switch (m) {
|
||||||
|
case AUTO -> ConsoleIO.ColorMode.ON;
|
||||||
|
case ON -> ConsoleIO.ColorMode.OFF;
|
||||||
|
case OFF -> ConsoleIO.ColorMode.AUTO;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConsoleIO.GlyphMode nextGlyph(ConsoleIO.GlyphMode m) {
|
||||||
|
return switch (m) {
|
||||||
|
case UNICODE -> ConsoleIO.GlyphMode.ASCII;
|
||||||
|
case ASCII -> ConsoleIO.GlyphMode.GLYPH;
|
||||||
|
case GLYPH -> ConsoleIO.GlyphMode.UNICODE;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package thb.jeanluc.adventure.menu;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import thb.jeanluc.adventure.io.TestIO;
|
||||||
|
import thb.jeanluc.adventure.save.SaveService;
|
||||||
|
import thb.jeanluc.adventure.save.Settings;
|
||||||
|
import thb.jeanluc.adventure.save.SettingsStore;
|
||||||
|
import thb.jeanluc.adventure.io.ConsoleIO;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class MenuTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mainMenuMapsChoiceToAction() {
|
||||||
|
// options order: New Game(1) / Load(2) / Settings(3) / Quit(4)
|
||||||
|
assertThat(MainMenu.show(new TestIO().enqueue("1"))).isEqualTo(MenuAction.NEW_GAME);
|
||||||
|
assertThat(MainMenu.show(new TestIO().enqueue("2"))).isEqualTo(MenuAction.LOAD);
|
||||||
|
assertThat(MainMenu.show(new TestIO().enqueue("3"))).isEqualTo(MenuAction.SETTINGS);
|
||||||
|
assertThat(MainMenu.show(new TestIO().enqueue("4"))).isEqualTo(MenuAction.QUIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void promptNameUsesDefaultOnBlank() {
|
||||||
|
assertThat(MainMenu.promptName(new TestIO().enqueue(""), "Manor")).isEqualTo("Manor");
|
||||||
|
assertThat(MainMenu.promptName(new TestIO().enqueue("Spooky"), "Manor")).isEqualTo("Spooky");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void pickSlotReturnsNullWhenEmpty(@TempDir Path dir) {
|
||||||
|
TestIO io = new TestIO();
|
||||||
|
assertThat(MainMenu.pickSlot(io, new SaveService(dir))).isNull();
|
||||||
|
assertThat(io.allOutput()).contains("No saved games");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void settingsMenuTogglesGlyphAndPersists(@TempDir Path dir) {
|
||||||
|
SettingsStore store = new SettingsStore(dir.resolve("settings.json"));
|
||||||
|
// choose "Glyph mode" (assume option 2) → its toggle, then "Back" (last)
|
||||||
|
TestIO io = new TestIO().enqueue("2").enqueue("3");
|
||||||
|
Settings result = SettingsMenu.show(io, Settings.defaults(), store, new ConsoleIO());
|
||||||
|
// UNICODE default toggles to ASCII; persisted
|
||||||
|
assertThat(result.glyphMode()).isEqualTo(ConsoleIO.GlyphMode.ASCII);
|
||||||
|
assertThat(store.load().glyphMode()).isEqualTo(ConsoleIO.GlyphMode.ASCII);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user