From 8b3220e343d8a7abbb341e11969f15cac0b9cccb Mon Sep 17 00:00:00 2001 From: = Date: Tue, 26 May 2026 03:41:29 +0200 Subject: [PATCH] Feat: Saving for everything other than stats and financial summary --- .../ntnu/idi/idatt2003/g40/mappe/Main.java | 160 +++++------- .../idatt2003/g40/mappe/engine/Exchange.java | 31 ++- .../idi/idatt2003/g40/mappe/model/Player.java | 33 ++- .../idatt2003/g40/mappe/model/SaveGame.java | 156 +++++++---- .../g40/mappe/service/GameStateLoader.java | 245 +++++++----------- .../g40/mappe/service/SaveGameService.java | 62 ++++- .../view/creategame/CreateGameController.java | 94 ++++++- .../dashboard/DashBoardController.java | 23 +- .../view/widgets/dashboard/DashBoardView.java | 25 ++ .../financialsummary/SummaryController.java | 74 ++++-- .../view/widgets/market/MarketController.java | 30 ++- .../mappe/view/widgets/market/MarketView.java | 11 + .../minigames/games/FindStockGame.java | 18 +- .../view/widgets/stats/StatsController.java | 54 +++- src/main/resources/saves/Tommy.json | 43 --- 15 files changed, 648 insertions(+), 411 deletions(-) delete mode 100644 src/main/resources/saves/Tommy.json diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java index 52f90fd..f5470bf 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java @@ -12,6 +12,7 @@ import edu.ntnu.idi.idatt2003.g40.mappe.utils.ConfigValues; import edu.ntnu.idi.idatt2003.g40.mappe.utils.ThemeManager; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewManager; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewEnum; import edu.ntnu.idi.idatt2003.g40.mappe.view.creategame.CreateGameController; import edu.ntnu.idi.idatt2003.g40.mappe.view.creategame.CreateGameView; import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameController; @@ -45,32 +46,22 @@ import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.topbar.TopBarView; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.transactions.TransactionsController; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.transactions.TransactionsView; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType; // Added EventType import import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.text.Font; import javafx.stage.Stage; -/** - * Main class. - * - *

- * Extends {@link Application} - *

- * - *

- * Initializes the application through the javafx framework. - *

- */ public class Main extends Application { - static void main(String[] args) { + private Exchange exchange; + private Player player; + + public static void main(String[] args) { launch(args); } - /** - * {@inheritDoc} - */ @Override public void start(final Stage stage) throws Exception { Scene scene = new Scene(new Pane()); @@ -81,145 +72,134 @@ public void start(final Stage stage) throws Exception { stage.setWidth(ConfigValues.VIEWPORT_WIDTH.getValue()); stage.setHeight(ConfigValues.VIEWPORT_HEIGHT.getValue()); - // Register the scene with the theme manager so it can toggle dark/light. ThemeManager.getInstance().registerScene(scene); EventManager eventManager = new EventManager(); ViewManager viewManager = new ViewManager(stage, eventManager); - List stocksInFile; + // Initial fallback data loop configuration StockFileManager fileManager = new StockFileManager("src/main/resources/sp500.csv"); - StockFileParser fileParser = new StockFileParser(); - stocksInFile = fileParser.getStocksFromStrings(fileManager.readFile()); + List stocksInFile = fileParser.getStocksFromStrings(fileManager.readFile()); - Exchange exchange = new Exchange("Exchange", stocksInFile); - Player player = new Player("Player 1", new BigDecimal("10000")); + exchange = new Exchange("Exchange", stocksInFile); + player = new Player("Player 1", new BigDecimal("10000")); - // Bridges the save list to the live player + exchange. Subscribes - // to LOAD_SAVE; mutates state; publishes STATE_RESET for widgets. GameStateLoader gameStateLoader = new GameStateLoader( player, exchange, stocksInFile, eventManager); - // Main menu MainMenuView mainMenuView = new MainMenuView(); new MainMenuController(mainMenuView, eventManager); - // Play game (mellom hovedmeny og spillet) PlayGameView playGameView = new PlayGameView(); SaveGameService saveGameService = new SaveGameService(); PlayGameController playGameController = new PlayGameController(playGameView, eventManager, saveGameService); playGameController.refresh(); - - // Create game (mellom play-game og selve spillet) CreateGameView createGameView = new CreateGameView(); CreateGameController createGameController = new CreateGameController(createGameView, eventManager, saveGameService); - // Refresh save-listen etter at en ny save er skrevet til disk. createGameController.setOnSaveCreated(playGameController::refresh); - // Settings SettingsView settingsView = new SettingsView(); new SettingsController(settingsView, eventManager); - // Summary section of the top bar SummaryView summaryView = new SummaryView(); - new SummaryController(summaryView, eventManager, exchange, player); + SummaryController summaryController = + new SummaryController(summaryView, eventManager, exchange, player); - // Top bar with summary section TopBarView topBarView = new TopBarView(summaryView); TopBarController topBarController = new TopBarController(topBarView, eventManager); - // Top bar without summary section TopBarView topBarView2 = new TopBarView(); new TopBarController(topBarView2, eventManager); - // Dashboard (default center-view in-game - første siden du ser) DashBoardView dashBoardView = new DashBoardView(); - new DashBoardController(dashBoardView, - eventManager, - player, - exchange, - stocksInFile); + DashBoardController dashBoardController = + new DashBoardController(dashBoardView, eventManager, player, exchange, stocksInFile); - // Stats page (Stats-knappen i topbaren tar deg hit) StatsView statsView = new StatsView(); - new StatsController(statsView, eventManager, player, exchange); + StatsController statsController = + new StatsController(statsView, eventManager, player, exchange); - // Market page (Market-knappen tar deg hit) MarketView marketView = new MarketView(); - new MarketController(marketView, - eventManager, - player, - exchange, - stocksInFile); - - // In-game (Change "topBarView" to "topBarView2" if no summary section). - // Dashboard er default center-view. + MarketController marketController = + new MarketController(marketView, eventManager, player, exchange, stocksInFile); + InGameView inGameView = new InGameView(topBarView, dashBoardView.getRootPane()); - InGameController inGameController = new InGameController( - inGameView, - eventManager - ); + InGameController inGameController = new InGameController(inGameView, eventManager); - // Transaction history page TransactionsView transactionsView = new TransactionsView(); TransactionsController transactionsController = new TransactionsController( - transactionsView, - eventManager, - player.getTransactionArchive()); + transactionsView, eventManager, player.getTransactionArchive()); ClickerGame clickerGame = new ClickerGame(); FindStockGame findStockGame = new FindStockGame( - stocksInFile.stream() - .map(Stock::getSymbol) - .toList() + stocksInFile.stream().map(Stock::getSymbol).toList() ); TimeInputsGame timeInputsGame = new TimeInputsGame(); GameEngineView gameEngineView = new GameEngineView(); GameEngineController gameEngineController = new GameEngineController( - gameEngineView, - eventManager, - stocksInFile.getFirst() + gameEngineView, eventManager, stocksInFile.getFirst() ); MiniGamesView miniGamesView = new MiniGamesView(); - new MiniGamesController( - miniGamesView, - eventManager, - stocksInFile.getFirst(), - gameEngineView, - gameEngineController, - clickerGame, - inGameView, - findStockGame, - timeInputsGame + MiniGamesController miniGamesController = + new MiniGamesController( + miniGamesView, eventManager, stocksInFile.getFirst(), gameEngineView, + gameEngineController, clickerGame, inGameView, findStockGame, timeInputsGame ); - // Wire top bar buttons til å bytte mellom dashboard / stats / market / - // transactions. Stats-knappen tar deg til stats-siden. + // --- NEW: Dynamic Context Interceptor Hook --- + // When a game loads, update controllers that keep internal stock arrays + eventManager.addSubscriber(new edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventSubscriber() { + @Override + public void handleEvent(final edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData eventData) { + if (eventData.data() instanceof SaveGame save) { + + // Only pull from CreateGameController if it contains references + if (createGameController.getExchange() != null && createGameController.getPlayer() != null) { + gameStateLoader.setExchange(createGameController.getExchange()); + + exchange = createGameController.getExchange(); + player = createGameController.getPlayer(); + } else { + // Default directly to what GameStateLoader just populated from disk + exchange = gameStateLoader.getExchange(); + player = gameStateLoader.getPlayer(); + } + + // Pull from the explicitly assigned exchange instance + List dynamicStocks = exchange.getStocks(); + + // Update UI contexts + findStockGame.updateStockPool(dynamicStocks.stream().map(Stock::getSymbol).toList()); + summaryController.handleContextUpdate(exchange, player); + dashBoardController.handleStockPoolUpdate(dynamicStocks); + statsController.handleContextUpdate(exchange, player); + marketController.handleStockPoolUpdate(exchange, player, dynamicStocks); + + if (!dynamicStocks.isEmpty()) { + miniGamesController.setActiveStock(dynamicStocks.getFirst()); + } + } + } + }, EventType.STATE_RESET); + // --------------------------------------------- + topBarController.setMarketIntegration( - inGameView::changeCenterView, - dashBoardView.getRootPane(), - marketView.getRootPane(), - statsView.getRootPane(), - transactionsView.getRootPane(), - transactionsController::refresh, + inGameView::changeCenterView, dashBoardView.getRootPane(), marketView.getRootPane(), + statsView.getRootPane(), transactionsView.getRootPane(), transactionsController::refresh, miniGamesView.getRootPane() ); - // In-game settings overlay (Dark / Light theme toggle). InGameSettingsView inGameSettingsView = new InGameSettingsView(); InGameSettingsController inGameSettingsController = new InGameSettingsController(inGameSettingsView, eventManager, inGameView); topBarController.setSettingsAction(inGameSettingsController::show); - // Auto-save the currently active save when the player quits back - // to the main menu. Silent if no save is active (i.e. the user - // got into the in-game scene without going through the save list). topBarController.setOnQuitToMainMenu(() -> { System.out.println("[auto-save] Quit triggered, attempting snapshot..."); SaveGame snapshot = gameStateLoader.snapshotActiveSave(); @@ -227,21 +207,14 @@ public void start(final Stage stage) throws Exception { System.out.println("[auto-save] No active save - nothing to write."); return; } - System.out.println("[auto-save] Snapshot built for '" + snapshot.getName() - + "', balance=" + snapshot.getBalance() - + ", week=" + snapshot.getWeek() - + ", shares=" + snapshot.getOwnedShares().size() - + ", txns=" + snapshot.getTransactions().size()); try { saveGameService.saveGame(snapshot); System.out.println("[auto-save] Wrote save '" + snapshot.getName() + "' to disk."); } catch (Exception e) { System.err.println("[auto-save] Failed: " + e.getMessage()); - e.printStackTrace(); } }); - // Register all views viewManager.addView(mainMenuView); viewManager.addView(playGameView); viewManager.addView(createGameView); @@ -249,7 +222,6 @@ public void start(final Stage stage) throws Exception { viewManager.addView(inGameView); viewManager.setScene(mainMenuView); - // Register all widgets inGameController.addwidget(WidgetEnum.DASHBOARD, dashBoardView.getRootPane()); inGameController.addwidget(WidgetEnum.MARKET, marketView.getRootPane()); inGameController.addwidget(WidgetEnum.MINIGAMES_OVERVIEW, miniGamesView.getRootPane()); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java index 0ef3b95..25402a3 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java @@ -109,7 +109,7 @@ public ReadOnlyIntegerProperty weekProperty() { *

* Used by the save-loading flow when applying a * {@link edu.ntnu.idi.idatt2003.g40.mappe.model.SaveGame} to the - * exchange. The {@link IntegerProperty} fires a change event so any + * exchange. The {@link ReadOnlyIntegerProperty} fires a change event so any * listeners that re-render on week changes refresh themselves. *

* @@ -163,6 +163,15 @@ public boolean hasStock(final String symbol) { return stockMap.containsKey(symbol); } + /** + * Getter method for all stocks in exchange. + * + * @return List of stocks. + * */ + public List getStocks() { + return List.copyOf(this.stockMap.values()); + } + /** * Getter method for stock element. * @@ -387,4 +396,24 @@ public List getLosers(final int limit) .map(Map.Entry::getValue) .toList(); } + + /** + * Clears the current stock listings and replaces them with a new pool of stocks. + * + *

Used when loading a save game that uses a completely different set of + * stocks than the currently active simulation context.

+ * + * @param newStocks the new list of {@link Stock} objects to list on the exchange. + * @throws IllegalArgumentException if the provided list is null or empty. + */ + public void updateStockPool(final List newStocks) throws IllegalArgumentException { + if (newStocks == null || newStocks.isEmpty()) { + throw new IllegalArgumentException("New stock pool cannot be null or empty!"); + } + + this.stockMap.clear(); + for (Stock stock : newStocks) { + this.stockMap.put(stock.getSymbol(), stock); + } + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java index 9e5ac5f..1c3cbbd 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java @@ -5,6 +5,9 @@ import edu.ntnu.idi.idatt2003.g40.mappe.exceptions.NotEnoughMoneyException; import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + import javafx.beans.property.ReadOnlyFloatProperty; import javafx.beans.property.ReadOnlyFloatWrapper; @@ -39,6 +42,11 @@ public final class Player { * */ private BigDecimal money; + /** + * History record tracking the player's net worth values over time. + */ + private final List netWorthHistory = new ArrayList<>(); + /** * Current net-worth of player as a listenable, * read-only, {@link ReadOnlyFloatWrapper} object. @@ -91,6 +99,7 @@ public Player(final String name, this.money = this.startingMoney; this.portfolio = new Portfolio(); this.transactionArchive = new TransactionArchive(); + this.netWorthHistory.add(this.startingMoney); updateObservableProperties(); } @@ -121,6 +130,28 @@ public BigDecimal getMoney() { return money; } + /** + * Returns a read-only list of the player's historical net worth data points. + * + * @return immutable sequence list of tracking records. + */ + public List getNetWorthHistory() { + return List.copyOf(netWorthHistory); + } + + /** + * Clears out current history logs and replaces them with an explicit sequence list. + * Essential utility hook used by the save-game parsing framework. + * + * @param history the historic snapshots sequence data payload. + */ + public void setNetWorthHistory(final List history) { + this.netWorthHistory.clear(); + if (history != null) { + this.netWorthHistory.addAll(history); + } + } + /** * Adds money to the players balance. * @@ -185,7 +216,7 @@ public void setMoney(final BigDecimal newMoney) throws IllegalArgumentException /** * Re-publishes the players current money and net-worth to the - * {@link FloatProperty} listeners. + * {@link ReadOnlyFloatWrapper} listeners. * *

* Used after the save-loading flow has mutated the player state diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java index ac406fb..74bfaf9 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java @@ -2,6 +2,7 @@ import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; +import java.math.BigDecimal; import java.util.Collections; import java.util.List; @@ -11,20 +12,8 @@ *

* Holds the display name, current balance, starting capital, * (optionally) the path to a custom stock data file, the in-game week, - * the list of owned shares and the list of committed transactions for a - * single saved game. - *

- * - *

- * When {@link #getStockDataPath()} returns {@code null} the save is - * expected to be loaded with the default bundled stock data file. - *

- * - *

- * Older saves on disk may not contain {@link #getWeek()}, - * {@link #getOwnedShares()} or {@link #getTransactions()}; in that case - * the convenience constructor defaults week to 1 and the two lists to - * empty, so the save still loads cleanly. + * the list of owned shares, the list of committed transactions, and + * the current state of the exchange stocks for a single saved game. *

*/ public class SaveGame { @@ -41,7 +30,7 @@ public class SaveGame { /** * Absolute path to a custom stock data file, or {@code null} * if the save should use the default bundled stock data. - * */ + */ private final String stockDataPath; /** In-game week when the save was last written. */ @@ -53,19 +42,25 @@ public class SaveGame { /** Committed transactions when the save was last written. */ private final List transactions; + /** The snapshot state of the exchange's stocks when saved. */ + private final List exchangeStocks; + + /** The snapshot state of the players's networth history when saved. */ + private final List netWorthHistory; + /** - * Full constructor. + * Full constructor including exchange stocks and net worth history state tracking. * * @param name the display name of the save. * @param balance the current balance value. * @param startingCapital the starting capital chosen on creation. * @param stockDataPath absolute path to a custom stock data file, - * or {@code null} to use the default file. + * or {@code null} to use the default file. * @param week the in-game week when the save was written. - * @param ownedShares the shares the player owned (may be null; - * treated as empty). - * @param transactions the committed transactions (may be null; - * treated as empty). + * @param ownedShares the shares the player owned (may be null; treated as empty). + * @param transactions the committed transactions (may be null; treated as empty). + * @param exchangeStocks the current list of stocks from the exchange (may be null; treated as empty). + * @param netWorthHistory the history profile tracking trajectory (may be null; treated as empty). */ public SaveGame(final String name, final double balance, @@ -73,7 +68,9 @@ public SaveGame(final String name, final String stockDataPath, final int week, final List ownedShares, - final List transactions) { + final List transactions, + final List exchangeStocks, + final List netWorthHistory) { if (!Validator.NOT_EMPTY.isValid(name) || balance <= 0 || startingCapital <= 0) { @@ -90,49 +87,76 @@ public SaveGame(final String name, this.transactions = (transactions != null) ? List.copyOf(transactions) : Collections.emptyList(); + this.exchangeStocks = (exchangeStocks != null) + ? List.copyOf(exchangeStocks) + : Collections.emptyList(); + this.netWorthHistory = (netWorthHistory != null) + ? List.copyOf(netWorthHistory) + : Collections.emptyList(); } /** - * Convenience constructor matching the original "name + balance + capital - * + stockDataPath" format used by the create-game flow. - * - *

- * Week defaults to 1, owned shares and transactions default to empty - * lists. This keeps the create-game flow unchanged while older save - * files (which don't yet contain gameplay data) load with sensible - * defaults. - *

- * - * @param name the display name of the save. - * @param balance the current balance value. - * @param startingCapital the starting capital chosen on creation. - * @param stockDataPath absolute path to a custom stock data file, - * or {@code null} to use the default file. - * */ + * Overloaded constructor that converts a List of Floats to BigDecimals. + * Helps your JSON parser map the array smoothly on load. + */ + public SaveGame(final String name, + final double balance, + final double startingCapital, + final String stockDataPath, + final int week, + final List ownedShares, + final List transactions, + final List exchangeStocks, + final List floatNetWorthHistory, + final boolean dummyFlagForOverloading) { + this(name, balance, startingCapital, stockDataPath, week, ownedShares, transactions, exchangeStocks, + (floatNetWorthHistory != null) + ? floatNetWorthHistory.stream().map(BigDecimal::valueOf).toList() + : Collections.emptyList()); + } + + /** + * Legacy backward-compatible constructor matching your previous full configuration. + */ + public SaveGame(final String name, + final double balance, + final double startingCapital, + final String stockDataPath, + final int week, + final List ownedShares, + final List transactions, + final List exchangeStocks) { + this(name, balance, startingCapital, stockDataPath, week, ownedShares, transactions, exchangeStocks, Collections.emptyList()); + } + + /** + * Legacy backward-compatible constructor matching your previous configuration. + */ + public SaveGame(final String name, + final double balance, + final double startingCapital, + final String stockDataPath, + final int week, + final List ownedShares, + final List transactions) { + this(name, balance, startingCapital, stockDataPath, week, ownedShares, transactions, Collections.emptyList(), Collections.emptyList()); + } + + /** + * Convenience constructor matching the original format used by the create-game flow. + */ public SaveGame(final String name, final double balance, final double startingCapital, final String stockDataPath) { - this(name, balance, startingCapital, stockDataPath, - 1, Collections.emptyList(), Collections.emptyList()); + this(name, balance, startingCapital, stockDataPath, 1, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); } /** * Convenience constructor matching the legacy "name + balance" format. - * - *

- * Starting capital is defaulted to the balance value, - * {@code stockDataPath} is left {@code null} so the default stock - * data file is used, week defaults to 1 and the two gameplay lists - * default to empty. - *

- * - * @param name the display name of the save. - * @param balance the current balance value. */ public SaveGame(final String name, final double balance) { - this(name, balance, balance, null, - 1, Collections.emptyList(), Collections.emptyList()); + this(name, balance, balance, null, 1, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); } /** @@ -153,6 +177,15 @@ public double getBalance() { return balance; } + /** + * Getter method net-worth history. + * + * @return an immutable snapshot view of the net worth history. + */ + public List getNetWorthHistory() { + return netWorthHistory; + } + /** * Getter method for the starting capital. * @@ -166,7 +199,7 @@ public double getStartingCapital() { * Getter method for the custom stock data path. * * @return the absolute path to a custom stock data file, - * or {@code null} if the save uses the default data. + * or {@code null} if the save uses the default data. */ public String getStockDataPath() { return stockDataPath; @@ -176,7 +209,7 @@ public String getStockDataPath() { * Getter method for the in-game week. * * @return the week the save was last written at. - * */ + */ public int getWeek() { return week; } @@ -185,7 +218,7 @@ public int getWeek() { * Getter method for the owned shares. * * @return an immutable list of {@link OwnedShareData} entries. - * */ + */ public List getOwnedShares() { return ownedShares; } @@ -194,8 +227,17 @@ public List getOwnedShares() { * Getter method for the committed transactions. * * @return an immutable list of {@link TransactionData} entries. - * */ + */ public List getTransactions() { return transactions; } -} + + /** + * Getter method for the captured state of exchange stocks. + * + * @return an immutable list of {@link Stock} objects saved inside this snapshot. + */ + public List getExchangeStocks() { + return exchangeStocks; + } +} \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/GameStateLoader.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/GameStateLoader.java index 8cbd29e..78db068 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/GameStateLoader.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/GameStateLoader.java @@ -25,86 +25,22 @@ import java.util.Map; /** - * Service that applies a {@link SaveGame} to the active {@link Player} - * and {@link Exchange}. - * - *

- * Listens for {@link EventType#LOAD_SAVE} events. When one fires the - * service: - *

- *
    - *
  • Resets the stock prices on the exchange to the baseline they - * had when the application started.
  • - *
  • Resets the players money, portfolio and transaction archive.
  • - *
  • Re-applies the saved owned shares and transactions on top of - * that clean state.
  • - *
  • Sets the in-game week to the value stored in the save.
  • - *
  • Publishes a {@link EventType#STATE_RESET} event so widget - * controllers can refresh whatever derived state they hold - * (chart histories, week selectors, etc.).
  • - *
- * - *

- * Implements both {@link EventSubscriber} (to receive - * {@code LOAD_SAVE}) and {@link EventPublisher} (to emit - * {@code STATE_RESET}). Tracks the {@link SaveGame} most recently - * loaded so callers (the auto-save flow on quit) can ask which save - * is currently active. - *

+ * Service that applies a {@link SaveGame} to the active {@link Player} and {@link Exchange}. */ -public final class GameStateLoader - implements EventSubscriber, EventPublisher { +public final class GameStateLoader implements EventSubscriber, EventPublisher { - /** Active {@link Player} instance being mutated on load. */ private final Player player; - - /** Active {@link Exchange} instance being mutated on load. */ - private final Exchange exchange; - - /** Used to publish {@link EventType#STATE_RESET} after a load. */ + private Exchange exchange; // Non-final to allow runtime context swaps private final EventManager eventManager; - - /** - * Snapshot of every stock's price at construction time. - * - *

- * Captured so each save load can rewind the exchange to a known - * baseline regardless of how the simulation has drifted in this - * session. - *

- * */ private final Map baselinePrices; - /** - * The {@link SaveGame} most recently loaded, or {@code null} if no - * save has been loaded yet. - * - *

Used by the auto-save flow so it can write back to the same - * file the player opened.

- * */ private SaveGame activeSave; - /** - * Constructor. - * - * @param player the {@link Player} to apply saves to. - * @param exchange the {@link Exchange} to apply saves to. - * @param stockList the list of stocks from which to capture a - * baseline price snapshot. - * @param eventManager the active {@link EventManager}; the loader - * subscribes to {@link EventType#LOAD_SAVE} and - * publishes {@link EventType#STATE_RESET} - * through it. - * - * @throws IllegalArgumentException if any argument is null. - * */ public GameStateLoader(final Player player, final Exchange exchange, final List stockList, - final EventManager eventManager) - throws IllegalArgumentException { - if (player == null || exchange == null - || stockList == null || eventManager == null) { + final EventManager eventManager) throws IllegalArgumentException { + if (player == null || exchange == null || stockList == null || eventManager == null) { throw new IllegalArgumentException("Invalid GameStateLoader arguments!"); } this.player = player; @@ -115,31 +51,52 @@ public GameStateLoader(final Player player, } /** - * Getter method for the {@link SaveGame} most recently loaded. + * Updates the runtime exchange reference when loading a file custom configuration. + */ + public void setExchange(final Exchange dynamicExchange) { + if (dynamicExchange != null) { + this.exchange = dynamicExchange; + } + } + + /** + * Getter method for exchange. * - * @return the active save, or {@code null} if none has been loaded. + * @return exchange. * */ - public SaveGame getActiveSave() { - return activeSave; + public Exchange getExchange() { + return exchange; } /** - * Builds a fresh {@link SaveGame} snapshot of the current player - * and exchange state, using the active save's name and starting - * capital as identity. - * - *

- * Used by the auto-save flow when the player quits the in-game view - * back to the main menu. Returns {@code null} if no save is active - * (i.e. the player hasn't opened a save in this session). - *

+ * Getter method for player. * - * @return a snapshot {@link SaveGame}, or {@code null}. + * @return player. * */ - public SaveGame snapshotActiveSave() { - if (activeSave == null) { - return null; + public Player getPlayer() { + return player; + } + + /** + * Refreshes the internal baseline pricing snapshot map configuration. + */ + public void setBaseStocks(final List stockList) { + if (stockList != null) { + this.baselinePrices.clear(); + this.baselinePrices.putAll(captureBaseline(stockList)); } + } + + public SaveGame getActiveSave() { + return activeSave; + } + + /** + * Builds a fresh {@link SaveGame} snapshot, passing along the current exchange state. + */ + public SaveGame snapshotActiveSave() { + // Drop the activeSave == null early return entirely so brand new games can be saved! + List shares = new ArrayList<>(); for (Share s : player.getPortfolio().getShares()) { shares.add(new OwnedShareData( @@ -150,9 +107,7 @@ public SaveGame snapshotActiveSave() { List txns = new ArrayList<>(); for (Transaction t : player.getTransactionArchive().getTransactions()) { - TransactionType type = (t instanceof Sale) - ? TransactionType.SALE - : TransactionType.PURCHASE; + TransactionType type = (t instanceof Sale) ? TransactionType.SALE : TransactionType.PURCHASE; txns.add(new TransactionData( type, t.getShare().getStock().getSymbol(), @@ -161,68 +116,58 @@ public SaveGame snapshotActiveSave() { t.getWeek())); } + // Pull the name path configs safely using the live active models + String saveName = (activeSave != null) ? activeSave.getName() : player.getName(); + double startingCap = (activeSave != null) ? activeSave.getStartingCapital() : player.getStartingMoney().doubleValue(); + String stockPath = (activeSave != null) ? activeSave.getStockDataPath() : null; + return new SaveGame( - activeSave.getName(), + saveName, player.getMoney().doubleValue(), - activeSave.getStartingCapital(), - activeSave.getStockDataPath(), + startingCap, + stockPath, exchange.getWeek(), shares, - txns); + txns, + exchange.getStocks(), + player.getNetWorthHistory()); } - /** {@inheritDoc} */ @Override public void handleEvent(final EventData data) { if (data == null || !(data.data() instanceof SaveGame save)) { - throw new IllegalArgumentException( - "LOAD_SAVE event payload must be a SaveGame!"); + throw new IllegalArgumentException("LOAD_SAVE event payload must be a SaveGame!"); } - // Tell widget controllers to wipe their per-week history before - // we start the replay - otherwise history from the previous - // session leaks into the new chart. invoke(new EventData<>(EventType.PRE_LOAD_SAVE, save), eventManager); applySave(save); invoke(new EventData<>(EventType.STATE_RESET, save), eventManager); } - /** {@inheritDoc} */ @Override - public void invoke(final EventData data, - final EventManager manager) { + public void invoke(final EventData data, final EventManager manager) { manager.invokeEvent(data); } - /** - * Applies the contents of a {@link SaveGame} to the live player and - * exchange. - * - *

- * Resets stock prices to the captured baseline, then wipes and - * re-seeds the player's money, portfolio and transaction archive - * from the save, and finally sets the in-game week. - *

- * - * @param save the {@link SaveGame} to apply. - * */ private void applySave(final SaveGame save) { - System.out.println("[loader] applySave: name=" + save.getName() - + ", balance=" + save.getBalance() - + ", week=" + save.getWeek() - + ", shares=" + save.getOwnedShares().size() - + ", txns=" + save.getTransactions().size()); + System.out.println("[loader] applySave: name=" + save.getName()); + + // 1. Determine baseline context: use the save file's embedded stocks if present, + // otherwise fall back to the system's current baseline configuration. + Map activeBaseline; + if (save.getExchangeStocks() != null && !save.getExchangeStocks().isEmpty()) { + activeBaseline = captureBaseline(save.getExchangeStocks()); + + // Re-initialize exchange's stock collection if it has changed structurally + this.exchange.updateStockPool(save.getExchangeStocks()); + } else { + activeBaseline = this.baselinePrices; + } - // 1. Roll the exchange's prices back to the captured baseline and - // the exchange's week back to 1, so the simulation starts - // fresh. - exchange.resetStocksTo(baselinePrices); + // Reset exchange simulation state + exchange.resetStocksTo(activeBaseline); exchange.setWeek(1); - // 2. Reset the player to an empty state with the saved starting - // capital. We use starting capital here (rather than the - // final saved balance) so the per-week net-worth samples - // collected by widget listeners during the advances below - // have a meaningful baseline. + // 2. Clear out player fields Portfolio portfolio = player.getPortfolio(); portfolio.clear(); TransactionArchive archive = player.getTransactionArchive(); @@ -230,39 +175,26 @@ private void applySave(final SaveGame save) { player.setMoney(BigDecimal.valueOf(save.getStartingCapital())); player.refreshProperties(); - // 3. Advance the exchange (save.getWeek() - 1) times so the - // stock price graphs get a real history, and so widgets that - // track per-week net-worth (the financial summary and the - // stats screen) accumulate chart points via their existing - // weekProperty listeners. + // 3. Replay history steps int targetWeek = Math.max(1, save.getWeek()); while (exchange.getWeek() < targetWeek) { exchange.advance(); } - // 4. Re-apply the saved owned shares (skipping unknown symbols - // instead of throwing - keeps the load robust against stock - // files that have diverged from the save). + // 4. Re-populate portfolio balances for (OwnedShareData od : save.getOwnedShares()) { if (!exchange.hasStock(od.getSymbol())) { - System.err.println("Skipping unknown stock from save: " - + od.getSymbol()); + System.err.println("Skipping unknown stock from save: " + od.getSymbol()); continue; } Stock stock = exchange.getStock(od.getSymbol()); - portfolio.addShare(new Share( - stock, od.getQuantity(), od.getPurchasePrice())); + portfolio.addShare(new Share(stock, od.getQuantity(), od.getPurchasePrice())); } - // 5. Re-apply the saved transactions. We rebuild the original - // Purchase / Sale objects (with their share + calculator) - // directly into the archive, rather than re-running them - // through the player's transaction handler, so the player's - // balance is not mutated again here. + // 5. Build financial histories for (TransactionData td : save.getTransactions()) { if (!exchange.hasStock(td.getSymbol())) { - System.err.println("Skipping transaction with unknown stock: " - + td.getSymbol()); + System.err.println("Skipping transaction with unknown stock: " + td.getSymbol()); continue; } Stock stock = exchange.getStock(td.getSymbol()); @@ -276,22 +208,21 @@ private void applySave(final SaveGame save) { archive.add(transaction); } - // 6. Overwrite the balance with the saved value now that the - // history has been built up. + // 6 & 7. Finalize state adjustments player.setMoney(BigDecimal.valueOf(save.getBalance())); - // 7. Re-publish the float properties so the final loaded - // balance is visible to all listeners. + // Explicitly restore the saved net worth timeline history array + if (save.getNetWorthHistory() != null) { + player.setNetWorthHistory(save.getNetWorthHistory()); + } else { + player.setNetWorthHistory(new ArrayList<>()); + } + player.refreshProperties(); this.activeSave = save; } - /** - * Captures the current sales price of every stock provided, so saves - * can be loaded against a stable baseline regardless of subsequent - * price drift during the session. - * */ private static Map captureBaseline(final List stocks) { Map snapshot = new HashMap<>(); for (Stock s : stocks) { @@ -299,4 +230,4 @@ private static Map captureBaseline(final List stocks) } return snapshot; } -} +} \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java index aed5026..cc8c27a 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java @@ -2,6 +2,7 @@ import edu.ntnu.idi.idatt2003.g40.mappe.model.OwnedShareData; import edu.ntnu.idi.idatt2003.g40.mappe.model.SaveGame; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; import edu.ntnu.idi.idatt2003.g40.mappe.model.TransactionData; import java.io.IOException; @@ -256,8 +257,31 @@ private SaveGame parseFile(final Path file) { List transactions = parseTransactionsArray(extractArrayBody(content, "transactions")); +// Read the newly defined blocks safely + List exchangeStocks = new ArrayList<>(); + String stocksBody = extractArrayBody(content, "stocks"); + if (stocksBody != null && !stocksBody.trim().isEmpty()) { + for (String objectStr : splitArrayObjects(stocksBody)) { + Map stockFields = parseFlatJsonObject(objectStr); + if (stockFields == null) continue; + + String sym = stockFields.get("symbol"); + String nm = stockFields.get("name"); + String prcStr = stockFields.get("price"); + + if (sym != null && nm != null && prcStr != null) { + try { + exchangeStocks.add(new Stock(sym, nm, new BigDecimal(prcStr))); + } catch (Exception e) { + // Skip individual invalid elements to preserve full parsing stability + } + } + } + } + + // Pass exchangeStocks into your SaveGame data constructor wrapper context return new SaveGame(name, balance, startingCapital, stockDataPath, - week, ownedShares, transactions); + week, ownedShares, transactions, exchangeStocks); } catch (IOException | NumberFormatException e) { System.err.println("Skipping invalid save file " + file.getFileName() + ": " + e.getMessage()); @@ -283,10 +307,8 @@ private String toJson(final SaveGame save) { StringBuilder sb = new StringBuilder(); sb.append("{\n"); sb.append(" \"name\": ").append(quote(save.getName())).append(",\n"); - sb.append(" \"balance\": ") - .append(formatNumber(save.getBalance())).append(",\n"); - sb.append(" \"startingCapital\": ") - .append(formatNumber(save.getStartingCapital())).append(",\n"); + sb.append(" \"balance\": ").append(formatNumber(save.getBalance())).append(",\n"); + sb.append(" \"startingCapital\": ").append(formatNumber(save.getStartingCapital())).append(",\n"); sb.append(" \"stockDataPath\": "); if (save.getStockDataPath() == null) { sb.append("null"); @@ -295,14 +317,36 @@ private String toJson(final SaveGame save) { } sb.append(",\n"); sb.append(" \"week\": ").append(save.getWeek()).append(",\n"); - sb.append(" \"ownedShares\": ") - .append(ownedSharesToJson(save.getOwnedShares())).append(",\n"); - sb.append(" \"transactions\": ") - .append(transactionsToJson(save.getTransactions())).append("\n"); + sb.append(" \"ownedShares\": ").append(ownedSharesToJson(save.getOwnedShares())).append(",\n"); + sb.append(" \"transactions\": ").append(transactionsToJson(save.getTransactions())).append(",\n"); + + // Wire the live tracking stocks from your game state container layout here + sb.append(" \"stocks\": ").append(stocksToJson(save.getExchangeStocks())).append("\n"); sb.append("}\n"); return sb.toString(); } + private String stocksToJson(final List stocks) { + if (stocks == null || stocks.isEmpty()) { + return "[]"; + } + StringBuilder sb = new StringBuilder(); + sb.append("[\n"); + for (int i = 0; i < stocks.size(); i++) { + Stock s = stocks.get(i); + sb.append(" { \"symbol\": ").append(quote(s.getSymbol())) + .append(", \"name\": ").append(quote(s.getCompany())) + .append(", \"price\": ").append(s.getSalesPrice().toPlainString()) + .append(" }"); + if (i < stocks.size() - 1) { + sb.append(","); + } + sb.append("\n"); + } + sb.append(" ]"); + return sb.toString(); + } + /** * Serialises a list of {@link OwnedShareData} entries to a JSON array. * diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameController.java index e671f6c..d1bd01f 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameController.java @@ -1,13 +1,19 @@ package edu.ntnu.idi.idatt2003.g40.mappe.view.creategame; +import edu.ntnu.idi.idatt2003.g40.mappe.engine.Exchange; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Player; import edu.ntnu.idi.idatt2003.g40.mappe.model.SaveGame; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; import edu.ntnu.idi.idatt2003.g40.mappe.service.SaveGameService; +import edu.ntnu.idi.idatt2003.g40.mappe.service.StockFileManager; +import edu.ntnu.idi.idatt2003.g40.mappe.service.StockFileParser; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewEnum; import java.io.File; import java.io.IOException; +import java.util.List; import javafx.scene.Scene; import javafx.scene.control.Alert; @@ -36,6 +42,16 @@ public class CreateGameController extends ViewController { /** Service used to write new saves to disk. */ private final SaveGameService saveGameService; + /** + * Exchange defined by a selected list of stocks. + * */ + private Exchange exchange; + + /** + * Player object. + */ + private Player player; + /** * Callback invoked after a save has been written successfully. * Set by {@link #setOnSaveCreated(Runnable)} and defaults to a @@ -62,6 +78,24 @@ public CreateGameController(final CreateGameView view, this.saveGameService = saveGameService; } + /** + * Getter method for exchange. + * + * @return exchange. + * */ + public Exchange getExchange() { + return exchange; + } + + /** + * Getter method for player. + * + * @return player. + * */ + public Player getPlayer() { + return player; + } + /** * Sets the callback invoked after a save has been written to disk. * @@ -78,8 +112,19 @@ public void setOnSaveCreated(final Runnable handler) { */ @Override protected void initInteractions() { - getViewElement().setOnAction(CreateGameActions.USE_DEFAULT_STOCKS, - () -> getViewElement().selectDefaultStocks()); + getViewElement().setOnAction(CreateGameActions.USE_DEFAULT_STOCKS, () -> { + try { + // Read the default stock resource pool to populate the initial exchange baseline + StockFileManager stockFileManager = new StockFileManager("sp500.csv"); + StockFileParser stockFileParser = new StockFileParser(); + List defaultStocks = stockFileParser.getStocksFromStrings(stockFileManager.readFile()); + this.exchange = new Exchange("Exchange", defaultStocks); + + getViewElement().selectDefaultStocks(); + } catch (IOException | IllegalArgumentException e) { + showAlert(AlertType.ERROR, "Feil ved lasting", "Kunne ikke laste standard aksjedata: " + e.getMessage()); + } + }); getViewElement().setOnAction(CreateGameActions.CHOOSE_STOCK_FILE, this::handleChooseStockFile); @@ -98,19 +143,32 @@ protected void initInteractions() { */ private void handleChooseStockFile() { FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("Velg aksje fil"); + fileChooser.setTitle("Choose Stock File"); fileChooser.getExtensionFilters().addAll( - new FileChooser.ExtensionFilter("Stock data (*.txt)", "*.txt"), + new FileChooser.ExtensionFilter("Stock data (*.csv)", "*.csv"), new FileChooser.ExtensionFilter("All files", "*.*") ); - Window window = getOwnerWindow(); File selectedFile = fileChooser.showOpenDialog(window); if (selectedFile == null) { - // User cancelled the dialog. return; } - + StockFileManager stockFileManager = new StockFileManager(selectedFile.getAbsolutePath()); + StockFileParser stockFileParser = new StockFileParser(); + List fileValues; + try { + fileValues = stockFileManager.readFile(); + } catch (IOException e) { + showAlert(AlertType.ERROR, "Error when reading", "Could not read selected file."); + return; + } + try { + List stockList = stockFileParser.getStocksFromStrings(fileValues); + this.exchange = new Exchange("Exchange", stockList); + } catch (IllegalArgumentException e) { + showAlert(AlertType.ERROR, "Invalid format", "The file contains an invalid format."); + return; + } getViewElement().selectCustomStockFile(selectedFile); } @@ -124,8 +182,8 @@ private void handleCreateGame() { String name = view.getFileName(); Double capital = view.getStartingCapital(); CreateGameView.StockSelection selection = view.getStockSelection(); - - // The button should be disabled in this case, but guard anyway. + this.player = new edu.ntnu.idi.idatt2003.g40.mappe.model.Player(name, + java.math.BigDecimal.valueOf(capital)); if (name.isEmpty() || capital == null || selection == CreateGameView.StockSelection.NONE) { @@ -148,23 +206,35 @@ private void handleCreateGame() { } } + // Safety check: ensure exchange baseline was built during choice selection loops + List initialStocks = (this.exchange != null) ? this.exchange.getStocks() : List.of(); + + // Reconstruct utilizing the full array snapshot layout tracking parameter SaveGame newSave = new SaveGame( name, capital, capital, - stockDataPath + stockDataPath, + 1, + List.of(), + List.of(), + initialStocks ); try { saveGameService.saveGame(newSave); } catch (IOException | IllegalArgumentException e) { showAlert(AlertType.ERROR, - "Kunne ikke lagre", - "Klarte ikke å skrive save-filen:\n" + e.getMessage()); + "Could not save.", + "Could not save file:\n" + e.getMessage()); return; } onSaveCreated.run(); + + this.exchange = null; + this.player = null; + changeScene(ViewEnum.PLAY_GAME); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java index 3165949..a9ad60b 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java @@ -13,6 +13,7 @@ import javafx.scene.control.TextFormatter; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.ArrayList; import java.util.List; /** @@ -58,7 +59,7 @@ public DashBoardController(final DashBoardView viewElement, final List stockList) throws IllegalArgumentException { this.player = player; - this.stockList = stockList; + this.stockList = new ArrayList<>(stockList); this.exchange = exchange; this.selectedFilter = ""; this.selectedTimeRange = DashBoardTimeRange.DEFAULT; @@ -219,6 +220,26 @@ protected void initInteractions() { }); } + public void handleStockPoolUpdate(final List updatedStocks) { + if (updatedStocks == null || updatedStocks.isEmpty()) { + return; + } + + // Synchronize our final referenced list tracking structure + this.stockList.clear(); + this.stockList.addAll(updatedStocks); + + // Rebuild the sidebar UI item buttons with the new stock identifiers + populateStockList(this.selectedFilter); + + // Default active UI chart presentation view elements focus to the first stock + Stock firstStock = this.stockList.getFirst(); + BigDecimal ownedAmount = this.player.getPortfolio() + .getTotalShareQuantityBySymbol(firstStock.getSymbol()); + + handleStockSelection(firstStock, ownedAmount.floatValue()); + } + /** * Refresh the dashboard view after a save has been loaded. * diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java index d7edb2a..438be15 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java @@ -577,4 +577,29 @@ private String formatShares(final float amount) { String formatted = String.format(java.util.Locale.US, "%.3f", amount); return formatted.replaceAll("0+$", "").replaceAll("\\.$", ""); } + + /** + * Clears the sidebar and populates it with a new list of stock buttons, + * executing the provided configuration logic on each button. + * + * @param newStocks the new collection of {@link Stock} objects to display. + * @param clickLogic the controller logic to bind to each newly generated button. + */ + public void updateStockPool(final List newStocks, final Consumer clickLogic) { + if (newStocks == null || clickLogic == null) { + return; + } + + // Clear the sidebar layout elements container completely + clearStockList(); + + // Generate fresh buttons for every stock inside the new collection pool + for (Stock stock : newStocks) { + String buttonText = stock.getSymbol() + " - " + stock.getCompany(); + Button stockBtn = createStockButton(buttonText); + + // Wire the interaction mapping back to the controller logic + setOnStockAction(stockBtn, stock, clickLogic); + } + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryController.java index d4234e5..bf83ddc 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryController.java @@ -7,6 +7,8 @@ import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventSubscriber; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController; + +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -15,7 +17,7 @@ public class SummaryController extends ViewController private Exchange exchange; private Player player; - private List playerNetWorthHistory; + private List playerNetWorthHistory; /** * {@inheritDoc}. @@ -36,15 +38,16 @@ public SummaryController(final SummaryView viewElement, @Override protected void initInteractions() { - playerNetWorthHistory.add(player.getNetWorth().floatValue()); + playerNetWorthHistory.add(player.getNetWorth()); + player.setNetWorthHistory(playerNetWorthHistory); getViewElement().setOnAction(SummaryActions.NEXT_WEEK, () -> { exchange.advance(); }); exchange.weekProperty().addListener((observable, o, n) -> { getViewElement().setWeek((Integer) n); - playerNetWorthHistory.add(player.getNetWorth().floatValue()); - getViewElement().updateChart(playerNetWorthHistory); + playerNetWorthHistory.add(player.getNetWorth()); + getViewElement().updateChart(playerNetWorthHistory.stream().map(BigDecimal::floatValue).toList()); getViewElement().setBalance(player.getMoney().floatValue(), player.getNetWorth().floatValue()); }); @@ -57,6 +60,40 @@ protected void initInteractions() { }); } + /** + * Synchronizes the controller pointers with the active game engine state references. + * + * @param criticalExchange the current active exchange engine. + * @param activePlayer the current active player context profile. + */ + public void handleContextUpdate(final Exchange criticalExchange, final Player activePlayer) { + if (criticalExchange == null || activePlayer == null) { + return; + } + this.exchange = criticalExchange; + this.player = activePlayer; + + if (this.playerNetWorthHistory == null) { + this.playerNetWorthHistory = new ArrayList<>(); + } + this.playerNetWorthHistory.clear(); + + // 1. Recover history from the updated player instance if available + if (this.player.getNetWorthHistory() != null && !this.player.getNetWorthHistory().isEmpty()) { + this.playerNetWorthHistory.addAll(this.player.getNetWorthHistory()); + } else { + // Safe fallback trajectory baseline points + this.playerNetWorthHistory.add(this.player.getStartingMoney()); + this.playerNetWorthHistory.add(this.player.getNetWorth()); + } + + // Flush updates directly into view layer + getViewElement().setWeek(this.exchange.getWeek()); + getViewElement().updateChart(this.playerNetWorthHistory.stream().map(BigDecimal::floatValue).toList()); + getViewElement().setBalance(this.player.getMoney().floatValue(), this.player.getNetWorth().floatValue()); + } + + /** * Handles save-related events. * @@ -75,29 +112,22 @@ public void handleEvent(final EventData data) { playerNetWorthHistory.clear(); return; } - // STATE_RESET - System.out.println("[summary] STATE_RESET: money=" + player.getMoney() - + ", netWorth=" + player.getNetWorth() - + ", week=" + exchange.getWeek() - + ", history size=" + (playerNetWorthHistory == null ? 0 : playerNetWorthHistory.size())); + + // STATE_RESET block if (playerNetWorthHistory == null) { playerNetWorthHistory = new ArrayList<>(); } - // The advance-replay during the load has populated history with - // net-worth samples computed against the placeholder starting - // capital. The final point should reflect the actual loaded - // balance, so we overwrite it (or add it if the history is - // empty - e.g. for a week-1 save with no replay). - if (!playerNetWorthHistory.isEmpty()) { - playerNetWorthHistory.set( - playerNetWorthHistory.size() - 1, - player.getNetWorth().floatValue()); + + playerNetWorthHistory.clear(); + if (player.getNetWorthHistory() != null && !player.getNetWorthHistory().isEmpty()) { + playerNetWorthHistory.addAll(player.getNetWorthHistory()); } else { - playerNetWorthHistory.add(player.getNetWorth().floatValue()); + playerNetWorthHistory.add(player.getStartingMoney()); + playerNetWorthHistory.add(player.getNetWorth()); } + getViewElement().setWeek(exchange.getWeek()); - getViewElement().updateChart(playerNetWorthHistory); - getViewElement().setBalance(player.getMoney().floatValue(), - player.getNetWorth().floatValue()); + getViewElement().updateChart(playerNetWorthHistory.stream().map(BigDecimal::floatValue).toList()); + getViewElement().setBalance(player.getMoney().floatValue(), player.getNetWorth().floatValue()); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java index 7af130d..0ddbbe1 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java @@ -25,13 +25,13 @@ public class MarketController extends ViewController implements EventSubscriber { /** The {@link Player} owning shares displayed in the market. */ - private final Player player; + private Player player; /** The {@link Exchange} the market is connected to. */ - private final Exchange exchange; + private Exchange exchange; /** Stocks shown in the market grid. */ - private final List stockList; + private List stockList; /** * Constructor. @@ -112,4 +112,28 @@ private int ownedQuantity(final String symbol) { .reduce(BigDecimal.ZERO, BigDecimal::add) .intValue(); } + + /** + * Refreshes the underlying tracking context references and triggers a complete + * rebuilding processing loop for the graphical marketplace card components. + * + * @param updatedExchange the current active exchange engine. + * @param updatedPlayer the current active player profile context. + * @param freshStocks the new active collection pool of stocks. + */ + public void handleStockPoolUpdate(final Exchange updatedExchange, + final Player updatedPlayer, + final List freshStocks) { + if (updatedExchange == null || updatedPlayer == null || freshStocks == null) { + return; + } + + // Synchronize engine references + this.exchange = updatedExchange; + this.player = updatedPlayer; + this.stockList = freshStocks; + + // Push the changes down to the presentation layer view layout grid + getViewElement().updateStockPool(freshStocks); + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java index 4cef923..327c1b7 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java @@ -376,4 +376,15 @@ private Polyline buildMiniChart(final Stock stock) { line.getStyleClass().addAll("market-mini-chart", up ? "up" : "down"); return line; } + + /** + * Replaces the entire underlying stock data collection pool and forces + * a full graphical UI re-render layout processing update loop. + * + * @param updatedStocks the fresh list of stocks parsed from the save file. + */ + public void updateStockPool(final List updatedStocks) { + this.stockList = (updatedStocks == null) ? new ArrayList<>() : new ArrayList<>(updatedStocks); + renderStocks(); + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/FindStockGame.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/FindStockGame.java index c70ca90..c101d6e 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/FindStockGame.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/FindStockGame.java @@ -41,7 +41,7 @@ public final class FindStockGame /** * List of stock symbols to generate selection from. * */ - private final List availableSymbols; + private List availableSymbols; /** * The target stocks' symbol. @@ -125,6 +125,22 @@ private void handleChoice(final String chosen) { generateRound(); } + /** + * Updates the pool of available stock symbols used by the mini-game + * and immediately starts a new round with the updated choices. + * + * @param newSymbols the new list of stock symbols to use. + */ + public void updateStockPool(final List newSymbols) { + if (newSymbols == null) { + return; + } + this.availableSymbols = List.copyOf(newSymbols); + + // Regenerate the current round so symbols from a previous save don't linger on screen + generateRound(); + } + @Override protected void initLayout() { targetLabel = new Label(); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java index d2a7316..8cf6188 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java @@ -37,10 +37,10 @@ public class StatsController extends ViewController implements EventSubscriber { /** The {@link Player} whose state is displayed. */ - private final Player player; + private Player player; /** The {@link Exchange} that drives the week-by-week simulation. */ - private final Exchange exchange; + private Exchange exchange; /** * Snapshot of the players net worth at each recorded week. @@ -119,14 +119,11 @@ public void handleEvent(final EventData data) { balanceHistory.clear(); return; } - // STATE_RESET: the advance-replay populated the history against - // the placeholder starting capital. Replace the final entry - // with the actual loaded net worth so the chart's endpoint - // matches the displayed balance. - if (!balanceHistory.isEmpty()) { - balanceHistory.set( - balanceHistory.size() - 1, - player.getNetWorth()); + + // STATE_RESET block + balanceHistory.clear(); + if (player.getNetWorthHistory() != null && !player.getNetWorthHistory().isEmpty()) { + balanceHistory.addAll(player.getNetWorthHistory()); } else { balanceHistory.add(player.getStartingMoney()); balanceHistory.add(player.getNetWorth()); @@ -205,4 +202,41 @@ private static final class Aggregate { this.stock = stock; } } + + /** + * Updates the underlying tracking engine references and fully + * synchronizes the metric graphs to match the newly loaded save state. + * + * @param updatedExchange the current active exchange engine. + * @param updatedPlayer the current active player context profile. + */ + public void handleContextUpdate(final Exchange updatedExchange, final Player updatedPlayer) { + if (updatedExchange == null || updatedPlayer == null) { + return; + } + + this.exchange = updatedExchange; + this.player = updatedPlayer; + + if (this.balanceHistory == null) { + this.balanceHistory = new ArrayList<>(); + } + + // 1. Clear the stale session data entirely + this.balanceHistory.clear(); + + // 2. Reconstruct the entire weekly history timeline directly from the + // loaded player/stock data if available, or fetch it from the save. + // If your Player model stores historical net worth, parse it here: + if (this.player.getNetWorthHistory() != null && !this.player.getNetWorthHistory().isEmpty()) { + this.balanceHistory.addAll(this.player.getNetWorthHistory()); + } else { + // Fallback: If no history array exists, use the baseline points + this.balanceHistory.add(this.player.getStartingMoney()); + this.balanceHistory.add(this.player.getNetWorth()); + } + + // 3. Flush completely recalculated asset structures to the UI + pushSnapshot(); + } } \ No newline at end of file diff --git a/src/main/resources/saves/Tommy.json b/src/main/resources/saves/Tommy.json deleted file mode 100644 index 90979ce..0000000 --- a/src/main/resources/saves/Tommy.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "Tommy", - "balance": 93987.07495, - "startingCapital": 100000.0, - "stockDataPath": "C:\\Users\\tohja\\Desktop\\StockData1.txt", - "week": 18, - "ownedShares": [ - { "symbol": "NVDA", "quantity": 31.0, "purchasePrice": 191.27 } - ], - "transactions": [ - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 191.27, "week": 1 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 }, - { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 195.74, "week": 4 } - ] -}