250 lines
9.2 KiB
Java
250 lines
9.2 KiB
Java
package it.polimi.ingsw.gc14;
|
|
|
|
import it.polimi.ingsw.gc14.Controller.ClientController;
|
|
import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
|
|
import it.polimi.ingsw.gc14.Network.NetworkConfig;
|
|
import it.polimi.ingsw.gc14.Network.InterfaceResolver;
|
|
import it.polimi.ingsw.gc14.Network.RMI.Client.RMIClient;
|
|
import it.polimi.ingsw.gc14.Network.TCP.Client.TCPClient;
|
|
import it.polimi.ingsw.gc14.View.TUI.TUI;
|
|
|
|
import org.jline.builtins.Completers.TreeCompleter;
|
|
import org.jline.reader.*;
|
|
import org.jline.reader.impl.completer.StringsCompleter;
|
|
import org.jline.terminal.Terminal;
|
|
import org.jline.terminal.TerminalBuilder;
|
|
|
|
import java.io.IOException;
|
|
import java.io.PrintStream;
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
import static org.jline.builtins.Completers.TreeCompleter.node;
|
|
|
|
/**
|
|
* Entry point for the TUI-based game client.
|
|
* Handles login, connects to the server, and drives the JLine-powered command loop.
|
|
*/
|
|
public class ClientLauncherTUI {
|
|
|
|
private TUI view;
|
|
private Terminal terminal;
|
|
private String currentUsername = "";
|
|
|
|
/**
|
|
* Starts the TUI client. Creates a JLine terminal, loops through login → game → rematch.
|
|
*
|
|
* @throws InterruptedException if the thread is interrupted while waiting.
|
|
*/
|
|
public void main() throws InterruptedException {
|
|
try {
|
|
System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
|
|
System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8));
|
|
terminal = TerminalBuilder.builder()
|
|
.system(true)
|
|
.encoding(StandardCharsets.UTF_8)
|
|
.build();
|
|
} catch (IOException e) {
|
|
System.err.println("Could not initialise terminal: " + e.getMessage());
|
|
return;
|
|
}
|
|
terminal.writer().print("\033[H\033[2J");
|
|
terminal.writer().flush();
|
|
|
|
view = new TUI(null);
|
|
ClientController controller = new ClientController(view);
|
|
LineReader gameReader = buildGameReader();
|
|
view.setLineReader(gameReader);
|
|
|
|
while (true) {
|
|
if (!doLogin(controller)) continue;
|
|
|
|
while (controller.getClient() != null) {
|
|
String prompt = buildPrompt(controller);
|
|
String line;
|
|
try {
|
|
line = gameReader.readLine(prompt);
|
|
} catch (UserInterruptException e) {
|
|
safeQuit(controller);
|
|
return;
|
|
} catch (EndOfFileException e) {
|
|
safeQuit(controller);
|
|
return;
|
|
}
|
|
if (line == null || line.isBlank()) continue;
|
|
boolean rematch = handleCommand(line.trim(), controller);
|
|
if (rematch) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean doLogin(ClientController controller) {
|
|
LineReader loginReader = LineReaderBuilder.builder()
|
|
.terminal(terminal)
|
|
.completer(new StringsCompleter("rmi", "tcp"))
|
|
.build();
|
|
LineReader ipReader = LineReaderBuilder.builder()
|
|
.terminal(terminal)
|
|
.completer(new StringsCompleter("localhost"))
|
|
.build();
|
|
|
|
String username, networkStr, ip;
|
|
int nPlayers;
|
|
|
|
try {
|
|
username = loginReader.readLine("Username: ").trim();
|
|
nPlayers = Integer.parseInt(loginReader.readLine("Number of players [2-5]: ").trim());
|
|
networkStr = loginReader.readLine("Network [rmi/tcp]: ").trim().toLowerCase();
|
|
ip = ipReader.readLine("Server IP [localhost]: ").trim();
|
|
if (ip.isEmpty()) ip = "localhost";
|
|
} catch (UserInterruptException | EndOfFileException e) {
|
|
System.exit(0);
|
|
return false;
|
|
} catch (NumberFormatException e) {
|
|
System.out.println("Invalid number of players.");
|
|
return false;
|
|
}
|
|
|
|
currentUsername = username;
|
|
view.setUsername(username);
|
|
controller.setMyUsername(username);
|
|
|
|
if (networkStr.equals("rmi")) {
|
|
String myIP;
|
|
try {
|
|
myIP = InterfaceResolver.resolveLocalInterface(ip);
|
|
} catch (Exception e) {
|
|
System.out.println("Cannot resolve local interface: " + e.getMessage());
|
|
return false;
|
|
}
|
|
System.setProperty("java.rmi.server.hostname", myIP);
|
|
RMIClient client = new RMIClient(controller, ip, NetworkConfig.RMI_PORT, myIP);
|
|
ErrorType err = client.connect(username, nPlayers);
|
|
if (err != null) {
|
|
view.showError(err, err.toString());
|
|
return false;
|
|
}
|
|
System.out.println("Connected via RMI.");
|
|
controller.setClient(client);
|
|
|
|
} else if (networkStr.equals("tcp")) {
|
|
TCPClient client = new TCPClient(controller, ip, NetworkConfig.TCP_PORT, NetworkConfig.HEARTBEAT_PORT);
|
|
ErrorType err = client.connect(username, nPlayers);
|
|
if (err != null) {
|
|
view.showError(err, err.toString());
|
|
return false;
|
|
}
|
|
System.out.println("Connected via TCP.");
|
|
controller.setClient(client);
|
|
|
|
} else {
|
|
System.out.println("Unknown network type '" + networkStr + "'. Use rmi or tcp.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private LineReader buildGameReader() {
|
|
Completer completer = new TreeCompleter(
|
|
node("slot"),
|
|
node("draw",
|
|
node("upper",
|
|
node("tribe"),
|
|
node("building")),
|
|
node("lower",
|
|
node("tribe"),
|
|
node("building"))),
|
|
node("totem"),
|
|
node("skip"),
|
|
node("render"),
|
|
node("help"),
|
|
node("rematch"),
|
|
node("quit")
|
|
);
|
|
return LineReaderBuilder.builder()
|
|
.terminal(terminal)
|
|
.completer(completer)
|
|
.option(LineReader.Option.DISABLE_EVENT_EXPANSION, true)
|
|
.build();
|
|
}
|
|
|
|
private String buildPrompt(ClientController controller) {
|
|
return currentUsername.isEmpty() ? "> " : currentUsername + "> ";
|
|
}
|
|
|
|
/** Returns {@code true} when the caller should break out of the game loop (rematch requested). */
|
|
private boolean handleCommand(String line, ClientController controller) {
|
|
String[] parts = line.split("\\s+");
|
|
String cmd = parts[0].toLowerCase();
|
|
|
|
switch (cmd) {
|
|
case "slot" -> {
|
|
int pos = parsePos(parts, 1);
|
|
if (pos >= 0) controller.slotChoice(pos);
|
|
}
|
|
case "draw" -> handleDraw(parts, controller);
|
|
case "totem" -> {
|
|
int pos = parsePos(parts, 1);
|
|
if (pos >= 0) controller.totemChoice(pos);
|
|
}
|
|
case "skip" -> controller.skipTurn();
|
|
case "render" -> handleRender(parts);
|
|
case "help" -> view.renderHelp();
|
|
case "rematch" -> {
|
|
if (controller.miniModel == null
|
|
|| !controller.miniModel.currentState.getGameStage().equals(GameStages.ENDED)) {
|
|
System.out.println("Game not ended yet. Use 'quit' to disconnect.");
|
|
return false;
|
|
}
|
|
controller.disconnect();
|
|
controller.setClient(null);
|
|
controller.setModel(null);
|
|
return true;
|
|
}
|
|
case "quit" -> {
|
|
safeQuit(controller);
|
|
System.exit(0);
|
|
}
|
|
default -> view.showError(ErrorType.GENERIC_ERROR, "Unknown command: " + cmd);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void handleDraw(String[] parts, ClientController controller) {
|
|
if (parts.length < 4) {
|
|
view.showError(ErrorType.GENERIC_ERROR, "Usage: draw upper|lower tribe|building <pos>");
|
|
return;
|
|
}
|
|
boolean upper = parts[1].equalsIgnoreCase("upper");
|
|
boolean tribe = parts[2].equalsIgnoreCase("tribe");
|
|
int pos = parsePos(parts, 3);
|
|
if (pos < 0) return;
|
|
|
|
if (upper && tribe) controller.drawUpperTribeCard(pos);
|
|
else if (upper) controller.drawUpperBuildingCard(pos);
|
|
else if (tribe) controller.drawLowerTribeCard(pos);
|
|
else controller.drawLowerBuildingCard(pos);
|
|
}
|
|
|
|
private void handleRender(String[] parts) {
|
|
view.renderBoard();
|
|
}
|
|
|
|
private int parsePos(String[] parts, int idx) {
|
|
if (parts.length <= idx) {
|
|
view.showError(ErrorType.GENERIC_ERROR, "Missing position argument.");
|
|
return -1;
|
|
}
|
|
try {
|
|
return Integer.parseInt(parts[idx]);
|
|
} catch (NumberFormatException e) {
|
|
view.showError(ErrorType.GENERIC_ERROR,
|
|
"Invalid position '" + parts[idx] + "' — expected integer.");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
private void safeQuit(ClientController controller) {
|
|
if (controller.getClient() != null) controller.disconnect();
|
|
}
|
|
}
|