feat(io): GameIO.choose numbered-menu primitive (console default)

This commit is contained in:
2026-06-01 14:26:43 +02:00
parent c392f80ed0
commit 310ee65285
2 changed files with 68 additions and 0 deletions

View File

@@ -9,6 +9,8 @@ import thb.jeanluc.adventure.io.text.Renderings;
import thb.jeanluc.adventure.io.text.RoomView;
import thb.jeanluc.adventure.io.text.StyledText;
import java.util.List;
/**
* Bidirectional channel between the engine and the player. Commands emit
* semantic, role-styled output via {@link #print(StyledText)} (the primitive)
@@ -56,4 +58,38 @@ public interface GameIO {
default void showQuests(QuestView view) {
print(QuestText.render(view));
}
/**
* Presents a titled list of options and returns the 0-based index of the
* player's choice. Default (console) prints a numbered list and reads a
* number, re-prompting on invalid input. EOF / empty input returns the
* LAST option (menus place the safe/cancel option last). Frontends with
* native widgets (e.g. buttons) may override.
*
* @param title heading shown above the options
* @param options non-empty list of option labels
* @return 0-based index into {@code options}
*/
default int choose(String title, List<String> options) {
StyledText.Builder b = StyledText.builder().heading(title).plain("\n");
for (int i = 0; i < options.size(); i++) {
b.plain(" " + (i + 1) + ") " + options.get(i) + "\n");
}
print(b.build());
while (true) {
String line = readLine();
if (line == null || line.isBlank()) {
return options.size() - 1;
}
try {
int n = Integer.parseInt(line.trim());
if (n >= 1 && n <= options.size()) {
return n - 1;
}
} catch (NumberFormatException ignored) {
// fall through to re-prompt
}
write("Please enter a number between 1 and " + options.size() + ".");
}
}
}

View File

@@ -0,0 +1,32 @@
package thb.jeanluc.adventure.io;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class ChooseDefaultTest {
@Test
void returnsZeroBasedIndexOfValidChoice() {
TestIO io = new TestIO().enqueue("2");
int i = io.choose("Menu", List.of("A", "B", "C"));
assertThat(i).isEqualTo(1);
}
@Test
void rePromptsOnInvalidThenAccepts() {
TestIO io = new TestIO().enqueue("9").enqueue("x").enqueue("1");
int i = io.choose("Menu", List.of("A", "B"));
assertThat(i).isZero();
assertThat(io.allOutput()).contains("between 1 and 2");
}
@Test
void eofReturnsLastOption() {
TestIO io = new TestIO(); // empty queue → readLine() returns null
int i = io.choose("Menu", List.of("Play", "Quit"));
assertThat(i).isEqualTo(1);
}
}