diff --git a/Projekte/monopoly/client/build.gradle b/Projekte/monopoly/client/build.gradle
new file mode 100644
index 0000000..7582682
--- /dev/null
+++ b/Projekte/monopoly/client/build.gradle
@@ -0,0 +1,22 @@
+plugins {
+ id 'buildlogic.jme-application-conventions'
+}
+
+description = 'Monopoly Client'
+
+dependencies {
+ implementation project(":jme-common")
+ implementation project(":monopoly:model")
+
+ implementation libs.jme3.desktop
+
+ runtimeOnly libs.jme3.awt.dialogs
+ runtimeOnly libs.jme3.plugins
+ runtimeOnly libs.jme3.jogg
+ runtimeOnly libs.jme3.testdata
+}
+
+application {
+ mainClass = 'pp.monopoly.client.MonopolyApp'
+ applicationName = 'monopoly'
+}
diff --git a/Projekte/monopoly/client/client.properties b/Projekte/monopoly/client/client.properties
new file mode 100644
index 0000000..4403fa3
--- /dev/null
+++ b/Projekte/monopoly/client/client.properties
@@ -0,0 +1,73 @@
+########################################
+## Programming project code
+## UniBw M, 2022, 2023, 2024
+## www.unibw.de/inf2
+## (c) Mark Minas (mark.minas@unibw.de)
+########################################
+#
+# Battleship client configuration
+#
+# Specifies the map used by the opponent in single mode.
+# Single mode is activated if this property is set.
+#map.opponent=maps/map2.json
+#
+# Specifies the map used by the player in single mode.
+# The player must define their own map if this property is not set.
+map.own=maps/map1.json
+#
+# Coordinates of the shots fired by the RobotClient in the order listed.
+# Example:
+# 2, 0,\
+# 2, 1,\
+# 2, 2,\
+# 2, 3
+# defines four shots, namely at the coordinates
+# (x=2, y=0), (x=2, y=1), (x=2, y=2), and (x=2, y=3)
+robot.targets=2, 0,\
+ 2, 1,\
+ 2, 2,\
+ 2, 3
+#
+# Delay in milliseconds between each shot fired by the RobotClient.
+robot.delay=500
+#
+# The dimensions of the game map used in single mode.
+# 'map.width' defines the number of columns, and 'map.height' defines the number of rows.
+map.width=10
+map.height=10
+#
+# The number of ships of each length available in single mode.
+# The value is a comma-separated list where each element corresponds to the number of ships
+# with a specific length. For example:
+# ship.nums=4, 3, 2, 1
+# This configuration means:
+# - 4 ships of length 1
+# - 3 ships of length 2
+# - 2 ships of length 3
+# - 1 ship of length 4
+ship.nums=4, 3, 2, 1
+#
+# Screen settings
+#
+# Color of the text displayed at the top of the overlay.
+# The format is (red, green, blue, alpha) where each value ranges from 0 to 1.
+overlay.top.color=1, 1, 1, 1
+#
+# Application settings configuration
+# Determines whether the settings window is shown at startup.
+settings.show=false
+#
+# Specifies the width of the application window in pixels.
+settings.resolution.width=1200
+#
+# Specifies the height of the application window in pixels.
+settings.resolution.height=800
+#
+# Determines whether the application runs in full-screen mode.
+settings.full-screen=false
+#
+# Enables or disables gamma correction to improve color accuracy.
+settings.use-gamma-correction=true
+#
+# Indicates whether the statistics window is displayed during gameplay.
+statistics.show=false
diff --git a/Projekte/monopoly/client/logging.properties b/Projekte/monopoly/client/logging.properties
new file mode 100644
index 0000000..cbbaec0
--- /dev/null
+++ b/Projekte/monopoly/client/logging.properties
@@ -0,0 +1,8 @@
+handlers=java.util.logging.ConsoleHandler
+.level=INFO
+pp.level=FINE
+com.jme3.network.level=INFO
+;com.jme3.util.TangentBinormalGenerator.level=SEVERE
+java.util.logging.ConsoleHandler.level=FINER
+java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+;java.util.logging.SimpleFormatter.format=[%4$s %2$s] %5$s%n
diff --git a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/GameSound.java b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/GameSound.java
new file mode 100644
index 0000000..d74df40
--- /dev/null
+++ b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/GameSound.java
@@ -0,0 +1,135 @@
+////////////////////////////////////////
+// Programming project code
+// UniBw M, 2022, 2023, 2024
+// www.unibw.de/inf2
+// (c) Mark Minas (mark.minas@unibw.de)
+////////////////////////////////////////
+
+package pp.monopoly.client;
+
+import com.jme3.app.Application;
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.asset.AssetLoadException;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.audio.AudioData;
+import com.jme3.audio.AudioNode;
+import pp.monopoly.notification.GameEventListener;
+import pp.monopoly.notification.SoundEvent;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.prefs.Preferences;
+
+import static pp.util.PreferencesUtils.getPreferences;
+
+/**
+ * An application state that plays sounds.
+ */
+public class GameSound extends AbstractAppState implements GameEventListener {
+ private static final Logger LOGGER = System.getLogger(GameSound.class.getName());
+ private static final Preferences PREFERENCES = getPreferences(GameSound.class);
+ private static final String ENABLED_PREF = "enabled"; //NON-NLS
+
+ private AudioNode splashSound;
+ private AudioNode shipDestroyedSound;
+ private AudioNode explosionSound;
+
+ /**
+ * Checks if sound is enabled in the preferences.
+ *
+ * @return {@code true} if sound is enabled, {@code false} otherwise.
+ */
+ public static boolean enabledInPreferences() {
+ return PREFERENCES.getBoolean(ENABLED_PREF, true);
+ }
+
+ /**
+ * Toggles the game sound on or off.
+ */
+ public void toggleSound() {
+ setEnabled(!isEnabled());
+ }
+
+ /**
+ * Sets the enabled state of this AppState.
+ * Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)}
+ *
+ * @param enabled {@code true} to enable the AppState, {@code false} to disable it.
+ */
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (isEnabled() == enabled) return;
+ super.setEnabled(enabled);
+ LOGGER.log(Level.INFO, "Sound enabled: {0}", enabled); //NON-NLS
+ PREFERENCES.putBoolean(ENABLED_PREF, enabled);
+ }
+
+ /**
+ * Initializes the sound effects for the game.
+ * Overrides {@link AbstractAppState#initialize(AppStateManager, Application)}
+ *
+ * @param stateManager The state manager
+ * @param app The application
+ */
+ @Override
+ public void initialize(AppStateManager stateManager, Application app) {
+ super.initialize(stateManager, app);
+ shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS
+ splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS
+ explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
+ }
+
+ /**
+ * Loads a sound from the specified file.
+ *
+ * @param app The application
+ * @param name The name of the sound file.
+ * @return The loaded AudioNode.
+ */
+ private AudioNode loadSound(Application app, String name) {
+ try {
+ final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
+ sound.setLooping(false);
+ sound.setPositional(false);
+ return sound;
+ }
+ catch (AssetLoadException | AssetNotFoundException ex) {
+ LOGGER.log(Level.ERROR, ex.getMessage(), ex);
+ }
+ return null;
+ }
+
+ /**
+ * Plays the splash sound effect.
+ */
+ public void splash() {
+ if (isEnabled() && splashSound != null)
+ splashSound.playInstance();
+ }
+
+ /**
+ * Plays the explosion sound effect.
+ */
+ public void explosion() {
+ if (isEnabled() && explosionSound != null)
+ explosionSound.playInstance();
+ }
+
+ /**
+ * Plays sound effect when a ship has been destroyed.
+ */
+ public void shipDestroyed() {
+ if (isEnabled() && shipDestroyedSound != null)
+ shipDestroyedSound.playInstance();
+ }
+
+ @Override
+ public void receivedEvent(SoundEvent event) {
+ switch (event.sound()) {
+ case EXPLOSION -> explosion();
+ case SPLASH -> splash();
+ case DESTROYED_SHIP -> shipDestroyed();
+ }
+ }
+}
diff --git a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/Menu.java b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/Menu.java
new file mode 100644
index 0000000..34a281f
--- /dev/null
+++ b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/Menu.java
@@ -0,0 +1,59 @@
+////////////////////////////////////////
+// Programming project code
+// UniBw M, 2022, 2023, 2024
+// www.unibw.de/inf2
+// (c) Mark Minas (mark.minas@unibw.de)
+////////////////////////////////////////
+
+package pp.monopoly.client;
+
+import com.simsilica.lemur.Button;
+import com.simsilica.lemur.Checkbox;
+import com.simsilica.lemur.Label;
+import com.simsilica.lemur.style.ElementId;
+import pp.dialog.Dialog;
+import pp.dialog.StateCheckboxModel;
+import pp.dialog.TextInputDialog;
+
+import java.util.prefs.Preferences;
+
+import static pp.monopoly.Resources.lookup;
+import static pp.util.PreferencesUtils.getPreferences;
+
+/**
+ * The Menu class represents the main menu in the Battleship game application.
+ * It extends the Dialog class and provides functionalities for loading, saving,
+ * returning to the game, and quitting the application.
+ */
+class Menu extends Dialog {
+ private static final Preferences PREFERENCES = getPreferences(Menu.class);
+ private static final String LAST_PATH = "last.file.path";
+ private final MonopolyApp app;
+
+
+ /**
+ * Constructs the Menu dialog for the Battleship application.
+ *
+ * @param app the BattleshipApp instance
+ */
+ public Menu(MonopolyApp app) {
+ super(app.getDialogManager());
+ this.app = app;
+ }
+
+ /**
+ * Updates the state of the load and save buttons based on the game logic.
+ */
+ @Override
+ public void update() {
+
+ }
+
+ /**
+ * As an escape action, this method closes the menu if it is the top dialog.
+ */
+ @Override
+ public void escape() {
+ close();
+ }
+}
diff --git a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyApp.java b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyApp.java
new file mode 100644
index 0000000..618d42b
--- /dev/null
+++ b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyApp.java
@@ -0,0 +1,423 @@
+////////////////////////////////////////
+// Programming project code
+// UniBw M, 2022, 2023, 2024
+// www.unibw.de/inf2
+// (c) Mark Minas (mark.minas@unibw.de)
+////////////////////////////////////////
+
+package pp.monopoly.client;
+
+import com.jme3.app.DebugKeysAppState;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.StatsAppState;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.system.AppSettings;
+import com.simsilica.lemur.GuiGlobals;
+import com.simsilica.lemur.style.BaseStyles;
+import pp.monopoly.game.client.MonopolyClient;
+import pp.monopoly.game.client.ClientGameLogic;
+import pp.monopoly.game.client.ServerConnection;
+import pp.monopoly.notification.ClientStateEvent;
+import pp.monopoly.notification.GameEventListener;
+import pp.monopoly.notification.InfoTextEvent;
+import pp.dialog.DialogBuilder;
+import pp.dialog.DialogManager;
+import pp.graphics.Draw;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.LogManager;
+
+import static pp.monopoly.Resources.lookup;
+
+/**
+ * The main class for the Monopoly client application.
+ * It manages the initialization, input setup, GUI setup, and game states for the client.
+ */
+public class MonopolyApp extends SimpleApplication implements MonopolyClient, GameEventListener {
+
+ /**
+ * Logger for logging messages within the application.
+ */
+ private static final Logger LOGGER = System.getLogger(MonopolyApp.class.getName());
+
+ /**
+ * Path to the styles script for GUI elements.
+ */
+ private static final String STYLES_SCRIPT = "Interface/Lemur/pp-styles.groovy"; //NON-NLS
+
+ /**
+ * Path to the font resource used in the GUI.
+ */
+ private static final String FONT = "Interface/Fonts/Default.fnt"; //NON-NLS
+
+ /**
+ * Path to the client configuration file, if one exists.
+ */
+ private static final File CONFIG_FILE = new File("client.properties");
+
+ /**
+ * Input mapping name for mouse clicks.
+ */
+ public static final String CLICK = "CLICK";
+
+ /**
+ * Input mapping name for the Escape key.
+ */
+ private static final String ESC = "ESC";
+
+ /**
+ * Manager for handling dialogs within the application.
+ */
+ private final DialogManager dialogManager = new DialogManager(this);
+
+ /**
+ * The server connection instance, used for communicating with the game server.
+ */
+ private final ServerConnection serverConnection;
+
+ /**
+ * Instance of the {@link Draw} class for rendering graphics.
+ */
+ private Draw draw;
+
+ /**
+ * Text display at the top of the GUI for showing information to the user.
+ */
+ private BitmapText topText;
+
+ /**
+ * Executor service for handling asynchronous tasks within the application.
+ */
+ private ExecutorService executor;
+
+ /**
+ * Handler for managing the client's game logic.
+ */
+ private final ClientGameLogic logic;
+
+ /**
+ * Configuration settings for the Monopoly client application.
+ */
+ private final MonopolyAppConfig config;
+
+ /**
+ * Listener for handling actions triggered by the Escape key.
+ */
+ private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed);
+
+ static {
+ // Configure logging
+ LogManager manager = LogManager.getLogManager();
+ try {
+ manager.readConfiguration(new FileInputStream("logging.properties"));
+ LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS
+ }
+ catch (IOException e) {
+ LOGGER.log(Level.INFO, e.getMessage());
+ }
+ }
+
+ /**
+ * Starts the Monopoly application.
+ *
+ * @param args Command-line arguments for launching the application.
+ */
+ public static void main(String[] args) {
+ new MonopolyApp().start();
+ }
+
+ /**
+ * Constructs a new {@code MonopolyApp} instance.
+ * Initializes the configuration, server connection, and game logic listeners.
+ */
+ private MonopolyApp() {
+ config = new MonopolyAppConfig();
+ config.readFromIfExists(CONFIG_FILE);
+ serverConnection = makeServerConnection();
+ logic = new ClientGameLogic(serverConnection);
+ logic.addListener(this);
+ setShowSettings(config.getShowSettings());
+ setSettings(makeSettings());
+ }
+
+ /**
+ * Creates and configures application settings from the client configuration.
+ *
+ * @return A configured {@link AppSettings} object.
+ */
+ private AppSettings makeSettings() {
+ final AppSettings settings = new AppSettings(true);
+ settings.setTitle(lookup("Monopoly.name"));
+ settings.setResolution(config.getResolutionWidth(), config.getResolutionHeight());
+ settings.setFullscreen(config.fullScreen());
+ settings.setUseRetinaFrameBuffer(config.useRetinaFrameBuffer());
+ settings.setGammaCorrection(config.useGammaCorrection());
+ return settings;
+ }
+
+ /**
+ * Factory method for creating a server connection based on the current
+ * client configuration.
+ *
+ * @return A {@link ServerConnection} instance, which could be a real or mock server.
+ */
+ private ServerConnection makeServerConnection() {
+ return new NetworkSupport(this);
+ }
+
+ /**
+ * Returns the dialog manager responsible for managing in-game dialogs.
+ *
+ * @return The {@link DialogManager} instance.
+ */
+ DialogManager getDialogManager() {
+ return dialogManager;
+ }
+
+ /**
+ * Returns the game logic handler for the client.
+ *
+ * @return The {@link ClientGameLogic} instance.
+ */
+ @Override
+ public ClientGameLogic getGameLogic() {
+ return logic;
+ }
+
+ /**
+ * Returns the current configuration settings for the Monopoly client.
+ *
+ * @return The {@link MonopolyClientConfig} instance.
+ */
+ @Override
+ public MonopolyAppConfig getConfig() {
+ return config;
+ }
+
+ /**
+ * Initializes the application.
+ * Sets up input mappings, GUI, game states, and connects to the server.
+ */
+ @Override
+ public void simpleInitApp() {
+ setPauseOnLostFocus(false);
+ draw = new Draw(assetManager);
+ setupInput();
+ setupStates();
+ setupGui();
+ serverConnection.connect();
+ }
+
+ /**
+ * Sets up the graphical user interface (GUI) for the application.
+ */
+ private void setupGui() {
+ GuiGlobals.initialize(this);
+ BaseStyles.loadStyleResources(STYLES_SCRIPT);
+ GuiGlobals.getInstance().getStyles().setDefaultStyle("pp"); //NON-NLS
+ final BitmapFont normalFont = assetManager.loadFont(FONT); //NON-NLS
+ topText = new BitmapText(normalFont);
+ final int height = context.getSettings().getHeight();
+ topText.setLocalTranslation(10f, height - 10f, 0f);
+ topText.setColor(config.getTopColor());
+ guiNode.attachChild(topText);
+ }
+
+ /**
+ * Configures input mappings and sets up listeners for user interactions.
+ */
+ private void setupInput() {
+ inputManager.deleteMapping(INPUT_MAPPING_EXIT);
+ inputManager.setCursorVisible(false);
+ inputManager.addMapping(ESC, new KeyTrigger(KeyInput.KEY_ESCAPE));
+ inputManager.addMapping(CLICK, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+ inputManager.addListener(escapeListener, ESC);
+ }
+
+ /**
+ * Initializes and attaches the necessary application states for the game.
+ */
+ private void setupStates() {
+ if (config.getShowStatistics()) {
+ final BitmapFont normalFont = assetManager.loadFont(FONT); //NON-NLS
+ final StatsAppState stats = new StatsAppState(guiNode, normalFont);
+ stateManager.attach(stats);
+ }
+ flyCam.setEnabled(false);
+ stateManager.detach(stateManager.getState(StatsAppState.class));
+ stateManager.detach(stateManager.getState(DebugKeysAppState.class));
+
+ attachGameSound();
+
+ //TODO add states
+ stateManager.attachAll();
+ }
+
+ /**
+ * Attaches the game sound state and sets its initial enabled state.
+ */
+ private void attachGameSound() {
+ final GameSound gameSound = new GameSound();
+ logic.addListener(gameSound);
+ gameSound.setEnabled(GameSound.enabledInPreferences());
+ stateManager.attach(gameSound);
+ }
+
+ /**
+ * Updates the application state every frame.
+ * This method is called once per frame during the game loop.
+ *
+ * @param tpf Time per frame in seconds.
+ */
+ @Override
+ public void simpleUpdate(float tpf) {
+ super.simpleUpdate(tpf);
+ dialogManager.update(tpf);
+ logic.update(tpf);
+ }
+
+ /**
+ * Handles the Escape key action to either close the top dialog or show the main menu.
+ *
+ * @param isPressed Indicates whether the Escape key is pressed.
+ */
+ private void escape(boolean isPressed) {
+ if (!isPressed) return;
+ if (dialogManager.showsDialog())
+ dialogManager.escape();
+ else
+ new Menu(this).open();
+ }
+
+ /**
+ * Returns the {@link Draw} instance used for rendering graphical elements in the game.
+ *
+ * @return The {@link Draw} instance.
+ */
+ public Draw getDraw() {
+ return draw;
+ }
+
+ /**
+ * Handles a request to close the application.
+ * If the request is initiated by pressing ESC, this parameter is true.
+ *
+ * @param esc If true, the request is due to the ESC key being pressed.
+ */
+ @Override
+ public void requestClose(boolean esc) { /* do nothing */ }
+
+ /**
+ * Closes the application, displaying a confirmation dialog if the client is connected to a server.
+ */
+ public void closeApp() {
+ if (serverConnection.isConnected())
+ confirmDialog(lookup("confirm.leaving"), this::close);
+ else
+ close();
+ }
+
+ /**
+ * Closes the application, disconnecting from the server and stopping the application.
+ */
+ private void close() {
+ serverConnection.disconnect();
+ stop();
+ }
+
+ /**
+ * Updates the informational text displayed in the GUI.
+ *
+ * @param text The information text to display.
+ */
+ void setInfoText(String text) {
+ LOGGER.log(Level.DEBUG, "setInfoText {0}", text); //NON-NLS
+ topText.setText(text);
+ }
+
+ /**
+ * Updates the informational text in the GUI based on the key received in an {@link InfoTextEvent}.
+ *
+ * @param event The {@link InfoTextEvent} containing the key for the text to display.
+ */
+ @Override
+ public void receivedEvent(InfoTextEvent event) {
+ LOGGER.log(Level.DEBUG, "received info text {0}", event.key()); //NON-NLS
+ setInfoText(lookup(event.key()));
+ }
+
+ /**
+ * Handles client state events to update the game states accordingly.
+ *
+ * @param event The {@link ClientStateEvent} representing the state change.
+ */
+ @Override
+ public void receivedEvent(ClientStateEvent event) {
+ //TODO
+ throw new UnsupportedOperationException("unimplemented method");
+ }
+
+ /**
+ * Returns the executor service used for handling multithreaded tasks.
+ *
+ * @return The {@link ExecutorService} instance.
+ */
+ public ExecutorService getExecutor() {
+ if (executor == null)
+ executor = Executors.newCachedThreadPool();
+ return executor;
+ }
+
+ /**
+ * Stops the application, shutting down the executor service and halting execution.
+ *
+ * @param waitFor If true, waits for the application to stop before returning.
+ */
+ @Override
+ public void stop(boolean waitFor) {
+ if (executor != null) executor.shutdownNow();
+ super.stop(waitFor);
+ }
+
+ /**
+ * Displays a confirmation dialog with a specified question and action for the "Yes" button.
+ *
+ * @param question The question to display in the dialog.
+ * @param yesAction The action to perform if "Yes" is selected.
+ */
+ void confirmDialog(String question, Runnable yesAction) {
+ DialogBuilder.simple(dialogManager)
+ .setTitle(lookup("dialog.question"))
+ .setText(question)
+ .setOkButton(lookup("button.yes"), yesAction)
+ .setNoButton(lookup("button.no"))
+ .build()
+ .open();
+ }
+
+ /**
+ * Displays an error dialog with the specified error message.
+ *
+ * @param errorMessage The error message to display in the dialog.
+ */
+ void errorDialog(String errorMessage) {
+ DialogBuilder.simple(dialogManager)
+ .setTitle(lookup("dialog.error"))
+ .setText(errorMessage)
+ .setOkButton(lookup("button.ok"))
+ .build()
+ .open();
+ }
+}
diff --git a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyAppConfig.java b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyAppConfig.java
new file mode 100644
index 0000000..95070d2
--- /dev/null
+++ b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyAppConfig.java
@@ -0,0 +1,196 @@
+////////////////////////////////////////
+// Programming project code
+// UniBw M, 2022, 2023, 2024
+// www.unibw.de/inf2
+// (c) Mark Minas (mark.minas@unibw.de)
+////////////////////////////////////////
+
+package pp.monopoly.client;
+
+import com.jme3.math.ColorRGBA;
+import pp.monopoly.game.client.MonopolyClientConfig;
+
+/**
+ * Provides access to the Monopoly application configuration.
+ * Extends {@link MonopolyClientConfig} to include additional properties specific to the client,
+ * particularly those related to screen settings and visual customization.
+ *
+ * Note: Attributes of this class should not be marked as {@code final}
+ * to ensure proper functionality when reading from a properties file.
+ *
+ */
+public class MonopolyAppConfig extends MonopolyClientConfig {
+
+ /**
+ * Converts a string value found in the properties file into an object of the specified type.
+ * Extends the superclass method to support conversion to {@link ColorRGBA}.
+ *
+ * @param value the string value to be converted
+ * @param targetType the target type into which the value string is converted
+ * @return the converted object of the specified type
+ */
+ @Override
+ protected Object convertToType(String value, Class> targetType) {
+ if (targetType == ColorRGBA.class)
+ return makeColorRGBA(value);
+ return super.convertToType(value, targetType);
+ }
+
+ /**
+ * Converts the specified string value to a corresponding {@link ColorRGBA} object.
+ *
+ * @param value the color in the format "red, green, blue, alpha" with all values in the range [0..1]
+ * @return a {@link ColorRGBA} object representing the color
+ * @throws IllegalArgumentException if the input string is not in the expected format
+ */
+ private static ColorRGBA makeColorRGBA(String value) {
+ String[] split = value.split(",", -1);
+ try {
+ if (split.length == 4)
+ return new ColorRGBA(Float.parseFloat(split[0]),
+ Float.parseFloat(split[1]),
+ Float.parseFloat(split[2]),
+ Float.parseFloat(split[3]));
+ }
+ catch (NumberFormatException e) {
+ // deliberately left empty
+ }
+ throw new IllegalArgumentException(value + " should consist of exactly 4 numbers");
+ }
+
+ /**
+ * The width of the game view resolution in pixels.
+ */
+ @Property("settings.resolution.width") //NON-NLS
+ private int resolutionWidth = 1200;
+
+ /**
+ * The height of the game view resolution in pixels.
+ */
+ @Property("settings.resolution.height") //NON-NLS
+ private int resolutionHeight = 800;
+
+ /**
+ * Specifies whether the game should start in full-screen mode.
+ */
+ @Property("settings.full-screen") //NON-NLS
+ private boolean fullScreen = false;
+
+ /**
+ * Specifies whether gamma correction should be enabled.
+ * If enabled, the main framebuffer is configured for sRGB colors,
+ * and sRGB images are linearized.
+ *
+ * Requires a GPU that supports GL_ARB_framebuffer_sRGB; otherwise, this setting will be ignored.
+ *
+ */
+ @Property("settings.use-gamma-correction") //NON-NLS
+ private boolean useGammaCorrection = true;
+
+ /**
+ * Specifies whether full resolution framebuffers should be used on Retina displays.
+ * This setting is ignored on non-Retina platforms.
+ */
+ @Property("settings.use-retina-framebuffer") //NON-NLS
+ private boolean useRetinaFrameBuffer = false;
+
+ /**
+ * Specifies whether the settings window should be shown for configuring the game.
+ */
+ @Property("settings.show") //NON-NLS
+ private boolean showSettings = false;
+
+ /**
+ * Specifies whether the JME statistics window should be shown in the lower left corner of the screen.
+ */
+ @Property("statistics.show") //NON-NLS
+ private boolean showStatistics = false;
+
+ /**
+ * The color of the top text during gameplay, represented as a {@link ColorRGBA} object.
+ */
+ @Property("overlay.top.color") //NON-NLS
+ private ColorRGBA topColor = ColorRGBA.White;
+
+ /**
+ * Creates a default {@code MonopolyAppConfig} with predefined values.
+ */
+ public MonopolyAppConfig() {
+ // Default constructor
+ }
+
+ /**
+ * Returns the width of the game view resolution in pixels.
+ *
+ * @return the width of the game view resolution in pixels
+ */
+ public int getResolutionWidth() {
+ return resolutionWidth;
+ }
+
+ /**
+ * Returns the height of the game view resolution in pixels.
+ *
+ * @return the height of the game view resolution in pixels
+ */
+ public int getResolutionHeight() {
+ return resolutionHeight;
+ }
+
+ /**
+ * Returns whether the game should start in full-screen mode.
+ *
+ * @return {@code true} if the game should start in full-screen mode; {@code false} otherwise
+ */
+ public boolean fullScreen() {
+ return fullScreen;
+ }
+
+ /**
+ * Returns whether gamma correction is enabled.
+ * If enabled, the main framebuffer is configured for sRGB colors,
+ * and sRGB images are linearized.
+ *
+ * @return {@code true} if gamma correction is enabled; {@code false} otherwise
+ */
+ public boolean useGammaCorrection() {
+ return useGammaCorrection;
+ }
+
+ /**
+ * Returns whether full resolution framebuffers should be used on Retina displays.
+ * This setting is ignored on non-Retina platforms.
+ *
+ * @return {@code true} if full resolution framebuffers should be used on Retina displays; {@code false} otherwise
+ */
+ public boolean useRetinaFrameBuffer() {
+ return useRetinaFrameBuffer;
+ }
+
+ /**
+ * Returns whether the settings window should be shown for configuring the game.
+ *
+ * @return {@code true} if the settings window should be shown; {@code false} otherwise
+ */
+ public boolean getShowSettings() {
+ return showSettings;
+ }
+
+ /**
+ * Returns whether the JME statistics window should be shown in the lower left corner of the screen.
+ *
+ * @return {@code true} if the statistics window should be shown; {@code false} otherwise
+ */
+ public boolean getShowStatistics() {
+ return showStatistics;
+ }
+
+ /**
+ * Returns the color of the top text during gameplay as a {@link ColorRGBA} object.
+ *
+ * @return the color of the top text during gameplay
+ */
+ public ColorRGBA getTopColor() {
+ return topColor;
+ }
+}
diff --git a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyAppState.java b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyAppState.java
new file mode 100644
index 0000000..6342436
--- /dev/null
+++ b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyAppState.java
@@ -0,0 +1,102 @@
+////////////////////////////////////////
+// Programming project code
+// UniBw M, 2022, 2023, 2024
+// www.unibw.de/inf2
+// (c) Mark Minas (mark.minas@unibw.de)
+////////////////////////////////////////
+
+package pp.monopoly.client;
+
+import com.jme3.app.Application;
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
+import pp.monopoly.game.client.ClientGameLogic;
+
+/**
+ * Abstract class representing a state in the Battleship game.
+ * Extends the AbstractAppState from jMonkeyEngine to manage state behavior.
+ */
+public abstract class MonopolyAppState extends AbstractAppState {
+ private MonopolyApp app;
+
+ /**
+ * Creates a new MonopolyAppState that is initially disabled.
+ *
+ * @see #setEnabled(boolean)
+ */
+ protected MonopolyAppState() {
+ setEnabled(false);
+ }
+
+ /**
+ * Initializes the state manager and application.
+ *
+ * @param stateManager The state manager
+ * @param application The application instance
+ */
+ @Override
+ public void initialize(AppStateManager stateManager, Application application) {
+ super.initialize(stateManager, application);
+ this.app = (MonopolyApp) application;
+ if (isEnabled()) enableState();
+ }
+
+ /**
+ * Returns the MonopolyApp instance associated with this MonopolyAppState.
+ *
+ * @return The MonopolyApp instance.
+ */
+ public MonopolyApp getApp() {
+ return app;
+ }
+
+ /**
+ * Returns the client game logic handler.
+ *
+ * @return the client game logic handler
+ */
+ public ClientGameLogic getGameLogic() {
+ return app.getGameLogic();
+ }
+
+ /**
+ * Checks if any dialog is currently displayed.
+ *
+ * @return true if any dialog is currently shown, false otherwise
+ */
+ public boolean showsDialog() {
+ return app.getDialogManager().showsDialog();
+ }
+
+ /**
+ * Sets the enabled state of the MonopolyAppState.
+ * If the new state is the same as the current state, the method returns.
+ *
+ * @param enabled The new enabled state.
+ */
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (isEnabled() == enabled) return;
+ super.setEnabled(enabled);
+ if (app != null) {
+ if (enabled)
+ enableState();
+ else
+ disableState();
+ }
+ }
+
+ /**
+ * This method is called when the state is enabled.
+ * It is meant to be overridden by subclasses to perform
+ * specific actions when the state is enabled.
+ */
+ protected abstract void enableState();
+
+ /**
+ * This method is called when the state is disabled.
+ * It is meant to be overridden by subclasses to perform
+ * specific actions when the state is disabled.
+ */
+ protected abstract void disableState();
+}
diff --git a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/NetworkDialog.java b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/NetworkDialog.java
new file mode 100644
index 0000000..6b6ea98
--- /dev/null
+++ b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/NetworkDialog.java
@@ -0,0 +1,153 @@
+////////////////////////////////////////
+// Programming project code
+// UniBw M, 2022, 2023, 2024
+// www.unibw.de/inf2
+// (c) Mark Minas (mark.minas@unibw.de)
+////////////////////////////////////////
+
+package pp.monopoly.client;
+
+import com.simsilica.lemur.Container;
+import com.simsilica.lemur.Label;
+import com.simsilica.lemur.TextField;
+import com.simsilica.lemur.component.SpringGridLayout;
+import pp.dialog.Dialog;
+import pp.dialog.DialogBuilder;
+import pp.dialog.SimpleDialog;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import static pp.monopoly.Resources.lookup;
+
+/**
+ * Represents a dialog for setting up a network connection in the Battleship game.
+ * Allows users to specify the host and port for connecting to a game server.
+ */
+class NetworkDialog extends SimpleDialog {
+ private static final Logger LOGGER = System.getLogger(NetworkDialog.class.getName());
+ private static final String LOCALHOST = "localhost"; //NON-NLS
+ private static final String DEFAULT_PORT = "1234"; //NON-NLS
+ private final NetworkSupport network;
+ private final TextField host = new TextField(LOCALHOST);
+ private final TextField port = new TextField(DEFAULT_PORT);
+ private String hostname;
+ private int portNumber;
+ private Future