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