Add: client chat architecture for lobby and global chat

This commit is contained in:
Mathis Ginkel
2026-04-02 18:11:28 +02:00
parent 87e9305e82
commit 7d19e65428
17 changed files with 533 additions and 283 deletions
@@ -1,62 +1,58 @@
package ch.unibas.dmi.dbis.cs108.casono.client.chat; package ch.unibas.dmi.dbis.cs108.casono.client.chat;
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* responsible for the transferring of messages from the server to the ChatModel * responsible for the transferring of messages from the server to the ChatModel or from the
* or from the ChatViewController to the server * ChatViewController to the server
*/ */
public class ChatController { public class ChatController {
private final String username; private final String username;
private final ClientService clientService; private final ClientService clientService;
private ChatModel chatModel; private final ArrayList<ChatModel> chatModelArrayList;
private int game_id;
public ChatController(String username, ClientService clientService) { public ChatController(String username, ClientService clientService) {
this.username = username; this.username = username;
this.clientService = clientService; this.clientService = clientService;
chatModelArrayList = new ArrayList<>();
chatModelArrayList.add(new ChatModel(ChatType.GLOBAL, username));
} }
public void createChat(int game_id) { public void createLobbyChat(int lobbyId, ChatType chatType) {
chatModel = new ChatModel(ChatModel.ChatType.GLOBAL, username); ChatModel chatModel = new ChatModel(chatType, username, lobbyId);
this.game_id = game_id; chatModelArrayList.add(chatModel);
} }
public ChatModel getChatModel() { /** method to get all messages from the server */
return chatModel;
}
/**
* method to send a message, the ChatViewController received to the server
*/
public void sendMessage(String msg, String username) {
Message message = new Message(Message.MessageType.GLOBAL, 0, username, null, msg);
onSendToNetwork(message);
}
/**
* method to get all messages from the server
*/
public Boolean receiveMessage() { public Boolean receiveMessage() {
List<Message> newMessages = clientService.getMessages(); List<Message> newMessages = clientService.getMessages();
if (!newMessages.isEmpty()) { if (!newMessages.isEmpty()) {
for (Message msg : newMessages) { for (Message msg : newMessages) {
switch (msg.getMessageType()) {
case ChatType.GLOBAL:
chatModelArrayList.get(0).addMessage(msg);
case ChatType.LOBBY:
ChatModel chatModel = chatModelArrayList.get(1);
if (chatModel != null && chatModel.lobbyId == msg.lobbyId) {
chatModel.addMessage(msg); chatModel.addMessage(msg);
} }
case ChatType.WHISPER:
// TODO: Check, if target person is user and if yes, iterate through all
// whisper chats.
}
}
return true; return true;
} else { return false; } } else {
return false;
}
} }
/** /** method to send a message to the server */
* method to send a message to the server public void sendMessageToNetwork(Message message) {
* @param message
*/
public void onSendToNetwork(Message message) {
clientService.sendMessage(message); clientService.sendMessage(message);
} }
} }
@@ -5,31 +5,26 @@ import java.util.ArrayList;
/** /**
* ChatModel, stores the data for a specific chat * ChatModel, stores the data for a specific chat
* *
* Holds the current state of a chat * <p>Holds the current state of a chat
*/ */
public class ChatModel { public class ChatModel {
public ArrayList<Message> messages; public ArrayList<Message> messages;
public ChatType chattype; private ChatType chattype;
public String username; public String username;
public int count; public int count;
public enum ChatType { public int lobbyId;
GLOBAL,
LOBBY,
WHISPER
}
/** /**
* Creates a new ChatModel, given a username of the client * Creates a new ChatModel, given a username of the client
*
* @param chattype * @param chattype
* @param username * @param username
*/ */
public ChatModel(ChatType chattype, String username) { public ChatModel(ChatType chattype, String username) {
this.messages = new ArrayList<Message>(); this.messages = new ArrayList<Message>();
this.chattype = chattype; this.chattype = chattype;
@@ -37,8 +32,21 @@ public class ChatModel {
this.count = 0; this.count = 0;
} }
public ChatModel(ChatType chattype, String username, int lobbyId) {
this.messages = new ArrayList<Message>();
this.chattype = chattype;
this.username = username;
this.count = 0;
this.lobbyId = lobbyId;
}
public ChatType getChattype() {
return chattype;
}
/** /**
* method, used by the ChatViewController, to access all new messages, that are stored in the ChatModel * method, used by the ChatViewController, to access all new messages, that are stored in the
* ChatModel
*/ */
public synchronized String viewNextMessage() { public synchronized String viewNextMessage() {
count--; count--;
@@ -47,8 +55,8 @@ public class ChatModel {
} }
/** /**
* Adds a new message * Adds a new message method used by the ChatController
* method used by the ChatController *
* @param msg * @param msg
*/ */
public synchronized void addMessage(Message msg) { public synchronized void addMessage(Message msg) {
@@ -56,9 +64,7 @@ public class ChatModel {
count++; count++;
} }
/** /** method to send all current messages to the ChatViewController, if needed */
* method to send all current messages to the ChatViewController, if needed
*/
public void addCompleteChat() { public void addCompleteChat() {
for (int i = 0; i < this.messages.size(); i++) { for (int i = 0; i < this.messages.size(); i++) {
Message msg = this.messages.get(i); Message msg = this.messages.get(i);
@@ -0,0 +1,7 @@
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
public enum ChatType {
GLOBAL,
LOBBY,
WHISPER
}
@@ -6,33 +6,35 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* Message Object for internal handling of Chat-Messages * Message Object for internal handling of Chat-Messages TODO: Should be used on both sides of the
* TODO: Should be used on both sides of the network * network
*/ */
public class Message { public class Message {
private final MessageType type; private final ChatType type;
private final String message; private final String message;
public String user; public String user;
public String timestamp; public String timestamp;
public int game_id = 0; public int lobbyId = 0;
public String target = null; public String target = null;
public enum MessageType {
GLOBAL, LOBBY, WHISPER
};
/** /**
* Constructor for creating Messages with all information given * Constructor for creating Messages with all information given
*
* @param type - Either global, local or whisper * @param type - Either global, local or whisper
* @param game_id - lobby id, or null, if the type is global * @param lobbyId - lobby id, or null, if the typChatType
* @param user - username * @param user - username
* @param target - username of the target user, for whisper chat * @param target - username of the target user, for whisper chat
* @param message * @param message
*/ */
public Message(
public Message(MessageType type, int game_id, String user, String target, String timestamp, String message) { ChatType type,
int lobbyId,
String user,
String target,
String timestamp,
String message) {
this.type = type; this.type = type;
this.game_id = game_id; this.lobbyId = lobbyId;
this.user = user; this.user = user;
this.target = target; this.target = target;
this.timestamp = timestamp; this.timestamp = timestamp;
@@ -40,17 +42,18 @@ public class Message {
} }
/** /**
* Constructor for creating the Messages of the current user, using this client -> time of writing is being recorded * Constructor for creating the Messages of the current user, using this client -> time of
* writing is being recorded
*
* @param type - Either global, local or whisper * @param type - Either global, local or whisper
* @param game_id - lobby id, or null, if the type is global * @param lobbyId - lobby id, or null, if the type is global
* @param user - username * @param user - username
* @param target - username of the target user, for whisper chat * @param target - username of the target user, for whisper chat
* @param message * @param message
*/ */
public Message(ChatType type, int lobbyId, String user, String target, String message) {
public Message(MessageType type, int game_id, String user, String target, String message) {
this.type = type; this.type = type;
this.game_id = game_id; this.lobbyId = lobbyId;
this.user = user; this.user = user;
this.target = target; this.target = target;
this.message = message; this.message = message;
@@ -59,41 +62,40 @@ public class Message {
this.timestamp = now.format(formatter); this.timestamp = now.format(formatter);
} }
public String getMessage() { public String getMessage() {
return message; return message;
} }
public MessageType getMessageType() { public ChatType getMessageType() {
return type; return type;
} }
/*
* Method to test the system
* @return - String representation of the Message instance, as for example "player1: Hello World"
public String toString() {
return String.format("%s: %s", this.user, this.message);
}
*/
/** /**
* Method to create the request representation of the message object, to be sent to the server * Method to create the request representation of the message object, to be sent to the server
*
* @return - request as specified in the network protocol, as String * @return - request as specified in the network protocol, as String
*/ */
public String toArgsString() { public String toArgsString() {
return String.format("TYPE=%s GAME=%d USER=%s TARGET=%s TIME=%s TEXT=%s", return String.format(
this.type.toString(), this.game_id, this.user, this.target, this.timestamp, this.message); "TYPE=%s GAME=%d USER=%s TARGET=%s TIME=%s TEXT=%s",
this.type.toString(),
this.lobbyId,
this.user,
this.target,
this.timestamp,
this.message);
} }
/** Pattern, to analyze the response String with the given parameters */
/** public static Pattern msgRex =
* Pattern, to analyze the response String with the given parameters Pattern.compile(
*/ "TYPE=(?<type>\\w+) " + "GAME=(?<game>\\w+) " +
public static Pattern msgRex = Pattern.compile( "USER=(?<user>\\w+) " + "TARGET=(?<target>\\w+) " +
"TYPE=(?<type>\\w+) GAME=(?<game>\\w+) USER=(?<user>\\w+) TARGET=(?<target>\\w+) TIME=(?<time>[0-9:.]+) TEXT=(?<text>.*)$"); "TIME=(?<time>[0-9:.]+) " + "TEXT=(?<text>.*)$");
/** /**
* Method to create a Message Object, from the information given by the String * Method to create a Message Object, from the information given by the String
*
* @param response - String that got sent as a response from the server * @param response - String that got sent as a response from the server
* @return - New Message Object * @return - New Message Object
*/ */
@@ -106,7 +108,8 @@ public class Message {
switch (typeString) { switch (typeString) {
case "GLOBAL": case "GLOBAL":
return new Message(MessageType.GLOBAL, return new Message(
ChatType.GLOBAL,
0, 0,
m.group("user"), m.group("user"),
null, null,
@@ -114,7 +117,8 @@ public class Message {
m.group("text")); m.group("text"));
case "LOBBY": case "LOBBY":
return new Message(MessageType.LOBBY, return new Message(
ChatType.LOBBY,
Integer.parseInt(m.group("game")), Integer.parseInt(m.group("game")),
m.group("user"), m.group("user"),
null, null,
@@ -122,7 +126,8 @@ public class Message {
m.group("text")); m.group("text"));
case "WHISPER": case "WHISPER":
return new Message(MessageType.WHISPER, return new Message(
ChatType.WHISPER,
Integer.parseInt(m.group("game")), Integer.parseInt(m.group("game")),
m.group("user"), m.group("user"),
m.group("target"), m.group("target"),
@@ -1,9 +1,11 @@
package ch.unibas.dmi.dbis.cs108.casono.client.network; package ch.unibas.dmi.dbis.cs108.casono.client.network;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager;
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket; import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket;
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport; import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -11,18 +13,13 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
/** /**
* Responsible for the transferring of the data from the Client to the Server and the other way around * Responsible for the transferring of the data from the Client to the Server and the other way
* around
*/ */
public class ClientService { public class ClientService {
private final TcpTransport clienttcptransport; private final TcpTransport clienttcptransport;
@@ -32,57 +29,60 @@ public class ClientService {
public static ArrayList<String> response; public static ArrayList<String> response;
private final AtomicInteger idGenerator; private final AtomicInteger idGenerator;
private final Logger logger;
/** /**
* Creates a new ClientSession with a Socket, a Reader and Writer of the Input- and the Outputstream and a pool of threads to send requests and receive responses. * Creates a new ClientSession with a Socket, a Reader and Writer of the Input- and the
* Outputstream and a pool of threads to send requests and receive responses.
*
* @param ip : ip-adress of the server * @param ip : ip-adress of the server
* @param port : port of the server * @param port : port of the server
*/ */
public ClientService(String ip, int port) { public ClientService(String ip, int port) {
this.idGenerator = new AtomicInteger(0); this.idGenerator = new AtomicInteger(0);
this.logger = LogManager.getLogger(ClientService.class);
try { try {
socket = new Socket(ip, port); socket = new Socket(ip, port);
clienttcptransport = new TcpTransport(socket); clienttcptransport = new TcpTransport(socket);
} logger.info("Connected to server at " + ip);
catch (IOException i) { } catch (IOException i) {
throw new RuntimeException(i); throw new RuntimeException(i);
} }
executor = Executors.newSingleThreadExecutor(); executor = Executors.newSingleThreadExecutor();
} }
/** /**
* Sends a ping to the server * Sends a ping to the server Returns nothing, the method processMessage() already handles the
* Returns nothing, the method processMessage() already handles the cases "+OK" or "-ERROR" * cases "+OK" or "-ERROR"
*/ */
public void ping() { public void ping() {
processMessage("PING"); processMessage("PING");
} }
/** /**
* Sends a login request to the server to create a new user on the server * Sends a login request to the server to create a new user on the server
*
* @param username * @param username
* @return - a new username, if the same username is already used by someone else * @return - a new username, if the same username is already used by someone else
*/ */
public String login(String username) { public String login(String username) {
String msg = "LOGIN USERNAME=" + username; String msg = "LOGIN USERNAME=" + username;
return processMessage(msg); return processMessage(msg);
} }
/** /**
* Send a Request to get the number of Messages currently in the Queue for the client. * Send a Request to get the number of Messages currently in the Queue for the client. Then
* Then proceeds, if needed, to get the messages by sending * proceeds, if needed, to get the messages by sending
*/ */
public List<Message> getMessages() { public List<Message> getMessages() {
String countStr = processMessage("GET_MESSAGE_COUNT"); String countStr = processMessage("GET_MESSAGE_COUNT");
int count = Integer.parseInt(countStr); int count = Integer.parseInt(countStr);
System.out.println("Got " + count + " messages"); logger.info("Got " + count + " messages");
List<Message> messages = new ArrayList<>(); List<Message> messages = new ArrayList<>();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
String message = processMessage("GET_NEXT_MESSAGE"); String message = processMessage("GET_NEXT_MESSAGE");
@@ -95,34 +95,33 @@ public class ClientService {
} }
/** /**
* Sends a Request to the Server containing all relevant information of the message the client wrote. * Sends a Request to the Server containing all relevant information of the message the client
* wrote.
*
* @param message * @param message
* @return
*/ */
public void sendMessage(Message message) { public void sendMessage(Message message) {
String request = "SEND_MESSAGE " + message.toArgsString(); String request = "SEND_MESSAGE " + message.toArgsString();
processMessage(request); processMessage(request);
} }
/** /**
* Sends the Requests to the server and waits for the response * Sends the Requests to the server and waits for the response If the response is "+OK" it
* If the response is "+OK" it proceeds normal * proceeds normal If the response "-ERROR" it throws a runtime exception
* If the response "-ERROR" it throws a runtime exception *
* @param message * @param message
* @return - The response as a string, if it has to be returned * @return - The response as a string, if it has to be returned (+OK will not be returned)
* (+OK will not be returned)
*/ */
private String processMessage(String message) { private String processMessage(String message) {
AtomicReference<String> response = new AtomicReference<>(); AtomicReference<String> response = new AtomicReference<>();
sendRequest(() -> { sendRequest(
() -> {
try { try {
writeToTransport(message); writeToTransport(message);
String responseLine = null; String responseLine = null;
do { do {
responseLine = clienttcptransport.read().payload(); responseLine = clienttcptransport.read().payload();
System.out.println("Raw message '" + responseLine + "'"); logger.info("Raw message '" + responseLine + "'");
if ("+OK".equals(responseLine)) { if ("+OK".equals(responseLine)) {
return; return;
} else if (("-ERROR").equals(responseLine)) { } else if (("-ERROR").equals(responseLine)) {
@@ -138,10 +137,8 @@ public class ClientService {
} }
/** /**
*
* @param request * @param request
*/ */
private void sendRequest(Runnable request) { private void sendRequest(Runnable request) {
Future<?> future = executor.submit(request); Future<?> future = executor.submit(request);
try { try {
@@ -155,10 +152,10 @@ public class ClientService {
/** /**
* Returns a Runtime Exceptions thrown by the sendRequest method * Returns a Runtime Exceptions thrown by the sendRequest method
*
* @param e - an Exception * @param e - an Exception
* @return - a Runtime Exception * @return - a Runtime Exception
*/ */
private static RuntimeException getRuntimeException(Exception e) { private static RuntimeException getRuntimeException(Exception e) {
Throwable reason = e.getCause(); Throwable reason = e.getCause();
RuntimeException re; RuntimeException re;
@@ -171,30 +168,26 @@ public class ClientService {
return re; return re;
} }
/** /** Closes the Socket and shuts down the Threadpool associated with that Socket-Connection. */
* Closes the Socket and shuts down the Threadpool associated with that Socket-Connection.
*/
public void closeSocket() { public void closeSocket() {
try { try {
executor.shutdown(); executor.shutdown();
clienttcptransport.close(); clienttcptransport.close();
socket.close(); socket.close();
logger.info("Socket closed");
} catch (IOException j) { } catch (IOException j) {
System.out.println(j); logger.debug(j);
} }
} }
/** /**
* Method to write with the tcp transport to the server * Method to write with the tcp transport to the server
*
* @param s - Message to be sent * @param s - Message to be sent
* @throws IOException * @throws IOException
*/ */
private void writeToTransport(String s) throws IOException { private void writeToTransport(String s) throws IOException {
int id = this.idGenerator.incrementAndGet(); int id = this.idGenerator.incrementAndGet();
this.clienttcptransport.write(new RawPacket(id, s)); this.clienttcptransport.write(new RawPacket(id, s));
} }
} }
@@ -2,36 +2,43 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel;
import javafx.fxml.FXML; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatType;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import javafx.fxml.FXML;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.VBox;
/** /** Responsible for the presentation of the ChatModel to the Client */
* Responsible for the presentation of the ChatModel to the Client
*/
public class ChatViewController { public class ChatViewController {
private final ChatModel chatmodel; private final GlobalChatView globalChatView;
private LobbyChatView lobbyChatView;
private final String username; private final String username;
private final ChatController controller; private final ChatController controller;
private final Timer timer; private final Timer timer;
@FXML private VBox chatVBox; private Boolean lobbyActivated;
@FXML private TextField inputField; @FXML private VBox whisperChatVBox;
@FXML private ScrollPane chatScrollPane; @FXML private VBox whisperChat;
@FXML private Button sendButton; @FXML private ToggleButton changeGlobalChatButton;
@FXML private Button refreshButton; @FXML private ToggleButton changeLobbyChatButton;
@FXML private ToggleButton changeWhisperChatButton;
@FXML private ButtonBar buttonBar;
private static final int REFRESH_TIME = 1000;
private static final int CHAT_PADDING = 20; private static final int CHAT_PADDING = 20;
@@ -39,49 +46,63 @@ public class ChatViewController {
this(null, null, null); this(null, null, null);
} }
public ChatViewController(String username, ChatModel chatmodel, ChatController controller) { public ChatViewController(
String username, ChatModel globalChatModel, ChatController controller) {
this.username = username; this.username = username;
this.chatmodel = chatmodel;
this.controller = controller; this.controller = controller;
this.globalChatView =
new GlobalChatView(username, new ChatModel(ChatType.GLOBAL, username), controller);
this.timer = new Timer(); this.timer = new Timer();
timer.schedule(new TimerTask() { timer.schedule(
new TimerTask() {
@Override @Override
public void run() { public void run() {
System.err.println("tick");
if (controller.receiveMessage()) { if (controller.receiveMessage()) {
showMessage(); globalChatView.showGlobalMessage();
if (lobbyActivated) {
lobbyChatView.showLobbyMessage();
} }
} }
}, 0, 1000); }
},
0,
REFRESH_TIME);
} }
@FXML @FXML
public void initialize() { public void initialize() {}
inputField.setOnAction(event -> sendMessage());
sendButton.setOnAction(event -> sendMessage()); @FXML
refreshButton.setOnAction(event -> showMessage()); public void setLobbyChat(int lobbyId) {
chatScrollPane.vvalueProperty().bind(chatVBox.heightProperty()); this.lobbyChatView =
new LobbyChatView(
username, new ChatModel(ChatType.LOBBY, username), controller, lobbyId);
changeGlobalChatButton.setOnAction(event -> switchChat("globalChatButton"));
changeLobbyChatButton.setOnAction(event -> switchChat("lobbyChatButton"));
changeWhisperChatButton.setOnAction(event -> switchChat("whisperChatButton"));
ToggleGroup toggleGroup = new ToggleGroup();
changeGlobalChatButton.setToggleGroup(toggleGroup);
changeLobbyChatButton.setToggleGroup(toggleGroup);
changeWhisperChatButton.setToggleGroup(toggleGroup);
this.lobbyActivated = true;
} }
public void showMessage() { @FXML
while (chatmodel.count > 0) { public void switchChat(String button) {
String msg = chatmodel.viewNextMessage(); switch (button) {
Label label = new Label(msg); case "globalChatButton":
label.getStyleClass().add("info-text"); globalChatView.setVisibility(true);
label.setWrapText(true); lobbyChatView.setVisibility(false);
label.maxWidthProperty().bind(chatVBox.widthProperty().subtract(CHAT_PADDING));
chatVBox.getChildren().add(label);
}
} case "lobbyChatButton":
globalChatView.setVisibility(false);
lobbyChatView.setVisibility(true);
public void sendMessage() { case "whisperChatButton":
String message = inputField.getText().trim(); globalChatView.setVisibility(false);
if (!message.isEmpty()) { lobbyChatView.setVisibility(false);
inputField.clear();
this.controller.sendMessage(message, username);
} }
} }
public void endController() { timer.cancel(); }
} }
@@ -0,0 +1,69 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatType;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
public class GlobalChatView {
private final String username;
private ChatModel globalChatModel;
private final ChatController controller;
@FXML private Button globalChatSendButton;
@FXML private TextField globalChatInputField;
@FXML private VBox globalChatVBox;
@FXML private VBox globalChat;
@FXML private ScrollPane globalChatScrollPane;
private static final int CHAT_PADDING = 20;
public GlobalChatView(String username, ChatModel globalChatModel, ChatController controller) {
this.username = username;
this.controller = controller;
this.globalChatModel = globalChatModel;
}
@FXML
public void initialize() {
globalChatInputField.setOnAction(event -> sendGlobalMessage());
globalChatSendButton.setOnAction(event -> sendGlobalMessage());
globalChatScrollPane.vvalueProperty().bind(globalChatVBox.heightProperty());
}
public void sendGlobalMessage() {
String message = globalChatInputField.getText().trim();
if (!message.isEmpty()) {
globalChatInputField.clear();
Message msg = new Message(ChatType.GLOBAL, 0, username, null, message);
controller.sendMessageToNetwork(msg);
}
}
public void showGlobalMessage() {
for (int i = 0; i < globalChatModel.count; i++) {
String msg = globalChatModel.viewNextMessage();
Label label = new Label(msg);
label.getStyleClass().add("info-text");
label.setWrapText(true);
label.maxWidthProperty().bind(globalChatVBox.widthProperty().subtract(CHAT_PADDING));
globalChatVBox.getChildren().add(label);
}
}
public void setVisibility(Boolean bool) {
globalChatVBox.setVisible(bool);
}
}
@@ -0,0 +1,72 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatType;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
public class LobbyChatView {
private final String username;
private final int lobbyId;
private ChatModel lobbyChatModel;
private final ChatController controller;
@FXML private Button lobbyChatSendButton;
@FXML private TextField lobbyChatInputField;
@FXML private VBox lobbyChatVBox;
@FXML private VBox lobbyChat;
@FXML private ScrollPane lobbyChatScrollPane;
private static final int CHAT_PADDING = 20;
public LobbyChatView(
String username, ChatModel lobbyChatModel, ChatController controller, int lobbyId) {
this.username = username;
this.controller = controller;
this.lobbyChatModel = lobbyChatModel;
this.lobbyId = lobbyId;
}
@FXML
public void initialize() {
lobbyChatInputField.setOnAction(event -> sendLobbyMessage());
lobbyChatSendButton.setOnAction(event -> sendLobbyMessage());
lobbyChatScrollPane.vvalueProperty().bind(lobbyChatVBox.heightProperty());
}
public void sendLobbyMessage() {
String message = lobbyChatInputField.getText().trim();
if (!message.isEmpty()) {
lobbyChatInputField.clear();
Message msg = new Message(ChatType.LOBBY, lobbyId, username, null, message);
controller.sendMessageToNetwork(msg);
}
}
public void showLobbyMessage() {
for (int i = 0; i < lobbyChatModel.count; i++) {
String msg = lobbyChatModel.viewNextMessage();
Label label = new Label(msg);
label.getStyleClass().add("info-text");
label.setWrapText(true);
label.maxWidthProperty().bind(lobbyChatVBox.widthProperty().subtract(CHAT_PADDING));
lobbyChatVBox.getChildren().add(label);
}
}
public void setVisibility(Boolean bool) {
lobbyChatVBox.setVisible(bool);
}
}
@@ -0,0 +1,3 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
public class WhisperChatView {}
@@ -5,7 +5,7 @@
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.image.*?> <?import javafx.scene.image.*?>
<!-- Main container: Links the UI to the CasinoGameController and loads Casinogameui.css --> <!-- Hauptcontainer: Verknüpft die UI mit dem CasinoGameController und lädt Casinogameui.css -->
<AnchorPane xmlns="http://javafx.com/javafx/21" <AnchorPane xmlns="http://javafx.com/javafx/21"
xmlns:fx="http://javafx.com/fxml/1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameController" fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameController"
@@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<!--
This window will later be linked to the server requests,
to display messages from other players and system messages in real time.
The chat internally distinguishes between:
- Player-to-Player (Private 2-person chat)
- Lobby Chat (Current room)
- Global Chat (Entire server)
Features planned for later implementation:
- Stylish chat bubbles with names and timestamps.
-->
<VBox xmlns="http://javafx.com/javafx/21"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatController"
alignment="CENTER"
stylesheets="@Chatui.css">
<!-- Inner spacing: Creates 30 pixels of space at the top, right and bottom, only 10 pixels on the left (asymmetrical) -->
<padding>
<Insets top="30" right="30" bottom="30" left="10"/>
</padding>
<children>
<VBox VBox.vgrow="ALWAYS"
styleClass="chat-box"
spacing="10"
maxWidth="Infinity">
<children>
<Label text="== CHAT ==" styleClass="chat-header" alignment="CENTER" maxWidth="Infinity"/>
<Region minHeight="4" styleClass="chat-separator"/>
<!-- Chat messages will be added here -->
<ScrollPane fx:id="chatScrollPane" fitToWidth="true" vbarPolicy="AS_NEEDED" hbarPolicy="NEVER" VBox.vgrow="ALWAYS" styleClass="chat-scroll-pane">
<content>
<VBox fx:id="chatVBox" spacing="10" styleClass="chat-VBox"/>
</content>
</ScrollPane>
<!-- Input area -->
<HBox spacing="10">
<!-- TODO: Dynamically adjust the size of the text field -->
<TextField fx:id="inputField" HBox.hgrow="ALWAYS" promptText="Nachricht eingeben..." styleClass="gray-input-field" onAction="#sendMessage"/>
<Button fx:id="sendButton" text="SENDEN" onAction="#sendMessage" styleClass="yellow-button"/>
</HBox>
</children>
</VBox>
</children>
</VBox>
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<!--
Dieses Fenster wird später mit den Serveranfragen verknüpft,
um Nachrichten von Mitspielern und Systemmeldungen in Echtzeit anzuzeigen.
Der Chat unterscheidet intern zwischen:
- Player-to-Player (Privater 2-Personen-Chat)
- Lobby-Chat (Aktueller Raum)
- Globaler Chat (Gesamter Server)
Features, die später implementiert werden sollen:
- Schicke Chat-Bubbles mit Namen und Uhrzeit.
-->
<VBox xmlns="http://javafx.com/javafx/21"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatViewController"
alignment="CENTER"
stylesheets="@chatui.css">
<!-- Innenabstand: Schafft oben, rechts und unten 30 Pixel Platz, links nur 10 Pixel (asymmetrisch) -->
<padding>
<Insets top="30" right="30" bottom="30" left="10"/>
</padding>
<children>
<fx:include source="globalchat.fxml"/>
<fx:include source="lobbychat.fxml"/>
<fx:include source="whisperchat.fxml"/>
<ButtonBar fx:id="buttonBar">
<padding>
<Insets>...</Insets>
</padding>
<buttons>
<ToggleButton fx:id="changeGlobalChatButton" text="Global Chat" onAction="#" styleClass="yellow-button"/>
<ToggleButton fx:id="changeLobbyChatButton" text="Lobby Chat" onAction="#" styleClass="yellow-button"/>
<ToggleButton fx:id="changeWhisperChatButton" text="Whisper Chat" onAction="#" styleClass="yellow-button"/>
</buttons>
</ButtonBar>
</children>
</VBox>
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!-- VBox for the global chat, should only be visible when activated via changeGlobalChatButton-->
<VBox fx:id="globalChatVBox"
VBox.vgrow="ALWAYS"
styleClass="chat-box"
spacing="10"
maxWidth="Infinity"
visible="true"
fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.GlobalChatView">
<children>
<Label text="== GLOBAL CHAT ==" styleClass="chat-header" alignment="CENTER" maxWidth="Infinity"/>
<Region minHeight="4" styleClass="chat-separator"/>
<!-- Chatnachrichten werden hier hinzugefügt -->
<ScrollPane fx:id="globalChatScrollPane" fitToWidth="true" vbarPolicy="AS_NEEDED" hbarPolicy="NEVER"
VBox.vgrow="ALWAYS" styleClass="chat-scroll-pane">
<content>
<VBox fx:id="globalChat" spacing="10" styleClass="chat-VBox"/>
</content>
</ScrollPane>
<!-- Eingabebereich -->
<HBox spacing="10">
<!-- TODO: Größe des TextFields dynamisch anpassen -->
<TextField fx:id="globalChatInputField" HBox.hgrow="ALWAYS" promptText="Nachricht eingeben..."
styleClass="gray-input-field" onAction="#sendGlobalMessage"/>
<Button fx:id="globalChatSendButton" text="SENDEN" onAction="#sendGlobalMessage"
styleClass="yellow-button"/>
</HBox>
</children>
</VBox>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!-- VBox for the lobby chat, should only be visible when activated via changeLobbyChatButton-->
<VBox fx:id="lobbyChatVBox"
VBox.vgrow="ALWAYS"
styleClass="chat-box"
spacing="10"
maxWidth="Infinity"
visible="false"
fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.LobbyChatView">
<children>
<Label text="== LOBBY CHAT ==" styleClass="chat-header" alignment="CENTER" maxWidth="Infinity"/>
<Region minHeight="4" styleClass="chat-separator"/>
<!-- Chatnachrichten werden hier hinzugefügt -->
<ScrollPane fx:id="lobbyChatScrollPane" fitToWidth="true" vbarPolicy="AS_NEEDED" hbarPolicy="NEVER" VBox.vgrow="ALWAYS" styleClass="chat-scroll-pane">
<content>
<VBox fx:id="lobbyChat" spacing="10" styleClass="chat-VBox"/>
</content>
</ScrollPane>
<!-- Eingabebereich -->
<HBox spacing="10">
<!-- TODO: Größe des TextFields dynamisch anpassen -->
<TextField fx:id="lobbyChatInputField" HBox.hgrow="ALWAYS" promptText="Nachricht eingeben..." styleClass="gray-input-field" onAction="#sendLobbyMessage"/>
<Button fx:id="lobbyChatSendButton" text="SENDEN" onAction="#sendLobbyMessage" styleClass="yellow-button"/>
</HBox>
</children>
</VBox>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!-- VBox for the whisper chat, should only be visible when activated via changeWhisperChatButton-->
<VBox fx:id="whisperChatVBox"
VBox.vgrow="ALWAYS"
styleClass="chat-box"
spacing="10"
maxWidth="Infinity"
visible="false">
<children>
<Label text="== WHISPER CHAT ==" styleClass="chat-header" alignment="CENTER" maxWidth="Infinity"/>
<Region minHeight="4" styleClass="chat-separator"/>
<!-- Chatnachrichten werden hier hinzugefügt
<ScrollPane fx:id="chatScrollPane" fitToWidth="true" vbarPolicy="AS_NEEDED" hbarPolicy="NEVER" VBox.vgrow="ALWAYS" styleClass="chat-scroll-pane">
<content>
<VBox fx:id="chatVBox" spacing="10" styleClass="chat-VBox"/>
</content>
</ScrollPane>
<HBox spacing="10">
<TextField fx:id="inputField" HBox.hgrow="ALWAYS" promptText="Nachricht eingeben..." styleClass="gray-input-field" onAction="#sendMessage"/>
<Button fx:id="sendButton" text="SENDEN" onAction="#sendMessage" styleClass="yellow-button"/>
</HBox>
-->
</children>
</VBox>
@@ -1,28 +0,0 @@
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI;
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
import ch.unibas.dmi.dbis.cs108.casono.server.ServerApp;
import javafx.application.Application;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class ChatControllerTest {
@BeforeEach
public void setup() {
}
@AfterEach
public void teardown() {
}
@Test
public void testChatController() {
Client client = new Client("user1");
client.startApplication();
}
}