mirror of
				https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-02.git
				synced 2025-10-31 01:51:51 +01:00 
			
		
		
		
	Tests bis T075 überarbeitet
This commit is contained in:
		| @@ -7,22 +7,21 @@ | ||||
|  | ||||
| package pp.battleship.client; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.prefs.Preferences; | ||||
|  | ||||
| import com.simsilica.lemur.Button; | ||||
| import com.simsilica.lemur.Checkbox; | ||||
| import com.simsilica.lemur.Label; | ||||
| import com.simsilica.lemur.style.ElementId; | ||||
|  | ||||
| import static pp.battleship.Resources.lookup; | ||||
| import pp.battleship.client.gui.GameMusic; | ||||
| import pp.battleship.client.gui.VolumeSlider; | ||||
| import pp.dialog.Dialog; | ||||
| import pp.dialog.StateCheckboxModel; | ||||
| import pp.dialog.TextInputDialog; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.prefs.Preferences; | ||||
|  | ||||
| import static pp.battleship.Resources.lookup; | ||||
| import static pp.util.PreferencesUtils.getPreferences; | ||||
|  | ||||
| /** | ||||
| @@ -39,7 +38,7 @@ class Menu extends Dialog { | ||||
|     private final VolumeSlider slider; | ||||
|  | ||||
|     /** | ||||
|      * Constructs the Menu dialog for the Battleship application. | ||||
|      * Constructs the Menu dialog for the Battleship application.+ | ||||
|      * | ||||
|      * @param app the BattleshipApp instance | ||||
|      */ | ||||
|   | ||||
| @@ -45,7 +45,12 @@ public class ModelExporter extends SimpleApplication { | ||||
|         export("Models/BoatSmall/12219_boat_v2_L2.obj", "BoatSmall.j3o"); //NON-NLS | ||||
|         export("Models/Battle/14084_WWII_Ship_German_Type_II_U-boat_v2_L1.obj", "Battle.j3o"); //NON-NLS | ||||
|         export("Models/CV/essex_scb-125_generic.obj", "CV.j3o"); //NON-NLS | ||||
|  | ||||
|         export("Models/Figures/Würfel_blau.obj", "Würfel_blau.j30"); | ||||
|         export("Models/Figures/Würfel_gelb.obj", "Würfel_gelb.j30"); | ||||
|         export("Models/Figures/Würfel_grün.obj", "Würfel_grün.j30"); | ||||
|         export("Models/Figures/Würfel_rosa.obj", "Würfel_rosa.j30"); | ||||
|         export("Models/Figures/Würfel_rot.obj", "Würfel_rot.j30"); | ||||
|         export("Models/Figures/Würfel_schwarz.obj", "Würfel_schwarz.j30"); | ||||
|         stop(); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| // Styling of Lemur components | ||||
| // For documentation, see | ||||
| // For documentation, see: | ||||
| // https://github.com/jMonkeyEngine-Contributions/Lemur/wiki/Styling | ||||
|  | ||||
| import com.simsilica.lemur.* | ||||
| import com.simsilica.lemur.component.QuadBackgroundComponent | ||||
| import com.simsilica.lemur.Button | ||||
| import com.simsilica.lemur.Button.ButtonAction | ||||
| import com.simsilica.lemur.Command | ||||
| @@ -11,19 +12,17 @@ import com.simsilica.lemur.component.QuadBackgroundComponent | ||||
| import com.simsilica.lemur.component.TbtQuadBackgroundComponent | ||||
|  | ||||
| def bgColor = color(1, 1, 1, 1) | ||||
| def buttonEnabledColor = color(0.8, 0.9, 1, 1) | ||||
| def buttonEnabledColor = color(0, 0, 0, 1) | ||||
| def buttonDisabledColor = color(0.8, 0.9, 1, 0.2) | ||||
| //def buttonBgColor = color(0, 0.75, 0.75, 1) | ||||
| def buttonBgColor = color(1, 1, 1, 1) | ||||
| def sliderColor = color(0.6, 0.8, 0.8, 1) | ||||
| def sliderBgColor = color(0.5, 0.75, 0.75, 1) | ||||
| def gradientColor = color(0.5, 0.75, 0.85, 0.5) | ||||
| def tabbuttonEnabledColor = color(0.4, 0.45, 0.5, 1) | ||||
| def solidWhiteBackground = new QuadBackgroundComponent(color(1, 1, 1, 1)) // Solid white | ||||
| def greyBackground = color(0.8, 0.8, 0.8, 1)  // Grey background color | ||||
| def redBorderColor = color(1, 0, 0, 1)        // Red border color | ||||
|  | ||||
| def playButtonBorderColor = color(1, 0.6, 0, 1) // Orange border for "Spielen" button | ||||
| def playButtonTextColor = color(0, 0, 0, 1) // Black text color for "Spielen" button | ||||
| def buttonBgColor = color(1, 1, 1, 1) // White background for "Spiel beenden" and "Einstellungen" buttons | ||||
| def buttonTextColor = color(0, 0, 0, 1) // Black text color for "Spiel beenden" and "Einstellungen" buttons | ||||
| def borderColor = color(0, 0, 0, 1) // Black border for "Spiel beenden" and "Einstellungen" | ||||
|  | ||||
|  | ||||
| def gradient = TbtQuadBackgroundComponent.create( | ||||
| @@ -34,7 +33,14 @@ def gradient = TbtQuadBackgroundComponent.create( | ||||
|  | ||||
| def doubleGradient = new QuadBackgroundComponent(gradientColor) | ||||
| doubleGradient.texture = texture(name: "/com/simsilica/lemur/icons/double-gradient-128.png", | ||||
|                                  generateMips: false) | ||||
|         generateMips: false) | ||||
|  | ||||
| def orangeBorder = TbtQuadBackgroundComponent.create( | ||||
|         texture(name: "/com/simsilica/lemur/icons/bordered-gradient.png", // Replace with an appropriate texture if needed | ||||
|                 generateMips: false), | ||||
|         1, 1, 1, 126, 126, | ||||
|         1f, false) | ||||
| orangeBorder.color = color(1, 0.5, 0, 1) // Orange color | ||||
|  | ||||
| selector("pp") { | ||||
|     font = font("Interface/Fonts/Metropolis/Metropolis-Regular-32.fnt") | ||||
| @@ -53,33 +59,31 @@ selector("header", "pp") { | ||||
| } | ||||
|  | ||||
| selector("container", "pp") { | ||||
|     background = gradient.clone() | ||||
|     background = solidWhiteBackground.clone() | ||||
|     background.setColor(bgColor) | ||||
| } | ||||
|  | ||||
| selector("toolbar") { | ||||
|     // Set the grey background | ||||
|     background = new QuadBackgroundComponent(greyBackground) | ||||
|  | ||||
|     // Add a red border using a TbtQuadBackgroundComponent | ||||
|     def redBorder = TbtQuadBackgroundComponent.create( | ||||
|             texture(name: "/com/simsilica/lemur/icons/bordered-gradient.png", | ||||
|                     generateMips: false), | ||||
|             1, 1, 1, 1, 1, | ||||
|             1f, false) | ||||
|     redBorder.color = redBorderColor | ||||
|     background = greyBackground | ||||
|  | ||||
|     // Optional: Set padding inside the toolbar | ||||
|     insets = new Insets3f(10, 10, 10, 10) | ||||
| } | ||||
| selector("slider", "pp") { | ||||
|     background = gradient.clone() | ||||
|     background.setColor(bgColor) | ||||
| } | ||||
|  | ||||
| selector("play-button", "pp") { | ||||
|     color = playButtonTextColor // Black text color | ||||
|     background = new QuadBackgroundComponent(playButtonBorderColor) // Orange border background | ||||
|     insets = new Insets3f(15, 25, 15, 25) // Padding for larger button size | ||||
|     background.setMargin(5, 5) // Thin border effect around the background color | ||||
|     fontSize = 36 // Larger font size for prominence | ||||
| } | ||||
|  | ||||
| selector("menu-button", "pp") { | ||||
|     color = buttonTextColor // Black text color | ||||
|     background = new QuadBackgroundComponent(buttonBgColor) // White background | ||||
|     insets = new Insets3f(10, 20, 10, 20) // Padding | ||||
|     background.setMargin(1, 1) // Thin black border | ||||
|     background.setColor(borderColor) // Set black border color | ||||
|  | ||||
|     fontSize = 24 // Standard font size | ||||
| } | ||||
|  | ||||
| def pressedCommand = new Command<Button>() { | ||||
|     void execute(Button source) { | ||||
|         if (source.isPressed()) | ||||
| @@ -140,7 +144,7 @@ selector("title", "pp") { | ||||
|     shadowOffset = vec3(2, -2, -1) | ||||
|     background = new QuadBackgroundComponent(color(0.5, 0.75, 0.85, 1)) | ||||
|     background.texture = texture(name: "/com/simsilica/lemur/icons/double-gradient-128.png", | ||||
|                                  generateMips: false) | ||||
|             generateMips: false) | ||||
|     insets = new Insets3f(2, 2, 2, 2) | ||||
|  | ||||
|     buttonCommands = stdButtonCommands | ||||
| @@ -148,11 +152,14 @@ selector("title", "pp") { | ||||
|  | ||||
|  | ||||
| selector("button", "pp") { | ||||
|     background = gradient.clone() | ||||
|     color = buttonEnabledColor | ||||
|     background.setColor(buttonBgColor) | ||||
|     insets = new Insets3f(2, 2, 2, 2) | ||||
|     def outerBackground = new QuadBackgroundComponent(color(1, 0.5, 0, 1)) // Orange border | ||||
|     def innerBackground = new QuadBackgroundComponent(buttonBgColor) // Inner button background | ||||
|  | ||||
|     // Apply the outer border as the main background | ||||
|     background = outerBackground | ||||
|  | ||||
|     // Use insets to create a margin/padding effect for the inner background | ||||
|     insets = new Insets3f(3, 3, 3, 3) // Adjust the border thickness | ||||
|     buttonCommands = stdButtonCommands | ||||
| } | ||||
|  | ||||
| @@ -201,6 +208,7 @@ selector("slider.down.button", "pp") { | ||||
|  | ||||
| selector("checkbox", "pp") { | ||||
|     color = buttonEnabledColor | ||||
|     fontSize = 20 | ||||
| } | ||||
|  | ||||
| selector("rollup", "pp") { | ||||
| @@ -217,10 +225,14 @@ selector("tabbedPanel.container", "pp") { | ||||
| } | ||||
|  | ||||
| selector("tab.button", "pp") { | ||||
|     background = gradient.clone() | ||||
|     background = solidWhiteBackground.clone() | ||||
|     background.setColor(bgColor) | ||||
|     color = tabbuttonEnabledColor | ||||
|     insets = new Insets3f(4, 2, 0, 2) | ||||
|  | ||||
|     buttonCommands = stdButtonCommands | ||||
| } | ||||
| selector("settings-title", "pp") { | ||||
|     fontSize = 48 // Set font size | ||||
|     background = new QuadBackgroundComponent(color(0.4157f, 0.4235f, 0.4392f, 1.0f)) // Grey background | ||||
|  } | ||||
| @@ -7,6 +7,7 @@ description = 'Monopoly Client' | ||||
| dependencies { | ||||
|     implementation project(":jme-common") | ||||
|     implementation project(":monopoly:model") | ||||
|     implementation project(":monopoly:server") | ||||
|  | ||||
|     implementation libs.jme3.desktop | ||||
|  | ||||
|   | ||||
| @@ -7,6 +7,10 @@ | ||||
|  | ||||
| package pp.monopoly.client; | ||||
|  | ||||
| import java.lang.System.Logger; | ||||
| import java.lang.System.Logger.Level; | ||||
| import java.util.prefs.Preferences; | ||||
|  | ||||
| import com.jme3.app.Application; | ||||
| import com.jme3.app.state.AbstractAppState; | ||||
| import com.jme3.app.state.AppStateManager; | ||||
| @@ -14,13 +18,9 @@ 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; | ||||
|  | ||||
| /** | ||||
| @@ -31,6 +31,10 @@ public class GameSound extends AbstractAppState implements GameEventListener { | ||||
|     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. | ||||
|      * | ||||
| @@ -93,10 +97,33 @@ public class GameSound extends AbstractAppState implements GameEventListener { | ||||
|         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()) { | ||||
|  | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,17 +7,9 @@ | ||||
|  | ||||
| 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 pp.dialog.Dialog; | ||||
| import static pp.util.PreferencesUtils.getPreferences; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -1,423 +1,235 @@ | ||||
| //////////////////////////////////////// | ||||
| // 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 java.util.concurrent.ExecutorService; | ||||
| import java.util.concurrent.Executors; | ||||
|  | ||||
| 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.Label; | ||||
| 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 pp.monopoly.client.gui.SettingsMenu; | ||||
| import pp.monopoly.client.gui.TestWorld; | ||||
| import pp.monopoly.game.client.ClientGameLogic; | ||||
| import pp.monopoly.game.client.MonopolyClient; | ||||
| import pp.monopoly.game.client.ServerConnection; | ||||
| import pp.monopoly.notification.GameEventListener; | ||||
| import pp.monopoly.notification.InfoTextEvent; | ||||
| import pp.monopoly.server.MonopolyServer; | ||||
|  | ||||
| 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()); | ||||
|     private BitmapText topText; | ||||
|     private final ServerConnection serverConnection; | ||||
|     private final ClientGameLogic logic; | ||||
|     private final MonopolyAppConfig config; | ||||
|     private final ActionListener escapeListener = (name, isPressed, tpf) -> handleEscape(isPressed); | ||||
|     private final DialogManager dialogManager = new DialogManager(this); | ||||
|     private final ExecutorService executor = Executors.newCachedThreadPool(); | ||||
|     private final Draw draw; | ||||
|     private SettingsMenu settingsMenu; | ||||
|     private TestWorld testWorld; | ||||
|     private boolean isSettingsMenuOpen = false; | ||||
|     private boolean inputBlocked = false; | ||||
|     private MonopolyServer monopolyServer; | ||||
|  | ||||
|     /** | ||||
|      * 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() { | ||||
|     public MonopolyApp() { | ||||
|         this.draw = new Draw(assetManager); | ||||
|         config = new MonopolyAppConfig(); | ||||
|         config.readFromIfExists(CONFIG_FILE); | ||||
|         serverConnection = makeServerConnection(); | ||||
|         serverConnection = new NetworkSupport(this); | ||||
|         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. | ||||
|      */ | ||||
|     public 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(); | ||||
|     public ClientGameLogic getGameLogic() { | ||||
|         return logic; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sets up the graphical user interface (GUI) for the application. | ||||
|      */ | ||||
|     private void setupGui() { | ||||
|     private AppSettings makeSettings() { | ||||
|         final AppSettings settings = new AppSettings(true); | ||||
|         settings.setTitle("Monopoly Game"); | ||||
|         settings.setResolution(config.getResolutionWidth(), config.getResolutionHeight()); | ||||
|         settings.setFullscreen(config.fullScreen()); | ||||
|         return settings; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void simpleInitApp() { | ||||
|         GuiGlobals.initialize(this); | ||||
|         BaseStyles.loadStyleResources(STYLES_SCRIPT); | ||||
|         GuiGlobals.getInstance().getStyles().setDefaultStyle("pp"); //NON-NLS | ||||
|         final BitmapFont normalFont = assetManager.loadFont(FONT); //NON-NLS | ||||
|  | ||||
|         setupInput(); | ||||
|         setupGui(); | ||||
|  | ||||
|         // Zeige das Startmenü | ||||
|         StartMenu.createStartMenu(this); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     private void setupGui() { | ||||
|         BitmapFont normalFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); | ||||
|         topText = new BitmapText(normalFont); | ||||
|         final int height = context.getSettings().getHeight(); | ||||
|         topText.setLocalTranslation(10f, height - 10f, 0f); | ||||
|         topText.setColor(config.getTopColor()); | ||||
|         topText.setLocalTranslation(10, settings.getHeight() - 10, 0); | ||||
|         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); | ||||
|         inputManager.setCursorVisible(true); | ||||
|         inputManager.addMapping("ESC", new KeyTrigger(KeyInput.KEY_ESCAPE)); | ||||
|         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); | ||||
|     void handleEscape(boolean isPressed) { | ||||
|         if (isPressed) { | ||||
|             if (settingsMenu != null && isSettingsMenuOpen) { | ||||
|                 // Schließe das SettingsMenu | ||||
|                 System.out.println("Schließe SettingsMenu..."); | ||||
|                 settingsMenu.close(); | ||||
|                 settingsMenu = null; | ||||
|                 setSettingsMenuOpen(false); | ||||
|             } else { | ||||
|                 // Öffne das SettingsMenu | ||||
|                 System.out.println("Öffne SettingsMenu..."); | ||||
|                 settingsMenu = new SettingsMenu(this); | ||||
|                 settingsMenu.open(); | ||||
|                 setSettingsMenuOpen(true); | ||||
|             } | ||||
|         } | ||||
|         flyCam.setEnabled(false); | ||||
|         stateManager.detach(stateManager.getState(StatsAppState.class)); | ||||
|         stateManager.detach(stateManager.getState(DebugKeysAppState.class)); | ||||
|     } | ||||
|      | ||||
|      | ||||
|  | ||||
|         attachGameSound(); | ||||
|  | ||||
|         //TODO add states | ||||
|         stateManager.attachAll(); | ||||
|     private void blockInputs() { | ||||
|         if (!inputBlocked) { | ||||
|             System.out.println("Blockiere Eingaben..."); | ||||
|             inputManager.setCursorVisible(true); // Cursor sichtbar machen | ||||
|             inputManager.clearMappings();       // Alle Mappings entfernen | ||||
|             inputBlocked = true; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     public void unblockInputs() { | ||||
|         if (inputBlocked) { | ||||
|             System.out.println("Aktiviere Eingaben..."); | ||||
|             setupInput(); // Standard-Eingaben neu registrieren | ||||
|             inputBlocked = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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); | ||||
|     public void setInfoText(String text) { | ||||
|         topText.setText(text); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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); | ||||
|     public void receivedEvent(InfoTextEvent event) { | ||||
|         setInfoText(event.key()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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(); | ||||
|     @Override | ||||
|     public void stop(boolean waitFor) { | ||||
|         if (executor != null) executor.shutdownNow(); | ||||
|         serverConnection.disconnect(); | ||||
|         super.stop(waitFor); | ||||
|     } | ||||
|  | ||||
|     public DialogManager getDialogManager() { | ||||
|         return dialogManager; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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. | ||||
|      */ | ||||
|     public void closeApp() { | ||||
|         stop(); | ||||
|     } | ||||
|  | ||||
|     public void errorDialog(String errorMessage) { | ||||
|         DialogBuilder.simple(dialogManager) | ||||
|                 .setTitle("Fehler") | ||||
|                 .setText(errorMessage) | ||||
|                 .setOkButton("OK") | ||||
|                 .build() | ||||
|                 .open(); | ||||
|     } | ||||
|  | ||||
|     public void setSettingsMenuOpen(boolean isOpen) { | ||||
|         this.isSettingsMenuOpen = isOpen; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void stop(boolean waitFor) { | ||||
|         if (executor != null) executor.shutdownNow(); | ||||
|         super.stop(waitFor); | ||||
|     public void simpleUpdate(float tpf) { | ||||
|         if (testWorld != null) { | ||||
|             testWorld.update(tpf); // Aktualisiere die Kamera in der TestWorld | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void startTestWorld() { | ||||
|         guiNode.detachAllChildren(); // Entferne GUI | ||||
|         testWorld = new TestWorld(this); // Erstelle eine Instanz von TestWorld | ||||
|         testWorld.initializeScene();     // Initialisiere die Szene | ||||
|     } | ||||
|  | ||||
|     public void returnToMenu() { | ||||
|         guiNode.detachAllChildren(); // Entferne die GUI | ||||
|         StartMenu.createStartMenu(this); // Zeige das Startmenü erneut | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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. | ||||
|      * Startet den Server in einem neuen Thread. | ||||
|      */ | ||||
|     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(); | ||||
|     public void startServer() { | ||||
|         new Thread(() -> { | ||||
|             try { | ||||
|                 monopolyServer = new MonopolyServer(); // Erstelle Serverinstanz | ||||
|             } catch (Exception e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|         }).start(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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(); | ||||
|     public MonopolyServer getMonopolyServer() { | ||||
|         return monopolyServer; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,13 +1,7 @@ | ||||
| //////////////////////////////////////// | ||||
| // 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; | ||||
|  | ||||
| /** | ||||
| @@ -193,4 +187,4 @@ public class MonopolyAppConfig extends MonopolyClientConfig { | ||||
|     public ColorRGBA getTopColor() { | ||||
|         return topColor; | ||||
|     } | ||||
| } | ||||
| } | ||||
| @@ -1,19 +1,13 @@ | ||||
| //////////////////////////////////////// | ||||
| // 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. | ||||
|  * Abstract class representing a state in the Monopoly game. | ||||
|  * Extends the AbstractAppState from jMonkeyEngine to manage state behavior. | ||||
|  */ | ||||
| public abstract class MonopolyAppState extends AbstractAppState { | ||||
| @@ -21,8 +15,6 @@ public abstract class MonopolyAppState extends AbstractAppState { | ||||
|  | ||||
|     /** | ||||
|      * Creates a new MonopolyAppState that is initially disabled. | ||||
|      * | ||||
|      * @see #setEnabled(boolean) | ||||
|      */ | ||||
|     protected MonopolyAppState() { | ||||
|         setEnabled(false); | ||||
| @@ -38,7 +30,9 @@ public abstract class MonopolyAppState extends AbstractAppState { | ||||
|     public void initialize(AppStateManager stateManager, Application application) { | ||||
|         super.initialize(stateManager, application); | ||||
|         this.app = (MonopolyApp) application; | ||||
|         if (isEnabled()) enableState(); | ||||
|         if (isEnabled()) { | ||||
|             enableState(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -59,15 +53,6 @@ public abstract class MonopolyAppState extends AbstractAppState { | ||||
|         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. | ||||
| @@ -79,24 +64,21 @@ public abstract class MonopolyAppState extends AbstractAppState { | ||||
|         if (isEnabled() == enabled) return; | ||||
|         super.setEnabled(enabled); | ||||
|         if (app != null) { | ||||
|             if (enabled) | ||||
|             if (enabled) { | ||||
|                 enableState(); | ||||
|             else | ||||
|             } 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. | ||||
|      * Called when the state is enabled. Override to define specific behavior. | ||||
|      */ | ||||
|     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. | ||||
|      * Called when the state is disabled. Override to define specific behavior. | ||||
|      */ | ||||
|     protected abstract void disableState(); | ||||
| } | ||||
| } | ||||
| @@ -1,35 +1,27 @@ | ||||
| //////////////////////////////////////// | ||||
| // 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; | ||||
| import com.simsilica.lemur.Button; | ||||
| import com.simsilica.lemur.Container; | ||||
| import com.simsilica.lemur.Label; | ||||
| import com.simsilica.lemur.TextField; | ||||
|  | ||||
| import pp.dialog.Dialog; | ||||
| import pp.dialog.DialogBuilder; | ||||
| import pp.dialog.SimpleDialog; | ||||
|  | ||||
| /** | ||||
|  * Represents a dialog for setting up a network connection in the Battleship game. | ||||
|  * Represents a dialog for setting up a network connection in the Monopoly 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 static final String LOCALHOST = "localhost"; | ||||
|     private static final String DEFAULT_PORT = "1234"; | ||||
|     private final NetworkSupport network; | ||||
|     private final TextField host = new TextField(LOCALHOST); | ||||
|     private final TextField port = new TextField(DEFAULT_PORT); | ||||
| @@ -46,56 +38,59 @@ class NetworkDialog extends SimpleDialog { | ||||
|     NetworkDialog(NetworkSupport network) { | ||||
|         super(network.getApp().getDialogManager()); | ||||
|         this.network = network; | ||||
|         host.setSingleLine(true); | ||||
|         host.setPreferredWidth(400f); | ||||
|         port.setSingleLine(true); | ||||
|  | ||||
|         final MonopolyApp app = network.getApp(); | ||||
|         final Container input = new Container(new SpringGridLayout()); | ||||
|         input.addChild(new Label(lookup("host.name") + ":  ")); | ||||
|         input.addChild(host, 1); | ||||
|         input.addChild(new Label(lookup("port.number") + ":  ")); | ||||
|         input.addChild(port, 1); | ||||
|  | ||||
|         DialogBuilder.simple(app.getDialogManager()) | ||||
|                      .setTitle(lookup("server.dialog")) | ||||
|                      .setExtension(d -> d.addChild(input)) | ||||
|                      .setOkButton(lookup("button.connect"), d -> connect()) | ||||
|                      .setNoButton(lookup("button.cancel"), app::closeApp) | ||||
|                      .setOkClose(false) | ||||
|                      .setNoClose(false) | ||||
|                      .build(this); | ||||
|         initializeDialog(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handles the action for the connect button in the connection dialog. | ||||
|      * Tries to parse the port number and initiate connection to the server. | ||||
|      * Initializes the dialog with input fields and connection buttons. | ||||
|      */ | ||||
|     private void initializeDialog() { | ||||
|         final MonopolyApp app = network.getApp(); | ||||
|         Container inputContainer = new Container(); | ||||
|  | ||||
|         // Titel und Eingabefelder für Host und Port | ||||
|         inputContainer.addChild(new Label("Server-Adresse")); | ||||
|         inputContainer.addChild(host); | ||||
|  | ||||
|         inputContainer.addChild(new Label("Port")); | ||||
|         inputContainer.addChild(port); | ||||
|  | ||||
|         Button connectButton = inputContainer.addChild(new Button("Verbinden")); | ||||
|         connectButton.addClickCommands(source -> connect()); | ||||
|  | ||||
|         Button cancelButton = inputContainer.addChild(new Button("Abbrechen")); | ||||
|         cancelButton.addClickCommands(source -> app.closeApp()); | ||||
|  | ||||
|         app.getGuiNode().attachChild(inputContainer); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Initiates the connection attempt based on the entered host and port. | ||||
|      */ | ||||
|     private void connect() { | ||||
|         LOGGER.log(Level.INFO, "connect to host={0}, port={1}", host, port); //NON-NLS | ||||
|         LOGGER.log(Level.INFO, "Connecting to host={0}, port={1}", host, port); | ||||
|         try { | ||||
|             hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText(); | ||||
|             portNumber = Integer.parseInt(port.getText()); | ||||
|             openProgressDialog(); | ||||
|             connectionFuture = network.getApp().getExecutor().submit(this::initNetwork); | ||||
|         } | ||||
|         catch (NumberFormatException e) { | ||||
|             network.getApp().errorDialog(lookup("port.must.be.integer")); | ||||
|         } catch (NumberFormatException e) { | ||||
|             network.getApp().errorDialog("Port muss eine Zahl sein."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a dialog indicating that the connection is in progress. | ||||
|      * Opens a progress dialog while connecting. | ||||
|      */ | ||||
|     private void openProgressDialog() { | ||||
|         progressDialog = DialogBuilder.simple(network.getApp().getDialogManager()) | ||||
|                                       .setText(lookup("label.connecting")) | ||||
|                                       .setText("Verbinde zum Server...") | ||||
|                                       .build(); | ||||
|         progressDialog.open(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Tries to initialize the network connection. | ||||
|      * Attempts to initialize the network connection. | ||||
|      * | ||||
|      * @throws RuntimeException If an error occurs when creating the client. | ||||
|      */ | ||||
| @@ -103,40 +98,37 @@ class NetworkDialog extends SimpleDialog { | ||||
|         try { | ||||
|             network.initNetwork(hostname, portNumber); | ||||
|             return null; | ||||
|         } | ||||
|         catch (Exception e) { | ||||
|         } catch (Exception e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * This method is called by {@linkplain pp.dialog.DialogManager#update(float)} for periodically | ||||
|      * updating this dialog. T | ||||
|      * Updates the connection status and handles completion or failure. | ||||
|      */ | ||||
|     @Override | ||||
|     public void update(float delta) { | ||||
|         if (connectionFuture != null && connectionFuture.isDone()) | ||||
|         if (connectionFuture != null && connectionFuture.isDone()) { | ||||
|             try { | ||||
|                 connectionFuture.get(); | ||||
|                 success(); | ||||
|             } | ||||
|             catch (ExecutionException e) { | ||||
|                 failure(e.getCause()); | ||||
|             } | ||||
|             catch (InterruptedException e) { | ||||
|                 LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS | ||||
|                 onSuccess(); | ||||
|             } catch (ExecutionException e) { | ||||
|                 onFailure(e.getCause()); | ||||
|             } catch (InterruptedException e) { | ||||
|                 LOGGER.log(Level.WARNING, "Connection interrupted.", e); | ||||
|                 Thread.currentThread().interrupt(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handles a successful connection to the game server. | ||||
|      */ | ||||
|     private void success() { | ||||
|     private void onSuccess() { | ||||
|         connectionFuture = null; | ||||
|         progressDialog.close(); | ||||
|         this.close(); | ||||
|         network.getApp().setInfoText(lookup("wait.for.an.opponent")); | ||||
|         network.getApp().setInfoText("Warte auf einen Gegner..."); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -144,10 +136,11 @@ class NetworkDialog extends SimpleDialog { | ||||
|      * | ||||
|      * @param e The cause of the failure. | ||||
|      */ | ||||
|     private void failure(Throwable e) { | ||||
|     private void onFailure(Throwable e) { | ||||
|         connectionFuture = null; | ||||
|         progressDialog.close(); | ||||
|         network.getApp().errorDialog(lookup("server.connection.failed")); | ||||
|         network.getApp().errorDialog("Verbindung zum Server fehlgeschlagen."); | ||||
|         network.getApp().setInfoText(e.getLocalizedMessage()); | ||||
|     } | ||||
| } | ||||
|      | ||||
| } | ||||
| @@ -1,29 +1,21 @@ | ||||
| //////////////////////////////////////// | ||||
| // Programming project code | ||||
| // UniBw M, 2022, 2023, 2024 | ||||
| // www.unibw.de/inf2 | ||||
| // (c) Mark Minas (mark.minas@unibw.de) | ||||
| //////////////////////////////////////// | ||||
|  | ||||
| package pp.monopoly.client; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.lang.System.Logger; | ||||
| import java.lang.System.Logger.Level; | ||||
|  | ||||
| import com.jme3.network.Client; | ||||
| import com.jme3.network.ClientStateListener; | ||||
| import com.jme3.network.Message; | ||||
| import com.jme3.network.MessageListener; | ||||
| import com.jme3.network.Network; | ||||
|  | ||||
| import pp.monopoly.game.client.ServerConnection; | ||||
| import pp.monopoly.message.client.ClientMessage; | ||||
| import pp.monopoly.message.server.ServerMessage; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.lang.System.Logger; | ||||
| import java.lang.System.Logger.Level; | ||||
|  | ||||
| import static pp.monopoly.Resources.lookup; | ||||
|  | ||||
| /** | ||||
|  * Manages the network connection for the Battleship application. | ||||
|  * Manages the network connection for the Monopoly application. | ||||
|  * Handles connecting to and disconnecting from the server, and sending messages. | ||||
|  */ | ||||
| class NetworkSupport implements MessageListener<Client>, ClientStateListener, ServerConnection { | ||||
| @@ -32,18 +24,18 @@ class NetworkSupport implements MessageListener<Client>, ClientStateListener, Se | ||||
|     private Client client; | ||||
|  | ||||
|     /** | ||||
|      * Constructs a NetworkSupport instance for the given Battleship application. | ||||
|      * Constructs a NetworkSupport instance for the Monopoly application. | ||||
|      * | ||||
|      * @param app The Battleship application instance. | ||||
|      * @param app The Monopoly application instance. | ||||
|      */ | ||||
|     public NetworkSupport(MonopolyApp app) { | ||||
|         this.app = app; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the Battleship application instance. | ||||
|      * Returns the Monopoly application instance. | ||||
|      * | ||||
|      * @return Battleship application instance | ||||
|      * @return Monopoly application instance | ||||
|      */ | ||||
|     MonopolyApp getApp() { | ||||
|         return app; | ||||
| @@ -65,8 +57,9 @@ class NetworkSupport implements MessageListener<Client>, ClientStateListener, Se | ||||
|      */ | ||||
|     @Override | ||||
|     public void connect() { | ||||
|         if (client == null) | ||||
|         if (client == null) { | ||||
|             new NetworkDialog(this).open(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -77,7 +70,7 @@ class NetworkSupport implements MessageListener<Client>, ClientStateListener, Se | ||||
|         if (client == null) return; | ||||
|         client.close(); | ||||
|         client = null; | ||||
|         LOGGER.log(Level.INFO, "client closed"); //NON-NLS | ||||
|         LOGGER.log(Level.INFO, "Client connection closed."); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -88,8 +81,9 @@ class NetworkSupport implements MessageListener<Client>, ClientStateListener, Se | ||||
|      * @throws IOException If an I/O error occurs when creating the client. | ||||
|      */ | ||||
|     void initNetwork(String host, int port) throws IOException { | ||||
|         if (client != null) | ||||
|             throw new IllegalStateException("trying to join a game again"); | ||||
|         if (client != null) { | ||||
|             throw new IllegalStateException("Already connected to the game server."); | ||||
|         } | ||||
|         client = Network.connectToServer(host, port); | ||||
|         client.start(); | ||||
|         client.addMessageListener(this); | ||||
| @@ -104,9 +98,10 @@ class NetworkSupport implements MessageListener<Client>, ClientStateListener, Se | ||||
|      */ | ||||
|     @Override | ||||
|     public void messageReceived(Client client, Message message) { | ||||
|         LOGGER.log(Level.INFO, "message received from server: {0}", message); //NON-NLS | ||||
|         if (message instanceof ServerMessage serverMessage) | ||||
|         LOGGER.log(Level.INFO, "Message received from server: {0}", message); | ||||
|         if (message instanceof ServerMessage serverMessage) { | ||||
|             app.enqueue(() -> serverMessage.accept(app.getGameLogic())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -116,7 +111,7 @@ class NetworkSupport implements MessageListener<Client>, ClientStateListener, Se | ||||
|      */ | ||||
|     @Override | ||||
|     public void clientConnected(Client client) { | ||||
|         LOGGER.log(Level.INFO, "Client connected: {0}", client); //NON-NLS | ||||
|         LOGGER.log(Level.INFO, "Successfully connected to server: {0}", client); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -127,13 +122,9 @@ class NetworkSupport implements MessageListener<Client>, ClientStateListener, Se | ||||
|      */ | ||||
|     @Override | ||||
|     public void clientDisconnected(Client client, DisconnectInfo disconnectInfo) { | ||||
|         LOGGER.log(Level.INFO, "Client {0} disconnected: {1}", client, disconnectInfo); //NON-NLS | ||||
|         if (this.client != client) | ||||
|             throw new IllegalArgumentException("parameter value must be client"); | ||||
|         LOGGER.log(Level.INFO, "client still connected: {0}", client.isConnected()); //NON-NLS | ||||
|         LOGGER.log(Level.INFO, "Disconnected from server: {0}", disconnectInfo); | ||||
|         this.client = null; | ||||
|         disconnect(); | ||||
|         app.enqueue(() -> app.setInfoText(lookup("lost.connection.to.server"))); | ||||
|         app.enqueue(() -> app.setInfoText("Verbindung zum Server verloren.")); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -143,10 +134,11 @@ class NetworkSupport implements MessageListener<Client>, ClientStateListener, Se | ||||
|      */ | ||||
|     @Override | ||||
|     public void send(ClientMessage message) { | ||||
|         LOGGER.log(Level.INFO, "sending {0}", message); //NON-NLS | ||||
|         if (client == null) | ||||
|             app.errorDialog(lookup("lost.connection.to.server")); | ||||
|         else | ||||
|         LOGGER.log(Level.INFO, "Sending message to server: {0}", message); | ||||
|         if (client == null) { | ||||
|             app.errorDialog("Verbindung zum Server verloren."); | ||||
|         } else { | ||||
|             client.send(message); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
| @@ -1,117 +1,181 @@ | ||||
| package pp.monopoly.client; | ||||
|  | ||||
| import com.jme3.asset.TextureKey; | ||||
| import com.jme3.material.Material; | ||||
| import com.jme3.math.Vector3f; | ||||
| import com.jme3.scene.Geometry; | ||||
| import com.jme3.scene.shape.Quad; | ||||
| import com.jme3.texture.Texture; | ||||
| import com.simsilica.lemur.Axis; | ||||
| import com.simsilica.lemur.Button; | ||||
| import com.simsilica.lemur.Insets3f; | ||||
| import com.simsilica.lemur.Label; | ||||
| import com.simsilica.lemur.Panel; | ||||
| import com.simsilica.lemur.style.ElementId; | ||||
| import com.simsilica.lemur.Container; | ||||
| import com.simsilica.lemur.HAlignment; | ||||
| import com.simsilica.lemur.component.QuadBackgroundComponent; | ||||
| import com.jme3.math.ColorRGBA; | ||||
| import com.simsilica.lemur.component.SpringGridLayout; | ||||
|  | ||||
| import pp.dialog.Dialog; | ||||
| import pp.monopoly.client.gui.CreateGameMenu; | ||||
| import pp.monopoly.client.gui.SettingsMenu; | ||||
|  | ||||
| /** | ||||
|  * Constructs the startup menu dialog for the Monopoly application. | ||||
| import pp.monopoly.client.gui.GameMenu; | ||||
| import pp.dialog.DialogManager; | ||||
|  | ||||
| import java.util.prefs.Preferences; | ||||
|  | ||||
| import static pp.monopoly.Resources.lookup; | ||||
| import static pp.util.PreferencesUtils.getPreferences; | ||||
|  | ||||
|  */ | ||||
| public class StartMenu extends Dialog { | ||||
|     private static final Preferences PREFERENCES = getPreferences(StartMenu.class); | ||||
|     private final MonopolyApp app; | ||||
|  | ||||
|     // Buttons for the menu | ||||
|     private final Button playButton = new Button(lookup("button.play")); | ||||
|     private final Button quitButton = new Button(lookup("menu.quit")); | ||||
|     private final Button settingsButton = new Button("Einstellungen", new ElementId("menu-button")); | ||||
|     private Container logoContainer; | ||||
|     private Container unibwLogoContainer; | ||||
|  | ||||
|     /** | ||||
|      * Constructs the StartMenu dialog for the Monopoly application. | ||||
|      * Constructs the Startup Menu dialog for the Monopoly application. | ||||
|      * | ||||
|      * @param app the MonopolyApp instance | ||||
|      */ | ||||
|     public StartMenu(MonopolyApp app) { | ||||
|         super(app.getDialogManager()); | ||||
|         this.app = app; | ||||
|     } | ||||
|  | ||||
|         // Load and display the background image | ||||
|         TextureKey backgroundKey = new TextureKey("unibw-bib", false); | ||||
|         Texture backgroundTexture = app.getAssetManager().loadTexture(backgroundKey); | ||||
|     /** | ||||
|      * Creates and displays the Start Menu with buttons for starting the game, | ||||
|      * opening settings, and quitting the application. | ||||
|      */ | ||||
|     public static void createStartMenu(MonopolyApp app) { | ||||
|         int screenWidth = app.getContext().getSettings().getWidth(); | ||||
|         int screenHeight = app.getContext().getSettings().getHeight(); | ||||
|  | ||||
|         // Set up the background image | ||||
|         Texture backgroundImage = app.getAssetManager().loadTexture("Pictures/unibw-Bib2.png"); | ||||
|         Quad quad = new Quad(screenWidth, screenHeight); | ||||
|         Geometry background = new Geometry("Background", quad); | ||||
|         Material backgroundMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); | ||||
|         backgroundMaterial.setTexture("ColorMap", backgroundTexture); | ||||
|  | ||||
|         // Create a large Quad for the background | ||||
|         Quad backgroundQuad = new Quad(16, 9); // Adjust size as necessary to fill the screen | ||||
|         Geometry background = new Geometry("Background", backgroundQuad); | ||||
|         backgroundMaterial.setTexture("ColorMap", backgroundImage); | ||||
|         background.setMaterial(backgroundMaterial); | ||||
|         background.setLocalTranslation(new Vector3f(-8, -4.5f, -1)); // Position it behind the UI components | ||||
|  | ||||
|         // Attach the background as the first element | ||||
|         background.setLocalTranslation(0, 0, -1); // Ensure it is behind other GUI elements | ||||
|         app.getGuiNode().attachChild(background); | ||||
|  | ||||
|         // Load and display the Monopoly logo | ||||
|         TextureKey monopolyLogoKey = new TextureKey("log-Monopoly", false); | ||||
|         Texture monopolyLogoTexture = app.getAssetManager().loadTexture(monopolyLogoKey); | ||||
|         Material monopolyLogoMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); | ||||
|         monopolyLogoMaterial.setTexture("ColorMap", monopolyLogoTexture); | ||||
|         createMonopolyLogo(app); | ||||
|         createUnibwLogo(app); | ||||
|  | ||||
|         Quad monopolyQuad = new Quad(5, 1.5f); // Adjust dimensions as necessary | ||||
|         Geometry monopolyLogo = new Geometry("MonopolyLogo", monopolyQuad); | ||||
|         monopolyLogo.setMaterial(monopolyLogoMaterial); | ||||
|         monopolyLogo.setLocalTranslation(new Vector3f(0, 5, 0)); // Position Monopoly logo at the top | ||||
|         // Center container for title and play button | ||||
|         Container centerMenu = new Container(new SpringGridLayout(Axis.Y, Axis.X)); | ||||
|  | ||||
|         Panel monopolyLogoPanel = new Panel(); | ||||
|         addChild(monopolyLogoPanel); | ||||
|         Button startButton = new Button("Spielen"); | ||||
|         startButton.setPreferredSize(new Vector3f(190, 60, 0)); // Increase button size (width, height) | ||||
|         startButton.setFontSize(40); // Set the font size for the button text | ||||
|         startButton.setTextHAlignment(HAlignment.Center); // Center the text horizontally | ||||
|  | ||||
|         // Load and display the university logo | ||||
|         TextureKey universityLogoKey = new TextureKey("unibw-logo.png", false); | ||||
|         Texture universityLogoTexture = app.getAssetManager().loadTexture(universityLogoKey); | ||||
|         Material universityLogoMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); | ||||
|         universityLogoMaterial.setTexture("ColorMap", universityLogoTexture); | ||||
|         startButton.addClickCommands(source -> startGame(app)); | ||||
|         centerMenu.addChild(startButton); | ||||
|  | ||||
|         Quad universityQuad = new Quad(4, 1); // Adjust dimensions to fit below Monopoly logo | ||||
|         Geometry universityLogo = new Geometry("UniversityLogo", universityQuad); | ||||
|         universityLogo.setMaterial(universityLogoMaterial); | ||||
|         universityLogo.setLocalTranslation(new Vector3f(0, 3, 0)); // Position below the Monopoly logo | ||||
|         // Position the center container in the middle of the screen | ||||
|         centerMenu.setLocalTranslation(new Vector3f(screenWidth / 2f - centerMenu.getPreferredSize().x / 2f, | ||||
|                                                     screenHeight / 2f - 280 + centerMenu.getPreferredSize().y / 2f, | ||||
|                                                     0)); | ||||
|         app.getGuiNode().attachChild(centerMenu); | ||||
|  | ||||
|         Panel universityLogoPanel = new Panel(); | ||||
|         addChild(universityLogoPanel); | ||||
|         // Lower-left container for "Spiel beenden" button | ||||
|         Container lowerLeftMenu = new Container(); | ||||
|         lowerLeftMenu.setLocalTranslation(new Vector3f(100, 90, 0)); | ||||
|         Button quitButton = new Button("Spiel beenden"); | ||||
|         quitButton.setPreferredSize(new Vector3f(130, 40, 0)); // Increase button size slightly (width, height) | ||||
|         quitButton.setFontSize(18); | ||||
|         quitButton.addClickCommands(source -> quitGame()); | ||||
|         lowerLeftMenu.addChild(quitButton); | ||||
|         app.getGuiNode().attachChild(lowerLeftMenu); | ||||
|  | ||||
|  | ||||
|  | ||||
|         // Button actions | ||||
|         playButton.addClickCommands(source -> startGame()); | ||||
|         quitButton.addClickCommands(source -> app.closeApp()); | ||||
|         settingsButton.addClickCommands(source -> openSettings()); | ||||
|  | ||||
|         addChild(monopolyLogoPanel); | ||||
|         addChild(universityLogoPanel); | ||||
|         addChild(playButton); | ||||
|         addChild(quitButton); | ||||
|         addChild(settingsButton); | ||||
|         // Lower-right container for "Einstellungen" button | ||||
|         Container lowerRightMenu = new Container(); | ||||
|         lowerRightMenu.setLocalTranslation(new Vector3f(screenWidth - 200, 90, 0)); | ||||
|         Button settingsButton = new Button("Einstellungen"); | ||||
|         settingsButton.setPreferredSize(new Vector3f(130, 40, 0)); // Increase button size slightly (width, height) | ||||
|         settingsButton.setFontSize(18); // Increase the font size for the text | ||||
|         settingsButton.addClickCommands(source -> openSettings(app)); | ||||
|         lowerRightMenu.addChild(settingsButton); | ||||
|         app.getGuiNode().attachChild(lowerRightMenu); | ||||
|     } | ||||
|  | ||||
|     private void startGame() { | ||||
|         System.out.println("Starting game..."); | ||||
|     /** | ||||
|      * Creates and positions the Monopoly logo container in the center of the screen. | ||||
|      */ | ||||
|     private static void createMonopolyLogo(MonopolyApp app) { | ||||
|         int screenWidth = app.getContext().getSettings().getWidth(); | ||||
|         int screenHeight = app.getContext().getSettings().getHeight(); | ||||
|  | ||||
|         // Load the Monopoly logo as a texture | ||||
|         Texture logoTexture = app.getAssetManager().loadTexture("Pictures/logo-monopoly.png"); | ||||
|  | ||||
|         // Create a container for the logo | ||||
|         Container logoContainer = new Container(); | ||||
|         QuadBackgroundComponent logoBackground = new QuadBackgroundComponent(logoTexture); | ||||
|         logoContainer.setBackground(logoBackground); | ||||
|  | ||||
|         // Set the size of the container to fit the logo | ||||
|         float logoWidth = 512;  // Adjust these values based on the logo dimensions | ||||
|         float logoHeight = 128; // Adjust these values based on the logo dimensions | ||||
|         logoContainer.setPreferredSize(new Vector3f(logoWidth, logoHeight, 0)); | ||||
|  | ||||
|         // Position the container at the center of the screen | ||||
|         logoContainer.setLocalTranslation(new Vector3f( | ||||
|                 screenWidth / 2f - logoWidth / 2f, | ||||
|                 screenHeight / 2f + 200, // Adjust this value for vertical position | ||||
|                 0 | ||||
|         )); | ||||
|  | ||||
|         // Attach the container to the GUI node | ||||
|         app.getGuiNode().attachChild(logoContainer); | ||||
|     } | ||||
|  | ||||
|     private void openSettings() { | ||||
|         app.getDialogManager().close(this); | ||||
|         app.getDialogManager().open(new GameMenu(app)); | ||||
|     /** | ||||
|      * Creates and positions the Unibw logo container in the center of the screen. | ||||
|      */ | ||||
|     private static void createUnibwLogo(MonopolyApp app) { | ||||
|         int screenWidth = app.getContext().getSettings().getWidth(); | ||||
|         int screenHeight = app.getContext().getSettings().getHeight(); | ||||
|  | ||||
|         // Load the Unibw logo as a texture | ||||
|         Texture unibwTexture = app.getAssetManager().loadTexture("Pictures/logo-unibw.png"); | ||||
|  | ||||
|         // Create a container for the Unibw logo | ||||
|         Container unibwContainer = new Container(); | ||||
|         QuadBackgroundComponent unibwBackground = new QuadBackgroundComponent(unibwTexture); | ||||
|         unibwContainer.setBackground(unibwBackground); | ||||
|  | ||||
|         // Set the size of the container to fit the Unibw logo | ||||
|         float unibwWidth = 512;  // Adjust these values based on the logo dimensions | ||||
|         float unibwHeight = 128; // Adjust these values based on the logo dimensions | ||||
|         unibwContainer.setPreferredSize(new Vector3f(unibwWidth, unibwHeight, 0)); | ||||
|  | ||||
|         // Position the container slightly below the Monopoly logo | ||||
|         unibwContainer.setLocalTranslation(new Vector3f( | ||||
|                 screenWidth / 2f - unibwWidth / 2f, | ||||
|                 screenHeight / 2f + 100, // Adjust this value for vertical position | ||||
|                 0 | ||||
|         )); | ||||
|  | ||||
|         // Attach the container to the GUI node | ||||
|         app.getGuiNode().attachChild(unibwContainer); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void update() { | ||||
|     /** | ||||
|      * Starts the game by transitioning to the CreateGameMenu. | ||||
|      */ | ||||
|     private static void startGame(MonopolyApp app) { | ||||
|         app.getGuiNode().detachAllChildren(); | ||||
|         new CreateGameMenu(app); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void escape() { | ||||
|         close(); | ||||
|     /** | ||||
|      * Opens the settings menu. | ||||
|      */ | ||||
|     private static void openSettings(MonopolyApp app) { | ||||
|         app.getGuiNode().detachAllChildren(); | ||||
|         new SettingsMenu(app); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     /** | ||||
|      * Quits the game application. | ||||
|      */ | ||||
|     private static void quitGame() { | ||||
|         System.exit(0); | ||||
|     } | ||||
| } | ||||
| @@ -1,16 +1,10 @@ | ||||
| //////////////////////////////////////// | ||||
| // Programming project code | ||||
| // UniBw M, 2022, 2023, 2024 | ||||
| // www.unibw.de/inf2 | ||||
| // (c) Mark Minas (mark.minas@unibw.de) | ||||
| //////////////////////////////////////// | ||||
|  | ||||
| package pp.monopoly.client.gui; | ||||
|  | ||||
| import com.jme3.scene.Node; | ||||
| import com.jme3.scene.Spatial; | ||||
| import pp.monopoly.model.Item; | ||||
|  | ||||
| import pp.monopoly.model.Board; | ||||
| import pp.monopoly.model.Item; | ||||
| import pp.monopoly.model.Visitor; | ||||
| import pp.monopoly.notification.GameEventListener; | ||||
| import pp.monopoly.notification.ItemAddedEvent; | ||||
| @@ -21,30 +15,23 @@ import pp.view.ModelViewSynchronizer; | ||||
|  * Abstract base class for synchronizing the visual representation of a {@link Board} with its model state. | ||||
|  * This class handles the addition and removal of items from the map, ensuring that changes in the model | ||||
|  * are accurately reflected in the view. | ||||
|  * <p> | ||||
|  * Subclasses are responsible for providing the specific implementation of how each item in the map | ||||
|  * is represented visually by implementing the {@link Visitor} interface. | ||||
|  * </p> | ||||
|  */ | ||||
| abstract class BoardSynchronizer extends ModelViewSynchronizer<Item> implements Visitor<Spatial>, GameEventListener { | ||||
|     // The map that this synchronizer is responsible for | ||||
|     private final Board board; | ||||
|     protected final Board board; | ||||
|  | ||||
|     /** | ||||
|      * Constructs a new BoardSynchronizer. | ||||
|      * Initializes the synchronizer with the provided map and the root node for attaching view representations. | ||||
|      * | ||||
|      * @param map  the map to be synchronized | ||||
|      * @param root the root node to which the view representations of the map items are attached | ||||
|      * @param board the game board to synchronize | ||||
|      * @param root  the root node to which the view representations of the board items are attached | ||||
|      */ | ||||
|     protected BoardSynchronizer(Board map, Node root) { | ||||
|     protected BoardSynchronizer(Board board, Node root) { | ||||
|         super(root); | ||||
|         this.board = map; | ||||
|         this.board = board; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Translates a model item into its corresponding visual representation. | ||||
|      * The specific visual representation is determined by the concrete implementation of the {@link Visitor} interface. | ||||
|      * | ||||
|      * @param item the item from the model to be translated | ||||
|      * @return the visual representation of the item as a {@link Spatial} | ||||
| @@ -55,35 +42,33 @@ abstract class BoardSynchronizer extends ModelViewSynchronizer<Item> implements | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Adds the existing items from the map to the view. | ||||
|      * This method should be called during initialization to ensure that all current items in the map | ||||
|      * are visually represented. | ||||
|      * Adds the existing items from the board to the view during initialization. | ||||
|      */ | ||||
|     protected void addExisting() { | ||||
|         board.getItems().forEach(this::add); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handles the event when an item is removed from the map. | ||||
|      * Removes the visual representation of the item from the view if it belongs to the synchronized map. | ||||
|      * Handles the event when an item is removed from the board. | ||||
|      * | ||||
|      * @param event the event indicating that an item has been removed from the map | ||||
|      * @param event the event indicating that an item has been removed from the board | ||||
|      */ | ||||
|     @Override | ||||
|     public void receivedEvent(ItemRemovedEvent event) { | ||||
|         if (board == event.map()) | ||||
|             delete(event.item()); | ||||
|         if (board == event.getBoard()) { | ||||
|             delete(event.getItem()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handles the event when an item is added to the map. | ||||
|      * Adds the visual representation of the new item to the view if it belongs to the synchronized map. | ||||
|      * Handles the event when an item is added to the board. | ||||
|      * | ||||
|      * @param event the event indicating that an item has been added to the map | ||||
|      * @param event the event indicating that an item has been added to the board | ||||
|      */ | ||||
|     @Override | ||||
|     public void receivedEvent(ItemAddedEvent event) { | ||||
|         if (board == event.map()) | ||||
|             add(event.item()); | ||||
|         if (board == event.getBoard()) { | ||||
|             add(event.getItem()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import com.jme3.math.ColorRGBA; | ||||
| import com.simsilica.lemur.Button; | ||||
| import com.simsilica.lemur.Label; | ||||
| import com.simsilica.lemur.style.ElementId; | ||||
|  | ||||
| import pp.dialog.Dialog; | ||||
| import pp.monopoly.client.MonopolyApp; | ||||
|  | ||||
|   | ||||
| @@ -1,55 +1,38 @@ | ||||
| //////////////////////////////////////// | ||||
| // Programming project code | ||||
| // UniBw M, 2022, 2023, 2024 | ||||
| // www.unibw.de/inf2 | ||||
| // (c) Mark Minas (mark.minas@unibw.de) | ||||
| //////////////////////////////////////// | ||||
|  | ||||
| package pp.monopoly.client.gui; | ||||
|  | ||||
| import com.jme3.material.Material; | ||||
| import com.jme3.material.RenderState.BlendMode; | ||||
| import com.jme3.math.ColorRGBA; | ||||
| import com.jme3.math.Vector2f; | ||||
| import com.jme3.math.Vector3f; | ||||
| import com.jme3.scene.Geometry; | ||||
| import com.jme3.scene.Node; | ||||
| import com.jme3.scene.Spatial.CullHint; | ||||
| import com.jme3.scene.shape.Quad; | ||||
|  | ||||
| import pp.monopoly.client.MonopolyApp; | ||||
| import pp.monopoly.model.IntPoint; | ||||
| import pp.monopoly.model.Board; | ||||
| import pp.util.FloatPoint; | ||||
| import pp.util.Position; | ||||
|  | ||||
| /** | ||||
|  * Represents the visual view of a {@link Board}, used to display the map structure such as the player's map, harbor, | ||||
|  * and opponent's map. This class handles the graphical representation of the map, including background setup, grid lines, | ||||
|  * and interaction between the model and the view. | ||||
|  * Represents the visual view of a {@link Board}, used to display the map structure and elements. | ||||
|  * This class handles the graphical representation of the board, including background setup and grid lines. | ||||
|  */ | ||||
| class MapView { | ||||
|     private static final float FIELD_SIZE = 40f; | ||||
|     private static final float GRID_LINE_WIDTH = 2f; | ||||
|     private static final float BACKGROUND_DEPTH = -4f; | ||||
|     private static final float GRID_DEPTH = -1f; | ||||
|     private static final ColorRGBA BACKGROUND_COLOR = new ColorRGBA(0, 0.05f, 0.05f, 0.5f); | ||||
|     private static final ColorRGBA GRID_COLOR = ColorRGBA.Green; | ||||
|  | ||||
|     // Reference to the main application and the ship map being visualized | ||||
|     private final MonopolyApp app; | ||||
|     private final Node mapNode = new Node("map"); // NON-NLS | ||||
|     private final Board map; | ||||
|     private final Node mapNode = new Node("map"); | ||||
|     private final Board board; | ||||
|     private final MapViewSynchronizer synchronizer; | ||||
|  | ||||
|     /** | ||||
|      * Constructs a new MapView for a given {@link Board} and {@link MonopolyApp}. | ||||
|      * Initializes the view by setting up the background and registering a synchronizer to listen to changes in the map. | ||||
|      * | ||||
|      * @param map the ship map to visualize | ||||
|      * @param app the main application instance | ||||
|      * @param board the board to visualize | ||||
|      * @param app   the main application instance | ||||
|      */ | ||||
|     MapView(Board map, MonopolyApp app) { | ||||
|         this.map = map; | ||||
|     MapView(Board board, MonopolyApp app) { | ||||
|         this.board = board; | ||||
|         this.app = app; | ||||
|         this.synchronizer = new MapViewSynchronizer(this); | ||||
|         setupBackground(); | ||||
| @@ -57,65 +40,26 @@ class MapView { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Unregisters the {@link MapViewSynchronizer} from the listener list of the ClientGameLogic, | ||||
|      * stopping the view from receiving updates when the underlying {@link Board} changes. | ||||
|      * After calling this method, this MapView instance should no longer be used. | ||||
|      * Unregisters the {@link MapViewSynchronizer} from listening to board changes. | ||||
|      */ | ||||
|     void unregister() { | ||||
|         app.getGameLogic().removeListener(synchronizer); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the {@link Board} associated with this view. | ||||
|      * | ||||
|      * @return the ship map | ||||
|      */ | ||||
|     public Board getMap() { | ||||
|         return map; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the {@link MonopolyApp} instance associated with this view. | ||||
|      * | ||||
|      * @return the main application instance | ||||
|      */ | ||||
|     public MonopolyApp getApp() { | ||||
|         return app; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sets up the background of the map view using a quad geometry. | ||||
|      * The background is configured with a semi-transparent color and placed at a specific depth. | ||||
|      */ | ||||
|     private void setupBackground() { | ||||
|         final Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); // NON-NLS | ||||
|         mat.setColor("Color", BACKGROUND_COLOR); // NON-NLS | ||||
|         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); | ||||
|         mat.setColor("Color", BACKGROUND_COLOR); | ||||
|         mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); | ||||
|         final Position corner = modelToView(map.getWidth(), map.getHeight()); | ||||
|         final Geometry background = new Geometry("MapBackground", new Quad(corner.getX(), corner.getY())); | ||||
|         Geometry background = new Geometry("MapBackground", new Quad(board.getWidth() * FIELD_SIZE, board.getHeight() * FIELD_SIZE)); | ||||
|         background.setMaterial(mat); | ||||
|         background.setLocalTranslation(0f, 0f, BACKGROUND_DEPTH); | ||||
|         background.setLocalTranslation(0f, 1f, BACKGROUND_DEPTH); | ||||
|         background.setCullHint(CullHint.Never); | ||||
|         mapNode.attachChild(background); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Adds grid lines to the map view to visually separate the fields within the map. | ||||
|      * The grid lines are drawn based on the dimensions of the ship map. | ||||
|      */ | ||||
|     public void addGrid() { | ||||
|         for (int x = 0; x <= map.getWidth(); x++) { | ||||
|             final Position f = modelToView(x, 0); | ||||
|             final Position t = modelToView(x, map.getHeight()); | ||||
|             mapNode.attachChild(gridLine(f, t)); | ||||
|         } | ||||
|         for (int y = 0; y <= map.getHeight(); y++) { | ||||
|             final Position f = modelToView(0, y); | ||||
|             final Position t = modelToView(map.getWidth(), y); | ||||
|             mapNode.attachChild(gridLine(f, t)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the root node containing all visual elements in this map view. | ||||
|      * | ||||
| @@ -125,67 +69,7 @@ class MapView { | ||||
|         return mapNode; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the total width of the map in view coordinates. | ||||
|      * | ||||
|      * @return the width of the map in view coordinates | ||||
|      */ | ||||
|     public float getWidth() { | ||||
|         return FIELD_SIZE * map.getWidth(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the total height of the map in view coordinates. | ||||
|      * | ||||
|      * @return the height of the map in view coordinates | ||||
|      */ | ||||
|     public float getHeight() { | ||||
|         return FIELD_SIZE * map.getHeight(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Converts coordinates from view coordinates to model coordinates. | ||||
|      * | ||||
|      * @param x the x-coordinate in view space | ||||
|      * @param y the y-coordinate in view space | ||||
|      * @return the corresponding model coordinates as an {@link IntPoint} | ||||
|      */ | ||||
|     public IntPoint viewToModel(float x, float y) { | ||||
|         return new IntPoint((int) Math.floor(x / FIELD_SIZE), (int) Math.floor(y / FIELD_SIZE)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Converts coordinates from model coordinates to view coordinates. | ||||
|      * | ||||
|      * @param x the x-coordinate in model space | ||||
|      * @param y the y-coordinate in model space | ||||
|      * @return the corresponding view coordinates as a {@link Position} | ||||
|      */ | ||||
|     public Position modelToView(float x, float y) { | ||||
|         return new FloatPoint(x * FIELD_SIZE, y * FIELD_SIZE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Converts the mouse position to model coordinates. | ||||
|      * This method takes into account the map's transformation in the 3D scene. | ||||
|      * | ||||
|      * @param pos the 2D vector representing the mouse position in the view | ||||
|      * @return the corresponding model coordinates as an {@link IntPoint} | ||||
|      */ | ||||
|     public IntPoint mouseToModel(Vector2f pos) { | ||||
|         final Vector3f world = new Vector3f(pos.getX(), pos.getY(), 0f); | ||||
|         final Vector3f view = mapNode.getWorldTransform().transformInverseVector(world, null); | ||||
|         return viewToModel(view.getX(), view.getY()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a visual representation of a grid line between two positions. | ||||
|      * | ||||
|      * @param p1 the start position of the grid line | ||||
|      * @param p2 the end position of the grid line | ||||
|      * @return a {@link Geometry} representing the grid line | ||||
|      */ | ||||
|     private Geometry gridLine(Position p1, Position p2) { | ||||
|         return app.getDraw().makeFatLine(p1, p2, GRID_DEPTH, GRID_COLOR, GRID_LINE_WIDTH); | ||||
|     public Board getBoard() { | ||||
|         return board; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,63 +1,45 @@ | ||||
| //////////////////////////////////////// | ||||
| // Programming project code | ||||
| // UniBw M, 2022, 2023, 2024 | ||||
| // www.unibw.de/inf2 | ||||
| // (c) Mark Minas (mark.minas@unibw.de) | ||||
| //////////////////////////////////////// | ||||
|  | ||||
| package pp.monopoly.client.gui; | ||||
|  | ||||
| import com.jme3.math.ColorRGBA; | ||||
| import com.jme3.scene.Geometry; | ||||
| import com.jme3.scene.Node; | ||||
| import com.jme3.scene.Spatial; | ||||
| import pp.util.Position; | ||||
|  | ||||
| import pp.monopoly.model.Figure; | ||||
|  | ||||
| /** | ||||
|  * Synchronizes the visual representation of the ship map with the game model. | ||||
|  * It handles the rendering of ships and shots on the map view, updating the view | ||||
|  * whenever changes occur in the model. | ||||
|  * Synchronizes the visual representation of the board with the game model. | ||||
|  * Handles updates for items on the board. | ||||
|  */ | ||||
| class MapViewSynchronizer extends BoardSynchronizer { | ||||
|     // Constants for rendering properties | ||||
|     private static final float SHIP_LINE_WIDTH = 6f; | ||||
|     private static final float SHOT_DEPTH = -2f; | ||||
|     private static final float SHIP_DEPTH = 0f; | ||||
|     private static final float INDENT = 4f; | ||||
|  | ||||
|     // Colors used for different visual elements | ||||
|     private static final ColorRGBA HIT_COLOR = ColorRGBA.Red; | ||||
|     private static final ColorRGBA MISS_COLOR = ColorRGBA.Blue; | ||||
|     private static final ColorRGBA SHIP_BORDER_COLOR = ColorRGBA.White; | ||||
|     private static final ColorRGBA PREVIEW_COLOR = ColorRGBA.Gray; | ||||
|     private static final ColorRGBA ERROR_COLOR = ColorRGBA.Red; | ||||
|  | ||||
|     // The MapView associated with this synchronizer | ||||
|     private final MapView view; | ||||
|  | ||||
|     /** | ||||
|      * Constructs a new MapViewSynchronizer for the given MapView. | ||||
|      * Initializes the synchronizer and adds existing elements from the model to the view. | ||||
|      * | ||||
|      * @param view the MapView to synchronize with the game model | ||||
|      */ | ||||
|     public MapViewSynchronizer(MapView view) { | ||||
|         super(view.getMap(), view.getNode()); | ||||
|         super(view.getBoard(), view.getNode()); | ||||
|         this.view = view; | ||||
|         addExisting(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a line geometry representing part of the ship's border. | ||||
|      * | ||||
|      * @param x1    the starting x-coordinate of the line | ||||
|      * @param y1    the starting y-coordinate of the line | ||||
|      * @param x2    the ending x-coordinate of the line | ||||
|      * @param y2    the ending y-coordinate of the line | ||||
|      * @param color the color of the line | ||||
|      * @return a Geometry representing the line | ||||
|      * Enables the state by performing initial setup, such as adding any items to the view. | ||||
|      */ | ||||
|     private Geometry shipLine(float x1, float y1, float x2, float y2, ColorRGBA color) { | ||||
|         return view.getApp().getDraw().makeFatLine(x1, y1, x2, y2, SHIP_DEPTH, color, SHIP_LINE_WIDTH); | ||||
|      | ||||
|     protected void enableState() { | ||||
|         // Platz für zusätzliche Initialisierungen | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Disables the state by clearing the view. | ||||
|      */ | ||||
|      | ||||
|     protected void disableState() { | ||||
|         view.getNode().detachAllChildren(); // Entfernt alle visuellen Elemente vom Knoten | ||||
|     } | ||||
|  | ||||
|      | ||||
|     public Spatial visit(Figure figure) { | ||||
|         return figure.accept(this); | ||||
|     } | ||||
| } | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 484 KiB | 
| @@ -7,4 +7,9 @@ description = 'Monopoly common model' | ||||
| dependencies { | ||||
|     api project(":common") | ||||
|     api libs.jme3.networking | ||||
| } | ||||
|     testImplementation libs.mockito.core | ||||
|  | ||||
|     testImplementation project(":monopoly:client") | ||||
|     testImplementation project(":monopoly:model") | ||||
|     testImplementation project(":monopoly:server") | ||||
| } | ||||
| @@ -7,13 +7,10 @@ | ||||
|  | ||||
| package pp.monopoly; | ||||
|  | ||||
| import pp.util.config.Config; | ||||
|  | ||||
| import java.util.Map; | ||||
| import java.util.TreeMap; | ||||
|  | ||||
| import static java.lang.Math.max; | ||||
|  | ||||
| import pp.util.config.Config; | ||||
|  | ||||
| /** | ||||
|  * Provides access to the configuration settings for the Monopoly game. | ||||
|  * <p> | ||||
| @@ -31,7 +28,7 @@ public class MonopolyConfig extends Config { | ||||
|      * The default port number for the Monopoly server. | ||||
|      */ | ||||
|     @Property("port") | ||||
|     private int port = 1234; | ||||
|     private int port = 42069; | ||||
|  | ||||
|     /** | ||||
|      * The width of the game map in terms of grid units. | ||||
|   | ||||
| @@ -7,6 +7,13 @@ | ||||
|  | ||||
| package pp.monopoly.game.client; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.lang.System.Logger; | ||||
| import java.lang.System.Logger.Level; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import pp.monopoly.message.client.ClientMessage; | ||||
| import pp.monopoly.message.server.BuyPropertyResponse; | ||||
| import pp.monopoly.message.server.DiceResult; | ||||
| @@ -19,10 +26,9 @@ import pp.monopoly.message.server.ServerInterpreter; | ||||
| import pp.monopoly.message.server.TimeOutWarning; | ||||
| import pp.monopoly.message.server.TradeReply; | ||||
| import pp.monopoly.message.server.TradeRequest; | ||||
| import pp.monopoly.message.server.UpdatePlayerAssets; | ||||
| import pp.monopoly.message.server.ViewAssetsResponse; | ||||
| import pp.monopoly.model.IntPoint; | ||||
| import pp.monopoly.model.Board; | ||||
| import pp.monopoly.model.IntPoint; | ||||
| import pp.monopoly.notification.ClientStateEvent; | ||||
| import pp.monopoly.notification.GameEvent; | ||||
| import pp.monopoly.notification.GameEventBroker; | ||||
| @@ -38,8 +44,6 @@ import java.lang.System.Logger.Level; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import static java.lang.Math.max; | ||||
|  | ||||
| /** | ||||
|  * Controls the client-side game logic for Monopoly. | ||||
|  * Manages the player's placement, interactions with the map, and response to server messages. | ||||
| @@ -48,9 +52,8 @@ public class ClientGameLogic implements ServerInterpreter, GameEventBroker { | ||||
|     static final Logger LOGGER = System.getLogger(ClientGameLogic.class.getName()); | ||||
|     private final ClientSender clientSender; | ||||
|     private final List<GameEventListener> listeners = new ArrayList<>(); | ||||
|     private Board ownMap; | ||||
|     private Board harbor; | ||||
|     private Board opponentMap; | ||||
|     private Board board; | ||||
|  | ||||
|     private ClientState state = new ClientState(this) { | ||||
|          | ||||
|     }; | ||||
| @@ -88,8 +91,8 @@ public class ClientGameLogic implements ServerInterpreter, GameEventBroker { | ||||
|      * | ||||
|      * @return the player's own map | ||||
|      */ | ||||
|     public Board getMap() { | ||||
|         return ownMap; | ||||
|     public Board getBoard() { | ||||
|         return board; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -185,73 +188,73 @@ public class ClientGameLogic implements ServerInterpreter, GameEventBroker { | ||||
|  | ||||
|     @Override | ||||
|     public void received(BuyPropertyResponse msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|         if (msg.isSuccessful()) { | ||||
|             setInfoText("You successfully bought " + msg.getPropertyName() + "!"); | ||||
|             playSound(Sound.MONEY_LOST); | ||||
|         } else { | ||||
|             setInfoText("Unable to buy " + msg.getPropertyName() + ". Reason: " + msg.getReason()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|      | ||||
|     @Override | ||||
|     public void received(DiceResult msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|         setInfoText("You rolled a " + msg.calcTotal() + "!"); | ||||
|         playSound(Sound.DICE_ROLL); | ||||
|     } | ||||
|  | ||||
|      | ||||
|     @Override | ||||
|     public void received(EventDrawCard msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|         setInfoText("Event card drawn: " + msg.getCardDescription()); | ||||
|         playSound(Sound.EVENT_CARD); | ||||
|     } | ||||
|  | ||||
|      | ||||
|     @Override | ||||
|     public void received(GameOver msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|         if (msg.isWinner()) { | ||||
|             setInfoText("Congratulations! You have won the game!"); | ||||
|             playSound(Sound.WINNER); | ||||
|         } else { | ||||
|             setInfoText("Game over. Better luck next time!"); | ||||
|             playSound(Sound.LOSER); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|      | ||||
|     @Override | ||||
|     public void received(GameStart msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|         setInfoText("The game has started! Good luck!"); | ||||
|     } | ||||
|  | ||||
|      | ||||
|     @Override | ||||
|     public void received(JailEvent msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|         if (msg.isGoingToJail()) { | ||||
|             setInfoText("You are sent to jail!"); | ||||
|             playSound(Sound.GULAG); | ||||
|         } else { | ||||
|             setInfoText("You are out of jail!"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|      | ||||
|     @Override | ||||
|     public void received(PlayerStatusUpdate msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|         setInfoText("Player " + msg.getPlayerName() + " status updated: " + msg.getStatus()); | ||||
|     } | ||||
|  | ||||
|      | ||||
|     @Override | ||||
|     public void received(TimeOutWarning msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|         setInfoText("Warning! Time is running out. You have " + msg.getRemainingTime() + " seconds left."); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void received(UpdatePlayerAssets msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|     } | ||||
|  | ||||
|      | ||||
|     @Override | ||||
|     public void received(ViewAssetsResponse msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|         setInfoText("Your current assets are being displayed."); | ||||
|     } | ||||
|  | ||||
|      | ||||
|     @Override | ||||
|     public void received(TradeReply msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|     } | ||||
|  | ||||
|      | ||||
|     @Override | ||||
|     public void received(TradeRequest msg) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|     } | ||||
|      | ||||
| } | ||||
|   | ||||
| @@ -7,13 +7,9 @@ | ||||
|  | ||||
| package pp.monopoly.game.client; | ||||
|  | ||||
| import pp.monopoly.MonopolyConfig; | ||||
| import pp.monopoly.model.IntPoint; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
|  | ||||
| import pp.monopoly.MonopolyConfig; | ||||
|  | ||||
| /** | ||||
|  * Class providing access to the Monopoly client configuration. | ||||
|   | ||||
| @@ -307,7 +307,7 @@ public class Player implements FieldVisitor<Void>{ | ||||
|         return count; | ||||
|     } | ||||
|  | ||||
|         /** | ||||
|     /** | ||||
|      * Inner class for dice functionality in the game. | ||||
|      * Rolls random dice values. | ||||
|      */ | ||||
| @@ -438,7 +438,7 @@ public class Player implements FieldVisitor<Void>{ | ||||
|             getOutOfJailCard--; | ||||
|             state = new ActiveState(); | ||||
|         } | ||||
|          | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private class BankruptState implements PlayerState { | ||||
| @@ -460,7 +460,7 @@ public class Player implements FieldVisitor<Void>{ | ||||
|             // TODO Auto-generated method stub | ||||
|             throw new UnsupportedOperationException("Unimplemented method 'useJailCard'"); | ||||
|         } | ||||
|          | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private class WaitForTurnState implements PlayerState { | ||||
| @@ -482,6 +482,6 @@ public class Player implements FieldVisitor<Void>{ | ||||
|             // TODO Auto-generated method stub | ||||
|             throw new UnsupportedOperationException("Unimplemented method 'useJailCard'"); | ||||
|         } | ||||
|          | ||||
|  | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -109,8 +109,10 @@ public class PlayerHandler { | ||||
|      * @return the next players who is active | ||||
|      */ | ||||
|     Player nextPlayer() { | ||||
|         players.addLast(players.removeFirst()); | ||||
|         return players.getFirst(); | ||||
|         Player tmp = players.get(0); | ||||
|         players.remove(0); | ||||
|         players.add(tmp); | ||||
|         return players.get(0); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -12,6 +12,10 @@ import pp.monopoly.model.fields.PropertyField; | ||||
| import java.lang.System.Logger; | ||||
| import java.lang.System.Logger.Level; | ||||
|  | ||||
| import pp.monopoly.MonopolyConfig; | ||||
| import pp.monopoly.message.client.ClientInterpreter; | ||||
| import pp.monopoly.message.server.ServerMessage; | ||||
|  | ||||
| /** | ||||
|  * Controls the server-side game logic for Monopoly. | ||||
|  * Manages game states, player interactions, and message handling. | ||||
| @@ -95,6 +99,31 @@ public class ServerGameLogic implements ClientInterpreter { | ||||
|         return player; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Adds a new player to the game if the game is in the LOBBY state and the maximum | ||||
|      * player limit has not been reached. | ||||
|      * | ||||
|      * @param id the id of the player to add to the game | ||||
|      * @return the added Player, or null if the player could not be added | ||||
|      */ | ||||
|     public Player addPlayer(int id) { | ||||
|         Player player = new Player(id, playerHandler); | ||||
|         if (state != ServerState.LOBBY) { | ||||
|             LOGGER.log(Level.WARNING, "Cannot add player; game is not in LOBBY state."); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         if (playerHandler.getPlayerCount() >= MAX_PLAYERS) { | ||||
|             LOGGER.log(Level.WARNING, "Cannot add player; maximum player limit reached."); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         playerHandler.addPlayer(player); | ||||
|         LOGGER.log(Level.DEBUG, "Player added: {0}", player.getId()); | ||||
|  | ||||
|         return player; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handles a BuyPropertyRequest from a player, allowing the player to purchase a property | ||||
|      * if it is unowned and they have sufficient funds. | ||||
| @@ -223,4 +252,8 @@ public class ServerGameLogic implements ClientInterpreter { | ||||
|     public BoardManager getBoardManager() { | ||||
|         return boardManager; | ||||
|     } | ||||
|  | ||||
|     public Player getPlayerById(int id) { | ||||
|         return playerHandler.getPlayerById(id); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,34 @@ | ||||
| package pp.monopoly.message.server; | ||||
|  | ||||
| /** | ||||
|  * Represents the server's response to a player's request to buy a property. | ||||
|  */ | ||||
| public class BuyPropertyResponse extends ServerMessage{ | ||||
|     private final boolean successful; | ||||
|     private final String propertyName; | ||||
|     private final String reason; // Reason for failure, if any | ||||
|  | ||||
|     public BuyPropertyResponse(boolean successful, String propertyName, String reason) { | ||||
|         this.successful = successful; | ||||
|         this.propertyName = propertyName; | ||||
|         this.reason = reason; | ||||
|     } | ||||
|  | ||||
|     public boolean isSuccessful() { | ||||
|         return successful; | ||||
|     } | ||||
|  | ||||
|     public String getPropertyName() { | ||||
|         return propertyName; | ||||
|     } | ||||
|  | ||||
|     public String getReason() { | ||||
|         return reason; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void accept(ServerInterpreter interpreter) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'accept'"); | ||||
|         interpreter.received(this); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -13,5 +36,4 @@ public class BuyPropertyResponse extends ServerMessage{ | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'"); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,11 @@ | ||||
| package pp.monopoly.message.server; | ||||
|  | ||||
| public class EventDrawCard extends ServerMessage{ | ||||
|     private final String cardDescription; | ||||
|  | ||||
|     public EventDrawCard(String cardDescription) { | ||||
|         this.cardDescription = cardDescription; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void accept(ServerInterpreter interpreter) { | ||||
| @@ -13,4 +18,8 @@ public class EventDrawCard extends ServerMessage{ | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'"); | ||||
|     } | ||||
|  | ||||
|     public String getCardDescription() { | ||||
|         return cardDescription; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,15 @@ | ||||
| package pp.monopoly.message.server; | ||||
|  | ||||
| public class GameOver extends ServerMessage{ | ||||
|     private final boolean isWinner; | ||||
|  | ||||
|     public GameOver(boolean isWinner) { | ||||
|         this.isWinner = isWinner; | ||||
|     } | ||||
|  | ||||
|     public boolean isWinner() { | ||||
|         return isWinner; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void accept(ServerInterpreter interpreter) { | ||||
|   | ||||
| @@ -2,6 +2,16 @@ package pp.monopoly.message.server; | ||||
|  | ||||
| public class JailEvent extends ServerMessage{ | ||||
|  | ||||
|     private final boolean goingToJail; | ||||
|  | ||||
|     public JailEvent(boolean goingToJail) { | ||||
|         this.goingToJail = goingToJail; | ||||
|     } | ||||
|  | ||||
|     public boolean isGoingToJail() { | ||||
|         return goingToJail; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void accept(ServerInterpreter interpreter) { | ||||
|         interpreter.received(this); | ||||
|   | ||||
| @@ -1,7 +1,31 @@ | ||||
| package pp.monopoly.message.server; | ||||
|  | ||||
| import pp.monopoly.game.server.PlayerColor; | ||||
|  | ||||
| public class PlayerStatusUpdate extends ServerMessage{ | ||||
|  | ||||
|     private final String playerName; | ||||
|     private final String status; | ||||
|     private final PlayerColor color; | ||||
|  | ||||
|     public PlayerStatusUpdate(String playerName, String status, PlayerColor color) { | ||||
|         this.playerName = playerName; | ||||
|         this.status = status; | ||||
|         this.color = color; | ||||
|     } | ||||
|  | ||||
|     public String getPlayerName() { | ||||
|         return playerName; | ||||
|     } | ||||
|  | ||||
|     public String getStatus() { | ||||
|         return status; | ||||
|     } | ||||
|  | ||||
|     public PlayerColor getColor() { | ||||
|         return color; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void accept(ServerInterpreter interpreter) { | ||||
|         interpreter.received(this); | ||||
|   | ||||
| @@ -69,13 +69,6 @@ public interface ServerInterpreter { | ||||
|      */ | ||||
|     void received(TimeOutWarning msg); | ||||
|  | ||||
|     /** | ||||
|      * Handles a UpdatePlayerAssets message received from the server. | ||||
|      * | ||||
|      * @param msg the UpdatePlayerAssets message received | ||||
|      */ | ||||
|     void received(UpdatePlayerAssets msg); | ||||
|  | ||||
|     /** | ||||
|      * Handles a ViewAssetsResponse message received from the server. | ||||
|      * | ||||
|   | ||||
| @@ -2,6 +2,16 @@ package pp.monopoly.message.server; | ||||
|  | ||||
| public class TimeOutWarning extends ServerMessage{ | ||||
|  | ||||
|     private final int remainingTime; | ||||
|  | ||||
|     public TimeOutWarning(int remainingTime) { | ||||
|         this.remainingTime = remainingTime; | ||||
|     } | ||||
|  | ||||
|     public int getRemainingTime() { | ||||
|         return remainingTime; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void accept(ServerInterpreter interpreter) { | ||||
|         interpreter.received(this); | ||||
|   | ||||
| @@ -1,16 +0,0 @@ | ||||
| package pp.monopoly.message.server; | ||||
|  | ||||
| public class UpdatePlayerAssets extends ServerMessage{ | ||||
|  | ||||
|     @Override | ||||
|     public void accept(ServerInterpreter interpreter) { | ||||
|         interpreter.received(this); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getInfoTextKey() { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'"); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -8,9 +8,9 @@ import pp.monopoly.model.fields.PropertyField; | ||||
|  */ | ||||
| public class ViewAssetsResponse extends ServerMessage{ | ||||
|  | ||||
|     private List<PropertyField> properties; | ||||
|     private int accountBalance; | ||||
|     private int jailCards; | ||||
|     private final List<PropertyField> properties; | ||||
|     private final int accountBalance; | ||||
|     private final int jailCards; | ||||
|  | ||||
|     /** | ||||
|      * Constructs a ViewAssetsResponse with the specified properties and account balance. | ||||
|   | ||||
| @@ -7,15 +7,16 @@ | ||||
|  | ||||
| package pp.monopoly.model; | ||||
|  | ||||
| import pp.monopoly.notification.GameEvent; | ||||
| import pp.monopoly.notification.GameEventBroker; | ||||
| import pp.monopoly.notification.ItemAddedEvent; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| import pp.monopoly.notification.GameEvent; | ||||
| import pp.monopoly.notification.GameEventBroker; | ||||
| import pp.monopoly.notification.ItemAddedEvent; | ||||
| import pp.monopoly.notification.ItemRemovedEvent; | ||||
|  | ||||
| /** | ||||
|  * Represents a rectangular map that holds figures and registers houses, hotels | ||||
|  * It also supports event notification for game state changes such as item addition or removal. | ||||
| @@ -56,6 +57,7 @@ public class Board { | ||||
|         this.width = width; | ||||
|         this.height = height; | ||||
|         this.eventBroker = eventBroker; | ||||
|         addItem(new Figure(5, 5, 5, Rotation.LEFT)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -65,7 +67,7 @@ public class Board { | ||||
|      */ | ||||
|     private void addItem(Item item) { | ||||
|         items.add(item); | ||||
|         notifyListeners(new ItemAddedEvent(item, this)); | ||||
|         notifyListeners((GameEvent) new ItemAddedEvent(item, null)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -75,7 +77,7 @@ public class Board { | ||||
|      */ | ||||
|     public void remove(Item item) { | ||||
|         items.remove(item); | ||||
|         notifyListeners(new ItemAddedEvent(item, this)); | ||||
|         notifyListeners((GameEvent) new ItemRemovedEvent(item, null)); // Falls es ein entsprechendes ItemRemovedEvent gibt | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -1,17 +1,309 @@ | ||||
| package pp.monopoly.model; | ||||
|  | ||||
| public class Figure implements Item{ | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
|  | ||||
|     @Override | ||||
|     public <T> T accept(Visitor<T> visitor) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'accept'"); | ||||
| import static java.lang.Math.max; | ||||
| import static java.lang.Math.min; | ||||
|  | ||||
| public class Figure implements Item{ | ||||
|     /** | ||||
|      * Enumeration representing the different statuses a Figure can have during the game. | ||||
|      */ | ||||
|     public enum Status { | ||||
|         /** | ||||
|          * The ship is in its normal state, not being previewed for placement. | ||||
|          */ | ||||
|         NORMAL, | ||||
|  | ||||
|         /** | ||||
|          * The ship is being previewed in a valid position for placement. | ||||
|          */ | ||||
|         VALID_PREVIEW, | ||||
|  | ||||
|         /** | ||||
|          * The ship is being previewed in an invalid position for placement. | ||||
|          */ | ||||
|         INVALID_PREVIEW | ||||
|     } | ||||
|  | ||||
|     private final int length; // The length of the Figure | ||||
|     private int x;            // The x-coordinate of the Figure's position | ||||
|     private int y;            // The y-coordinate of the Figure's position | ||||
|     private Rotation rot;     // The rotation of the Figure | ||||
|     private Status status;    // The current status of the Figure | ||||
|     private final Set<IntPoint> damaged = new HashSet<>(); // The set of positions that have been hit on this ship | ||||
|  | ||||
|     /** | ||||
|      * Default constructor for serialization. Initializes a Figure with length 0, | ||||
|      * at position (0, 0), with a default rotation of RIGHT. | ||||
|      */ | ||||
|     private Figure() { | ||||
|         this(0, 0, 0, Rotation.RIGHT); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Constructs a new Figure with the specified length, position, and rotation. | ||||
|      * | ||||
|      * @param length the length of the Figure | ||||
|      * @param x      the x-coordinate of the Figure's initial position | ||||
|      * @param y      the y-coordinate of the Figure's initial position | ||||
|      * @param rot    the rotation of the Figure | ||||
|      */ | ||||
|     public Figure(int length, int x, int y, Rotation rot) { | ||||
|         this.x = x; | ||||
|         this.y = y; | ||||
|         this.rot = rot; | ||||
|         this.length = length; | ||||
|         this.status = Status.NORMAL; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the current x-coordinate of the Figure's position. | ||||
|      * | ||||
|      * @return the x-coordinate of the Figure | ||||
|      */ | ||||
|     public int getX() { | ||||
|         return x; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the current y-coordinate of the Figure's position. | ||||
|      * | ||||
|      * @return the y-coordinate of the Figure | ||||
|      */ | ||||
|     public int getY() { | ||||
|         return y; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Moves the Figure to the specified coordinates. | ||||
|      * | ||||
|      * @param x the new x-coordinate of the Figure's position | ||||
|      * @param y the new y-coordinate of the Figure's position | ||||
|      */ | ||||
|     public void moveTo(int x, int y) { | ||||
|         this.x = x; | ||||
|         this.y = y; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Moves the Figure to the specified position. | ||||
|      * | ||||
|      * @param pos the new position of the Figure | ||||
|      */ | ||||
|     public void moveTo(IntPosition pos) { | ||||
|         moveTo(pos.getX(), pos.getY()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the current status of the Figure. | ||||
|      * | ||||
|      * @return the status of the Figure | ||||
|      */ | ||||
|     public Status getStatus() { | ||||
|         return status; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sets the status of the Figure. | ||||
|      * | ||||
|      * @param status the new status to be set for the Figure | ||||
|      */ | ||||
|     public void setStatus(Status status) { | ||||
|         this.status = status; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the length of the Figure. | ||||
|      * | ||||
|      * @return the length of the Figure | ||||
|      */ | ||||
|     public int getLength() { | ||||
|         return length; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the minimum x-coordinate that the Figure occupies based on its current position and rotation. | ||||
|      * | ||||
|      * @return the minimum x-coordinate of the Figure | ||||
|      */ | ||||
|     public int getMinX() { | ||||
|         return x + min(0, (length - 1) * rot.dx()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the maximum x-coordinate that the Figure occupies based on its current position and rotation. | ||||
|      * | ||||
|      * @return the maximum x-coordinate of the Figure | ||||
|      */ | ||||
|     public int getMaxX() { | ||||
|         return x + max(0, (length - 1) * rot.dx()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the minimum y-coordinate that the Figure occupies based on its current position and rotation. | ||||
|      * | ||||
|      * @return the minimum y-coordinate of the Figure | ||||
|      */ | ||||
|     public int getMinY() { | ||||
|         return y + min(0, (length - 1) * rot.dy()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the maximum y-coordinate that the Figure occupies based on its current position and rotation. | ||||
|      * | ||||
|      * @return the maximum y-coordinate of the Figure | ||||
|      */ | ||||
|     public int getMaxY() { | ||||
|         return y + max(0, (length - 1) * rot.dy()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the current rotation of the Figure. | ||||
|      * | ||||
|      * @return the rotation of the Figure | ||||
|      */ | ||||
|     public Rotation getRot() { | ||||
|         return rot; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sets the rotation of the Figure. | ||||
|      * | ||||
|      * @param rot the new rotation to be set for the Figure | ||||
|      */ | ||||
|     public void setRotation(Rotation rot) { | ||||
|         this.rot = rot; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Rotates the Figure by 90 degrees clockwise. | ||||
|      */ | ||||
|     public void rotated() { | ||||
|         setRotation(rot.rotate()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Attempts to hit the Figure at the specified position. | ||||
|      * If the position is part of the Figure, the hit is recorded. | ||||
|      * | ||||
|      * @param x the x-coordinate of the position to hit | ||||
|      * @param y the y-coordinate of the position to hit | ||||
|      * @return true if the position is part of the Figure, false otherwise | ||||
|      * @see #contains(int, int) | ||||
|      */ | ||||
|     public boolean hit(int x, int y) { | ||||
|         if (!contains(x, y)) | ||||
|             return false; | ||||
|         damaged.add(new IntPoint(x, y)); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Attempts to hit the Figure at the specified position. | ||||
|      * If the position is part of the Figure, the hit is recorded. | ||||
|      * This is a convenience method for {@linkplain #hit(int, int)}. | ||||
|      * | ||||
|      * @param position the position to hit | ||||
|      * @return true if the position is part of the Figure, false otherwise | ||||
|      */ | ||||
|     public boolean hit(IntPosition position) { | ||||
|         return hit(position.getX(), position.getY()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the positions of this Figure that have been hit. | ||||
|      * | ||||
|      * @return a set of positions that have been hit | ||||
|      * @see #hit(int, int) | ||||
|      */ | ||||
|     public Set<IntPoint> getDamaged() { | ||||
|         return Collections.unmodifiableSet(damaged); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks whether the specified position is covered by the Figure. This method does | ||||
|      * not record a hit, only checks coverage. | ||||
|      * This is a convenience method for {@linkplain #contains(int, int)}. | ||||
|      * | ||||
|      * @param pos the position to check | ||||
|      * @return true if the position is covered by the Figure, false otherwise | ||||
|      */ | ||||
|     public boolean contains(IntPosition pos) { | ||||
|         return contains(pos.getX(), pos.getY()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks whether the specified position is covered by the Figure. This method does | ||||
|      * not record a hit, only checks coverage. | ||||
|      * | ||||
|      * @param x the x-coordinate of the position to check | ||||
|      * @param y the y-coordinate of the position to check | ||||
|      * @return true if the position is covered by the Figure, false otherwise | ||||
|      */ | ||||
|     public boolean contains(int x, int y) { | ||||
|         return getMinX() <= x && x <= getMaxX() && | ||||
|                getMinY() <= y && y <= getMaxY(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Determines if the Figure has been completely destroyed. A Figure is considered | ||||
|      * destroyed if all of its positions have been hit. | ||||
|      * | ||||
|      * @return true if the Figure is destroyed, false otherwise | ||||
|      * @see #hit(int, int) | ||||
|      */ | ||||
|     public boolean isDestroyed() { | ||||
|         return damaged.size() == length; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks whether this Figure collides with another Figure. Two Figures collide | ||||
|      * if any of their occupied positions overlap. | ||||
|      * | ||||
|      * @param other the other Figure to check collision with | ||||
|      * @return true if the Figures collide, false otherwise | ||||
|      */ | ||||
|     public boolean collidesWith(Figure other) { | ||||
|         return other.getMaxX() >= getMinX() && getMaxX() >= other.getMinX() && | ||||
|                other.getMaxY() >= getMinY() && getMaxY() >= other.getMinY(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns a string representation of the Figure, including its length, position, | ||||
|      * and rotation. | ||||
|      * | ||||
|      * @return a string representation of the Figure | ||||
|      */ | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return "Ship{length=" + length + ", x=" + x + ", y=" + y + ", rot=" + rot + '}';  //NON-NLS | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Accepts a visitor that returns a value of type {@code T}. This method is part of the | ||||
|      * Visitor design pattern. | ||||
|      * | ||||
|      * @param visitor the visitor to accept | ||||
|      * @param <T>     the type of the value returned by the visitor | ||||
|      * @return the value returned by the visitor | ||||
|      */ | ||||
|     @Override | ||||
|     public <T> T accept(Visitor<T> visitor) { | ||||
|         return visitor.visit(this); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Accepts a visitor that does not return a value. This method is part of the | ||||
|      * Visitor design pattern. | ||||
|      * | ||||
|      * @param visitor the visitor to accept | ||||
|      */ | ||||
|     @Override | ||||
|     public void accept(VoidVisitor visitor) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'accept'"); | ||||
|         visitor.visit(this); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -7,10 +7,10 @@ | ||||
|  | ||||
| package pp.monopoly.model; | ||||
|  | ||||
| import com.jme3.network.serializing.Serializable; | ||||
|  | ||||
| import java.util.Objects; | ||||
|  | ||||
| import com.jme3.network.serializing.Serializable; | ||||
|  | ||||
| /** | ||||
|  * Represents a point in the two-dimensional plane with integer coordinates. | ||||
|  */ | ||||
|   | ||||
| @@ -14,4 +14,12 @@ package pp.monopoly.model; | ||||
|  */ | ||||
| public interface Visitor<T> { | ||||
|  | ||||
|     /** | ||||
|      * Visits a Figure element. | ||||
|      * | ||||
|      * @param figure the figure element to visit | ||||
|      * @return the result of visiting the figure element | ||||
|      */ | ||||
|     T visit(Figure figure); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -12,5 +12,11 @@ package pp.monopoly.model; | ||||
|  * without returning any result. | ||||
|  */ | ||||
| public interface VoidVisitor { | ||||
|     /** | ||||
|      * Visits a Figure element. | ||||
|      * | ||||
|      * @param figure the Figure element to visit | ||||
|      */ | ||||
|     void visit(Figure figure); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -13,7 +13,7 @@ public class Card { | ||||
|         visitor.visit(this); | ||||
|     } | ||||
|  | ||||
|     String getDescription() { | ||||
|     public String getDescription() { | ||||
|         return description; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -25,46 +25,46 @@ public class BoardManager { | ||||
|     private static List<Field> createBoard() { | ||||
|         ArrayList<Field> fields = new ArrayList<>(); | ||||
|  | ||||
|         fields.addLast(new GoField()); | ||||
|         fields.addLast(new BuildingProperty("Gym", 1, 600, 20)); | ||||
|         fields.addLast(new EventField("Hausfeier", 2)); | ||||
|         fields.addLast(new BuildingProperty("Sportplatz", 3, 600, 40)); | ||||
|         fields.addLast(new FineField("Diszi", 4, 2000)); | ||||
|         fields.addLast(new GateField("Südtor", 5)); | ||||
|         fields.addLast(new BuildingProperty("Studium+", 6, 1000, 60)); | ||||
|         fields.addLast(new EventField("Üvas", 7)); | ||||
|         fields.addLast(new BuildingProperty("PhysikHörsaal", 8, 1000, 60)); | ||||
|         fields.addLast(new BuildingProperty("Audimax", 9, 1200, 80)); | ||||
|         fields.addLast(new GulagField()); | ||||
|         fields.addLast(new BuildingProperty("99er", 11, 1400, 100)); | ||||
|         fields.addLast(new FoodField("Brandl", 12)); | ||||
|         fields.addLast(new BuildingProperty("12er", 13, 1400, 100)); | ||||
|         fields.addLast(new BuildingProperty("23er", 14, 1600, 120)); | ||||
|         fields.addLast(new GateField("HauptWache", 15)); | ||||
|         fields.addLast(new BuildingProperty("Schwimmhalle", 16, 1800, 140)); | ||||
|         fields.addLast(new BuildingProperty("CISM-Bahn", 17, 1800, 140)); | ||||
|         fields.addLast(new EventField("Marine-Welcome-Party", 18)); | ||||
|         fields.addLast(new BuildingProperty("Kletterturm", 19, 2000, 160)); | ||||
|         fields.addLast(new TestStreckeField()); | ||||
|         fields.addLast(new BuildingProperty("StudFBer C", 21, 2200, 180)); | ||||
|         fields.addLast(new EventField("Üvas", 22)); | ||||
|         fields.addLast(new BuildingProperty("StudFBer B", 23, 2200, 180)); | ||||
|         fields.addLast(new BuildingProperty("StudFBer A", 24, 2400, 200)); | ||||
|         fields.addLast(new GateField("Nordtor", 25)); | ||||
|         fields.addLast(new BuildingProperty("Cascada", 26, 2600, 220)); | ||||
|         fields.addLast(new BuildingProperty("Fakultätsgebäude", 27, 2600, 220)); | ||||
|         fields.addLast(new FoodField("Truppenküche", 28)); | ||||
|         fields.addLast(new BuildingProperty("Prüfungsamt", 29, 2800, 240)); | ||||
|         fields.addLast(new WacheField()); | ||||
|         fields.addLast(new BuildingProperty("Feuerwehr", 31, 3000, 260)); | ||||
|         fields.addLast(new BuildingProperty("SanZ", 32, 300, 260)); | ||||
|         fields.addLast(new EventField("Maibock", 33)); | ||||
|         fields.addLast(new BuildingProperty("Rechenzentrum", 34, 3200, 280)); | ||||
|         fields.addLast(new GateField("Osttor", 35)); | ||||
|         fields.addLast(new EventField("Üvas", 36)); | ||||
|         fields.addLast(new BuildingProperty("2er", 37, 3500, 350)); | ||||
|         fields.addLast(new FineField("EZM", 38, 1000)); | ||||
|         fields.addLast(new BuildingProperty("20er", 39, 4000, 500)); | ||||
|         fields.add(new GoField()); | ||||
|         fields.add(new BuildingProperty("Gym", 1, 600, 20)); | ||||
|         fields.add(new EventField("Hausfeier", 2)); | ||||
|         fields.add(new BuildingProperty("Sportplatz", 3, 600, 40)); | ||||
|         fields.add(new FineField("Diszi", 4, 2000)); | ||||
|         fields.add(new GateField("Südtor", 5)); | ||||
|         fields.add(new BuildingProperty("Studium+", 6, 1000, 60)); | ||||
|         fields.add(new EventField("Üvas", 7)); | ||||
|         fields.add(new BuildingProperty("PhysikHörsaal", 8, 1000, 60)); | ||||
|         fields.add(new BuildingProperty("Audimax", 9, 1200, 80)); | ||||
|         fields.add(new GulagField()); | ||||
|         fields.add(new BuildingProperty("99er", 11, 1400, 100)); | ||||
|         fields.add(new FoodField("Brandl", 12)); | ||||
|         fields.add(new BuildingProperty("12er", 13, 1400, 100)); | ||||
|         fields.add(new BuildingProperty("23er", 14, 1600, 120)); | ||||
|         fields.add(new GateField("HauptWache", 15)); | ||||
|         fields.add(new BuildingProperty("Schwimmhalle", 16, 1800, 140)); | ||||
|         fields.add(new BuildingProperty("CISM-Bahn", 17, 1800, 140)); | ||||
|         fields.add(new EventField("Marine-Welcome-Party", 18)); | ||||
|         fields.add(new BuildingProperty("Kletterturm", 19, 2000, 160)); | ||||
|         fields.add(new TestStreckeField()); | ||||
|         fields.add(new BuildingProperty("StudFBer C", 21, 2200, 180)); | ||||
|         fields.add(new EventField("Üvas", 22)); | ||||
|         fields.add(new BuildingProperty("StudFBer B", 23, 2200, 180)); | ||||
|         fields.add(new BuildingProperty("StudFBer A", 24, 2400, 200)); | ||||
|         fields.add(new GateField("Nordtor", 25)); | ||||
|         fields.add(new BuildingProperty("Cascada", 26, 2600, 220)); | ||||
|         fields.add(new BuildingProperty("Fakultätsgebäude", 27, 2600, 220)); | ||||
|         fields.add(new FoodField("Truppenküche", 28)); | ||||
|         fields.add(new BuildingProperty("Prüfungsamt", 29, 2800, 240)); | ||||
|         fields.add(new WacheField()); | ||||
|         fields.add(new BuildingProperty("Feuerwehr", 31, 3000, 260)); | ||||
|         fields.add(new BuildingProperty("SanZ", 32, 300, 260)); | ||||
|         fields.add(new EventField("Maibock", 33)); | ||||
|         fields.add(new BuildingProperty("Rechenzentrum", 34, 3200, 280)); | ||||
|         fields.add(new GateField("Osttor", 35)); | ||||
|         fields.add(new EventField("Üvas", 36)); | ||||
|         fields.add(new BuildingProperty("2er", 37, 3500, 350)); | ||||
|         fields.add(new FineField("EZM", 38, 1000)); | ||||
|         fields.add(new BuildingProperty("20er", 39, 4000, 500)); | ||||
|  | ||||
|         return fields; | ||||
|     } | ||||
|   | ||||
| @@ -1,29 +1,41 @@ | ||||
| //////////////////////////////////////// | ||||
| // Programming project code | ||||
| // UniBw M, 2022, 2023, 2024 | ||||
| // www.unibw.de/inf2 | ||||
| // (c) Mark Minas (mark.minas@unibw.de) | ||||
| //////////////////////////////////////// | ||||
|  | ||||
| package pp.monopoly.notification; | ||||
|  | ||||
| import pp.monopoly.model.Item; | ||||
| import pp.monopoly.model.Board; | ||||
| import pp.monopoly.model.Item; | ||||
|  | ||||
| /** | ||||
|  * Event when an item is added to a map. | ||||
|  * | ||||
|  * @param item the added item | ||||
|  * @param map  the map that got the additional item | ||||
|  * Event that is triggered when an item is added to a board. | ||||
|  */ | ||||
| public record ItemAddedEvent(Item item, Board map) implements GameEvent { | ||||
| public class ItemAddedEvent { | ||||
|     private final Item item; | ||||
|     private final Board board; | ||||
|  | ||||
|     /** | ||||
|      * Notifies the game event listener of this event. | ||||
|      * Constructs a new ItemAddedEvent. | ||||
|      * | ||||
|      * @param listener the game event listener | ||||
|      * @param item  the item that was added | ||||
|      * @param board the board to which the item was added | ||||
|      */ | ||||
|     @Override | ||||
|     public void notifyListener(GameEventListener listener) { | ||||
|         listener.receivedEvent(this); | ||||
|     public ItemAddedEvent(Item item, Board board) { | ||||
|         this.item = item; | ||||
|         this.board = board; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the item that was added. | ||||
|      * | ||||
|      * @return the added item | ||||
|      */ | ||||
|     public Item getItem() { | ||||
|         return item; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the board to which the item was added. | ||||
|      * | ||||
|      * @return the board | ||||
|      */ | ||||
|     public Board getBoard() { | ||||
|         return board; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,22 +7,30 @@ | ||||
|  | ||||
| package pp.monopoly.notification; | ||||
|  | ||||
| import pp.monopoly.model.Item; | ||||
| import pp.monopoly.model.Board; | ||||
| import pp.monopoly.model.Item; | ||||
|  | ||||
| /** | ||||
|  * Event when an item gets removed. | ||||
|  * | ||||
|  * @param item the destroyed item | ||||
|  */ | ||||
| public record ItemRemovedEvent(Item item, Board map) implements GameEvent { | ||||
|     /** | ||||
|      * Notifies the game event listener of this event. | ||||
|      * | ||||
|      * @param listener the game event listener | ||||
|      */ | ||||
|     @Override | ||||
|     public void notifyListener(GameEventListener listener) { | ||||
|         listener.receivedEvent(this); | ||||
| public class ItemRemovedEvent { | ||||
|     private final Item item; | ||||
|     private final Board board; | ||||
|  | ||||
|     public ItemRemovedEvent(Item item, Board board) { | ||||
|         this.item = item; | ||||
|         this.board = board; | ||||
|     } | ||||
|  | ||||
|     public Item getItem() { | ||||
|         return item; | ||||
|     } | ||||
|  | ||||
|     public Board getBoard() { | ||||
|         return board; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,61 @@ | ||||
| package pp.monopoly.notification; | ||||
|  | ||||
| /** | ||||
|  * Enumeration representing different types of sounds used in the game. | ||||
|  * Enum representing various sound effects in the game. | ||||
|  */ | ||||
| public enum Sound { | ||||
|     /** | ||||
|      * UC-sound-01: Sound effect for passing the start/Los field. | ||||
|      */ | ||||
|     PASS_START, | ||||
|  | ||||
| } | ||||
|     /** | ||||
|      * UC-sound-02: Sound effect for drawing an event card. | ||||
|      */ | ||||
|     EVENT_CARD, | ||||
|  | ||||
|     /** | ||||
|      * UC-sound-03: Sound effect for entering the Gulag. | ||||
|      */ | ||||
|     GULAG, | ||||
|  | ||||
|     /** | ||||
|      * UC-sound-04: Sound effect for rolling the dice. | ||||
|      */ | ||||
|     DICE_ROLL, | ||||
|  | ||||
|     /** | ||||
|      * UC-sound-05: Sound effect for collecting money. | ||||
|      */ | ||||
|     MONEY_COLLECTED, | ||||
|  | ||||
|     /** | ||||
|      * UC-sound-06: Sound effect for losing money. | ||||
|      */ | ||||
|     MONEY_LOST, | ||||
|  | ||||
|     /** | ||||
|      * UC-sound-07: Sound effect for accepting a trade offer. | ||||
|      */ | ||||
|     TRADE_ACCEPTED, | ||||
|  | ||||
|     /** | ||||
|      * UC-sound-08: Sound effect for rejecting a trade offer. | ||||
|      */ | ||||
|     TRADE_REJECTED, | ||||
|  | ||||
|     /** | ||||
|      * UC-sound-09: Sound effect for winning the game. | ||||
|      */ | ||||
|     WINNER, | ||||
|  | ||||
|     /** | ||||
|      * UC-sound-10: Sound effect for losing the game. | ||||
|      */ | ||||
|     LOSER, | ||||
|  | ||||
|     /** | ||||
|      * UC-sound-11: Sound effect for button click. | ||||
|      */ | ||||
|     BUTTON; | ||||
| } | ||||
| @@ -40,3 +40,4 @@ dialog.question=Question | ||||
| port.must.be.integer=Port must be an integer number | ||||
| map.doesnt.fit=The map doesn't fit to this game | ||||
| client.server-start=Start server | ||||
| menu.settings=Open Settings | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| /* | ||||
| package pp.monopoly; | ||||
| import org.junit.Test; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| @@ -383,3 +384,4 @@ public class Testhandbuch { | ||||
|     } | ||||
|  | ||||
|  | ||||
| */ | ||||
| @@ -1,30 +1,106 @@ | ||||
| package pp.monopoly.client; | ||||
|  | ||||
| import com.jme3.input.KeyInput; | ||||
| import com.jme3.scene.Spatial; | ||||
| import com.simsilica.lemur.Button; | ||||
| import com.jme3.scene.Node; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
|  | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.mockito.Mockito.*; | ||||
|  | ||||
| public class ClientLogicTest { | ||||
|  | ||||
|     private MonopolyApp app; | ||||
|     private Node guiNodeMock; | ||||
|  | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         app = new MonopolyApp(); | ||||
|         // Erstelle eine Mock-Instanz der MonopolyApp | ||||
|         app = spy(new MonopolyApp()); | ||||
|  | ||||
|         // Mock GuiNode | ||||
|         guiNodeMock = mock(Node.class); | ||||
|         doReturn(guiNodeMock).when(app).getGuiNode(); | ||||
|  | ||||
|         // Initialisiere die App | ||||
|         doNothing().when(app).simpleInitApp(); | ||||
|         app.simpleInitApp(); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T001: UC-game-01 - Überprüft, ob die Anwendung erfolgreich gestartet wird und das Hauptmenü angezeigt wird | ||||
|     public void testStartApplication() { | ||||
|         // Mock des Hauptmenü-Kindes | ||||
|         Spatial mainMenuMock = mock(Spatial.class); | ||||
|         when(guiNodeMock.getChild("MainMenu")).thenReturn(mainMenuMock); | ||||
|  | ||||
|         // Test, ob das Hauptmenü angezeigt wird | ||||
|         Spatial mainMenu = app.getGuiNode().getChild("MainMenu"); | ||||
|         assertNotNull("Das Hauptmenü sollte sichtbar sein", mainMenu); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T002: UC-game-02 - Überprüft, ob das Startmenü nach dem Start der Anwendung angezeigt wird | ||||
|     public void testOpenStartMenu() { | ||||
|         // Mock des Startmenü-Kindes | ||||
|         Spatial startMenuMock = mock(Spatial.class); | ||||
|         when(guiNodeMock.getChild("StartMenu")).thenReturn(startMenuMock); | ||||
|  | ||||
|         // Test, ob das Startmenü angezeigt wird | ||||
|         Spatial startMenu = app.getGuiNode().getChild("StartMenu"); | ||||
|         assertNotNull("Das Startmenü sollte sichtbar sein", startMenu); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T003: UC-game-03 - Überprüft, ob der „Spiel starten“-Button das Spielerstellungsmenü öffnet | ||||
|     public void testNavigateToPlayOption() { | ||||
|         // Mock des Spielerstellungsmenü-Kindes | ||||
|         Spatial playMenuMock = mock(Spatial.class); | ||||
|         when(guiNodeMock.getChild("PlayMenu")).thenReturn(playMenuMock); | ||||
|  | ||||
|         // Test, ob das Spielerstellungsmenü angezeigt wird | ||||
|         Spatial playMenu = app.getGuiNode().getChild("PlayMenu"); | ||||
|         assertNotNull("Das Spielerstellungsmenü sollte sichtbar sein", playMenu); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T004: UC-game-04 - Testet, ob die Anwendung geschlossen wird, wenn „Beenden“ im Hauptmenü gewählt wird | ||||
|     public void testExitApplicationFromMenu() { | ||||
|         // Simuliere den Schließen-Aufruf | ||||
|         doNothing().when(app).closeApp(); | ||||
|  | ||||
|         // Rufe die Schließen-Methode auf | ||||
|         app.closeApp(); | ||||
|  | ||||
|         // Verifiziere, dass die Methode aufgerufen wurde | ||||
|         verify(app, times(1)).closeApp(); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T005: UC-game-05 - Überprüft, ob das Einstellungen-Menü aus dem Hauptmenü aufgerufen werden kann | ||||
|     public void testOpenSettingsFromMenu() { | ||||
|         // Mock des Einstellungsmenü-Kindes | ||||
|         Spatial settingsMenuMock = mock(Spatial.class); | ||||
|         when(guiNodeMock.getChild("SettingsMenu")).thenReturn(settingsMenuMock); | ||||
|  | ||||
|         // Test, ob das Einstellungsmenü angezeigt wird | ||||
|         Spatial settingsMenu = app.getGuiNode().getChild("SettingsMenu"); | ||||
|         assertNotNull("Das Einstellungsmenü sollte sichtbar sein", settingsMenu); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T006: UC-game-06 - Testet, ob das Spielmenü geöffnet wird, wenn der Spieler im Spiel „ESC“ drückt | ||||
|     public void testOpenGameMenuWithESC() { | ||||
|         // Simuliere den ESC-Tastendruck | ||||
|         doNothing().when(app).handleEscape(true); | ||||
|  | ||||
|         // Rufe die ESC-Tastenmethode auf | ||||
|         app.handleEscape(true); | ||||
|  | ||||
|         // Verifiziere, dass die Methode aufgerufen wurde | ||||
|         verify(app, times(1)).handleEscape(true); | ||||
|     } | ||||
| } | ||||
| /* | ||||
|     @Test | ||||
|     // T002: UC-game-02 - Überprüft, ob das Startmenü nach dem Start der Anwendung angezeigt wird | ||||
| @@ -33,6 +109,7 @@ public class ClientLogicTest { | ||||
|         assertNotNull("Das Startmenü sollte sichtbar sein", startMenu); | ||||
|     } | ||||
| */ | ||||
| /* | ||||
| @Test | ||||
| // T002: UC-game-02 - Überprüft, ob das Startmenü (MainMenu) angezeigt wird und die Buttons korrekt funktionieren | ||||
| public void testMainMenuButtons() { | ||||
| @@ -158,4 +235,5 @@ public void testMainMenuButtons() { | ||||
|             throw new AssertionError("'ReturnButton' ist kein Button-Objekt."); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
| */ | ||||
| @@ -1,606 +1,123 @@ | ||||
| package pp.monopoly.game.client; | ||||
|  | ||||
| import com.jme3.scene.Spatial; | ||||
| import com.simsilica.lemur.Button; | ||||
| import com.simsilica.lemur.TextField; | ||||
| /* | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import pp.monopoly.client.MonopolyApp; | ||||
| import pp.monopoly.game.server.Player; | ||||
| import pp.monopoly.message.server.DiceResult; | ||||
| import pp.monopoly.notification.ClientStateEvent; | ||||
| import org.mockito.Mock; | ||||
| import org.mockito.MockitoAnnotations; | ||||
| import pp.monopoly.server.MonopolyServer; | ||||
| import pp.monopoly.client.ClientSender; | ||||
|  | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.Mockito.*; | ||||
|  | ||||
| /** | ||||
|  * Tests the client-side logic of the Monopoly game. | ||||
|  */ | ||||
| /* | ||||
| public class ClientGameLogicTest { | ||||
|  | ||||
|     private MonopolyApp app; | ||||
|     private ClientGameLogic logic; | ||||
|  | ||||
|     private MonopolyApp app; | ||||
|     private NewGameMenu newGameMenu; | ||||
|     @Mock | ||||
|     private MonopolyServer serverMock; | ||||
|  | ||||
|     @Mock | ||||
|     private ClientSender clientSenderMock; | ||||
|  | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         app = new MonopolyApp(); | ||||
|         app.simpleInitApp(); // Initialisiert die App mit GUI und Spielzuständen | ||||
|         newGameMenu = new NewGameMenu(); | ||||
|         MockitoAnnotations.openMocks(this); | ||||
|         logic = new ClientGameLogic(clientSenderMock); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T006: UC-game-06 - Testet, ob das Spielmenü geöffnet wird, wenn der Spieler im Spiel „ESC“ drückt | ||||
|     public void testOpenGameMenuWithESC() { | ||||
|         app.escape(true); // Simuliert das Drücken der ESC-Taste | ||||
|  | ||||
|         Spatial gameMenu = app.getGuiNode().getChild("GameMenu"); | ||||
|         assertNotNull("Das Spielmenü sollte sichtbar sein, nachdem ESC gedrückt wurde", gameMenu); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T007: UC-game-07 - Testet, ob der Spieler erfolgreich den Namen des Hosts eingeben kann | ||||
|     public void testEnterHostName() { | ||||
|         Spatial hostNameField = app.getGuiNode().getChild("HostNameField"); | ||||
|         assertNotNull("Das Eingabefeld für den Hostnamen sollte sichtbar sein", hostNameField); | ||||
|         String hostName = "localhost"; | ||||
|         logic.setHostName(hostName); | ||||
|  | ||||
|         if (hostNameField instanceof TextField) { | ||||
|             TextField hostNameInput = (TextField) hostNameField; | ||||
|             hostNameInput.setText("TestHost"); | ||||
|  | ||||
|             assertEquals("Der eingegebene Hostname sollte 'TestHost' sein", "TestHost", hostNameInput.getText()); | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'HostNameField' ist kein TextField-Objekt."); | ||||
|         } | ||||
|         assertEquals("The hostname should be correctly set.", hostName, logic.getHostName()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T008: UC-game-08 - Testet, ob der Spieler die Portnummer des Hosts eingeben kann | ||||
|     public void testEnterPortNumber() { | ||||
|         Spatial portNumberField = app.getGuiNode().getChild("PortNumberField"); | ||||
|         assertNotNull("Das Eingabefeld für die Portnummer sollte sichtbar sein", portNumberField); | ||||
|         int portNumber = 12345; | ||||
|         logic.setPortNumber(portNumber); | ||||
|  | ||||
|         if (portNumberField instanceof TextField) { | ||||
|             TextField portNumberInput = (TextField) portNumberField; | ||||
|             portNumberInput.setText("12345"); | ||||
|  | ||||
|             assertEquals("Die eingegebene Portnummer sollte '12345' sein", "12345", portNumberInput.getText()); | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'PortNumberField' ist kein TextField-Objekt."); | ||||
|         } | ||||
|         assertEquals("The port number should be correctly set.", portNumber, logic.getPortNumber()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T009: UC-game-09 - Testet, ob der Spieler das Erstellen eines Spiels abbrechen kann | ||||
|     public void testCancelGameCreation() { | ||||
|         Spatial cancelButtonSpatial = app.getGuiNode().getChild("CancelButton"); | ||||
|         assertNotNull("Der 'Abbrechen'-Button sollte existieren", cancelButtonSpatial); | ||||
|         doNothing().when(clientSenderMock).returnToMainMenu(); | ||||
|  | ||||
|         if (cancelButtonSpatial instanceof Button) { | ||||
|             Button cancelButton = (Button) cancelButtonSpatial; | ||||
|             cancelButton.click(); | ||||
|         logic.cancelGameCreation(); | ||||
|  | ||||
|             Spatial mainMenu = app.getGuiNode().getChild("MainMenu"); | ||||
|             assertNotNull("Das Hauptmenü sollte nach dem Abbrechen des Spiels sichtbar sein", mainMenu); | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'CancelButton' ist kein Button-Objekt."); | ||||
|         } | ||||
|         verify(clientSenderMock, times(1)).returnToMainMenu(); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testEnterPlayerLobby() { | ||||
|         doNothing().when(clientSenderMock).enterLobby(); | ||||
|  | ||||
|         logic.enterLobby(); | ||||
|  | ||||
|         verify(clientSenderMock, times(1)).enterLobby(); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|         @Test | ||||
|         // T010: UC-game-10 - Testet, ob der Spieler die Spielerlobby betreten kann | ||||
|         public void testEnterPlayerLobby() { | ||||
|             app.receivedEvent(new pp.monopoly.notification.ClientStateEvent(pp.monopoly.notification.ClientStateEvent.State.LOBBY)); | ||||
|  | ||||
|             Spatial playerLobby = app.getGuiNode().getChild("PlayerLobby"); | ||||
|             assertNotNull("Die Spielerlobby sollte nach dem Beitritt sichtbar sein", playerLobby); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         @Test | ||||
|         // T010: UC-game-10 - Testet, ob der Spieler die Spielerlobby betreten kann | ||||
|         public void testEnterPlayerLobby() { | ||||
|             // Simuliert den Empfang eines ClientStateEvent für den Lobby-State | ||||
|             app.receivedEvent(new ClientStateEvent(ClientStateEvent.State.LOBBY)); | ||||
|  | ||||
|             // Überprüft, ob das Lobby-UI sichtbar ist | ||||
|             Spatial playerLobby = app.getGuiNode().getChild("PlayerLobby"); | ||||
|             assertNotNull("Die Spielerlobby sollte nach dem Beitritt sichtbar sein", playerLobby); | ||||
|     */ | ||||
|     @Test | ||||
|     // T011: UC-game-11 - Testet, ob der hostende Spieler einen Startbetrag eingeben kann | ||||
|     public void testEnterStartingCapital() { | ||||
|         Spatial startingCapitalField = app.getGuiNode().getChild("StartingCapitalField"); | ||||
|         assertNotNull("Das Eingabefeld für den Startbetrag sollte existieren", startingCapitalField); | ||||
|         int startingCapital = 1500; | ||||
|         logic.setStartingCapital(startingCapital); | ||||
|  | ||||
|         if (startingCapitalField instanceof TextField) { | ||||
|             TextField startingCapitalInput = (TextField) startingCapitalField; | ||||
|             startingCapitalInput.setText("1500"); | ||||
|  | ||||
|             assertEquals("Der eingegebene Startbetrag sollte '1500' sein", "1500", startingCapitalInput.getText()); | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'StartingCapitalField' ist kein TextField-Objekt."); | ||||
|         } | ||||
|         assertEquals("The starting capital should be correctly set.", startingCapital, logic.getStartingCapital()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T012: UC-game-12 - Testet, ob der Spieler den Startbetrag um 100 € erhöhen kann | ||||
|     public void testIncreaseStartingCapital() { | ||||
|         Spatial increaseButton = app.getGuiNode().getChild("IncreaseCapitalButton"); | ||||
|         assertNotNull("Der 'Erhöhen'-Button sollte existieren", increaseButton); | ||||
|         logic.setStartingCapital(1500); | ||||
|         logic.increaseStartingCapital(); | ||||
|  | ||||
|         if (increaseButton instanceof Button) { | ||||
|             Button increaseCapitalButton = (Button) increaseButton; | ||||
|             increaseCapitalButton.click(); | ||||
|  | ||||
|             Spatial startingCapitalField = app.getGuiNode().getChild("StartingCapitalField"); | ||||
|             if (startingCapitalField instanceof TextField) { | ||||
|                 TextField startingCapitalInput = (TextField) startingCapitalField; | ||||
|                 assertEquals("Der Startbetrag sollte um 100 erhöht worden sein", "1600", startingCapitalInput.getText()); | ||||
|             } | ||||
|             else { | ||||
|                 throw new AssertionError("'StartingCapitalField' ist kein TextField-Objekt."); | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'IncreaseCapitalButton' ist kein Button-Objekt."); | ||||
|         } | ||||
|         assertEquals("The starting capital should increase by 100.", 1600, logic.getStartingCapital()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T013: UC-game-13 - Testet, ob der Spieler den Startbetrag um 100 € senken kann | ||||
|     public void testDecreaseStartingCapital() { | ||||
|         Spatial decreaseButton = app.getGuiNode().getChild("DecreaseCapitalButton"); | ||||
|         assertNotNull("Der 'Senken'-Button sollte existieren", decreaseButton); | ||||
|         logic.setStartingCapital(1500); | ||||
|         logic.decreaseStartingCapital(); | ||||
|  | ||||
|         if (decreaseButton instanceof Button) { | ||||
|             Button decreaseCapitalButton = (Button) decreaseButton; | ||||
|             decreaseCapitalButton.click(); | ||||
|  | ||||
|             Spatial startingCapitalField = app.getGuiNode().getChild("StartingCapitalField"); | ||||
|             if (startingCapitalField instanceof TextField) { | ||||
|                 TextField startingCapitalInput = (TextField) startingCapitalField; | ||||
|                 assertEquals("Der Startbetrag sollte um 100 gesenkt worden sein", "1400", startingCapitalInput.getText()); | ||||
|             } | ||||
|             else { | ||||
|                 throw new AssertionError("'StartingCapitalField' ist kein TextField-Objekt."); | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'DecreaseCapitalButton' ist kein Button-Objekt."); | ||||
|         } | ||||
|         assertEquals("The starting capital should decrease by 100.", 1400, logic.getStartingCapital()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T014: UC-game-14 - Testet, ob die Standard-Spielernamen korrekt voreingestellt sind | ||||
|     public void testDefaultPlayerName() { | ||||
|         Spatial playerNameField = app.getGuiNode().getChild("PlayerNameField"); | ||||
|         assertNotNull("Das Eingabefeld für den Spielernamen sollte existieren", playerNameField); | ||||
|         logic.addPlayer("Player 1"); | ||||
|  | ||||
|         if (playerNameField instanceof TextField) { | ||||
|             TextField playerNameInput = (TextField) playerNameField; | ||||
|             assertEquals("Der voreingestellte Spielername sollte 'Spieler 1' sein", "Spieler 1", playerNameInput.getText()); | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'PlayerNameField' ist kein TextField-Objekt."); | ||||
|         } | ||||
|         assertTrue("The player name should be correctly set.", logic.getPlayerNames().contains("Player 1")); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T015: UC-game-15 - Testet, ob der Spieler einen Anzeigenamen eingeben kann | ||||
|     public void testEnterDisplayName() { | ||||
|         Spatial displayNameField = app.getGuiNode().getChild("DisplayNameField"); | ||||
|         assertNotNull("Das Eingabefeld für den Anzeigenamen sollte existieren", displayNameField); | ||||
|         String displayName = "JohnDoe"; | ||||
|         logic.setPlayerName(displayName); | ||||
|  | ||||
|         if (displayNameField instanceof TextField) { | ||||
|             TextField displayNameInput = (TextField) displayNameField; | ||||
|             displayNameInput.setText("MaxMustermann"); | ||||
|  | ||||
|             assertEquals("Der eingegebene Anzeigename sollte 'MaxMustermann' sein", "MaxMustermann", displayNameInput.getText()); | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'DisplayNameField' ist kein TextField-Objekt."); | ||||
|         } | ||||
|         assertEquals("The display name should be correctly set.", displayName, logic.getPlayerName()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T016: UC-game-16 - Testet, ob eine Warnung angezeigt wird, wenn ein Spieler einen bereits belegten Namen eingibt | ||||
|     public void testDuplicateNameEntry() { | ||||
|         Spatial playerNameField = app.getGuiNode().getChild("PlayerNameField"); | ||||
|         assertNotNull("Das Eingabefeld für den Spielernamen sollte existieren", playerNameField); | ||||
|         logic.addPlayer("Player 1"); | ||||
|         boolean result = logic.addPlayer("Player 1"); | ||||
|  | ||||
|         if (playerNameField instanceof TextField) { | ||||
|             TextField playerNameInput = (TextField) playerNameField; | ||||
|             playerNameInput.setText("Spieler 1"); | ||||
|             app.getGuiNode().getChild("AddPlayerButton").click(); // Spieler hinzufügen | ||||
|             playerNameInput.setText("Spieler 1"); | ||||
|             app.getGuiNode().getChild("AddPlayerButton").click(); // Spieler mit gleichem Namen hinzufügen | ||||
|  | ||||
|             Spatial warning = app.getGuiNode().getChild("DuplicateNameWarning"); | ||||
|             assertNotNull("Es sollte eine Warnung angezeigt werden, wenn ein Name doppelt vergeben wird", warning); | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'PlayerNameField' ist kein TextField-Objekt."); | ||||
|         } | ||||
|         assertTrue("Duplicate names should not be allowed.", !result); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T017: UC-game-17 - Testet, ob der Spieler eine verfügbare Spielfigurfarbe auswählen kann | ||||
|     public void testSelectPlayerColor() { | ||||
|         Spatial colorSelector = app.getGuiNode().getChild("ColorSelector"); | ||||
|         assertNotNull("Der Farbwähler sollte existieren", colorSelector); | ||||
|         logic.addPlayer("Player 1"); | ||||
|         boolean result = logic.setPlayerColor("Player 1", "Red"); | ||||
|  | ||||
|         if (colorSelector instanceof Button) { | ||||
|             Button colorButton = (Button) colorSelector; | ||||
|             colorButton.click(); | ||||
|  | ||||
|             Spatial selectedColor = app.getGuiNode().getChild("SelectedColor"); | ||||
|             assertNotNull("Die gewählte Farbe sollte sichtbar sein", selectedColor); | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'ColorSelector' ist kein Button-Objekt."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T018: UC-game-18 - Testet, ob eine belegte Spielfigurfarbe nicht ausgewählt werden kann | ||||
|     public void testSelectOccupiedColor() { | ||||
|         app.getGuiNode().getChild("ColorSelectorRed").click(); // Spieler 1 wählt Rot | ||||
|         app.getGuiNode().getChild("AddPlayerButton").click(); // Spieler 1 hinzufügen | ||||
|  | ||||
|         app.getGuiNode().getChild("ColorSelectorRed").click(); // Spieler 2 versucht Rot zu wählen | ||||
|         Spatial warning = app.getGuiNode().getChild("ColorOccupiedWarning"); | ||||
|         assertNotNull("Es sollte eine Warnung angezeigt werden, wenn eine belegte Farbe ausgewählt wird", warning); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T019: UC-game-19 - Testet, ob der Spieler eine Spielfigur auswählen kann | ||||
|     public void testSelectPlayerToken() { | ||||
|         Spatial tokenSelector = app.getGuiNode().getChild("TokenSelector"); | ||||
|         assertNotNull("Der Token-Wähler sollte existieren", tokenSelector); | ||||
|  | ||||
|         if (tokenSelector instanceof Button) { | ||||
|             Button tokenButton = (Button) tokenSelector; | ||||
|             tokenButton.click(); | ||||
|  | ||||
|             Spatial selectedToken = app.getGuiNode().getChild("SelectedToken"); | ||||
|             assertNotNull("Die gewählte Spielfigur sollte sichtbar sein", selectedToken); | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'TokenSelector' ist kein Button-Objekt."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T020: UC-game-20 - Testet, ob eine belegte Spielfigur nicht ausgewählt werden kann | ||||
|     public void testSelectOccupiedToken() { | ||||
|         app.getGuiNode().getChild("TokenSelectorShip").click(); | ||||
|         app.getGuiNode().getChild("AddPlayerButton").click(); | ||||
|  | ||||
|         app.getGuiNode().getChild("TokenSelectorShip").click(); | ||||
|         Spatial warning = app.getGuiNode().getChild("TokenOccupiedWarning"); | ||||
|         assertNotNull("Es sollte eine Warnung angezeigt werden, wenn eine belegte Spielfigur ausgewählt wird", warning); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T021: UC-game-14 - Überprüft, ob der Spieler zur „Spiel erstellen“-Ansicht zurückkehrt, wenn Abbrechen gedrückt wird | ||||
|     public void testCancelPlayerLobby() { | ||||
|         Spatial cancelButtonSpatial = app.getGuiNode().getChild("CancelLobbyButton"); | ||||
|         assertNotNull("Der 'Abbrechen'-Button in der Lobby sollte existieren", cancelButtonSpatial); | ||||
|  | ||||
|         if (cancelButtonSpatial instanceof Button) { | ||||
|             Button cancelButton = (Button) cancelButtonSpatial; | ||||
|             cancelButton.click(); | ||||
|  | ||||
|             Spatial mainMenu = app.getGuiNode().getChild("MainMenu"); | ||||
|             assertNotNull("Das Hauptmenü sollte nach dem Abbrechen der Lobby sichtbar sein", mainMenu); | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'CancelLobbyButton' ist kein Button-Objekt."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T022: UC-game-15 - Überprüft, ob das Spielmenü in der Lobby durch Drücken der ESC-Taste geöffnet wird | ||||
|     public void testOpenLobbyMenuWithESC() { | ||||
|         app.escape(true); // Simuliert das Drücken der ESC-Taste | ||||
|  | ||||
|         Spatial lobbyMenu = app.getGuiNode().getChild("LobbyMenu"); | ||||
|         assertNotNull("Das Lobby-Menü sollte sichtbar sein, nachdem ESC in der Lobby gedrückt wurde", lobbyMenu); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T023: UC-game-16 - Testet, ob der Spieler die Auswahl der Spielfigur bestätigen kann | ||||
|     public void testPlayerReadyConfirmation() { | ||||
|         Spatial confirmButtonSpatial = app.getGuiNode().getChild("ConfirmTokenButton"); | ||||
|         assertNotNull("Der 'Bestätigen'-Button für die Spielfigur sollte existieren", confirmButtonSpatial); | ||||
|  | ||||
|         if (confirmButtonSpatial instanceof Button) { | ||||
|             Button confirmButton = (Button) confirmButtonSpatial; | ||||
|             confirmButton.click(); | ||||
|  | ||||
|             Spatial readyStatus = app.getGuiNode().getChild("PlayerReadyStatus"); | ||||
|             assertNotNull("Der Status 'Bereit' sollte angezeigt werden, nachdem die Spielfigur bestätigt wurde", readyStatus); | ||||
|         } | ||||
|         else { | ||||
|             throw new AssertionError("'ConfirmTokenButton' ist kein Button-Objekt."); | ||||
|         } | ||||
|     } | ||||
|         @Test | ||||
|         // T024: UC-game-16 - Überprüft, ob das Spiel startet, wenn alle Spieler ihre Auswahl bestätigt haben | ||||
|         public void testAllPlayersReady() { | ||||
|             app.receivedEvent(new pp.monopoly.notification.ClientStateEvent(pp.monopoly.notification.ClientStateEvent.State.ALL_PLAYERS_READY)); | ||||
|  | ||||
|             Spatial gameScreen = app.getGuiNode().getChild("GameScreen"); | ||||
|             assertNotNull("Das Spiel sollte starten, wenn alle Spieler bereit sind", gameScreen); | ||||
|         } | ||||
|         @Test | ||||
|         // T025: UC-game-17 - Testet, ob das Einstellungen-Menü während des Spiels geöffnet wird | ||||
|         public void testOpenMainGameSettings () { | ||||
|             app.escape(true); | ||||
|  | ||||
|             Spatial settingsButton = app.getGuiNode().getChild("SettingsButton"); | ||||
|             assertNotNull("Der 'Einstellungen'-Button sollte im Spielmenü vorhanden sein", settingsButton); | ||||
|  | ||||
|             if (settingsButton instanceof Button) { | ||||
|                 ((Button) settingsButton).click(); | ||||
|                 Spatial settingsMenu = app.getGuiNode().getChild("SettingsMenu"); | ||||
|                 assertNotNull("Das Einstellungen-Menü sollte im Spiel angezeigt werden", settingsMenu); | ||||
|             } | ||||
|             else { | ||||
|                 throw new AssertionError("'SettingsButton' ist kein Button-Objekt."); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     @Test | ||||
| // T026: UC-gameplay-01 - Überprüft, ob der Spieler erfolgreich würfeln kann | ||||
|     public void testRollDice() { | ||||
|         Spatial rollDiceButton = app.getGuiNode().getChild("RollDiceButton"); | ||||
|         assertNotNull("Der 'Würfeln'-Button sollte sichtbar sein", rollDiceButton); | ||||
|  | ||||
|         if (rollDiceButton instanceof Button) { | ||||
|             ((Button) rollDiceButton).click(); // Simuliert einen Würfelwurf | ||||
|             Spatial diceResult = app.getGuiNode().getChild("DiceResult"); | ||||
|             assertNotNull("Das Ergebnis des Würfelwurfs sollte angezeigt werden", diceResult); | ||||
|         } | ||||
|     } | ||||
|     @Test | ||||
| // T027: UC-gameplay-01 - Überprüft, ob der aktive Spieler die richtige Anzahl an Feldern basierend auf dem Wurf bewegt | ||||
|     public void testMovePlayerAutomatically() { | ||||
|         Game game = new Game(); // Initialisiert ein neues Spiel | ||||
|         PlayerHandler playerHandler = game.getPlayerHandler(); // Holt den PlayerHandler, der die Spieler verwaltet | ||||
|  | ||||
|         Player activePlayer = playerHandler.getActivePlayer(); // Holt den aktuellen aktiven Spieler | ||||
|         assertNotNull("Es sollte einen aktiven Spieler geben", activePlayer); | ||||
|  | ||||
|         DiceResult diceResult = game.rollDice(); // Würfelwurf simulieren | ||||
|         int steps = diceResult.getTotal(); | ||||
|  | ||||
|         int initialPosition = activePlayer.getFieldId(); // Ursprüngliche Position des aktiven Spielers | ||||
|         playerHandler.moveActivePlayer(steps); // Bewegt den aktiven Spieler basierend auf dem Wurf | ||||
|  | ||||
|         int expectedPosition = (initialPosition + steps) % game.getGameBoard().getNumberOfFields(); // Zielposition berechnen | ||||
|         int newPosition = activePlayer.getFieldId(); | ||||
|  | ||||
|         assertEquals("Der aktive Spieler sollte sich korrekt bewegen", expectedPosition, newPosition); | ||||
|  | ||||
|         // Überprüfen, dass alle anderen Spieler im WaitForTurn-State bleiben | ||||
|         playerHandler.getPlayers().stream() | ||||
|                      .filter(player -> player != activePlayer) | ||||
|                      .forEach(player -> assertTrue("Andere Spieler sollten im WaitForTurn-State sein", player.getState() instanceof WaitForTurnState)); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
| // T028: UC-gameplay-02 - Überprüft, ob die Würfel zufällige Zahlen generieren | ||||
|     public void testGenerationDice() { | ||||
|         boolean isRandom = false; | ||||
|         DiceResult previousResult = null; | ||||
|  | ||||
|         for (int i = 0; i < 10; i++) { | ||||
|             DiceResult currentResult = app.getGame().rollDice(); | ||||
|  | ||||
|             assertNotNull("Das Würfelergebnis sollte nicht null sein", currentResult); | ||||
|             assertTrue("Die Würfelzahl 1 sollte zwischen 1 und 6 liegen", currentResult.getDice1() >= 1 && currentResult.getDice1() <= 6); | ||||
|             assertTrue("Die Würfelzahl 2 sollte zwischen 1 und 6 liegen", currentResult.getDice2() >= 1 && currentResult.getDice2() <= 6); | ||||
|  | ||||
|             if (previousResult != null && (currentResult.getDice1() != previousResult.getDice1() || currentResult.getDice2() != previousResult.getDice2())) { | ||||
|                 isRandom = true; // Unterschiedliche Würfelwerte gefunden | ||||
|                 break; | ||||
|             } | ||||
|             previousResult = currentResult; | ||||
|         } | ||||
|         assertTrue("Die Würfelergebnisse sollten zufällig sein", isRandom); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
| // T029: UC-gameplay-03 - Überprüft, ob die richtigen Augenzahlen angezeigt werden | ||||
|     public void testDisplayResults() { | ||||
|         DiceResult result = app.getGame().rollDice(); | ||||
|  | ||||
|         assertNotNull("Das Würfelergebnis sollte nicht null sein", result); | ||||
|         assertTrue("Die Würfelzahl 1 sollte zwischen 1 und 6 liegen", result.getDice1() >= 1 && result.getDice1() <= 6); | ||||
|         assertTrue("Die Würfelzahl 2 sollte zwischen 1 und 6 liegen", result.getDice2() >= 1 && result.getDice2() <= 6); | ||||
|         assertEquals("Die Summe sollte korrekt berechnet werden", result.getDice1() + result.getDice2(), result.getTotal()); | ||||
|     } | ||||
|     @Test | ||||
|     // T030: UC-gameplay-04 - Überprüfen, ob die Summe der Würfelergebnisse korrekt berechnet wird | ||||
|     public void testSumDiceResults() { | ||||
|         Spatial rollDiceButton = app.getGuiNode().getChild("RollDiceButton"); | ||||
|         assertNotNull("Der 'Würfeln'-Button sollte sichtbar sein", rollDiceButton); | ||||
|  | ||||
|         if (rollDiceButton instanceof Button) { | ||||
|             ((Button) rollDiceButton).click(); // Simuliert einen Würfelwurf | ||||
|             Spatial diceResult1 = app.getGuiNode().getChild("DiceResult1"); | ||||
|             Spatial diceResult2 = app.getGuiNode().getChild("DiceResult2"); | ||||
|  | ||||
|             assertNotNull("Die Ergebnisse des Würfelwurfs sollten angezeigt werden", diceResult1); | ||||
|             assertNotNull("Die Ergebnisse des Würfelwurfs sollten angezeigt werden", diceResult2); | ||||
|  | ||||
|             int result1 = Integer.parseInt(diceResult1.getUserData("value").toString()); | ||||
|             int result2 = Integer.parseInt(diceResult2.getUserData("value").toString()); | ||||
|             int expectedSum = result1 + result2; | ||||
|  | ||||
|             Spatial sumDisplay = app.getGuiNode().getChild("DiceSum"); | ||||
|             assertEquals("Die Summe der Würfelergebnisse sollte korrekt angezeigt werden", expectedSum, Integer.parseInt(sumDisplay.getUserData("value").toString())); | ||||
|         } else { | ||||
|             throw new AssertionError("'RollDiceButton' ist kein Button-Objekt."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T031: UC-gameplay-05 - Überprüfen, ob die Würfel nach dem Wurf ausgegraut werden | ||||
|     public void testGrayOutDiceAfterRoll() { | ||||
|         Spatial rollDiceButton = app.getGuiNode().getChild("RollDiceButton"); | ||||
|         assertNotNull("Der 'Würfeln'-Button sollte sichtbar sein", rollDiceButton); | ||||
|  | ||||
|         if (rollDiceButton instanceof Button) { | ||||
|             ((Button) rollDiceButton).click(); // Simuliert einen Würfelwurf | ||||
|  | ||||
|             Spatial diceDisplay = app.getGuiNode().getChild("DiceDisplay"); | ||||
|             assertNotNull("Die Würfelanzeige sollte nach dem Wurf sichtbar sein", diceDisplay); | ||||
|  | ||||
|             boolean isGrayedOut = diceDisplay.getUserData("grayedOut"); | ||||
|             assertTrue("Die Würfel sollten nach dem Wurf ausgegraut sein", isGrayedOut); | ||||
|         } else { | ||||
|             throw new AssertionError("'RollDiceButton' ist kein Button-Objekt."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T032: UC-gameplay-06 - Überprüfen, ob das Würfeln eines Paschs erkannt wird | ||||
|     public void testDetectDouble() { | ||||
|         Spatial rollDiceButton = app.getGuiNode().getChild("RollDiceButton"); | ||||
|         assertNotNull("Der 'Würfeln'-Button sollte sichtbar sein", rollDiceButton); | ||||
|  | ||||
|         if (rollDiceButton instanceof Button) { | ||||
|             // Simuliert mehrere Würfe, um einen Pasch zu erkennen | ||||
|             for (int i = 0; i < 10; i++) { | ||||
|                 ((Button) rollDiceButton).click(); | ||||
|                 Spatial diceResult1 = app.getGuiNode().getChild("DiceResult1"); | ||||
|                 Spatial diceResult2 = app.getGuiNode().getChild("DiceResult2"); | ||||
|  | ||||
|                 int result1 = Integer.parseInt(diceResult1.getUserData("value").toString()); | ||||
|                 int result2 = Integer.parseInt(diceResult2.getUserData("value").toString()); | ||||
|  | ||||
|                 if (result1 == result2) { | ||||
|                     Spatial doubleIndicator = app.getGuiNode().getChild("DoubleIndicator"); | ||||
|                     assertNotNull("Ein Pasch sollte angezeigt werden, wenn zwei identische Zahlen geworfen werden", doubleIndicator); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             throw new AssertionError("'RollDiceButton' ist kein Button-Objekt."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T033: UC-gameplay-06 - Überprüfen, ob der Spieler bei einem Pasch erneut würfeln darf | ||||
|     public void testDoubleRoll() { | ||||
|         Spatial rollDiceButton = app.getGuiNode().getChild("RollDiceButton"); | ||||
|         assertNotNull("Der 'Würfeln'-Button sollte sichtbar sein", rollDiceButton); | ||||
|  | ||||
|         if (rollDiceButton instanceof Button) { | ||||
|             // Simuliert das Würfeln eines Paschs | ||||
|             ((Button) rollDiceButton).click(); | ||||
|             Spatial diceResult1 = app.getGuiNode().getChild("DiceResult1"); | ||||
|             Spatial diceResult2 = app.getGuiNode().getChild("DiceResult2"); | ||||
|  | ||||
|             int result1 = Integer.parseInt(diceResult1.getUserData("value").toString()); | ||||
|             int result2 = Integer.parseInt(diceResult2.getUserData("value").toString()); | ||||
|  | ||||
|             if (result1 == result2) { // Überprüft, ob ein Pasch geworfen wurde | ||||
|                 Spatial rollAgainIndicator = app.getGuiNode().getChild("RollAgainIndicator"); | ||||
|                 assertNotNull("Der Spieler sollte bei einem Pasch erneut würfeln dürfen", rollAgainIndicator); | ||||
|             } | ||||
|         } else { | ||||
|             throw new AssertionError("'RollDiceButton' ist kein Button-Objekt."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     // T034: UC-gameplay-06 - Überprüfen, ob der Spieler nach dem dritten Pasch ins Gefängnis muss | ||||
|     public void testTripleDoubleGulag() { | ||||
|         int doubleCount = 0; | ||||
|  | ||||
|         for (int i = 0; i < 3; i++) { | ||||
|             Spatial rollDiceButton = app.getGuiNode().getChild("RollDiceButton"); | ||||
|             assertNotNull("Der 'Würfeln'-Button sollte sichtbar sein", rollDiceButton); | ||||
|  | ||||
|             if (rollDiceButton instanceof Button) { | ||||
|                 ((Button) rollDiceButton).click(); | ||||
|                 Spatial diceResult1 = app.getGuiNode().getChild("DiceResult1"); | ||||
|                 Spatial diceResult2 = app.getGuiNode().getChild("DiceResult2"); | ||||
|  | ||||
|                 int result1 = Integer.parseInt(diceResult1.getUserData("value").toString()); | ||||
|                 int result2 = Integer.parseInt(diceResult2.getUserData("value").toString()); | ||||
|  | ||||
|                 if (result1 == result2) { | ||||
|                     doubleCount++; | ||||
|                 } | ||||
|  | ||||
|                 if (doubleCount == 3) { | ||||
|                     Spatial jailIndicator = app.getGuiNode().getChild("JailIndicator"); | ||||
|                     assertNotNull("Der Spieler sollte nach dem dritten Pasch ins Gefängnis gehen", jailIndicator); | ||||
|                     break; | ||||
|                 } | ||||
|             } else { | ||||
|                 throw new AssertionError("'RollDiceButton' ist kein Button-Objekt."); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         assertTrue("Der Spieler sollte drei Paschs geworfen haben", doubleCount == 3); | ||||
|     } | ||||
|     @Test | ||||
| // T035: UC-gameplay-07 - Überprüfen, ob ein Spieler für Steuerfelder korrekt belastet wird | ||||
|     public void testTaxFieldCharges() { | ||||
|         Game game = new Game(); | ||||
|         PlayerHandler playerHandler = game.getPlayerHandler(); | ||||
|         Player activePlayer = playerHandler.getActivePlayer(); | ||||
|         assertNotNull("Es sollte einen aktiven Spieler geben", activePlayer); | ||||
|  | ||||
|         int initialBalance = activePlayer.getAccountBalance(); | ||||
|  | ||||
|         activePlayer.moveToField(game.getGameBoard().getFieldById(4)); // ID 4: Steuerfeld "Diszi" | ||||
|         game.getGameBoard().applyFieldEffect(activePlayer); | ||||
|  | ||||
|         int expectedBalance = initialBalance - 200; // Beispielsteuer: 200 € | ||||
|         assertEquals("Der Spieler sollte für das Steuerfeld korrekt belastet werden", expectedBalance, activePlayer.getAccountBalance()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
| // T036: UC-gameplay-08 - Überprüfen, ob ein Spieler korrekt ins Gefängnis geschickt wird | ||||
|     public void testGoToJailField() { | ||||
|         Game game = new Game(); | ||||
|         PlayerHandler playerHandler = game.getPlayerHandler(); | ||||
|         Player activePlayer = playerHandler.getActivePlayer(); | ||||
|         assertNotNull("Es sollte einen aktiven Spieler geben", activePlayer); | ||||
|  | ||||
|         activePlayer.moveToField(game.getGameBoard().getFieldById(30)); // ID 30: Ab ins Gefängnis | ||||
|         game.getGameBoard().applyFieldEffect(activePlayer); | ||||
|  | ||||
|         assertEquals("Der Spieler sollte ins Gefängnis geschickt werden", 10, activePlayer.getFieldId()); // ID 10: Gefängnis | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
| // T037: UC-gameplay-09 - Überprüfen, ob ein Spieler bei "Frei Parken" keine Gebühren zahlt | ||||
|     public void testFreeParking() { | ||||
|         Game game = new Game(); | ||||
|         PlayerHandler playerHandler = game.getPlayerHandler(); | ||||
|         Player activePlayer = playerHandler.getActivePlayer(); | ||||
|         assertNotNull("Es sollte einen aktiven Spieler geben", activePlayer); | ||||
|  | ||||
|         int initialBalance = activePlayer.getAccountBalance(); | ||||
|  | ||||
|         activePlayer.moveToField(game.getGameBoard().getFieldById(20)); // ID 20: Frei Parken | ||||
|         game.getGameBoard().applyFieldEffect(activePlayer); | ||||
|  | ||||
|         assertEquals("Der Spieler sollte bei 'Frei Parken' keine Gebühren zahlen", initialBalance, activePlayer.getAccountBalance()); | ||||
|         assertTrue("The player should be able to select an available color.", result); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| */ | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -65,7 +65,7 @@ public class MonopolyServer implements MessageListener<HostedConnection>, Connec | ||||
|     /** | ||||
|      * Creates the server. | ||||
|      */ | ||||
|     MonopolyServer() { | ||||
|     public MonopolyServer() { | ||||
|         config.readFromIfExists(CONFIG_FILE); | ||||
|         LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS | ||||
|         logic = new ServerGameLogic(this, config); | ||||
| @@ -120,7 +120,7 @@ public class MonopolyServer implements MessageListener<HostedConnection>, Connec | ||||
|     @Override | ||||
|     public void connectionAdded(Server server, HostedConnection hostedConnection) { | ||||
|         LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS | ||||
|         logic.addPlayer(new Player(hostedConnection.getId())); | ||||
|         logic.addPlayer(hostedConnection.getId()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
		Reference in New Issue
	
	Block a user