From 77c7f7c0a51c5e0bc209d88608d0ea8d1172b629 Mon Sep 17 00:00:00 2001 From: Julian Kropff Date: Thu, 2 Apr 2026 16:08:24 +0200 Subject: [PATCH] Add: initial client structure for game and network --- .../dbis/cs108/casono/client/game/Card.java | 9 ++ .../cs108/casono/client/game/GameState.java | 23 ++++ .../dbis/cs108/casono/client/game/Player.java | 17 +++ .../casono/client/network/ChatClient.java | 30 ++++- .../casono/client/network/ClientService.java | 78 ++++++----- .../casono/client/network/CoreClient.java | 24 +++- .../casono/client/network/GameClient.java | 125 ++++++++++++++++++ .../casono/client/network/LobbyClient.java | 64 +++++++++ 8 files changed, 330 insertions(+), 40 deletions(-) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/Card.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/GameState.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/Player.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/GameClient.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/LobbyClient.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/Card.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/Card.java new file mode 100644 index 0000000..1d2a7f4 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/Card.java @@ -0,0 +1,9 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.game; + +/** + * Represents a playing card with a value and suit. + */ +public class Card { + public String value; + public String suit; +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/GameState.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/GameState.java new file mode 100644 index 0000000..8cd2183 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/GameState.java @@ -0,0 +1,23 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.game; + +import ch.unibas.dmi.dbis.cs108.casono.client.game.Card; +import ch.unibas.dmi.dbis.cs108.casono.client.game.Player; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents the current state of the poker game, including the phase, pot + * size, current bet, dealer position, + * active player, community cards, and player information. + */ +public class GameState { + public String phase; + public int pot; + public int currentBet; + public int dealer; + public int activePlayer; + + public List communityCards = new ArrayList<>(); + public List players = new ArrayList<>(); +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/Player.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/Player.java new file mode 100644 index 0000000..2702d97 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/Player.java @@ -0,0 +1,17 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.game; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a player in the poker game, including their name, chip count, + * current bet, state (e.g., "active", "folded"), and their hole cards. + */ +public class Player { + public String name; + public int chips; + public int bet; + public String state; + + public List cards = new ArrayList<>(); +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ChatClient.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ChatClient.java index b9f30c4..e5a17cb 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ChatClient.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ChatClient.java @@ -5,36 +5,52 @@ import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; import java.util.ArrayList; import java.util.List; +/** + * The ChatClient class is responsible for sending messages to the server and + * retrieving messages from the server. It uses the ClientService to send + * commands and receive responses from the server. + */ public class ChatClient { private ClientService clientService; + /** + * Constructs a ChatClient with the given ClientService for communication. + * + * @param clientService The ClientService instance used to send commands and + * receive responses from the server. + */ public ChatClient(ClientService clientService) { this.clientService = clientService; } /** - * sends a message to the server - * @param message + * Send a Message to the server by converting it to a string format and + * sending a "SEND_MESSAGE" command with the message content as arguments. + * + * @param message The Message object to be sent to the server. */ - public void sendMessage(Message message) { String request = "SEND_MESSAGE " + message.toArgsString(); clientService.processCommand(request); } /** - * Send a Request to get the number of Messages currently in the Queue for the client. - * Then proceeds, if needed, to get the messages by sending + * Retrieve messages from the server by first sending a "GET_MESSAGE_COUNT" + * command to determine how many messages are available and then sending + * "GET_NEXT_MESSAGE" commands in a loop to retrieve each message. The + * retrieved messages are parsed into Message objects and returned as a list. + * + * @return A list of Message objects representing the messages retrieved from + * the server. */ - public List getMessages() { String countStr = clientService.processCommand("GET_MESSAGE_COUNT"); int count = Integer.parseInt(countStr); System.out.println("Got " + count + " messages"); List messages = new ArrayList<>(); for (int i = 0; i < count; i++) { - String message = clientService.processCommand("GET_NEXT_MESSAGE"); + String message = clientService.processCommand("GET_NEXT_MESSAGE"); if (message != null) { Message message1 = Message.toMessage(message); messages.add(message1); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java index 78594e4..c4d8550 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java @@ -1,6 +1,5 @@ package ch.unibas.dmi.dbis.cs108.casono.client.network; - import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket; import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport; import java.net.Socket; @@ -14,11 +13,13 @@ import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; - /** - * Responsible for the transferring of the data from the Client to the Server and the other way around + * The ClientService class is responsible for managing the connection to the + * server, + * sending commands, and receiving responses. It uses a TcpTransport to + * communicate + * with the server and an ExecutorService to handle asynchronous requests. */ - public class ClientService { private final TcpTransport clienttcptransport; @@ -30,11 +31,13 @@ public class ClientService { private final AtomicInteger idGenerator; /** - * 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 port : port of the server + * Constructs a ClientService with the given server IP and port. It establishes + * a socket connection to the server and initializes the TcpTransport and + * ExecutorService for communication. + * + * @param ip The IP address of the server to connect to. + * @param port The port number of the server to connect to. */ - public ClientService(String ip, int port) { this.idGenerator = new AtomicInteger(0); @@ -42,30 +45,28 @@ public class ClientService { try { socket = new Socket(ip, port); clienttcptransport = new TcpTransport(socket); - } - catch (IOException i) { + } catch (IOException i) { throw new RuntimeException(i); } executor = Executors.newSingleThreadExecutor(); } - /** - * Sends the Requests to the server and waits for the response - * If the response is "+OK" it proceeds normal - * If the response "-ERROR" it throws a runtime exception - * @param message - * @return - The response as a string, if it has to be returned - * (+OK will not be returned) + * Sends a command to the server and waits for the response. The command is + * sent using the TcpTransport, and the response is read in a loop until a + * valid response is received. The method handles "+OK" and "-ERROR" responses + * from the server and returns the actual response content. + * + * @param message The command message to be sent to the server. + * @return The response from the server as a string. */ - protected String processCommand(String message) { AtomicReference response = new AtomicReference<>(); sendRequest(() -> { try { writeToTransport(message); - String responseLine=null; + String responseLine = null; do { responseLine = clienttcptransport.read().payload(); System.out.println("Raw message '" + responseLine + "'"); @@ -75,7 +76,7 @@ public class ClientService { throw new RuntimeException(responseLine); } response.set(responseLine); - } while(true); + } while (true); } catch (Exception e) { throw getRuntimeException(e); } @@ -84,10 +85,14 @@ public class ClientService { } /** + * Helper method to send a request to the server using the ExecutorService. It + * submits the request as a Runnable task and waits for its completion. If + * the task is interrupted or encounters an execution exception, it throws a + * RuntimeException with the appropriate cause. * - * @param request + * @param request The Runnable task representing the request to be sent to the + * server. */ - private void sendRequest(Runnable request) { Future future = executor.submit(request); try { @@ -100,11 +105,15 @@ public class ClientService { } /** - * Returns a Runtime Exceptions thrown by the sendRequest method - * @param e - an Exception - * @return - a Runtime Exception + * Helper method to extract the cause of an exception and return it as a + * RuntimeException. If the cause is null, it returns the original exception as + * a RuntimeException. If the cause is already a RuntimeException, it returns + * it directly. Otherwise, it wraps the cause in a new RuntimeException and + * returns it. + * + * @param e The exception from which to extract the cause. + * @return A RuntimeException representing the cause of the original exception. */ - private static RuntimeException getRuntimeException(Exception e) { Throwable reason = e.getCause(); RuntimeException re; @@ -118,9 +127,11 @@ public class ClientService { } /** - * Closes the Socket and shuts down the Threadpool associated with that Socket-Connection. + * Closes the socket connection to the server and shuts down the + * ExecutorService. + * It also closes the TcpTransport used for communication. If any IOException + * occurs during this process, it prints the exception to the console. */ - public void closeSocket() { try { executor.shutdown(); @@ -133,11 +144,14 @@ public class ClientService { } /** - * Method to write with the tcp transport to the server - * @param s - Message to be sent - * @throws IOException + * Helper method to write a command string to the TcpTransport. It generates a + * unique ID for the command using the idGenerator and sends a RawPacket + * containing the ID and the command string to the server. If an IOException + * occurs during this process, it throws a RuntimeException with the cause. + * + * @param s The command string to be sent to the server. + * @throws IOException If an I/O error occurs while writing to the transport. */ - private void writeToTransport(String s) throws IOException { int id = this.idGenerator.incrementAndGet(); this.clienttcptransport.write(new RawPacket(id, s)); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/CoreClient.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/CoreClient.java index 92902bc..2db2111 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/CoreClient.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/CoreClient.java @@ -1,17 +1,39 @@ package ch.unibas.dmi.dbis.cs108.casono.client.network; +/** + * The CoreClient class provides basic functionalities for communicating with + * the + * server, such as sending a ping command to check connectivity and logging in + * with a username. It uses the ClientService to send commands and receive + * responses from the server. + */ public class CoreClient { private final ClientService clientService; + /** + * Constructs a CoreClient with the given ClientService for communication. + * + * @param clientservice The ClientService instance used to send commands and + * receive responses from the server. + */ public CoreClient(ClientService clientservice) { this.clientService = clientservice; } + /** + * Sends a "PING" command to the server to check connectivity. The server + * should respond with a "PONG" message if the connection is successful. + */ public void ping() { clientService.processCommand("PING"); } + /** + * Logs in to the server with the given username by sending a "LOGIN" command. + * + * @param user The username to log in with. + */ public void login(String user) { clientService.processCommand("LOGIN USERNAME=" + user); } -} \ No newline at end of file +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/GameClient.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/GameClient.java new file mode 100644 index 0000000..0ae8225 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/GameClient.java @@ -0,0 +1,125 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.network; + +import ch.unibas.dmi.dbis.cs108.casono.client.game.Card; +import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState; +import ch.unibas.dmi.dbis.cs108.casono.client.game.Player; + +import java.util.ArrayList; +import java.util.List; + +/** + * The GameClient class is responsible for communicating with the server to + * retrieve the current game state. It sends a command to the server and + * parses the response into a structured GameState object. + */ +public class GameClient { + + private final ClientService client; + + /** + * Constructs a GameClient with the given ClientService for communication. + * + * @param client The ClientService instance used to send commands and receive + * responses from the server. + */ + public GameClient(ClientService client) { + this.client = client; + } + + /** + * Retrieves the current game state from the server by sending a command and + * parsing the response. + * + * @return A GameState object representing the current state of the game. + */ + public GameState getGameState() { + String response = client.processCommand("GET_GAME_STATE"); + return parseGameState(response); + } + + /** + * Parses the raw response from the server into a structured GameState object. + * + * @param input The raw response string from the server. + * @return A GameState object representing the current state of the game. + */ + private GameState parseGameState(String input) { + + GameState state = new GameState(); + + String[] lines = input.split("\n"); + + Player currentPlayer = null; + Card currentCard = null; + + for (String rawLine : lines) { + + String line = rawLine.trim(); + + if (line.startsWith("+OK") || line.equals("END")) { + continue; + } + + if (line.startsWith("PHASE=")) { + state.phase = line.split("=")[1]; + } + + else if (line.startsWith("POT=")) { + state.pot = Integer.parseInt(line.split("=")[1]); + } + + else if (line.startsWith("CURRENT_BET=")) { + state.currentBet = Integer.parseInt(line.split("=")[1]); + } + + else if (line.startsWith("DEALER=")) { + state.dealer = Integer.parseInt(line.split("=")[1]); + } + + else if (line.startsWith("ACTIVE_PLAYER=")) { + state.activePlayer = Integer.parseInt(line.split("=")[1]); + } + + else if (line.startsWith("PLAYER")) { + currentPlayer = new Player(); + state.players.add(currentPlayer); + } + + else if (line.startsWith("NAME=") && currentPlayer != null) { + currentPlayer.name = line.split("=")[1]; + } + + else if (line.startsWith("CHIPS=") && currentPlayer != null) { + currentPlayer.chips = Integer.parseInt(line.split("=")[1]); + } + + else if (line.startsWith("BET=") && currentPlayer != null) { + currentPlayer.bet = Integer.parseInt(line.split("=")[1]); + } + + else if (line.startsWith("STATE=") && currentPlayer != null) { + currentPlayer.state = line.split("=")[1]; + } + + else if (line.startsWith("CARD")) { + currentCard = new Card(); + + if (currentPlayer != null) { + currentPlayer.cards.add(currentCard); + } else { + state.communityCards.add(currentCard); + } + } + + else if (line.startsWith("VALUE=") && currentCard != null) { + currentCard.value = line.split("=")[1]; + } + + else if (line.startsWith("SUIT=") && currentCard != null) { + currentCard.suit = line.split("=")[1]; + } + } + + return state; + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/LobbyClient.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/LobbyClient.java new file mode 100644 index 0000000..996f767 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/LobbyClient.java @@ -0,0 +1,64 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.network; + +/** + * The LobbyClient class is responsible for communicating with the server to + * manage game lobbies. It provides methods to create a lobby, join a lobby, + * and fetch the current status of a lobby by sending appropriate commands to + * the server and processing the responses. + */ +public class LobbyClient { + private final ClientService client; + + /** + * Constructs a LobbyClient with the given ClientService for communication. + * + * @param client The ClientService instance used to send commands and receive + * responses from the server. + */ + public LobbyClient(ClientService client) { + this.client = client; + } + + /** + * Fetch the current status of the lobby with the given id from the server. + * + * @param lobbyId The id of the lobby to fetch the status for. + * @return A string representing the current status of the lobby, as returned + * by the server. + */ + public String fetchLobbyStatusString(int lobbyId) { + return client.processCommand("GET_LOBBY_STATUS ID=" + lobbyId); + } + + /** + * Request the server to create a new lobby and return the id of the newly + * created lobby. + * + * @return The id of the newly created lobby, as returned by the server. + */ + public int createLobby() { + String response = client.processCommand("CREATE_LOBBY"); + return Integer.parseInt(response); + } + + /** + * Request the server to return the id of the lobby that the client is + * currently in. + * + * @return The id of the lobby that the client is currently in, as returned by + * the server. + */ + public int getLobbyId() { + String response = client.processCommand("GET_LOBBY_ID"); + return Integer.parseInt(response); + } + + /** + * Request the server to join the lobby with the given id. + * + * @param lobbyId The id of the lobby to join. + */ + public void joinLobby(int lobbyId) { + client.processCommand("JOIN_LOBBY ID=" + lobbyId); + } +}