fix: EOF ends game loop (ConsoleIO returns null on EOF); settings.json excluded from save slots
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -71,10 +71,11 @@ public class ConsoleIO implements GameIO {
|
||||
out.print("> ");
|
||||
out.flush();
|
||||
try {
|
||||
String line = in.readLine();
|
||||
return line == null ? "" : line;
|
||||
// null propagates as true EOF (ends the game loop); "" stays "" (blank input re-prompts).
|
||||
return in.readLine();
|
||||
} catch (IOException e) {
|
||||
return "";
|
||||
// Treat a stream failure as EOF so input ends cleanly rather than spinning.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ import java.util.stream.Stream;
|
||||
@Slf4j
|
||||
public class SaveService {
|
||||
|
||||
private static final String SETTINGS_FILENAME = "settings.json";
|
||||
|
||||
private final Path dir;
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@@ -38,7 +40,11 @@ public class SaveService {
|
||||
/** Slug used for the on-disk filename of a slot name. */
|
||||
public static String slug(String slotName) {
|
||||
String s = slotName.trim().toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9_-]+", "_");
|
||||
return s.isBlank() ? "save" : s;
|
||||
if (s.isBlank()) {
|
||||
return "save";
|
||||
}
|
||||
// Never let a user-named save collide with the settings file.
|
||||
return s.equals("settings") ? "settings_" : s;
|
||||
}
|
||||
|
||||
/** Writes the session to {@code <slug>.json} atomically. */
|
||||
@@ -71,7 +77,10 @@ public class SaveService {
|
||||
}
|
||||
List<SaveSlotInfo> out = new ArrayList<>();
|
||||
try (Stream<Path> files = Files.list(dir)) {
|
||||
for (Path f : files.filter(p -> p.toString().endsWith(".json")).toList()) {
|
||||
for (Path f : files
|
||||
.filter(p -> p.toString().endsWith(".json"))
|
||||
.filter(p -> !p.getFileName().toString().equals(SETTINGS_FILENAME))
|
||||
.toList()) {
|
||||
try {
|
||||
SaveData d = mapper.readValue(f.toFile(), SaveData.class);
|
||||
String slug = f.getFileName().toString().replaceFirst("\\.json$", "");
|
||||
|
||||
@@ -50,6 +50,25 @@ class ConsoleIORenderTest {
|
||||
assertThat(out).contains("Kitchen").contains("turn 7").contains("light: off");
|
||||
}
|
||||
|
||||
@Test
|
||||
void readLine_returnsNullOnEof() {
|
||||
ByteArrayOutputStream sink = new ByteArrayOutputStream();
|
||||
PrintStream ps = new PrintStream(sink, true, StandardCharsets.UTF_8);
|
||||
ConsoleIO io = new ConsoleIO(new BufferedReader(new StringReader("")), ps,
|
||||
ConsoleIO.ColorMode.OFF, ConsoleIO.GlyphMode.UNICODE);
|
||||
assertThat(io.readLine()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void readLine_returnsEmptyStringForBlankLineThenNullAtEof() {
|
||||
ByteArrayOutputStream sink = new ByteArrayOutputStream();
|
||||
PrintStream ps = new PrintStream(sink, true, StandardCharsets.UTF_8);
|
||||
ConsoleIO io = new ConsoleIO(new BufferedReader(new StringReader("\n")), ps,
|
||||
ConsoleIO.ColorMode.OFF, ConsoleIO.GlyphMode.UNICODE);
|
||||
assertThat(io.readLine()).isEqualTo(""); // blank interactive line, re-prompt
|
||||
assertThat(io.readLine()).isNull(); // stream now exhausted → EOF
|
||||
}
|
||||
|
||||
@Test
|
||||
void asciiGlyphMode_usesPlusDashFrame() {
|
||||
ByteArrayOutputStream sink = new ByteArrayOutputStream();
|
||||
|
||||
@@ -55,6 +55,23 @@ class SaveServiceTest {
|
||||
assertThat(new SaveService(dir).list()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void listExcludesSettingsFile(@TempDir Path dir) throws Exception {
|
||||
SaveService svc = new SaveService(dir);
|
||||
GameSession s = freshSession("Real Save");
|
||||
svc.save(s);
|
||||
Files.writeString(dir.resolve("settings.json"), "{\"color\":\"on\"}");
|
||||
|
||||
List<SaveSlotInfo> slots = svc.list();
|
||||
assertThat(slots).hasSize(1);
|
||||
assertThat(slots.getFirst().slotName()).isEqualTo("Real Save");
|
||||
}
|
||||
|
||||
@Test
|
||||
void slugForSettingsDoesNotCollideWithSettingsFile() {
|
||||
assertThat(SaveService.slug("settings")).isNotEqualTo("settings");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadCorruptFileThrowsSaveException(@TempDir Path dir) throws Exception {
|
||||
Files.writeString(dir.resolve("bad.json"), "{ not json");
|
||||
|
||||
Reference in New Issue
Block a user