diff --git a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyApp.java b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyApp.java index 6fbda6a..e92f74e 100644 --- a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyApp.java +++ b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/MonopolyApp.java @@ -1,85 +1,380 @@ +//////////////////////////////////////// +// 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.concurrent.ExecutorService; -import java.util.concurrent.Executors; - +import com.jme3.app.DebugKeysAppState; import com.jme3.app.SimpleApplication; +import com.jme3.app.StatsAppState; import com.jme3.font.BitmapFont; import com.jme3.font.BitmapText; import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; import com.jme3.system.AppSettings; import com.simsilica.lemur.GuiGlobals; import com.simsilica.lemur.style.BaseStyles; - +import pp.monopoly.game.client.MonopolyClient; +import pp.monopoly.game.client.ClientGameLogic; +import pp.monopoly.game.client.ServerConnection; +import pp.monopoly.notification.ClientStateEvent; +import pp.monopoly.notification.GameEventListener; +import pp.monopoly.notification.InfoTextEvent; import pp.dialog.DialogBuilder; import pp.dialog.DialogManager; import pp.graphics.Draw; -import pp.monopoly.client.gui.*; -import pp.monopoly.client.gui.popups.BuildingPropertyCard; -import pp.monopoly.client.gui.popups.BuyCard; -import pp.monopoly.client.gui.popups.FoodFieldCard; -import pp.monopoly.client.gui.popups.GateFieldCard; -import pp.monopoly.game.client.ClientGameLogic; -import pp.monopoly.game.client.MonopolyClient; -import pp.monopoly.game.client.ServerConnection; -import pp.monopoly.model.fields.BoardManager; -import pp.monopoly.notification.GameEventListener; -import pp.monopoly.notification.InfoTextEvent; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.logging.LogManager; +import static pp.monopoly.Resources.lookup; + +/** + * The main class for the Battleship client application. + * It manages the initialization, input setup, GUI setup, and game states for the client. + */ 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 ExecutorService executor; - private final Draw draw; - private SettingsMenu settingsMenu; - private TestWorld testWorld; - private boolean isSettingsMenuOpen = false; - private boolean inputBlocked = false; - private NetworkSupport networkSupport; - private BoardManager boardManager = new BoardManager(); - - // TODO Temp später entfernen - - private BuildingPropertyCard buildingProperty; - private FoodFieldCard foodField; - private GateFieldCard gateField; - private BuyCard buyCard; - private boolean isBuyCardPopupOpen = false; - private final ActionListener BListener = (name, isPressed, tpf) -> handleB(isPressed); + /** + * Logger for logging messages within the application. + */ + private static final Logger LOGGER = System.getLogger(MonopolyApp.class.getName()); /** * Path to the styles script for GUI elements. */ - private static final String STYLES_SCRIPT = "Interface/Lemur/pp-styles.groovy"; // NON-NLS + 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 + private static final String FONT = "Interface/Fonts/Default.fnt"; //NON-NLS + /** + * Path to the client configuration file, if one exists. + */ + private static final File CONFIG_FILE = new File("client.properties"); + + /** + * Input mapping name for mouse clicks. + */ + public static final String CLICK = "CLICK"; + + /** + * Input mapping name for the Escape key. + */ + private static final String ESC = "ESC"; + + /** + * Manager for handling dialogs within the application. + */ + private final DialogManager dialogManager = new DialogManager(this); + + /** + * The server connection instance, used for communicating with the game server. + */ + private final ServerConnection serverConnection; + + /** + * Instance of the {@link Draw} class for rendering graphics. + */ + private Draw draw; + + /** + * Text display at the top of the GUI for showing information to the user. + */ + private BitmapText topText; + + /** + * Executor service for handling asynchronous tasks within the application. + */ + private ExecutorService executor; + + /** + * Handler for managing the client's game logic. + */ + private final ClientGameLogic logic; + + /** + * Configuration settings for the Battleship client application. + */ + private final MonopolyAppConfig config; + + /** + * Listener for handling actions triggered by the Escape key. + */ + private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed); + + static { + // Configure logging + LogManager manager = LogManager.getLogManager(); + try { + manager.readConfiguration(new FileInputStream("logging.properties")); + LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS + } + catch (IOException e) { + LOGGER.log(Level.INFO, e.getMessage()); + } + } + + /** + * Starts the Battleship application. + * + * @param args Command-line arguments for launching the application. + */ public static void main(String[] args) { new MonopolyApp().start(); } - public MonopolyApp() { - this.draw = new Draw(assetManager); + /** + * Constructs a new {@code MonopolyApp} instance. + * Initializes the configuration, server connection, and game logic listeners. + */ + private MonopolyApp() { config = new MonopolyAppConfig(); - serverConnection = new NetworkSupport(this); // Initialize NetworkSupport + config.readFromIfExists(CONFIG_FILE); + serverConnection = makeServerConnection(); logic = new ClientGameLogic(serverConnection); logic.addListener(this); setShowSettings(config.getShowSettings()); setSettings(makeSettings()); } - /** + /** + * Creates and configures application settings from the client configuration. + * + * @return A configured {@link AppSettings} object. + */ + private AppSettings makeSettings() { + final AppSettings settings = new AppSettings(true); + settings.setTitle(lookup("battleship.name")); + settings.setResolution(config.getResolutionWidth(), config.getResolutionHeight()); + settings.setFullscreen(config.fullScreen()); + settings.setUseRetinaFrameBuffer(config.useRetinaFrameBuffer()); + settings.setGammaCorrection(config.useGammaCorrection()); + return settings; + } + + /** + * Factory method for creating a server connection based on the current + * client configuration. + * + * @return A {@link ServerConnection} instance, which could be a real or mock server. + */ + private ServerConnection makeServerConnection() { + return new NetworkSupport(this); + } + + /** + * Returns the dialog manager responsible for managing in-game dialogs. + * + * @return The {@link DialogManager} instance. + */ + public DialogManager getDialogManager() { + return dialogManager; + } + + /** + * Returns the game logic handler for the client. + * + * @return The {@link ClientGameLogic} instance. + */ + @Override + public ClientGameLogic getGameLogic() { + return logic; + } + + /** + * Returns the current configuration settings for the Battleship client. + * + * @return The {@link BattleshipClientConfig} instance. + */ + @Override + public MonopolyAppConfig getConfig() { + return config; + } + + /** + * Initializes the application. + * Sets up input mappings, GUI, game states, and connects to the server. + */ + @Override + public void simpleInitApp() { + setPauseOnLostFocus(false); + draw = new Draw(assetManager); + setupInput(); + setupStates(); + setupGui(); + serverConnection.connect(); + } + + /** + * Sets up the graphical user interface (GUI) for the application. + */ + private void setupGui() { + GuiGlobals.initialize(this); + BaseStyles.loadStyleResources(STYLES_SCRIPT); + GuiGlobals.getInstance().getStyles().setDefaultStyle("pp"); //NON-NLS + final BitmapFont normalFont = assetManager.loadFont(FONT); //NON-NLS + topText = new BitmapText(normalFont); + final int height = context.getSettings().getHeight(); + topText.setLocalTranslation(10f, height - 10f, 0f); + topText.setColor(config.getTopColor()); + guiNode.attachChild(topText); + } + + /** + * Configures input mappings and sets up listeners for user interactions. + */ + private void setupInput() { + inputManager.deleteMapping(INPUT_MAPPING_EXIT); + inputManager.setCursorVisible(false); + inputManager.addMapping(ESC, new KeyTrigger(KeyInput.KEY_ESCAPE)); + inputManager.addMapping(CLICK, new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(escapeListener, ESC); + } + + /** + * Initializes and attaches the necessary application states for the game. + */ + private void setupStates() { + if (config.getShowStatistics()) { + final BitmapFont normalFont = assetManager.loadFont(FONT); //NON-NLS + final StatsAppState stats = new StatsAppState(guiNode, normalFont); + stateManager.attach(stats); + } + flyCam.setEnabled(false); + stateManager.detach(stateManager.getState(StatsAppState.class)); + stateManager.detach(stateManager.getState(DebugKeysAppState.class)); + + attachGameSound(); + attachGameMusic(); + } + + /** + * Attaches the game sound state and sets its initial enabled state. + */ + private void attachGameSound() { + final GameSound gameSound = new GameSound(); + logic.addListener(gameSound); + gameSound.setEnabled(GameSound.enabledInPreferences()); + stateManager.attach(gameSound); + } + + /** + * Attaches the background music state and sets its initial enabled state. + */ + private void attachGameMusic() { + final GameMusic gameSound = new GameMusic(); + gameSound.setEnabled(GameMusic.enabledInPreferences()); + stateManager.attach(gameSound); + } + + /** + * Updates the application state every frame. + * This method is called once per frame during the game loop. + * + * @param tpf Time per frame in seconds. + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + dialogManager.update(tpf); + logic.update(tpf); + } + + /** + * Handles the Escape key action to either close the top dialog or show the main menu. + * + * @param isPressed Indicates whether the Escape key is pressed. + */ + private void escape(boolean isPressed) { + if (!isPressed) return; + if (dialogManager.showsDialog()) + dialogManager.escape(); + else + new Menu(this).open(); + } + + /** + * Returns the {@link Draw} instance used for rendering graphical elements in the game. + * + * @return The {@link Draw} instance. + */ + public Draw getDraw() { + return draw; + } + + /** + * Handles a request to close the application. + * If the request is initiated by pressing ESC, this parameter is true. + * + * @param esc If true, the request is due to the ESC key being pressed. + */ + @Override + public void requestClose(boolean esc) { /* do nothing */ } + + /** + * Closes the application, displaying a confirmation dialog if the client is connected to a server. + */ + public void closeApp() { + if (serverConnection.isConnected()) + confirmDialog(lookup("confirm.leaving"), this::close); + else + close(); + } + + /** + * Closes the application, disconnecting from the server and stopping the application. + */ + private void close() { + serverConnection.disconnect(); + stop(); + } + + /** + * Updates the informational text displayed in the GUI. + * + * @param text The information text to display. + */ + public void setInfoText(String text) { + LOGGER.log(Level.DEBUG, "setInfoText {0}", text); //NON-NLS + topText.setText(text); + } + + /** + * Updates the informational text in the GUI based on the key received in an {@link InfoTextEvent}. + * + * @param event The {@link InfoTextEvent} containing the key for the text to display. + */ + @Override + public void receivedEvent(InfoTextEvent event) { + LOGGER.log(Level.DEBUG, "received info text {0}", event.key()); //NON-NLS + setInfoText(lookup(event.key())); + } + + /** + * Handles client state events to update the game states accordingly. + * + * @param event The {@link ClientStateEvent} representing the state change. + */ + @Override + public void receivedEvent(ClientStateEvent event) { + } + + /** * Returns the executor service used for handling multithreaded tasks. * * @return The {@link ExecutorService} instance. @@ -101,183 +396,33 @@ public class MonopolyApp extends SimpleApplication implements MonopolyClient, Ga super.stop(waitFor); } - @Override - public MonopolyAppConfig getConfig() { - return config; - } - - @Override - public ClientGameLogic getGameLogic() { - return logic; - } - - public BoardManager getBoardManager() { - return boardManager; - } - - public NetworkSupport getNetworkSupport() { - return networkSupport; - } - - 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 - BaseStyles.loadStyleResources("com/simsilica/lemur/style/base/glass-styles.groovy"); - GuiGlobals.getInstance().getStyles(); - 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"); - - inputManager.addMapping("B", new KeyTrigger(KeyInput.KEY_B)); - inputManager.addListener(BListener, "B"); - } - - private 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); - } - } - } - - //logik zum wechselnden erscheinen und verschwinden beim drücken von B //TODO süäter entfernen - private void handleB(boolean isPressed) { - if (isPressed) { - if (gateField != null && isBuyCardPopupOpen) { - // Schließe das SettingsMenu - System.out.println("Schließe BuyCardPopup..."); - gateField.close(); - gateField = null; - setBuyCardPopupOpen(false); - } else { - // Öffne das SettingsMenu - System.out.println("Öffne BuyCardPopup..."); - gateField = new GateFieldCard(this); - gateField.open(); - setBuyCardPopupOpen(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()); - } - - public DialogManager getDialogManager() { - return dialogManager; - } - - public Draw getDraw() { - return draw; - } - - public void closeApp() { - stop(); + /** + * Displays a confirmation dialog with a specified question and action for the "Yes" button. + * + * @param question The question to display in the dialog. + * @param yesAction The action to perform if "Yes" is selected. + */ + public void confirmDialog(String question, Runnable yesAction) { + DialogBuilder.simple(dialogManager) + .setTitle(lookup("dialog.question")) + .setText(question) + .setOkButton(lookup("button.yes"), yesAction) + .setNoButton(lookup("button.no")) + .build() + .open(); } + /** + * Displays an error dialog with the specified error message. + * + * @param errorMessage The error message to display in the dialog. + */ public void errorDialog(String errorMessage) { DialogBuilder.simple(dialogManager) - .setTitle("Fehler") - .setText(errorMessage) - .setOkButton("OK") - .build() - .open(); - } - - public void setSettingsMenuOpen(boolean isOpen) { - this.isSettingsMenuOpen = isOpen; - } - - // TODO später entfernen - - public void setBuyCardPopupOpen(boolean isOpen) { - this.isBuyCardPopupOpen = 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 - } - - // TODO später entfernen - - public void startBuyCard() { - } - - public void returnToMenu() { - guiNode.detachAllChildren(); // Entferne die GUI - StartMenu.createStartMenu(this); // Zeige das Startmenü erneut - } - - public ServerConnection getServerConnection() { - return serverConnection; + .setTitle(lookup("dialog.error")) + .setText(errorMessage) + .setOkButton(lookup("button.ok")) + .build() + .open(); } }