diff --git a/src/main/java/millions/App.java b/src/main/java/millions/App.java index 825265b..107ec61 100644 --- a/src/main/java/millions/App.java +++ b/src/main/java/millions/App.java @@ -1,11 +1,14 @@ 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. */ public class App extends Application { @Override @@ -13,6 +16,29 @@ 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); diff --git a/src/main/java/millions/calculators/TransactionCalculatorFactory.java b/src/main/java/millions/calculators/TransactionCalculatorFactory.java index 8d461bd..36a71c0 100644 --- a/src/main/java/millions/calculators/TransactionCalculatorFactory.java +++ b/src/main/java/millions/calculators/TransactionCalculatorFactory.java @@ -5,6 +5,9 @@ import millions.model.calculators.SaleCalculator; import millions.model.calculators.TransactionCalculator; +/** + * Factory for creating transaction calculators. + */ public class TransactionCalculatorFactory { private TransactionCalculatorFactory() {} diff --git a/src/main/java/millions/controller/GameController.java b/src/main/java/millions/controller/GameController.java index 075230c..5638338 100644 --- a/src/main/java/millions/controller/GameController.java +++ b/src/main/java/millions/controller/GameController.java @@ -2,25 +2,37 @@ 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; import millions.model.Player; import millions.model.Stock; +/** Controls game initialization. */ 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 lines = reader.readFile(); CSVStockFileParser parser = new CSVStockFileParser(lines); List 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() { @@ -30,4 +42,35 @@ public Player getPlayer() { public Exchange getExchange() { return exchange; } + + public List getStocks() { + return exchange.getStocks().values().stream() + .sorted(Comparator.comparing(Stock::getSymbol)) + .collect(Collectors.toList()); + } + + /** + * Gives alphabetic sort of findStocks + * + * @param searchTerm + * @return + */ + public List 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); + } } diff --git a/src/main/java/millions/controller/fileIO/CSVStockFileParser.java b/src/main/java/millions/controller/fileIO/CSVStockFileParser.java index 5a0cee0..e52ebf9 100644 --- a/src/main/java/millions/controller/fileIO/CSVStockFileParser.java +++ b/src/main/java/millions/controller/fileIO/CSVStockFileParser.java @@ -1,19 +1,18 @@ package millions.controller.fileIO; -import millions.model.Stock; - import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import millions.model.Stock; +/** Parses CSV lines into Stock objects. */ public class CSVStockFileParser { private List lines; public CSVStockFileParser(List lines) { if (verifyCSV(lines)) { this.lines = lines; - } - else { + } else { // throw file format error } } @@ -21,22 +20,22 @@ public CSVStockFileParser(List lines) { // returns true if all entries have exactly 3 data points public boolean verifyCSV(List lines) { return lines.stream() - .filter(l -> !(l.startsWith("#") || l.isBlank())) - .noneMatch(l -> l.split(",").length != 3); - + .filter(l -> !(l.startsWith("#") || l.isBlank())) + .noneMatch(l -> l.split(",").length != 3); } public List parse() { List stocks = new ArrayList<>(); lines.stream() .filter(l -> !((l.startsWith("#") || l.isBlank()))) - .forEach(l -> { - String[] split = l.split(","); - String symbol = split[0]; - String company = split[1]; - BigDecimal price = new BigDecimal(split[2]); - stocks.add(new Stock(symbol, company, price)); - }); + .forEach( + l -> { + String[] split = l.split(","); + String symbol = split[0]; + String company = split[1]; + BigDecimal price = new BigDecimal(split[2]); + stocks.add(new Stock(symbol, company, price)); + }); return stocks; } } diff --git a/src/main/java/millions/controller/fileIO/CSVStockFileWriter.java b/src/main/java/millions/controller/fileIO/CSVStockFileWriter.java index 463ea04..52fee6e 100644 --- a/src/main/java/millions/controller/fileIO/CSVStockFileWriter.java +++ b/src/main/java/millions/controller/fileIO/CSVStockFileWriter.java @@ -8,6 +8,9 @@ import java.util.List; //TODO: Validation of data before writing +/** + * Writes stock data to a CSV file. + */ public class CSVStockFileWriter implements StockFileWriter { private final List stocks; private String finalString; diff --git a/src/main/java/millions/controller/fileIO/StockFileReader.java b/src/main/java/millions/controller/fileIO/StockFileReader.java index 47a0809..31e37ee 100644 --- a/src/main/java/millions/controller/fileIO/StockFileReader.java +++ b/src/main/java/millions/controller/fileIO/StockFileReader.java @@ -10,6 +10,9 @@ import java.util.List; +/** + * Reads a file and returns its lines as a list of strings. + */ public class StockFileReader { private final Path filePath; diff --git a/src/main/java/millions/controller/fileIO/StockFileWriter.java b/src/main/java/millions/controller/fileIO/StockFileWriter.java index 32a8e13..cfd1baf 100644 --- a/src/main/java/millions/controller/fileIO/StockFileWriter.java +++ b/src/main/java/millions/controller/fileIO/StockFileWriter.java @@ -2,6 +2,9 @@ import java.nio.file.Path; +/** + * Interface for writing stock data to a file. + */ public interface StockFileWriter { public void formatString(); public boolean write(Path path); diff --git a/src/main/java/millions/model/Exchange.java b/src/main/java/millions/model/Exchange.java index 57d91fd..ef59694 100644 --- a/src/main/java/millions/model/Exchange.java +++ b/src/main/java/millions/model/Exchange.java @@ -14,6 +14,9 @@ import millions.model.factories.SaleFactory; import millions.model.factories.TransactionFactory; +/** + * The stock exchange where players buy and sell shares. Manages stocks and simulates weekly price changes. + */ public class Exchange { private String name; private Map stocks; @@ -66,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 getStocks() { return this.stocks; } diff --git a/src/main/java/millions/model/ExchangeListener.java b/src/main/java/millions/model/ExchangeListener.java index b75057e..6731d45 100644 --- a/src/main/java/millions/model/ExchangeListener.java +++ b/src/main/java/millions/model/ExchangeListener.java @@ -1,5 +1,8 @@ package millions.model; +/** + * Listener for exchange events such as week advances and completed transactions. + */ public interface ExchangeListener { void onWeekAdvanced(int newWeek); diff --git a/src/main/java/millions/model/Player.java b/src/main/java/millions/model/Player.java index 09f43d4..2dfd684 100644 --- a/src/main/java/millions/model/Player.java +++ b/src/main/java/millions/model/Player.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; +/** Player class. */ public class Player { private String name; private BigDecimal startingMoney; @@ -15,6 +16,11 @@ public class Player { public int weeksTraded; private final List listeners = new ArrayList<>(); + /** + * @param name Name of player + * @param startingMoney Amount of money the player starts with + * @throws IllegalArgumentException + */ public Player(String name, BigDecimal startingMoney) { this.name = name; this.startingMoney = startingMoney; @@ -31,6 +37,10 @@ public Player(String name, BigDecimal startingMoney) { } } + /** + * @param amount How much money to add + * @throws IllegalArgumentException + */ public void addMoney(BigDecimal amount) { if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("Amount cannot be null or negative"); @@ -39,6 +49,10 @@ public void addMoney(BigDecimal amount) { notifyMoneyChanged(); } + /** + * @param amount How much money to withdeaw + * @throws IllegalArgumentException + */ public void withdrawMoney(BigDecimal amount) { if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("Amount cannot be null or negative"); @@ -47,6 +61,9 @@ public void withdrawMoney(BigDecimal amount) { notifyMoneyChanged(); } + /** + * @return + */ public String getStatus() { // TODO dobbel sjekk logikken int weeksTraded = transactionArchive.countDistinctWeeks(); @@ -63,42 +80,69 @@ public String getStatus() { return status; } + /** + * @return + */ public String getName() { return this.name; } + /** + * @return + */ public BigDecimal getMoney() { return this.money; } + /** + * @return + */ public Portfolio getPortfolio() { return this.portfolio; } + /** + * @param share Share to be added + */ public void addShareToPortfolio(Share share) { this.portfolio.addShare(share); notifyPortfolioChanged(); notifyStatusChanged(); } + /** + * @param share Share to be removed + */ public void removeShareFromPortfolio(Share share) { this.portfolio.removeShare(share); notifyPortfolioChanged(); notifyStatusChanged(); } + /** + * @return + */ public BigDecimal getNetWorth() { return this.money.add(this.portfolio.getNetWorth()); } + /** + * @return + */ public TransactionArchive getTransactionArchive() { return this.transactionArchive; } + /** + * @param listener + */ public void addListener(PlayerListener listener) { listeners.add(listener); } + /** + * @param listener + */ public void removeListener(PlayerListener listener) { listeners.remove(listener); } diff --git a/src/main/java/millions/model/PlayerListener.java b/src/main/java/millions/model/PlayerListener.java index f576ac7..e397ead 100644 --- a/src/main/java/millions/model/PlayerListener.java +++ b/src/main/java/millions/model/PlayerListener.java @@ -2,6 +2,7 @@ import java.math.BigDecimal; +/** Listener for player state changes. */ public interface PlayerListener { void onMoneyChanged(BigDecimal newBalance); diff --git a/src/main/java/millions/model/Portfolio.java b/src/main/java/millions/model/Portfolio.java index 0c4af05..210f784 100644 --- a/src/main/java/millions/model/Portfolio.java +++ b/src/main/java/millions/model/Portfolio.java @@ -5,6 +5,7 @@ import java.util.List; import millions.model.calculators.SaleCalculator; +/** A collection of shares owned by a player. */ public class Portfolio { List shares; @@ -12,24 +13,42 @@ public Portfolio() { shares = new ArrayList<>(); } + /** + * @param share Share to be added + * @return + */ public boolean addShare(Share share) { return this.shares.add(share); } + /** + * @param share Share to be removed + * @return + */ public boolean removeShare(Share share) { return this.shares.remove(share); } + /** + * @return + */ public List getShares() { return this.shares; } + /** + * @param symbol + * @return + */ public List getShares(String symbol) { return this.shares.stream() .filter(share -> share.getStock().getSymbol().equals(symbol)) .toList(); } + /** + * @return + */ public BigDecimal getNetWorth() { BigDecimal total = BigDecimal.ZERO; for (Share share : shares) { @@ -39,6 +58,10 @@ public BigDecimal getNetWorth() { return total; } + /** + * @param share + * @return + */ public boolean contains(Share share) { return this.shares.contains(share); } diff --git a/src/main/java/millions/model/Purchase.java b/src/main/java/millions/model/Purchase.java index 59da2c0..8bbacb8 100644 --- a/src/main/java/millions/model/Purchase.java +++ b/src/main/java/millions/model/Purchase.java @@ -2,6 +2,9 @@ import millions.model.calculators.PurchaseCalculator; +/** + * A transaction representing the purchase of shares. + */ public class Purchase extends Transaction { public Purchase(Share share, int week) { diff --git a/src/main/java/millions/model/Sale.java b/src/main/java/millions/model/Sale.java index dbd3637..1c17745 100644 --- a/src/main/java/millions/model/Sale.java +++ b/src/main/java/millions/model/Sale.java @@ -2,6 +2,9 @@ import millions.model.calculators.SaleCalculator; +/** + * A transaction representing the sale of shares. + */ public class Sale extends Transaction { public Sale(Share share, int week) { diff --git a/src/main/java/millions/model/Share.java b/src/main/java/millions/model/Share.java index 615dc68..7967b95 100644 --- a/src/main/java/millions/model/Share.java +++ b/src/main/java/millions/model/Share.java @@ -2,11 +2,18 @@ import java.math.BigDecimal; +/** Represents a holding of a specific stock with a quantity and purchase price. */ public class Share { Stock stock; BigDecimal quantity; BigDecimal purchasePrice; + /** + * @param stock Which stock the share is for. + * @param quantity How many stocks + * @param purchasePrice Purchase price of the share + * @throws IllegalArgumentException + */ public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) { this.stock = stock; this.quantity = quantity; @@ -23,18 +30,28 @@ public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) { } } + /** Share() with int quantity */ public Share(Stock stock, int quantity, BigDecimal purchasePrice) { this(stock, BigDecimal.valueOf(quantity), purchasePrice); } + /** + * @return + */ public Stock getStock() { return this.stock; } + /** + * @return + */ public BigDecimal getQuantity() { return this.quantity; } + /** + * @return + */ public BigDecimal getPurchasePrice() { return this.purchasePrice; } diff --git a/src/main/java/millions/model/Stock.java b/src/main/java/millions/model/Stock.java index 65b70da..cc1a23f 100644 --- a/src/main/java/millions/model/Stock.java +++ b/src/main/java/millions/model/Stock.java @@ -4,11 +4,18 @@ import java.util.ArrayList; import java.util.List; +/** Stock */ public class Stock { String symbol; String company; List prices; + /** + * @param symbol Stock ticker symbol + * @param company company name + * @param prices List of prices + * @throws IllegalArgumentException + */ public Stock(String symbol, String company, List prices) { this.symbol = symbol; this.company = company; @@ -23,30 +30,49 @@ public Stock(String symbol, String company, List prices) { } } + /** Stock() with single price instead of list */ public Stock(String symbol, String company, BigDecimal initialPrice) { this(symbol, company, new ArrayList<>(List.of(initialPrice))); } + /** + * @return + */ public String getSymbol() { return this.symbol; } + /** + * @return + */ public String getCompany() { return this.company; } + /** + * @return + */ public BigDecimal getSalesPrice() { return this.prices.getLast(); } + /** + * @param price Sales price + */ public void addNewSalesPrice(BigDecimal price) { this.prices.add(price); } + /** + * @return + */ public List getHistoricalPrices() { return this.prices; } + /** + * @return + */ public BigDecimal getHighestPrice() { BigDecimal highestPrice = this.prices.get(0); for (BigDecimal price : this.prices) { @@ -57,6 +83,9 @@ public BigDecimal getHighestPrice() { return highestPrice; } + /** + * @return + */ public BigDecimal getLowestPrice() { BigDecimal lowestPrice = this.prices.get(0); for (BigDecimal price : this.prices) { @@ -67,6 +96,9 @@ public BigDecimal getLowestPrice() { return lowestPrice; } + /** + * @return + */ public BigDecimal getLatestPriceChange() { if (this.prices.size() < 2) { return BigDecimal.ZERO; diff --git a/src/main/java/millions/model/Transaction.java b/src/main/java/millions/model/Transaction.java index 8dfdd4f..6e1e8b1 100644 --- a/src/main/java/millions/model/Transaction.java +++ b/src/main/java/millions/model/Transaction.java @@ -2,6 +2,7 @@ import millions.model.calculators.TransactionCalculator; +/** Abstract base class for stock transactions */ public abstract class Transaction { private Share share; diff --git a/src/main/java/millions/model/TransactionArchive.java b/src/main/java/millions/model/TransactionArchive.java index 5e8b407..6910a6c 100644 --- a/src/main/java/millions/model/TransactionArchive.java +++ b/src/main/java/millions/model/TransactionArchive.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.stream.Collectors; +/** Stores and queries commited transactions. */ public class TransactionArchive { List transactions; @@ -24,6 +25,10 @@ public boolean isEmpty() { return transactions.isEmpty(); } + public List getTransactions() { + return new ArrayList<>(transactions); + } + public List getTransactions(int week) { return transactions.stream().filter(x -> x.getWeek() == week).collect(Collectors.toList()); } diff --git a/src/main/java/millions/model/calculators/PurchaseCalculator.java b/src/main/java/millions/model/calculators/PurchaseCalculator.java index 1f7b341..ab2608d 100644 --- a/src/main/java/millions/model/calculators/PurchaseCalculator.java +++ b/src/main/java/millions/model/calculators/PurchaseCalculator.java @@ -3,6 +3,7 @@ import java.math.BigDecimal; import millions.model.Share; +/** Calculates costs for a purchase transaction. Commission */ public class PurchaseCalculator implements TransactionCalculator { BigDecimal purchasePrice; BigDecimal quantity; diff --git a/src/main/java/millions/model/calculators/SaleCalculator.java b/src/main/java/millions/model/calculators/SaleCalculator.java index 9b0a84e..a907b46 100644 --- a/src/main/java/millions/model/calculators/SaleCalculator.java +++ b/src/main/java/millions/model/calculators/SaleCalculator.java @@ -4,6 +4,7 @@ import java.math.RoundingMode; import millions.model.Share; +/** Calculates costs for a sale transaction. Commission and profit tax */ public class SaleCalculator implements TransactionCalculator { BigDecimal purchasePrice; BigDecimal salesPrice; diff --git a/src/main/java/millions/model/calculators/TransactionCalculator.java b/src/main/java/millions/model/calculators/TransactionCalculator.java index d2d3917..9e6ecfb 100644 --- a/src/main/java/millions/model/calculators/TransactionCalculator.java +++ b/src/main/java/millions/model/calculators/TransactionCalculator.java @@ -2,6 +2,9 @@ import java.math.BigDecimal; +/** + * Interface for calculating transaction costs including gross, commission, tax, and total. + */ public interface TransactionCalculator { public BigDecimal calculateGross(); diff --git a/src/main/java/millions/model/factories/PurchaseFactory.java b/src/main/java/millions/model/factories/PurchaseFactory.java index e2b52f7..0af39aa 100644 --- a/src/main/java/millions/model/factories/PurchaseFactory.java +++ b/src/main/java/millions/model/factories/PurchaseFactory.java @@ -3,6 +3,9 @@ import millions.model.Purchase; import millions.model.Share; +/** + * Factory for creating purchase transactions. + */ public class PurchaseFactory extends TransactionFactory { public Purchase createTransaction(Share share, int week) { return new Purchase(share, week); diff --git a/src/main/java/millions/model/factories/SaleFactory.java b/src/main/java/millions/model/factories/SaleFactory.java index 4da40ee..6753a5e 100644 --- a/src/main/java/millions/model/factories/SaleFactory.java +++ b/src/main/java/millions/model/factories/SaleFactory.java @@ -3,6 +3,9 @@ import millions.model.Sale; import millions.model.Share; +/** + * Factory for creating sale transactions. + */ public class SaleFactory extends TransactionFactory { public Sale createTransaction(Share share, int week) { return new Sale(share, week); diff --git a/src/main/java/millions/model/factories/TransactionFactory.java b/src/main/java/millions/model/factories/TransactionFactory.java index 8c2eb5c..e77d31d 100644 --- a/src/main/java/millions/model/factories/TransactionFactory.java +++ b/src/main/java/millions/model/factories/TransactionFactory.java @@ -3,6 +3,9 @@ import millions.model.Share; import millions.model.Transaction; +/** + * Abstract factory for creating transactions. + */ public abstract class TransactionFactory { public abstract Transaction createTransaction(Share share, int week); } diff --git a/src/main/java/millions/view/GameView.java b/src/main/java/millions/view/GameView.java new file mode 100644 index 0000000..526c4f3 --- /dev/null +++ b/src/main/java/millions/view/GameView.java @@ -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 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 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 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 series = new XYChart.Series<>(); + List 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(); + } +} diff --git a/src/main/java/millions/view/StartView.java b/src/main/java/millions/view/StartView.java index 564883b..ffccb6f 100644 --- a/src/main/java/millions/view/StartView.java +++ b/src/main/java/millions/view/StartView.java @@ -1,6 +1,7 @@ package millions.view; import java.io.File; +import java.math.BigDecimal; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; @@ -10,10 +11,12 @@ import javafx.stage.FileChooser; import javafx.stage.Stage; +/** The initial game setup screen where the player enters their info. */ public class StartView extends VBox { private TextField nameField; private TextField startingAmountField; + private TextField preRunWeeksField; private File selectedFile; private Button filepickerButton; private Button startButton; @@ -23,17 +26,22 @@ public StartView(Stage stage) { setSpacing(12); setPadding(new Insets(40)); - nameField = new TextField(); + nameField = new TextField("user"); nameField.setPromptText("Player name:"); nameField.setMaxWidth(250); nameField.textProperty().addListener((obs, oldVal, newVal) -> checkStartButtonValid()); - - startingAmountField = new TextField(); + // Default to 50000 + startingAmountField = new TextField("50000"); startingAmountField.setPromptText("Starting amount:"); startingAmountField.setMaxWidth(250); startingAmountField .textProperty() .addListener((obs, oldVal, newVal) -> checkStartButtonValid()); + // Pre run weeks to run simulated weeks before the player starts + preRunWeeksField = new TextField("12"); + preRunWeeksField.setPromptText("Pre run weeks:"); + preRunWeeksField.setMaxWidth(250); + preRunWeeksField.textProperty().addListener((obs, oldVal, newVal) -> checkStartButtonValid()); filepickerButton = new Button(); filepickerButton.setText("Pick file"); @@ -48,6 +56,7 @@ public StartView(Stage stage) { if (file != null) { selectedFile = file; filepickerButton.setText(file.getName()); + checkStartButtonValid(); } }); @@ -57,9 +66,12 @@ public StartView(Stage stage) { Label title = new Label("Millions"); title.setStyle("-fx-font-size: 32px; -fx-font-weight: bold;"); - getChildren().addAll(title, nameField, startingAmountField, filepickerButton, startButton); + getChildren() + .addAll( + title, nameField, startingAmountField, preRunWeeksField, filepickerButton, startButton); } + /** Enables/Disables start button */ private void checkStartButtonValid() { boolean valid = true; @@ -67,8 +79,20 @@ private void checkStartButtonValid() { valid = false; } + if (selectedFile == null) { + valid = false; + } + try { - Integer.valueOf(startingAmountField.getText()); + new BigDecimal(startingAmountField.getText()); + } catch (NumberFormatException e) { + valid = false; + } + + try { + if (Integer.parseInt(preRunWeeksField.getText()) < 0) { + valid = false; + } } catch (NumberFormatException e) { valid = false; } @@ -84,6 +108,10 @@ public String getStartingAmount() { return startingAmountField.getText(); } + public int getPreRunWeeks() { + return Integer.parseInt(preRunWeeksField.getText()); + } + public File getSelectedFile() { return selectedFile; }