From e942ca1a4895b82be4615b0d5b3424f25a41a456 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 26 May 2026 00:04:23 +0200 Subject: [PATCH 1/6] Fix: Git ignore save files --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 70cdfab..5149e43 100644 --- a/.gitignore +++ b/.gitignore @@ -55,5 +55,6 @@ failsafe-reports/ coverage/ jacoco.exec -# ====== Stock data files ======= # -.txt \ No newline at end of file +# ====== Stock data files and save files ======= # +.txt +.json From c1780bb3eed8193c0367a2db12f7158a8561fdd1 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 26 May 2026 00:36:04 +0200 Subject: [PATCH 2/6] Fix: Gitignore .txt and .json files --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 5149e43..baa62d6 100644 --- a/.gitignore +++ b/.gitignore @@ -56,5 +56,5 @@ coverage/ jacoco.exec # ====== Stock data files and save files ======= # -.txt -.json +*.txt +*.json From 8b3220e343d8a7abbb341e11969f15cac0b9cccb Mon Sep 17 00:00:00 2001 From: = Date: Tue, 26 May 2026 03:41:29 +0200 Subject: [PATCH 3/6] 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 } - ] -} From f7ae9aba07c1e8ed6e95ccfcd4fb8a49ea684209 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 26 May 2026 04:04:54 +0200 Subject: [PATCH 4/6] Docs: doc fixes, and gitignore .csv files --- .gitignore | 1 + .../ntnu/idi/idatt2003/g40/mappe/Main.java | 72 ++++++++++++------- .../idatt2003/g40/mappe/engine/Exchange.java | 10 +-- .../idi/idatt2003/g40/mappe/model/Player.java | 10 ++- .../idatt2003/g40/mappe/model/SaveGame.java | 5 +- 5 files changed, 60 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index baa62d6..5b1c7f5 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ jacoco.exec # ====== Stock data files and save files ======= # *.txt *.json +*.csv \ No newline at end of file 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 f5470bf..348cf5c 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 @@ -8,11 +8,13 @@ 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.EventData; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; +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.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; @@ -25,18 +27,17 @@ import edu.ntnu.idi.idatt2003.g40.mappe.view.playgame.PlayGameView; import edu.ntnu.idi.idatt2003.g40.mappe.view.settings.SettingsController; import edu.ntnu.idi.idatt2003.g40.mappe.view.settings.SettingsView; -import java.math.BigDecimal; -import java.util.List; -import java.util.Objects; - import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.WidgetEnum; -import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.financialsummary.SummaryController; -import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.financialsummary.SummaryView; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.dashboard.DashBoardController; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.dashboard.DashBoardView; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.financialsummary.SummaryController; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.financialsummary.SummaryView; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market.MarketController; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market.MarketView; -import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.*; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameEngineController; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameEngineView; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesController; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesView; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.ClickerGame; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.FindStockGame; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.TimeInputsGame; @@ -46,19 +47,40 @@ 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 java.math.BigDecimal; +import java.util.List; +import java.util.Objects; 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}

+ * + *

Launches the javafx thread and starts the program.

+ * */ public class Main extends Application { + /** + * Active {@link Exchange} object. + * */ private Exchange exchange; + + /** + * Active {@link Player} object. + * */ private Player player; - public static void main(String[] args) { + /** + * Main method, launches the start javafx method. + * + * @param args standard Java parameter. + * */ + public static void main(final String[] args) { launch(args); } @@ -77,7 +99,7 @@ public void start(final Stage stage) throws Exception { EventManager eventManager = new EventManager(); ViewManager viewManager = new ViewManager(stage, eventManager); - // Initial fallback data loop configuration + // Loads a default file in case of misreading (fallback) StockFileManager fileManager = new StockFileManager("src/main/resources/sp500.csv"); StockFileParser fileParser = new StockFileParser(); List stocksInFile = fileParser.getStocksFromStrings(fileManager.readFile()); @@ -152,34 +174,35 @@ public void start(final Stage stage) throws Exception { gameEngineController, clickerGame, inGameView, findStockGame, timeInputsGame ); - // --- 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() { + // Adds a generic event subscriber to the event manager + // that handles STATE_RESET events. These events are + // triggered when a new save file is loaded. + // We define this implementation here, because + // this class has the highest level overview of the application. + eventManager.addSubscriber(new EventSubscriber() { @Override - public void handleEvent(final edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData eventData) { - if (eventData.data() instanceof SaveGame save) { + public void handleEvent(final EventData eventData) { + if (eventData.data() instanceof SaveGame) { - // 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 + // Updates the various UI components that + // depend on an exchange, player, or list of stocks. findStockGame.updateStockPool(dynamicStocks.stream().map(Stock::getSymbol).toList()); - summaryController.handleContextUpdate(exchange, player); + summaryController.handleContextUpdate(Main.this.exchange, Main.this.player); dashBoardController.handleStockPoolUpdate(dynamicStocks); - statsController.handleContextUpdate(exchange, player); - marketController.handleStockPoolUpdate(exchange, player, dynamicStocks); + statsController.handleContextUpdate(Main.this.exchange, Main.this.player); + marketController.handleStockPoolUpdate(Main.this.exchange, Main.this.player, dynamicStocks); if (!dynamicStocks.isEmpty()) { miniGamesController.setActiveStock(dynamicStocks.getFirst()); @@ -187,7 +210,6 @@ public void handleEvent(final edu.ntnu.idi.idatt2003.g40.mappe.service.event } } }, EventType.STATE_RESET); - // --------------------------------------------- topBarController.setMarketIntegration( inGameView::changeCenterView, dashBoardView.getRootPane(), marketView.getRootPane(), @@ -231,4 +253,4 @@ public void handleEvent(final edu.ntnu.idi.idatt2003.g40.mappe.service.event stage.show(); } -} \ No newline at end of file +} 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 25402a3..bfeaad4 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 @@ -24,7 +24,8 @@ * *

Delegates buying and selling to player elements using calculators

* - *

Advances week.

+ *

Additionally controls the time in weeks, and therefore has the main + * responsibility for advancing weeks.

* * @see Player * @see edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator @@ -372,7 +373,7 @@ public List getGainers(final int limit) * Method for getting the stocks with the highest * loss of price since last week. * - * @param limit the maximum amount of stocks returned + * @param limit the maximum amount of stocks returned * * @return list of {@link Stock} objects. * @@ -398,12 +399,13 @@ public List getLosers(final int limit) } /** - * Clears the current stock listings and replaces them with a new pool of stocks. + * Replaces list of stocks with a new list. * *

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. + * @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 { 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 1c3cbbd..cf63d80 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 @@ -7,7 +7,6 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; - import javafx.beans.property.ReadOnlyFloatProperty; import javafx.beans.property.ReadOnlyFloatWrapper; @@ -131,19 +130,18 @@ public BigDecimal getMoney() { } /** - * Returns a read-only list of the player's historical net worth data points. + * Returns a read-only list of the player's net worth history. * - * @return immutable sequence list of tracking records. + * @return read only list of players net-worth instances. */ 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. + * Replaces the net-worth history list of this player with a new list. * - * @param history the historic snapshots sequence data payload. + * @param history the list to replace current list with. */ public void setNetWorthHistory(final List history) { this.netWorthHistory.clear(); 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 74bfaf9..a0f2ec9 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 @@ -1,13 +1,12 @@ package edu.ntnu.idi.idatt2003.g40.mappe.model; import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; - import java.math.BigDecimal; import java.util.Collections; import java.util.List; /** - * Represents one save game entry. + * Represents one save entry for the game. * *

* Holds the display name, current balance, starting capital, @@ -49,7 +48,7 @@ public class SaveGame { private final List netWorthHistory; /** - * Full constructor including exchange stocks and net worth history state tracking. + * Constructor. * * @param name the display name of the save. * @param balance the current balance value. From fd78a3fea50374ec40dad2a58416e679de757d30 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 26 May 2026 04:38:29 +0200 Subject: [PATCH 5/6] Docs: Javadoc --- .../idatt2003/g40/mappe/model/SaveGame.java | 57 ++++++++--- .../g40/mappe/service/GameStateLoader.java | 89 ++++++++++------- .../g40/mappe/service/SaveGameService.java | 96 +++++++------------ 3 files changed, 131 insertions(+), 111 deletions(-) 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 a0f2ec9..27a938d 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 @@ -8,12 +8,10 @@ /** * Represents one save entry for the game. * - *

- * Holds the display name, current balance, starting capital, + *

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, the list of committed transactions, and - * the current state of the exchange stocks for a single saved game. - *

+ * the current state of the exchange stocks for a single saved game.

*/ public class SaveGame { @@ -96,7 +94,19 @@ public SaveGame(final String name, /** * Overloaded constructor that converts a List of Floats to BigDecimals. - * Helps your JSON parser map the array smoothly on load. + * + *

Used by JSON parser to map array

+ * + * @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. + * @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 exchangeStocks the current list of stocks from the exchange (may be null; treated as empty). + * @param dummyFlagForOverloading sample dummy flag used for overload. */ public SaveGame(final String name, final double balance, @@ -115,7 +125,17 @@ public SaveGame(final String name, } /** - * Legacy backward-compatible constructor matching your previous full configuration. + * Backwards-compatible overload used in a previous implementation. + * + * @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. + * @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 exchangeStocks the current list of stocks from the exchange (may be null; treated as empty). */ public SaveGame(final String name, final double balance, @@ -129,7 +149,16 @@ public SaveGame(final String name, } /** - * Legacy backward-compatible constructor matching your previous configuration. + * Backwards-compatible overload used in a previous implementation. + * + * @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. + * @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). */ public SaveGame(final String name, final double balance, @@ -143,6 +172,11 @@ public SaveGame(final String name, /** * Convenience constructor matching the original format used by the create-game flow. + * @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. */ public SaveGame(final String name, final double balance, @@ -151,13 +185,6 @@ public SaveGame(final String name, this(name, balance, startingCapital, stockDataPath, 1, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); } - /** - * Convenience constructor matching the legacy "name + balance" format. - */ - public SaveGame(final String name, final double balance) { - this(name, balance, balance, null, 1, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - } - /** * Getter method for the name. * @@ -239,4 +266,4 @@ public List getTransactions() { 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 78db068..874f6ec 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 @@ -17,7 +17,6 @@ import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventPublisher; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventSubscriber; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType; - import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; @@ -26,16 +25,44 @@ /** * Service that applies a {@link SaveGame} to the active {@link Player} and {@link Exchange}. + * + *

Calls events of type

*/ public final class GameStateLoader implements EventSubscriber, EventPublisher { + /** + * Local reference to player. + * */ private final Player player; - private Exchange exchange; // Non-final to allow runtime context swaps + + /** + * Local reference to exchange. + * */ + private Exchange exchange; + + /** + * Local reference to event manager. + * */ private final EventManager eventManager; + + /** + * Baseline prices of symbols. + * */ private final Map baselinePrices; + /** + * Currently active save. + * */ private SaveGame activeSave; + /** + * Constructor. + * + * @param player player object. + * @param exchange exchange. + * @param stockList list of stocks for the game state. + * @param eventManager the event manager instance. + * */ public GameStateLoader(final Player player, final Exchange exchange, final List stockList, @@ -51,7 +78,9 @@ public GameStateLoader(final Player player, } /** - * Updates the runtime exchange reference when loading a file custom configuration. + * Setter method for exchange. + * + * @param dynamicExchange new exchange. */ public void setExchange(final Exchange dynamicExchange) { if (dynamicExchange != null) { @@ -78,25 +107,12 @@ public Player getPlayer() { } /** - * 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. + * Builds a new {@link SaveGame} snapshot, + * passing along the current exchange state. + * + * @return {@link SaveGame} object. */ 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( @@ -116,7 +132,6 @@ 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; @@ -148,26 +163,30 @@ public void invoke(final EventData data, final EventManager manager) { manager.invokeEvent(data); } + /** + * Applies a save. + * + *

If save includes valid stocks, uses them. If not, + * uses fallback stocks (default data).

+ * + *

Clears and rebuilds exchange, player, transactions, + * portfolio and net-worth history of player.

+ * + * @param save save to apply. + * */ private void applySave(final SaveGame save) { - 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; } - // Reset exchange simulation state exchange.resetStocksTo(activeBaseline); exchange.setWeek(1); - // 2. Clear out player fields Portfolio portfolio = player.getPortfolio(); portfolio.clear(); TransactionArchive archive = player.getTransactionArchive(); @@ -175,13 +194,11 @@ private void applySave(final SaveGame save) { player.setMoney(BigDecimal.valueOf(save.getStartingCapital())); player.refreshProperties(); - // 3. Replay history steps int targetWeek = Math.max(1, save.getWeek()); while (exchange.getWeek() < targetWeek) { exchange.advance(); } - // 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()); @@ -191,7 +208,6 @@ private void applySave(final SaveGame save) { portfolio.addShare(new Share(stock, od.getQuantity(), od.getPurchasePrice())); } - // 5. Build financial histories for (TransactionData td : save.getTransactions()) { if (!exchange.hasStock(td.getSymbol())) { System.err.println("Skipping transaction with unknown stock: " + td.getSymbol()); @@ -208,10 +224,8 @@ private void applySave(final SaveGame save) { archive.add(transaction); } - // 6 & 7. Finalize state adjustments player.setMoney(BigDecimal.valueOf(save.getBalance())); - // Explicitly restore the saved net worth timeline history array if (save.getNetWorthHistory() != null) { player.setNetWorthHistory(save.getNetWorthHistory()); } else { @@ -223,6 +237,13 @@ private void applySave(final SaveGame save) { this.activeSave = save; } + /** + * Creates a new map keyed by stock symbol and valued by latest stock price. + * + * @param stocks stocks to use when creating map. + * + * @return map keyed with stock symbol and valued with stock price. + * */ private static Map captureBaseline(final List stocks) { Map snapshot = new HashMap<>(); for (Stock s : stocks) { @@ -230,4 +251,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 cc8c27a..2a3b8e0 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 @@ -4,7 +4,6 @@ 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; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; @@ -22,10 +21,8 @@ /** * Service for loading and saving {@link SaveGame} entries from disk. * - *

- * Each save is stored as a separate JSON file in a single directory, - * which lets several saves co-exist independently. - *

+ *

Each save is stored as a separate JSON file in a single directory, + * which lets several saves co-exist independently.

* *

JSON file format:

* @@ -43,23 +40,26 @@ * { "type": "PURCHASE", "symbol": "AAPL", "quantity": 5, * "price": 150.00, "week": 1 } * ] + * "stocks": [ + * { "symbol": "AAPL", "companyName": "Apple Inc.", "salesPrice": 155.25 } + * ] + * "netWorthHistory": [ + * 10000.00, + * 10026.25 + * ] * } * * - *

- * The {@code week}, {@code ownedShares} and {@code transactions} + *

The {@code week}, {@code ownedShares} and {@code transactions} * fields are optional - old saves without them load with sensible - * defaults (week 1, empty lists). - *

+ * defaults (week 1, empty lists).

* - *

- * The service exposes both a "load all saves" entry point (used by + *

The service exposes both a "load all saves" entry point (used by * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.playgame.PlayGameView}) * and a "save one" entry point (used by the create-game flow and the * auto-save flow). The JSON parsing logic is intentionally minimal and * tailored to this format - invalid files are skipped rather than - * throwing. - *

+ * throwing.

*/ public class SaveGameService { @@ -91,16 +91,12 @@ public SaveGameService(final String directoryPath) { /** * Loads all save games from the directory. * - *

- * Walks the saves directory, parses every file with a + *

Walks the saves directory, parses every file with a * {@code .json} extension and returns the resulting list. - * Files that fail to parse are silently skipped. - *

+ * Files that fail to parse are silently skipped.

* - *

- * Returns an empty list if the directory does not exist or cannot - * be read. - *

+ *

Returns an empty list if the directory does not exist or cannot + * be read.

* * @return the loaded {@link SaveGame} entries. */ @@ -134,12 +130,10 @@ public List loadSaves() { /** * Writes a single {@link SaveGame} to disk as a JSON file. * - *

- * The file name is derived from {@link SaveGame#getName()} sanitised + *

The file name is derived from {@link SaveGame#getName()} sanitised * to filesystem-safe characters and given the {@code .json} * extension. The saves directory is created automatically if it - * doesn't exist. - *

+ * doesn't exist.

* * @param save the {@link SaveGame} object to write. * @@ -185,11 +179,9 @@ public boolean saveExists(final String name) { /** * Loads a single save from an explicit file path. * - *

- * Used by the upload flow on the play-game screen so a save file + *

Used by the upload flow on the play-game screen so a save file * located anywhere on disk can be added to the displayed list - * without having to live inside the saves directory. - *

+ * without having to live inside the saves directory.

* * @param file the file to parse. * @return the parsed {@link SaveGame}, or {@code null} if the file @@ -205,12 +197,10 @@ public SaveGame loadSaveFromFile(final Path file) { /** * Parses a single JSON file into a {@link SaveGame}. * - *

- * Returns {@code null} for files that don't contain a valid save + *

Returns {@code null} for files that don't contain a valid save * record (missing required fields or malformed JSON). Old saves * that don't have week / ownedShares / transactions still parse - * cleanly, with those fields defaulted (week 1, empty lists). - *

+ * cleanly, with those fields defaulted (week 1, empty lists).

* * @param file path to the file to parse. * @return the parsed {@link SaveGame}, or {@code null}. @@ -234,13 +224,11 @@ private SaveGame parseFile(final Path file) { ? Double.parseDouble(fields.get("startingCapital")) : balance; - // stockDataPath may be absent or explicitly "null". String stockDataPath = fields.get("stockDataPath"); if (stockDataPath != null && stockDataPath.isEmpty()) { stockDataPath = null; } - // Week - defaults to 1 for older saves that didn't store it. int week = 1; if (fields.containsKey("week") && !fields.get("week").isEmpty()) { week = Integer.parseInt(fields.get("week")); @@ -249,15 +237,11 @@ private SaveGame parseFile(final Path file) { } } - // The flat parser can't represent arrays in the result map, so - // the array bodies are pulled straight out of the raw content - // with extractArrayBody and then parsed object-by-object. List ownedShares = parseOwnedSharesArray(extractArrayBody(content, "ownedShares")); 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()) { @@ -273,13 +257,12 @@ private SaveGame parseFile(final Path file) { try { exchangeStocks.add(new Stock(sym, nm, new BigDecimal(prcStr))); } catch (Exception e) { - // Skip individual invalid elements to preserve full parsing stability + // Skip individual invalid elements. } } } } - // Pass exchangeStocks into your SaveGame data constructor wrapper context return new SaveGame(name, balance, startingCapital, stockDataPath, week, ownedShares, transactions, exchangeStocks); } catch (IOException | NumberFormatException e) { @@ -292,13 +275,11 @@ private SaveGame parseFile(final Path file) { /** * Serialises a {@link SaveGame} to a JSON object string. * - *

- * Output is pretty-printed with two-space indentation and a trailing + *

Output is pretty-printed with two-space indentation and a trailing * newline so the files are human-readable. Numeric values are * written via {@link BigDecimal#toPlainString()} so they never * surface as scientific notation. Owned shares and transactions - * write as JSON arrays of small flat objects. - *

+ * write as JSON arrays of small flat objects.

* * @param save the save to convert. * @return JSON object string. @@ -320,7 +301,6 @@ private String toJson(final SaveGame save) { 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(); @@ -422,22 +402,16 @@ private String formatNumber(final double value) { * Extracts the body of a top-level JSON array field from a raw save * file's content. * - *

- * Given the raw content {@code {"name": "x", "ownedShares": [ ... ]}} + *

Given the raw content {@code {"name": "x", "ownedShares": [ ... ]}} * and a field name {@code "ownedShares"}, returns the substring - * between the opening {@code [} and the matching closing {@code ]}. - *

+ * between the opening {@code [} and the matching closing {@code ]}.

* - *

- * Returns {@code null} if the field is not present, or if the array + *

Returns {@code null} if the field is not present, or if the array * is malformed (no closing bracket). Returns an empty string for an - * empty array. - *

+ * empty array.

* - *

- * Brackets inside quoted strings are ignored, so a string value like - * {@code "weird]name"} won't confuse the matcher. - *

+ *

Brackets inside quoted strings are ignored, so a string value like + * {@code "weird]name"} won't confuse the matcher.

* * @param content the raw file content. * @param fieldName the JSON field name to look for. @@ -499,12 +473,10 @@ private String extractArrayBody(final String content, /** * Splits a JSON array body into the substrings of its inner objects. * - *

- * Tracks bracket and quote nesting so that commas inside the inner + *

Tracks bracket and quote nesting so that commas inside the inner * objects (or inside quoted strings) don't split the body * incorrectly. Returns the substrings of each top-level - * {@code {...}} found. - *

+ * {@code {...}} found.

* * @param body the substring between {@code [} and {@code ]}. * @return list of object substrings, or an empty list. From a6e82186b8252de29927b9b529bef9d296388d6d Mon Sep 17 00:00:00 2001 From: = Date: Tue, 26 May 2026 04:52:19 +0200 Subject: [PATCH 6/6] Docs: JavaDoc for save classes --- .../g40/mappe/service/SaveGameService.java | 24 +-- .../view/creategame/CreateGameController.java | 17 +- .../dashboard/DashBoardController.java | 10 +- .../view/widgets/dashboard/DashBoardView.java | 25 --- .../financialsummary/SummaryController.java | 7 +- .../view/widgets/market/MarketController.java | 11 +- .../mappe/view/widgets/market/MarketView.java | 3 +- .../minigames/games/FindStockGame.java | 1 - .../view/widgets/stats/StatsController.java | 12 +- src/main/resources/saves/Halleluja.json | 9 +- src/main/resources/saves/Newbie.json | 169 ++++++++++++++++-- 11 files changed, 194 insertions(+), 94 deletions(-) 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 2a3b8e0..cf86ca5 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 @@ -610,21 +610,17 @@ private List parseTransactionsArray(final String body) { /** * Parses a flat (one level deep) JSON object into a map. * - *

- * Only supports string, number, boolean and null values for the + *

Only supports string, number, boolean and null values for the * top-level entries (which is everything {@link SaveGame} and * {@link OwnedShareData} / {@link TransactionData} need). Nested * arrays and objects are skipped past with an empty-string value, * since the array contents are read separately via * {@link #extractArrayBody}. Returns {@code null} if the content - * can't be parsed. - *

+ * can't be parsed.

* - *

- * Values are returned as strings; {@code null} values are stored as + *

Values are returned as strings; {@code null} values are stored as * an empty string so callers can distinguish "absent" from "null" - * using {@link Map#containsKey(Object)}. - *

+ * using {@link Map#containsKey(Object)}.

* * @param content the raw file content. * @return a map of field name to raw value, or {@code null}. @@ -750,11 +746,9 @@ private int findStringEnd(final String s, final int start) { * opening bracket / brace at {@code start}, accounting for quoted * strings and nested structures. Returns -1 if no match is found. * - *

- * Used by {@link #parseFlatJsonObject} to skip past nested arrays + *

Used by {@link #parseFlatJsonObject} to skip past nested arrays * and objects whose content isn't needed at the top level (top-level - * arrays are read separately by {@link #extractArrayBody}). - *

+ * arrays are read separately by {@link #extractArrayBody}).

* */ private int findStructuredEnd(final String s, final int start) { char open = s.charAt(start); @@ -849,11 +843,9 @@ private String unquote(final String token) { /** * Sanitises a save name to a safe filesystem identifier. * - *

- * All characters outside {@code [A-Za-z0-9_-]} are replaced with + *

All characters outside {@code [A-Za-z0-9_-]} are replaced with * underscores. This keeps the on-disk file name predictable while - * preserving the original display name in the JSON content. - *

+ * preserving the original display name in the JSON content.

*/ private String sanitiseFileName(final String name) { String trimmed = name.trim(); 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 d1bd01f..ccd0d25 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 @@ -10,11 +10,9 @@ 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; import javafx.scene.control.Alert.AlertType; @@ -26,16 +24,12 @@ * *

Extends {@link ViewController}.

* - *

- * Handles the four interactions on the create-game screen: picking + *

Handles the four interactions on the create-game screen: picking * the default stock data, choosing a custom stock data file, cancel - * (back to the play-game screen), and finally creating the save. - *

+ * (back to the play-game screen), and finally creating the save.

* - *

- * When a save is successfully written to disk a callback can be - * notified so the play-game view can refresh its save list. - *

+ *

When a save is successfully written to disk a callback can be + * notified so the play-game view can refresh its save list.

*/ public class CreateGameController extends ViewController { @@ -114,7 +108,6 @@ public void setOnSaveCreated(final Runnable handler) { protected void initInteractions() { 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()); @@ -206,10 +199,8 @@ 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, 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 a9ad60b..9a5cd9c 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 @@ -220,19 +220,21 @@ protected void initInteractions() { }); } + /** + * Method for setting a new list of stocks for the view and controller. + * + * @param updatedStocks new list of stocks. + * */ 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()); @@ -249,8 +251,6 @@ public void handleStockPoolUpdate(final List updatedStocks) { * */ @Override public void handleEvent(final EventData data) { - System.out.println("[dashboard] STATE_RESET: shares in portfolio = " - + player.getPortfolio().getShares().size()); populateStockList(selectedFilter); if (!stockList.isEmpty()) { Stock first = stockList.getFirst(); 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 438be15..d7edb2a 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,29 +577,4 @@ 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 bf83ddc..28a6224 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,7 +7,6 @@ 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; @@ -33,7 +32,8 @@ public SummaryController(final SummaryView viewElement, super(viewElement, eventManager); eventManager.addSubscriber(this, EventType.STATE_RESET); eventManager.addSubscriber(this, EventType.PRE_LOAD_SAVE); - getViewElement().setBalance(player.getStartingMoney().floatValue(), player.getStartingMoney().floatValue()); + getViewElement().setBalance(player.getStartingMoney().floatValue(), + player.getStartingMoney().floatValue()); } @Override @@ -113,13 +113,12 @@ public void handleEvent(final EventData data) { return; } - // STATE_RESET block if (playerNetWorthHistory == null) { playerNetWorthHistory = new ArrayList<>(); } playerNetWorthHistory.clear(); - if (player.getNetWorthHistory() != null && !player.getNetWorthHistory().isEmpty()) { + if (!player.getNetWorthHistory().isEmpty()) { playerNetWorthHistory.addAll(player.getNetWorthHistory()); } else { playerNetWorthHistory.add(player.getStartingMoney()); 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 0ddbbe1..b8ab2dc 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 @@ -114,12 +114,11 @@ private int ownedQuantity(final String symbol) { } /** - * Refreshes the underlying tracking context references and triggers a complete - * rebuilding processing loop for the graphical marketplace card components. + * Refreshes the view element with updated context. * - * @param updatedExchange the current active exchange engine. - * @param updatedPlayer the current active player profile context. - * @param freshStocks the new active collection pool of stocks. + * @param updatedExchange the current active exchange. + * @param updatedPlayer the current active player. + * @param freshStocks the new active collection of stocks. */ public void handleStockPoolUpdate(final Exchange updatedExchange, final Player updatedPlayer, @@ -128,12 +127,10 @@ public void handleStockPoolUpdate(final Exchange updatedExchange, 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 327c1b7..c6750d7 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 @@ -378,8 +378,7 @@ private Polyline buildMiniChart(final Stock stock) { } /** - * Replaces the entire underlying stock data collection pool and forces - * a full graphical UI re-render layout processing update loop. + * Re-renders the ui with a list of updated stocks. * * @param updatedStocks the fresh list of stocks parsed from the save file. */ 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 c101d6e..5e4c4f3 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 @@ -137,7 +137,6 @@ public void updateStockPool(final List newSymbols) { } this.availableSymbols = List.copyOf(newSymbols); - // Regenerate the current round so symbols from a previous save don't linger on screen generateRound(); } 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 8cf6188..ac4d39e 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 @@ -204,8 +204,7 @@ private static final class Aggregate { } /** - * Updates the underlying tracking engine references and fully - * synchronizes the metric graphs to match the newly loaded save state. + * Updates the graphs and information of the page given a new exchange and player. * * @param updatedExchange the current active exchange engine. * @param updatedPlayer the current active player context profile. @@ -222,21 +221,14 @@ public void handleContextUpdate(final Exchange updatedExchange, final Player upd 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()) { + if (!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/Halleluja.json b/src/main/resources/saves/Halleluja.json index 6068147..7737bf7 100644 --- a/src/main/resources/saves/Halleluja.json +++ b/src/main/resources/saves/Halleluja.json @@ -2,5 +2,12 @@ "name": "Halleluja", "balance": 1000650901.43, "startingCapital": 10000.0, - "stockDataPath": null + "stockDataPath": null, + "week": 1, + "ownedShares": [], + "transactions": [], + "stocks": [ + { "symbol": "TOTA", "name": "Total badass1", "price": 136.80 }, + { "symbol": "LIBR", "name": "Libra avenger of worlds", "price": 19134.23 } + ] } diff --git a/src/main/resources/saves/Newbie.json b/src/main/resources/saves/Newbie.json index 823acaf..2ea4a42 100644 --- a/src/main/resources/saves/Newbie.json +++ b/src/main/resources/saves/Newbie.json @@ -5,16 +5,7 @@ "stockDataPath": null, "week": 5, "ownedShares": [ - { "symbol": "NVDA", "quantity": 5.0, "purchasePrice": 191.27 }, - { "symbol": "NVDA", "quantity": 5.0, "purchasePrice": 191.27 }, - { "symbol": "NVDA", "quantity": 5.0, "purchasePrice": 191.27 }, - { "symbol": "NVDA", "quantity": 5.0, "purchasePrice": 191.27 }, - { "symbol": "NVDA", "quantity": 5.0, "purchasePrice": 191.27 }, - { "symbol": "NVDA", "quantity": 5.0, "purchasePrice": 191.27 }, - { "symbol": "NVDA", "quantity": 5.0, "purchasePrice": 191.27 }, - { "symbol": "NVDA", "quantity": 5.0, "purchasePrice": 191.27 }, - { "symbol": "NVDA", "quantity": 5.0, "purchasePrice": 191.27 }, - { "symbol": "NVDA", "quantity": 5.0, "purchasePrice": 191.27 } + { "symbol": "NVDA", "quantity": 50.0, "purchasePrice": 191.27 } ], "transactions": [ { "type": "PURCHASE", "symbol": "NVDA", "quantity": 5.0, "price": 191.27, "week": 1 }, @@ -27,5 +18,163 @@ { "type": "PURCHASE", "symbol": "NVDA", "quantity": 5.0, "price": 191.27, "week": 1 }, { "type": "PURCHASE", "symbol": "NVDA", "quantity": 5.0, "price": 191.27, "week": 1 }, { "type": "PURCHASE", "symbol": "NVDA", "quantity": 5.0, "price": 191.27, "week": 1 } + ], + "stocks": [ + { "symbol": "CARR", "name": "Carrier Global", "price": 67.95 }, + { "symbol": "AAPL", "name": "Apple Inc.", "price": 258.10 }, + { "symbol": "SNDK", "name": "Sandisk Corporation", "price": 634.05 }, + { "symbol": "WYNN", "name": "Wynn Resorts", "price": 117.80 }, + { "symbol": "TSCO", "name": "Tractor Supply", "price": 51.12 }, + { "symbol": "AMGN", "name": "Amgen", "price": 329.74 }, + { "symbol": "TSLA", "name": "Tesla Inc.", "price": 376.58 }, + { "symbol": "GDDY", "name": "GoDaddy", "price": 92.35 }, + { "symbol": "SBUX", "name": "Starbucks", "price": 99.41 }, + { "symbol": "KVUE", "name": "Kenvue", "price": 17.97 }, + { "symbol": "META", "name": "Meta Platforms", "price": 756.48 }, + { "symbol": "DLTR", "name": "Dollar Tree", "price": 144.19 }, + { "symbol": "ABBV", "name": "AbbVie", "price": 251.10 }, + { "symbol": "HUBB", "name": "Hubbell Incorporated", "price": 548.39 }, + { "symbol": "JKHY", "name": "Jack Henry & Associates", "price": 180.51 }, + { "symbol": "FSLR", "name": "First Solar", "price": 206.22 }, + { "symbol": "FTNT", "name": "Fortinet", "price": 92.09 }, + { "symbol": "EPAM", "name": "EPAM Systems", "price": 199.46 }, + { "symbol": "POOL", "name": "Pool Corporation", "price": 284.22 }, + { "symbol": "MCHP", "name": "Microchip Technology", "price": 72.57 }, + { "symbol": "VRSK", "name": "Verisk Analytics", "price": 169.99 }, + { "symbol": "MRNA", "name": "Moderna", "price": 33.00 }, + { "symbol": "HOOD", "name": "Robinhood Markets Inc.", "price": 64.62 }, + { "symbol": "VRSN", "name": "Verisign", "price": 247.98 }, + { "symbol": "AMAT", "name": "Applied Materials", "price": 379.06 }, + { "symbol": "MDLZ", "name": "Mondelez International", "price": 66.56 }, + { "symbol": "PCAR", "name": "Paccar", "price": 128.11 }, + { "symbol": "NDAQ", "name": "Nasdaq Inc.", "price": 79.41 }, + { "symbol": "INTU", "name": "Intuit", "price": 403.44 }, + { "symbol": "HSIC", "name": "Henry Schein", "price": 83.94 }, + { "symbol": "FISV", "name": "Fiserv Inc.", "price": 65.43 }, + { "symbol": "CSGP", "name": "CoStar Group", "price": 48.87 }, + { "symbol": "DDOG", "name": "Datadog", "price": 129.43 }, + { "symbol": "BLDR", "name": "Builders FirstSource", "price": 118.27 }, + { "symbol": "AXON", "name": "Axon Enterprise", "price": 473.33 }, + { "symbol": "ALGN", "name": "Align Technology", "price": 187.51 }, + { "symbol": "FICO", "name": "Fair Isaac", "price": 1242.10 }, + { "symbol": "FITB", "name": "Fifth Third Bancorp", "price": 50.53 }, + { "symbol": "NTAP", "name": "NetApp", "price": 105.53 }, + { "symbol": "FOXA", "name": "Fox Corporation (Class A)", "price": 60.47 }, + { "symbol": "QCOM", "name": "Qualcomm", "price": 152.25 }, + { "symbol": "VTRS", "name": "Viatris", "price": 16.25 }, + { "symbol": "LRCX", "name": "Lam Research", "price": 239.39 }, + { "symbol": "PLTR", "name": "Palantir Technologies", "price": 132.98 }, + { "symbol": "PANW", "name": "Palo Alto Networks", "price": 170.52 }, + { "symbol": "NFLX", "name": "Netflix", "price": 75.58 }, + { "symbol": "KLAC", "name": "KLA Corporation", "price": 1342.60 }, + { "symbol": "NVDA", "name": "Nvidia", "price": 187.67 }, + { "symbol": "IBKR", "name": "Interactive Brokers Group", "price": 74.25 }, + { "symbol": "CRWD", "name": "CrowdStrike", "price": 448.16 }, + { "symbol": "ULTA", "name": "Ulta Beauty", "price": 577.29 }, + { "symbol": "JBHT", "name": "J.B. Hunt", "price": 225.21 }, + { "symbol": "SMCI", "name": "Supermicro", "price": 31.69 }, + { "symbol": "NXPI", "name": "NXP Semiconductors", "price": 258.77 }, + { "symbol": "VRTX", "name": "Vertex Pharmaceuticals", "price": 456.77 }, + { "symbol": "MTCH", "name": "Match Group", "price": 29.70 }, + { "symbol": "CBOE", "name": "Cboe Global Markets", "price": 261.74 }, + { "symbol": "CPRT", "name": "Copart", "price": 38.12 }, + { "symbol": "VICI", "name": "Vici Properties", "price": 33.48 }, + { "symbol": "CHRW", "name": "C.H. Robinson", "price": 182.86 }, + { "symbol": "INTC", "name": "Intel", "price": 44.51 }, + { "symbol": "ROST", "name": "Ross Stores", "price": 206.42 }, + { "symbol": "GEHC", "name": "GE HealthCare", "price": 86.95 }, + { "symbol": "SCHW", "name": "Charles Schwab Corporation", "price": 96.49 }, + { "symbol": "CVNA", "name": "Carvana Co.", "price": 368.51 }, + { "symbol": "IDXX", "name": "Idexx Laboratories", "price": 766.54 }, + { "symbol": "INCY", "name": "Incyte", "price": 101.02 }, + { "symbol": "GNRC", "name": "Generac", "price": 224.22 }, + { "symbol": "CPAY", "name": "Corpay", "price": 366.15 }, + { "symbol": "REGN", "name": "Regeneron Pharmaceuticals", "price": 833.09 }, + { "symbol": "CTAS", "name": "Cintas", "price": 230.80 }, + { "symbol": "FAST", "name": "Fastenal", "price": 42.07 }, + { "symbol": "AMZN", "name": "Amazon", "price": 207.86 }, + { "symbol": "ZBRA", "name": "Zebra Technologies", "price": 285.78 }, + { "symbol": "ODFL", "name": "Old Dominion", "price": 208.16 }, + { "symbol": "TTWO", "name": "Take-Two Interactive", "price": 176.58 }, + { "symbol": "CTRA", "name": "Coterra", "price": 28.69 }, + { "symbol": "LDOS", "name": "Leidos", "price": 172.90 }, + { "symbol": "ARES", "name": "Ares Management Corporation", "price": 131.22 }, + { "symbol": "CINF", "name": "Cincinnati Financial", "price": 172.93 }, + { "symbol": "ANET", "name": "Arista Networks", "price": 142.22 }, + { "symbol": "AMCR", "name": "Amcor", "price": 45.14 }, + { "symbol": "HBAN", "name": "Huntington Bancshares", "price": 20.67 }, + { "symbol": "EVRG", "name": "Evergy", "price": 75.68 }, + { "symbol": "ABNB", "name": "Airbnb", "price": 125.39 }, + { "symbol": "DASH", "name": "DoorDash", "price": 164.09 }, + { "symbol": "COIN", "name": "Coinbase Global", "price": 146.37 }, + { "symbol": "CIEN", "name": "Ciena Corporation", "price": 254.65 }, + { "symbol": "FANG", "name": "Diamondback Energy", "price": 155.78 }, + { "symbol": "PSKY", "name": "Paramount Skydance Corp", "price": 10.73 }, + { "symbol": "ORLY", "name": "O'Reilly Auto Parts", "price": 96.40 }, + { "symbol": "SBAC", "name": "SBA Communications", "price": 193.40 }, + { "symbol": "ACGL", "name": "Arch Capital Group", "price": 103.61 }, + { "symbol": "CTSH", "name": "Cognizant", "price": 60.84 }, + { "symbol": "VLTO", "name": "Veralto", "price": 102.80 }, + { "symbol": "MPWR", "name": "Monolithic Power Systems", "price": 1210.08 }, + { "symbol": "PODD", "name": "Insulet Corporation", "price": 264.60 }, + { "symbol": "MRSH", "name": "Marsh & McLennan Companies Inc.", "price": 159.69 }, + { "symbol": "PAYC", "name": "Paycom", "price": 112.44 }, + { "symbol": "NTRS", "name": "Northern Trust", "price": 138.26 }, + { "symbol": "ADSK", "name": "Autodesk", "price": 232.08 }, + { "symbol": "MSCI", "name": "MSCI", "price": 472.01 }, + { "symbol": "ORCL", "name": "Oracle Corporation", "price": 168.14 }, + { "symbol": "ERIE", "name": "Erie Indemnity", "price": 273.98 }, + { "symbol": "TECH", "name": "Bio-Techne", "price": 59.22 }, + { "symbol": "TRMB", "name": "Trimble Inc.", "price": 70.40 }, + { "symbol": "EBAY", "name": "eBay", "price": 67.25 }, + { "symbol": "INVH", "name": "Invitation Homes", "price": 26.09 }, + { "symbol": "NDSN", "name": "Nordson Corporation", "price": 289.84 }, + { "symbol": "DECK", "name": "Deckers Brands", "price": 114.76 }, + { "symbol": "ADBE", "name": "Adobe Inc.", "price": 258.37 }, + { "symbol": "SNPS", "name": "Synopsys", "price": 452.44 }, + { "symbol": "CHTR", "name": "Charter Communications", "price": 270.87 }, + { "symbol": "STLD", "name": "Steel Dynamics", "price": 193.65 }, + { "symbol": "BIIB", "name": "Biogen", "price": 193.61 }, + { "symbol": "TRGP", "name": "Targa Resources", "price": 242.36 }, + { "symbol": "SOLV", "name": "Solventum", "price": 70.47 }, + { "symbol": "TROW", "name": "T. Rowe Price", "price": 92.55 }, + { "symbol": "AVGO", "name": "Broadcom", "price": 365.11 }, + { "symbol": "CSCO", "name": "Cisco", "price": 75.74 }, + { "symbol": "CTVA", "name": "Corteva", "price": 76.63 }, + { "symbol": "EXPD", "name": "Expeditors International", "price": 169.73 }, + { "symbol": "EXPE", "name": "Expedia Group", "price": 281.69 }, + { "symbol": "AKAM", "name": "Akamai Technologies", "price": 85.09 }, + { "symbol": "CBRE", "name": "CBRE Group", "price": 137.79 }, + { "symbol": "FFIV", "name": "F5 Inc.", "price": 285.06 }, + { "symbol": "MSFT", "name": "Microsoft", "price": 389.46 }, + { "symbol": "COST", "name": "Costco", "price": 932.30 }, + { "symbol": "NCLH", "name": "Norwegian Cruise Line Holdings", "price": 24.07 }, + { "symbol": "KEYS", "name": "Keysight Technologies", "price": 207.44 }, + { "symbol": "SPGI", "name": "S&P Global", "price": 426.67 }, + { "symbol": "UBER", "name": "Uber", "price": 75.84 }, + { "symbol": "BKNG", "name": "Booking Holdings", "price": 4386.71 }, + { "symbol": "DELL", "name": "Dell Technologies", "price": 142.26 }, + { "symbol": "DXCM", "name": "Dexcom", "price": 65.13 }, + { "symbol": "PYPL", "name": "PayPal", "price": 32.31 }, + { "symbol": "GOOG", "name": "Alphabet Inc. (Class C)", "price": 282.90 }, + { "symbol": "BALL", "name": "Ball Corporation", "price": 60.67 }, + { "symbol": "WELL", "name": "Welltower", "price": 194.82 }, + { "symbol": "MNST", "name": "Monster Beverage", "price": 80.45 }, + { "symbol": "OTIS", "name": "Otis Worldwide", "price": 84.53 }, + { "symbol": "SWKS", "name": "Skyworks Solutions", "price": 63.13 }, + { "symbol": "GRMN", "name": "Garmin", "price": 185.49 }, + { "symbol": "WDAY", "name": "Workday Inc.", "price": 118.22 }, + { "symbol": "APTV", "name": "Aptiv", "price": 83.68 }, + { "symbol": "RVTY", "name": "Revvity", "price": 91.38 }, + { "symbol": "TMUS", "name": "T-Mobile US", "price": 204.96 }, + { "symbol": "LULU", "name": "Lululemon Athletica", "price": 167.54 }, + { "symbol": "HOLX", "name": "Hologic", "price": 64.16 }, + { "symbol": "NWSA", "name": "News Corp (Class A)", "price": 21.24 }, + { "symbol": "PAYX", "name": "Paychex", "price": 89.52 }, + { "symbol": "CDNS", "name": "Cadence Design Systems", "price": 295.12 }, + { "symbol": "ALLE", "name": "Allegion", "price": 198.52 }, + { "symbol": "GILD", "name": "Gilead Sciences", "price": 144.57 }, + { "symbol": "EQIX", "name": "Equinix", "price": 932.33 }, + { "symbol": "ISRG", "name": "Intuitive Surgical", "price": 590.38 } ] }