39 Commits

Author SHA1 Message Date
Filip Szepielewicz
f1d52c445b Tests bis T075 überarbeitet 2024-11-22 21:45:22 +01:00
Filip Szepielewicz
30e4298f88 Merge remote-tracking branch 'origin/Testhandbuch' into Testhandbuch 2024-11-17 20:38:30 +01:00
Filip Szepielewicz
7a9e84f49c Tests bis T071 erweitert 2024-11-17 20:06:47 +01:00
Filip Szepielewicz
7fce07ac19 Tests bis T070 erweitert 2024-11-17 20:00:05 +01:00
Johannes Schmelz
2496ad812a Merge branch 'main' into 'Testhandbuch'
# Conflicts:
#   Projekte/monopoly/model/src/test/java/pp/monopoly/Testhandbuch.java
2024-11-17 18:27:54 +00:00
Johannes Schmelz
2f6d6037de Merge branch 'logic' into 'main'
Serverseitige Logik

See merge request progproj/gruppen-ht24/Gruppe-02!6
2024-11-17 18:25:32 +00:00
Johannes Schmelz
1d69bcc814 refactor and bug fixes 2024-11-17 17:43:05 +01:00
Johannes Schmelz
62c363068b fixed bug with client server messages 2024-11-16 18:57:52 +01:00
Johannes Schmelz
d2e0b8187b implemented PlayerStates 2024-11-16 18:42:05 +01:00
Johannes Schmelz
628b98af9b recive logic for messages 2024-11-16 18:03:19 +01:00
Johannes Schmelz
627e3dbd7f added fine to FineField 2024-11-16 17:53:07 +01:00
Filip Szepielewicz
f4fb04d17e T001 - T037 überarbeitet 2024-11-15 19:46:26 +01:00
Filip Szepielewicz
1b4fee2853 T001 - T034 erster versuch 2024-11-15 08:42:56 +01:00
Filip Szepielewicz
b65f458302 T001 - T034 erster versuch 2024-11-15 08:42:11 +01:00
Johannes Schmelz
10b978debf fixed addPlayer 2024-11-15 04:27:32 +01:00
Johannes Schmelz
3bdfd6a78a TradeHandler logic 2024-11-15 03:19:48 +01:00
Johannes Schmelz
e59ab4a320 cleintmessages logic 2024-11-15 03:06:07 +01:00
Johannes Schmelz
7a2ad1d31a corrected server states 2024-11-14 23:50:18 +01:00
Johannes Schmelz
c5ad476eaf added rent payment logic
added player on field logic
2024-11-14 23:50:06 +01:00
Filip Szepielewicz
c792a8b3fb T001 - T034 erster versuch 2024-11-14 21:58:47 +01:00
Filip Szepielewicz
12978ff410 T001 - T034 erster versuch 2024-11-14 21:54:27 +01:00
Johannes Schmelz
232e3a117c corrected rent prices 2024-11-14 01:08:22 +01:00
Johannes Schmelz
3668382911 FineField in Player 2024-11-13 23:50:06 +01:00
Johannes Schmelz
fafa53ffb7 rent calculation 2024-11-13 23:49:37 +01:00
Johannes Schmelz
44673fd57e createBoard method 2024-11-13 23:30:04 +01:00
Johannes Schmelz
f7149f225c Interpreter fuer messages hinzugefuegt 2024-11-13 18:54:24 +01:00
Johannes Schmelz
6773e18d34 Message Klassen hinzugefuegt 2024-11-13 14:11:20 +01:00
Johannes Schmelz
19a9b06f3c Merge branch 'gui' into 'main'
First compileable version of Client

See merge request progproj/gruppen-ht24/Gruppe-02!4
2024-11-13 12:42:37 +00:00
Johannes Schmelz
7ee2273761 First compileable version of Client 2024-11-13 12:42:37 +00:00
Johannes Schmelz
2cc1a338ec added Figure to Player for visual representation 2024-11-12 22:36:02 +01:00
Johannes Schmelz
81731247c7 added Fields 2024-11-12 22:35:41 +01:00
Johannes Schmelz
65c85aacf0 started UC-gameplay-15 2024-11-12 22:34:46 +01:00
Johannes Schmelz
dca23151a8 added Visitors 2024-11-12 22:33:10 +01:00
Johannes Schmelz
25305760c5 added Player 2024-11-12 22:32:55 +01:00
Johannes Schmelz
29a56f42a8 Merge branch 'main' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-02 2024-11-12 14:53:08 +01:00
Johannes Schmelz
3784348f91 init for monopoly 2024-11-12 14:53:04 +01:00
Johannes Schmelz
188ec03abd working version of battleship 2024-11-12 00:37:05 +01:00
Johannes Schmelz
f47cda7dc4 Merge branch 'b_schmelz_johannes' into 'main'
Revert "branched"

See merge request progproj/gruppen-ht24/Gruppe-02!1
2024-10-03 14:03:23 +00:00
Mark Minas
71a4ac8d12 added contents 2024-09-18 17:04:31 +02:00
108 changed files with 7779 additions and 23 deletions

View File

@@ -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
*/

View File

@@ -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();
}

View File

@@ -111,7 +111,7 @@ public class DialogManager {
*
* @param dialog the dialog to open
*/
void open(Dialog dialog) {
public void open(Dialog dialog) {
dialogStack.push(dialog);
dialog.update();
app.getGuiNode().attachChild(dialog);
@@ -133,7 +133,7 @@ public class DialogManager {
* @param dialog the dialog to close
* @throws IllegalArgumentException if the specified dialog is not the top dialog
*/
void close(Dialog dialog) {
public void close(Dialog dialog) {
if (!isTop(dialog))
throw new IllegalArgumentException(dialog + " is not the top dialog");
dialogStack.pop();

View File

@@ -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
@@ -10,14 +11,19 @@ import com.simsilica.lemur.Insets3f
import com.simsilica.lemur.component.QuadBackgroundComponent
import com.simsilica.lemur.component.TbtQuadBackgroundComponent
def bgColor = color(0.25, 0.5, 0.5, 1)
def buttonEnabledColor = color(0.8, 0.9, 1, 1)
def bgColor = color(1, 1, 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 gradient = TbtQuadBackgroundComponent.create(
texture(name: "/com/simsilica/lemur/icons/bordered-gradient.png",
@@ -27,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")
@@ -46,10 +59,26 @@ 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)
@@ -115,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
@@ -123,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
}
@@ -176,6 +208,7 @@ selector("slider.down.button", "pp") {
selector("checkbox", "pp") {
color = buttonEnabledColor
fontSize = 20
}
selector("rollup", "pp") {
@@ -192,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
}

View File

@@ -0,0 +1,23 @@
plugins {
id 'buildlogic.jme-application-conventions'
}
description = 'Monopoly Client'
dependencies {
implementation project(":jme-common")
implementation project(":monopoly:model")
implementation project(":monopoly:server")
implementation libs.jme3.desktop
runtimeOnly libs.jme3.awt.dialogs
runtimeOnly libs.jme3.plugins
runtimeOnly libs.jme3.jogg
runtimeOnly libs.jme3.testdata
}
application {
mainClass = 'pp.monopoly.client.MonopolyApp'
applicationName = 'monopoly'
}

View File

@@ -0,0 +1,73 @@
########################################
## Programming project code
## UniBw M, 2022, 2023, 2024
## www.unibw.de/inf2
## (c) Mark Minas (mark.minas@unibw.de)
########################################
#
# Battleship client configuration
#
# Specifies the map used by the opponent in single mode.
# Single mode is activated if this property is set.
#map.opponent=maps/map2.json
#
# Specifies the map used by the player in single mode.
# The player must define their own map if this property is not set.
map.own=maps/map1.json
#
# Coordinates of the shots fired by the RobotClient in the order listed.
# Example:
# 2, 0,\
# 2, 1,\
# 2, 2,\
# 2, 3
# defines four shots, namely at the coordinates
# (x=2, y=0), (x=2, y=1), (x=2, y=2), and (x=2, y=3)
robot.targets=2, 0,\
2, 1,\
2, 2,\
2, 3
#
# Delay in milliseconds between each shot fired by the RobotClient.
robot.delay=500
#
# The dimensions of the game map used in single mode.
# 'map.width' defines the number of columns, and 'map.height' defines the number of rows.
map.width=10
map.height=10
#
# The number of ships of each length available in single mode.
# The value is a comma-separated list where each element corresponds to the number of ships
# with a specific length. For example:
# ship.nums=4, 3, 2, 1
# This configuration means:
# - 4 ships of length 1
# - 3 ships of length 2
# - 2 ships of length 3
# - 1 ship of length 4
ship.nums=4, 3, 2, 1
#
# Screen settings
#
# Color of the text displayed at the top of the overlay.
# The format is (red, green, blue, alpha) where each value ranges from 0 to 1.
overlay.top.color=1, 1, 1, 1
#
# Application settings configuration
# Determines whether the settings window is shown at startup.
settings.show=false
#
# Specifies the width of the application window in pixels.
settings.resolution.width=1200
#
# Specifies the height of the application window in pixels.
settings.resolution.height=800
#
# Determines whether the application runs in full-screen mode.
settings.full-screen=false
#
# Enables or disables gamma correction to improve color accuracy.
settings.use-gamma-correction=true
#
# Indicates whether the statistics window is displayed during gameplay.
statistics.show=false

View File

@@ -0,0 +1,8 @@
handlers=java.util.logging.ConsoleHandler
.level=INFO
pp.level=FINE
com.jme3.network.level=INFO
;com.jme3.util.TangentBinormalGenerator.level=SEVERE
java.util.logging.ConsoleHandler.level=FINER
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
;java.util.logging.SimpleFormatter.format=[%4$s %2$s] %5$s%n

View File

@@ -0,0 +1,129 @@
////////////////////////////////////////
// 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.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;
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 static pp.util.PreferencesUtils.getPreferences;
/**
* An application state that plays sounds.
*/
public class GameSound extends AbstractAppState implements GameEventListener {
private static final Logger LOGGER = System.getLogger(GameSound.class.getName());
private static final Preferences PREFERENCES = getPreferences(GameSound.class);
private static final String ENABLED_PREF = "enabled"; //NON-NLS
private AudioNode splashSound;
private AudioNode shipDestroyedSound;
private AudioNode explosionSound;
/**
* Checks if sound is enabled in the preferences.
*
* @return {@code true} if sound is enabled, {@code false} otherwise.
*/
public static boolean enabledInPreferences() {
return PREFERENCES.getBoolean(ENABLED_PREF, true);
}
/**
* Toggles the game sound on or off.
*/
public void toggleSound() {
setEnabled(!isEnabled());
}
/**
* Sets the enabled state of this AppState.
* Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)}
*
* @param enabled {@code true} to enable the AppState, {@code false} to disable it.
*/
@Override
public void setEnabled(boolean enabled) {
if (isEnabled() == enabled) return;
super.setEnabled(enabled);
LOGGER.log(Level.INFO, "Sound enabled: {0}", enabled); //NON-NLS
PREFERENCES.putBoolean(ENABLED_PREF, enabled);
}
/**
* Initializes the sound effects for the game.
* Overrides {@link AbstractAppState#initialize(AppStateManager, Application)}
*
* @param stateManager The state manager
* @param app The application
*/
@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
}
/**
* Loads a sound from the specified file.
*
* @param app The application
* @param name The name of the sound file.
* @return The loaded AudioNode.
*/
private AudioNode loadSound(Application app, String name) {
try {
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
sound.setLooping(false);
sound.setPositional(false);
return sound;
}
catch (AssetLoadException | AssetNotFoundException ex) {
LOGGER.log(Level.ERROR, ex.getMessage(), ex);
}
return null;
}
/**
* Plays the splash sound effect.
*/
public void splash() {
if (isEnabled() && splashSound != null)
splashSound.playInstance();
}
/**
* Plays the explosion sound effect.
*/
public void explosion() {
if (isEnabled() && explosionSound != null)
explosionSound.playInstance();
}
/**
* Plays sound effect when a ship has been destroyed.
*/
public void shipDestroyed() {
if (isEnabled() && shipDestroyedSound != null)
shipDestroyedSound.playInstance();
}
@Override
public void receivedEvent(SoundEvent event) {
switch (event.sound()) {
}
}
}

View File

@@ -0,0 +1,51 @@
////////////////////////////////////////
// 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.util.prefs.Preferences;
import pp.dialog.Dialog;
import static pp.util.PreferencesUtils.getPreferences;
/**
* The Menu class represents the main menu in the Battleship game application.
* It extends the Dialog class and provides functionalities for loading, saving,
* returning to the game, and quitting the application.
*/
class Menu extends Dialog {
private static final Preferences PREFERENCES = getPreferences(Menu.class);
private static final String LAST_PATH = "last.file.path";
private final MonopolyApp app;
/**
* Constructs the Menu dialog for the Battleship application.
*
* @param app the BattleshipApp instance
*/
public Menu(MonopolyApp app) {
super(app.getDialogManager());
this.app = app;
}
/**
* Updates the state of the load and save buttons based on the game logic.
*/
@Override
public void update() {
}
/**
* As an escape action, this method closes the menu if it is the top dialog.
*/
@Override
public void escape() {
close();
}
}

View File

@@ -0,0 +1,235 @@
package pp.monopoly.client;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.system.AppSettings;
import com.simsilica.lemur.GuiGlobals;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.style.BaseStyles;
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;
public class MonopolyApp extends SimpleApplication implements MonopolyClient, GameEventListener {
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
public static void main(String[] args) {
new MonopolyApp().start();
}
public MonopolyApp() {
this.draw = new Draw(assetManager);
config = new MonopolyAppConfig();
serverConnection = new NetworkSupport(this);
logic = new ClientGameLogic(serverConnection);
logic.addListener(this);
setShowSettings(config.getShowSettings());
setSettings(makeSettings());
}
@Override
public MonopolyAppConfig getConfig() {
return config;
}
@Override
public ClientGameLogic getGameLogic() {
return logic;
}
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);
topText.setLocalTranslation(10, settings.getHeight() - 10, 0);
guiNode.attachChild(topText);
}
private void setupInput() {
inputManager.deleteMapping(INPUT_MAPPING_EXIT);
inputManager.setCursorVisible(true);
inputManager.addMapping("ESC", new KeyTrigger(KeyInput.KEY_ESCAPE));
inputManager.addListener(escapeListener, "ESC");
}
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);
}
}
}
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;
}
}
public void setInfoText(String text) {
topText.setText(text);
}
@Override
public void receivedEvent(InfoTextEvent event) {
setInfoText(event.key());
}
@Override
public void stop(boolean waitFor) {
if (executor != null) executor.shutdownNow();
serverConnection.disconnect();
super.stop(waitFor);
}
public DialogManager getDialogManager() {
return dialogManager;
}
public Draw getDraw() {
return draw;
}
public ExecutorService getExecutor() {
return executor;
}
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 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
}
/**
* Startet den Server in einem neuen Thread.
*/
public void startServer() {
new Thread(() -> {
try {
monopolyServer = new MonopolyServer(); // Erstelle Serverinstanz
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
public MonopolyServer getMonopolyServer() {
return monopolyServer;
}
}

View File

@@ -0,0 +1,190 @@
package pp.monopoly.client;
import com.jme3.math.ColorRGBA;
import pp.monopoly.game.client.MonopolyClientConfig;
/**
* Provides access to the Monopoly application configuration.
* Extends {@link MonopolyClientConfig} to include additional properties specific to the client,
* particularly those related to screen settings and visual customization.
* <p>
* <b>Note:</b> Attributes of this class should not be marked as {@code final}
* to ensure proper functionality when reading from a properties file.
* </p>
*/
public class MonopolyAppConfig extends MonopolyClientConfig {
/**
* Converts a string value found in the properties file into an object of the specified type.
* Extends the superclass method to support conversion to {@link ColorRGBA}.
*
* @param value the string value to be converted
* @param targetType the target type into which the value string is converted
* @return the converted object of the specified type
*/
@Override
protected Object convertToType(String value, Class<?> targetType) {
if (targetType == ColorRGBA.class)
return makeColorRGBA(value);
return super.convertToType(value, targetType);
}
/**
* Converts the specified string value to a corresponding {@link ColorRGBA} object.
*
* @param value the color in the format "red, green, blue, alpha" with all values in the range [0..1]
* @return a {@link ColorRGBA} object representing the color
* @throws IllegalArgumentException if the input string is not in the expected format
*/
private static ColorRGBA makeColorRGBA(String value) {
String[] split = value.split(",", -1);
try {
if (split.length == 4)
return new ColorRGBA(Float.parseFloat(split[0]),
Float.parseFloat(split[1]),
Float.parseFloat(split[2]),
Float.parseFloat(split[3]));
}
catch (NumberFormatException e) {
// deliberately left empty
}
throw new IllegalArgumentException(value + " should consist of exactly 4 numbers");
}
/**
* The width of the game view resolution in pixels.
*/
@Property("settings.resolution.width") //NON-NLS
private int resolutionWidth = 1200;
/**
* The height of the game view resolution in pixels.
*/
@Property("settings.resolution.height") //NON-NLS
private int resolutionHeight = 800;
/**
* Specifies whether the game should start in full-screen mode.
*/
@Property("settings.full-screen") //NON-NLS
private boolean fullScreen = false;
/**
* Specifies whether gamma correction should be enabled.
* If enabled, the main framebuffer is configured for sRGB colors,
* and sRGB images are linearized.
* <p>
* Requires a GPU that supports GL_ARB_framebuffer_sRGB; otherwise, this setting will be ignored.
* </p>
*/
@Property("settings.use-gamma-correction") //NON-NLS
private boolean useGammaCorrection = true;
/**
* Specifies whether full resolution framebuffers should be used on Retina displays.
* This setting is ignored on non-Retina platforms.
*/
@Property("settings.use-retina-framebuffer") //NON-NLS
private boolean useRetinaFrameBuffer = false;
/**
* Specifies whether the settings window should be shown for configuring the game.
*/
@Property("settings.show") //NON-NLS
private boolean showSettings = false;
/**
* Specifies whether the JME statistics window should be shown in the lower left corner of the screen.
*/
@Property("statistics.show") //NON-NLS
private boolean showStatistics = false;
/**
* The color of the top text during gameplay, represented as a {@link ColorRGBA} object.
*/
@Property("overlay.top.color") //NON-NLS
private ColorRGBA topColor = ColorRGBA.White;
/**
* Creates a default {@code MonopolyAppConfig} with predefined values.
*/
public MonopolyAppConfig() {
// Default constructor
}
/**
* Returns the width of the game view resolution in pixels.
*
* @return the width of the game view resolution in pixels
*/
public int getResolutionWidth() {
return resolutionWidth;
}
/**
* Returns the height of the game view resolution in pixels.
*
* @return the height of the game view resolution in pixels
*/
public int getResolutionHeight() {
return resolutionHeight;
}
/**
* Returns whether the game should start in full-screen mode.
*
* @return {@code true} if the game should start in full-screen mode; {@code false} otherwise
*/
public boolean fullScreen() {
return fullScreen;
}
/**
* Returns whether gamma correction is enabled.
* If enabled, the main framebuffer is configured for sRGB colors,
* and sRGB images are linearized.
*
* @return {@code true} if gamma correction is enabled; {@code false} otherwise
*/
public boolean useGammaCorrection() {
return useGammaCorrection;
}
/**
* Returns whether full resolution framebuffers should be used on Retina displays.
* This setting is ignored on non-Retina platforms.
*
* @return {@code true} if full resolution framebuffers should be used on Retina displays; {@code false} otherwise
*/
public boolean useRetinaFrameBuffer() {
return useRetinaFrameBuffer;
}
/**
* Returns whether the settings window should be shown for configuring the game.
*
* @return {@code true} if the settings window should be shown; {@code false} otherwise
*/
public boolean getShowSettings() {
return showSettings;
}
/**
* Returns whether the JME statistics window should be shown in the lower left corner of the screen.
*
* @return {@code true} if the statistics window should be shown; {@code false} otherwise
*/
public boolean getShowStatistics() {
return showStatistics;
}
/**
* Returns the color of the top text during gameplay as a {@link ColorRGBA} object.
*
* @return the color of the top text during gameplay
*/
public ColorRGBA getTopColor() {
return topColor;
}
}

View File

@@ -0,0 +1,84 @@
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 Monopoly game.
* Extends the AbstractAppState from jMonkeyEngine to manage state behavior.
*/
public abstract class MonopolyAppState extends AbstractAppState {
private MonopolyApp app;
/**
* Creates a new MonopolyAppState that is initially disabled.
*/
protected MonopolyAppState() {
setEnabled(false);
}
/**
* Initializes the state manager and application.
*
* @param stateManager The state manager
* @param application The application instance
*/
@Override
public void initialize(AppStateManager stateManager, Application application) {
super.initialize(stateManager, application);
this.app = (MonopolyApp) application;
if (isEnabled()) {
enableState();
}
}
/**
* Returns the MonopolyApp instance associated with this MonopolyAppState.
*
* @return The MonopolyApp instance.
*/
public MonopolyApp getApp() {
return app;
}
/**
* Returns the client game logic handler.
*
* @return the client game logic handler
*/
public ClientGameLogic getGameLogic() {
return app.getGameLogic();
}
/**
* Sets the enabled state of the MonopolyAppState.
* If the new state is the same as the current state, the method returns.
*
* @param enabled The new enabled state.
*/
@Override
public void setEnabled(boolean enabled) {
if (isEnabled() == enabled) return;
super.setEnabled(enabled);
if (app != null) {
if (enabled) {
enableState();
} else {
disableState();
}
}
}
/**
* Called when the state is enabled. Override to define specific behavior.
*/
protected abstract void enableState();
/**
* Called when the state is disabled. Override to define specific behavior.
*/
protected abstract void disableState();
}

View File

@@ -0,0 +1,146 @@
package pp.monopoly.client;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
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 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";
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);
private String hostname;
private int portNumber;
private Future<Object> connectionFuture;
private Dialog progressDialog;
/**
* Constructs a new NetworkDialog.
*
* @param network The NetworkSupport instance to be used for network operations.
*/
NetworkDialog(NetworkSupport network) {
super(network.getApp().getDialogManager());
this.network = network;
initializeDialog();
}
/**
* 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, "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("Port muss eine Zahl sein.");
}
}
/**
* Opens a progress dialog while connecting.
*/
private void openProgressDialog() {
progressDialog = DialogBuilder.simple(network.getApp().getDialogManager())
.setText("Verbinde zum Server...")
.build();
progressDialog.open();
}
/**
* Attempts to initialize the network connection.
*
* @throws RuntimeException If an error occurs when creating the client.
*/
private Object initNetwork() {
try {
network.initNetwork(hostname, portNumber);
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Updates the connection status and handles completion or failure.
*/
@Override
public void update(float delta) {
if (connectionFuture != null && connectionFuture.isDone()) {
try {
connectionFuture.get();
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 onSuccess() {
connectionFuture = null;
progressDialog.close();
this.close();
network.getApp().setInfoText("Warte auf einen Gegner...");
}
/**
* Handles a failed connection attempt.
*
* @param e The cause of the failure.
*/
private void onFailure(Throwable e) {
connectionFuture = null;
progressDialog.close();
network.getApp().errorDialog("Verbindung zum Server fehlgeschlagen.");
network.getApp().setInfoText(e.getLocalizedMessage());
}
}

View File

@@ -0,0 +1,144 @@
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;
/**
* 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 {
private static final Logger LOGGER = System.getLogger(NetworkSupport.class.getName());
private final MonopolyApp app;
private Client client;
/**
* Constructs a NetworkSupport instance for the Monopoly application.
*
* @param app The Monopoly application instance.
*/
public NetworkSupport(MonopolyApp app) {
this.app = app;
}
/**
* Returns the Monopoly application instance.
*
* @return Monopoly application instance
*/
MonopolyApp getApp() {
return app;
}
/**
* Checks if there is a connection to the game server.
*
* @return true if there is a connection to the game server, false otherwise.
*/
@Override
public boolean isConnected() {
return client != null && client.isConnected();
}
/**
* Attempts to join the game if there is no connection yet.
* Opens a dialog for the user to enter the host and port information.
*/
@Override
public void connect() {
if (client == null) {
new NetworkDialog(this).open();
}
}
/**
* Closes the client connection.
*/
@Override
public void disconnect() {
if (client == null) return;
client.close();
client = null;
LOGGER.log(Level.INFO, "Client connection closed.");
}
/**
* Initializes the network connection.
*
* @param host The server's address.
* @param port The server's port.
* @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("Already connected to the game server.");
}
client = Network.connectToServer(host, port);
client.start();
client.addMessageListener(this);
client.addClientStateListener(this);
}
/**
* Called when a message is received from the server.
*
* @param client The client instance that received the message.
* @param message The message received from the server.
*/
@Override
public void messageReceived(Client client, Message message) {
LOGGER.log(Level.INFO, "Message received from server: {0}", message);
if (message instanceof ServerMessage serverMessage) {
app.enqueue(() -> serverMessage.accept(app.getGameLogic()));
}
}
/**
* Called when the client has successfully connected to the server.
*
* @param client The client that connected to the server.
*/
@Override
public void clientConnected(Client client) {
LOGGER.log(Level.INFO, "Successfully connected to server: {0}", client);
}
/**
* Called when the client is disconnected from the server.
*
* @param client The client that was disconnected.
* @param disconnectInfo Information about the disconnection.
*/
@Override
public void clientDisconnected(Client client, DisconnectInfo disconnectInfo) {
LOGGER.log(Level.INFO, "Disconnected from server: {0}", disconnectInfo);
this.client = null;
app.enqueue(() -> app.setInfoText("Verbindung zum Server verloren."));
}
/**
* Sends the specified message to the server.
*
* @param message The message to be sent to the server.
*/
@Override
public void send(ClientMessage message) {
LOGGER.log(Level.INFO, "Sending message to server: {0}", message);
if (client == null) {
app.errorDialog("Verbindung zum Server verloren.");
} else {
client.send(message);
}
}
}

View File

@@ -0,0 +1,181 @@
package pp.monopoly.client;
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.Container;
import com.simsilica.lemur.HAlignment;
import com.simsilica.lemur.component.QuadBackgroundComponent;
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;
*/
public class StartMenu extends Dialog {
private final MonopolyApp app;
private Container logoContainer;
private Container unibwLogoContainer;
/**
* Constructs the Startup Menu dialog for the Monopoly application.
*
* @param app the MonopolyApp instance
*/
public StartMenu(MonopolyApp app) {
super(app.getDialogManager());
this.app = app;
}
/**
* 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", backgroundImage);
background.setMaterial(backgroundMaterial);
background.setLocalTranslation(0, 0, -1); // Ensure it is behind other GUI elements
app.getGuiNode().attachChild(background);
createMonopolyLogo(app);
createUnibwLogo(app);
// Center container for title and play button
Container centerMenu = new Container(new SpringGridLayout(Axis.Y, Axis.X));
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
startButton.addClickCommands(source -> startGame(app));
centerMenu.addChild(startButton);
// 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);
// 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);
// 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* Starts the game by transitioning to the CreateGameMenu.
*/
private static void startGame(MonopolyApp app) {
app.getGuiNode().detachAllChildren();
new CreateGameMenu(app);
}
/**
* 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);
}
}

View File

@@ -0,0 +1,74 @@
package pp.monopoly.client.gui;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
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;
import pp.monopoly.notification.ItemRemovedEvent;
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.
*/
abstract class BoardSynchronizer extends ModelViewSynchronizer<Item> implements Visitor<Spatial>, GameEventListener {
protected final Board board;
/**
* Constructs a new BoardSynchronizer.
*
* @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 board, Node root) {
super(root);
this.board = board;
}
/**
* Translates a model item into its corresponding visual representation.
*
* @param item the item from the model to be translated
* @return the visual representation of the item as a {@link Spatial}
*/
@Override
protected Spatial translate(Item item) {
return item.accept(this);
}
/**
* 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 board.
*
* @param event the event indicating that an item has been removed from the board
*/
@Override
public void receivedEvent(ItemRemovedEvent event) {
if (board == event.getBoard()) {
delete(event.getItem());
}
}
/**
* Handles the event when an item is added to the board.
*
* @param event the event indicating that an item has been added to the board
*/
@Override
public void receivedEvent(ItemAddedEvent event) {
if (board == event.getBoard()) {
add(event.getItem());
}
}
}

View File

@@ -0,0 +1,51 @@
package pp.monopoly.client.gui;
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;
public class GameMenu extends Dialog {
private final MonopolyApp app;
/**
* Constructs the SettingsMenu dialog for the Monopoly application.
*
* @param app the MonopolyApp instance
*/
public GameMenu(MonopolyApp app) {
super(app.getDialogManager());
this.app = app;
// Add a title label for Settings
Label settingsTitle = new Label("Einstellungen", new ElementId("settings-title"));
settingsTitle.setFontSize(48); // Set font size for the title
settingsTitle.setColor(ColorRGBA.White);
// Add any settings-related components here, such as volume control, toggles, etc.
// Add a back button to return to StartMenu
Button backButton = new Button("Zurück", new ElementId("menu-button"));
backButton.setColor(ColorRGBA.White);
backButton.setFontSize(24);
backButton.addClickCommands(source -> returnToStartMenu());
// Add components to this dialog
addChild(settingsTitle);
addChild(backButton);
// You can add more settings components here, like checkboxes or sliders.
}
/**
* Returns to the StartMenu when the back button is clicked.
*/
private void returnToStartMenu() {
app.getDialogManager().close(this); // Close the current settings dialog
//TODO return zum Ausgangsmenü
}
}

View File

@@ -0,0 +1,75 @@
package pp.monopoly.client.gui;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
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.Board;
/**
* 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 BACKGROUND_DEPTH = -4f;
private static final ColorRGBA BACKGROUND_COLOR = new ColorRGBA(0, 0.05f, 0.05f, 0.5f);
private final MonopolyApp app;
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}.
*
* @param board the board to visualize
* @param app the main application instance
*/
MapView(Board board, MonopolyApp app) {
this.board = board;
this.app = app;
this.synchronizer = new MapViewSynchronizer(this);
setupBackground();
app.getGameLogic().addListener(synchronizer);
}
/**
* Unregisters the {@link MapViewSynchronizer} from listening to board changes.
*/
void unregister() {
app.getGameLogic().removeListener(synchronizer);
}
/**
* Sets up the background of the map view using a quad geometry.
*/
private void setupBackground() {
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", BACKGROUND_COLOR);
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
Geometry background = new Geometry("MapBackground", new Quad(board.getWidth() * FIELD_SIZE, board.getHeight() * FIELD_SIZE));
background.setMaterial(mat);
background.setLocalTranslation(0f, 1f, BACKGROUND_DEPTH);
background.setCullHint(CullHint.Never);
mapNode.attachChild(background);
}
/**
* Gets the root node containing all visual elements in this map view.
*
* @return the root node for the map view
*/
public Node getNode() {
return mapNode;
}
public Board getBoard() {
return board;
}
}

View File

@@ -0,0 +1,45 @@
package pp.monopoly.client.gui;
import com.jme3.scene.Spatial;
import pp.monopoly.model.Figure;
/**
* Synchronizes the visual representation of the board with the game model.
* Handles updates for items on the board.
*/
class MapViewSynchronizer extends BoardSynchronizer {
private final MapView view;
/**
* Constructs a new MapViewSynchronizer for the given MapView.
*
* @param view the MapView to synchronize with the game model
*/
public MapViewSynchronizer(MapView view) {
super(view.getBoard(), view.getNode());
this.view = view;
addExisting();
}
/**
* Enables the state by performing initial setup, such as adding any items to the view.
*/
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.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

View File

@@ -0,0 +1,15 @@
plugins {
id 'buildlogic.java-library-conventions'
}
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")
}

View File

@@ -0,0 +1,79 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly;
import static java.lang.Math.max;
import pp.util.config.Config;
/**
* Provides access to the configuration settings for the Monopoly game.
* <p>
* This class allows for loading configuration settings from a properties file,
* including the server port, map dimensions
* </p>
* <p>
* <b>Note:</b> Attributes of this class are not marked as {@code final} to allow
* for proper initialization when reading from a properties file.
* </p>
*/
public class MonopolyConfig extends Config {
/**
* The default port number for the Monopoly server.
*/
@Property("port")
private int port = 42069;
/**
* The width of the game map in terms of grid units.
*/
@Property("map.width")
private int mapWidth = 10;
/**
* The height of the game map in terms of grid units.
*/
@Property("map.height")
private int mapHeight = 10;
/**
* Creates an instance of {@code MonopolyConfig} with default settings.
*/
public MonopolyConfig() {
// Default constructor
}
/**
* Returns the port number configured for the Monopoly server.
*
* @return the port number
*/
public int getPort() {
return port;
}
/**
* Returns the width of the game map. The width is guaranteed to be at least 2 units.
*
* @return the width of the game map
*/
public int getMapWidth() {
return max(mapWidth, 2);
}
/**
* Returns the height of the game map. The height is guaranteed to be at least 2 units.
*
* @return the height of the game map
*/
public int getMapHeight() {
return max(mapHeight, 2);
}
}

View File

@@ -0,0 +1,40 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly;
import java.util.ResourceBundle;
/**
* Provides access to the resource bundle of the game.
*
* @see #BUNDLE
*/
public class Resources {
/**
* The resource bundle for the Monopoly game.
*/
public static final ResourceBundle BUNDLE = ResourceBundle.getBundle("monopoly"); //NON-NLS
/**
* Gets a string for the given key from the resource bundle in {@linkplain #BUNDLE}.
*
* @param key the key for the desired string
* @return the string for the given key
* @throws NullPointerException if {@code key} is {@code null}
* @throws java.util.MissingResourceException if no object for the given key can be found
* @throws ClassCastException if the object found for the given key is not a string
*/
public static String lookup(String key) {
return BUNDLE.getString(key);
}
/**
* Private constructor to prevent instantiation.
*/
private Resources() { /* do not instantiate */ }
}

View File

@@ -0,0 +1,260 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
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;
import pp.monopoly.message.server.EventDrawCard;
import pp.monopoly.message.server.GameOver;
import pp.monopoly.message.server.GameStart;
import pp.monopoly.message.server.JailEvent;
import pp.monopoly.message.server.PlayerStatusUpdate;
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.ViewAssetsResponse;
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;
import pp.monopoly.notification.GameEventListener;
import pp.monopoly.notification.InfoTextEvent;
import pp.monopoly.notification.Sound;
import pp.monopoly.notification.SoundEvent;
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;
/**
* Controls the client-side game logic for Monopoly.
* Manages the player's placement, interactions with the map, and response to server messages.
*/
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 board;
private ClientState state = new ClientState(this) {
};
/**
* Constructs a ClientGameLogic with the specified sender object.
*
* @param clientSender the object used to send messages to the server
*/
public ClientGameLogic(ClientSender clientSender) {
this.clientSender = clientSender;
}
/**
* Returns the current state of the game logic.
*/
ClientState getState() {
return state;
}
/**
* Sets the current state of the game logic.
*
* @param newState the new state to be set
*/
void setState(ClientState newState) {
LOGGER.log(Level.DEBUG, "state transition {0} --> {1}", state.getName(), newState.getName()); //NON-NLS
state = newState;
notifyListeners(new ClientStateEvent());
state.entry();
}
/**
* Returns the player's own map.
*
* @return the player's own map
*/
public Board getBoard() {
return board;
}
/**
* Moves the preview figure to the specified position.
*
* @param pos the new position for the preview figure
*/
public void movePreview(IntPoint pos) {
state.movePreview(pos);
}
/**
* Sets the informational text to be displayed to the player.
*
* @param key the key for the info text
*/
void setInfoText(String key) {
notifyListeners(new InfoTextEvent(key));
}
/**
* Emits an event to play the specified sound.
*
* @param sound the sound to be played.
*/
public void playSound(Sound sound) {
notifyListeners(new SoundEvent(sound));
}
/**
* Loads a map from the specified file.
*
* @param file the file to load the map from
* @throws IOException if an I/O error occurs
*/
public void loadMap(File file) throws IOException {
state.loadMap(file);
}
/**
* Sends a message to the server.
*
* @param msg the message to be sent
*/
void send(ClientMessage msg) {
if (clientSender == null)
LOGGER.log(Level.ERROR, "trying to send {0} with sender==null", msg); //NON-NLS
else
clientSender.send(msg);
}
/**
* Adds a listener to receive game events.
*
* @param listener the listener to add
*/
public synchronized void addListener(GameEventListener listener) {
listeners.add(listener);
}
/**
* Removes a listener from receiving game events.
*
* @param listener the listener to remove
*/
public synchronized void removeListener(GameEventListener listener) {
listeners.remove(listener);
}
/**
* Notifies all listeners of a game event.
*
* @param event the game event to notify listeners of
*/
@Override
public void notifyListeners(GameEvent event) {
final List<GameEventListener> copy;
synchronized (this) {
copy = new ArrayList<>(listeners);
}
for (GameEventListener listener : copy)
event.notifyListener(listener);
}
/**
* Called once per frame by the update loop.
*
* @param delta time in seconds since the last update call
*/
public void update(float delta) {
state.update(delta);
}
@Override
public void received(BuyPropertyResponse msg) {
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) {
setInfoText("You rolled a " + msg.calcTotal() + "!");
playSound(Sound.DICE_ROLL);
}
@Override
public void received(EventDrawCard msg) {
setInfoText("Event card drawn: " + msg.getCardDescription());
playSound(Sound.EVENT_CARD);
}
@Override
public void received(GameOver msg) {
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) {
setInfoText("The game has started! Good luck!");
}
@Override
public void received(JailEvent msg) {
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) {
setInfoText("Player " + msg.getPlayerName() + " status updated: " + msg.getStatus());
}
@Override
public void received(TimeOutWarning msg) {
setInfoText("Warning! Time is running out. You have " + msg.getRemainingTime() + " seconds left.");
}
@Override
public void received(ViewAssetsResponse msg) {
setInfoText("Your current assets are being displayed.");
}
@Override
public void received(TradeReply msg) {
}
@Override
public void received(TradeRequest msg) {
}
}

View File

@@ -0,0 +1,22 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.game.client;
import pp.monopoly.message.client.ClientMessage;
/**
* Interface for sending messages to the server.
*/
public interface ClientSender {
/**
* Send the specified message to the server.
*
* @param message the message
*/
void send(ClientMessage message);
}

View File

@@ -0,0 +1,85 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.game.client;
import pp.monopoly.model.IntPoint;
import java.io.File;
import java.io.IOException;
import java.lang.System.Logger.Level;
/**
* Defines the behavior and state transitions for the client-side game logic.
* Different states of the game logic implement this interface to handle various game events and actions.
*/
abstract class ClientState {
/**
* The game logic object.
*/
final ClientGameLogic logic;
/**
* Constructs a client state of the specified game logic.
*
* @param logic the game logic
*/
ClientState(ClientGameLogic logic) {
this.logic = logic;
}
/**
* Method to be overridden by subclasses for post-transition initialization.
* By default, it does nothing, but it can be overridden in derived states.
*/
void entry() {
// Default implementation does nothing
}
/**
* Returns the name of the current state.
*
* @return the name of the current state
*/
String getName() {
return getClass().getSimpleName();
}
/**
* Checks if the battle state should be shown.
*
* @return true if the battle state should be shown, false otherwise
*/
boolean showTurn() {
return false;
}
/**
* Moves the preview figure to the specified position.
*
* @param pos the new position for the preview figure
*/
void movePreview(IntPoint pos) {
ClientGameLogic.LOGGER.log(Level.DEBUG, "movePreview has no effect in {0}", getName()); //NON-NLS
}
/**
* Loads a map from the specified file.
*
* @param file the file to load the map from
* @throws IOException if the map cannot be loaded in the current state
*/
void loadMap(File file) throws IOException {
throw new IOException("You are not allowed to load a map in this state of the game");
}
/**
* Called once per frame by the update loop if this state is active.
*
* @param delta time in seconds since the last update call
*/
void update(float delta) { /* do nothing by default */ }
}

View File

@@ -0,0 +1,36 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.game.client;
/**
* Interface representing a Monopoly client.
* Provides methods to access game logic, configuration, and to enqueue tasks.
*/
public interface MonopolyClient {
/**
* Returns the game logic associated with this client.
*
* @return the ClientGameLogic instance
*/
ClientGameLogic getGameLogic();
/**
* Returns the configuration associated with this client.
*
* @return the MonopolyConfig instance
*/
MonopolyClientConfig getConfig();
/**
* Enqueues a task to be executed by the client.
*
* @param runnable the task to be executed
*/
void enqueue(Runnable runnable);
}

View File

@@ -0,0 +1,48 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.game.client;
import java.io.File;
import pp.monopoly.MonopolyConfig;
/**
* Class providing access to the Monopoly client configuration.
* Extends {@link MonopolyConfig} to include additional properties specific to the client.
* This class manages configuration settings related to the RobotClient's behavior
* and the game maps used in single mode.
* <p>
* <b>Note:</b> Attributes of this class should not be marked as {@code final}
* to ensure proper functionality when reading from a properties file.
* </p>
*/
public class MonopolyClientConfig extends MonopolyConfig {
/**
* Path to the file representing the map.
*/
@Property("map")
private String map;
/**
* Creates a default {@code MonopolyClientConfig} with predefined values.
*/
public MonopolyClientConfig() {
// Default constructor
}
/**
* Returns the file representing the opponent's map.
*
* @return the opponent's map file, or {@code null} if not set.
*/
public File getMap() {
return map == null ? null : new File(map);
}
}

View File

@@ -0,0 +1,32 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.game.client;
/**
* Interface representing a connection to the server.
* Extends ClientSender to allow sending messages to the server.
*/
public interface ServerConnection extends ClientSender {
/**
* Checks if the client is currently connected to the server.
*
* @return true if connected, false otherwise.
*/
boolean isConnected();
/**
* Establishes a connection to the server.
*/
void connect();
/**
* Disconnects from the server.
*/
void disconnect();
}

View File

@@ -0,0 +1,487 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.game.server;
import java.util.List;
import java.util.Random;
import pp.monopoly.message.server.DiceResult;
import pp.monopoly.model.FieldVisitor;
import pp.monopoly.model.Figure;
import pp.monopoly.model.card.DeckHelper;
import pp.monopoly.model.fields.BuildingProperty;
import pp.monopoly.model.fields.EventField;
import pp.monopoly.model.fields.FineField;
import pp.monopoly.model.fields.FoodField;
import pp.monopoly.model.fields.GateField;
import pp.monopoly.model.fields.GoField;
import pp.monopoly.model.fields.GulagField;
import pp.monopoly.model.fields.PropertyField;
import pp.monopoly.model.fields.TestStreckeField;
import pp.monopoly.model.fields.WacheField;
/**
* Class representing a player
*/
public class Player implements FieldVisitor<Void>{
private final int id;
private String name;
private PlayerColor color;
private int accountBalance = 0;
private Figure figure;
private List<PropertyField> properties;
private int getOutOfJailCard;
private int fieldID;
private DiceResult rollResult;
private final PlayerHandler handler;
private PlayerState state = new LobbyState();
/**
* Constructs a player with the speciefied params
* @param id the id of the player
* @param name the name of the player
* @param handler the PlayerHandler thispalyer is a part of
*/
public Player(int id, String name, PlayerHandler handler) {
this.name = name;
this.id = id;
this.handler = handler;
}
/**
* Constructs a player with the specified id
* @param id the id of the player
* @param handler the PlayerHandler this player is a part of
*/
public Player(int id, PlayerHandler handler) {
this.id = id;
this.handler = handler;
}
/**
* Set the name of the Player
* @param name the new name
*/
void setName(String name) {
this.name = name;
}
/**
* Set the PlayerColor
* @param color the color to be set to
*/
void setColor(PlayerColor color) {
this.color = color;
}
/**
* Returns this players id
* @return th eid of this player
*/
public int getId() {
return id;
}
/**
* Returns the current position of the player
* @return the current position of this player
*/
public int getFieldID() {
return fieldID;
}
/**
* Moves by the specified amount of steps
* @param steps the number of steps to move
* @return the new position
*/
public int move(int steps){
return movePos(fieldID+steps);
}
/**
* Moves the player to the specified Position on the board
* @param position the position to move to
* @return the new position
*/
public int movePos(int position){
fieldID = fieldID+position;
if(fieldID >= 40) {
fieldID = fieldID%40;
earnMoney(2000);
}
return fieldID;
}
/**
* Gets all the properties owned by this player
* @return List of all properties owned by this player
*/
public List<PropertyField> getProperties() {
return properties;
}
/**
* Buy the speciefied property.
* Properties can olny be bought when they are not sold yet and you have enough money left to buy
* @param property to property to be bought
*/
public void buyProperty(PropertyField property) {
if (property.getOwner() == null && accountBalance >= property.getPrice()) {
properties.add(property);
pay(property.getPrice());
}
}
/**
* Sell the property
* @param property the property to be sold
*/
public void sellProperty(PropertyField property) {
if (property.getOwner() == this) {
properties.remove(property);
property.setOwner(null);
}
}
/**
* Gets this players current accountBalanece
* @return the amount of money currently owned by this player
*/
public int getAccountBalance() {
return accountBalance;
}
/**
* Removed the speciefied amount of money to this players accountabalance
* @param amount the amount to be removed
*/
public void pay(int amount) {
accountBalance -= amount;
}
/**
* Add the speciefied amount of money to this players accountabalance
* @param amount the amount to be added
*/
public void earnMoney(int amount) {
accountBalance += amount;
}
/**
* Return the players name
* @return the name of this player
*/
public String getName() {
return name;
}
/**
* Return the number of GEtOutOfJailCards owned by this player
* @return
*/
public int getNumJailCard() {
return getOutOfJailCard;
}
/**
* Adds a GetOutOfJailCard
*/
public void addJailCard() {
getOutOfJailCard++;
}
/**
* Removes a GetOutOfJailCard.
* Removes one single card per call, to a minimum of 0 cards
*/
public void removeJailCard() {
if (getOutOfJailCard ==0) {
throw new IllegalStateException("Has no JailCard to remove");
}
getOutOfJailCard--;
}
/**
* Handles the logic of paying the jail bail
*/
public void payBail() {
state.payBail();
}
/**
* Handles the logic of using a GetOutOfJailCard
*/
public void useJailCard() {
state.useJailCard();
}
@Override
public Void visit(BuildingProperty field) {
int rent = field.calcRent();
field.getOwner().earnMoney(rent);
pay(rent);
return null;
}
@Override
public Void visit(FoodField field) {
int factor = 4;
if (field.getOwner().getNumProp(field) == 2) {
factor = 10;
}
field.getOwner().earnMoney(rollResult.calcTotal()*factor);
pay(rollResult.calcTotal()*factor);
return null;
}
@Override
public Void visit(GateField field) {
int rent = field.calcRent() * field.getOwner().getNumProp(field);
field.getOwner().earnMoney(rent);
pay(rent);
return null;
}
@Override
public Void visit(GulagField field) {
state = new JailState();
return null;
}
@Override
public Void visit(TestStreckeField field) {
earnMoney(field.collectMoney());
return null;
}
@Override
public Void visit(EventField field) {
DeckHelper.drawCard();
return null;
}
@Override
public Void visit(WacheField field) {
movePos(10);
return null;
}
@Override
public Void visit(GoField field) {
earnMoney(2000);
GulagField res = (GulagField) handler.getLogic().getBoardManager().getFieldAtIndex(10);
res.accept(this);
return null;
}
@Override
public Void visit(FineField field) {
int amount = field.getFine();
pay(amount);
TestStreckeField res =(TestStreckeField) handler.getLogic().getBoardManager().getFieldAtIndex(20);
res.addMoney(amount);
return null;
}
/**
* Return the number of Properties of the speciefied fild type
* @param field the type of field to search for
* @return the number of the fields owned with the specified type
*/
public int getNumProp(PropertyField field) {
int count = 0;
for (PropertyField propertyField : properties) {
if (propertyField.getClass() == field.getClass()) {
count++;
}
}
return count;
}
/**
* Inner class for dice functionality in the game.
* Rolls random dice values.
*/
private class Dice {
private static Random random = new Random();
/**
* Rolls a single die and returns a random value from 1 to 6.
*
* @return the result of a dice roll (1 to 6)
*/
private static int rollDice() {
return random.nextInt(6) + 1;
}
}
/**
* Rolls two dice and returns a list with the results.
*
* @return a List of two integers representing the dice roll results
*/
DiceResult rollDice() {
return state.rollDice();
}
/**
* A interface representing the PlayerStates
*/
private interface PlayerState {
/**
* Handles the logic for rolling Dice
* @return the {@link DiceResult} of this the DiceRoll
*/
DiceResult rollDice();
/**
* Handles the logic for paying the Jail Bail
*/
void payBail();
/**
* Handles the action of using a GetOutOfJail Card
*/
void useJailCard();
}
/**
* Class to represent the Active PlayerState
* This class is set when it is the Players turn to do actions
*/
private class ActiveState implements PlayerState {
@Override
public DiceResult rollDice() {
List<Integer> roll = List.of(Dice.rollDice(), Dice.rollDice());
rollResult = new DiceResult(roll);
return rollResult;
}
@Override
public void payBail() {
// do nothing
}
@Override
public void useJailCard() {
// do nothings
}
}
/**
* A class to represent the Lobby PlayerState
* Set when in Lobby
*/
private class LobbyState implements PlayerState{
@Override
public DiceResult rollDice() {
//do nothing
return null;
}
@Override
public void payBail() {
//do nothing
}
@Override
public void useJailCard() {
// do nothing
}
}
/**
* A class to represent the Jailed PlayerState
* Set when in Gulag
*/
private class JailState implements PlayerState {
private int DoubletsCounter = 3;
@Override
public DiceResult rollDice() {
List<Integer> roll = List.of(Dice.rollDice(), Dice.rollDice());
rollResult = new DiceResult(roll);
if (rollResult.isDoublets()) {
state = new ActiveState();
} else if (DoubletsCounter == 0) {
} else {
DoubletsCounter--;
}
return rollResult;
}
@Override
public void payBail() {
pay(500);
state = new ActiveState();
}
@Override
public void useJailCard() {
getOutOfJailCard--;
state = new ActiveState();
}
}
private class BankruptState implements PlayerState {
@Override
public DiceResult rollDice() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'rollDice'");
}
@Override
public void payBail() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'payBail'");
}
@Override
public void useJailCard() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'useJailCard'");
}
}
private class WaitForTurnState implements PlayerState {
@Override
public DiceResult rollDice() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'rollDice'");
}
@Override
public void payBail() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'payBail'");
}
@Override
public void useJailCard() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'useJailCard'");
}
}
}

View File

@@ -0,0 +1,35 @@
package pp.monopoly.game.server;
import com.jme3.math.ColorRGBA;
/**
* Enum representing six distinct colors for players in the game.
*/
public enum PlayerColor {
GREEN_LIGHT(new ColorRGBA(0 / 255f, 204 / 255f, 0 / 255f, 1)), // Hex: 00cc00
RED(new ColorRGBA(255 / 255f, 0 / 255f, 0 / 255f, 1)), // Hex: ff0000
BLUE(new ColorRGBA(0 / 255f, 0 / 255f, 204 / 255f, 1)), // Hex: 0000cc
PINK(new ColorRGBA(255 / 255f, 77 / 255f, 166 / 255f, 1)), // Hex: ff4da6
GREEN_DARK(new ColorRGBA(0 / 255f, 102 / 255f, 0 / 255f, 1)), // Hex: 006600
YELLOW(new ColorRGBA(255 / 255f, 255 / 255f, 0 / 255f, 1)); // Hex: ffff00
private final ColorRGBA color;
/**
* Constructs a PlayerColor with the specified ColorRGBA value.
*
* @param color the ColorRGBA value associated with the player color
*/
PlayerColor(ColorRGBA color) {
this.color = color;
}
/**
* Gets the ColorRGBA value of the player color.
*
* @return the ColorRGBA value
*/
public ColorRGBA getColor() {
return color;
}
}

View File

@@ -0,0 +1,137 @@
package pp.monopoly.game.server;
import java.util.LinkedList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A class for helping with player actions and managing thier turns
*/
public class PlayerHandler {
private List<Player> players = new LinkedList<>();
private Set<Player> readyPlayers = new HashSet<>();
private ServerGameLogic logic;
/**
* Contructs a PlayerHandler
* @param logic the {@link ServerGameLogic} this PlayerHandler is a part of
*/
PlayerHandler(ServerGameLogic logic) {
this.logic = logic;
}
/**
* Contructs a PlayerHandler
* @param logic the {@link ServerGameLogic} this PlayerHandler is a part of
* @param p1 a Player to be added
*/
PlayerHandler(ServerGameLogic logic, Player p1) {
this(logic);
players.add(p1);
}
/**
* Contructs a PlayerHandler
* @param logic the {@link ServerGameLogic} this PlayerHandler is a part of
* @param players a Collection of Players to be added
*/
PlayerHandler(ServerGameLogic logic, Collection<Player> players) {
this(logic);
players.addAll(players);
}
/**
* Return the number of players
* @return number of players in the game
*/
public int getPlayerCount() {
return players.size();
}
/**
* Chechs if all players are ready to start the game
* @return {@code true} if all players are ready, otherwise {@code false}
*/
public boolean allPlayersReady() {
if (readyPlayers.size() == players.size()) return true;
return false;
}
/**
* Sets a players Ready status
* @param player the player to alter
* @param ready the new Status
*/
void setPlayerReady(Player player, boolean ready) {
if (!players.contains(player)) {
throw new IllegalArgumentException("Player does not belong to this PlayerHandler");
} else {
if (ready) {
readyPlayers.add(player);
} else {
readyPlayers.remove(player);
}
}
}
/**
* Adds a player to the Queue
* @param player the player to be added to the queue
*/
void addPlayer(Player player) {
if (players.contains(player)) {
throw new IllegalArgumentException("Player already registered");
}
players.add(player);
}
/**
* Removes the specified Player from the Queue
* @param player the player to be removed
*/
void removePlayer(Player player) {
players.remove(player);
}
/**
* Gets Player based on their id in the Queue
* @param index the index of the queue
* @return the Player at the required index
*/
Player getPlayerAtIndex(int index) {
return players.get(index);
}
/**
* Completes a player turn and return the next player
* @return the next players who is active
*/
Player nextPlayer() {
Player tmp = players.get(0);
players.remove(0);
players.add(tmp);
return players.get(0);
}
/**
* Returns the {@link ServerGameLogic} of this PlayerHandler
* @return the {@link ServerGameLogic} of this PlayerHandler
*/
ServerGameLogic getLogic() {
return logic;
}
/**
* Gets a player based on their id
* @param id the id to be searched for
* @return the player with the required id
*/
Player getPlayerById(int id) {
for (Player player : players) {
if (player.getId() == id) return player;
}
throw new NoSuchElementException("Player mit id "+id+" existiert nicht");
}
}

View File

@@ -0,0 +1,259 @@
package pp.monopoly.game.server;
import pp.monopoly.MonopolyConfig;
import pp.monopoly.message.client.*;
import pp.monopoly.message.server.ServerMessage;
import pp.monopoly.message.server.TradeReply;
import pp.monopoly.message.server.TradeRequest;
import pp.monopoly.message.server.ViewAssetsResponse;
import pp.monopoly.model.fields.BoardManager;
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.
*/
public class ServerGameLogic implements ClientInterpreter {
private static final Logger LOGGER = System.getLogger(ServerGameLogic.class.getName());
private final MonopolyConfig config;
private final PlayerHandler playerHandler = new PlayerHandler(this);
private final ServerSender serverSender;
private ServerState state = ServerState.CREATEGAME;
private static final int MAX_PLAYERS = 6;
private BoardManager boardManager = new BoardManager();
/**
* Constructs a ServerGameLogic instance with the specified sender and configuration.
*
* @param serverSender the sender used to send messages to clients
* @param config the game configuration
*/
public ServerGameLogic(ServerSender serverSender, MonopolyConfig config) {
this.serverSender = serverSender;
this.config = config;
}
/**
* Retrieves the current state of the game.
*
* @return the current ServerState
*/
ServerState getState() {
return state;
}
/**
* Sets a new state for the game and logs the state transition.
*
* @param newState the new ServerState to transition to
*/
void setState(ServerState newState) {
LOGGER.log(Level.DEBUG, "State transition {0} --> {1}", state, newState);
state = newState;
}
/**
* Sends a message to a specified player.
*
* @param player the Player to whom the message is sent
* @param msg the ServerMessage to send
*/
void send(Player player, ServerMessage msg) {
if (player != null && msg != null) {
serverSender.send(player.getId(), msg);
LOGGER.log(Level.DEBUG, "Message sent to player {0}: {1}", player.getName(), msg.getClass().getSimpleName());
} else {
LOGGER.log(Level.WARNING, "Attempted to send a null message or to a null 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 player the Player to add to the game
* @return the added Player, or null if the player could not be added
*/
public Player addPlayer(Player player) {
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;
}
/**
* 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.
*
* @param msg the BuyPropertyRequest received from the player
* @param from the connection ID of the player who sent the request
*/
@Override
public void received(BuyPropertyRequest msg, int from) {
Player player = playerHandler.getPlayerById(from);
if (player != null && state == ServerState.INGAME) {
PropertyField property = (PropertyField) boardManager.getFieldAtIndex(player.move(0)); // Assuming player position for property
if (property.getOwner() == null && player.getAccountBalance() >= property.getPrice()) {
player.buyProperty(property);
property.setOwner(player);
player.earnMoney(-property.getPrice());
LOGGER.log(Level.INFO, "Player {0} bought property {1}", player.getName(), property.getName());
} else {
LOGGER.log(Level.WARNING, "Player {0} cannot buy property {1}", player.getName(), property.getName());
}
}
}
/**
* Handles an EndTurn request, ending the player's turn and advancing to the next player.
*
* @param msg the EndTurn message received from the player
* @param from the connection ID of the player who sent the request
*/
@Override
public void received(EndTurn msg, int from) {
Player player = playerHandler.getPlayerById(from);
if (player != null && state == ServerState.INGAME) {
LOGGER.log(Level.DEBUG, "Ending turn for player {0}", player.getName());
playerHandler.nextPlayer();
}
}
/**
* Handles a PlayerReady message, marking the player as ready.
*
* @param msg the PlayerReady message received from the player
* @param from the connection ID of the player who sent the request
*/
@Override
public void received(PlayerReady msg, int from) {
Player player = playerHandler.getPlayerById(from);
if (player != null) {
player.setName(msg.getName());
player.setColor(msg.getColor());
player.setName(msg.getName());
LOGGER.log(Level.DEBUG, "Player {0} is ready", player.getName());
}
}
/**
* Handles a RollDice message, rolling dice for the player and moving them on the board.
*
* @param msg the RollDice message received from the player
* @param from the connection ID of the player who sent the request
*/
@Override
public void received(RollDice msg, int from) {
Player player = playerHandler.getPlayerById(from);
if (player != null && state == ServerState.INGAME) {
send(player, player.rollDice());
}
}
/**
* Handles a TradeOffer message by forwarding the trade offer to the receiving player.
*
* @param msg the TradeOffer message received from the initiating player
* @param from the connection ID of the player who sent the offer
*/
@Override
public void received(TradeOffer msg, int from) {
Player sender = playerHandler.getPlayerById(from);
Player receiver = playerHandler.getPlayerById(msg.getReceiverId());
if (sender != null && receiver != null) {
LOGGER.log(Level.INFO, "Player {0} offers a trade to player {1}", sender.getName(), receiver.getName());
send(playerHandler.getPlayerById(msg.getReceiverId()), new TradeRequest(msg.getReceiverId(), msg.getTradeHandler()));
}
}
/**
* Handles a TradeResponse message by forwarding the response back to the initiating player.
*
* @param msg the TradeResponse message received from the receiving player
* @param from the connection ID of the player who sent the response
*/
@Override
public void received(TradeResponse msg, int from) {
Player responder = playerHandler.getPlayerById(from);
Player initiator = playerHandler.getPlayerById(msg.getInitiatorId());
if (responder != null && initiator != null) {
LOGGER.log(Level.INFO, "Player {0} responded to trade with player {1}", responder.getName(), initiator.getName());
send(initiator, new TradeReply(msg.getInitiatorId(), msg.getTradeHandler()));
}
}
/**
* Handles a ViewAssetsRequest message, sending the player a response containing their assets.
*
* @param msg the ViewAssetsRequest message received from the player
* @param from the connection ID of the player who sent the request
*/
@Override
public void received(ViewAssetsRequest msg, int from) {
Player player = playerHandler.getPlayerById(from);
if (player != null) {
LOGGER.log(Level.DEBUG, "Processing ViewAssetsRequest for player {0}", player.getName());
send(player, new ViewAssetsResponse(player.getProperties(), player.getAccountBalance(), player.getNumJailCard()));
}
}
/**
* Retrieves the board manager, which manages the game board.
*
* @return the BoardManager instance managing the game board
*/
public BoardManager getBoardManager() {
return boardManager;
}
public Player getPlayerById(int id) {
return playerHandler.getPlayerById(id);
}
}

View File

@@ -0,0 +1,23 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.game.server;
import pp.monopoly.message.server.ServerMessage;
/**
* Interface for sending messages to a client.
*/
public interface ServerSender {
/**
* Send the specified message to the client.
*
* @param id the id of the client that shall receive the message
* @param message the message
*/
void send(int id, ServerMessage message);
}

View File

@@ -0,0 +1,33 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.game.server;
/**
* Represents the different states of the Monopoly server during the game lifecycle.
*/
enum ServerState {
/**
* The server is waiting for clients to connect.
*/
CREATEGAME,
/**
* The server is waiting for clients to set up their status to ready
*/
LOBBY,
/**
* The battle of the game where players take turns
*/
INGAME,
/**
* The game has ended because all the players went bankrupt
*/
GAMEOVER
}

View File

@@ -0,0 +1,31 @@
package pp.monopoly.message.client;
/**
* Represents a request from a player to buy a property.
*/
public class BuyPropertyRequest extends ClientMessage{
private int propertyId;
/**
* Constructs a BuyPropertyRequest with the specified property ID.
*
* @param propertyId the ID of the property to buy
*/
public BuyPropertyRequest(int propertyId) {
this.propertyId = propertyId;
}
/**
* Gets the ID of the property to buy.
*
* @return the property ID
*/
public int getPropertyId() {
return propertyId;
}
@Override
public void accept(ClientInterpreter interpreter, int from) {
interpreter.received(this, from);
}
}

View File

@@ -0,0 +1,69 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.message.client;
/**
* Visitor interface for processing all client messages.
*/
public interface ClientInterpreter {
/**
* Processes a received BuyPropertyRequest.
*
* @param msg the BuyPropertyRequest to be processed
* @param from the connection ID from which the message was received
*/
void received(BuyPropertyRequest msg, int from);
/**
* Processes a received EndTurn.
*
* @param msg the EndTurn to be processed
* @param from the connection ID from which the message was received
*/
void received(EndTurn msg, int from);
/**
* Processes a received PlayerReady.
*
* @param msg the PlayerReady to be processed
* @param from the connection ID from which the message was received
*/
void received(PlayerReady msg, int from);
/**
* Processes a received RollDice.
*
* @param msg the RollDice to be processed
* @param from the connection ID from which the message was received
*/
void received(RollDice msg, int from);
/**
* Processes a received TradeOffer.
*
* @param msg the TradeOffer to be processed
* @param from the connection ID from which the message was received
*/
void received(TradeOffer msg, int from);
/**
* Processes a received TradeResponse.
*
* @param msg the TradeResponse to be processed
* @param from the connection ID from which the message was received
*/
void received(TradeResponse msg, int from);
/**
* Processes a received ViewAssetsRequest.
*
* @param msg the ViewAssetsRequest to be processed
* @param from the connection ID from which the message was received
*/
void received(ViewAssetsRequest msg, int from);
}

View File

@@ -0,0 +1,32 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.message.client;
import com.jme3.network.AbstractMessage;
/**
* An abstract base class for client messages used in network transfer.
* It extends the AbstractMessage class provided by the jme3-network library.
*/
public abstract class ClientMessage extends AbstractMessage {
/**
* Constructs a new ClientMessage instance.
*/
protected ClientMessage() {
super(true);
}
/**
* Accepts a visitor for processing this message.
*
* @param interpreter the visitor to be used for processing
* @param from the connection ID of the sender
*/
public abstract void accept(ClientInterpreter interpreter, int from);
}

View File

@@ -0,0 +1,12 @@
package pp.monopoly.message.client;
/**
* Represents a message indicating the player wants to end their turn.
*/
public class EndTurn extends ClientMessage{
@Override
public void accept(ClientInterpreter interpreter, int from) {
interpreter.received(this, from);
}
}

View File

@@ -0,0 +1,52 @@
package pp.monopoly.message.client;
import pp.monopoly.game.server.PlayerColor;
/**
* Represents a message indicating the player is ready to play.
*/
public class PlayerReady extends ClientMessage{
private boolean isReady;
private String name;
private PlayerColor color;
/**
* Constructs a PlayerReady message.
*
* @param isReady true if the player is ready, false otherwise
*/
public PlayerReady(boolean isReady) {
this.isReady = isReady;
}
/**
* Getter for the Name
* @return the Name
*/
public String getName() {
return name;
}
/**
* Getter for the Playercolor
* @return the Playercolor
*/
public PlayerColor getColor() {
return color;
}
/**
* Checks if the player is ready.
*
* @return true if ready, false otherwise
*/
public boolean isReady() {
return isReady;
}
@Override
public void accept(ClientInterpreter interpreter, int from) {
interpreter.received(this, from);
}
}

View File

@@ -0,0 +1,12 @@
package pp.monopoly.message.client;
/**
* Represents a message requesting to roll the dice.
*/
public class RollDice extends ClientMessage{
@Override
public void accept(ClientInterpreter interpreter, int from) {
interpreter.received(this, from);
}
}

View File

@@ -0,0 +1,32 @@
package pp.monopoly.message.client;
import pp.monopoly.model.TradeHandler;
/**
* Represents a trade Request message from one player to another.
*/
public class TradeOffer extends ClientMessage{
private int receiverId;
private TradeHandler tradehandler;
/**
* Constructs a TradeOffer with the specified details.
*
* @param receiverId the ID of the player receiving the Request
* @param tradehandler the tradehandler
*/
public TradeOffer(int receiverId, TradeHandler tradehandler) {
this.receiverId = receiverId;
this.tradehandler = tradehandler;
}
public int getReceiverId() { return receiverId; }
public TradeHandler getTradeHandler() { return tradehandler; }
@Override
public void accept(ClientInterpreter interpreter, int from) {
interpreter.received(this, from);
}
}

View File

@@ -0,0 +1,32 @@
package pp.monopoly.message.client;
import pp.monopoly.model.TradeHandler;
/**
* Represents a response to a trade offer.
*/
public class TradeResponse extends ClientMessage{
private int initiatorId;
private TradeHandler tradeHandler;
/**
* Constructs a TradeResponse with the specified response details.
*
* @param initiatorId the ID of the player who initiated the trade
* @param accepted true if the offer is accepted, false if declined
*/
public TradeResponse(int initiatorId, TradeHandler tradeHandler) {
this.initiatorId = initiatorId;
this.tradeHandler = tradeHandler;
}
public int getInitiatorId() { return initiatorId; }
public TradeHandler getTradeHandler() {
return tradeHandler;
}
@Override
public void accept(ClientInterpreter interpreter, int from) {
interpreter.received(this, from);
}
}

View File

@@ -0,0 +1,12 @@
package pp.monopoly.message.client;
/**
* Represents a request from a player to view their assets.
*/
public class ViewAssetsRequest extends ClientMessage{
@Override
public void accept(ClientInterpreter interpreter, int from) {
interpreter.received(this, from);
}
}

View File

@@ -0,0 +1,39 @@
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) {
interpreter.received(this);
}
@Override
public String getInfoTextKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'");
}
}

View File

@@ -0,0 +1,35 @@
package pp.monopoly.message.server;
import java.util.List;
public class DiceResult extends ServerMessage{
private List<Integer> rollResult;
public DiceResult(List<Integer> rollResult) {
this.rollResult = rollResult;
}
public List<Integer> getRollResult() {
return rollResult;
}
@Override
public void accept(ServerInterpreter interpreter) {
interpreter.received(this);
}
@Override
public String getInfoTextKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'");
}
public boolean isDoublets() {
return rollResult.get(0) == rollResult.get(1);
}
public int calcTotal() {
return rollResult.get(0)+rollResult.get(1);
}
}

View File

@@ -0,0 +1,25 @@
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) {
interpreter.received(this);
}
@Override
public String getInfoTextKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'");
}
public String getCardDescription() {
return cardDescription;
}
}

View File

@@ -0,0 +1,25 @@
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) {
interpreter.received(this);
}
@Override
public String getInfoTextKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'");
}
}

View File

@@ -0,0 +1,16 @@
package pp.monopoly.message.server;
public class GameStart 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'");
}
}

View File

@@ -0,0 +1,26 @@
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);
}
@Override
public String getInfoTextKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'");
}
}

View File

@@ -0,0 +1,40 @@
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);
}
@Override
public String getInfoTextKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'");
}
}

View File

@@ -0,0 +1,92 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.message.server;
/**
* An interface for processing server messages.
* Implementations of this interface can be used to handle different types of server messages.
*/
public interface ServerInterpreter {
/**
* Handles a BuyPropertyResponse message received from the server.
*
* @param msg the BuyPropertyResponse message received
*/
void received(BuyPropertyResponse msg);
/**
* Handles a DiceResult message received from the server.
*
* @param msg the DiceResult message received
*/
void received(DiceResult msg);
/**
* Handles a EventDrawCard message received from the server.
*
* @param msg the EventDrawCard message received
*/
void received(EventDrawCard msg);
/**
* Handles a GameOver message received from the server.
*
* @param msg the GameOver message received
*/
void received(GameOver msg);
/**
* Handles a GameStart message received from the server.
*
* @param msg the GameStart message received
*/
void received(GameStart msg);
/**
* Handles a JailEvent message received from the server.
*
* @param msg the JailEvent message received
*/
void received(JailEvent msg);
/**
* Handles a PlayerStatusUpdate message received from the server.
*
* @param msg the PlayerStatusUpdate message received
*/
void received(PlayerStatusUpdate msg);
/**
* Handles a TimeOutWarning message received from the server.
*
* @param msg the TimeOutWarning message received
*/
void received(TimeOutWarning msg);
/**
* Handles a ViewAssetsResponse message received from the server.
*
* @param msg the ViewAssetsResponse message received
*/
void received(ViewAssetsResponse msg);
/**
* Handles a TradeReply message received from the server.
*
* @param msg the TradeReply message received
*/
void received(TradeReply msg);
/**
* Handles a TradeRequest message received from the server.
*
* @param msg the TradeRequest message received
*/
void received(TradeRequest msg);
}

View File

@@ -0,0 +1,39 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.message.server;
import com.jme3.network.AbstractMessage;
/**
* An abstract base class for server messages used in network transfer.
* It extends the AbstractMessage class provided by the jme3-network library.
*/
public abstract class ServerMessage extends AbstractMessage {
/**
* Constructs a new ServerMessage instance.
*/
protected ServerMessage() {
super(true);
}
/**
* Accepts a visitor for processing this message.
*
* @param interpreter the visitor to be used for processing
*/
public abstract void accept(ServerInterpreter interpreter);
/**
* Gets the bundle key of the informational text to be shown at the client.
* This key is used to retrieve the appropriate localized text for display.
*
* @return the bundle key of the informational text
*/
public abstract String getInfoTextKey();
}

View File

@@ -0,0 +1,26 @@
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);
}
@Override
public String getInfoTextKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'");
}
}

View File

@@ -0,0 +1,39 @@
package pp.monopoly.message.server;
import pp.monopoly.model.TradeHandler;
/**
* Represents a response to a trade offer.
*/
public class TradeReply extends ServerMessage{
private int initiatorId;
private TradeHandler tradeHandler;
/**
* Constructs a TradeResponse with the specified response details.
*
* @param initiatorId the ID of the player who initiated the trade
* @param accepted true if the offer is accepted, false if declined
*/
public TradeReply(int initiatorId, TradeHandler tradeHandler) {
this.initiatorId = initiatorId;
this.tradeHandler = tradeHandler;
}
public int getInitiatorId() { return initiatorId; }
public TradeHandler getTradeHandler() {
return tradeHandler;
}
@Override
public void accept(ServerInterpreter interpreter) {
interpreter.received(this);
}
@Override
public String getInfoTextKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'");
}
}

View File

@@ -0,0 +1,39 @@
package pp.monopoly.message.server;
import pp.monopoly.model.TradeHandler;
/**
* Represents a trade Request message from one player to another.
*/
public class TradeRequest extends ServerMessage{
private int receiverId;
private TradeHandler tradehandler;
/**
* Constructs a TradeRequest with the specified details.
*
* @param receiverId the ID of the player receiving the Request
* @param tradehandler the tradehandler
*/
public TradeRequest(int receiverId, TradeHandler tradehandler) {
this.receiverId = receiverId;
this.tradehandler = tradehandler;
}
public int getReceiverId() { return receiverId; }
public TradeHandler getTradeHandler() { return tradehandler; }
@Override
public void accept(ServerInterpreter interpreter) {
interpreter.received(this);
}
@Override
public String getInfoTextKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'");
}
}

View File

@@ -0,0 +1,50 @@
package pp.monopoly.message.server;
import java.util.List;
import pp.monopoly.model.fields.PropertyField;
/**
* Represents a response containing the player's assets.
*/
public class ViewAssetsResponse extends ServerMessage{
private final List<PropertyField> properties;
private final int accountBalance;
private final int jailCards;
/**
* Constructs a ViewAssetsResponse with the specified properties and account balance.
*
* @param properties a List of PropertyField objects representing the player's properties
* @param accountBalance the player's current account balance
*/
public ViewAssetsResponse(List<PropertyField> properties, int accountBalance, int jailCards) {
this.properties = properties;
this.accountBalance = accountBalance;
this.jailCards = jailCards;
}
@Override
public void accept(ServerInterpreter interpreter) {
interpreter.received(this);
}
@Override
public String getInfoTextKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getInfoTextKey'");
}
public List<PropertyField> getProperties() {
return properties;
}
public int getAccountBalance() {
return accountBalance;
}
public int getJailCards() {
return jailCards;
}
}

View File

@@ -0,0 +1,169 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.model;
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.
* Valid positions on this map have x-coordinates in the range of 0 to width-1 and y-coordinates
* in the range of 0 to height-1.
*
* @see #getWidth()
* @see #getHeight()
*/
public class Board {
/**
* A list of items (figure, house, etc.) placed on the map.
*/
private final List<Item> items = new ArrayList<>();
/**
* The broker responsible for notifying registered listeners of events
* (such as when an item is added or removed from the map).
* Can be null, in which case no notifications will be sent.
*/
private final GameEventBroker eventBroker;
private final int width;
private final int height;
/**
* Constructs an empty map with the given dimensions. The specified event broker
* will handle the notification of changes in the map state, such as adding or removing items.
* Passing null as the event broker is allowed, but in that case, no notifications will occur.
*
* @param width the number of columns (width) of the map
* @param height the number of rows (height) of the map
* @param eventBroker the event broker used for notifying listeners, or null if event distribution is not needed
*/
public Board(int width, int height, GameEventBroker eventBroker) {
if (width < 1 || height < 1)
throw new IllegalArgumentException("Invalid map size");
this.width = width;
this.height = height;
this.eventBroker = eventBroker;
addItem(new Figure(5, 5, 5, Rotation.LEFT));
}
/**
* Adds an item (e.g., a figure or a house) to the map and triggers the appropriate event.
*
* @param item the item to be added to the map
*/
private void addItem(Item item) {
items.add(item);
notifyListeners((GameEvent) new ItemAddedEvent(item, null));
}
/**
* Removes an item from the map and triggers an item removal event.
*
* @param item the item to be removed from the map
*/
public void remove(Item item) {
items.remove(item);
notifyListeners((GameEvent) new ItemRemovedEvent(item, null)); // Falls es ein entsprechendes ItemRemovedEvent gibt
}
/**
* Removes all items from the map and triggers corresponding removal events for each.
*/
public void clear() {
new ArrayList<>(items).forEach(this::remove);
}
/**
* Returns a stream of items of a specified type (class).
*
* @param clazz the class type to filter items by
* @param <T> the type of items to return
* @return a stream of items matching the specified class type
*/
private <T extends Item> Stream<T> getItems(Class<T> clazz) {
return items.stream().filter(clazz::isInstance).map(clazz::cast);
}
/**
* Returns an unmodifiable list of all items currently on the map.
*
* @return an unmodifiable list of all items
*/
public List<Item> getItems() {
return Collections.unmodifiableList(items);
}
/**
* Returns the width (number of columns) of the map.
*
* @return the width of the map
*/
public int getWidth() {
return width;
}
/**
* Returns the height (number of rows) of the map.
*
* @return the height of the map
*/
public int getHeight() {
return height;
}
/**
* Validates whether the specified position is within the map boundaries.
*
* @param pos the position to validate
* @return true if the position is within the map, false otherwise
*/
public boolean isValid(IntPosition pos) {
return isValid(pos.getX(), pos.getY());
}
/**
* Checks if the specified coordinates are within the map boundaries.
*
* @param x the x-coordinate to validate
* @param y the y-coordinate to validate
* @return true if the coordinates are valid, false otherwise
*/
public boolean isValid(int x, int y) {
return x >= 0 && x < width &&
y >= 0 && y < height;
}
/**
* Returns a string representation of the map.
*
* @return a string representation of the map
*/
@Override
public String toString() {
return "Board:{" + items + '}'; //NON-NLS
}
/**
* Notifies all registered listeners about a game event if the event broker is available.
*
* @param event the event to be distributed to listeners
*/
private void notifyListeners(GameEvent event) {
if (eventBroker != null)
eventBroker.notifyListeners(event);
}
}

View File

@@ -0,0 +1,7 @@
package pp.monopoly.model;
import pp.monopoly.model.card.Card;
public interface CardVisitor<T> {
T visit(Card c);
}

View File

@@ -0,0 +1,23 @@
package pp.monopoly.model;
import pp.monopoly.model.fields.BuildingProperty;
import pp.monopoly.model.fields.EventField;
import pp.monopoly.model.fields.FineField;
import pp.monopoly.model.fields.FoodField;
import pp.monopoly.model.fields.GateField;
import pp.monopoly.model.fields.GoField;
import pp.monopoly.model.fields.GulagField;
import pp.monopoly.model.fields.TestStreckeField;
import pp.monopoly.model.fields.WacheField;
public interface FieldVisitor<T> {
T visit(BuildingProperty field);
T visit(FoodField field);
T visit(GateField field);
T visit(GulagField field);
T visit(TestStreckeField field);
T visit(EventField field);
T visit(WacheField field);
T visit(GoField field);
T visit(FineField field);
}

View File

@@ -0,0 +1,309 @@
package pp.monopoly.model;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
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) {
visitor.visit(this);
}
}

View File

@@ -0,0 +1,94 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.model;
import java.util.Objects;
import com.jme3.network.serializing.Serializable;
/**
* Represents a point in the two-dimensional plane with integer coordinates.
*/
@Serializable
public final class IntPoint implements IntPosition {
private int x;
private int y;
/**
* Default constructor for serialization purposes.
*/
private IntPoint() { /* do nothing */ }
/**
* Constructs a new IntPoint with the specified coordinates.
*
* @param x the x-coordinate of the point
* @param y the y-coordinate of the point
*/
public IntPoint(int x, int y) {
this.x = x;
this.y = y;
}
/**
* Gets the x-coordinate of the point.
*
* @return the x-coordinate
*/
@Override
public int getX() {
return x;
}
/**
* Gets the y-coordinate of the point.
*
* @return the y-coordinate
*/
@Override
public int getY() {
return y;
}
/**
* Indicates whether some other object is "equal to" this one.
*
* @param obj the reference object with which to compare
* @return true if this object is the same as the obj argument; false otherwise
*/
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (IntPoint) obj;
return this.x == that.x &&
this.y == that.y;
}
/**
* Returns a hash code value for the IntPoint.
*
* @return a hash code value for this object
*/
@Override
public int hashCode() {
return Objects.hash(x, y);
}
/**
* Returns a string representation of the IntPoint.
*
* @return a string representation of the object
*/
@Override
public String toString() {
return "IntPoint[" + //NON-NLS
"x=" + x + ", " + //NON-NLS
"y=" + y + ']'; //NON-NLS
}
}

View File

@@ -0,0 +1,28 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.model;
/**
* Interface representing a position with X and Y coordinates.
*/
public interface IntPosition {
/**
* Returns the X coordinate of this position.
*
* @return the X coordinate.
*/
int getX();
/**
* Returns the Y coordinate of this position.
*
* @return the Y coordinate.
*/
int getY();
}

View File

@@ -0,0 +1,31 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.model;
/**
* An interface representing any item on a board
* It extends the IntPosition interface to provide position information.
*/
public interface Item {
/**
* Accepts a visitor to perform operations on the item.
*
* @param visitor the visitor performing operations on the item
* @param <T> the type of result returned by the visitor
* @return the result of the visitor's operation on the item
*/
<T> T accept(Visitor<T> visitor);
/**
* Accepts a visitor to perform operations on the item without returning a result.
*
* @param visitor the visitor performing operations on the item
*/
void accept(VoidVisitor visitor);
}

View File

@@ -0,0 +1,67 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.model;
import java.io.Serializable;
/**
* Represents the rotation of a Item and provides functionality related to rotation.
*/
public enum Rotation implements Serializable {
/**
* Represents the item facing upwards.
*/
UP,
/**
* Represents the item facing rightwards.
*/
RIGHT,
/**
* Represents the item facing downwards.
*/
DOWN,
/**
* Represents the item facing leftwards.
*/
LEFT;
/**
* Gets the change in x-coordinate corresponding to this rotation.
*
* @return the change in x-coordinate
*/
public int dx() {
return switch (this) {
case UP, DOWN -> 0;
case RIGHT -> 1;
case LEFT -> -1;
};
}
/**
* Gets the change in y-coordinate corresponding to this rotation.
*
* @return the change in y-coordinate
*/
public int dy() {
return switch (this) {
case UP -> 1;
case LEFT, RIGHT -> 0;
case DOWN -> -1;
};
}
/**
* Rotates the orientation clockwise and returns the next rotation.
*
* @return the next rotation after rotating clockwise
*/
public Rotation rotate() {
return values()[(ordinal() + 1) % values().length];
}
}

View File

@@ -0,0 +1,183 @@
package pp.monopoly.model;
import pp.monopoly.game.server.Player;
import pp.monopoly.model.fields.PropertyField;
import java.util.List;
/**
* Helper class that handles the trade logic between two players.
* Manages trade initiation, validation, acceptance, and rejection involving multiple properties, money, and jail cards.
*/
public class TradeHandler {
/**
* Initiates a trade offer between two players involving properties, money, and jail cards.
*
* @param sender the Player who is initiating the trade
* @param receiver the Player who is the target of the trade offer
* @param offeredAmount the amount of money the sender offers
* @param offeredProperties the list of properties the sender offers
* @param offeredJailCards the number of jail cards the sender offers
* @param requestedAmount the amount of money the sender requests from the receiver
* @param requestedProperties the list of properties the sender requests from the receiver
* @param requestedJailCards the number of jail cards the sender requests from the receiver
* @return true if the trade offer is valid and initiated, false otherwise
*/
public boolean initiateTrade(Player sender, Player receiver, int offeredAmount, List<PropertyField> offeredProperties,
int offeredJailCards, int requestedAmount, List<PropertyField> requestedProperties, int requestedJailCards) {
// Validate the trade offer
if (!validateTrade(sender, offeredAmount, offeredProperties, offeredJailCards, receiver, requestedAmount, requestedProperties, requestedJailCards)) {
System.out.println("Trade offer is invalid.");
return false;
}
// Notify the receiver about the trade offer (this would be an actual message in a real implementation)
System.out.println("Trade offer initiated by " + sender.getName() + " to " + receiver.getName());
return true;
}
/**
* Accepts the trade offer and completes the trade between two players.
*
* @param sender the Player who initiated the trade
* @param receiver the Player who accepted the trade
* @param offeredAmount the amount of money to transfer from the sender to the receiver
* @param offeredProperties the list of properties to transfer from the sender to the receiver
* @param offeredJailCards the number of jail cards to transfer from the sender to the receiver
* @param requestedAmount the amount of money to transfer from the receiver to the sender
* @param requestedProperties the list of properties to transfer from the receiver to the sender
* @param requestedJailCards the number of jail cards to transfer from the receiver to the sender
*/
public void acceptTrade(Player sender, Player receiver, int offeredAmount, List<PropertyField> offeredProperties,
int offeredJailCards, int requestedAmount, List<PropertyField> requestedProperties, int requestedJailCards) {
// Transfer money
sender.earnMoney(-offeredAmount); // Deduct money from the sender
receiver.earnMoney(offeredAmount); // Add money to the receiver
receiver.earnMoney(-requestedAmount); // Deduct money from the receiver
sender.earnMoney(requestedAmount); // Add money to the sender
// Transfer ownership of the properties from sender to receiver
if (offeredProperties != null) {
for (PropertyField property : offeredProperties) {
transferProperty(sender, receiver, property);
}
}
// Transfer ownership of the properties from receiver to sender
if (requestedProperties != null) {
for (PropertyField property : requestedProperties) {
transferProperty(receiver, sender, property);
}
}
// Transfer jail cards
transferJailCards(sender, receiver, offeredJailCards);
transferJailCards(receiver, sender, requestedJailCards);
System.out.println("Trade accepted. " + sender.getName() + " and " + receiver.getName() + " completed the trade.");
}
/**
* Rejects the trade offer.
*
* @param receiver the Player who is rejecting the trade
*/
public void rejectTrade(Player receiver) {
System.out.println("Trade rejected by " + receiver.getName());
}
/**
* Validates a trade offer by checking if the sender and receiver own the properties involved,
* have sufficient funds for the money involved in the trade, and have enough jail cards.
*
* @param sender the Player initiating the trade
* @param offeredAmount the amount of money the sender is offering
* @param offeredProperties the list of properties the sender is offering
* @param offeredJailCards the number of jail cards the sender is offering
* @param receiver the Player receiving the trade offer
* @param requestedAmount the amount of money the sender is requesting
* @param requestedProperties the list of properties the sender is requesting from the receiver
* @param requestedJailCards the number of jail cards the sender is requesting from the receiver
* @return true if the trade offer is valid, false otherwise
*/
private boolean validateTrade(Player sender, int offeredAmount, List<PropertyField> offeredProperties, int offeredJailCards,
Player receiver, int requestedAmount, List<PropertyField> requestedProperties, int requestedJailCards) {
// Check if sender has enough money to offer
if (sender.getAccountBalance() < offeredAmount) {
System.out.println("Sender does not have enough balance to make this offer.");
return false;
}
// Check if receiver has enough money to offer
if (receiver.getAccountBalance() < requestedAmount) {
System.out.println("Receiver does not have enough balance to fulfill requested amount.");
return false;
}
// Check if sender owns all the offered properties
if (offeredProperties != null) {
for (PropertyField property : offeredProperties) {
if (!sender.getProperties().contains(property)) {
System.out.println("Sender does not own the property " + property.getName() + " being offered.");
return false;
}
}
}
// Check if receiver owns all the requested properties
if (requestedProperties != null) {
for (PropertyField property : requestedProperties) {
if (!receiver.getProperties().contains(property)) {
System.out.println("Receiver does not own the property " + property.getName() + " requested.");
return false;
}
}
}
// Check if sender has enough jail cards to offer
if (sender.getNumJailCard() < offeredJailCards) {
System.out.println("Sender does not have enough jail cards to offer.");
return false;
}
// Check if receiver has enough jail cards to fulfill the request
if (receiver.getNumJailCard() < requestedJailCards) {
System.out.println("Receiver does not have enough jail cards to fulfill the request.");
return false;
}
return true;
}
/**
* Transfers a property from one player to another.
*
* @param from the Player transferring the property
* @param to the Player receiving the property
* @param property the PropertyField being transferred
*/
private void transferProperty(Player from, Player to, PropertyField property) {
from.sellProperty(property);
to.buyProperty(property);
property.setOwner(to); // Update the property's owner
System.out.println("Property " + property.getName() + " transferred from " + from.getName() + " to " + to.getName());
}
/**
* Transfers jail cards between players.
*
* @param from the Player transferring jail cards
* @param to the Player receiving jail cards
* @param numCards the number of jail cards to transfer
*/
private void transferJailCards(Player from, Player to, int numCards) {
for (int i = 0; i < numCards; i++) {
from.removeJailCard();
to.addJailCard();
}
System.out.println("Transferred " + numCards + " jail card(s) from " + from.getName() + " to " + to.getName());
}
}

View File

@@ -0,0 +1,25 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.model;
/**
* An interface for implementing the Visitor pattern for different types of elements in the Monopoly model.
*
* @param <T> the type of result returned by the visit methods
*/
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);
}

View File

@@ -0,0 +1,22 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.model;
/**
* An interface for implementing the Visitor pattern for different types of elements in the Monopoly model
* without returning any result.
*/
public interface VoidVisitor {
/**
* Visits a Figure element.
*
* @param figure the Figure element to visit
*/
void visit(Figure figure);
}

View File

@@ -0,0 +1,23 @@
package pp.monopoly.model.card;
public class Card {
private final String description;
private final String keyword;
Card(String description, String keyword) {
this.description = description;
this.keyword = keyword;
}
public void accept(DeckHelper visitor) {
visitor.visit(this);
}
public String getDescription() {
return description;
}
String getKeyword() {
return keyword;
}
}

View File

@@ -0,0 +1,34 @@
package pp.monopoly.model.card;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import pp.monopoly.model.CardVisitor;
public class DeckHelper implements CardVisitor<Void>{
private static Queue<Card> cards;
private DeckHelper() {
}
@Override
public Void visit(Card c) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'visit'");
}
private void shuffle() {
List<Card> cardList = new ArrayList<>(cards);
Collections.shuffle(cardList);
cards.clear();
cards.addAll(cardList);
}
public static Card drawCard() {
return cards != null ? cards.poll() : null;
}
}

View File

@@ -0,0 +1,91 @@
package pp.monopoly.model.fields;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
/**
* Simple Manager class responsible for managing the GameBoard of Monopoly
*/
public class BoardManager {
private List<Field> board;
/**
* Constructs a BoardManager
*/
public BoardManager() {
board = createBoard();
}
/**
* Creates a Monopoly GameBoard
* @return the List of Fields in correct Order
*/
private static List<Field> createBoard() {
ArrayList<Field> fields = new ArrayList<>();
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;
}
/**
* Method to find the Field at specific index
* @param index the index for which to find the field
* @return the field at the index
*/
public Field getFieldAtIndex(int index) {
return board.get(index);
}
/**
* Method to find the index of a Monopoly field
* @param field the Field to get the Index of
* @return the Index of the field
*/
public int getIndexOfField(Field field) {
if (board.contains(field)) return field.getId();
else throw new NoSuchElementException();
}
}

View File

@@ -0,0 +1,70 @@
package pp.monopoly.model.fields;
import pp.monopoly.game.server.Player;
public class BuildingProperty extends PropertyField {
private int houses;
private boolean hotel = false;
BuildingProperty(String name, int id, int price, int rent) {
super(name, id, price, rent);
}
@Override
public int calcRent() {
if (hotel) {
return (int) Math.round(rent*70/10)*10;
}
switch (houses) {
case 1:
return (int) Math.round(rent*5/10)*10;
case 2:
return (int) Math.round(rent*15/10)*10;
case 3:
return (int) Math.round(rent*40/10)*10;
case 4:
return (int) Math.round(rent*55/10)*10;
default:
return rent;
}
}
public boolean buildHouse() {
if (houses < 4) {
houses++;
return true;
}
return false;
}
public boolean buildHotel() {
if (hotel) {
return false;
}
hotel = true;
return true;
}
public boolean removeHouse() {
if (houses == 0) {
return false;
}
houses--;
return true;
}
public boolean removeHotel() {
if (!hotel) {
return false;
}
hotel = false;
return true;
}
@Override
public void accept(Player player) {
player.visit(this);
}
}

View File

@@ -0,0 +1,23 @@
package pp.monopoly.model.fields;
import pp.monopoly.game.server.Player;
import pp.monopoly.model.card.Card;
import pp.monopoly.model.card.DeckHelper;
public class EventField extends Field{
public EventField(String name, int id) {
super(name, id);
}
@Override
public void accept(Player player) {
player.visit(this);
}
public Card drawCard() {
return DeckHelper.drawCard();
}
}

View File

@@ -0,0 +1,23 @@
package pp.monopoly.model.fields;
import pp.monopoly.game.server.Player;
abstract class Field {
protected final String name;
protected final int id;
protected Field(String name, int id) {
this.name = name;
this.id= id;
}
public abstract void accept(Player player);
public int getId() {
return id;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,23 @@
package pp.monopoly.model.fields;
import pp.monopoly.game.server.Player;
public class FineField extends Field{
private final int fine;
FineField(String name, int id, int fine) {
super(name, id);
this.fine = fine;
}
public int getFine() {
return fine;
}
@Override
public void accept(Player player) {
player.visit(this);
}
}

View File

@@ -0,0 +1,20 @@
package pp.monopoly.model.fields;
import pp.monopoly.game.server.Player;
public class FoodField extends PropertyField {
public FoodField(String name, int id) {
super(name, id, 1500,0);
}
@Override
public int calcRent() {
return 0;
}
@Override
public void accept(Player player) {
player.visit(this);
}
}

View File

@@ -0,0 +1,21 @@
package pp.monopoly.model.fields;
import pp.monopoly.game.server.Player;
public class GateField extends PropertyField{
GateField(String name, int id) {
super(name, id, 2000, 25);
}
@Override
public int calcRent() {
return rent;
}
@Override
public void accept(Player player) {
player.visit(this);
}
}

View File

@@ -0,0 +1,15 @@
package pp.monopoly.model.fields;
import pp.monopoly.game.server.Player;
public class GoField extends Field{
public GoField() {
super("Monatsgehalt", 0);
}
@Override
public void accept(Player player) {
player.visit(this);
}
}

View File

@@ -0,0 +1,18 @@
package pp.monopoly.model.fields;
import pp.monopoly.game.server.Player;
public class GulagField extends Field{
private int bailCost = 500;
GulagField() {
super("Gulag", 10);
}
@Override
public void accept(Player player) {
player.visit(this);
}
}

View File

@@ -0,0 +1,92 @@
package pp.monopoly.model.fields;
import pp.monopoly.game.server.Player;
/**
* Represents an abstract property field in the Monopoly game.
* Contains attributes related to ownership, price, rent, and mortgage status.
*/
public abstract class PropertyField extends Field {
private final int price;
protected final int rent;
private Player owner;
private boolean mortgaged = false;
/**
* Constructs a PropertyField with the specified name, ID, price, and rent.
*
* @param name the name of the property
* @param id the unique identifier for the property
* @param price the purchase price of the property
* @param rent the base rent for the property
*/
protected PropertyField(String name, int id, int price, int rent) {
super(name, id);
this.price = price;
this.rent = rent;
}
/**
* Calculates the rent for this property.
* The calculation may depend on various factors specific to property type.
*
* @return the calculated rent for this property
*/
public abstract int calcRent();
/**
* Gets the purchase price of the property.
*
* @return the price of the property
*/
public int getPrice() {
return price;
}
/**
* Gets the mortgage value (hypothecary value) of the property.
* Typically, this is half of the property price.
*
* @return the mortgage value of the property
*/
public int getHypo() {
return price / 2;
}
/**
* Gets the owner of the property.
*
* @return the Player who owns the property, or null if the property is unowned
*/
public Player getOwner() {
return owner;
}
/**
* Sets the owner of the property.
*
* @param player the Player who will own this property
*/
public void setOwner(Player player) {
owner = player;
}
/**
* Checks if the property is currently mortgaged.
*
* @return true if the property is mortgaged, false otherwise
*/
public boolean isMortgaged() {
return mortgaged;
}
/**
* Sets the mortgage status of the property.
*
* @param mortgaged true to mark the property as mortgaged, false to unmark it
*/
public void setMortgaged(boolean mortgaged) {
this.mortgaged = mortgaged;
}
}

View File

@@ -0,0 +1,24 @@
package pp.monopoly.model.fields;
import pp.monopoly.game.server.Player;
public class TestStreckeField extends Field{
private int money;
TestStreckeField() {
super("Teststrecke", 20);
}
@Override
public void accept(Player player) {
player.visit(this);
}
public void addMoney(int amount) {
money += amount;
}
public int collectMoney() {
return money = 0;
}
}

View File

@@ -0,0 +1,16 @@
package pp.monopoly.model.fields;
import pp.monopoly.game.server.Player;
public class WacheField extends Field{
public WacheField() {
super("Wache", 30);
}
@Override
public void accept(Player player) {
player.visit(this);
}
}

View File

@@ -0,0 +1,23 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.notification;
/**
* Event when an item is added to a map.
*/
public record ClientStateEvent() 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);
}
}

View File

@@ -0,0 +1,20 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.notification;
/**
* An interface used for all game events.
*/
public interface GameEvent {
/**
* Notifies the game event listener of the event.
*
* @param listener the game event listener to be notified
*/
void notifyListener(GameEventListener listener);
}

View File

@@ -0,0 +1,20 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.notification;
/**
* Defines a broker for distributing game events to registered listeners.
*/
public interface GameEventBroker {
/**
* Notifies all registered listeners about the specified game event.
*
* @param event the game event to be broadcast to listeners
*/
void notifyListeners(GameEvent event);
}

View File

@@ -0,0 +1,48 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.notification;
/**
* Listener interface for all events implemented by subclasses of {@linkplain pp.monopoly.notification.GameEvent}.
*/
public interface GameEventListener {
/**
* Indicates that an item has been destroyed
*
* @param event the received event
*/
default void receivedEvent(ItemRemovedEvent event) { /* do nothing */ }
/**
* Indicates that an item has been added to a map.
*
* @param event the received event
*/
default void receivedEvent(ItemAddedEvent event) { /* do nothing */ }
/**
* Indicates that an info text shall be shown.
*
* @param event the received event
*/
default void receivedEvent(InfoTextEvent event) { /* do nothing */ }
/**
* Indicates that a sound shall be played.
*
* @param event the received event
*/
default void receivedEvent(SoundEvent event) { /* do nothing */ }
/**
* Indicates that the client's state has changed.
*
* @param event the received event
*/
default void receivedEvent(ClientStateEvent event) { /* do nothing */ }
}

View File

@@ -0,0 +1,25 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.notification;
/**
* Event when an item is added to a map.
*
* @param key the bundle key for the message
*/
public record InfoTextEvent(String key) 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);
}
}

View File

@@ -0,0 +1,41 @@
package pp.monopoly.notification;
import pp.monopoly.model.Board;
import pp.monopoly.model.Item;
/**
* Event that is triggered when an item is added to a board.
*/
public class ItemAddedEvent {
private final Item item;
private final Board board;
/**
* Constructs a new ItemAddedEvent.
*
* @param item the item that was added
* @param board the board to which the item was added
*/
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;
}
}

View File

@@ -0,0 +1,36 @@
////////////////////////////////////////
// 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.Board;
import pp.monopoly.model.Item;
/**
* Event when an item gets removed.
*
* @param item the destroyed item
*/
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;
}
}

View File

@@ -0,0 +1,61 @@
package pp.monopoly.notification;
/**
* 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;
}

View File

@@ -0,0 +1,26 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.monopoly.notification;
/**
* Event when an item is added to a map.
*
* @param sound the sound to be played
*/
public record SoundEvent(Sound sound) 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);
}
}

View File

@@ -0,0 +1,43 @@
########################################
## Programming project code
## UniBw M, 2022, 2023, 2024
## www.unibw.de/inf2
## (c) Mark Minas (mark.minas@unibw.de)
########################################
#
monopoly.name=Monopoly
button.play=Start Game
button.ready=Ready
button.rotate=Rotate
server.connection.failed=Failed to establish a server connection.
its.your.turn=It's your turn! Click on the opponent's field to shoot...
lost.connection.to.server=Lost connection to server. The game terminated.
place.ships.in.your.map=Place ships in your map.
wait.for.an.opponent=Wait for an opponent!
wait.for.opponent=Wait for your opponent!
confirm.leaving=Would you really like to leave the game?
you.lost.the.game=You lost the game!
you.won.the.game=You won the game!
button.yes=Yes
button.no=No
button.ok=Ok
button.connect=Connect
button.cancel=Cancel
server.dialog=Server
host.name=Host
port.number=Port
wait.its.not.your.turn=Wait, it's not your turn!!
menu.quit=Quit game
menu.return-to-game=Return to game
menu.sound-enabled=Sound switched on
menu.background-sound-enabled=Background music switched on
menu.map.load=Load map from file...
menu.map.save=Save map in file...
label.file=File:
label.connecting=Connecting...
dialog.error=Error
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

View File

@@ -0,0 +1,42 @@
########################################
## Programming project code
## UniBw M, 2022, 2023, 2024
## www.unibw.de/inf2
## (c) Mark Minas (mark.minas@unibw.de)
########################################
#
monopoly.name=Monopoly
button.play=Start Game
button.ready=Ready
button.rotate=Rotate
server.connection.failed=Failed to establish a server connection.
its.your.turn=It's your turn! Click on the opponent's field to shoot...
lost.connection.to.server=Lost connection to server. The game terminated.
place.ships.in.your.map=Place ships in your map.
wait.for.an.opponent=Wait for an opponent!
wait.for.opponent=Wait for your opponent!
confirm.leaving=Would you really like to leave the game?
you.lost.the.game=You lost the game!
you.won.the.game=You won the game!
button.yes=Yes
button.no=No
button.ok=Ok
button.connect=Connect
button.cancel=Cancel
server.dialog=Server
host.name=Host
port.number=Port
wait.its.not.your.turn=Wait, it's not your turn!!
menu.quit=Quit game
menu.return-to-game=Return to game
menu.sound-enabled=Sound switched on
menu.background-sound-enabled=Background music switched on
menu.map.load=Load map from file...
menu.map.save=Save map in file...
label.file=File:
label.connecting=Connecting...
dialog.error=Error
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

View File

@@ -0,0 +1,387 @@
/*
package pp.monopoly;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
public class Testhandbuch {
// T001 UC-game-01 - testStartApplication
@Test
public void testStartApplication() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
MainMenu mainMenu = app.getStateManager().getState(MainMenu.class);
assertNotNull(mainMenu);
}
// T002 UC-game-02 - testOpenStartMenu
@Test
public void testOpenStartMenu() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
MainMenu mainMenu = app.getStateManager().getState(MainMenu.class);
mainMenu.showMenu();
assertTrue(mainMenu.isMenuVisible());
}
// T003 UC-game-03 - testNavigateToPlayOption
@Test
public void testNavigateToPlayOption() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
MainMenu mainMenu = app.getStateManager().getState(MainMenu.class);
mainMenu.showMenu();
mainMenu.startNewGame();
NewGameMenu newGameMenu = app.getStateManager().getState(NewGameMenu.class);
assertNotNull(newGameMenu);
}
// T004 UC-game-04 - testExitApplicationFromMenu
@Test
public void testExitApplicationFromMenu() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
MainMenu mainMenu = app.getStateManager().getState(MainMenu.class);
mainMenu.showMenu();
mainMenu.exitGame();
assertTrue(app.isClosed());
}
// T005 UC-game-05 - testOpenSettingsFromMenu
@Test
public void testOpenSettingsFromMenu() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
MainMenu mainMenu = app.getStateManager().getState(MainMenu.class);
mainMenu.showMenu();
mainMenu.openSettings();
SettingMenu settingMenu = app.getStateManager().getState(SettingMenu.class);
assertNotNull(settingMenu);
}
// T006 UC-game-06 - testOpenGameMenuWithESC
@Test
public void testOpenGameMenuWithESC() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
app.simpleUpdate(0.1f);
GameMenu gameMenu = app.getStateManager().getState(GameMenu.class);
assertTrue(gameMenu.isVisible());
}
// T007 UC-game-07 - testEnterHostName
@Test
public void testEnterHostName() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
NewGameMenu newGameMenu = app.getStateManager().getState(NewGameMenu.class);
newGameMenu.showMenu();
newGameMenu.enterHostName("localhost");
assertEquals("localhost", newGameMenu.getHostName());
}
// T008 UC-game-07 - testEnterPortNumber
@Test
public void testEnterPortNumber() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
NewGameMenu newGameMenu = app.getStateManager().getState(NewGameMenu.class);
newGameMenu.showMenu();
newGameMenu.enterPortNumber(12345);
assertEquals(12345, newGameMenu.getPortNumber());
}
// T009 UC-game-07 - testCancelGameCreation
@Test
public void testCancelGameCreation() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
NewGameMenu newGameMenu = app.getStateManager().getState(NewGameMenu.class);
newGameMenu.showMenu();
newGameMenu.cancel();
MainMenu mainMenu = app.getStateManager().getState(MainMenu.class);
assertTrue(mainMenu.isMenuVisible());
}
// T010 UC-game-08 - testEnterPlayerLobby
@Test
public void testEnterPlayerLobby() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
app.getStateManager().getState(NetworkDialog.class).connect();
Lobby lobby = app.getStateManager().getState(Lobby.class);
assertNotNull(lobby);
}
// T011 UC-game-09 - testEnterStartingCapital
@Test
public void testEnterStartingCapital() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
NewGameMenu newGameMenu = app.getStateManager().getState(NewGameMenu.class);
newGameMenu.showMenu();
newGameMenu.enterStartingCapital(1500);
assertEquals(1500, newGameMenu.getStartingCapital());
}
// T012 UC-game-09 - testIncreaseStartingCapital
@Test
public void testIncreaseStartingCapital() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
NewGameMenu newGameMenu = app.getStateManager().getState(NewGameMenu.class);
newGameMenu.showMenu();
newGameMenu.enterStartingCapital(1500);
newGameMenu.increaseStartingCapital(100);
assertEquals(1600, newGameMenu.getStartingCapital());
}
// T013 UC-game-09 - testDecreaseStartingCapital
@Test
public void testDecreaseStartingCapital() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
NewGameMenu newGameMenu = app.getStateManager().getState(NewGameMenu.class);
newGameMenu.showMenu();
newGameMenu.enterStartingCapital(1500);
newGameMenu.decreaseStartingCapital(100);
assertEquals(1400, newGameMenu.getStartingCapital());
}
// T014 UC-game-10 - testDefaultPlayerName
@Test
public void testDefaultPlayerName() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
app.getStateManager().getState(Lobby.class).initializePlayerNames();
assertEquals("Spieler 1", app.getStateManager().getState(Lobby.class).getPlayerName(0));
assertEquals("Spieler 2", app.getStateManager().getState(Lobby.class).getPlayerName(1));
}
// T015 UC-game-11 - testEnterDisplayName
@Test
public void testEnterDisplayName() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Lobby lobby = app.getStateManager().getState(Lobby.class);
lobby.enterDisplayName("TestPlayer");
assertEquals("TestPlayer", lobby.getPlayerName(0));
}
// T016 UC-game-11 - testDuplicateNameEntry
@Test
public void testDuplicateNameEntry() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Lobby lobby = app.getStateManager().getState(Lobby.class);
lobby.enterDisplayName("Player1");
assertTrue(lobby.isDuplicateName("Player1"));
}
// T017 UC-game-12 - testSelectPlayerColor
@Test
public void testSelectPlayerColor() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Lobby lobby = app.getStateManager().getState(Lobby.class);
lobby.selectColor("Red");
assertEquals("Red", lobby.getPlayerColor(0));
}
// T018 UC-game-12 - testSelectOccupiedColor
@Test
public void testSelectOccupiedColor() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Lobby lobby = app.getStateManager().getState(Lobby.class);
lobby.selectColor("Red");
assertTrue(lobby.isColorOccupied("Red"));
}
// T019 UC-game-13 - testSelectPlayerToken
@Test
public void testSelectPlayerToken() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Lobby lobby = app.getStateManager().getState(Lobby.class);
lobby.selectToken("Ship");
assertEquals("Ship", lobby.getPlayerToken(0));
}
// T020 UC-game-13 - testSelectOccupiedToken
@Test
public void testSelectOccupiedToken() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Lobby lobby = app.getStateManager().getState(Lobby.class);
lobby.selectToken("Ship");
assertTrue(lobby.isTokenOccupied("Ship"));
}
// T021 UC-game-14 - testCancelPlayerLobby
@Test
public void testCancelPlayerLobby() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Lobby lobby = app.getStateManager().getState(Lobby.class);
lobby.cancel();
MainMenu mainMenu = app.getStateManager().getState(MainMenu.class);
assertTrue(mainMenu.isMenuVisible());
}
// T022 UC-game-15 - testOpenLobbyMenuWithESC
@Test
public void testOpenLobbyMenuWithESC() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
app.simpleUpdate(0.1f);
LobbyMenu lobbyMenu = app.getStateManager().getState(LobbyMenu.class);
assertTrue(lobbyMenu.isVisible());
}
// T023 UC-game-16 - testPlayerReadyConfirmation
@Test
public void testPlayerReadyConfirmation() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Lobby lobby = app.getStateManager().getState(Lobby.class);
lobby.setPlayerReady(true);
assertTrue(lobby.isPlayerReady(0));
}
// T024 UC-game-17 - testStartGame
@Test
public void testStartGame() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Lobby lobby = app.getStateManager().getState(Lobby.class);
lobby.startGame();
assertEquals(GameState.InGame, lobby.getGameState());
}
// T025 UC-game-18 - testPlayerMovement
@Test
public void testPlayerMovement() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Game game = app.getStateManager().getState(Game.class);
Player player = game.getPlayer(0);
player.move(5);
assertEquals(5, player.getPosition());
}
// T026 UC-game-19 - testPurchaseProperty
@Test
public void testPurchaseProperty() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Game game = app.getStateManager().getState(Game.class);
Player player = game.getPlayer(0);
Property property = game.getProperty(0);
player.buyProperty(property);
assertTrue(player.getProperties().contains(property));
}
// T027 UC-game-20 - testMovePlayerOnDiceRoll
@Test
public void testMovePlayerOnDiceRoll() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Game game = app.getStateManager().getState(Game.class);
Player player = game.getPlayer(0);
int initialPosition = player.getPosition();
player.rollDice();
assertTrue(player.getPosition() > initialPosition);
}
// T028 UC-game-21 - testPassGo
@Test
public void testPassGo() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Game game = app.getStateManager().getState(Game.class);
Player player = game.getPlayer(0);
player.move(40); // Assuming 40 steps moves player to Go
assertTrue(player.passedGo());
}
// T029 UC-game-22 - testCollectMoneyFromGo
@Test
public void testCollectMoneyFromGo() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Game game = app.getStateManager().getState(Game.class);
Player player = game.getPlayer(0);
int initialBalance = player.getBalance();
player.move(40); // Move to Go
assertTrue(player.getBalance() > initialBalance);
}
// T030 UC-game-23 - testPayRent
@Test
public void testPayRent() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Game game = app.getStateManager().getState(Game.class);
Player player = game.getPlayer(0);
PropertyField property = (PropertyField) game.getField(1);
player.move(1);
int initialBalance = player.getBalance();
player.payRent(property);
assertTrue(player.getBalance() < initialBalance);
}
// T031 UC-game-24 - testDeclareBankruptcy
@Test
public void testDeclareBankruptcy() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Game game = app.getStateManager().getState(Game.class);
Player player = game.getPlayer(0);
player.declareBankruptcy();
assertEquals(PlayerState.Bankrupt, player.getState());
}
// T032 UC-game-25 - testTradeProperty
@Test
public void testTradeProperty() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Game game = app.getStateManager().getState(Game.class);
Player player1 = game.getPlayer(0);
Player player2 = game.getPlayer(1);
Property property = game.getProperty(0);
player1.offerTrade(player2, property);
assertTrue(player2.hasProperty(property));
}
// T033 UC-game-26 - testGameOverCondition
@Test
public void testGameOverCondition() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Game game = app.getStateManager().getState(Game.class);
Player player = game.getPlayer(0);
player.declareBankruptcy();
assertTrue(game.isGameOver());
}
// T034 UC-game-27 - testPlayerInJail
@Test
public void testPlayerInJail() {
MonopolyApp app = new MonopolyApp();
app.simpleInitApp();
Game game = app.getStateManager().getState(Game.class);
Player player = game.getPlayer(0);
game.sendToJail(player);
assertEquals(PlayerState.InJail, player.getState());
}
}
*/

View File

@@ -0,0 +1,239 @@
package pp.monopoly.client;
import com.jme3.scene.Spatial;
import com.jme3.scene.Node;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.*;
public class ClientLogicTest {
private MonopolyApp app;
private Node guiNodeMock;
@Before
public void setUp() {
// 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
public void testOpenStartMenu() {
Spatial startMenu = app.getGuiNode().getChild("StartMenu");
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() {
Spatial mainMenu = app.getGuiNode().getChild("MainMenu");
assertNotNull("Das Hauptmenü (MainMenu) sollte sichtbar sein", mainMenu);
Spatial playButtonSpatial = app.getGuiNode().getChild("PlayButton");
assertNotNull("Der 'Spielen'-Button sollte existieren", playButtonSpatial);
Spatial settingsButtonSpatial = app.getGuiNode().getChild("SettingsButton");
assertNotNull("Der 'Einstellungen'-Button sollte existieren", settingsButtonSpatial);
Spatial exitButtonSpatial = app.getGuiNode().getChild("ExitButton");
assertNotNull("Der 'Spiel beenden'-Button sollte existieren", exitButtonSpatial);
// Optional: Überprüfung der Funktionalität (Simulation von Button-Klicks)
if (playButtonSpatial instanceof Button) {
Button playButton = (Button) playButtonSpatial;
playButton.click();
Spatial newGameMenu = app.getGuiNode().getChild("NewGameMenu");
assertNotNull("Das Spielerstellungsmenü sollte nach dem Klicken auf 'Spielen' sichtbar sein", newGameMenu);
} else {
throw new AssertionError("'PlayButton' ist kein Button-Objekt.");
}
if (settingsButtonSpatial instanceof Button) {
Button settingsButton = (Button) settingsButtonSpatial;
settingsButton.click();
Spatial settingsMenu = app.getGuiNode().getChild("SettingsMenu");
assertNotNull("Das Einstellungsmenü sollte nach dem Klicken auf 'Einstellungen' sichtbar sein", settingsMenu);
} else {
throw new AssertionError("'SettingsButton' ist kein Button-Objekt.");
}
if (exitButtonSpatial instanceof Button) {
Button exitButton = (Button) exitButtonSpatial;
exitButton.click();
Spatial mainMenuAfterExit = app.getGuiNode().getChild("MainMenu");
assertNull("Das Hauptmenü sollte nach dem Klicken auf 'Spiel beenden' nicht mehr sichtbar sein", mainMenuAfterExit);
} else {
throw new AssertionError("'ExitButton' ist kein Button-Objekt.");
}
}
@Test
// T003: UC-game-03 - Überprüft, ob der „Spiel starten“-Button das Spielerstellungsmenü öffnet
public void testNavigateToPlayOption() {
Spatial startGameButtonSpatial = app.getGuiNode().getChild("StartGameButton");
assertNotNull("Der 'Spiel starten'-Button sollte existieren", startGameButtonSpatial);
if (startGameButtonSpatial instanceof Button) {
Button startGameButton = (Button) startGameButtonSpatial;
startGameButton.click();
Spatial newGameMenu = app.getGuiNode().getChild("NewGameMenu");
assertNotNull("Das Spielerstellungsmenü sollte sichtbar sein", newGameMenu);
} else {
throw new AssertionError("'StartGameButton' ist kein Button-Objekt.");
}
}
@Test
// T004: UC-game-04 - Testet, ob die Anwendung geschlossen wird, wenn „Beenden“ im Hauptmenü gewählt wird
public void testExitApplicationFromMenu() {
Spatial exitButtonSpatial = app.getGuiNode().getChild("ExitButton");
assertNotNull("Der 'Beenden'-Button sollte existieren", exitButtonSpatial);
if (exitButtonSpatial instanceof Button) {
Button exitButton = (Button) exitButtonSpatial;
exitButton.click();
Spatial mainMenuAfterExit = app.getGuiNode().getChild("MainMenu");
assertNull("Das Hauptmenü sollte nach dem Beenden nicht mehr sichtbar sein", mainMenuAfterExit);
} else {
throw new AssertionError("'ExitButton' ist kein Button-Objekt.");
}
}
@Test
// T005: UC-game-05 - Überprüft, ob das Einstellungen-Menü aus dem Hauptmenü aufgerufen werden kann
public void testOpenSettingsFromMenu() {
Spatial settingsButtonSpatial = app.getGuiNode().getChild("SettingsButton");
assertNotNull("Der 'Einstellungen'-Button sollte existieren", settingsButtonSpatial);
if (settingsButtonSpatial instanceof Button) {
Button settingsButton = (Button) settingsButtonSpatial;
settingsButton.click();
Spatial settingsMenu = app.getGuiNode().getChild("SettingsMenu");
assertNotNull("Das Einstellungsmenü sollte sichtbar sein", settingsMenu);
} else {
throw new AssertionError("'SettingsButton' ist kein Button-Objekt.");
}
}
@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 nach Drücken von ESC sichtbar sein", gameMenu);
app.escape(false); // Simuliert das Loslassen der ESC-Taste
}
@Test
// T083: UC-menu-05 - Überprüfen, ob der Spieler erfolgreich ins Spiel zurückkehren kann
public void testReturnToGame() {
Spatial returnButtonSpatial = app.getGuiNode().getChild("ReturnButton");
assertNotNull("Der 'Zurück'-Button sollte existieren", returnButtonSpatial);
if (returnButtonSpatial instanceof Button) {
Button returnButton = (Button) returnButtonSpatial;
returnButton.click();
Spatial gameScene = app.getGuiNode().getChild("GameScene");
assertNotNull("Die Spielszene sollte nach dem Klick auf 'Zurück' sichtbar sein", gameScene);
} else {
throw new AssertionError("'ReturnButton' ist kein Button-Objekt.");
}
}
}
*/

View File

@@ -0,0 +1,123 @@
package pp.monopoly.game.client;
/*
import org.junit.Before;
import org.junit.Test;
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.assertTrue;
import static org.mockito.Mockito.*;
/**
* Tests the client-side logic of the Monopoly game.
*/
/*
public class ClientGameLogicTest {
private ClientGameLogic logic;
@Mock
private MonopolyServer serverMock;
@Mock
private ClientSender clientSenderMock;
@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
logic = new ClientGameLogic(clientSenderMock);
}
@Test
public void testEnterHostName() {
String hostName = "localhost";
logic.setHostName(hostName);
assertEquals("The hostname should be correctly set.", hostName, logic.getHostName());
}
@Test
public void testEnterPortNumber() {
int portNumber = 12345;
logic.setPortNumber(portNumber);
assertEquals("The port number should be correctly set.", portNumber, logic.getPortNumber());
}
@Test
public void testCancelGameCreation() {
doNothing().when(clientSenderMock).returnToMainMenu();
logic.cancelGameCreation();
verify(clientSenderMock, times(1)).returnToMainMenu();
}
@Test
public void testEnterPlayerLobby() {
doNothing().when(clientSenderMock).enterLobby();
logic.enterLobby();
verify(clientSenderMock, times(1)).enterLobby();
}
@Test
public void testEnterStartingCapital() {
int startingCapital = 1500;
logic.setStartingCapital(startingCapital);
assertEquals("The starting capital should be correctly set.", startingCapital, logic.getStartingCapital());
}
@Test
public void testIncreaseStartingCapital() {
logic.setStartingCapital(1500);
logic.increaseStartingCapital();
assertEquals("The starting capital should increase by 100.", 1600, logic.getStartingCapital());
}
@Test
public void testDecreaseStartingCapital() {
logic.setStartingCapital(1500);
logic.decreaseStartingCapital();
assertEquals("The starting capital should decrease by 100.", 1400, logic.getStartingCapital());
}
@Test
public void testDefaultPlayerName() {
logic.addPlayer("Player 1");
assertTrue("The player name should be correctly set.", logic.getPlayerNames().contains("Player 1"));
}
@Test
public void testEnterDisplayName() {
String displayName = "JohnDoe";
logic.setPlayerName(displayName);
assertEquals("The display name should be correctly set.", displayName, logic.getPlayerName());
}
@Test
public void testDuplicateNameEntry() {
logic.addPlayer("Player 1");
boolean result = logic.addPlayer("Player 1");
assertTrue("Duplicate names should not be allowed.", !result);
}
@Test
public void testSelectPlayerColor() {
logic.addPlayer("Player 1");
boolean result = logic.setPlayerColor("Player 1", "Red");
assertTrue("The player should be able to select an available color.", result);
}
}
*/

View File

@@ -0,0 +1,993 @@
package pp.monopoly.game.server;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockedStatic;
import pp.monopoly.message.server.DiceResult;
import pp.monopoly.model.card.Card;
import pp.monopoly.model.card.DeckHelper;
import pp.monopoly.model.fields.BoardManager;
import pp.monopoly.model.fields.EventField;
import pp.monopoly.model.fields.FineField;
import pp.monopoly.model.fields.GulagField;
import pp.monopoly.model.fields.PropertyField;
import pp.monopoly.model.fields.BuildingProperty;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import static junit.framework.TestCase.assertSame;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class ServerGameLogicTest {
private ServerGameLogic serverGameLogic;
private PlayerHandler playerHandler;
private Player player;
private PropertyField property;
@Before
public void setUp() {
// Initialisierung von Abhängigkeiten
serverGameLogic = new ServerGameLogic(null, null);
playerHandler = new PlayerHandler(serverGameLogic);
player = new Player(1, "TestPlayer", playerHandler);
}
/**
* T026: Überprüfen, ob der Spieler erfolgreich würfeln kann.
*/
@Test
public void testRollDice() {
// Act: Spieler würfelt
player = mock(Player.class);
when(player.rollDice()).thenReturn(new DiceResult(List.of(4, 3))); // Beispiel: Würfel zeigen 4 und 3
DiceResult diceResult = player.rollDice();
// Assert: Würfelwerte liegen im Bereich von 2 bis 12
assertTrue(diceResult.calcTotal() >= 2 && diceResult.calcTotal() <= 12);
}
/**
* T027: Überprüfen, ob der Spieler die richtige Anzahl an Feldern basierend auf dem Wurf bewegt.
*/
@Test
public void testMovePlayer() {
// Arrange: Spieler initialisieren und Position setzen
player.getFieldID(); // Startfeld
DiceResult diceResult = new DiceResult(List.of(3, 4)); // Würfel: 3 und 4
// Act: Spieler bewegen
int newFieldID = (player.getFieldID() + diceResult.calcTotal()) % 40; // Spielfeld mit 40 Feldern
player.move(newFieldID);
// Assert: Position überprüfen
assertEquals(newFieldID, player.getFieldID());
}
/**
* T028: Überprüft, ob die Würfel zufällige Zahlen generieren.
*/
@Test
public void testGenerationDice() {
// Arrange: Statistiken für Würfel
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
// Act: Simuliere mehrere Würfe
for (int i = 0; i < 1000; i++) {
DiceResult diceResult = new DiceResult(List.of(
(int) (Math.random() * 6) + 1,
(int) (Math.random() * 6) + 1
));
int total = diceResult.calcTotal();
// Erfasse den minimalen und maximalen Wert
min = Math.min(min, total);
max = Math.max(max, total);
}
// Assert: Überprüfung der Zufälligkeit (Werte zwischen 2 und 12)
assertTrue(min >= 2); // Minimaler Wurf: 1 + 1
assertTrue(max <= 12); // Maximaler Wurf: 6 + 6
}
/**
* T030: Überprüfen, ob die Summe der Würfelergebnisse korrekt berechnet wird.
*/
@Test
public void testSumDiceResults() {
// Arrange: Teste alle möglichen Würfelkombinationen
for (int dice1 = 1; dice1 <= 6; dice1++) {
for (int dice2 = 1; dice2 <= 6; dice2++) {
// Act: Simuliere die Würfelergebnisse und berechne die Summe
DiceResult diceResult = new DiceResult(List.of(dice1, dice2));
int sum = diceResult.calcTotal();
// Assert: Überprüfe die korrekte Summe
assertEquals(dice1 + dice2, sum); // Erwartetes Ergebnis: Summe der beiden Würfel
}
}
}
/**
* T031 Überprüfen, ob die Würfel nach dem Wurf ausgegraut werden
*/
@Test
public void testGrayOutDiceAfterRoll() {
//TODO
}
/**
* T032: Überprüfen, ob alle Paschs (zwei identische Augenzahlen) korrekt erkannt werden.
*/
@Test
public void testDetectDoubleForAllPossibleDoubles() {
// Act & Assert: Überprüfe für alle möglichen Paschs (1-1 bis 6-6)
for (int i = 1; i <= 6; i++) {
DiceResult diceResult = new DiceResult(List.of(i, i)); // Pasch mit zwei gleichen Zahlen
assertTrue("Pasch wurde nicht korrekt erkannt für " + i + "-" + i, diceResult.isDoublets());
}
}
/**
* T033: Überprüfen, ob ein Spieler bei einem Pasch erneut würfeln darf.
*/
@Test
public void testDoubleRoll() {
// Arrange: Mock für Player erstellen
Player mockPlayer = mock(Player.class);
// Würfel-Ergebnis simulieren
DiceResult doubleResult = new DiceResult(List.of(6, 6));
when(mockPlayer.rollDice()).thenReturn(doubleResult); // rollDice wird gemockt
// Act: Spieler würfelt
DiceResult result = mockPlayer.rollDice();
// Assert: Überprüfen, ob Pasch erkannt wird
assertTrue(result.isDoublets()); // Pasch sollte erkannt werden
verify(mockPlayer, times(1)).rollDice(); // Sicherstellen, dass rollDice genau einmal aufgerufen wurde
}
/**
* T034: Überprüfen, ob ein Spieler nach dem dritten Pasch ins Gulag kommt.
*/
@Test
public void testTripleDoubleGulag() {
// Arrange: Spieler-Mock erstellen
Player mockedPlayer = mock(Player.class);
// Drei aufeinanderfolgende Paschs simulieren
when(mockedPlayer.rollDice())
.thenReturn(new DiceResult(List.of(2, 2))) // Erster Pasch
.thenReturn(new DiceResult(List.of(5, 5))) // Zweiter Pasch
.thenReturn(new DiceResult(List.of(6, 6))); // Dritter Pasch
// Act: Spieler würfelt dreimal
boolean firstDouble = mockedPlayer.rollDice().isDoublets();
boolean secondDouble = mockedPlayer.rollDice().isDoublets();
boolean thirdDouble = mockedPlayer.rollDice().isDoublets();
// Spieler wird ins Jail bewegt, wenn drei Paschs gewürfelt wurden
if (firstDouble && secondDouble && thirdDouble) {
mockedPlayer.movePos(10); // Jail-Position im Spiel
}
// Assert: Spieler ist nach dem dritten Pasch im Jail
assertTrue(firstDouble);
assertTrue(secondDouble);
assertTrue(thirdDouble);
verify(mockedPlayer, times(1)).movePos(10); // Spieler sollte genau einmal ins Jail bewegt werden
}
/*
/**
* T035: Überprüft, ob der Spieler ein Grundstück kaufen kann.
*/
/*
@Test
public void testBuyProperty() {
// Arrange
Player player = mock(Player.class);
PropertyField propertyField = mock(PropertyField.class);
List<PropertyField> properties = new ArrayList<>(); // Liste der Immobilien
// Simuliere das Verhalten von getProperties() und add
when(player.getProperties()).thenReturn(properties);
when(propertyField.getOwner()).thenReturn(null); // Kein Besitzer
when(propertyField.getPrice()).thenReturn(200); // Preis
when(player.getAccountBalance()).thenReturn(500); // Spieler hat genug Geld
// Act
player.buyProperty(propertyField);
// Hinzufügen der Immobilie simulieren
properties.add(propertyField);
// Assert
verify(propertyField).setOwner(player); // Eigentümer setzen
verify(player).pay(200); // Betrag abziehen
assertTrue(properties.contains(propertyField)); // Überprüfen, ob die Immobilie hinzugefügt wurde
}
*/
/**
* T037: Überprüft, ob der Spieler in den Gulag geschickt wird.
*/
@Test
public void testGoToGulag() {
// Arrange
Player player = mock(Player.class);
GulagField gulagField = mock(GulagField.class);
// Spieler wird auf ein Ereignis gesetzt, das ihn ins Gefängnis schickt
when(player.getFieldID()).thenReturn(30); // Beispiel: Feld 30 = Gehe-ins-Gefängnis-Feld
// Act: Spieler wird ins Gefängnis geschickt
player.movePos(gulagField.getId());
// Assert: Überprüfe, ob der Spieler korrekt ins Gefängnis bewegt wurde
verify(player, times(1)).movePos(gulagField.getId());
verify(player, never()).earnMoney(anyInt()); // Spieler sollte kein Geld erhalten
}
/**
* T039: Überprüft, ob der Spieler nicht mehr als vier Häuser bauen kann.
*/
@Test
public void testMaxBuildHouses() {
// Arrange: Mock des BuildingProperty
BuildingProperty buildingProperty = mock(BuildingProperty.class);
// Setze Rückgabewerte für Methoden
when(buildingProperty.buildHouse()).thenReturn(true).thenReturn(true).thenReturn(true).thenReturn(true).thenReturn(false);
// Act: Spieler versucht mehr als 4 Häuser zu bauen
boolean firstBuild = buildingProperty.buildHouse(); // Haus 1
boolean secondBuild = buildingProperty.buildHouse(); // Haus 2
boolean thirdBuild = buildingProperty.buildHouse(); // Haus 3
boolean fourthBuild = buildingProperty.buildHouse(); // Haus 4
boolean fifthBuild = buildingProperty.buildHouse(); // Versuch, Haus 5 zu bauen
// Assert: Überprüfung, dass der fünfte Bau abgelehnt wird
assertTrue(firstBuild);
assertTrue(secondBuild);
assertTrue(thirdBuild);
assertTrue(fourthBuild);
assertFalse(fifthBuild); // Der fünfte Hausbau sollte blockiert werden
}
/**
* T040: Überprüfen, ob der Spieler eine Hypothek auf ein Grundstück aufnehmen kann.
*/
@Test
public void testTakeMortgage() {
// Arrange: Mock für Spieler und Grundstück
Player player = mock(Player.class);
BuildingProperty property = mock(BuildingProperty.class);
// Voraussetzungen: Spieler ist der Besitzer und Grundstück ist nicht hypothekiert
when(property.getOwner()).thenReturn(player);
when(property.isMortgaged()).thenReturn(false);
when(property.getHypo()).thenReturn(500);
// Act: Spieler nimmt Hypothek auf
property.setMortgaged(true); // Hypothek setzen
verify(property).setMortgaged(true); // Hypothek bestätigt
when(property.isMortgaged()).thenReturn(true); // Zustand simulieren
player.earnMoney(property.getHypo()); // Spieler erhält Geld
// Assert: Überprüfe die Ergebnisse
verify(player).earnMoney(500); // Spieler erhält Hypothekenwert
assertTrue(property.isMortgaged()); // Hypothek erfolgreich
}
/**
* T041: UC-gameplay-12
* Überprüfen, ob eine Würfelanimation beim Würfeln ausgeführt wird.
*/
@Test
public void testDiceRollAnimation() {
//TODO
}
/**
* T042: UC-gameplay-13
* Überprüfen, ob der Spieler beim Betreten von Steuerfeldern Steuern zahlt.
*/
@Test
public void testPayTaxes() {
// Arrange: Mock für Spieler und Steuerfeld
Player player = mock(Player.class);
FineField taxField = mock(FineField.class);
// Mocking: Stub für Steuerbetrag und Guthaben des Spielers
when(taxField.getFine()).thenReturn(200); // Steuerbetrag
when(player.getAccountBalance()).thenReturn(1000); // Spieler hat 1000 Guthaben
// Mock visit() Logik, um die Zahlung auszulösen
doAnswer(invocation -> {
FineField field = invocation.getArgument(0);
player.pay(field.getFine());
return null;
}).when(player).visit(any(FineField.class));
// Act: Spieler betritt das Steuerfeld
taxField.accept(player);
assertEquals(800, player.getAccountBalance());
}
/**
* T044: UC-gameplay-14
* Überprüfen, ob der Spieler eine Ereigniskarte zieht und die entsprechende Aktion ausgeführt wird.
*/
@Test
public void testDrawEventCard() {
// Arrange: Mock für Spieler und Karten
Player player = mock(Player.class);
EventField eventField = new EventField("Ereignisfeld", 1);
// Mocking: Ereigniskarte simulieren
Card eventCard = mock(Card.class);
Queue<Card> mockCardQueue = mock(Queue.class);
// Mock DeckHelper: Simuliere Kartenstapel
try (MockedStatic<DeckHelper> mockedDeckHelper = mockStatic(DeckHelper.class)) {
mockedDeckHelper.when(DeckHelper::drawCard).thenReturn(eventCard);
// Mocking: Simuliere die Aktion der Karte
doNothing().when(eventCard).accept(any());
// Act: Spieler betritt das Ereignisfeld und zieht eine Karte
eventField.accept(player);
Card drawnCard = eventField.drawCard();
// Assert: Überprüfe die Interaktionen
verify(eventCard).accept(any()); // Aktion der Karte ausführen
assertSame(eventCard, drawnCard); // Sicherstellen, dass die gezogene Karte zurückgegeben wurde
}
}
/**
* T043: Überprüfen, ob der Spieler die Hypothek zurückzahlt.
*/
@Test
public void testPayBackMortgage() {
// Arrange: Mock für Spieler und Grundstück
Player player = mock(Player.class);
BuildingProperty property = mock(BuildingProperty.class);
// Voraussetzungen: Spieler ist der Besitzer und Grundstück ist hypothekiert
when(property.getOwner()).thenReturn(player);
when(property.isMortgaged()).thenReturn(true);
when(property.getHypo()).thenReturn(500);
// Act: Spieler zahlt Hypothek zurück
int repaymentAmount = (int) Math.round(property.getHypo() * 1.1); // Hypothek + 10% Zinsen
player.pay(repaymentAmount); // Spieler zahlt zurück
verify(player).pay(repaymentAmount); // Zahlung bestätigt
property.setMortgaged(false); // Hypothek wird aufgehoben
when(property.isMortgaged()).thenReturn(false); // Zustand simulieren
// Assert: Überprüfe die Ergebnisse
verify(property).setMortgaged(false); // Hypothek aufgehoben
assertFalse(property.isMortgaged()); // Hypothekenstatus ist aufgehoben
}
/**
* T045 - UC-gameplay-16
* Testet, ob der Spieler nur ein Hotel auf einem Grundstück bauen kann.
*/
@Test
public void testBuildHotel() {
// Arrange: Spieler und Grundstück mocken
Player player = mock(Player.class);
BuildingProperty buildingProperty = mock(BuildingProperty.class);
// Verhalten des Mocks definieren
when(buildingProperty.getOwner()).thenReturn(player); // Spieler ist der Besitzer
when(buildingProperty.buildHotel())
.thenReturn(true) // Erster Versuch: Hotel erfolgreich gebaut
.thenReturn(false); // Zweiter Versuch: Hotelbau blockiert
// Act: Spieler versucht, Hotels zu bauen
boolean firstBuildSuccess = buildingProperty.buildHotel(); // Erster Versuch
boolean secondBuildSuccess = buildingProperty.buildHotel(); // Zweiter Versuch
// Assert: Überprüfen der Ergebnisse
assertTrue(firstBuildSuccess); // Erstes Hotel sollte erlaubt sein
assertFalse(secondBuildSuccess); // Zweites Hotel sollte blockiert werden
}
/**
* T046: Überprüfen, ob das Spiel korrekt zum nächsten Spieler wechselt.
*/
@Test
public void testSwitchTurnToNextPlayer() {
// Arrange
ServerGameLogic logic = mock(ServerGameLogic.class);
Player player1 = mock(Player.class);
Player player2 = mock(Player.class);
Player player3 = mock(Player.class);
when(player1.getId()).thenReturn(1);
when(player2.getId()).thenReturn(2);
when(player3.getId()).thenReturn(3);
// Initialisiere den PlayerHandler mit Spielern
PlayerHandler playerHandler = new PlayerHandler(logic);
playerHandler.addPlayer(player1);
playerHandler.addPlayer(player2);
playerHandler.addPlayer(player3);
// Act
Player currentPlayer = playerHandler.nextPlayer();
// Assert
assertEquals(player2, currentPlayer); // Spieler 2 ist an der Reihe
assertEquals(player3, playerHandler.nextPlayer()); // Spieler 3 ist danach dran
assertEquals(player1, playerHandler.nextPlayer()); // Spieler 1 ist danach wieder dran
}
/**
* T048: Überprüft, ob der Spieler korrekt die Strafzahlung entrichtet.
*/
@Test
public void testPenaltyPayment() {
// Arrange: Mock für Spieler und die Strafe
Player player = mock(Player.class);
int penaltyAmount = 300; // Beispiel-Strafbetrag
// Voraussetzungen: Der Spieler hat ein Konto mit genügend Guthaben
when(player.getAccountBalance()).thenReturn(1000);
// Act: Spieler zahlt die Strafe
player.pay(penaltyAmount);
// Assert: Überprüfe, ob der Betrag abgezogen wurde
verify(player).pay(penaltyAmount);
assertEquals(700, player.getAccountBalance() - penaltyAmount); // Neues Guthaben nach Strafzahlung
}
/**
* T049: Überprüft, ob der Spieler korrekt Strafzahlungen erhält.
*/
@Test
public void testReceivePenaltyPayment() {
// Arrange: Mock für den zahlenden und empfangenden Spieler
Player payingPlayer = mock(Player.class);
Player receivingPlayer = mock(Player.class);
int penaltyAmount = 200; // Beispiel-Strafbetrag
// Voraussetzungen: Der zahlende Spieler hat genug Guthaben
when(payingPlayer.getAccountBalance()).thenReturn(1000);
// Act: Strafzahlung durchführen
payingPlayer.pay(penaltyAmount);
receivingPlayer.earnMoney(penaltyAmount);
// Assert: Überprüfen, ob der Betrag korrekt abgezogen wurde
verify(payingPlayer).pay(penaltyAmount);
// Überprüfen, ob der Betrag beim Empfänger gutgeschrieben wurde
verify(receivingPlayer).earnMoney(penaltyAmount);
// Optional: Überprüfen der neuen Guthabenstände
assertEquals(800, payingPlayer.getAccountBalance() - penaltyAmount); // Neues Guthaben des zahlenden Spielers
assertEquals(200, receivingPlayer.getAccountBalance() + penaltyAmount); // Neues Guthaben des empfangenden Spielers
}
/**
* T050: Überprüfen, ob der Spieler das Gulag verlassen kann.
*
* /
@Test public void testLeaveGulag() {
// Arrange: Mock für PlayerHandler
PlayerHandler handlerMock = mock(PlayerHandler.class);
Player player = new Player(1, handlerMock);
// Simuliere, dass der Spieler eine "Gulag-Frei"-Karte besitzt
player.addJailCard(); // Spieler erhält eine "Gulag-Frei"-Karte
assertEquals(1, player.getNumJailCard()); // Sicherstellen, dass die Karte vorhanden ist
// Simuliere, dass der Spieler das Gulag betritt
GulagField gulagFieldMock = mock(GulagField.class);
gulagFieldMock.accept(player); // Spieler wird in den JailState versetzt
// Act: Spieler nutzt die "Gulag-Frei"-Karte
player.useJailCard(); // Spieler verlässt das Gulag durch die Karte
// Assert: Überprüfen, ob die Karte entfernt wurde und der Spieler aktiv ist
assertEquals(0, player.getNumJailCard()); // Keine Karten mehr übrig
assertEquals(1, player.getFieldID()); // Sicherstellen, dass der Spieler nicht mehr im Gulag ist
}
/**
* T051: UC-gameplay-23
* Überprüfen, ob die Spielreihenfolge korrekt bestimmt wird.
*/
/*
@Test
public void testDetermineTurnOrder() {
// Arrange: Erstellen einer Liste von Spielern mit simulierten Würfel-Ergebnissen
PlayerHandler playerHandler = new PlayerHandler(mock(ServerGameLogic.class));
Player player1 = mock(Player.class);
Player player2 = mock(Player.class);
Player player3 = mock(Player.class);
when(player1.rollDice()).thenReturn(new DiceResult(List.of(4, 3))); // Ergebnis: 7
when(player2.rollDice()).thenReturn(new DiceResult(List.of(6, 1))); // Ergebnis: 7
when(player3.rollDice()).thenReturn(new DiceResult(List.of(5, 5))); // Ergebnis: 10
playerHandler.addPlayer(player1);
playerHandler.addPlayer(player2);
playerHandler.addPlayer(player3);
// Act: Reihenfolge der Spieler bestimmen
List<Player> turnOrder = playerHandler.determineTurnOrder();
// Assert: Überprüfung, ob die Spieler in der korrekten Reihenfolge sortiert sind
assertEquals(player3, turnOrder.get(0)); // Spieler 3 hat die höchste Summe (10)
assertTrue(turnOrder.containsAll(List.of(player1, player2))); // Spieler 1 und 2 folgen
assertEquals(3, turnOrder.size()); // Überprüfung, ob alle Spieler berücksichtigt wurden
}
*/
/**
* T052: UC-gameplay-24
* Überprüfen, ob der Spieler Bankrott erklären kann.
*//*
@Test
public void testDeclareBankruptcy() {
// Arrange: Mock für Spieler, Bank und Besitz
Player player = new Player(1, "Spieler 1", mock(PlayerHandler.class));
PropertyField property1 = mock(PropertyField.class);
PropertyField property2 = mock(PropertyField.class);
List<PropertyField> properties = List.of(property1, property2);
// Spieler besitzt zwei Grundstücke und hat kein Geld mehr
player.earnMoney(500); // Spieler hat 500 auf dem Konto
when(property1.getOwner()).thenReturn(player);
when(property2.getOwner()).thenReturn(player);
player.getProperties().addAll(properties);
// Spieler hat Schulden (z. B. 1000)
int debt = 1000;
// Act: Spieler erklärt Bankrott
player.pay(debt); // Schulden abziehen
player.declareBankruptcy(); // Bankrott erklären
// Assert: Überprüfen, ob Besitz zurückgegeben wurde und Spieler bankrott ist
for (PropertyField property : properties) {
verify(property).setOwner(null); // Besitz zurück an die Bank
}
assertTrue(player.state instanceof Player.BankruptState); // Spieler ist jetzt bankrott
assertEquals(0, player.getAccountBalance()); // Spieler hat kein Geld mehr
}
*/
/**
* T053: UC-gameplay-25
* Überprüfen, ob der Spieler aufgrund eines anderen Spielers Bankrott geht.
*//*
@Test
public void testBankruptcyByPlayer() {
// Arrange: Mock für Spieler und Besitz
Player player1 = new Player(1, "Spieler 1", mock(PlayerHandler.class)); // Schuldner
Player player2 = new Player(2, "Spieler 2", mock(PlayerHandler.class)); // Gläubiger
PropertyField property1 = mock(PropertyField.class);
PropertyField property2 = mock(PropertyField.class);
List<PropertyField> properties = List.of(property1, property2);
// Spieler 1 besitzt zwei Grundstücke und hat wenig Geld
player1.earnMoney(500); // Spieler 1 hat 500 auf dem Konto
when(property1.getOwner()).thenReturn(player1);
when(property2.getOwner()).thenReturn(player1);
player1.getProperties().addAll(properties);
// Spieler 2 ist der Gläubiger
player2.earnMoney(1000); // Spieler 2 hat genug Geld
// Act: Spieler 1 zahlt an Spieler 2, bis er bankrott geht
int debt = 1000; // Spieler 1 schuldet Spieler 2 1000
player1.pay(debt);
player2.earnMoney(debt); // Spieler 2 erhält das Geld
if (player1.getAccountBalance() < 0) {
player1.declareBankruptcy();
}
// Assert: Überprüfen, ob Spieler 1 bankrott ist und Spieler 2 das Geld erhalten hat
for (PropertyField property : properties) {
verify(property).setOwner(null); // Besitz von Spieler 1 zurück an die Bank
}
assertTrue(player1.state instanceof Player.BankruptState); // Spieler 1 ist bankrott
assertEquals(0, player1.getAccountBalance()); // Spieler 1 hat kein Geld mehr
assertEquals(1500, player2.getAccountBalance()); // Spieler 2 hat das Geld erhalten
}
*/
/**
* T054: UC-gameplay-26
* Überprüfen, ob das Spiel bei Bankrott eines Spielers mit Game Over endet.
*/
/*@Test
public void testGameOverBankruptcy() {
// Arrange: Mock für Spieler und Spiel-Logik
PlayerHandler playerHandler = mock(PlayerHandler.class);
ServerGameLogic gameLogic = mock(ServerGameLogic.class);
when(playerHandler.getLogic()).thenReturn(gameLogic);
Player player1 = new Player(1, "Spieler 1", playerHandler); // Spieler, der bankrott geht
Player player2 = new Player(2, "Spieler 2", playerHandler); // Letzter verbleibender Spieler
// Spieler in die PlayerHandler-Liste hinzufügen
List<Player> players = List.of(player1, player2);
when(playerHandler.getPlayerCount()).thenReturn(players.size());
// Spieler 1 verliert alles und erklärt Bankrott
player1.declareBankruptcy();
players.remove(player1); // Spieler 1 wird entfernt
when(playerHandler.getPlayerCount()).thenReturn(players.size() - 1);
// Act: Überprüfen, ob nur noch ein Spieler übrig ist
if (players.size() == 1) {
gameLogic.endGame(player2); // Spielende auslösen
}
// Assert: Prüfen, ob das Spiel im GameOver-Zustand ist und der Gewinner korrekt gesetzt wurde
verify(gameLogic).setGameState(GameState.GAME_OVER); // Spielstatus auf "Game Over" setzen
verify(gameLogic).endGame(player2); // Gewinner ist Spieler 2
assertTrue(player2.getAccountBalance() > 0); // Gewinner hat ein positives Guthaben
}
*/
/**
* T056: UC-gameplay-29
* Überprüfen, ob das Ereignisfeld eine zufällige Karte anzeigt und die entsprechende Aktion ausführt.
*/
@Test
public void testTriggerEventTile() {
// Arrange: Mock für Spieler, Ereignisfeld und DeckHelper
Player player = mock(Player.class);
EventField eventField = mock(EventField.class);
DeckHelper deckHelper = mock(DeckHelper.class);
Card eventCard = mock(Card.class); // Ereigniskarte
// Stubbing: Ereigniskarte ziehen
when(eventField.drawCard()).thenReturn(eventCard);
when(eventCard.getDescription()).thenReturn("Du bekommst 200€!"); // Beispieltext
// Stubbing: Spieleraktion durch Ereigniskarte
doAnswer(invocation -> {
player.earnMoney(200); // Aktion: Geld dem Spieler gutschreiben
return null;
}).when(eventCard).accept(any());
// Act: Spieler betritt das Ereignisfeld
eventField.accept(player); // Spieler interagiert mit dem Ereignisfeld
Card drawnCard = eventField.drawCard(); // Ereigniskarte wird gezogen
drawnCard.accept(deckHelper); // Ereigniskarte führt ihre Aktion aus
// Assert: Überprüfen, ob die Karte korrekt angezeigt und die Aktion ausgeführt wurde
assertEquals("Du bekommst 200€!", drawnCard.getDescription()); // Überprüfung der Kartenbeschreibung
verify(player).earnMoney(200); // Überprüfung, ob dem Spieler 200€ gutgeschrieben wurden
}
/**
* T057: UC-gameplay-30
* Überprüfen, ob der Spieler beim Erreichen des Gulag-Feldes in den Gulag versetzt wird.
*/
/*
@Test
public void testTriggerGulagTransfer() {
// Arrange: Mock-Objekte erstellen
Player player = mock(Player.class);
PlayerHandler handler = mock(PlayerHandler.class);
ServerGameLogic logic = mock(ServerGameLogic.class);
BoardManager boardManager = mock(BoardManager.class);
GulagField gulagField = mock(GulagField.class);
// Setup: Spieler-Setup mit Logik und Mock-Feld
when(handler.getLogic()).thenReturn(logic);
when(logic.getBoardManager()).thenReturn(boardManager);
// Act: Spieler interagiert mit dem Gulag-Feld
doCallRealMethod().when(player).visit(gulagField);
player.visit(gulagField); // Spieler landet auf dem Gulag-Feld
// Assert: Überprüfen, ob der Spieler in den "JailState" versetzt wird
verify(player).setState(any(Player.JailState.class));
assertTrue(player.getState() instanceof Player.JailState); // Spieler ist jetzt im JailState
}*/
/**
* T058: UC-gameplay-31
* Überprüfen, ob der Spieler eine Karte von der Bank erfolgreich kaufen kann.
*/
/*
@Test
public void testBuyCard() {
// Arrange: Mock-Objekte für Spieler und Property erstellen
Player player = spy(new Player(1, mock(PlayerHandler.class))); // Verwenden Sie spy, um die reale Methode zu testen
BuildingProperty property = mock(BuildingProperty.class);
// Voraussetzungen: Karte ist unbesessen und der Spieler hat genug Geld
when(property.getOwner()).thenReturn(null); // Karte gehört niemandem
when(property.getPrice()).thenReturn(1000); // Kartenpreis
when(player.getAccountBalance()).thenReturn(2000); // Spieler hat genug Geld
// Act: Spieler kauft die Karte
player.buyProperty(property);
// Assert: Überprüfen, ob der Spieler die Karte besitzt und das Geld abgezogen wurde
verify(player).pay(1000); // Geld abgezogen
verify(property).setOwner(player); // Spieler wird als Besitzer gesetzt
verify(property).getOwner(); // Prüfen, ob das Eigentümer-Attribut korrekt gesetzt wurde
}
*/
/**
* T059: UC-gameplay-32
* Überprüfen, ob der Kartenerwerb fehlschlägt, wenn der Spieler nicht genug Geld hat.
*/
/*@Test
public void testCardPurchaseFailed() {
// Arrange: Mock-Objekte für Spieler und Property erstellen
Player player = spy(new Player(1, mock(PlayerHandler.class))); // Spy verwendet für reale Methoden
BuildingProperty property = mock(BuildingProperty.class);
// Voraussetzungen: Karte gehört der Bank und Spieler hat zu wenig Geld
when(property.getOwner()).thenReturn(null); // Karte gehört der Bank
when(property.getPrice()).thenReturn(1000); // Preis der Karte
when(player.getAccountBalance()).thenReturn(500); // Spieler hat zu wenig Geld
// Act & Assert: Spieler versucht, die Karte zu kaufen
Exception exception = assertThrows(IllegalStateException.class, () -> {
player.buyProperty(property); // Kaufversuch
});
// Überprüfen, ob die erwartete Ausnahme mit der korrekten Nachricht ausgelöst wurde
assertEquals("Property cannot be purchased", exception.getMessage());
// Verifizieren, dass der Besitzer nicht geändert wurde
verify(property, never()).setOwner(player); // Spieler darf nicht als Besitzer gesetzt werden
// Verifizieren, dass kein Geld abgezogen wurde
verify(player, never()).pay(anyInt());
}
*/
/**
* T60: Überprüfen, ob der Spieler korrekt Miete zahlt, wenn er auf einem besetzten Grundstück landet.
*/
/*
@Test
public void testPayRent() {
// Arrange: Erstelle Mock-Objekte für Spieler und Grundstück
Player tenant = spy(new Player(1, mock(PlayerHandler.class))); // Spieler, der die Miete zahlt
Player owner = spy(new Player(2, mock(PlayerHandler.class))); // Spieler, der die Miete erhält
BuildingProperty property = mock(BuildingProperty.class); // Grundstück
// Voraussetzungen:
when(property.getOwner()).thenReturn(owner); // Eigentümer des Grundstücks
when(property.calcRent()).thenReturn(300); // Berechnete Miete beträgt 300
when(tenant.getAccountBalance()).thenReturn(1000); // Mieter hat 1000 Guthaben
when(owner.getAccountBalance()).thenReturn(2000); // Vermieter hat 2000 Guthaben
// Act: Spieler besucht das Grundstück
property.accept(tenant);
// Assert: Überprüfen, ob die Miete korrekt verarbeitet wurde
verify(tenant).pay(300); // Spieler zahlt 300
verify(owner).earnMoney(300); // Eigentümer erhält 300
assertEquals(700, tenant.getAccountBalance()); // Mieter hat noch 700 übrig
assertEquals(2300, owner.getAccountBalance()); // Vermieter hat jetzt 2300
}
*/
/**
* T61: Überprüfen, ob der Spieler bei fehlendem Guthaben die Miete nicht zahlen kann.
*/
@Test
public void testRentPaymentFailed() {
// Arrange: Mock-Objekte für Spieler und Grundstück
Player tenant = spy(new Player(1, mock(PlayerHandler.class))); // Spieler, der die Miete zahlen soll
Player owner = spy(new Player(2, mock(PlayerHandler.class))); // Vermieter
BuildingProperty property = mock(BuildingProperty.class); // Grundstück
// Voraussetzungen
when(property.getOwner()).thenReturn(owner); // Eigentümer des Grundstücks
when(property.calcRent()).thenReturn(300); // Berechnete Miete
when(tenant.getAccountBalance()).thenReturn(200); // Guthaben des Mieters unzureichend
when(owner.getAccountBalance()).thenReturn(2000); // Guthaben des Vermieters
// Act: Spieler besucht das Grundstück
property.accept(tenant);
// Assert: Überprüfe, dass keine Zahlung stattfindet
verify(tenant, never()).pay(300); // Spieler zahlt nichts
verify(owner, never()).earnMoney(300); // Vermieter erhält nichts
assertEquals(200, tenant.getAccountBalance()); // Guthaben des Mieters bleibt unverändert
assertEquals(2000, owner.getAccountBalance()); // Guthaben des Vermieters bleibt unverändert
}
/**
* T63: Überprüfen, ob der Spieler aufgrund einer Strafe eine Runde aussetzen muss.
*/
/*
@Test
public void testSkipTurnDueToPenalty() {
// Arrange
Player player = mock(Player.class);
PlayerHandler handler = mock(PlayerHandler.class);
when(handler.nextPlayer()).thenReturn(player); // Spielerwechsel simulieren
EventField penaltyField = mock(EventField.class);
ServerGameLogic logic = mock(ServerGameLogic.class);
when(penaltyField.accept(player)).then(invocation -> {
player.skipTurn(); // Spieler erhält eine Strafe und muss aussetzen
return null;
});
// Act
penaltyField.accept(player);
// Assert
verify(player).skipTurn(); // Spieler muss Runde aussetzen
}
*/
/**
T68: Überprüfen, ob die Anzahl der "Gulag-Frei"-Karten nach der Verwendung korrekt abgezogen wird
*/
@Test
public void testDeductGulagFreeCard() {
// Arrange
Player player = new Player(1, mock(PlayerHandler.class));
player.addJailCard(); // Spieler besitzt eine Karte
player.addJailCard(); // Spieler besitzt zwei Karten
assertEquals(2, player.getNumJailCard()); // Sicherstellen, dass Spieler 2 Karten hat
// Act
player.removeJailCard(); // Spieler verwendet eine Karte
// Assert
assertEquals(1, player.getNumJailCard()); // Anzahl der Karten sollte um 1 reduziert sein
}
/**
* T070: Überprüfen, ob der Spieler erfolgreich aus dem Gulag rauswürfeln kann.
*/
/*
@Test
public void testRollToExitGulag() {
// Arrange
Player player = new Player(1, mock(PlayerHandler.class));
player.addJailCard();
player.state = player.new JailState();
DiceResult successfulRoll = new DiceResult(List.of(3, 3)); // Doppelte gewürfelt
when(player.rollDice()).thenReturn(successfulRoll);
// Act
DiceResult rollResult = player.rollDice();
// Assert
assertTrue(rollResult.isDoublets()); // Sicherstellen, dass doppelte gewürfelt wurde
assertTrue(player.state instanceof Player.ActiveState); // Spieler ist jetzt aktiv
}
*/
/**
* T071: Überprüfen, ob das Rauswürfeln aus dem Gulag fehlschlägt.
*/
/*
@Test
public void testFailRollToExitGulag() {
// Arrange
Player player = new Player(1, mock(PlayerHandler.class));
player.state = player.new JailState();
DiceResult unsuccessfulRoll = new DiceResult(List.of(1, 2)); // Keine Doppel
when(player.rollDice()).thenReturn(unsuccessfulRoll);
// Act
DiceResult rollResult = player.rollDice();
// Assert
assertFalse(rollResult.isDoublets()); // Sicherstellen, dass keine Doppel gewürfelt wurden
assertTrue(player.state instanceof Player.JailState); // Spieler bleibt im Gulag
}
*/
/**
* T072: Überprüfen, ob der Spieler durch Zahlung erfolgreich das Gulag verlassen kann.
*/
/*
@Test
public void testPayToExitGulag() {
// Arrange
Player player = new Player(1, mock(PlayerHandler.class));
player.state = player.new JailState();
player.earnMoney(500); // Spieler hat genug Geld
// Act
player.payBail();
// Assert
assertTrue(player.state instanceof Player.ActiveState); // Spieler ist jetzt aktiv
assertEquals(0, player.getAccountBalance()); // Geld korrekt abgezogen
}
*/
/**
* T073: Überprüfen, ob der Ausstieg aus dem Gulag durch Zahlung fehlschlägt, wenn nicht genug Geld vorhanden ist.
*/
/*
@Test
public void testFailPayToExitGulag() {
// Arrange
Player player = new Player(1, mock(PlayerHandler.class));
player.state = player.new JailState();
player.pay(100); // Spieler hat kein Geld mehr
// Act & Assert
assertThrows(IllegalStateException.class, () -> player.payBail());
assertTrue(player.state instanceof Player.JailState); // Spieler bleibt im Gulag
}
*/
/*
/**
* T074: Überprüfen, ob der Spieler eine "Gulag-Frei"-Karte erfolgreich nutzen kann.
*/
/*
@Test
public void testUseGulagFreeCardToExit() {
// Arrange
Player player = new Player(1, mock(PlayerHandler.class));
player.state = player.new JailState();
player.addJailCard(); // Spieler besitzt eine Karte
// Act
player.useJailCard();
// Assert
assertEquals(0, player.getNumJailCard()); // Karte wurde verbraucht
assertTrue(player.state instanceof Player.ActiveState); // Spieler ist jetzt aktiv
}
*/
/**
* T075: Überprüfen, ob der Spieler das Gulag nicht verlassen kann, wenn keine "Gulag-Frei"-Karten verfügbar sind.
*/
/*
@Test
public void testFailUseGulagFreeCardToExit() {
// Arrange
Player player = new Player(1, mock(PlayerHandler.class));
player.state = player.new JailState();
// Act & Assert
assertThrows(IllegalStateException.class, () -> player.useJailCard());
assertTrue(player.state instanceof Player.JailState); // Spieler bleibt im Gulag
}
*/
}

View File

@@ -0,0 +1,9 @@
package pp.monopoly.model;
import org.junit.Test;
import static org.junit.Assert.fail;
public class RandomPositionIteratorTest {
}

Some files were not shown because too many files have changed in this diff Show More