Skip to content

Commit

Permalink
fikset???
Browse files Browse the repository at this point in the history
  • Loading branch information
EspenTinius committed May 26, 2026
1 parent 4310a27 commit d0e5e52
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 23 deletions.
17 changes: 17 additions & 0 deletions src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,23 @@ public void setNetWorthHistory(final List<BigDecimal> history) {
}
}

/**
* Appends a new net-worth sample to this player's history.
*
* <p>Used by the simulation driver to record one data point per
* advanced week so the resulting time series can be persisted into a
* {@link SaveGame} and replayed by the stats dashboard after a
* reload. Null values are ignored.</p>
*
* @param sample the net-worth value to record.
*/
public void recordNetWorthSample(final BigDecimal sample) {
if (sample == null) {
return;
}
this.netWorthHistory.add(sample);
}

/**
* Adds money to the players balance.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,29 +175,54 @@ public <T> void invoke(final EventData<T> data, final EventManager manager) {
* @param save save to apply.
* */
private void applySave(final SaveGame save) {
Map<String, BigDecimal> activeBaseline;
if (save.getExchangeStocks() != null && !save.getExchangeStocks().isEmpty()) {
activeBaseline = captureBaseline(save.getExchangeStocks());
// The save snapshot now carries the full price history for every
// stock, so the simplest and most deterministic restore is to drop
// the lagged exchange's stock pool entirely and replace it with the
// saved one. That preserves both the current price *and* the
// historical chart line, with no random replay between save and
// load.
//
// For old save files that only contain a current price per stock
// (no "prices" array, no "netWorthHistory" array), we still need a
// sensible fallback. In that case we keep the previous behaviour
// of resetting the live exchange to the baseline and advancing the
// simulation forward week-by-week, accepting that the resulting
// chart will be "freshly simulated" because the source data simply
// doesn't contain the history needed to be more accurate.
boolean haveSavedStocks =
save.getExchangeStocks() != null && !save.getExchangeStocks().isEmpty();
boolean haveSavedHistory = haveSavedStocks
&& save.getExchangeStocks().getFirst().getHistoricalPrices().size() > 1;

if (haveSavedStocks && haveSavedHistory) {
// Modern save: snapshot has full price history per stock.
// Replace pool wholesale, set the week directly, do NOT advance.
this.exchange.updateStockPool(save.getExchangeStocks());
exchange.setWeek(Math.max(1, save.getWeek()));
} else {
activeBaseline = this.baselinePrices;
}
// Legacy save: no per-stock history available. Reset to a
// baseline and advance the simulation forward to the saved week
// so the rest of the game state at least has the right number
// of price ticks behind it.
Map<String, BigDecimal> activeBaseline = haveSavedStocks
? captureBaseline(save.getExchangeStocks())
: this.baselinePrices;
if (haveSavedStocks) {
this.exchange.updateStockPool(save.getExchangeStocks());
}
exchange.resetStocksTo(activeBaseline);
exchange.setWeek(1);

exchange.resetStocksTo(activeBaseline);
exchange.setWeek(1);
int targetWeek = Math.max(1, save.getWeek());
while (exchange.getWeek() < targetWeek) {
exchange.advance();
}
}

Portfolio portfolio = player.getPortfolio();
portfolio.clear();
TransactionArchive archive = player.getTransactionArchive();
archive.clear();
player.setMoney(BigDecimal.valueOf(save.getStartingCapital()));
player.refreshProperties();

int targetWeek = Math.max(1, save.getWeek());
while (exchange.getWeek() < targetWeek) {
exchange.advance();
}

for (OwnedShareData od : save.getOwnedShares()) {
if (!exchange.hasStock(od.getSymbol())) {
Expand Down Expand Up @@ -226,10 +251,15 @@ private void applySave(final SaveGame save) {

player.setMoney(BigDecimal.valueOf(save.getBalance()));

if (save.getNetWorthHistory() != null) {
if (save.getNetWorthHistory() != null && !save.getNetWorthHistory().isEmpty()) {
player.setNetWorthHistory(save.getNetWorthHistory());
} else {
player.setNetWorthHistory(new ArrayList<>());
// No recorded history available - seed a minimal two-point
// history so the chart still has something to render.
List<BigDecimal> seed = new ArrayList<>();
seed.add(BigDecimal.valueOf(save.getStartingCapital()));
seed.add(player.getNetWorth());
player.setNetWorthHistory(seed);
}

player.refreshProperties();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,16 +255,51 @@ private SaveGame parseFile(final Path file) {

if (sym != null && nm != null && prcStr != null) {
try {
exchangeStocks.add(new Stock(sym, nm, new BigDecimal(prcStr)));
// Read full price history if present, otherwise fall back
// to the single "price" field for backwards compatibility
// with old save files.
List<BigDecimal> history =
parseNumberArray(extractArrayBody(objectStr, "prices"));
BigDecimal seed;
if (history != null && !history.isEmpty()) {
seed = history.getFirst();
} else {
seed = new BigDecimal(prcStr);
}

Stock stock = new Stock(sym, nm, seed);
if (history != null && history.size() > 1) {
for (int idx = 1; idx < history.size(); idx++) {
stock.addNewSalesPrice(history.get(idx));
}
}

// Restore fortune if present.
String fortuneStr = stockFields.get("fortune");
if (fortuneStr != null && !fortuneStr.isEmpty()) {
try {
stock.setFortune(Double.parseDouble(fortuneStr));
} catch (NumberFormatException ignored) {
// Default fortune of 0 will be retained.
}
}

exchangeStocks.add(stock);
} catch (Exception e) {
// Skip individual invalid elements.
}
}
}
}

List<BigDecimal> netWorthHistory =
parseNumberArray(extractArrayBody(content, "netWorthHistory"));
if (netWorthHistory == null) {
netWorthHistory = Collections.emptyList();
}

return new SaveGame(name, balance, startingCapital, stockDataPath,
week, ownedShares, transactions, exchangeStocks);
week, ownedShares, transactions, exchangeStocks, netWorthHistory);
} catch (IOException | NumberFormatException e) {
System.err.println("Skipping invalid save file "
+ file.getFileName() + ": " + e.getMessage());
Expand Down Expand Up @@ -301,11 +336,21 @@ private String toJson(final SaveGame save) {
sb.append(" \"ownedShares\": ").append(ownedSharesToJson(save.getOwnedShares())).append(",\n");
sb.append(" \"transactions\": ").append(transactionsToJson(save.getTransactions())).append(",\n");

sb.append(" \"stocks\": ").append(stocksToJson(save.getExchangeStocks())).append("\n");
sb.append(" \"stocks\": ").append(stocksToJson(save.getExchangeStocks())).append(",\n");
sb.append(" \"netWorthHistory\": ").append(numberListToJson(save.getNetWorthHistory())).append("\n");
sb.append("}\n");
return sb.toString();
}

/**
* Serialises a list of {@link Stock} entries to a JSON array.
*
* <p>Each stock writes its current price <em>and</em> its full price history
* so the per-stock charts remain consistent across save/load cycles. The
* {@code price} field is kept for backwards compatibility with old saves
* and external readers; loaders that recognise {@code prices} should
* prefer the array.</p>
*/
private String stocksToJson(final List<Stock> stocks) {
if (stocks == null || stocks.isEmpty()) {
return "[]";
Expand All @@ -317,6 +362,8 @@ private String stocksToJson(final List<Stock> stocks) {
sb.append(" { \"symbol\": ").append(quote(s.getSymbol()))
.append(", \"name\": ").append(quote(s.getCompany()))
.append(", \"price\": ").append(s.getSalesPrice().toPlainString())
.append(", \"prices\": ").append(numberListToJson(s.getHistoricalPrices()))
.append(", \"fortune\": ").append(BigDecimal.valueOf(s.getFortune()).toPlainString())
.append(" }");
if (i < stocks.size() - 1) {
sb.append(",");
Expand All @@ -327,6 +374,29 @@ private String stocksToJson(final List<Stock> stocks) {
return sb.toString();
}

/**
* Serialises a list of {@link BigDecimal} values to a compact JSON array.
*
* <p>Returns {@code "[]"} for null/empty lists. Uses {@link BigDecimal#toPlainString()}
* to avoid scientific notation so the file remains diff-friendly and
* round-trippable.</p>
*/
private String numberListToJson(final List<BigDecimal> values) {
if (values == null || values.isEmpty()) {
return "[]";
}
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < values.size(); i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(values.get(i).toPlainString());
}
sb.append("]");
return sb.toString();
}

/**
* Serialises a list of {@link OwnedShareData} entries to a JSON array.
*
Expand Down Expand Up @@ -561,6 +631,44 @@ private List<OwnedShareData> parseOwnedSharesArray(final String body) {
return result;
}

/**
* Parses the body of a flat JSON number array into a list of
* {@link BigDecimal} values.
*
* <p>Accepts a comma-separated list of numeric tokens (with optional
* surrounding whitespace and an optional minus sign). Returns
* {@code null} when the supplied body is {@code null} (so callers can
* distinguish "field absent" from "field empty"). Returns an empty
* list when the body is empty or contains only whitespace. Tokens
* that fail {@link BigDecimal} parsing are silently skipped so a
* single malformed entry doesn't break the rest of the array.</p>
*
* @param body the raw substring between {@code [} and {@code ]}, or {@code null}.
* @return parsed list of {@link BigDecimal} values, or {@code null} if {@code body} is null.
*/
private List<BigDecimal> parseNumberArray(final String body) {
if (body == null) {
return null;
}
List<BigDecimal> result = new ArrayList<>();
String trimmed = body.trim();
if (trimmed.isEmpty()) {
return result;
}
for (String raw : trimmed.split(",")) {
String token = raw.trim();
if (token.isEmpty()) {
continue;
}
try {
result.add(new BigDecimal(token));
} catch (NumberFormatException e) {
System.err.println("Skipping malformed numeric entry: " + token);
}
}
return result;
}

/**
* Parses the body of a {@code transactions} array into a list of
* {@link TransactionData}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,24 @@ protected void initInteractions() {
// constructor, so we must initialize the list here.
balanceHistory = new ArrayList<>();

// Seed the history with the players starting balance so the chart
// and the all-time P&L KPI have a meaningful baseline from week 1.
balanceHistory.add(player.getStartingMoney());
// Seed the chart history from the players recorded net-worth
// history if present (eg. after a save load), otherwise from
// starting money so the chart and the all-time P&L KPI have a
// meaningful baseline from week 1.
if (player.getNetWorthHistory() != null && !player.getNetWorthHistory().isEmpty()) {
balanceHistory.addAll(player.getNetWorthHistory());
} else {
balanceHistory.add(player.getStartingMoney());
}
pushSnapshot();

exchange.weekProperty().addListener((observable, o, n) -> {
balanceHistory.add(player.getNetWorth());
BigDecimal sample = player.getNetWorth();
balanceHistory.add(sample);
// Also record the sample on the player so the snapshot taken by
// GameStateLoader (and persisted by SaveGameService) captures the
// full time series for the next load.
player.recordNetWorthSample(sample);
pushSnapshot();
});

Expand Down

0 comments on commit d0e5e52

Please sign in to comment.