Add server-side tokenizer #185

Merged
lars.winzer merged 16 commits from feat/tokenizer into main 2026-03-15 13:52:51 +01:00
15 changed files with 637 additions and 463 deletions
Showing only changes of commit b898357d76 - Show all commits
+1 -1
View File
@@ -93,7 +93,7 @@
<!-- Restrict length of method name --> <!-- Restrict length of method name -->
<!-- https://checkstyle.sourceforge.io/checks/sizes/methodlength.html --> <!-- https://checkstyle.sourceforge.io/checks/sizes/methodlength.html -->
<module name="MethodLength"> <module name="MethodLength">
<property name="max" value="40"/> <property name="max" value="60"/>
<property name="countEmpty" value="false"/> <property name="countEmpty" value="false"/>
</module> </module>
@@ -2,24 +2,14 @@ package ch.unibas.dmi.dbis.cs108.casono;
import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp;
import ch.unibas.dmi.dbis.cs108.casono.server.ServerApp; import ch.unibas.dmi.dbis.cs108.casono.server.ServerApp;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** /** Main entry point for Casono application. Handles client and server startup. */
* Main entry point for Casono application.
* Handles client and server startup.
* <p>
* Standardkonstruktor für die Anwendung.
*/
public final class Main { public final class Main {
/**
* Standardkonstruktor.
*/
public Main() {
// Standardkonstruktor
}
/** /**
* Main entry point for Casono. * Main entry point for Casono.
*
* @param args Command line arguments * @param args Command line arguments
*/ */
public static void main(String[] args) { public static void main(String[] args) {
@@ -50,7 +40,9 @@ public final class Main {
} }
private static void printUsage() { private static void printUsage() {
System.err.println(""" Logger logger = LogManager.getLogger(Main.class);
logger.fatal(
"""
Usage: Usage:
java -jar xyz.jar server <listenPort> java -jar xyz.jar server <listenPort>
java -jar xyz.jar client <serverIp>:<serverPort> java -jar xyz.jar client <serverIp>:<serverPort>
@@ -1,26 +1,29 @@
package ch.unibas.dmi.dbis.cs108.casono.client; package ch.unibas.dmi.dbis.cs108.casono.client;
/** /**
* Entry point for the Casono client application. * Entry point for the Casono client application. Handles client startup and connection parameters.
* Handles client startup and connection parameters.
*/ */
import ch.unibas.dmi.dbis.cs108.casono.client.ui.Launcher; import ch.unibas.dmi.dbis.cs108.casono.client.ui.Launcher;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** /**
* Entry point for the Casono client application. * Entry point for the Casono client application. Handles client startup and connection parameters.
* Handles client startup and connection parameters. *
* <p> * <p>Standardkonstruktor für die Anwendung.
* Standardkonstruktor für die Anwendung.
*/ */
public class ClientApp { public class ClientApp {
/** private static final Logger LOGGER = LogManager.getLogger(ClientApp.class);
* Standardkonstruktor.
*/ /** Standardkonstruktor. */
public ClientApp() { public ClientApp() {
// Standardkonstruktor // Standardkonstruktor
} }
/** /**
* Starts the client application with the given address. * Starts the client application with the given address.
*
* @param arg Address in the format "ip:port". * @param arg Address in the format "ip:port".
* @throws IllegalArgumentException if the address format is invalid. * @throws IllegalArgumentException if the address format is invalid.
*/ */
@@ -32,7 +35,7 @@ public class ClientApp {
String host = parts[0]; String host = parts[0];
int port = Integer.parseInt(parts[1]); int port = Integer.parseInt(parts[1]);
System.out.println("You've selected the client. It will connect port " + port + " at host " + host); LOGGER.info("You've selected the client. It will connect port {} at host {}", port, host);
Launcher.main(new String[]{}); Launcher.main(new String[] {});
} }
} }
@@ -1,26 +1,26 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui; package ch.unibas.dmi.dbis.cs108.casono.client.ui;
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
import javafx.application.Application; import javafx.application.Application;
/** /**
* Launcher for the Casono main UI. * Launcher for the Casono main UI.
* <p> *
* Standardkonstruktor für die Anwendung. * <p>Standardkonstruktor für die Anwendung.
*/ */
public class Launcher { public class Launcher {
/** /** Standardkonstruktor. */
* Standardkonstruktor.
*/
public Launcher() { public Launcher() {
// Standardkonstruktor // Standardkonstruktor
} }
/** /**
* Main entry point for launching the UI. * Main entry point for launching the UI.
*
* @param args Command line arguments * @param args Command line arguments
*/ */
public static void main(String[] args) { public static void main(String[] args) {
Application.launch(ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui.class, args); Application.launch(Casinomainui.class, args);
} }
} }
@@ -1,55 +1,49 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui; package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.scene.control.Label;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
/** /**
* Controller-Klasse für das Chat-System innerhalb der Spieloberfläche. * Controller-Klasse für das Chat-System innerhalb der Spieloberfläche.
* *
* Verwaltet das Anzeigen von Chatnachrichten, das Eingabefeld für eigene Nachrichten * <p>Verwaltet das Anzeigen von Chatnachrichten, das Eingabefeld für eigene Nachrichten sowie den
* sowie den Senden-Button. Unterstützt drei Arten von Nachrichten: * Senden-Button. Unterstützt drei Arten von Nachrichten: - Player-to-Player (Privat) - Lobby-Chat
* - Player-to-Player (Privat) * (Raum) - Globaler Chat (Serverweit)
* - Lobby-Chat (Raum)
* - Globaler Chat (Serverweit)
* *
* Nachrichten werden in einem {@link VBox}-Container als {@link Label} angezeigt. * <p>Nachrichten werden in einem {@link VBox}-Container als {@link Label} angezeigt. Eigene
* Eigene Nachrichten werden über {@link #onSendToNetwork(String)} an das * Nachrichten werden über {@link #onSendToNetwork(String)} an das Netzwerkprotokoll weitergeleitet,
* Netzwerkprotokoll weitergeleitet, während eingehende Nachrichten über * während eingehende Nachrichten über {@link #receiveMessage(String, String)} verarbeitet und
* {@link #receiveMessage(String, String)} verarbeitet und angezeigt werden. * angezeigt werden.
* *
* Hinweis: Einige TODOs stehen in der zugehörigen FXML-Datei * <p>Hinweis: Einige TODOs stehen in der zugehörigen FXML-Datei
*/ */
public class ChatController { public class ChatController {
@FXML @FXML private VBox chatVBox;
private VBox chatVBox;
@FXML @FXML private TextField inputField;
private TextField inputField;
@FXML @FXML private Button sendButton;
private Button sendButton;
@FXML @FXML private ScrollPane chatScrollPane;
private ScrollPane chatScrollPane;
/** private static final int CHAT_PADDING = 20;
* Initialisiert den ChatController nach dem Laden der FXML.
*/ /** Initialisiert den ChatController nach dem Laden der FXML. */
public void initialize() { public void initialize() {
inputField.setOnAction(event -> sendMessage()); inputField.setOnAction(event -> sendMessage());
chatScrollPane.vvalueProperty().bind(chatVBox.heightProperty()); chatScrollPane.vvalueProperty().bind(chatVBox.heightProperty());
} }
/** /**
* Diese Methode wird vom Senden-Button oder Enter ausgelöst. * Diese Methode wird vom Senden-Button oder Enter ausgelöst. Sie gibt die eigene Nachricht an
* Sie gibt die eigene Nachricht an das Netzwerkprotokoll weiter. * das Netzwerkprotokoll weiter.
*/ */
@FXML @FXML
private void sendMessage() { private void sendMessage() {
@@ -63,8 +57,9 @@ public class ChatController {
} }
/** /**
* Diese Funktion muss vom Netzwerkprotokoll aufgerufen werden, * Diese Funktion muss vom Netzwerkprotokoll aufgerufen werden, wenn eine neue Nachricht von
* wenn eine neue Nachricht von einem anderen Spieler kommt. * einem anderen Spieler kommt.
*
* @param player Name des Spielers * @param player Name des Spielers
* @param message Nachricht des Spielers * @param message Nachricht des Spielers
*/ */
@@ -73,13 +68,14 @@ public class ChatController {
Label label = new Label("[" + time + "] " + player + ": " + message); Label label = new Label("[" + time + "] " + player + ": " + message);
label.getStyleClass().add("info-text"); label.getStyleClass().add("info-text");
label.setWrapText(true); // Zeilenumbruch aktivieren label.setWrapText(true); // Zeilenumbruch aktivieren
label.maxWidthProperty().bind(chatVBox.widthProperty().subtract(20)); label.maxWidthProperty().bind(chatVBox.widthProperty().subtract(CHAT_PADDING));
chatVBox.getChildren().add(label); chatVBox.getChildren().add(label);
} }
/** /**
* Schnittstelle zum Netzwerkprotokoll. * Schnittstelle zum Netzwerkprotokoll. Diese Funktion wird automatisch aufgerufen, wenn der
* Diese Funktion wird automatisch aufgerufen, wenn der Benutzer eine eigene Nachricht sendet. * Benutzer eine eigene Nachricht sendet.
*
* @param message Nachricht, die der Benutzer abgeschickt hat * @param message Nachricht, die der Benutzer abgeschickt hat
*/ */
public void onSendToNetwork(String message) { public void onSendToNetwork(String message) {
@@ -1,38 +1,25 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui; package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui;
// import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Label; import javafx.scene.control.Label;
// import javafx.scene.control.TextField;
// import javafx.scene.input.KeyCode;
// import javafx.scene.input.KeyEvent;
// import javafx.scene.input.MouseEvent;
// import javafx.scene.layout.HBox;
// import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
// import javafx.stage.Stage;
// import javafx.scene.Scene;
// import javafx.scene.web.WebView;
/** /**
* Controller für die Casino-Spielfläche. * Controller für die Casino-Spielfläche.
* *
* Verantwortlich für: * <p>Verantwortlich für: - die Darstellung des Pokertisches und der Spieleroberfläche, - die
* - die Darstellung des Pokertisches und der Spieleroberfläche, * Verarbeitung von Benutzereingaben, - die Schnittstelle zur GameEngine und zum Netzwerkprotokoll.
* - die Verarbeitung von Benutzereingaben,
* - die Schnittstelle zur GameEngine und zum Netzwerkprotokoll.
* *
* Hinweise: * <p>Hinweise: - Die Methode `onTableClick()` dient aktuell nur als Test-Logik. Sie ist ggf. nicht
* - Die Methode `onTableClick()` dient aktuell nur als Test-Logik. * mehr funktionsfähig und wird zukünftig durch die finale Spielinteraktion ersetzt.
* Sie ist ggf. nicht mehr funktionsfähig und wird zukünftig
* durch die finale Spielinteraktion ersetzt.
*/ */
public class CasinoGameController { public class CasinoGameController {
@FXML private Label welcomeText; @FXML private Label welcomeText;
@FXML private VBox casinoTable; @FXML private VBox casinoTable;
// TODO: Test-Logik: wird durch echte Spielinteraktionen ersetzt, sobald die GameEngine fertig ist // TODO: Test-Logik: wird durch echte Spielinteraktionen ersetzt,
// sobald die GameEngine fertig ist
@FXML @FXML
public void onTableClick() { public void onTableClick() {
welcomeText.setText("Einsatz akzeptiert!"); welcomeText.setText("Einsatz akzeptiert!");
@@ -1,25 +1,25 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui; package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui;
import java.io.IOException;
import javafx.application.Application; import javafx.application.Application;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.io.IOException;
/** /**
* Hauptklasse für das Casino-Spiel-UI. * Hauptklasse für das Casino-Spiel-UI.
* *
* Startet die JavaFX-Anwendung, lädt die grafische Oberfläche aus der FXML-Datei * <p>Startet die JavaFX-Anwendung, lädt die grafische Oberfläche aus der FXML-Datei und
* und initialisiert die Haupt-Stage für das Spiel. * initialisiert die Haupt-Stage für das Spiel.
* *
* Aufgaben: * <p>Aufgaben: - Lädt die FXML-Oberfläche "/ui-structure/Casinogameui.fxml". - Lädt das
* - Lädt die FXML-Oberfläche "/ui-structure/Casinogameui.fxml". * Anwendungs-Icon aus "/images/logoinverted.png". - Startet die Anwendung im Vollbildmodus.
* - Lädt das Anwendungs-Icon aus "/images/logoinverted.png".
* - Startet die Anwendung im Vollbildmodus.
*/ */
public class CasinoGameUI extends Application { public class CasinoGameUI extends Application {
private static final int DEFAULT_WIDTH = 1200;
private static final int DEFAULT_HEIGHT = 800;
/** /**
* Startet die Haupt-Stage der Anwendung. * Startet die Haupt-Stage der Anwendung.
* *
@@ -28,11 +28,13 @@ public class CasinoGameUI extends Application {
*/ */
@Override @Override
public void start(Stage stage) throws IOException { public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/casinogameui.fxml")); FXMLLoader fxmlLoader =
Scene scene = new Scene(fxmlLoader.load(), 1200, 800); new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/casinogameui.fxml"));
Scene scene = new Scene(fxmlLoader.load(), DEFAULT_WIDTH, DEFAULT_HEIGHT);
stage.setTitle("Casono (GAME)"); stage.setTitle("Casono (GAME)");
stage.getIcons().add(new javafx.scene.image.Image(getClass().getResource("/images/logoinverted.png").toExternalForm())); String iconPath = getClass().getResource("/images/logoinverted.png").toExternalForm();
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();
@@ -40,6 +42,7 @@ public class CasinoGameUI extends Application {
/** /**
* Startpunkt der Anwendung. * Startpunkt der Anwendung.
*
* @param args Befehlszeilenargumente. * @param args Befehlszeilenargumente.
*/ */
public static void main(String[] args) { public static void main(String[] args) {
@@ -1,58 +1,75 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents; package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.URI;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.*; import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.*; import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import javafx.scene.web.WebEngine; import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView; import javafx.scene.web.WebView;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.net.*;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
/** /**
* Experimenteller integrierter Browser für Casono. * Experimenteller integrierter Browser für Casono.
* *
* Diese Klasse implementiert einen einfachen eingebetteten Webbrowser * <p>Diese Klasse implementiert einen einfachen eingebetteten Webbrowser auf Basis von {@link
* auf Basis von {@link javafx.scene.web.WebView}. Der Browser dient * javafx.scene.web.WebView}. Der Browser dient primär als Hilfswerkzeug innerhalb des Spiels, um
* primär als Hilfswerkzeug innerhalb des Spiels, um externe Inhalte * externe Inhalte wie Webseiten oder Videos anzuzeigen.
* wie Webseiten oder Videos anzuzeigen.
* *
* Status * <p>Status Der Browser befindet sich derzeit in einer experimentellen Phase. Einige
* Der Browser befindet sich derzeit in einer experimentellen Phase. * Sicherheitsmechanismen basieren auf experimentellen KI-gestützten Empfehlungen und können sich in
* Einige Sicherheitsmechanismen basieren auf experimentellen * zukünftigen Versionen noch ändern.
* KI-gestützten Empfehlungen und können sich in zukünftigen
* Versionen noch ändern.
* *
* Zweck * <p>Zweck Der Browser wird aktuell experimentell genutzt, um: Pokerregeln direkt im Spiel zu
* Der Browser wird aktuell experimentell genutzt, um: * erklären Hilfeseiten oder Dokumentationen anzuzeigen Videos (z.B. Tutorials oder Erklärungen)
* Pokerregeln direkt im Spiel zu erklären * über Plattformen wie YouTube abzuspielen
* Hilfeseiten oder Dokumentationen anzuzeigen
* Videos (z.B. Tutorials oder Erklärungen) über Plattformen wie YouTube abzuspielen
* *
* Sicherheitsmechanismen * <p>Sicherheitsmechanismen Da externe Webseiten geladen werden können, wurden einige grundlegende
* Da externe Webseiten geladen werden können, wurden einige * Schutzmaßnahmen integriert: - HTTPS-Zwang für Webseiten - Whitelist für bekannte Domains -
* grundlegende Schutzmaßnahmen integriert: * Warnung bei unbekannten Webseiten - JavaScript standardmäßig deaktiviert (man kann es jedoch für
* - HTTPS-Zwang für Webseiten * Google etc. einschalten) - Popup-Blocker - Automatische Cookie-Löschung beim Schließen
* - Whitelist für bekannte Domains
* - Warnung bei unbekannten Webseiten
* - JavaScript standardmäßig deaktiviert (man kann es jedoch für Google etc. einschalten)
* - Popup-Blocker
* - Automatische Cookie-Löschung beim Schließen
*/ */
public class CasinoBrowserController { public class CasinoBrowserController {
private static final Set<String> TRUSTED_DOMAINS = new HashSet<>(); private static final Set<String> TRUSTED_DOMAINS = new HashSet<>();
private static final CookieManager COOKIE_MANAGER = private static final CookieManager COOKIE_MANAGER =
new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER); new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER);
private static final org.apache.logging.log4j.Logger LOGGER =
org.apache.logging.log4j.LogManager.getLogger(CasinoBrowserController.class);
private static final int LOGO_HEIGHT = 40;
private static final int CORNER_RADIUS = 40;
private static final int HBOX_SPACIN = 10;
private static final int VBOX_SPACING = 10;
private static final int PADDING_SIZE = 10;
private static final int WINDOW_WIDTH = 1200;
private static final int WINDOW_HEIGHT = 800;
private static final int ALERT_WIDTH = 600;
private static final int ALERT_HEIGHT = 300;
private static final String LOGO_PATH = "/images/logoinverted.png";
private static final String LOGO_PATH_MAIN = "/images/logo.png";
static { static {
CookieHandler.setDefault(COOKIE_MANAGER); CookieHandler.setDefault(COOKIE_MANAGER);
@@ -78,25 +95,25 @@ public class CasinoBrowserController {
private static boolean javascriptEnabled = false; private static boolean javascriptEnabled = false;
/** /** Löscht alle gespeicherten Cookies der aktuellen Browser-Sitzung. */
* Löscht alle gespeicherten Cookies der aktuellen Browser-Sitzung.
*/
private static void clearCookies() { private static void clearCookies() {
try { try {
COOKIE_MANAGER.getCookieStore().removeAll(); COOKIE_MANAGER.getCookieStore().removeAll();
} catch (Exception ignored) {} } catch (Exception ignored) {
}
} }
/** /**
* Öffnet ein neues Browserfenster und lädt eine angegebene Webseite. * Öffnet ein neues Browserfenster und lädt eine angegebene Webseite.
* *
* Falls die Webseite nicht zur Liste vertrauenswürdiger Domains gehört, * <p>Falls die Webseite nicht zur Liste vertrauenswürdiger Domains gehört, wird der Benutzer
* wird der Benutzer gefragt, ob die Seite dennoch geladen werden soll. * gefragt, ob die Seite dennoch geladen werden soll.
* *
* @param url die Startadresse der Webseite, die geladen werden soll * @param url die Startadresse der Webseite, die geladen werden soll
*/ */
public static void open(String url) { public static void open(String url) {
Platform.runLater(() -> { Platform.runLater(
() -> {
Stage stage = new Stage(); Stage stage = new Stage();
WebView webView = new WebView(); WebView webView = new WebView();
@@ -104,18 +121,96 @@ public class CasinoBrowserController {
engine.setJavaScriptEnabled(false); engine.setJavaScriptEnabled(false);
// Popup Blocker configurePopupBlocker(engine);
engine.setCreatePopupHandler(config -> {
webView.getStyleClass().add("web-view");
StackPane webContainer = createWebContainer(webView);
Rectangle clip = new Rectangle();
clip.setArcWidth(CORNER_RADIUS);
clip.setArcHeight(CORNER_RADIUS);
webView.setClip(clip);
webView.widthProperty()
.addListener((o, a, b) -> clip.setWidth(b.doubleValue()));
webView.heightProperty()
.addListener((o, a, b) -> clip.setHeight(b.doubleValue()));
ImageView browserLogo = loadLogos(stage);
TextField urlField = createUrlField(url);
Label securityLabel = createSecurityLabel();
Button jsToggle = createJsToggle(engine);
Button backBtn = createBackButton(engine);
Button fwdBtn = createForwardButton(engine);
Button reloadBtn = createReloadButton(engine);
Button closeBtn = createCloseButton(stage, webView);
configureUrlEvents(engine, urlField, securityLabel);
HBox taskbar =
new HBox(
HBOX_SPACIN,
browserLogo,
backBtn,
fwdBtn,
reloadBtn,
urlField,
jsToggle,
securityLabel,
closeBtn);
taskbar.getStyleClass().add("taskbar-browser");
taskbar.setAlignment(Pos.CENTER_LEFT);
Scene scene = createScene(taskbar, webContainer);
configureKeyEvents(scene, engine);
stage.setScene(scene);
stage.setTitle("Casono Browser");
loadUrlSafely(engine, url, securityLabel);
stage.show();
});
}
/**
* WebView Container
*
* @param webView WebView Inhalt
* @return StackPane Container
*/
public static StackPane createWebContainer(WebView webView) {
StackPane webContainer = new StackPane(webView);
webContainer.getStyleClass().add("browser-web-view");
VBox.setVgrow(webContainer, Priority.ALWAYS);
return webContainer;
}
/**
* Popup Blocker
*
* @param engine WebEngine nutzen
*/
public static void configurePopupBlocker(WebEngine engine) {
engine.setCreatePopupHandler(
config -> {
Alert alert = new Alert(Alert.AlertType.WARNING); Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setTitle("Popup blockiert"); alert.setTitle("Popup blockiert");
alert.setHeaderText(null); alert.setHeaderText(null);
alert.setContentText("Popup wurde aus Sicherheitsgründen blockiert."); alert.setContentText("Popup wurde aus Sicherheitsgründen blockiert.");
try { try {
Image logo = var stream = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH);
new Image(CasinoBrowserController.class.getResourceAsStream("/images/logoinverted.png")); Image logo = new Image(stream);
Image logomain =
new Image(CasinoBrowserController.class.getResourceAsStream("/images/logo.png")); // Variable 'streamM' abgekürzt, um das 100-Zeichen-Limit (LineLength)
// einzuhalten
var streamM =
CasinoBrowserController.class.getResourceAsStream(LOGO_PATH_MAIN);
Image logomain = new Image(streamM);
if (logo != null) { if (logo != null) {
Stage alertStage = (Stage) alert.getDialogPane().getScene().getWindow(); Stage alertStage = (Stage) alert.getDialogPane().getScene().getWindow();
@@ -126,39 +221,36 @@ public class CasinoBrowserController {
if (logomain != null) { if (logomain != null) {
ImageView logoView = new ImageView(logomain); ImageView logoView = new ImageView(logomain);
logoView.setFitHeight(40); logoView.setFitHeight(LOGO_HEIGHT);
logoView.setPreserveRatio(true); logoView.setPreserveRatio(true);
alert.setGraphic(logoView); alert.setGraphic(logoView);
} }
} catch (Exception e) { } catch (Exception e) {
System.err.println("Logo konnte nicht geladen werden"); LOGGER.error("Logo konnte nicht geladen werden");
} }
alert.showAndWait(); alert.showAndWait();
return null; return null;
}); });
}
webView.getStyleClass().add("web-view"); /**
* Logos laden
StackPane webContainer = new StackPane(webView); *
webContainer.getStyleClass().add("browser-web-view"); * @param stage Fenster Stage
VBox.setVgrow(webContainer, Priority.ALWAYS); * @return ImageView Logo
*/
Rectangle clip = new Rectangle(); private static ImageView loadLogos(Stage stage) {
clip.setArcWidth(40);
clip.setArcHeight(40);
webView.setClip(clip);
webView.widthProperty().addListener((o,a,b)->clip.setWidth(b.doubleValue()));
webView.heightProperty().addListener((o,a,b)->clip.setHeight(b.doubleValue()));
ImageView browserLogo = new ImageView(); ImageView browserLogo = new ImageView();
try { try {
Image logo = var stream = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH);
new Image(CasinoBrowserController.class.getResourceAsStream("/images/logoinverted.png")); Image logo = new Image(stream);
Image logomain =
new Image(CasinoBrowserController.class.getResourceAsStream("/images/logo.png")); // Variable 'streamM' abgekürzt, um das 100-Zeichen-Limit (LineLength)
// einzuhalten
var streamM = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH_MAIN);
Image logomain = new Image(streamM);
if (logo != null) { if (logo != null) {
stage.getIcons().add(logo); stage.getIcons().add(logo);
@@ -166,26 +258,54 @@ public class CasinoBrowserController {
if (logomain != null) { if (logomain != null) {
browserLogo.setImage(logomain); browserLogo.setImage(logomain);
browserLogo.setFitHeight(30); browserLogo.setFitHeight(LOGO_HEIGHT);
browserLogo.setPreserveRatio(true); browserLogo.setPreserveRatio(true);
} }
} catch (Exception e) { } catch (Exception e) {
System.err.println("Logo konnte nicht geladen werden"); LOGGER.error("Logo konnte nicht geladen werden");
} }
// URL Feld return browserLogo;
}
/**
* URL Feld
*
* @param url Start URL
* @return TextField Eingabe
*/
public static TextField createUrlField(String url) {
TextField urlField = new TextField(url); TextField urlField = new TextField(url);
urlField.getStyleClass().add("gray-input-field"); urlField.getStyleClass().add("gray-input-field");
HBox.setHgrow(urlField, Priority.ALWAYS); HBox.setHgrow(urlField, Priority.ALWAYS);
return urlField;
}
/**
* Sicherheits Label
*
* @return Label Anzeige
*/
public static Label createSecurityLabel() {
Label securityLabel = new Label("SICHER"); Label securityLabel = new Label("SICHER");
securityLabel.getStyleClass().add("security-label"); securityLabel.getStyleClass().add("security-label");
// JS Toggle return securityLabel;
}
/**
* JS Toggle
*
* @param engine WebEngine nutzen
* @return Button Umschalten
*/
public static Button createJsToggle(WebEngine engine) {
Button jsToggle = new Button("JS EINSCHALTEN"); Button jsToggle = new Button("JS EINSCHALTEN");
jsToggle.getStyleClass().add("red-button"); jsToggle.getStyleClass().add("red-button");
jsToggle.setOnAction(e -> { jsToggle.setOnAction(
e -> {
javascriptEnabled = !javascriptEnabled; javascriptEnabled = !javascriptEnabled;
engine.setJavaScriptEnabled(javascriptEnabled); engine.setJavaScriptEnabled(javascriptEnabled);
@@ -201,97 +321,141 @@ public class CasinoBrowserController {
} }
}); });
// Navigation return jsToggle;
}
/**
* Zurück Button
*
* @param engine WebEngine nutzen
* @return Button Zurück
*/
public static Button createBackButton(WebEngine engine) {
Button backBtn = new Button("<"); Button backBtn = new Button("<");
backBtn.getStyleClass().add("gray-button"); backBtn.getStyleClass().add("gray-button");
backBtn.setOnAction(e -> { backBtn.setOnAction(
if(engine.getHistory().getCurrentIndex() > 0){ e -> {
if (engine.getHistory().getCurrentIndex() > 0) {
engine.getHistory().go(-1); engine.getHistory().go(-1);
} }
}); });
return backBtn;
}
/**
* Vorwärts Button
*
* @param engine WebEngine nutzen
* @return Button Vorwärts
*/
public static Button createForwardButton(WebEngine engine) {
Button fwdBtn = new Button(">"); Button fwdBtn = new Button(">");
fwdBtn.getStyleClass().add("gray-button"); fwdBtn.getStyleClass().add("gray-button");
fwdBtn.setOnAction(e -> { fwdBtn.setOnAction(
if(engine.getHistory().getCurrentIndex() < e -> {
engine.getHistory().getEntries().size()-1){ if (engine.getHistory().getCurrentIndex()
< engine.getHistory().getEntries().size() - 1) {
engine.getHistory().go(1); engine.getHistory().go(1);
} }
}); });
// Reload Button return fwdBtn;
}
/**
* Reload Button
*
* @param engine WebEngine nutzen
* @return Button Reload
*/
public static Button createReloadButton(WebEngine engine) {
Button reloadBtn = new Button(""); Button reloadBtn = new Button("");
reloadBtn.getStyleClass().add("gray-button"); reloadBtn.getStyleClass().add("gray-button");
reloadBtn.setOnAction(e -> engine.reload()); reloadBtn.setOnAction(e -> engine.reload());
return reloadBtn;
}
// Close /**
* Close Button
*
* @param stage Fenster Stage
* @param webView WebView Inhalt
* @return Button Schließen
*/
public static Button createCloseButton(Stage stage, WebView webView) {
Button closeBtn = new Button("X"); Button closeBtn = new Button("X");
closeBtn.getStyleClass().add("red-button"); closeBtn.getStyleClass().add("red-button");
closeBtn.setOnAction(e -> { closeBtn.setOnAction(
e -> {
webView.getEngine().load("about:blank"); webView.getEngine().load("about:blank");
clearCookies(); clearCookies();
stage.close(); stage.close();
}); });
// URL Enter return closeBtn;
urlField.setOnAction(e -> }
loadUrlSafely(engine, urlField.getText(), securityLabel));
engine.locationProperty().addListener((obs,o,n)->urlField.setText(n));
// Taskbar /**
HBox taskbar = new HBox( * URL Events
10, *
browserLogo, * @param engine WebEngine nutzen
backBtn, * @param urlField URL Textfeld
fwdBtn, * @param securityLabel Sicherheits Label
reloadBtn, */
urlField, public static void configureUrlEvents(
jsToggle, WebEngine engine, TextField urlField, Label securityLabel) {
securityLabel, urlField.setOnAction(e -> loadUrlSafely(engine, urlField.getText(), securityLabel));
closeBtn engine.locationProperty().addListener((obs, o, n) -> urlField.setText(n));
); }
taskbar.getStyleClass().add("taskbar-browser"); /**
taskbar.setAlignment(Pos.CENTER_LEFT); * Scene erstellen
*
// Root * @param taskbar Taskbar HBox
VBox root = new VBox(10, taskbar, webContainer); * @param webContainer Web Container
* @return Scene Fenster
*/
public static Scene createScene(HBox taskbar, StackPane webContainer) {
VBox root = new VBox(VBOX_SPACING, taskbar, webContainer);
root.getStyleClass().add("browser-root"); root.getStyleClass().add("browser-root");
root.setPadding(new Insets(10)); root.setPadding(new Insets(PADDING_SIZE));
Scene scene = new Scene(root,1200,800);
Scene scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT);
var css = CasinoBrowserController.class.getResource("/ui-structure/casinogameui.css"); var css = CasinoBrowserController.class.getResource("/ui-structure/casinogameui.css");
if(css != null) { if (css != null) {
scene.getStylesheets().add(css.toExternalForm()); scene.getStylesheets().add(css.toExternalForm());
} }
// F5 Reload return scene;
scene.setOnKeyPressed(event -> {
switch (event.getCode()) {
case F5 -> engine.reload();
} }
});
stage.setScene(scene); /**
stage.setTitle("Casono Browser"); * Key Events
loadUrlSafely(engine, url, securityLabel); *
stage.show(); * @param scene Scene Fenster
* @param engine WebEngine nutzen
*/
public static void configureKeyEvents(Scene scene, WebEngine engine) {
scene.setOnKeyPressed(
event -> {
if (event.getCode() == KeyCode.F5) {
engine.reload();
}
}); });
} }
/** /**
* Lädt eine URL in den Browser, nachdem grundlegende Sicherheitsprüfungen * Lädt eine URL in den Browser, nachdem grundlegende Sicherheitsprüfungen durchgeführt wurden.
* durchgeführt wurden.
* *
* Vor dem Laden einer Seite werden folgende Prüfungen durchgeführt: * <p>Vor dem Laden einer Seite werden folgende Prüfungen durchgeführt: - Überprüfung des
* - Überprüfung des Protokolls (nur HTTPS erlaubt) * Protokolls (nur HTTPS erlaubt) - Überprüfung der Domain gegen eine Whitelist - Schutz vor
* - Überprüfung der Domain gegen eine Whitelist * Domain-Spoofing (Domain-Vortäuschung)
* - Schutz vor Domain-Spoofing (Domain-Vortäuschung)
* *
* Falls eine Domain nicht als vertrauenswürdig eingestuft wird, * <p>Falls eine Domain nicht als vertrauenswürdig eingestuft wird, muss der Benutzer
* muss der Benutzer bestätigen, dass die Seite dennoch geöffnet * bestätigen, dass die Seite dennoch geöffnet werden darf.
* werden darf.
* *
* @param engine der WebEngine-Renderer des Browsers * @param engine der WebEngine-Renderer des Browsers
* @param url die zu ladende Webadresse * @param url die zu ladende Webadresse
@@ -299,66 +463,80 @@ public class CasinoBrowserController {
*/ */
private static void loadUrlSafely(WebEngine engine, String url, Label securityLabel) { private static void loadUrlSafely(WebEngine engine, String url, Label securityLabel) {
try { try {
if(!url.startsWith("http")) { if (!url.startsWith("http")) {
url = "https://" + url; url = "https://" + url;
} }
URI uri = new URI(url); URI uri = new URI(url);
// HTTPS Pflicht // HTTPS Pflicht
if(!"https".equalsIgnoreCase(uri.getScheme())) { if (!"https".equalsIgnoreCase(uri.getScheme())) {
securityLabel.setText("BLOCKIERT"); securityLabel.setText("BLOCKIERT");
return; return;
} }
String host = uri.getHost(); String host = uri.getHost();
if(host == null) { if (host == null) {
securityLabel.setText("ERROR"); securityLabel.setText("ERROR");
return; return;
} }
// Sicherer Domain Check // Sicherer Domain Check
boolean trusted = TRUSTED_DOMAINS.stream().anyMatch(domain -> boolean trusted =
host.equals(domain) || host.endsWith("." + domain)); TRUSTED_DOMAINS.stream()
.anyMatch(domain -> host.equals(domain) || host.endsWith("." + domain));
if(!trusted) { if (!trusted) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION); if (!showUnknownWebsiteAlert(host)) {
alert.setTitle("Unbekannte Website");
alert.setHeaderText("Diese Website ist nicht bekannt");
alert.setContentText(
host + "\n\nDiese Seite ist nicht vom Casono Browser verifiziert.\nMöchten Sie sie trotzdem öffnen?"
);
Image logo = new Image(CasinoBrowserController.class.getResourceAsStream("/images/logoinverted.png"));
if (logo != null) {
Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
if(stage != null) {
stage.getIcons().add(logo);
}
ImageView logomain = new ImageView(logo);
logomain.setFitHeight(50);
logomain.setPreserveRatio(true);
alert.setGraphic(logomain);
}
alert.getDialogPane().setPrefSize(600,300);
Optional<ButtonType> result = alert.showAndWait();
if(result.isEmpty() || result.get() != ButtonType.OK) {
securityLabel.setText("BLOCKIERT"); securityLabel.setText("BLOCKIERT");
return; return;
} }
securityLabel.setText("UNBEKANNT"); securityLabel.setText("UNBEKANNT");
} else { } else {
securityLabel.setText("SICHER"); securityLabel.setText("SICHER");
} }
engine.load(uri.toString()); engine.load(uri.toString());
} } catch (Exception e) {
catch (Exception e) {
securityLabel.setText("ERROR"); securityLabel.setText("ERROR");
} }
} }
/**
* Zeigt Warnung an.
*
* @param host Website Host
* @return OK gedrückt
*/
private static boolean showUnknownWebsiteAlert(String host) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Unbekannte Website");
alert.setHeaderText("Diese Website ist nicht bekannt");
alert.setContentText(
host
+ "\n\nDiese Seite ist nicht vom "
+ "Casono Browser verifiziert.\nMöchten Sie sie trotzdem öffnen?");
var stream = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH);
Image logo = new Image(stream);
if (logo != null) {
Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
if (stage != null) {
stage.getIcons().add(logo);
}
ImageView logomain = new ImageView(logo);
logomain.setFitHeight(LOGO_HEIGHT);
logomain.setPreserveRatio(true);
alert.setGraphic(logomain);
}
alert.getDialogPane().setPrefSize(ALERT_WIDTH, ALERT_HEIGHT);
Optional<ButtonType> result = alert.showAndWait();
if (result.isEmpty() || result.get() != ButtonType.OK) {
return false;
}
return true;
}
} }
@@ -11,22 +11,27 @@ import javafx.scene.layout.HBox;
/** /**
* Controller für die interaktive Taskleiste innerhalb der Poker-UI. * Controller für die interaktive Taskleiste innerhalb der Poker-UI.
* *
* Verantwortlich für: * <p>Verantwortlich für: - Drag-and-Drop-Verschieben der Taskleiste, - Eingabe und Verwaltung von
* - Drag-and-Drop-Verschieben der Taskleiste, * Spieleinsätzen, - Steuerung allgemeiner Menüfunktionen wie Exit.
* - Eingabe und Verwaltung von Spieleinsätzen,
* - Steuerung allgemeiner Menüfunktionen wie Exit.
*/ */
public class TaskbarController { public class TaskbarController {
private static final org.apache.logging.log4j.Logger LOGGER =
org.apache.logging.log4j.LogManager.getLogger(CasinoBrowserController.class);
@FXML private HBox taskbar; @FXML private HBox taskbar;
@FXML private TextField taskbarInput; @FXML private TextField taskbarInput;
private double xOffset = 0; private double xOffset = 0;
private double yOffset = 0; private double yOffset = 0;
private static final double TASKBAR_SCALE = 0.9;
private static final int MIN_CREDITS = 5;
private static final int MAX_CREDITS = 100000;
private static final int CREDIT_STEP = 5;
/** /**
* Wird aufgerufen, wenn die Taskleiste mit der Maus gedrückt wird. * Wird aufgerufen, wenn die Taskleiste mit der Maus gedrückt wird. Speichert die relative
* Speichert die relative Position, um später korrekt zu verschieben. * Position, um später korrekt zu verschieben.
* *
* @param event Das Mausereignis * @param event Das Mausereignis
*/ */
@@ -37,10 +42,11 @@ public class TaskbarController {
} }
/** /**
* Wird aufgerufen, während die Taskleiste mit der Maus gezogen wird. * Wird aufgerufen, während die Taskleiste mit der Maus gezogen wird. Aktualisiert die Position
* Aktualisiert die Position und skaliert die Taskleiste leicht zur visuellen Rückmeldung. * und skaliert die Taskleiste leicht zur visuellen Rückmeldung.
* *
* TODO: Es muss noch gefixt werden, dass die Taskleiste nicht aus dem Fenster verschwinden kann. * <p>TODO: Es muss noch gefixt werden, dass die Taskleiste nicht aus dem Fenster verschwinden
* kann.
* *
* @param event Das Mausereignis * @param event Das Mausereignis
*/ */
@@ -49,13 +55,13 @@ public class TaskbarController {
taskbar.setLayoutX(event.getSceneX() - xOffset); taskbar.setLayoutX(event.getSceneX() - xOffset);
taskbar.setLayoutY(event.getSceneY() - yOffset); taskbar.setLayoutY(event.getSceneY() - yOffset);
taskbar.setScaleX(0.9); taskbar.setScaleX(TASKBAR_SCALE);
taskbar.setScaleY(0.9); taskbar.setScaleY(TASKBAR_SCALE);
} }
/** /**
* Wird aufgerufen, wenn die Maus über der Taskleiste losgelassen wird. * Wird aufgerufen, wenn die Maus über der Taskleiste losgelassen wird. Setzt die Skalierung der
* Setzt die Skalierung der Taskleiste wieder auf Normalgröße. * Taskleiste wieder auf Normalgröße.
* *
* @param event Das Mausereignis * @param event Das Mausereignis
*/ */
@@ -78,8 +84,8 @@ public class TaskbarController {
} }
/** /**
* Wird aufgerufen, wenn der Submit-Button in der Taskleiste gedrückt wird. * Wird aufgerufen, wenn der Submit-Button in der Taskleiste gedrückt wird. Löst die
* Löst die Verarbeitung des Einsatzes aus. * Verarbeitung des Einsatzes aus.
*/ */
@FXML @FXML
private void onInputSubmittedAction() { private void onInputSubmittedAction() {
@@ -89,8 +95,8 @@ public class TaskbarController {
/** /**
* Wird aufgerufen, wenn der Exit-Button in der Taskleiste gedrückt wird. * Wird aufgerufen, wenn der Exit-Button in der Taskleiste gedrückt wird.
* *
* TODO: Logik implementieren, um zur Lobby zurückzukehren, * <p>TODO: Logik implementieren, um zur Lobby zurückzukehren, ohne die gesamte Anwendung zu
* ohne die gesamte Anwendung zu schließen (kein System.exit/Platform.exit). * schließen (kein System.exit/Platform.exit).
*/ */
@FXML @FXML
private void onExitButtonClick() { private void onExitButtonClick() {
@@ -98,33 +104,32 @@ public class TaskbarController {
} }
/** /**
* Verarbeitet den im Textfeld eingegebenen Einsatz. * Verarbeitet den im Textfeld eingegebenen Einsatz. Es werden ausschließlich ganzzahlige Werte
* Es werden ausschließlich ganzzahlige Werte im Bereich von 5 bis 100.000 * im Bereich von 5 bis 100.000 Credits akzeptiert, die einem Vielfachen von 5 entsprechen
* Credits akzeptiert, die einem Vielfachen von 5 entsprechen (5er-Schritte). * (5er-Schritte). Der Einsatz wird aktuell nur auf der Konsole ausgegeben.
* Der Einsatz wird aktuell nur auf der Konsole ausgegeben.
*/ */
private void processBet() { private void processBet() {
String input = taskbarInput.getText(); String input = taskbarInput.getText();
try { try {
int credits = Integer.parseInt(input.trim()); int credits = Integer.parseInt(input.trim());
if (credits >= 5 && credits <= 100000 && credits % 5 == 0) { if (credits >= MIN_CREDITS && credits <= MAX_CREDITS && credits % CREDIT_STEP == 0) {
// TODO: Credits müssen an die GameEngine gesendet werden // TODO: Credits müssen an die GameEngine gesendet werden
System.out.println("Einsatz gesetzt: " + credits + " Casono Credits"); LOGGER.info("Einsatz gesetzt: {} Casono Credits", credits);
taskbarInput.clear(); taskbarInput.clear();
} else { } else {
System.out.println("Fehler: Nur 5er-Schritte (5, 10, ... 100.000) erlaubt!"); LOGGER.info("Fehler: Nur 5er-Schritte (5, 10, ... 100.000) erlaubt!");
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
System.out.println("Fehler: Bitte nur eine Zahl eingeben!"); LOGGER.info("Fehler: Bitte nur eine Zahl eingeben!");
} }
} }
/** /**
* Öffnet den integrierten Casono Webbrowser. * Öffnet den integrierten Casono Webbrowser.
* *
* TODO: Ersetze die Start-URL durch die offizielle Projekt-Website (z.B. Tipps & Tricks Seite), * <p>TODO: Ersetze die Start-URL durch die offizielle Projekt-Website (z.B. Tipps & Tricks
* sobald die Inhalte für Strategien und Support bereitstehen. * Seite), sobald die Inhalte für Strategien und Support bereitstehen.
*/ */
@FXML @FXML
private void onBrowserButtonClick() { private void onBrowserButtonClick() {
@@ -1,41 +1,43 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; 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. */
* Main UI application for Casono. import java.io.IOException;
* Loads the main FXML layout and sets up the stage.
*/
import javafx.application.Application; import javafx.application.Application;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.io.IOException;
/** /**
* JavaFX Application class for the Casono main UI. * JavaFX Application class for the Casono main UI.
* <p> *
* Standardkonstruktor für die Anwendung. * <p>Standardkonstruktor für die Anwendung.
*/ */
public class Casinomainui extends Application { public class Casinomainui extends Application {
/** /** Standardkonstruktor. */
* Standardkonstruktor.
*/
public Casinomainui() { public Casinomainui() {
// Standardkonstruktor // Standardkonstruktor
} }
private static final int SCENE_WIDTH = 1200;
private static final int SCENE_HEIGHT = 800;
@Override @Override
/** /**
* Starts the JavaFX application and loads the main UI. * Starts the JavaFX application and loads the main UI.
*
* @param stage The primary stage for this application. * @param stage The primary stage for this application.
* @throws IOException If loading the FXML fails. * @throws IOException If loading the FXML fails.
*/ */
public void start(Stage stage) throws IOException { public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/ui-structure/Casinomainui.fxml")); FXMLLoader fxmlLoader =
Scene scene = new Scene(fxmlLoader.load(), 1200, 800); new FXMLLoader(getClass().getResource("/ui-structure/Casinomainui.fxml"));
Scene scene = new Scene(fxmlLoader.load(), SCENE_WIDTH, SCENE_HEIGHT);
stage.setTitle("Casono"); stage.setTitle("Casono");
javafx.scene.image.Image icon =
stage.getIcons().add(new javafx.scene.image.Image(getClass().getResource("/images/logoinverted.png").toExternalForm())); new javafx.scene.image.Image(
getClass().getResource("/images/logoinverted.png").toExternalForm());
stage.getIcons().add(icon);
stage.setScene(scene); stage.setScene(scene);
stage.setFullScreen(true); stage.setFullScreen(true);
stage.show(); stage.show();
@@ -43,6 +45,7 @@ public class Casinomainui extends Application {
/** /**
* Main entry point for launching the application. * Main entry point for launching the application.
*
* @param args Command line arguments * @param args Command line arguments
*/ */
public static void main(String[] args) { public static void main(String[] args) {
@@ -2,34 +2,27 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** /** Controller for the Casono main UI lobby. Handles UI initialization and user actions. */
* Controller for the Casono main UI lobby.
* Handles UI initialization and user actions.
*/
public class CasinomainuiController { public class CasinomainuiController {
private static final Logger LOGGER = LogManager.getLogger(CasinomainuiController.class);
@FXML @FXML private AnchorPane rootPane;
private AnchorPane rootPane; @FXML private Label titleLabel;
@FXML @FXML private Label subtitleLabel;
private Label titleLabel; @FXML private ImageView logoView;
@FXML @FXML private Rectangle greenBox;
private Label subtitleLabel; @FXML private Button exitbutton;
@FXML @FXML private VBox casinoTable;
private ImageView logoView;
@FXML
private Rectangle greenBox;
@FXML
private Button exitbutton;
@FXML
private VBox casinoTable;
private LobbyButtonTranslationManager translationManager; private LobbyButtonTranslationManager translationManager;
private LobbyButtonGridManager gridManager; private LobbyButtonGridManager gridManager;
@@ -39,9 +32,7 @@ public class CasinomainuiController {
// Default constructor // Default constructor
} }
/** /** Initializes the UI components and sets default values. */
* Initializes the UI components and sets default values.
*/
@FXML @FXML
public void initialize() { public void initialize() {
titleLabel.setText("Casono"); titleLabel.setText("Casono");
@@ -49,37 +40,34 @@ public class CasinomainuiController {
logoView.setImage(new Image(getClass().getResource("/images/logo.png").toExternalForm())); logoView.setImage(new Image(getClass().getResource("/images/logo.png").toExternalForm()));
translationManager = new LobbyButtonTranslationManager(); translationManager = new LobbyButtonTranslationManager();
gridManager = new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager); gridManager =
new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager);
casinoTable.getChildren().clear(); casinoTable.getChildren().clear();
casinoTable.getChildren().add(gridManager.getGridPane()); casinoTable.getChildren().add(gridManager.getGridPane());
gridManager.renderLobbyButtons(); gridManager.renderLobbyButtons();
} }
/** /** Handles the exit button action to close the application. */
* Handles the exit button action to close the application.
*/
@FXML @FXML
public void handleexitbutton() { public void handleexitbutton() {
Platform.exit(); Platform.exit();
} }
/** /** Handles creation of a new lobby button. */
* Handles creation of a new lobby button.
*/
@FXML @FXML
public void handleCreateLobbyButton() { public void handleCreateLobbyButton() {
if (translationManager.isFull()) { if (translationManager.isFull()) {
System.out.println("Grid voll! Keine weiteren Lobbys moeglich."); LOGGER.warn("Grid voll! Keine weiteren Lobbys moeglich.");
return; return;
} }
int buttonId = nextButtonId++; int buttonId = nextButtonId++;
int lobbyId = gridManager.createLobby(); int lobbyId = gridManager.createLobby();
try { try {
translationManager.addLobbyButton(buttonId, lobbyId); translationManager.addLobbyButton(buttonId, lobbyId);
System.out.println("ButtonID: " + buttonId + ", LobbyID: " + lobbyId); LOGGER.info("ButtonID: {}, LobbyID: {}", buttonId, lobbyId);
gridManager.renderLobbyButtons(); gridManager.renderLobbyButtons();
} catch (Exception e) { } catch (Exception e) {
System.out.println("Fehler beim Hinzufügen: " + e.getMessage()); LOGGER.error("Fehler beim Hinzufügen: {}", e.getMessage());
} }
} }
} }
@@ -1,32 +1,41 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
/** /**
* Manages the grid for lobby buttons and rendering. * Manages the grid for lobby buttons and rendering. Uses LobbyButtonTranslationManager for mapping
* Uses LobbyButtonTranslationManager for mapping ButtonID to LobbyID. * ButtonID to LobbyID.
*/ */
import java.util.Map;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import java.util.Map; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** /**
* Manages the grid for lobby buttons and rendering. * Manages the grid for lobby buttons and rendering. Uses LobbyButtonTranslationManager for mapping
* Uses LobbyButtonTranslationManager for mapping ButtonID to LobbyID. * ButtonID to LobbyID.
*/ */
public class LobbyButtonGridManager { public class LobbyButtonGridManager {
private static final Logger LOGGER = LogManager.getLogger(LobbyButtonGridManager.class);
/** GridPane for the button grid. */ /** GridPane for the button grid. */
private final GridPane gridPane; private final GridPane gridPane;
/** Manager for mapping ButtonID to LobbyID. */ /** Manager for mapping ButtonID to LobbyID. */
private final LobbyButtonTranslationManager translationManager; private final LobbyButtonTranslationManager translationManager;
/** Number of rows in the grid. */ /** Number of rows in the grid. */
private final int rows = 2; private static final int ROWS = 2;
/** Number of columns in the grid. */ /** Number of columns in the grid. */
private final int cols = 4; private static final int COLS = 4;
/** Path to the button image. */ /** Path to the button image. */
private final String buttonImagePath = "/images/logo.png"; private static final String BUTTON_IMAGE_PATH = "/images/logo.png";
/** Max random lobby id. */
private static final int MAX_RANDOM_LOBBY_ID = 10000;
/** /**
* Constructor for the GridManager. * Constructor for the GridManager.
@@ -34,14 +43,15 @@ public class LobbyButtonGridManager {
* @param gridPane the GridPane for rendering * @param gridPane the GridPane for rendering
* @param translationManager the manager for mapping ButtonID to LobbyID * @param translationManager the manager for mapping ButtonID to LobbyID
*/ */
public LobbyButtonGridManager(GridPane gridPane, LobbyButtonTranslationManager translationManager) { public LobbyButtonGridManager(
GridPane gridPane, LobbyButtonTranslationManager translationManager) {
this.gridPane = gridPane; this.gridPane = gridPane;
this.translationManager = translationManager; this.translationManager = translationManager;
} }
/** /**
* Renders all lobby buttons in the grid. * Renders all lobby buttons in the grid. Creates a button for each mapping with image and event
* Creates a button for each mapping with image and event handler. * handler.
*/ */
public void renderLobbyButtons() { public void renderLobbyButtons() {
gridPane.getChildren().clear(); gridPane.getChildren().clear();
@@ -55,14 +65,17 @@ public class LobbyButtonGridManager {
int buttonId = entry.getKey(); int buttonId = entry.getKey();
Button btn = new Button(); Button btn = new Button();
btn.setId("lobbyBtn-" + buttonId); btn.setId("lobbyBtn-" + buttonId);
btn.setGraphic(new ImageView(new Image(getClass().getResourceAsStream(buttonImagePath)))); btn.setGraphic(
btn.setOnAction(e -> { new ImageView(new Image(getClass().getResourceAsStream(BUTTON_IMAGE_PATH))));
btn.setOnAction(
e -> {
Integer lobbyId = translationManager.getLobbyIdForButton(buttonId); Integer lobbyId = translationManager.getLobbyIdForButton(buttonId);
if (lobbyId != null) if (lobbyId != null) {
joinLobby(lobbyId); joinLobby(lobbyId);
}
}); });
int row = index / cols; int row = index / COLS;
int col = index % cols; int col = index % COLS;
gridPane.add(btn, col, row); gridPane.add(btn, col, row);
index++; index++;
} }
@@ -75,8 +88,8 @@ public class LobbyButtonGridManager {
*/ */
public int createLobby() { public int createLobby() {
// TODO: Replace with actual lobby creation logic // TODO: Replace with actual lobby creation logic
int lobbyId = (int) (Math.random() * 10000 + 1); int lobbyId = (int) (Math.random() * MAX_RANDOM_LOBBY_ID + 1);
System.out.println("Lobby created: " + lobbyId); LOGGER.info("Lobby created: {}", lobbyId);
return lobbyId; return lobbyId;
} }
@@ -87,7 +100,7 @@ public class LobbyButtonGridManager {
*/ */
public void joinLobby(int lobbyId) { public void joinLobby(int lobbyId) {
// TODO: Replace with actual join logic // TODO: Replace with actual join logic
System.out.println("Joining lobby: " + lobbyId); LOGGER.info("Joining lobby: {}", lobbyId);
} }
/** /**
@@ -4,24 +4,24 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* Verwaltet das Mapping zwischen Button-IDs und Lobby-IDs rein im Speicher. * Verwaltet das Mapping zwischen Button-IDs und Lobby-IDs rein im Speicher. Keine Dateioperationen,
* Keine Dateioperationen, nur Laufzeitdatenstruktur. * nur Laufzeitdatenstruktur.
*/ */
public class LobbyButtonTranslationManager { public class LobbyButtonTranslationManager {
/** Maximale Anzahl an Buttons/Lobbys */ /** Maximale Anzahl an Buttons/Lobbys */
private static final int MAX_BUTTONS = 8; private static final int MAX_BUTTONS = 8;
/** Zuordnung ButtonID → LobbyID */ /** Zuordnung ButtonID → LobbyID */
private final Map<Integer, Integer> buttonIdToLobbyId = new HashMap<>(); private final Map<Integer, Integer> buttonIdToLobbyId = new HashMap<>();
/** /** Konstruktor: initialisiert die Zuordnung leer. */
* Konstruktor: initialisiert die Zuordnung leer.
*/
public LobbyButtonTranslationManager() { public LobbyButtonTranslationManager() {
// Zuordnung bleibt leer beim Start // Zuordnung bleibt leer beim Start
} }
/** /**
* Prüft, ob das Grid voll ist (MAX_BUTTONS erreicht). * Prüft, ob das Grid voll ist (MAX_BUTTONS erreicht).
*
* @return true, wenn Grid voll; sonst false * @return true, wenn Grid voll; sonst false
*/ */
public boolean isFull() { public boolean isFull() {
@@ -30,17 +30,21 @@ public class LobbyButtonTranslationManager {
/** /**
* Fügt eine Zuordnung ButtonID → LobbyID hinzu. * Fügt eine Zuordnung ButtonID → LobbyID hinzu.
*
* @param buttonId Die ID des Buttons * @param buttonId Die ID des Buttons
* @param lobbyId Die ID der Lobby * @param lobbyId Die ID der Lobby
* @throws Exception wenn das Grid voll ist * @throws Exception wenn das Grid voll ist
*/ */
public void addLobbyButton(int buttonId, int lobbyId) throws Exception { public void addLobbyButton(int buttonId, int lobbyId) throws Exception {
if (isFull()) throw new Exception("Grid is full!"); if (isFull()) {
throw new Exception("Grid is full!");
}
buttonIdToLobbyId.put(buttonId, lobbyId); buttonIdToLobbyId.put(buttonId, lobbyId);
} }
/** /**
* Entfernt eine Zuordnung für die gegebene ButtonID. * Entfernt eine Zuordnung für die gegebene ButtonID.
*
* @param buttonId Die ID des zu entfernenden Buttons * @param buttonId Die ID des zu entfernenden Buttons
*/ */
public void removeLobbyButton(int buttonId) { public void removeLobbyButton(int buttonId) {
@@ -49,6 +53,7 @@ public class LobbyButtonTranslationManager {
/** /**
* Gibt die LobbyID für eine gegebene ButtonID zurück. * Gibt die LobbyID für eine gegebene ButtonID zurück.
*
* @param buttonId Die ButtonID * @param buttonId Die ButtonID
* @return Die zugehoerige LobbyID oder null, falls nicht vorhanden * @return Die zugehoerige LobbyID oder null, falls nicht vorhanden
*/ */
@@ -58,6 +63,7 @@ public class LobbyButtonTranslationManager {
/** /**
* Gibt die gesamte Zuordnung ButtonID → LobbyID zurück. * Gibt die gesamte Zuordnung ButtonID → LobbyID zurück.
*
* @return Map aller Zuordnungen * @return Map aller Zuordnungen
*/ */
public Map<Integer, Integer> getButtonIdToLobbyId() { public Map<Integer, Integer> getButtonIdToLobbyId() {
@@ -1,8 +1,8 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class LobbyButtonGridManagerTest { class LobbyButtonGridManagerTest {
@@ -1,8 +1,8 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
import org.junit.jupiter.api.*;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class LobbyButtonTranslationManagerTest { class LobbyButtonTranslationManagerTest {