Skip to content

Add observer #17

Merged
merged 3 commits into from
May 24, 2026
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions src/main/java/Model/Exchange.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public class Exchange {
private final Map<String, Stock> stockMap;
private final Random random;

// Registered observers
private final List<ExchangeObserver> observers = new ArrayList<>();

public Exchange(String name, List<Stock> stocks) {
this.name = name;
this.week = 1;
Expand All @@ -22,9 +25,26 @@ public Exchange(String name, List<Stock> stocks) {
for (Stock stock : stocks) {
stockMap.put(stock.getSymbol(), stock);
}
}

// ---- Observer ----

public void addObserver(ExchangeObserver observer) {
if (observer != null && !observers.contains(observer)) {
observers.add(observer);
}
}

public void removeObserver(ExchangeObserver observer) {
observers.remove(observer);
}


private void notifyObservers() {
for (ExchangeObserver observer : observers) {
observer.onExchangeUpdated(this);
}
}

public String getName() {
return name;
}
Expand Down Expand Up @@ -57,7 +77,7 @@ public List<Stock> findStocks(String searchTerm) {

public Transaction buy(String symbol, BigDecimal quantity, Player player) {
Stock stock = getStock(symbol);

// unngå nullpointerexception
if (stock == null) {
return null;
Expand All @@ -71,7 +91,9 @@ public Transaction buy(String symbol, BigDecimal quantity, Player player) {

// committer til player
purchase.commit(player);


notifyObservers();

return purchase;
}

Expand All @@ -86,7 +108,9 @@ public Transaction sell(Share share, Player player) {

// commiter til player
sale.commit(player);


notifyObservers();

return sale;
}

Expand All @@ -108,6 +132,8 @@ public void advance() {
stock.addNewSalesPrice(newPrice);
}
}

notifyObservers();
}

public List<Stock> getGainers(int limit) { // viser "vinnerne"
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/Model/ExchangeObserver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package Model;

/**
* Observer interface for the Exchange subject.
* Implement this interface to receive notifications whenever the exchange
* state changes (week advances, a trade is committed).
*/
public interface ExchangeObserver {

/**
* Called by the Exchange after its state has changed.
*
* @param exchange the Exchange that triggered the notification
*/
void onExchangeUpdated(Exchange exchange);
}
144 changes: 70 additions & 74 deletions src/main/java/View/MainGameScene.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@
import java.math.BigDecimal;
import java.util.List;

public class MainGameScene {
/**
* Main game UI. Implements ExchangeObserver so the view automatically refreshes
* whenever the Exchange notifies it of a state change (week advance or trade).
*/
public class MainGameScene implements ExchangeObserver {

private Scene scene;
private Exchange exchange;
private Player player;
private Runnable onExit;
private Label statusLabel;
// References to UI components for refreshing

// References to UI components that need refreshing
private TableView<PortfolioRow> portfolioTable;
private ListView<Share> holdingsList;
private TableView<HistoryRow> historyTable;
Expand All @@ -29,9 +34,23 @@ public MainGameScene(Exchange exchange, Player player, Runnable onExit) {
this.exchange = exchange;
this.player = player;
this.onExit = onExit;

// Register this view as an observer of the exchange
this.exchange.addObserver(this);

this.scene = createScene();
}

/**
* Called automatically by Exchange whenever its state changes.
* Refreshes all UI elements to reflect the latest data.
*/
@Override
public void onExchangeUpdated(Exchange exchange) {
updateStatus();
refreshAllUI();
}

private Scene createScene() {
VBox root = new VBox(0);

Expand All @@ -49,12 +68,13 @@ private Scene createScene() {

Button nextWeekBtn = new Button("Next week");
nextWeekBtn.getStyleClass().addAll("action-button");
nextWeekBtn.setOnAction(e -> advance());
nextWeekBtn.setOnAction(e -> exchange.advance()); // observer handles the UI update

Button exitBtn = new Button("Exit");
exitBtn.getStyleClass().add("exit-button");
exitBtn.setOnAction(e -> {
if (confirm("Exit Game?", "Final Net Worth: $" + formatMoney(getNetWorth()))) {
exchange.removeObserver(this); // clean up before closing
onExit.run();
}
});
Expand Down Expand Up @@ -156,11 +176,9 @@ private VBox createPortfolioPanel() {
alert("Error", "Select a holding to sell.");
return;
}
Transaction trans = exchange.sell(selected.s, player);
Transaction trans = exchange.sell(selected.s, player); // observer fires refresh
if (trans != null && trans.isCommitted()) {
showConfirmation("Sale successful", trans);
updateStatus();
refreshAllUI();
} else {
alert("Failed", "Could not complete the sale.");
}
Expand Down Expand Up @@ -226,14 +244,12 @@ private VBox createBuyTab() {
return;
}
BigDecimal qty = new BigDecimal(qtyField.getText());
Transaction trans = exchange.buy(s.getSymbol(), qty, player);
Transaction trans = exchange.buy(s.getSymbol(), qty, player); // observer fires refresh
if (trans != null && trans.isCommitted()) {
showConfirmation("Purchase successful", trans);
stockField.clear();
qtyField.clear();
infoLabel.setText("");
updateStatus();
refreshAllUI();
} else {
alert("Failed", "Insufficient funds or error");
}
Expand All @@ -247,62 +263,50 @@ private VBox createBuyTab() {
}

private VBox createSellTab() {
VBox box = new VBox(10);
box.getStyleClass().add("content-area");

Label heading = new Label("Your Holdings:");
VBox box = new VBox(10);
box.getStyleClass().add("content-area");

holdingsList = new ListView<>();
holdingsList.setPrefHeight(400);
Label heading = new Label("Your Holdings:");

holdingsList.setCellFactory(lv -> new ListCell<Share>() {
@Override
protected void updateItem(Share s, boolean empty) {
super.updateItem(s, empty);
holdingsList = new ListView<>();
holdingsList.setPrefHeight(400);

if (empty || s == null) {
setText(null);
} else {
setText(
s.getStock().getSymbol() + " - " +
s.getQuantity() + " @ $" +
formatMoney(s.getStock().getSalesPrice())
);
holdingsList.setCellFactory(lv -> new ListCell<Share>() {
@Override
protected void updateItem(Share s, boolean empty) {
super.updateItem(s, empty);
if (empty || s == null) {
setText(null);
} else {
setText(
s.getStock().getSymbol() + " - " +
s.getQuantity() + " @ $" +
formatMoney(s.getStock().getSalesPrice())
);
}
}
}
});

updateHoldingsList(holdingsList);

Button sellBtn = new Button("Sell Selected");

sellBtn.setOnAction(e -> {
Share selected = holdingsList.getSelectionModel().getSelectedItem();

if (selected == null) {
alert("Error", "Select a holding to sell");
return;
}

Transaction trans = exchange.sell(selected, player);

if (trans != null && trans.isCommitted()) {
showConfirmation("Sale successful", trans);
refreshAllUI();
} else {
alert("Failed", "Could not complete sale");
}
});
});

box.getChildren().addAll(
heading,
holdingsList,
sellBtn
);
updateHoldingsList(holdingsList);

return box;
}
Button sellBtn = new Button("Sell Selected");
sellBtn.setOnAction(e -> {
Share selected = holdingsList.getSelectionModel().getSelectedItem();
if (selected == null) {
alert("Error", "Select a holding to sell");
return;
}
Transaction trans = exchange.sell(selected, player); // observer fires refresh
if (trans != null && trans.isCommitted()) {
showConfirmation("Sale successful", trans);
} else {
alert("Failed", "Could not complete sale");
}
});

box.getChildren().addAll(heading, holdingsList, sellBtn);
return box;
}

private VBox createHistoryPanel() {
VBox panel = new VBox(10);
Expand All @@ -317,7 +321,8 @@ private VBox createHistoryPanel() {

weekFilterCombo.setOnAction(e -> updateHistory(
historyTable,
weekFilterCombo.getValue() == null || weekFilterCombo.getValue() == 0 ? null : weekFilterCombo.getValue()
weekFilterCombo.getValue() == null || weekFilterCombo.getValue() == 0
? null : weekFilterCombo.getValue()
));

HBox filterRow = new HBox(8);
Expand Down Expand Up @@ -394,13 +399,8 @@ private void updatePortfolio(TableView<PortfolioRow> table) {
}

private void updateHoldingsList(ListView<Share> list) {
ObservableList<Share> items =
FXCollections.observableArrayList();

items.addAll(
player.getPortfolio().getShares()
);

ObservableList<Share> items = FXCollections.observableArrayList();
items.addAll(player.getPortfolio().getShares());
list.setItems(items);
}

Expand Down Expand Up @@ -457,19 +457,15 @@ private void refreshAllUI() {
updateHoldingsList(holdingsList);
}
if (historyTable != null) {
updateHistory(historyTable, weekFilterCombo != null && weekFilterCombo.getValue() != null ? weekFilterCombo.getValue() : null);
updateHistory(historyTable,
weekFilterCombo != null && weekFilterCombo.getValue() != null
? weekFilterCombo.getValue() : null);
if (weekFilterCombo != null) {
updateWeekCombo(weekFilterCombo);
}
}
}

private void advance() {
exchange.advance();
updateStatus();
refreshAllUI();
}

private BigDecimal getNetWorth() {
return player.getMoney().add(player.getPortfolio().getNetWorth());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Controller\StockFileHandler.java
C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Exchange.java
C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\ExchangeObserver.java
C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Player.java
C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Portfolio.java
C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Purchase.java
Expand Down