diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/AudioManager.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/AudioManager.java index a1fff06..ca52674 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/AudioManager.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/AudioManager.java @@ -21,6 +21,8 @@ */ public final class AudioManager { + private static final System.Logger LOGGER = + System.getLogger(AudioManager.class.getName()); private static final AudioManager INSTANCE = new AudioManager(); private final Map sfxCache = new EnumMap<>(Audio.class); @@ -36,8 +38,8 @@ private AudioManager() { if (stream != null) { sfxCache.put(audio, stream.readAllBytes()); } - } catch (IOException ignored) { - // Fail silently + } catch (IOException e) { + LOGGER.log(System.Logger.Level.WARNING, "Failed to cache SFX: {0}", audio.getPath()); } } } @@ -94,8 +96,8 @@ public void playSfx(final Audio audio) { }); clip.start(); - } catch (Exception ignored) { - // SFX playback failed silently + } catch (Exception e) { + LOGGER.log(System.Logger.Level.WARNING, "SFX playback failed", e); } }, "sfx-thread").start(); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java index 85b1cd3..10add7b 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java @@ -23,6 +23,9 @@ /** Controller for StockApp. */ public final class StockAppController implements AppController { + private static final System.Logger LOGGER = + System.getLogger(StockAppController.class.getName()); + private final MarketController marketController; private final StockApp stockApp; private final Player player; @@ -264,8 +267,8 @@ private void handleTransaction() { if (onInsufficientFunds != null) { onInsufficientFunds.run(); } - } catch (Exception ignored) { - // Log unexpected error silently or handle it + } catch (Exception e) { + LOGGER.log(System.Logger.Level.WARNING, "Unexpected error during buy transaction", e); } } else { handleSell(currentStock, quantity); @@ -296,8 +299,8 @@ private void handleSell(final Stock stock, final BigDecimal quantity) { try { transaction.commit(player); player.updateStatus(); - } catch (Exception ignored) { - // Silent fail intended for this specific case + } catch (Exception e) { + LOGGER.log(System.Logger.Level.WARNING, "Unexpected error during sell transaction", e); } } }); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java index 1547af2..16e54bd 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java @@ -239,12 +239,12 @@ private void setupListeners() { timeAndWeatherController.temperatureProperty().addListener((obs, ov, nv) -> desktopView.updateTemperature(String.valueOf(nv))); - player.getNameProperty().addListener((obs, ov, nv) -> - desktopView.updatePlayerName(nv)); + player.addNameListener(name -> + Platform.runLater(() -> desktopView.updatePlayerName(name))); player.updateStatus(); - player.getStatusProperty().addListener((obs, ov, nv) -> - desktopView.updatePlayerStatus(nv.name())); + player.addStatusListener(s -> + Platform.runLater(() -> desktopView.updatePlayerStatus(s.name()))); desktopView.updateClock(timeAndWeatherController.getTimeString()); String startDay = timeAndWeatherController.getDayOfWeekString(); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/StartViewController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/StartViewController.java index 7e3fcd9..174356f 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/StartViewController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/StartViewController.java @@ -32,6 +32,9 @@ */ public class StartViewController { + private static final System.Logger LOGGER = + System.getLogger(StartViewController.class.getName()); + private String username = ""; private final StartView startView; @@ -132,8 +135,8 @@ private void handleStart() { try { userSelectedPath = Path.of(Objects.requireNonNull( getClass().getClassLoader().getResource("stocks.csv")).toURI()); - } catch (Exception ignored) { - // Fallback or ignore + } catch (Exception e) { + LOGGER.log(System.Logger.Level.WARNING, "Could not resolve default stocks.csv resource", e); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java index 7494e7b..cbedf32 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java @@ -3,12 +3,9 @@ import edu.ntnu.idi.idatt2003.gruppe42.model.exceptions.InsufficientFunds; import edu.ntnu.idi.idatt2003.gruppe42.model.transaction.TransactionArchive; import java.math.BigDecimal; -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleIntegerProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; /** * Represents a player in the game, holding their financial state, portfolio, @@ -26,13 +23,17 @@ public class Player { private static final double INVESTOR_NET_WORTH_MULTIPLIER = 1.2; private static final double SPECULATOR_NET_WORTH_MULTIPLIER = 5.0; - private final StringProperty name = new SimpleStringProperty(); + private String name; private final BigDecimal startingMoney; private BigDecimal money; private final Portfolio portfolio; private final TransactionArchive transactionArchive; - private final IntegerProperty lives = new SimpleIntegerProperty(MAX_LIVES); - private final ObjectProperty status = new SimpleObjectProperty<>(Status.NOVICE); + private int lives = MAX_LIVES; + private Status status = Status.NOVICE; + + private final List> nameListeners = new ArrayList<>(); + private final List> livesListeners = new ArrayList<>(); + private final List> statusListeners = new ArrayList<>(); /** * Constructs a new {@code Player} with the given name and starting balance. @@ -50,7 +51,7 @@ public Player(final String name, final BigDecimal startingMoney) { if (startingMoney == null || startingMoney.compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("Starting money cannot be null or negative"); } - this.name.set(name); + this.name = name; this.startingMoney = startingMoney; this.money = startingMoney; this.portfolio = new Portfolio(); @@ -58,14 +59,12 @@ public Player(final String name, final BigDecimal startingMoney) { } /** - * Returns the observable {@link StringProperty} backing the player's name. - * - *

Useful for binding UI components directly to the player's name. + * Registers a listener that is notified whenever the player's name changes. * - * @return the name property; never {@code null} + * @param listener the consumer to call with the new name; must not be {@code null} */ - public StringProperty getNameProperty() { - return name; + public void addNameListener(final Consumer listener) { + nameListeners.add(listener); } /** @@ -74,16 +73,17 @@ public StringProperty getNameProperty() { * @return the name; never {@code null} */ public String getName() { - return name.get(); + return name; } /** - * Sets the player's display name. + * Sets the player's display name and notifies registered name listeners. * * @param name the new name; must not be {@code null} */ public void setName(final String name) { - this.name.set(name); + this.name = name; + nameListeners.forEach(l -> l.accept(name)); } /** @@ -110,7 +110,7 @@ public BigDecimal getMoney() { * @return the status name (e.g., {@code "NOVICE"}, {@code "INVESTOR"}, {@code "SPECULATOR"}) */ public String getStatus() { - return status.get().name(); + return status.name(); } /** @@ -168,8 +168,13 @@ public BigDecimal getNetWorth() { return money.add(portfolio.getNetWorth()); } - public IntegerProperty getLivesProperty() { - return lives; + /** + * Registers a listener that is notified whenever the player's life count changes. + * + * @param listener the consumer to call with the new life count; must not be {@code null} + */ + public void addLivesListener(final Consumer listener) { + livesListeners.add(listener); } /** @@ -178,14 +183,16 @@ public IntegerProperty getLivesProperty() { * @return the number of lives remaining */ public int getLives() { - return lives.get(); + return lives; } /** - * Decrements the player's life count by one, stopping at zero. + * Decrements the player's life count by one, stopping at zero, + * and notifies registered lives listeners. */ public void loseLife() { - lives.set(Math.max(0, lives.get() - 1)); + lives = Math.max(0, lives - 1); + livesListeners.forEach(l -> l.accept(lives)); } /** @@ -237,16 +244,27 @@ public void updateStatus() { boolean isInvestor = weeksActive >= INVESTOR_WEEKS && netWorth.compareTo(investorTarget) >= 0; + Status next; if (isSpeculator) { - status.set(Status.SPECULATOR); + next = Status.SPECULATOR; } else if (isInvestor) { - status.set(Status.INVESTOR); + next = Status.INVESTOR; } else { - status.set(Status.NOVICE); + next = Status.NOVICE; + } + + if (next != status) { + status = next; + statusListeners.forEach(l -> l.accept(status)); } } - public ObjectProperty getStatusProperty() { - return status; + /** + * Registers a listener that is notified whenever the player's status changes. + * + * @param listener the consumer to call with the new status; must not be {@code null} + */ + public void addStatusListener(final Consumer listener) { + statusListeners.add(listener); } } \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Purchase.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Purchase.java index cbc715e..841f236 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Purchase.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Purchase.java @@ -42,4 +42,14 @@ public void commit(final Player player) { } player.getTransactionArchive().add(this); } + + /** + * Returns the total cost of this purchase as a negative contribution to weekly income. + * + * @return the purchase total negated + */ + @Override + public java.math.BigDecimal netIncomeContribution() { + return getCalculator().calculateTotal().negate(); + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Sale.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Sale.java index 80ae53c..e5ba743 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Sale.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Sale.java @@ -48,4 +48,14 @@ public void commit(final Player player) { } player.getTransactionArchive().add(this); } + + /** + * Returns the net proceeds of this sale as a positive contribution to weekly income. + * + * @return the sale total after commission and tax + */ + @Override + public java.math.BigDecimal netIncomeContribution() { + return getCalculator().calculateTotal(); + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Transaction.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Transaction.java index 1755d71..3918095 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Transaction.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Transaction.java @@ -3,6 +3,7 @@ import edu.ntnu.idi.idatt2003.gruppe42.model.Player; import edu.ntnu.idi.idatt2003.gruppe42.model.Share; import edu.ntnu.idi.idatt2003.gruppe42.model.calculator.TransactionCalculator; +import java.math.BigDecimal; /** * Base class for all stock-related transactions. @@ -68,4 +69,14 @@ public TransactionCalculator getCalculator() { * @param player the player executing the transaction */ public abstract void commit(Player player); + + /** + * Returns this transaction's signed contribution to weekly net income. + * + *

Sales return a positive value (money received); purchases return a negative + * value (money spent). Used by {@link TransactionArchive#calculateNetIncome(int)}. + * + * @return the signed net-income contribution of this transaction + */ + public abstract BigDecimal netIncomeContribution(); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/TransactionArchive.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/TransactionArchive.java index 647d881..9093438 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/TransactionArchive.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/TransactionArchive.java @@ -126,11 +126,7 @@ public int countDistinctWeeks() { public BigDecimal calculateNetIncome(final int week) { BigDecimal net = BigDecimal.ZERO; for (Transaction t : getTransactions(week)) { - if (t instanceof Sale) { - net = net.add(t.getCalculator().calculateTotal()); - } else if (t instanceof Purchase) { - net = net.subtract(t.getCalculator().calculateTotal()); - } + net = net.add(t.netIncomeContribution()); } return net; } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/BankApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/BankApp.java index fa9e4bd..b2c3b50 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/BankApp.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/BankApp.java @@ -44,7 +44,7 @@ public class BankApp extends Popup { *

Initializes the portfolio list view and builds the UI layout. */ public BankApp() { - super(550, 500, 220, 100, App.BANK); + super(550, 600, 220, 100, App.BANK); portfolioList = new ListView<>(); portfolioList.setSelectionModel(null); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/components/DesktopBottomBar.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/components/DesktopBottomBar.java index 96585c7..b427a7e 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/components/DesktopBottomBar.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/components/DesktopBottomBar.java @@ -128,7 +128,7 @@ private HBox buildHeartsBox(final Player player) { hearts[i].getStyleClass().add(active ? "heart-active" : "heart-empty"); } }; - player.getLivesProperty().addListener((obs, ov, nv) -> Platform.runLater(refresh)); + player.addLivesListener(nv -> Platform.runLater(refresh)); refresh.run(); return box;