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:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user