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