Fix: TUI initial refactor
This commit is contained in:
@@ -1,210 +1,248 @@
|
||||
package it.polimi.ingsw.gc14;
|
||||
|
||||
import it.polimi.ingsw.gc14.Controller.ClientController;
|
||||
import it.polimi.ingsw.gc14.Model.Cards.TribeCards.Character;
|
||||
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.NetworkEvent;
|
||||
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 java.net.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
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 static org.jline.builtins.Completers.TreeCompleter.node;
|
||||
|
||||
/**
|
||||
* Entry point for the TUI-based game client.
|
||||
* Handles the initial setup by asking the user for a username, the desired number of players,
|
||||
* and the preferred network protocol (RMI or TCP).
|
||||
* Once connected to the server, it continuously reads and dispatches user input to the controller.
|
||||
* Handles login, connects to the server, and drives the JLine-powered command loop.
|
||||
*/
|
||||
public class ClientLauncherTUI {
|
||||
|
||||
List<String> admissibleChar=new ArrayList<>();
|
||||
private TUI view;
|
||||
private Terminal terminal;
|
||||
private String currentUsername = "";
|
||||
|
||||
/**
|
||||
* The TUI view associated with this client.
|
||||
*/
|
||||
TUI view;
|
||||
|
||||
/**
|
||||
* Starts the TUI client.
|
||||
* Prompts the user for a username, the desired number of players, and the network protocol.
|
||||
* Attempts to connect to the server using either RMI or TCP depending on the selection.
|
||||
* If the connection is successful, enters a loop to continuously read and process user input.
|
||||
* 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 {
|
||||
terminal = TerminalBuilder.builder().system(true).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);
|
||||
admissibleChar.add("0");
|
||||
admissibleChar.add("1");
|
||||
admissibleChar.add("2");
|
||||
admissibleChar.add("3");
|
||||
admissibleChar.add("4");
|
||||
admissibleChar.add("5");
|
||||
admissibleChar.add("6");
|
||||
admissibleChar.add("7");
|
||||
admissibleChar.add("8");
|
||||
admissibleChar.add("A");
|
||||
admissibleChar.add("B");
|
||||
admissibleChar.add("C");
|
||||
admissibleChar.add("a");
|
||||
admissibleChar.add("b");
|
||||
admissibleChar.add("c");
|
||||
ClientController controller = new ClientController(view);
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
LineReader gameReader = buildGameReader();
|
||||
view.setLineReader(gameReader);
|
||||
|
||||
while (true) {
|
||||
System.out.println("Insert username: ");
|
||||
String username = scanner.next();
|
||||
view.setUsername(username);
|
||||
controller.setMyUsername(username);
|
||||
System.out.println("Insert preferred number of players: ");
|
||||
int proposedNumPlayers = scanner.nextInt();
|
||||
System.out.println("Select RMI[0] o TCP[1]: ");
|
||||
int networkType = scanner.nextInt();
|
||||
System.out.println("Insert server IP: ");
|
||||
String IP = scanner.next();
|
||||
if (!doLogin(controller)) continue;
|
||||
|
||||
// RMI
|
||||
if (networkType == 0) {
|
||||
// Connect
|
||||
String myIP;
|
||||
while (controller.getClient() != null) {
|
||||
String prompt = buildPrompt(controller);
|
||||
String line;
|
||||
try {
|
||||
myIP = InterfaceResolver.resolveLocalInterface(IP);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.setProperty("java.rmi.server.hostname", myIP);
|
||||
RMIClient client = new RMIClient(controller, IP, NetworkConfig.RMI_PORT, myIP);
|
||||
ErrorType serverResponse=client.connect(username, proposedNumPlayers);
|
||||
if (serverResponse==null) {
|
||||
System.out.println("Succesfully connected to RMI server\n\n");
|
||||
} else {
|
||||
System.out.println("RMI connection refused\n\n");
|
||||
controller.view.showError(serverResponse,serverResponse.toString());
|
||||
continue;
|
||||
}
|
||||
controller.setClient(client);
|
||||
while (controller.getClient() != null) {
|
||||
getInput(scanner, controller, username);
|
||||
}
|
||||
// TCP
|
||||
} else if (networkType == 1) {
|
||||
// Connect
|
||||
TCPClient client = new TCPClient(controller, IP, NetworkConfig.TCP_PORT, NetworkConfig.HEARTBEAT_PORT);
|
||||
ErrorType serverResponse=client.connect(username, proposedNumPlayers);
|
||||
if (serverResponse==null) {
|
||||
System.out.println("Succesfully connected to TCP server\n\n");
|
||||
} else {
|
||||
System.out.println("TCP connection refused\n\n");
|
||||
controller.view.showError(serverResponse,serverResponse.toString());
|
||||
continue;
|
||||
}
|
||||
controller.setClient(client);
|
||||
// Play
|
||||
while (controller.getClient() != null) {
|
||||
getInput(scanner, controller, username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single action from the user and dispatches it to the controller.
|
||||
* The action is identified by a string code. Most actions also require a position index
|
||||
* (e.g. the index of the card to draw from a list), which is read as a second input.
|
||||
* Actions that do not require a position (7, 8, 9, A, B, C) skip the position prompt.
|
||||
* <p>
|
||||
* Available actions:
|
||||
* <ul>
|
||||
* <li>{@code 0} - Choose a slot by index.</li>
|
||||
* <li>{@code 1} - Draw an upper building card by index.</li>
|
||||
* <li>{@code 2} - Draw an upper tribe card by index.</li>
|
||||
* <li>{@code 3} - Draw a lower building card by index.</li>
|
||||
* <li>{@code 4} - Draw a lower tribe card by index.</li>
|
||||
* <li>{@code 5} - Pick an optional tribe card by index.</li>
|
||||
* <li>{@code 6} - Pick an optional building card by index.</li>
|
||||
* <li>{@code 7} - Skip the optional card choice.</li>
|
||||
* <li>{@code 8} - Skip the upper draw.</li>
|
||||
* <li>{@code 9} - Skip the lower draw.</li>
|
||||
* <li>{@code A} - Render the full game view.</li>
|
||||
* <li>{@code B} - Render the board view.</li>
|
||||
* <li>{@code C} - Render the player view.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param scanner the scanner used to read user input.
|
||||
* @param controller the client controller to which actions are dispatched.
|
||||
* @param username the username of the current player.
|
||||
*/
|
||||
private void getInput(Scanner scanner, ClientController controller, String username) {
|
||||
String action = scanner.nextLine().trim();
|
||||
int pos = -1;
|
||||
if(controller.getClient() == null)
|
||||
{
|
||||
if(action.equals("0"))
|
||||
{
|
||||
System.out.println("Are you sure? Y/N");
|
||||
String c= scanner.next();
|
||||
if(c.equals("Y") || c.equals("y"))
|
||||
{
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(action.isEmpty())
|
||||
return;
|
||||
if(admissibleChar.contains(action))
|
||||
{
|
||||
if(action.equals("0"))
|
||||
{
|
||||
System.out.println("Are you sure? Y/N");
|
||||
String c= scanner.next();
|
||||
if(!c.equals("Y") && !c.equals("y"))
|
||||
{
|
||||
view.render();
|
||||
return;
|
||||
}
|
||||
}else if (!action.equals("6")&&!action.equals("8") && !action.equals("A") && !action.equals("B") && !action.equals("C")&&!action.equals("a") && !action.equals("b") && !action.equals("c")) {
|
||||
try {
|
||||
System.out.println("Insert the required position:");
|
||||
pos = scanner.nextInt();
|
||||
} catch (Exception e) {
|
||||
view.showError(ErrorType.GENERIC_ERROR,"Invalid input(expected number)");
|
||||
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;
|
||||
}
|
||||
switch (action) {
|
||||
case "1" -> controller.slotChoice(pos);
|
||||
case "2" -> controller.drawUpperTribeCard(pos);
|
||||
case "3" -> controller.drawUpperBuildingCard(pos);
|
||||
case "4" -> controller.drawLowerTribeCard(pos);
|
||||
case "5" -> controller.drawLowerBuildingCard(pos);
|
||||
case "6" -> controller.skipTurn();
|
||||
case "7" -> controller.totemChoice(pos);
|
||||
case "8" ->{
|
||||
if(controller.miniModel.currentState.getGameStage().equals(GameStages.ENDED)) {
|
||||
controller.disconnect();
|
||||
controller.setClient(null);
|
||||
controller.setModel(null);
|
||||
}
|
||||
}
|
||||
case "A", "a" -> view.fullRender();
|
||||
case "B", "b" -> view.renderBoard();
|
||||
case "C", "c" -> view.renderPlayer();
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
view.showError(ErrorType.GENERIC_ERROR,"Invalid input(expected number)");
|
||||
}
|
||||
}
|
||||
|
||||
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("full"),
|
||||
node("board"),
|
||||
node("players")),
|
||||
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 "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) {
|
||||
String sub = parts.length > 1 ? parts[1].toLowerCase() : "board";
|
||||
switch (sub) {
|
||||
case "full" -> view.fullRender();
|
||||
case "players" -> view.renderPlayer();
|
||||
default -> 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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user