diff --git a/build.gradle b/build.gradle index 825c944..9fd8b42 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0") + implementation 'org.json:json:20240303' } test { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/Main.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/Main.java index d1890db..f7d2273 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/Main.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/Main.java @@ -3,8 +3,25 @@ package ch.unibas.dmi.dbis.cs108.casono; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; import ch.unibas.dmi.dbis.cs108.casono.server.ServerApp; +/** + * Main entry point for Casono application. + * Handles client and server startup. + *

+ * Standardkonstruktor für die Anwendung. + */ public final class Main { + /** + * Standardkonstruktor. + */ + public Main() { + // Standardkonstruktor + } + + /** + * Main entry point for Casono. + * @param args Command line arguments + */ public static void main(String[] args) { if (!isValid(args)) { printUsage(); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java index 2c17a37..76dc06d 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java @@ -1,6 +1,29 @@ package ch.unibas.dmi.dbis.cs108.casono.client; +/** + * Entry point for the Casono client application. + * Handles client startup and connection parameters. + */ +import ch.unibas.dmi.dbis.cs108.casono.client.ui.Launcher; +/** + * Entry point for the Casono client application. + * Handles client startup and connection parameters. + *

+ * Standardkonstruktor für die Anwendung. + */ public class ClientApp { + + /** + * Standardkonstruktor. + */ + public ClientApp() { + // Standardkonstruktor + } + /** + * Starts the client application with the given address. + * @param arg Address in the format "ip:port". + * @throws IllegalArgumentException if the address format is invalid. + */ public static void start(String arg) { String[] parts = arg.split(":", 2); if (parts.length != 2) { @@ -10,5 +33,6 @@ public class ClientApp { int port = Integer.parseInt(parts[1]); System.out.println("You've selected the client. It will connect port " + port + " at host " + host); + Launcher.main(new String[]{}); } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/Launcher.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/Launcher.java new file mode 100644 index 0000000..c687abf --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/Launcher.java @@ -0,0 +1,26 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui; + +import javafx.application.Application; + +/** + * Launcher for the Casono main UI. + *

+ * Standardkonstruktor für die Anwendung. + */ +public class Launcher { + + /** + * Standardkonstruktor. + */ + public Launcher() { + // Standardkonstruktor + } + /** + * Main entry point for launching the UI. + * @param args Command line arguments + */ + public static void main(String[] args) { + Application.launch(ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui.class, args); + } +} + \ No newline at end of file diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/Casinomainui.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/Casinomainui.java new file mode 100644 index 0000000..169da1d --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/Casinomainui.java @@ -0,0 +1,51 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; + +/** + * Main UI application for Casono. + * Loads the main FXML layout and sets up the stage. + */ +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.stage.Stage; + +import java.io.IOException; + +/** + * JavaFX Application class for the Casono main UI. + *

+ * Standardkonstruktor für die Anwendung. + */ +public class Casinomainui extends Application { + + /** + * Standardkonstruktor. + */ + public Casinomainui() { + // Standardkonstruktor + } + @Override + /** + * Starts the JavaFX application and loads the main UI. + * @param stage The primary stage for this application. + * @throws IOException If loading the FXML fails. + */ + public void start(Stage stage) throws IOException { + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/ui-structure/Casinomainui.fxml")); + Scene scene = new Scene(fxmlLoader.load(), 1200, 800); + stage.setTitle("Casono"); + + stage.getIcons().add(new javafx.scene.image.Image(getClass().getResource("/images/logoinverted.png").toExternalForm())); + stage.setScene(scene); + stage.setFullScreen(true); + stage.show(); + } + + /** + * Main entry point for launching the application. + * @param args Command line arguments + */ + public static void main(String[] args) { + launch(); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java new file mode 100644 index 0000000..5c235cf --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java @@ -0,0 +1,85 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.Button; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Rectangle; + +/** + * Controller for the Casono main UI lobby. + * Handles UI initialization and user actions. + */ +public class CasinomainuiController { + + @FXML + private AnchorPane rootPane; + @FXML + private Label titleLabel; + @FXML + private Label subtitleLabel; + @FXML + private ImageView logoView; + @FXML + private Rectangle greenBox; + @FXML + private Button exitbutton; + @FXML + private VBox casinoTable; + + private LobbyButtonTranslationManager translationManager; + private LobbyButtonGridManager gridManager; + private int nextButtonId = 1; + + public CasinomainuiController() { + // Default constructor + } + + /** + * Initializes the UI components and sets default values. + */ + @FXML + public void initialize() { + titleLabel.setText("Casono"); + subtitleLabel.setText("Texas Hold'em Poker"); + logoView.setImage(new Image(getClass().getResource("/images/logo.png").toExternalForm())); + + translationManager = new LobbyButtonTranslationManager(); + gridManager = new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager); + casinoTable.getChildren().clear(); + casinoTable.getChildren().add(gridManager.getGridPane()); + gridManager.renderLobbyButtons(); + } + + /** + * Handles the exit button action to close the application. + */ + @FXML + public void handleexitbutton() { + Platform.exit(); + } + + /** + * Handles creation of a new lobby button. + */ + @FXML + public void handleCreateLobbyButton() { + if (translationManager.isFull()) { + System.out.println("Grid voll! Keine weiteren Lobbys moeglich."); + return; + } + int buttonId = nextButtonId++; + int lobbyId = gridManager.createLobby(); + try { + translationManager.addLobbyButton(buttonId, lobbyId); + System.out.println("ButtonID: " + buttonId + ", LobbyID: " + lobbyId); + gridManager.renderLobbyButtons(); + } catch (Exception e) { + System.out.println("Fehler beim Hinzufügen: " + e.getMessage()); + } + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java new file mode 100644 index 0000000..6add82d --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java @@ -0,0 +1,101 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; + +/** + * Manages the grid for lobby buttons and rendering. + * Uses LobbyButtonTranslationManager for mapping ButtonID to LobbyID. + */ + +import javafx.scene.control.Button; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; +import java.util.Map; + +/** + * Manages the grid for lobby buttons and rendering. + * Uses LobbyButtonTranslationManager for mapping ButtonID to LobbyID. + */ +public class LobbyButtonGridManager { + + /** GridPane for the button grid. */ + private final GridPane gridPane; + /** Manager for mapping ButtonID to LobbyID. */ + private final LobbyButtonTranslationManager translationManager; + /** Number of rows in the grid. */ + private final int rows = 2; + /** Number of columns in the grid. */ + private final int cols = 4; + /** Path to the button image. */ + private final String buttonImagePath = "/images/logo.png"; + + /** + * Constructor for the GridManager. + * + * @param gridPane the GridPane for rendering + * @param translationManager the manager for mapping ButtonID to LobbyID + */ + public LobbyButtonGridManager(GridPane gridPane, LobbyButtonTranslationManager translationManager) { + this.gridPane = gridPane; + this.translationManager = translationManager; + } + + /** + * Renders all lobby buttons in the grid. + * Creates a button for each mapping with image and event handler. + */ + public void renderLobbyButtons() { + gridPane.getChildren().clear(); + int index = 0; + Map mapping = translationManager.getButtonIdToLobbyId(); + if (mapping.isEmpty()) { + // No buttons to render + return; + } + for (Map.Entry entry : mapping.entrySet()) { + int buttonId = entry.getKey(); + Button btn = new Button(); + btn.setId("lobbyBtn-" + buttonId); + btn.setGraphic(new ImageView(new Image(getClass().getResourceAsStream(buttonImagePath)))); + btn.setOnAction(e -> { + Integer lobbyId = translationManager.getLobbyIdForButton(buttonId); + if (lobbyId != null) + joinLobby(lobbyId); + }); + int row = index / cols; + int col = index % cols; + gridPane.add(btn, col, row); + index++; + } + } + + /** + * Placeholder for lobby creation logic. Returns a generated lobbyId. + * + * @return The generated lobbyId + */ + public int createLobby() { + // TODO: Replace with actual lobby creation logic + int lobbyId = (int) (Math.random() * 10000 + 1); + System.out.println("Lobby created: " + lobbyId); + return lobbyId; + } + + /** + * Placeholder for joining a lobby. + * + * @param lobbyId The lobbyId to join + */ + public void joinLobby(int lobbyId) { + // TODO: Replace with actual join logic + System.out.println("Joining lobby: " + lobbyId); + } + + /** + * Getter for the GridPane. + * + * @return The GridPane for the button grid + */ + public javafx.scene.layout.GridPane getGridPane() { + return gridPane; + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManager.java new file mode 100644 index 0000000..e3dd72e --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManager.java @@ -0,0 +1,66 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; + +import java.util.HashMap; +import java.util.Map; + +/** + * Verwaltet das Mapping zwischen Button-IDs und Lobby-IDs rein im Speicher. + * Keine Dateioperationen, nur Laufzeitdatenstruktur. + */ +public class LobbyButtonTranslationManager { + /** Maximale Anzahl an Buttons/Lobbys */ + private static final int MAX_BUTTONS = 8; + /** Zuordnung ButtonID → LobbyID */ + private final Map buttonIdToLobbyId = new HashMap<>(); + + /** + * Konstruktor: initialisiert die Zuordnung leer. + */ + public LobbyButtonTranslationManager() { + // Zuordnung bleibt leer beim Start + } + + /** + * Prüft, ob das Grid voll ist (MAX_BUTTONS erreicht). + * @return true, wenn Grid voll; sonst false + */ + public boolean isFull() { + return buttonIdToLobbyId.size() >= MAX_BUTTONS; + } + + /** + * Fügt eine Zuordnung ButtonID → LobbyID hinzu. + * @param buttonId Die ID des Buttons + * @param lobbyId Die ID der Lobby + * @throws Exception wenn das Grid voll ist + */ + public void addLobbyButton(int buttonId, int lobbyId) throws Exception { + if (isFull()) throw new Exception("Grid is full!"); + buttonIdToLobbyId.put(buttonId, lobbyId); + } + + /** + * Entfernt eine Zuordnung für die gegebene ButtonID. + * @param buttonId Die ID des zu entfernenden Buttons + */ + public void removeLobbyButton(int buttonId) { + buttonIdToLobbyId.remove(buttonId); + } + + /** + * Gibt die LobbyID für eine gegebene ButtonID zurück. + * @param buttonId Die ButtonID + * @return Die zugehoerige LobbyID oder null, falls nicht vorhanden + */ + public Integer getLobbyIdForButton(int buttonId) { + return buttonIdToLobbyId.get(buttonId); + } + + /** + * Gibt die gesamte Zuordnung ButtonID → LobbyID zurück. + * @return Map aller Zuordnungen + */ + public Map getButtonIdToLobbyId() { + return buttonIdToLobbyId; + } +} diff --git a/src/main/resources/images/background.png b/src/main/resources/images/background.png new file mode 100644 index 0000000..2b84fa7 Binary files /dev/null and b/src/main/resources/images/background.png differ diff --git a/src/main/resources/ui-structure/Casinomainui.css b/src/main/resources/ui-structure/Casinomainui.css new file mode 100644 index 0000000..8a9dc97 --- /dev/null +++ b/src/main/resources/ui-structure/Casinomainui.css @@ -0,0 +1,160 @@ +/* Pixel-Art Font (WICHTIG: Nutze eine Monospace-Schrift für Pixel-Look) */ +.root { + -fx-background-color: #000000; + -fx-font-family: "Monospaced", "Courier New"; +} + +.anchor-pane-bg { + -fx-background-color: #ffffff; + /* Optional: Bild entfernen oder als Overlay nutzen */ + -fx-background-image: url("/images/background.png"); + -fx-background-size: cover; + -fx-background-position: center center; + -fx-background-repeat: no-repeat; +} + +/* DIE SCHWEBENDE INFO-BOX (PIXEL-STYLE) */ +.floating-chat-box { + -fx-background-color: #0d9e3b; + /* Pixel-Ränder: Keine Rundung (0px) oder nur sehr kleine Stufen */ + -fx-background-radius: 58; + -fx-border-radius: 50; + -fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10; + -fx-border-width: 12; + -fx-padding: 20; + /* Harter Schatten statt weicher Glow */ + -fx-effect: dropshadow(one-pass-box, rgba(0,0,0,1), 0, 0, 15, 15); +} + +.chat-header { + -fx-text-fill: #ffffff; + -fx-font-size: 22px; + -fx-font-weight: bold; + -fx-padding: 0 0 10 0; +} + +.info-text { + -fx-text-fill: #ffffff; + -fx-font-size: 16px; +} + +/* DER OVALE PIXEL-TISCH */ +.casino-table { + -fx-background-color: #0d9e3b; /* Feste Farbe statt Gradient für Pixel-Look */ + /* Oval durch festen Radius - Pixel-Art-Ovale sind stufig */ + -fx-background-radius: 2000; + -fx-border-radius: 2000; + + /* Dicker, eckiger Holzrand */ + -fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10; + -fx-border-width: 12; + +} + + +.table-title { + -fx-text-fill: #ffffff; + -fx-font-size: 28px; + -fx-font-weight: bold; + -fx-effect: dropshadow(one-pass-box, #000, 0, 0, 3, 3); +} + + + + +/* Hauptcontainer der Statusbox */ +.player-status-pane { + -fx-background-color: rgb(129, 13, 158); + -fx-background-radius: 15; + -fx-border-radius: 15; + + /* -fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10; + -fx-border-width: 2; + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 10, 0, 0, 4);*/ +} + +.player-status-pane:hover { + -fx-translate-y: -3; +} + +/* Die zwei inneren Boxen */ +.status-inner-box-top { + -fx-background-color: rgb(13, 93, 158); + -fx-background-radius: 19; + -fx-border-radius: 15; + -fx-padding: 5 10; + -fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10; + -fx-border-width: 3; + -fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3); +} + +.status-inner-box-midle { + -fx-background-color: rgba(13, 158, 59, 0); +} + +.status-inner-box-bottom { + -fx-background-color: rgb(158, 13, 102); + -fx-background-radius: 19; + -fx-border-radius: 15; + -fx-padding: 5 10; + -fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10; + -fx-border-width: 3; + -fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3); +} + +.table-title { + /* Titel-Text anpassen */ + -fx-text-fill: #ffffff; + -fx-font-size: 32px; + -fx-font-weight: bold; + -fx-effect: dropshadow(one-pass-box, #000, 0, 0, 3, 3); + -fx-padding: 10 0 0 0; /* Abstand nach oben */ +} + +.green-box { + /* Grüne Box anpassen */ + -fx-fill: #4CAF50; + -fx-stroke: #FFFFFF; + -fx-stroke-width: 3; + -fx-effect: dropshadow(one-pass-box, #000, 0, 0, 10, 10); + -fx-padding: 0 0 40 0; +} + +/* EXIT BUTTON */ +.button-exit { + -fx-background-radius: 12; + -fx-border-radius: 12; + -fx-font-family: "Courier New"; + -fx-font-weight: bold; + -fx-border-width: 2; + -fx-padding: 6 15; + -fx-cursor: hand; + -fx-background-color: #333333; + -fx-border-color: #666666; + -fx-text-fill: #CCCCCC; +} +.button-exit:hover { + -fx-translate-y: -3; + -fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3); +} + +/* LOBBY ERSTELLEN BUTTON */ +.button-create-lobby { + -fx-background-radius: 12; + -fx-border-radius: 12; + -fx-font-family: "Courier New"; + -fx-font-weight: bold; + -fx-border-width: 2; + -fx-padding: 6 15; + -fx-cursor: hand; + -fx-background-color: #1a7f2e; + -fx-border-color: #2ecc71; + -fx-text-fill: #ffffff; +} +.button-create-lobby:hover { + -fx-translate-y: -3; + -fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3); + -fx-background-color: #27ae60; +} + + diff --git a/src/main/resources/ui-structure/Casinomainui.fxml b/src/main/resources/ui-structure/Casinomainui.fxml new file mode 100644 index 0000000..2d18a48 --- /dev/null +++ b/src/main/resources/ui-structure/Casinomainui.fxml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ui-structure/lobby_button_translation.json b/src/main/resources/ui-structure/lobby_button_translation.json new file mode 100644 index 0000000..aadb243 --- /dev/null +++ b/src/main/resources/ui-structure/lobby_button_translation.json @@ -0,0 +1,10 @@ +{ + "1": 1240, + "2": 3640, + "3": 7589, + "4": 3982, + "5": 5655, + "6": 6418, + "7": 8586, + "8": 7378 +} \ No newline at end of file diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManagerTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManagerTest.java new file mode 100644 index 0000000..3f4d574 --- /dev/null +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManagerTest.java @@ -0,0 +1,32 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; + +import javafx.scene.layout.GridPane; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +class LobbyButtonGridManagerTest { + LobbyButtonGridManager gridManager; + LobbyButtonTranslationManager translationManager; + GridPane gridPane; + + @BeforeEach + void setUp() { + gridPane = new GridPane(); + translationManager = new LobbyButtonTranslationManager(); + translationManager.getButtonIdToLobbyId().clear(); + gridManager = new LobbyButtonGridManager(gridPane, translationManager); + } + + @Test + void testCreateLobbyReturnsId() { + int lobbyId = gridManager.createLobby(); + assertTrue(lobbyId > 0); + } + + @Test + void testJoinLobbyPlaceholder() { + // check if exception is thrown + assertDoesNotThrow(() -> gridManager.joinLobby(123)); + } +} diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManagerTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManagerTest.java new file mode 100644 index 0000000..4c1e526 --- /dev/null +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManagerTest.java @@ -0,0 +1,47 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; + +import org.junit.jupiter.api.*; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class LobbyButtonTranslationManagerTest { + LobbyButtonTranslationManager manager; + + @BeforeEach + void setUp() { + manager = new LobbyButtonTranslationManager(); + manager.getButtonIdToLobbyId().clear(); + } + + @Test + void testAddLobbyButton() throws Exception { + manager.addLobbyButton(1, 100); + assertEquals(100, manager.getLobbyIdForButton(1)); + } + + @Test + void testRemoveLobbyButton() throws Exception { + manager.addLobbyButton(2, 200); + manager.removeLobbyButton(2); + assertNull(manager.getLobbyIdForButton(2)); + } + + @Test + void testIsFull() throws Exception { + for (int i = 1; i <= 8; i++) { + manager.addLobbyButton(i, 100 + i); + } + assertTrue(manager.isFull()); + Exception ex = assertThrows(Exception.class, () -> manager.addLobbyButton(9, 109)); + assertEquals("Grid is full!", ex.getMessage()); + } + + @Test + void testGetButtonIdToLobbyId() throws Exception { + manager.addLobbyButton(4, 400); + Map map = manager.getButtonIdToLobbyId(); + assertTrue(map.containsKey(4)); + assertEquals(400, map.get(4)); + } +}