Skip to content

Commit

Permalink
fet: Adding javafx tab view with graph for stocks
Browse files Browse the repository at this point in the history
  • Loading branch information
martin committed May 11, 2026
1 parent e72b5a4 commit cfbc5bc
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 10 deletions.
29 changes: 26 additions & 3 deletions src/main/java/millions/App.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
package millions;

import java.math.BigDecimal;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import millions.controller.GameController;
import millions.view.GameView;
import millions.view.StartView;

/**
* Main JavaFX application entry point for the Millions stock trading game.
*/
/** Main JavaFX application entry point for the Millions stock trading game. */
public class App extends Application {

@Override
public void start(Stage stage) {
GameController controller = new GameController();
StartView startView = new StartView(stage);

startView
.getStartButton()
.setOnAction(
event -> {
try {
controller.startGame(
startView.getName(),
new BigDecimal(startView.getStartingAmount()),
startView.getSelectedFile().toPath(),
startView.getPreRunWeeks());

GameView gameView = new GameView(controller);
controller.getPlayer().addListener(gameView);
controller.getExchange().addListener(gameView);

Scene gameScene = new Scene(gameView, 1920, 1080);
stage.setScene(gameScene);
} catch (RuntimeException ex) {
System.err.println(ex);
System.exit(0);
}
});

Scene scene = new Scene(startView, 400, 350);
stage.setTitle("Millions");
stage.setScene(scene);
Expand Down
46 changes: 44 additions & 2 deletions src/main/java/millions/controller/GameController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import java.math.BigDecimal;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import millions.controller.fileIO.CSVStockFileParser;
import millions.controller.fileIO.StockFileReader;
import millions.model.Exchange;
Expand All @@ -14,14 +16,23 @@ public class GameController {
private Player player;
private Exchange exchange;

public void startGame(String name, BigDecimal startingMoney, Path stockFilePath) {
public void startGame(
String name, BigDecimal startingMoney, Path stockFilePath, int preRunWeeks) {
if (preRunWeeks < 0) {
throw new IllegalArgumentException("Pre run weeks cannot be negative");
}

StockFileReader reader = new StockFileReader(stockFilePath);
List<String> lines = reader.readFile();
CSVStockFileParser parser = new CSVStockFileParser(lines);
List<Stock> stocks = parser.parse();

player = new Player(name, startingMoney);
exchange = new Exchange("Exchange", stocks);
for (int i = 0; i < preRunWeeks; i++) {
exchange.advance();
}

player = new Player(name, startingMoney);
}

public Player getPlayer() {
Expand All @@ -31,4 +42,35 @@ public Player getPlayer() {
public Exchange getExchange() {
return exchange;
}

public List<Stock> getStocks() {
return exchange.getStocks().values().stream()
.sorted(Comparator.comparing(Stock::getSymbol))
.collect(Collectors.toList());
}

/**
* Gives alphabetic sort of findStocks
*
* @param searchTerm
* @return
*/
public List<Stock> searchStocks(String searchTerm) {
if (searchTerm == null || searchTerm.isBlank()) {
return getStocks();
}
return exchange.findStocks(searchTerm).stream()
.sorted(Comparator.comparing(Stock::getSymbol))
.collect(Collectors.toList());
}

/**
* Get stocks with symbol
*
* @param symbol
* @return
*/
public Stock getStock(String symbol) {
return exchange.getStock(symbol);
}
}
8 changes: 8 additions & 0 deletions src/main/java/millions/model/Exchange.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ public Transaction sell(Share share, Player player) {
return sale;
}

public String getName() {
return this.name;
}

public int getWeekNumber() {
return this.weekNumber;
}

public Map<String, Stock> getStocks() {
return this.stocks;
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/millions/model/TransactionArchive.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public boolean isEmpty() {
return transactions.isEmpty();
}

public List<Transaction> getTransactions() {
return new ArrayList<>(transactions);
}

public List<Transaction> getTransactions(int week) {
return transactions.stream().filter(x -> x.getWeek() == week).collect(Collectors.toList());
}
Expand Down
203 changes: 203 additions & 0 deletions src/main/java/millions/view/GameView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package millions.view;

import java.math.BigDecimal;
import java.util.List;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import millions.controller.GameController;
import millions.model.Exchange;
import millions.model.ExchangeListener;
import millions.model.Player;
import millions.model.PlayerListener;
import millions.model.Stock;
import millions.model.Transaction;

/** Main game screen with tabs */
public class GameView extends BorderPane implements PlayerListener, ExchangeListener {

private final GameController controller;
private final Label playerNameLabel = new Label();
private final Label weekLabel = new Label();
private final Label moneyLabel = new Label();
private final Label netWorthLabel = new Label();
private final Label statusLabel = new Label();

private final TextField searchField = new TextField();
private final ListView<Stock> stocksList = new ListView<>();
private final Label selectedStockLabel = new Label("Select a stock to see chart");
private final NumberAxis xAxis = new NumberAxis();
private final NumberAxis yAxis = new NumberAxis();
private final LineChart<Number, Number> stockChart = new LineChart<>(xAxis, yAxis);

public GameView(GameController controller) {
this.controller = controller;
setTop(createHeader());
setCenter(createTabs());
configureStocksList();
refreshAll();
}

private HBox createHeader() {
Label title = new Label("Millions");
title.setStyle("-fx-font-size: 32px; -fx-font-weight: bold;");

HBox header =
new HBox(20, title, playerNameLabel, weekLabel, moneyLabel, netWorthLabel, statusLabel);
return header;
}

private TabPane createTabs() {
TabPane tabPane = new TabPane();
tabPane.getTabs().add(createStocksTab());
tabPane.getTabs().add(createPortfolioTab());
tabPane.getTabs().add(createTransactionsTab());
return tabPane;
}

private Tab createStocksTab() {
VBox leftPane = new VBox(10, new Label("Search"), searchField, stocksList);

searchField.setPromptText("Search");
searchField.textProperty().addListener((obs, oldVal, newVal) -> refreshStocks());

xAxis.setLabel("Week");
xAxis.setAutoRanging(false);
xAxis.setLowerBound(1); // Stop week 0
xAxis.setTickUnit(1);
yAxis.setLabel("Price");
stockChart.setTitle("Price history");
stockChart.setLegendVisible(false);
stockChart.setCreateSymbols(true);
stockChart.setAnimated(false);
stockChart.setPrefHeight(500);

VBox rightPane = new VBox(10, selectedStockLabel, stockChart);

HBox content = new HBox(12, leftPane, rightPane);
return new Tab("Stocks", content);
}

private Tab createPortfolioTab() {
VBox content = new VBox();
return new Tab("Portfolio", content);
}

private Tab createTransactionsTab() {
VBox content = new VBox();
return new Tab("Transactions", content);
}

private void configureStocksList() {
stocksList.setCellFactory(
listView ->
new ListCell<>() {
@Override
protected void updateItem(Stock stock, boolean empty) {
super.updateItem(stock, empty);
if (empty || stock == null) {
setText(null);
} else {
setText(formatStock(stock));
}
}
});

stocksList
.getSelectionModel()
.selectedItemProperty()
.addListener((obs, oldStock, newStock) -> showStockChart(newStock));
}

private void refreshAll() {
refreshPlayerInfo();
refreshStocks();
}

private void refreshPlayerInfo() {
Player player = controller.getPlayer();
Exchange exchange = controller.getExchange();

if (player == null || exchange == null) {
return;
}

playerNameLabel.setText("Player: " + player.getName());
weekLabel.setText("Week: " + exchange.getWeekNumber());
moneyLabel.setText("Money: " + player.getMoney());
netWorthLabel.setText("Net worth: " + player.getNetWorth());
statusLabel.setText("Status: " + player.getStatus());
}

private void refreshStocks() {
List<Stock> items = controller.searchStocks(searchField.getText());
stocksList.getItems().setAll(items);
}

private void showStockChart(Stock stock) {
stockChart.getData().clear();

if (stock == null) {
selectedStockLabel.setText("Select a stock to see chart");
return;
}

selectedStockLabel.setText(
stock.getSymbol()
+ " - "
+ stock.getCompany()
+ " | Current: "
+ stock.getSalesPrice()
+ " | High: "
+ stock.getHighestPrice()
+ " | Low: "
+ stock.getLowestPrice());

XYChart.Series<Number, Number> series = new XYChart.Series<>();
List<BigDecimal> prices = stock.getHistoricalPrices();
xAxis.setUpperBound(Math.max(2, prices.size()));
for (int i = 0; i < prices.size(); i++) {
series.getData().add(new XYChart.Data<>(i + 1, prices.get(i)));
}
stockChart.getData().add(series);
}

private String formatStock(Stock stock) {
return stock.getSymbol() + " - " + stock.getCompany() + " (" + stock.getSalesPrice() + ")";
}

// Listener callbacks update the shared header and the stocks tab.
@Override
public void onMoneyChanged(BigDecimal newBalance) {
refreshPlayerInfo();
}

@Override
public void onPortfolioChanged() {
refreshPlayerInfo();
}

@Override
public void onStatusChanged(String newStatus) {
refreshPlayerInfo();
}

@Override
public void onWeekAdvanced(int newWeek) {
refreshAll();
}

@Override
public void onTransactionCompleted(Transaction transaction) {
refreshPlayerInfo();
}
}
Loading

0 comments on commit cfbc5bc

Please sign in to comment.