Skip to content

Commit

Permalink
Feat: Saving for everything other than stats and financial summary
Browse files Browse the repository at this point in the history
  • Loading branch information
tommyah committed May 26, 2026
1 parent c1780bb commit 8b3220e
Show file tree
Hide file tree
Showing 15 changed files with 648 additions and 411 deletions.
160 changes: 66 additions & 94 deletions src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
* <p>
* Extends {@link Application}
* </p>
*
* <p>
* Initializes the application through the javafx framework.
* </p>
*/
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());
Expand All @@ -81,175 +72,156 @@ 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<Stock> 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<Stock> 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 <T> void handleEvent(final edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData<T> 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<Stock> 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();
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.");
} 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);
viewManager.addView(settingsView);
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public ReadOnlyIntegerProperty weekProperty() {
* <p>
* 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.
* </p>
*
Expand Down Expand Up @@ -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<Stock> getStocks() {
return List.copyOf(this.stockMap.values());
}

/**
* Getter method for stock element.
*
Expand Down Expand Up @@ -387,4 +396,24 @@ public List<Stock> getLosers(final int limit)
.map(Map.Entry::getValue)
.toList();
}

/**
* Clears the current stock listings and replaces them with a new pool of stocks.
*
* <p>Used when loading a save game that uses a completely different set of
* stocks than the currently active simulation context.</p>
*
* @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<Stock> 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);
}
}
}
33 changes: 32 additions & 1 deletion src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<BigDecimal> netWorthHistory = new ArrayList<>();

/**
* Current net-worth of player as a listenable,
* read-only, {@link ReadOnlyFloatWrapper} object.
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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<BigDecimal> 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<BigDecimal> history) {
this.netWorthHistory.clear();
if (history != null) {
this.netWorthHistory.addAll(history);
}
}

/**
* Adds money to the players balance.
*
Expand Down Expand Up @@ -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.
*
* <p>
* Used after the save-loading flow has mutated the player state
Expand Down
Loading

0 comments on commit 8b3220e

Please sign in to comment.