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..a57fa31 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 @@ -217,28 +217,62 @@ public void start(final Stage stage) throws Exception { 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..."); + // Auto-save the currently active save. Used by every trigger below + // (quit-button, window close, money change, week advance). Silent + // if no save is active (i.e. the user got into the in-game scene + // without going through the save list), or if a save-load is + // currently in progress (the money / week mutations inside + // applySave fire the same property listeners we hook up below, so + // without this guard we would write half-loaded state back to disk + // in the middle of a load). + Runnable autoSave = () -> { + if (gameStateLoader.isLoading()) { + return; + } SaveGame snapshot = gameStateLoader.snapshotActiveSave(); if (snapshot == null) { - 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."); + System.out.println("[auto-save] Wrote save '" + snapshot.getName() + + "' (balance=" + snapshot.getBalance() + + ", week=" + snapshot.getWeek() + + ", shares=" + snapshot.getOwnedShares().size() + + ", txns=" + snapshot.getTransactions().size() + ")."); } catch (Exception e) { System.err.println("[auto-save] Failed: " + e.getMessage()); e.printStackTrace(); } + }; + + // Trigger 1: quit-button in the top bar (refreshes the play-game + // list too so the row balance reflects what we just wrote). + topBarController.setOnQuitToMainMenu(() -> { + System.out.println("[auto-save] Quit-button triggered."); + autoSave.run(); + playGameController.refresh(); + }); + + // Trigger 2: window close (X-button on the OS window). Catches the + // case where the user closes the app directly instead of going + // back to the menu. + stage.setOnCloseRequest(e -> { + System.out.println("[auto-save] Window close triggered."); + autoSave.run(); + }); + + // Trigger 3: every time the player's money changes (buy / sell). + // This means progress is persisted immediately after each + // transaction, so a save is never more than one action stale. + player.getMoneyAsFloatProperty().addListener((obs, oldVal, newVal) -> { + autoSave.run(); + }); + + // Trigger 4: every time the in-game week advances. Captures price + // history-related state (the saved week is part of the snapshot). + exchange.weekProperty().addListener((obs, oldVal, newVal) -> { + autoSave.run(); }); // Register all views 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..201160e 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 @@ -84,6 +84,20 @@ public final class GameStateLoader * */ private SaveGame activeSave; + /** + * Flag set while {@link #applySave} is running. + * + *
+ * The apply-save flow mutates player money and the exchange week, + * both of which fire JavaFX property change events. The auto-save + * listeners hooked up in {@code Main} react to those events, so + * without this flag they would write the half-loaded state back to + * disk in the middle of a load. Auto-save callers check this via + * {@link #isLoading()} and skip while it is {@code true}. + *
+ * */ + private boolean loading; + /** * Constructor. * @@ -123,6 +137,21 @@ public SaveGame getActiveSave() { return activeSave; } + /** + * Returns whether a save-load is currently in progress. + * + *+ * Used by the auto-save flow to suppress writes during {@link #applySave}, + * since money and week mutations there fire JavaFX property change + * events that auto-save listens to. + *
+ * + * @return {@code true} while {@link #applySave} is running. + * */ + public boolean isLoading() { + return loading; + } + /** * Builds a fresh {@link SaveGame} snapshot of the current player * and exchange state, using the active save's name and starting @@ -212,79 +241,84 @@ private void applySave(final SaveGame save) { + ", shares=" + save.getOwnedShares().size() + ", txns=" + save.getTransactions().size()); - // 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); - exchange.setWeek(1); + loading = true; + try { + // 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); + 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. - Portfolio portfolio = player.getPortfolio(); - portfolio.clear(); - TransactionArchive archive = player.getTransactionArchive(); - archive.clear(); - player.setMoney(BigDecimal.valueOf(save.getStartingCapital())); - player.refreshProperties(); + // 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. + Portfolio portfolio = player.getPortfolio(); + portfolio.clear(); + TransactionArchive archive = player.getTransactionArchive(); + archive.clear(); + 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. - int targetWeek = Math.max(1, save.getWeek()); - while (exchange.getWeek() < targetWeek) { - exchange.advance(); - } + // 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. + 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). - for (OwnedShareData od : save.getOwnedShares()) { - if (!exchange.hasStock(od.getSymbol())) { - System.err.println("Skipping unknown stock from save: " - + od.getSymbol()); - continue; + // 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). + for (OwnedShareData od : save.getOwnedShares()) { + if (!exchange.hasStock(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())); } - Stock stock = exchange.getStock(od.getSymbol()); - 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. - for (TransactionData td : save.getTransactions()) { - if (!exchange.hasStock(td.getSymbol())) { - System.err.println("Skipping transaction with unknown stock: " - + td.getSymbol()); - continue; + // 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. + for (TransactionData td : save.getTransactions()) { + if (!exchange.hasStock(td.getSymbol())) { + System.err.println("Skipping transaction with unknown stock: " + + td.getSymbol()); + continue; + } + Stock stock = exchange.getStock(td.getSymbol()); + Share share = new Share(stock, td.getQuantity(), td.getPrice()); + TransactionCalculator calculator = (td.getType() == TransactionType.SALE) + ? new SaleCalculator(share) + : new PurchaseCalculator(share); + Transaction transaction = (td.getType() == TransactionType.SALE) + ? new Sale(share, td.getWeek(), calculator) + : new Purchase(share, td.getWeek(), calculator); + archive.add(transaction); } - Stock stock = exchange.getStock(td.getSymbol()); - Share share = new Share(stock, td.getQuantity(), td.getPrice()); - TransactionCalculator calculator = (td.getType() == TransactionType.SALE) - ? new SaleCalculator(share) - : new PurchaseCalculator(share); - Transaction transaction = (td.getType() == TransactionType.SALE) - ? new Sale(share, td.getWeek(), calculator) - : new Purchase(share, td.getWeek(), calculator); - archive.add(transaction); - } - // 6. Overwrite the balance with the saved value now that the - // history has been built up. - player.setMoney(BigDecimal.valueOf(save.getBalance())); + // 6. Overwrite the balance with the saved value now that the + // history has been built up. + player.setMoney(BigDecimal.valueOf(save.getBalance())); - // 7. Re-publish the float properties so the final loaded - // balance is visible to all listeners. - player.refreshProperties(); + // 7. Re-publish the float properties so the final loaded + // balance is visible to all listeners. + player.refreshProperties(); - this.activeSave = save; + this.activeSave = save; + } finally { + loading = false; + } } /** diff --git a/src/main/resources/saves/Halleluja.json b/src/main/resources/saves/Halleluja.json deleted file mode 100644 index 6068147..0000000 --- a/src/main/resources/saves/Halleluja.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Halleluja", - "balance": 1000650901.43, - "startingCapital": 10000.0, - "stockDataPath": null -} diff --git a/src/main/resources/saves/Newbie.json b/src/main/resources/saves/Newbie.json index 823acaf..bd833ea 100644 --- a/src/main/resources/saves/Newbie.json +++ b/src/main/resources/saves/Newbie.json @@ -1,20 +1,11 @@ { "name": "Newbie", - "balance": 388.6825, + "balance": 134.5153, "startingCapital": 10000.0, "stockDataPath": null, - "week": 5, + "week": 27, "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 }, @@ -26,6 +17,13 @@ { "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 }, - { "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": "SALE", "symbol": "NVDA", "quantity": 14.0, "price": 174.30, "week": 18 }, + { "type": "SALE", "symbol": "NVDA", "quantity": 14.0, "price": 174.30, "week": 18 }, + { "type": "SALE", "symbol": "NVDA", "quantity": 14.0, "price": 174.30, "week": 18 }, + { "type": "PURCHASE", "symbol": "NVDA", "quantity": 17.0, "price": 177.72, "week": 18 }, + { "type": "PURCHASE", "symbol": "NVDA", "quantity": 17.0, "price": 177.72, "week": 18 }, + { "type": "PURCHASE", "symbol": "NVDA", "quantity": 7.0, "price": 177.72, "week": 18 }, + { "type": "PURCHASE", "symbol": "NVDA", "quantity": 1.0, "price": 177.72, "week": 18 } ] } 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 } - ] -}