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.