semesterprojekt: implement full text adventure (phases 1-7)

Walking skeleton through Swing GUI: YAML-driven world (4 rooms,
4 items, 1 NPC), HashMap command dispatch with parser, three-tier
item hierarchy (readable / switchable / plain), and end-to-end
NPC give/receive flow. 67 tests green.
This commit is contained in:
2026-05-25 21:37:59 +02:00
parent 9b6528d800
commit 83643a192f
85 changed files with 4453 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
package thb.jeanluc.adventure.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
/**
* {@link GameIO} backed by {@code System.in} and {@code System.out}.
* Each {@code write} appends a newline so output blocks are visually
* separated. {@code readLine} prints a {@code > } prompt before
* blocking.
*/
public class ConsoleIO implements GameIO {
/** Input source. */
private final BufferedReader in;
/** Output sink. */
private final PrintStream out;
/**
* Creates a console IO using standard input and output.
*/
public ConsoleIO() {
this(new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)), System.out);
}
/**
* Test-friendly constructor allowing custom streams.
*
* @param in reader to consume player input
* @param out stream to write player output
*/
public ConsoleIO(BufferedReader in, PrintStream out) {
this.in = in;
this.out = out;
}
@Override
public String readLine() {
out.print("> ");
out.flush();
try {
String line = in.readLine();
return line == null ? "" : line;
} catch (IOException e) {
return "";
}
}
@Override
public void write(String s) {
out.println(s);
}
}

View File

@@ -0,0 +1,24 @@
package thb.jeanluc.adventure.io;
/**
* Bidirectional text channel between the engine and the player. Implemented
* by both {@code ConsoleIO} and {@code SwingIO} so the game loop is agnostic
* to the actual interface.
*/
public interface GameIO {
/**
* Blocks until a line of input is available.
*
* @return the next user input line, or an empty string at end of input
*/
String readLine();
/**
* Writes a message to the player. Each call corresponds to one logical
* output block; the implementation handles line breaks at the boundary.
*
* @param s message text; must not be null
*/
void write(String s);
}

View File

@@ -0,0 +1,85 @@
package thb.jeanluc.adventure.io;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.util.concurrent.LinkedBlockingQueue;
/**
* {@link GameIO} backed by a Swing window. A read-only output area shows
* game text; a bottom text field collects player input. The input from
* the EDT is handed to the (worker-thread) game loop through a
* {@link LinkedBlockingQueue}.
*/
public class SwingIO implements GameIO {
/** Bridge between the EDT and the game loop. One element = one line. */
private final LinkedBlockingQueue<String> inputs = new LinkedBlockingQueue<>();
/** Main window. */
private final JFrame frame;
/** Output text area; appended to from any thread via {@link SwingUtilities#invokeLater}. */
private final JTextArea output;
/** Input text field; ENTER pushes the line into {@link #inputs}. */
private final JTextField input;
/**
* Creates and shows the window with the given title. Must be called on
* the EDT (typical pattern: from {@code SwingUtilities.invokeLater}).
*
* @param title window title
*/
public SwingIO(String title) {
frame = new JFrame(title);
output = new JTextArea();
input = new JTextField();
output.setEditable(false);
output.setLineWrap(true);
output.setWrapStyleWord(true);
output.setBackground(Color.BLACK);
output.setForeground(Color.LIGHT_GRAY);
output.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
output.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
input.addActionListener(e -> {
String line = input.getText();
input.setText("");
output.append("> " + line + "\n");
inputs.offer(line);
});
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(output), BorderLayout.CENTER);
frame.add(input, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
input.requestFocusInWindow();
}
@Override
public String readLine() {
try {
return inputs.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "";
}
}
@Override
public void write(String s) {
SwingUtilities.invokeLater(() -> output.append(s + "\n"));
}
}