Feat/ms 4 integration #282
@@ -10,9 +10,10 @@ import org.apache.logging.log4j.Logger;
|
|||||||
/**
|
/**
|
||||||
* Entry point and bootstrap helper for the Casono client application.
|
* Entry point and bootstrap helper for the Casono client application.
|
||||||
*
|
*
|
||||||
* <p>This class is responsible for two tasks: - performing an optional startup LOGIN when a
|
* Responsibilities:
|
||||||
* username is supplied on the command line, and - providing a shared {@link ClientService} instance
|
* - Parse CLI args (host:port, optional username)
|
||||||
* that the UI can reuse so the initial connection remains active.
|
* - Store username centrally so UI can reuse it
|
||||||
|
* - Create a shared ClientService and do startup LOGIN (optional)
|
||||||
*/
|
*/
|
||||||
public class ClientApp {
|
public class ClientApp {
|
||||||
|
|
||||||
@@ -21,16 +22,13 @@ public class ClientApp {
|
|||||||
/** Shared client connection used when a username is provided at startup. */
|
/** Shared client connection used when a username is provided at startup. */
|
||||||
private static volatile ClientService sharedClientService;
|
private static volatile ClientService sharedClientService;
|
||||||
|
|
||||||
|
private static volatile String sharedUsername;
|
||||||
|
|
||||||
/** Default constructor. */
|
/** Default constructor. */
|
||||||
public ClientApp() {
|
public ClientApp() {
|
||||||
// Default constructor
|
// Default constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the shared {@link ClientService} instance created during startup, or {@code null} if
|
|
||||||
* none exists. The UI may call this to reuse the connection that already performed the initial
|
|
||||||
* LOGIN.
|
|
||||||
*/
|
|
||||||
public static ClientService getSharedClientService() {
|
public static ClientService getSharedClientService() {
|
||||||
return sharedClientService;
|
return sharedClientService;
|
||||||
}
|
}
|
||||||
@@ -39,26 +37,34 @@ public class ClientApp {
|
|||||||
sharedClientService = cs;
|
sharedClientService = cs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static String getSharedUsername() {
|
||||||
* Starts the client application with the given address.
|
return sharedUsername;
|
||||||
*
|
}
|
||||||
* @param arg Address in the format "ip:port".
|
|
||||||
* @throws IllegalArgumentException if the address format is invalid.
|
private static void setSharedUsername(String username) {
|
||||||
*/
|
sharedUsername = username;
|
||||||
|
LOGGER.info("sharedUsername set to '{}'", getSharedUsername());
|
||||||
|
}
|
||||||
|
|
||||||
public static void start(String arg, String username) {
|
public static void start(String arg, String username) {
|
||||||
String[] parts = arg.split(":", 2);
|
String[] parts = arg.split(":", 2);
|
||||||
if (parts.length != 2) {
|
if (parts.length != 2) {
|
||||||
throw new IllegalArgumentException("Address must be in format <ip>:<port>.");
|
throw new IllegalArgumentException("The address must be in the format <ip>:<port>.");
|
||||||
}
|
}
|
||||||
String host = parts[0];
|
String host = parts[0];
|
||||||
int port = Integer.parseInt(parts[1]);
|
int port = Integer.parseInt(parts[1]);
|
||||||
|
|
||||||
LOGGER.info("You've selected the client. It will connect port {} at host {}", port, host);
|
LOGGER.info("You've selected the client. It will connect port {} at host {}", port, host);
|
||||||
|
|
||||||
System.setProperty("casono.server.host", host);
|
System.setProperty("casono.server.host", host);
|
||||||
System.setProperty("casono.server.port", String.valueOf(port));
|
System.setProperty("casono.server.port", String.valueOf(port));
|
||||||
// If a username was provided, create a shared connection and perform
|
|
||||||
// the initial LOGIN asynchronously on that connection. The connection
|
if (username != null && !username.isBlank()) {
|
||||||
// is intentionally NOT closed so the UI can reuse it.
|
setSharedUsername(username.trim());
|
||||||
|
} else {
|
||||||
|
setSharedUsername(null);
|
||||||
|
}
|
||||||
|
|
||||||
if (username != null && !username.isBlank()) {
|
if (username != null && !username.isBlank()) {
|
||||||
try {
|
try {
|
||||||
ClientService clientService = new ClientService(host, port);
|
ClientService clientService = new ClientService(host, port);
|
||||||
@@ -72,15 +78,21 @@ public class ClientApp {
|
|||||||
t.setName("casono-startup-login");
|
t.setName("casono-startup-login");
|
||||||
return t;
|
return t;
|
||||||
});
|
});
|
||||||
|
|
||||||
bg.submit(
|
bg.submit(
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
LobbyClient lobbyClient = new LobbyClient(clientService);
|
LobbyClient lobbyClient = new LobbyClient(clientService);
|
||||||
LoginResult res = lobbyClient.login(username);
|
LoginResult res = lobbyClient.login(username);
|
||||||
|
|
||||||
|
if (res != null && res.getUsername() != null && !res.getUsername().isBlank()) {
|
||||||
|
setSharedUsername(res.getUsername().trim());
|
||||||
|
}
|
||||||
|
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
"Assigned username='{}' id={}",
|
"Assigned username='{}' id={}",
|
||||||
res.getUsername(),
|
res != null ? res.getUsername() : null,
|
||||||
res.getId());
|
res != null ? res.getId() : null);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOGGER.warn("Startup login failed: {}", e.getMessage());
|
LOGGER.warn("Startup login failed: {}", e.getMessage());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -104,19 +116,14 @@ public class ClientApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Main entry point. {@code <host:port>} (e.g. {@code 127.0.0.1:1234})
|
|
||||||
*
|
|
||||||
* <p>If a username is provided the application attempts a startup LOGIN on the shared
|
|
||||||
* connection; the UI will still start and will reuse the connection when possible. The username
|
|
||||||
* is not propagated via a system property for UI display.
|
|
||||||
*/
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
if (args == null || args.length == 0) {
|
if (args == null || args.length == 0) {
|
||||||
throw new IllegalArgumentException("Address argument required: <host:port>");
|
throw new IllegalArgumentException("Address argument required: <host:port>");
|
||||||
}
|
}
|
||||||
// Optional username argument
|
|
||||||
String username = args.length > 1 && args[1] != null && !args[1].isBlank() ? args[1] : null;
|
String username =
|
||||||
|
args.length > 1 && args[1] != null && !args[1].isBlank() ? args[1] : null;
|
||||||
|
|
||||||
start(args[0], username);
|
start(args[0], username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,118 +2,84 @@ package ch.unibas.dmi.dbis.cs108.casono.client.game;
|
|||||||
|
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.GameClient;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.GameClient;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
/**
|
|
||||||
* Service class responsible for managing the game state and providing methods to interact with the
|
|
||||||
* game.
|
|
||||||
*/
|
|
||||||
public class GameService {
|
public class GameService {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(GameService.class.getName());
|
||||||
|
|
||||||
private final GameClient client;
|
private final GameClient client;
|
||||||
private GameState state;
|
private GameState state;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a GameService with the given GameClient for communication with the server.
|
|
||||||
*
|
|
||||||
* @param client The GameClient instance used to send commands and receive responses from the
|
|
||||||
* server.
|
|
||||||
*/
|
|
||||||
public GameService(GameClient client) {
|
public GameService(GameClient client) {
|
||||||
|
if (client == null) throw new IllegalArgumentException("GameClient must not be null");
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
LOG.info("GameService created");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh the game state by fetching the latest state from the server using the GameClient.
|
|
||||||
*
|
|
||||||
* @return The updated GameState object representing the current state of the game after
|
|
||||||
* refreshing.
|
|
||||||
*/
|
|
||||||
public GameState refresh() {
|
public GameState refresh() {
|
||||||
state = client.fetchGameState();
|
LOG.fine("Refreshing game state...");
|
||||||
|
|
||||||
|
GameState newState;
|
||||||
|
try {
|
||||||
|
newState = client.fetchGameState();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Shouldn't happen with the new GameClient, but keep service stable.
|
||||||
|
LOG.log(Level.WARNING, "refresh() failed: " + e.getMessage(), e);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newState == null) {
|
||||||
|
LOG.fine("No new state (null) -> keeping previous state");
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = newState;
|
||||||
|
|
||||||
|
LOG.info(() -> "State updated: phase=" + state.phase
|
||||||
|
+ " pot=" + state.pot
|
||||||
|
+ " players=" + (state.players != null ? state.players.size() : 0)
|
||||||
|
+ " community=" + (state.communityCards != null ? state.communityCards.size() : 0));
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current game state.
|
|
||||||
*
|
|
||||||
* @return The current GameState object representing the state of the game.
|
|
||||||
*/
|
|
||||||
public int getPot() {
|
public int getPot() {
|
||||||
ensureState();
|
ensureState();
|
||||||
return state.pot;
|
return state.pot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current game state.
|
|
||||||
*
|
|
||||||
* @return The current GameState object representing the state of the game.
|
|
||||||
*/
|
|
||||||
public List<Card> getCommunityCards() {
|
public List<Card> getCommunityCards() {
|
||||||
ensureState();
|
ensureState();
|
||||||
return state.communityCards;
|
return state.communityCards != null ? state.communityCards : List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the list of players in the current game state.
|
|
||||||
*
|
|
||||||
* @return A list of Player objects representing the players in the current game state.
|
|
||||||
*/
|
|
||||||
public List<Player> getPlayers() {
|
public List<Player> getPlayers() {
|
||||||
ensureState();
|
ensureState();
|
||||||
return state.players;
|
return state.players != null ? state.players : List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Send a CALL command to the server to indicate that the player wants to call. */
|
|
||||||
public void call() {
|
|
||||||
client.sendCall();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Send a FOLD command to the server to indicate that the player wants to fold. */
|
|
||||||
public void fold() {
|
|
||||||
client.sendFold();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a BET command to the server to indicate that the player wants to bet with the specified
|
|
||||||
* amount.
|
|
||||||
*
|
|
||||||
* @param amount The amount the player wants to bet.
|
|
||||||
*/
|
|
||||||
public void bet(int amount) {
|
|
||||||
client.sendBet(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a RAISE command to the server to indicate that the player wants to raise to the
|
|
||||||
* specified amount.
|
|
||||||
*
|
|
||||||
* @param amount The amount the player wants to raise to.
|
|
||||||
*/
|
|
||||||
public void raise(int amount) {
|
|
||||||
client.sendRaise(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the winner of the game if there is one. If the game is still ongoing or if there is no
|
|
||||||
* winner, this method returns null.
|
|
||||||
*
|
|
||||||
* @return The Player object representing the winner of the game, or null if there is no winner
|
|
||||||
* yet.
|
|
||||||
*/
|
|
||||||
public Player getWinner() {
|
public Player getWinner() {
|
||||||
ensureState();
|
ensureState();
|
||||||
|
if (state.winnerIndex < 0 || state.players == null || state.winnerIndex >= state.players.size()) {
|
||||||
if (state.winnerIndex < 0) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.players.get(state.winnerIndex);
|
return state.players.get(state.winnerIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ensure that the game state has been initialized before accessing it. */
|
public void call() { client.sendCall(); }
|
||||||
|
public void fold() { client.sendFold(); }
|
||||||
|
public void bet(int amount) { client.sendBet(amount); }
|
||||||
|
public void raise(int amount) { client.sendRaise(amount); }
|
||||||
|
|
||||||
private void ensureState() {
|
private void ensureState() {
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
throw new IllegalStateException("Call refresh() first");
|
throw new IllegalStateException("GameService used before any successful refresh()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GameState peekStateOrNull() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import java.util.List;
|
|||||||
* dealer position, active player, community cards, and player information.
|
* dealer position, active player, community cards, and player information.
|
||||||
*/
|
*/
|
||||||
public class GameState {
|
public class GameState {
|
||||||
|
public List<Player> players = new ArrayList<>();
|
||||||
|
public List<Card> communityCards = new ArrayList<>();
|
||||||
|
|
||||||
public String phase;
|
public String phase;
|
||||||
public int pot;
|
public int pot;
|
||||||
public int currentBet;
|
public int currentBet;
|
||||||
public int dealer;
|
public int dealer;
|
||||||
public int activePlayer;
|
public int activePlayer;
|
||||||
public int winnerIndex = -1;
|
public int winnerIndex = -1;
|
||||||
|
|
||||||
public List<Card> communityCards = new ArrayList<>();
|
|
||||||
public List<Player> players = new ArrayList<>();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,11 +107,16 @@ public class ClientService {
|
|||||||
|
|
||||||
boolean hasStatus = false;
|
boolean hasStatus = false;
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
|
|
||||||
List<String> lines = new ArrayList<>();
|
List<String> lines = new ArrayList<>();
|
||||||
|
|
||||||
|
int depth = 0;
|
||||||
|
boolean inPlayer = false;
|
||||||
|
|
||||||
for (String rawLine : responseText.split("\\n")) {
|
for (String rawLine : responseText.split("\\n")) {
|
||||||
String line = rawLine;
|
String line = rawLine;
|
||||||
String trimmed = line.trim();
|
String trimmed = line.trim();
|
||||||
|
|
||||||
if (!hasStatus) {
|
if (!hasStatus) {
|
||||||
if ("+OK".equals(trimmed)) {
|
if ("+OK".equals(trimmed)) {
|
||||||
success = true;
|
success = true;
|
||||||
@@ -126,12 +131,39 @@ public class ClientService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trimmed.isEmpty()) continue;
|
||||||
|
|
||||||
|
line = line.replaceFirst("^\\s+", "");
|
||||||
|
trimmed = line.trim();
|
||||||
|
|
||||||
|
if ("PLAYER".equals(trimmed)) {
|
||||||
|
inPlayer = true;
|
||||||
|
lines.add("PLAYER");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isContainerStart(trimmed)) {
|
||||||
|
depth++;
|
||||||
|
lines.add(trimmed);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ("END".equals(trimmed)) {
|
if ("END".equals(trimmed)) {
|
||||||
|
if (inPlayer) {
|
||||||
|
inPlayer = false;
|
||||||
|
lines.add("END");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth > 0) {
|
||||||
|
depth--;
|
||||||
|
lines.add("END");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove leading indentation (tabs/spaces) so parameters match regex parsing
|
|
||||||
line = line.replaceFirst("^\\s+", "");
|
|
||||||
lines.add(line);
|
lines.add(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,16 +175,17 @@ public class ClientService {
|
|||||||
success = false;
|
success = false;
|
||||||
hasStatus = true;
|
hasStatus = true;
|
||||||
} else {
|
} else {
|
||||||
// best effort: treat as success
|
|
||||||
success = true;
|
success = true;
|
||||||
hasStatus = true;
|
hasStatus = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug("Parsed response lines (rid={}, success={}, depthEnd={}, inPlayerEnd={}): {}",
|
||||||
|
rid, success, depth, inPlayer, lines);
|
||||||
|
|
||||||
if (rid == 0) {
|
if (rid == 0) {
|
||||||
for (Consumer<List<String>> l : eventListeners) {
|
for (Consumer<List<String>> l : eventListeners) {
|
||||||
try {
|
try {
|
||||||
logger.debug("Parsed response lines: {}", lines);
|
|
||||||
l.accept(List.copyOf(lines));
|
l.accept(List.copyOf(lines));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("Event listener threw", e);
|
logger.warn("Event listener threw", e);
|
||||||
@@ -168,6 +201,14 @@ public class ClientService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isContainerStart(String token) {
|
||||||
|
return token.equals("LOBBIES")
|
||||||
|
|| token.equals("LOBBY")
|
||||||
|
|| token.equals("PLAYERS")
|
||||||
|
|| token.equals("CARDS");
|
||||||
|
// NOTE: PLAYER deliberately excluded
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a ClientService in offline mode. No network connection will be attempted and calls
|
* Constructs a ClientService in offline mode. No network connection will be attempted and calls
|
||||||
* to processCommand will throw a RuntimeException.
|
* to processCommand will throw a RuntimeException.
|
||||||
|
|||||||
@@ -5,320 +5,240 @@ import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState;
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Player;
|
import ch.unibas.dmi.dbis.cs108.casono.client.game.Player;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId;
|
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerState;
|
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerState;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GameClient class is responsible for communicating with the server to retrieve the current
|
* Fetches and parses game state from server.
|
||||||
* game state. It sends a command to the server and parses the response into a structured GameState
|
*
|
||||||
* object.
|
* Protocol notes (based on your logs):
|
||||||
|
* - Success replies contain lines like PHASE=..., POT=..., PLAYER ... END ... END
|
||||||
|
* - Error replies contain -ERR, CODE=..., MSG=..., END
|
||||||
*/
|
*/
|
||||||
public class GameClient {
|
public class GameClient {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(GameClient.class.getName());
|
||||||
|
|
||||||
private final ClientService client;
|
private final ClientService client;
|
||||||
private final int gameId;
|
private final int gameId;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, int gameId) {
|
public GameClient(ClientService client, int gameId) {
|
||||||
|
if (client == null) throw new IllegalArgumentException("ClientService must not be null");
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.gameId = gameId;
|
this.gameId = gameId;
|
||||||
|
LOG.info(() -> "GameClient initialized for gameId=" + gameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the current game state from the server by sending a "GET_GAME_STATE"
|
|
||||||
*
|
|
||||||
* @return A GameState object representing the current state of the game, as parsed
|
|
||||||
*/
|
|
||||||
public GameState fetchGameState() {
|
public GameState fetchGameState() {
|
||||||
List<String> responseLines = client.processCommand("GET_GAME_STATE GAME_ID=" + gameId);
|
final String cmd = "GET_GAME_STATE GAME_ID=" + gameId;
|
||||||
|
|
||||||
if (responseLines == null || responseLines.isEmpty()) {
|
List<String> lines;
|
||||||
throw new RuntimeException("Empty server response");
|
try {
|
||||||
|
lines = client.processCommand(cmd);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.log(Level.SEVERE, "processCommand failed for " + cmd + ": " + e.getMessage(), e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseLines.get(0).startsWith("-ERR")) {
|
if (lines == null || lines.isEmpty()) {
|
||||||
throw new RuntimeException("Server Error: " + String.join(" ", responseLines));
|
LOG.warning("Empty server response for " + cmd);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!responseLines.get(0).startsWith("+OK")) {
|
LOG.info(() -> "GET_GAME_STATE raw lines count=" + lines.size() + " lines=" + lines);
|
||||||
throw new RuntimeException("Invalid response: missing +OK");
|
|
||||||
|
String joined = String.join("\n", lines);
|
||||||
|
|
||||||
|
if (joined.contains("-ERR")) {
|
||||||
|
String code = extractValue(joined, "CODE");
|
||||||
|
String msg = extractValue(joined, "MSG");
|
||||||
|
LOG.info(() -> "GET_GAME_STATE returned -ERR code=" + code + " msg=" + msg);
|
||||||
|
|
||||||
|
if ("GAME_NOT_STARTED".equalsIgnoreCase(code)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String fullResponse = String.join("\n", responseLines);
|
try {
|
||||||
return parse(fullResponse);
|
GameState state = parseGameState(joined);
|
||||||
|
LOG.info(() -> "Parsed state: phase=" + state.phase
|
||||||
|
+ " pot=" + state.pot
|
||||||
|
+ " players=" + (state.players != null ? state.players.size() : 0)
|
||||||
|
+ " community=" + (state.communityCards != null ? state.communityCards.size() : 0));
|
||||||
|
return state;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.log(Level.SEVERE, "Failed parsing game state. Payload=\n" + joined, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Send a CALL command to the server to indicate that the player wants to call */
|
|
||||||
public void sendCall() {
|
public void sendCall() {
|
||||||
client.processCommand("CALL GAME_ID=" + gameId);
|
client.processCommand("CALL GAME_ID=" + gameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Send a FOLD command to the server to indicate that the player wants to fold */
|
|
||||||
public void sendFold() {
|
public void sendFold() {
|
||||||
client.processCommand("FOLD GAME_ID=" + gameId);
|
client.processCommand("FOLD GAME_ID=" + gameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Send a BET command to the server to indicate that the player wants to bet */
|
|
||||||
public void sendBet(int amount) {
|
public void sendBet(int amount) {
|
||||||
client.processCommand("BET GAME_ID=" + gameId + " AMOUNT=" + amount);
|
client.processCommand("BET GAME_ID=" + gameId + " AMOUNT=" + amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a RAISE command to the server to indicate that the player wants to raise
|
|
||||||
*
|
|
||||||
* @param amount The amount to raise to
|
|
||||||
*/
|
|
||||||
public void sendRaise(int amount) {
|
public void sendRaise(int amount) {
|
||||||
client.processCommand("RAISE GAME_ID=" + gameId + " AMOUNT=" + amount);
|
client.processCommand("RAISE GAME_ID=" + gameId + " AMOUNT=" + amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private GameState parseGameState(String input) {
|
||||||
* Parses the raw server response string into a structured GameState object.
|
GameState s = new GameState();
|
||||||
*
|
|
||||||
* @param input The raw server response string containing the game state information.
|
|
||||||
* @return A GameState object representing the current state of the game.
|
|
||||||
*/
|
|
||||||
private GameState parse(String input) {
|
|
||||||
|
|
||||||
if (input.startsWith("-ERR")) {
|
if (s.players == null) s.players = new ArrayList<>();
|
||||||
throw new RuntimeException("Server returned Error:\n" + input);
|
if (s.communityCards == null) s.communityCards = new ArrayList<>();
|
||||||
}
|
|
||||||
|
|
||||||
GameState state = new GameState();
|
Player currentPlayer = null;
|
||||||
ParserContext ctx = new ParserContext(state);
|
Card currentCard = null;
|
||||||
|
|
||||||
|
boolean inPlayerCards = false;
|
||||||
|
boolean inCommunityCards = false;
|
||||||
|
|
||||||
for (String raw : input.split("\n")) {
|
for (String raw : input.split("\n")) {
|
||||||
String line = raw.trim();
|
String line = raw.trim();
|
||||||
|
if (line.isEmpty()) continue;
|
||||||
|
if (line.startsWith("+OK")) continue;
|
||||||
|
|
||||||
if (shouldSkip(line)) {
|
if (line.startsWith("PHASE=")) {
|
||||||
|
s.phase = value(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.startsWith("POT=")) {
|
||||||
|
s.pot = intVal(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.startsWith("CURRENT_BET=")) {
|
||||||
|
s.currentBet = intVal(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.startsWith("DEALER=")) {
|
||||||
|
s.dealer = intVal(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.startsWith("ACTIVE_PLAYER=")) {
|
||||||
|
s.activePlayer = intVal(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.startsWith("WINNER=")) {
|
||||||
|
s.winnerIndex = intVal(line);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handleGlobal(line, ctx)) {
|
if (line.equals("PLAYER")) {
|
||||||
|
currentPlayer = new Player();
|
||||||
|
s.players.add(currentPlayer);
|
||||||
|
inPlayerCards = false;
|
||||||
|
currentCard = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handleSections(line, ctx)) {
|
if (line.equals("CARDS")) {
|
||||||
|
inPlayerCards = (currentPlayer != null);
|
||||||
|
inCommunityCards = (currentPlayer == null);
|
||||||
|
currentCard = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handlePlayer(line, ctx)) {
|
if (line.startsWith("CARD")) {
|
||||||
|
currentCard = new Card("", "");
|
||||||
|
if (inPlayerCards && currentPlayer != null) {
|
||||||
|
currentPlayer.addCard(currentCard);
|
||||||
|
} else if (inCommunityCards) {
|
||||||
|
s.communityCards.add(currentCard);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handleCards(line, ctx)) {
|
if (line.startsWith("VALUE=") && currentCard != null) {
|
||||||
|
currentCard.setValue(value(line));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
if (line.startsWith("SUIT=") && currentCard != null) {
|
||||||
}
|
currentCard.setSuit(value(line));
|
||||||
|
continue;
|
||||||
/** Holds the mutable parsing state while processing the server response. */
|
|
||||||
private static class ParserContext {
|
|
||||||
GameState state;
|
|
||||||
Player currentPlayer;
|
|
||||||
Card currentCard;
|
|
||||||
|
|
||||||
boolean inPlayers;
|
|
||||||
boolean inPlayerCards;
|
|
||||||
boolean inCommunityCards;
|
|
||||||
|
|
||||||
ParserContext(GameState state) {
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a line should be ignored.
|
|
||||||
*
|
|
||||||
* @param line the current input line
|
|
||||||
* @return true if the line is empty or a status message, false otherwise
|
|
||||||
*/
|
|
||||||
private boolean shouldSkip(String line) {
|
|
||||||
return line.isEmpty() || line.startsWith("+OK");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses global game state properties (e.g., phase, pot, current bet).
|
|
||||||
*
|
|
||||||
* @param line the current input line
|
|
||||||
* @param ctx the parser context containing the game state
|
|
||||||
* @return true if the line was handled, false otherwise
|
|
||||||
*/
|
|
||||||
private boolean handleGlobal(String line, ParserContext ctx) {
|
|
||||||
GameState state = ctx.state;
|
|
||||||
|
|
||||||
if (line.startsWith("PHASE=")) {
|
|
||||||
state.phase = value(line);
|
|
||||||
} else if (line.startsWith("POT=")) {
|
|
||||||
state.pot = intVal(line);
|
|
||||||
} else if (line.startsWith("CURRENT_BET=")) {
|
|
||||||
state.currentBet = intVal(line);
|
|
||||||
} else if (line.startsWith("DEALER=")) {
|
|
||||||
state.dealer = intVal(line);
|
|
||||||
} else if (line.startsWith("ACTIVE_PLAYER=")) {
|
|
||||||
state.activePlayer = intVal(line);
|
|
||||||
} else if (line.startsWith("WINNER=")) {
|
|
||||||
state.winnerIndex = intVal(line);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
// throw new RuntimeException("Unknown global field: " + line);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes section markers such as PLAYERS, PLAYER, CARDS, and END. Updates the parser context
|
|
||||||
* accordingly.
|
|
||||||
*
|
|
||||||
* @param line the current input line
|
|
||||||
* @param ctx the parser context
|
|
||||||
* @return true if the line was handled, false otherwise
|
|
||||||
*/
|
|
||||||
private boolean handleSections(String line, ParserContext ctx) {
|
|
||||||
|
|
||||||
if (line.equals("END")) {
|
|
||||||
// ctx.inPlayers = false;
|
|
||||||
ctx.inPlayerCards = false;
|
|
||||||
ctx.inCommunityCards = false;
|
|
||||||
ctx.currentPlayer = null;
|
|
||||||
ctx.currentCard = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.equals("PLAYERS")) {
|
|
||||||
ctx.inPlayers = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.equals("PLAYER")) {
|
|
||||||
ctx.currentPlayer = new Player();
|
|
||||||
ctx.state.players.add(ctx.currentPlayer);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.equals("CARDS")) {
|
|
||||||
if (ctx.inPlayers && ctx.currentPlayer != null) {
|
|
||||||
ctx.inPlayerCards = true;
|
|
||||||
ctx.inCommunityCards = false;
|
|
||||||
} else {
|
|
||||||
ctx.inCommunityCards = true;
|
|
||||||
ctx.inPlayerCards = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
if (line.equals("END")) {
|
||||||
}
|
if (currentCard != null) {
|
||||||
|
currentCard = null;
|
||||||
return false;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (inPlayerCards) {
|
||||||
/**
|
inPlayerCards = false;
|
||||||
* Parses properties of the current player (e.g., name, chips, bet, state).
|
continue;
|
||||||
*
|
}
|
||||||
* @param line the current input line
|
if (inCommunityCards) {
|
||||||
* @param ctx the parser context
|
inCommunityCards = false;
|
||||||
* @return true if the line was handled, false otherwise
|
continue;
|
||||||
*/
|
}
|
||||||
private boolean handlePlayer(String line, ParserContext ctx) {
|
if (currentPlayer != null) {
|
||||||
Player p = ctx.currentPlayer;
|
currentPlayer = null;
|
||||||
|
continue;
|
||||||
if (p == null) {
|
}
|
||||||
return false;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith("NAME=")) {
|
|
||||||
p.setId(PlayerId.of(value(line)));
|
|
||||||
} else if (line.startsWith("CHIPS=")) {
|
|
||||||
p.setChips(intVal(line));
|
|
||||||
} else if (line.startsWith("BET=")) {
|
|
||||||
p.setBet(intVal(line));
|
|
||||||
} else if (line.startsWith("STATE=")) {
|
|
||||||
p.setState(parseState(value(line)));
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses card-related lines and assigns cards to the current player or the community cards.
|
|
||||||
*
|
|
||||||
* @param line the current input line
|
|
||||||
* @param ctx the parser context
|
|
||||||
* @return true if the line was handled, false otherwise
|
|
||||||
*/
|
|
||||||
private boolean handleCards(String line, ParserContext ctx) {
|
|
||||||
|
|
||||||
if (line.startsWith("CARD")) {
|
|
||||||
ctx.currentCard = new Card("", "");
|
|
||||||
|
|
||||||
if (ctx.inPlayerCards && ctx.currentPlayer != null) {
|
|
||||||
ctx.currentPlayer.addCard(ctx.currentCard);
|
|
||||||
} else if (ctx.inCommunityCards) {
|
|
||||||
ctx.state.communityCards.add(ctx.currentCard);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
if (currentPlayer != null) {
|
||||||
|
if (line.startsWith("USERNAME=") || line.startsWith("NAME=")) {
|
||||||
|
currentPlayer.setId(PlayerId.of(value(line)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.startsWith("CHIPS=")) {
|
||||||
|
currentPlayer.setChips(intVal(line));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.startsWith("BET=")) {
|
||||||
|
currentPlayer.setBet(intVal(line));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.startsWith("STATE=")) {
|
||||||
|
currentPlayer.setState(parseState(value(line)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.fine(() -> "Ignored line: '" + line + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.startsWith("VALUE=") && ctx.currentCard != null) {
|
return s;
|
||||||
ctx.currentCard.setValue(value(line));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith("SUIT=") && ctx.currentCard != null) {
|
|
||||||
ctx.currentCard.setSuit(value(line));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static String value(String line) {
|
||||||
* Helper method to extract the value from a line in the format KEY=VALUE.
|
|
||||||
*
|
|
||||||
* @param line The input line from which to extract the value, expected to be in the format
|
|
||||||
* KEY=VALUE.
|
|
||||||
* @return The extracted value part of the input line, which is the substring after the first
|
|
||||||
* '=' character.
|
|
||||||
*/
|
|
||||||
private String value(String line) {
|
|
||||||
return line.split("=", 2)[1];
|
return line.split("=", 2)[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static int intVal(String line) {
|
||||||
* Helper method to extract an integer value from a line in the format KEY=VALUE.
|
|
||||||
*
|
|
||||||
* @param line The input line from which to extract the integer value, expected to be in the
|
|
||||||
* format KEY=VALUE where VALUE is an integer.
|
|
||||||
* @return The extracted integer value from the input line, parsed from the substring after the
|
|
||||||
* first '=' character.
|
|
||||||
*/
|
|
||||||
private int intVal(String line) {
|
|
||||||
return Integer.parseInt(value(line));
|
return Integer.parseInt(value(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static PlayerState parseState(String s) {
|
||||||
* Helper method to parse a string representation of a player's state into the corresponding
|
if (s == null) return PlayerState.ACTIVE;
|
||||||
* PlayerState enum value.
|
return switch (s.trim().toUpperCase()) {
|
||||||
*
|
|
||||||
* @param s The input string representing the player's state, expected to be one of ACTIVE,
|
|
||||||
* FOLDED, or DEALER (case-insensitive).
|
|
||||||
* @return The corresponding PlayerState enum value based on the input string. If the input does
|
|
||||||
* not match any known state, it defaults to PlayerState.ACTIVE.
|
|
||||||
*/
|
|
||||||
private PlayerState parseState(String s) {
|
|
||||||
return switch (s.toUpperCase()) {
|
|
||||||
case "ACTIVE" -> PlayerState.ACTIVE;
|
case "ACTIVE" -> PlayerState.ACTIVE;
|
||||||
case "FOLDED" -> PlayerState.FOLDED;
|
case "FOLDED" -> PlayerState.FOLDED;
|
||||||
case "DEALER" -> PlayerState.DEALER;
|
case "DEALER" -> PlayerState.DEALER;
|
||||||
default -> PlayerState.ACTIVE;
|
default -> PlayerState.ACTIVE;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String extractValue(String joined, String key) {
|
||||||
|
if (joined == null) return null;
|
||||||
|
for (String raw : joined.split("\n")) {
|
||||||
|
String line = raw.trim();
|
||||||
|
if (line.startsWith(key + "=")) {
|
||||||
|
return line.substring((key + "=").length()).replace("'", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+122
-40
@@ -8,6 +8,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId;
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
@@ -49,6 +50,7 @@ public class CasinoGameController {
|
|||||||
private TaskbarController taskbarController;
|
private TaskbarController taskbarController;
|
||||||
private Image dealerImage;
|
private Image dealerImage;
|
||||||
private javafx.animation.Timeline timeline;
|
private javafx.animation.Timeline timeline;
|
||||||
|
private boolean gameStarted = false;
|
||||||
|
|
||||||
private static final int TOTAL_SLOTS = 5;
|
private static final int TOTAL_SLOTS = 5;
|
||||||
private static final int PLAYER_SLOTS = 2;
|
private static final int PLAYER_SLOTS = 2;
|
||||||
@@ -305,23 +307,37 @@ public class CasinoGameController {
|
|||||||
*/
|
*/
|
||||||
private void updateUI() {
|
private void updateUI() {
|
||||||
|
|
||||||
java.util.concurrent.CompletableFuture.supplyAsync(
|
if (gameService == null) {
|
||||||
() -> {
|
LOGGER.warning("GameService is null, skipping update.");
|
||||||
try {
|
return;
|
||||||
return gameService.refresh();
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.severe("GameService Error: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.thenAccept(
|
|
||||||
s -> {
|
|
||||||
if (s == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
javafx.application.Platform.runLater(() -> applyState(s));
|
CompletableFuture
|
||||||
});
|
.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
return gameService.refresh();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.severe("refresh failed: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.thenAccept(state -> {
|
||||||
|
|
||||||
|
if (state == null) {
|
||||||
|
LOGGER.info("No state received yet.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
javafx.application.Platform.runLater(() -> {
|
||||||
|
applyState(state);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.exceptionally(ex -> {
|
||||||
|
LOGGER.severe("UI update Error: " + ex.getMessage());
|
||||||
|
ex.printStackTrace();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -332,20 +348,50 @@ public class CasinoGameController {
|
|||||||
*/
|
*/
|
||||||
private void applyState(GameState s) {
|
private void applyState(GameState s) {
|
||||||
|
|
||||||
renderCommunityCards(s.communityCards);
|
if (s == null) return;
|
||||||
|
|
||||||
|
LOGGER.info("applyState -> phase=" + s.phase +
|
||||||
|
" pot=" + s.pot +
|
||||||
|
" players=" + (s.players != null ? s.players.size() : 0));
|
||||||
|
|
||||||
|
List<Player> players = (s.players != null) ? s.players : List.of();
|
||||||
|
List<Card> community = (s.communityCards != null) ? s.communityCards : List.of();
|
||||||
|
|
||||||
|
renderCommunityCards(community);
|
||||||
|
renderPlayerCards(getMyCards(players));
|
||||||
renderPot(s.pot);
|
renderPot(s.pot);
|
||||||
|
|
||||||
updatePlayers(s.players);
|
updatePlayers(players);
|
||||||
updatePlayerCards(s);
|
|
||||||
|
|
||||||
updateGameInfo(s);
|
updateGameInfo(s);
|
||||||
highlightDealer(s);
|
highlightDealer(s);
|
||||||
|
|
||||||
if (taskbarController != null) {
|
updateTaskbar(s);
|
||||||
taskbarController.update(s, myPlayerId);
|
|
||||||
|
LOGGER.info("myPlayerId=" + myPlayerId);
|
||||||
|
for (int i = 0; i < players.size(); i++) {
|
||||||
|
Player p = players.get(i);
|
||||||
|
LOGGER.info("state.players[" + i + "] id=" + (p != null ? p.getId() : null)
|
||||||
|
+ " chips=" + (p != null ? p.getChips() : null)
|
||||||
|
+ " bet=" + (p != null ? p.getBet() : null)
|
||||||
|
+ " state=" + (p != null ? p.getState() : null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Card> getMyCards(List<Player> players) {
|
||||||
|
|
||||||
|
if (myPlayerId == null || players == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Player p : players) {
|
||||||
|
if (p.getId() != null && p.getId().equals(myPlayerId)) {
|
||||||
|
return p.getCards() != null ? p.getCards() : List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the player's hole cards based on the active player in the game state.
|
* Update the player's hole cards based on the active player in the game state.
|
||||||
*
|
*
|
||||||
@@ -398,32 +444,64 @@ public class CasinoGameController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void updatePlayers(List<Player> players) {
|
||||||
* Update the player status components with the latest player information from the game state.
|
if (players == null || players.isEmpty()) {
|
||||||
*
|
clearOpponentSlots();
|
||||||
* @param p The list of players in the current game state.
|
|
||||||
*/
|
|
||||||
private void updatePlayers(List<Player> p) {
|
|
||||||
|
|
||||||
if (p == null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p.size() > PLAYER_INDEX_0) {
|
java.util.List<Player> opponents = new java.util.ArrayList<>();
|
||||||
player1Controller.setPlayer(p.get(PLAYER_INDEX_0));
|
boolean meFound = false;
|
||||||
|
|
||||||
|
for (Player p : players) {
|
||||||
|
if (p == null) continue;
|
||||||
|
|
||||||
|
PlayerId pid = p.getId();
|
||||||
|
boolean isMe = myPlayerId != null && pid != null && pid.equals(myPlayerId);
|
||||||
|
|
||||||
|
if (isMe) {
|
||||||
|
meFound = true;
|
||||||
|
} else {
|
||||||
|
opponents.add(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p.size() > PLAYER_INDEX_1) {
|
if (!meFound) {
|
||||||
player2Controller.setPlayer(p.get(PLAYER_INDEX_1));
|
opponents.clear();
|
||||||
|
opponents.addAll(players);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p.size() > PLAYER_INDEX_2) {
|
LOGGER.info("updatePlayers: total=" + players.size()
|
||||||
player3Controller.setPlayer(p.get(PLAYER_INDEX_2));
|
+ " myPlayerId=" + myPlayerId
|
||||||
}
|
+ " meFound=" + meFound
|
||||||
|
+ " opponents=" + opponents.size());
|
||||||
|
|
||||||
player1Controller.refresh();
|
setOpponent(player1Controller, opponents, 0);
|
||||||
player2Controller.refresh();
|
setOpponent(player2Controller, opponents, 1);
|
||||||
player3Controller.refresh();
|
setOpponent(player3Controller, opponents, 2);
|
||||||
|
|
||||||
|
safeRefresh(player1Controller);
|
||||||
|
safeRefresh(player2Controller);
|
||||||
|
safeRefresh(player3Controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOpponent(PlayerStatusController slot, java.util.List<Player> list, int index) {
|
||||||
|
if (slot == null) return;
|
||||||
|
slot.setPlayer(index < list.size() ? list.get(index) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void safeRefresh(PlayerStatusController c) {
|
||||||
|
if (c == null) return;
|
||||||
|
try { c.refresh(); } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearOpponentSlots() {
|
||||||
|
if (player1Controller != null) player1Controller.setPlayer(null);
|
||||||
|
if (player2Controller != null) player2Controller.setPlayer(null);
|
||||||
|
if (player3Controller != null) player3Controller.setPlayer(null);
|
||||||
|
safeRefresh(player1Controller);
|
||||||
|
safeRefresh(player2Controller);
|
||||||
|
safeRefresh(player3Controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -913,4 +991,8 @@ public class CasinoGameController {
|
|||||||
|
|
||||||
st.play();
|
st.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMyPlayerId(PlayerId id) {
|
||||||
|
this.myPlayerId = id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui;
|
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui;
|
||||||
|
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.GameClient;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Logger;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
@@ -12,18 +18,18 @@ import javafx.stage.Stage;
|
|||||||
*
|
*
|
||||||
* <p>Starts the JavaFX application, loads the graphical user interface from the FXML file, and
|
* <p>Starts the JavaFX application, loads the graphical user interface from the FXML file, and
|
||||||
* initializes the main stage for the game.
|
* initializes the main stage for the game.
|
||||||
*
|
|
||||||
* <p>Tasks: - Loads the FXML interface "/ui-structure/Casinogameui.fxml". - Loads the application
|
|
||||||
* icon from "/images/logoinverted.png". - Starts the application in full-screen mode.
|
|
||||||
*/
|
*/
|
||||||
public class CasinoGameUI extends Application {
|
public class CasinoGameUI extends Application {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(CasinoGameUI.class.getName());
|
||||||
|
|
||||||
private static ClientService clientService;
|
private static ClientService clientService;
|
||||||
|
|
||||||
|
private static String username;
|
||||||
|
|
||||||
private static final int DEFAULT_WIDTH = 1200;
|
private static final int DEFAULT_WIDTH = 1200;
|
||||||
private static final int DEFAULT_HEIGHT = 800;
|
private static final int DEFAULT_HEIGHT = 800;
|
||||||
|
|
||||||
/** default constructor */
|
|
||||||
public CasinoGameUI() {
|
public CasinoGameUI() {
|
||||||
// default no-arg constructor
|
// default no-arg constructor
|
||||||
}
|
}
|
||||||
@@ -32,31 +38,69 @@ public class CasinoGameUI extends Application {
|
|||||||
CasinoGameUI.clientService = clientService;
|
CasinoGameUI.clientService = clientService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static void setUsername(String username) {
|
||||||
* Starts the main stage of the application.
|
CasinoGameUI.username = username;
|
||||||
*
|
}
|
||||||
* @param stage The main stage provided by the system.
|
|
||||||
* @throws IOException If the FXML file or resources cannot be loaded.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) throws IOException {
|
public void start(Stage stage) throws IOException {
|
||||||
|
|
||||||
|
if (clientService == null) {
|
||||||
|
clientService = ch.unibas.dmi.dbis.cs108.casono.client.ClientApp.getSharedClientService();
|
||||||
|
}
|
||||||
|
if (clientService == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"CasinoGameUI: clientService is null. "
|
||||||
|
+ "Call CasinoGameUI.setClientService(...) or start via ClientApp with a shared connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String effectiveUsername = normalize(username);
|
||||||
|
|
||||||
|
if (effectiveUsername == null) {
|
||||||
|
effectiveUsername =
|
||||||
|
normalize(ch.unibas.dmi.dbis.cs108.casono.client.ClientApp.getSharedUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectiveUsername == null) {
|
||||||
|
effectiveUsername = "Guest-" + UUID.randomUUID().toString().substring(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("CasinoGameUI starting: effectiveUsername='" + effectiveUsername
|
||||||
|
+ "', injectedUsername='" + username
|
||||||
|
+ "', sharedUsername='" + ch.unibas.dmi.dbis.cs108.casono.client.ClientApp.getSharedUsername()
|
||||||
|
+ "', hasClientService=" + (clientService != null));
|
||||||
|
|
||||||
FXMLLoader fxmlLoader =
|
FXMLLoader fxmlLoader =
|
||||||
new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/Casinogameui.fxml"));
|
new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/Casinogameui.fxml"));
|
||||||
Scene scene = new Scene(fxmlLoader.load(), DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
Parent root = fxmlLoader.load();
|
||||||
|
CasinoGameController controller = fxmlLoader.getController();
|
||||||
|
|
||||||
|
int gameId = 1; // TODO echte gameId einsetzen
|
||||||
|
GameClient gameClient = new GameClient(clientService, gameId);
|
||||||
|
GameService gameService = new GameService(gameClient);
|
||||||
|
controller.setGameService(gameService);
|
||||||
|
|
||||||
|
controller.setMyPlayerId(PlayerId.of(effectiveUsername));
|
||||||
|
|
||||||
|
Scene scene = new Scene(root, DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||||
stage.setTitle("Casono");
|
stage.setTitle("Casono");
|
||||||
|
|
||||||
String iconPath = getClass().getResource("/images/logoinverted.png").toExternalForm();
|
String iconPath = getClass().getResource("/images/logoinverted.png").toExternalForm();
|
||||||
stage.getIcons().add(new javafx.scene.image.Image(iconPath));
|
stage.getIcons().add(new javafx.scene.image.Image(iconPath));
|
||||||
|
|
||||||
stage.setScene(scene);
|
stage.setScene(scene);
|
||||||
stage.setFullScreen(true);
|
stage.setFullScreen(true);
|
||||||
stage.show();
|
stage.show();
|
||||||
|
|
||||||
|
controller.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalize(String s) {
|
||||||
|
if (s == null) return null;
|
||||||
|
String t = s.trim();
|
||||||
|
return t.isBlank() ? null : t;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Starting point of the application.
|
|
||||||
*
|
|
||||||
* @param args Command line arguments.
|
|
||||||
*/
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
launch();
|
launch();
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-4
@@ -2,6 +2,7 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
|||||||
|
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter;
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -550,11 +551,17 @@ public class LobbyButtonGridManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI
|
var cs = lobbyClient.getClientService();
|
||||||
.setClientService(lobbyClient.getClientService());
|
|
||||||
|
|
||||||
new ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI()
|
ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI.setClientService(cs);
|
||||||
.start(gameStage);
|
|
||||||
|
String username = ch.unibas.dmi.dbis.cs108.casono.client.ClientApp.getSharedUsername();
|
||||||
|
if (username == null || username.isBlank()) {
|
||||||
|
username = "Guest-" + java.util.UUID.randomUUID().toString().substring(0, 8);
|
||||||
|
}
|
||||||
|
ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI.setUsername(username);
|
||||||
|
|
||||||
|
new ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI().start(gameStage);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("Game UI failed: {}", e.getMessage());
|
LOGGER.error("Game UI failed: {}", e.getMessage());
|
||||||
|
|||||||
Reference in New Issue
Block a user