feat(io): GameIO.choose numbered-menu primitive (console default)
This commit is contained in:
@@ -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() + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user