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 }
]
}