From 64040adfedeee3c983f8b87878a5013618573137 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Thu, 23 Apr 2026 23:10:43 +0200 Subject: [PATCH 1/8] Added GameSetupScene.java class UI --- src/main/java/View/GameSetupScene.java | 165 +++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 src/main/java/View/GameSetupScene.java diff --git a/src/main/java/View/GameSetupScene.java b/src/main/java/View/GameSetupScene.java new file mode 100644 index 0000000..f6c8664 --- /dev/null +++ b/src/main/java/View/GameSetupScene.java @@ -0,0 +1,165 @@ +package View; + +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.layout.VBox; +import javafx.scene.layout.HBox; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import Controller.StockFileHandler; +import Model.Exchange; +import Model.Stock; + +import java.io.File; +import java.math.BigDecimal; +import java.util.List; +import java.util.function.Consumer; + +public class GameSetupScene { + private Scene scene; + private Consumer onGameStart; + private File selectedFile; + private Label fileLabel; + + public GameSetupScene(Consumer onGameStart) { + this.onGameStart = onGameStart; + this.scene = createScene(); + } + + private Scene createScene() { + VBox root = new VBox(15); + root.setPadding(new Insets(20)); + root.setStyle("-fx-font-family: Arial; -fx-font-size: 12;"); + + // title + Label titleLabel = new Label("Stock Trading Game"); + titleLabel.setStyle("-fx-font-size: 24; -fx-font-weight: bold;"); + root.getChildren().add(titleLabel); + + // player name + HBox nameBox = new HBox(10); + Label nameLabel = new Label("Player Name:"); + nameLabel.setMinWidth(120); + TextField nameField = new TextField(); + nameField.setPromptText("Enter your name"); + nameBox.getChildren().addAll(nameLabel, nameField); + + // starting capital + HBox capitalBox = new HBox(10); + Label capitalLabel = new Label("Starting Capital:"); + capitalLabel.setMinWidth(120); + TextField capitalField = new TextField(); + capitalField.setPromptText("Enter amount (e.g., 10000)"); + capitalBox.getChildren().addAll(capitalLabel, capitalField); + + // file selection + HBox fileBox = new HBox(10); + Label fileSelectLabel = new Label("Stock Data File:"); + fileSelectLabel.setMinWidth(120); + fileLabel = new Label("No file selected"); + fileLabel.setStyle("-fx-text-fill: #666666;"); + Button browseButton = new Button("Browse"); + browseButton.setOnAction(e -> selectFile()); + fileBox.getChildren().addAll(fileSelectLabel, fileLabel, browseButton); + + // button box + HBox buttonBox = new HBox(10); + buttonBox.setPadding(new Insets(20, 0, 0, 0)); + + Button startButton = new Button("Start Game"); + startButton.setStyle("-fx-font-size: 14; -fx-padding: 10 40 10 40;"); + startButton.setOnAction(e -> { + String name = nameField.getText().trim(); + String capitalStr = capitalField.getText().trim(); + + if (name.isEmpty()) { + showAlert("Validation Error", "Please enter a player name"); + return; + } + + if (capitalStr.isEmpty()) { + showAlert("Validation Error", "Please enter starting capital"); + return; + } + + if (selectedFile == null) { + showAlert("Validation Error", "Please select a stock data file"); + return; + } + + try { + BigDecimal capital = new BigDecimal(capitalStr); + if (capital.compareTo(BigDecimal.ZERO) <= 0) { + showAlert("Validation Error", "Starting capital must be greater than 0"); + return; + } + + StockFileHandler fileHandler = new StockFileHandler(); + List stocks = fileHandler.loadStocksFromFile(selectedFile.getAbsolutePath()); + + if (stocks.isEmpty()) { + showAlert("Error", "No stocks found in the selected file"); + return; + } + + Exchange exchange = new Exchange("Main Exchange", stocks); + onGameStart.accept(new StartGameData(name, capital, exchange)); + } catch (NumberFormatException ex) { + showAlert("Validation Error", "Starting capital must be a valid number"); + } catch (Exception ex) { + showAlert("Error", "Failed to load file: " + ex.getMessage()); + } + }); + + Button exitButton = new Button("Exit"); + exitButton.setOnAction(e -> System.exit(0)); + buttonBox.getChildren().addAll(startButton, exitButton); + + root.getChildren().addAll(nameBox, capitalBox, fileBox, buttonBox); + + ScrollPane scrollPane = new ScrollPane(root); + scrollPane.setFitToWidth(true); + return new Scene(scrollPane, 600, 400); + } + + private void selectFile() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Select Stock Data File"); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("CSV Files", "*.csv"), + new FileChooser.ExtensionFilter("All Files", "*.*") + ); + + File file = fileChooser.showOpenDialog(new Stage()); + if (file != null) { + selectedFile = file; + fileLabel.setText(file.getName()); + fileLabel.setStyle("-fx-text-fill: #000000;"); + } + } + + private void showAlert(String title, String message) { + Alert alert = new Alert(Alert.AlertType.WARNING); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } + + public Scene getScene() { + return scene; + } + + public static class StartGameData { + public final String playerName; + public final BigDecimal startingCapital; + public final Exchange exchange; + + public StartGameData(String playerName, BigDecimal startingCapital, Exchange exchange) { + this.playerName = playerName; + this.startingCapital = startingCapital; + this.exchange = exchange; + } + } +} From 9c8e41ea88cb7f888998b037847674d0d8487a5c Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Thu, 23 Apr 2026 23:10:52 +0200 Subject: [PATCH 2/8] Added Launcher.java class --- src/main/java/View/Launcher.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/View/Launcher.java diff --git a/src/main/java/View/Launcher.java b/src/main/java/View/Launcher.java new file mode 100644 index 0000000..0dd5db2 --- /dev/null +++ b/src/main/java/View/Launcher.java @@ -0,0 +1,10 @@ +package View; + +/** + * Launcher class for the Stock Trading Game + */ +public class Launcher { + public static void main(String[] args) { + StockTradingGameApp.main(args); + } +} From 6d01998f6d97718b81f787d1b47cf34667c4731d Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Thu, 23 Apr 2026 23:11:07 +0200 Subject: [PATCH 3/8] Added MainClassScene.java class --- src/main/java/View/MainGameScene.java | 449 ++++++++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 src/main/java/View/MainGameScene.java diff --git a/src/main/java/View/MainGameScene.java b/src/main/java/View/MainGameScene.java new file mode 100644 index 0000000..5947dd2 --- /dev/null +++ b/src/main/java/View/MainGameScene.java @@ -0,0 +1,449 @@ +package View; + +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.layout.*; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import Model.*; + +import java.math.BigDecimal; +import java.util.List; + +public class MainGameScene { + private Scene scene; + private Exchange exchange; + private Player player; + private Runnable onExit; + private Label statusLabel; + + public MainGameScene(Exchange exchange, Player player, Runnable onExit) { + this.exchange = exchange; + this.player = player; + this.onExit = onExit; + this.scene = createScene(); + } + + private Scene createScene() { + VBox root = new VBox(5); + root.setPadding(new Insets(10)); + + // TOP STATUS BAR + HBox topBar = new HBox(15); + topBar.setPadding(new Insets(10)); + topBar.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #999;"); + + statusLabel = new Label(); + updateStatus(); + + Button nextWeekBtn = new Button("Next week"); + nextWeekBtn.setStyle("-fx-font-size: 12; -fx-padding: 10 20; -fx-font-weight: bold;"); + nextWeekBtn.setOnAction(e -> advance()); + + Button exitBtn = new Button("Exit"); + exitBtn.setOnAction(e -> { + if (confirm("Exit Game?", "Final Net Worth: $" + formatMoney(getNetWorth()))) { + onExit.run(); + } + }); + + topBar.getChildren().addAll(statusLabel, new Separator(javafx.geometry.Orientation.VERTICAL), + nextWeekBtn, exitBtn); + root.getChildren().add(topBar); + + // MAIN CONTENT TABS + TabPane tabs = new TabPane(); + tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); + + tabs.getTabs().addAll( + new Tab("Stocks", createStocksPanel()), + new Tab("Portfolio", createPortfolioPanel()), + new Tab("Trade", createTradePanel()), + new Tab("History", createHistoryPanel()) + ); + + VBox.setVgrow(tabs, Priority.ALWAYS); + root.getChildren().add(tabs); + + return new Scene(root, 1000, 700); + } + + private VBox createStocksPanel() { + VBox panel = new VBox(10); + panel.setPadding(new Insets(10)); + + HBox searchBox = new HBox(10); + TextField search = new TextField(); + search.setPromptText("Search symbol or company..."); + ComboBox filter = new ComboBox<>(); + filter.setItems(FXCollections.observableArrayList("All", "Gainers", "Losers")); + filter.setValue("All"); + + TableView table = new TableView<>(); + addStockColumns(table); + + Runnable loadStocks = () -> { + List stocks; + String filterVal = filter.getValue(); + String searchVal = search.getText().trim(); + + if ("Gainers".equals(filterVal)) { + stocks = exchange.getGainers(20); + } else if ("Losers".equals(filterVal)) { + stocks = exchange.getLosers(20); + } else { + stocks = exchange.findStocks(searchVal); + } + + ObservableList data = FXCollections.observableArrayList(); + for (Stock s : stocks) { + data.add(new StockRow(s)); + } + table.setItems(data); + }; + + Button searchBtn = new Button("Search"); + searchBtn.setOnAction(e -> loadStocks.run()); + filter.setOnAction(e -> loadStocks.run()); + + searchBox.getChildren().addAll(search, searchBtn, filter); + loadStocks.run(); + + VBox.setVgrow(table, Priority.ALWAYS); + panel.getChildren().addAll(searchBox, table); + return panel; + } + + private VBox createPortfolioPanel() { + VBox panel = new VBox(10); + panel.setPadding(new Insets(10)); + + TableView table = new TableView<>(); + addPortfolioColumns(table); + updatePortfolio(table); + + Button refresh = new Button("Refresh"); + refresh.setOnAction(e -> updatePortfolio(table)); + + VBox.setVgrow(table, Priority.ALWAYS); + panel.getChildren().addAll(new Label("Holdings:"), table, refresh); + return panel; + } + + private VBox createTradePanel() { + VBox panel = new VBox(10); + panel.setPadding(new Insets(10)); + + TabPane tradeTabs = new TabPane(); + tradeTabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); + tradeTabs.getTabs().addAll( + new Tab("Buy", createBuyTab()), + new Tab("Sell", createSellTab()) + ); + + VBox.setVgrow(tradeTabs, Priority.ALWAYS); + panel.getChildren().add(tradeTabs); + return panel; + } + + private VBox createBuyTab() { + VBox box = new VBox(10); + box.setPadding(new Insets(10)); + + TextField stockField = new TextField(); + stockField.setPromptText("Stock symbol (e.g., AAPL)"); + + TextField qtyField = new TextField(); + qtyField.setPromptText("Quantity"); + + Label infoLabel = new Label(); + + stockField.textProperty().addListener((obs, old, val) -> updateBuyInfo(val, qtyField, infoLabel)); + qtyField.textProperty().addListener((obs, old, val) -> updateBuyInfo(stockField.getText(), qtyField, infoLabel)); + + Button buyBtn = new Button("Buy"); + buyBtn.setOnAction(e -> { + try { + Stock s = exchange.getStock(stockField.getText().toUpperCase()); + if (s == null) { + alert("Error", "Stock not found"); + return; + } + BigDecimal qty = new BigDecimal(qtyField.getText()); + Transaction trans = exchange.buy(s.getSymbol(), qty, player); + if (trans != null && trans.isCommitted()) { + showConfirmation("Purchase successful", trans); + stockField.clear(); + qtyField.clear(); + infoLabel.setText(""); + updateStatus(); + } else { + alert("Failed", "Insufficient funds or error"); + } + } catch (Exception ex) { + alert("Error", ex.getMessage()); + } + }); + + box.getChildren().addAll( + new Label("Buy Stocks:"), + stockField, qtyField, infoLabel, buyBtn + ); + return box; + } + + private VBox createSellTab() { + VBox box = new VBox(10); + box.setPadding(new Insets(10)); + + ListView holdingsList = new ListView<>(); + updateHoldingsList(holdingsList); + + TextField qtyField = new TextField(); + qtyField.setPromptText("Quantity to sell"); + + Label infoLabel = new Label(); + + Button sellBtn = new Button("Sell"); + sellBtn.setOnAction(e -> { + String selected = holdingsList.getSelectionModel().getSelectedItem(); + if (selected == null) { + alert("Error", "Select a holding"); + return; + } + try { + String symbol = selected.split(" ")[0]; + List shares = player.getPortfolio().getShares(symbol); + if (shares.isEmpty()) return; + + Transaction trans = exchange.sell(shares.get(0), player); + if (trans != null && trans.isCommitted()) { + showConfirmation("Sale successful", trans); + qtyField.clear(); + updateHoldingsList(holdingsList); + updateStatus(); + } + } catch (Exception ex) { + alert("Error", ex.getMessage()); + } + }); + + holdingsList.setPrefHeight(200); + VBox.setVgrow(holdingsList, Priority.ALWAYS); + box.getChildren().addAll( + new Label("Your Holdings:"), + holdingsList, qtyField, infoLabel, sellBtn + ); + return box; + } + + private VBox createHistoryPanel() { + VBox panel = new VBox(10); + panel.setPadding(new Insets(10)); + + ComboBox weekFilter = new ComboBox<>(); + updateWeekCombo(weekFilter); + + TableView table = new TableView<>(); + addHistoryColumns(table); + updateHistory(table, null); + + weekFilter.setOnAction(e -> updateHistory(table, weekFilter.getValue() == 0 ? null : weekFilter.getValue())); + + VBox.setVgrow(table, Priority.ALWAYS); + panel.getChildren().addAll(new HBox(10, new Label("Week:"), weekFilter), table); + return panel; + } + + // HELPER METHODS + private void addStockColumns(TableView table) { + table.getColumns().addAll( + createCol("Symbol", 80, "symbol"), + createCol("Company", 150, "company"), + createCol("Price", 80, "price"), + createCol("Change", 80, "change"), + createCol("High", 80, "high"), + createCol("Low", 80, "low") + ); + } + + private void addPortfolioColumns(TableView table) { + table.getColumns().addAll( + createCol("Symbol", 80, "symbol"), + createCol("Qty", 60, "qty"), + createCol("Buy Price", 80, "buyPrice"), + createCol("Current Price", 100, "currentPrice"), + createCol("Gain/Loss", 80, "gainLoss") + ); + } + + private void addHistoryColumns(TableView table) { + table.getColumns().addAll( + createCol("Week", 60, "week"), + createCol("Type", 60, "type"), + createCol("Symbol", 80, "symbol"), + createCol("Qty", 60, "qty"), + createCol("Total", 100, "total") + ); + } + + private TableColumn createCol(String header, int width, String property) { + TableColumn col = new TableColumn<>(header); + col.setPrefWidth(width); + col.setCellValueFactory(cellData -> { + try { + return new javafx.beans.property.SimpleStringProperty( + cellData.getValue().getClass().getMethod("get" + capitalize(property)).invoke(cellData.getValue()).toString() + ); + } catch (Exception e) { + return new javafx.beans.property.SimpleStringProperty(""); + } + }); + return col; + } + + private String capitalize(String s) { + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + + private void updatePortfolio(TableView table) { + ObservableList data = FXCollections.observableArrayList(); + for (Share s : player.getPortfolio().getShares()) { + data.add(new PortfolioRow(s)); + } + table.setItems(data); + } + + private void updateHoldingsList(ListView list) { + ObservableList items = FXCollections.observableArrayList(); + for (Share s : player.getPortfolio().getShares()) { + items.add(s.getStock().getSymbol() + " - " + formatMoney(s.getQuantity()) + " @ $" + formatMoney(s.getStock().getSalesPrice())); + } + list.setItems(items); + } + + private void updateHistory(TableView table, Integer week) { + ObservableList data = FXCollections.observableArrayList(); + List trans = week == null ? player.getTransactionArchive().getAllTransactions() : + player.getTransactionArchive().getTransactions(week); + for (Transaction t : trans) { + data.add(new HistoryRow(t)); + } + table.setItems(data); + } + + private void updateWeekCombo(ComboBox combo) { + ObservableList weeks = FXCollections.observableArrayList(); + weeks.add(0); + int total = player.getTransactionArchive().countDistinctWeeks(); + for (int i = 1; i <= total; i++) weeks.add(i); + combo.setItems(weeks); + combo.setValue(0); + } + + private void updateBuyInfo(String symbol, TextField qtyField, Label label) { + try { + if (symbol.isEmpty()) return; + Stock s = exchange.getStock(symbol.toUpperCase()); + if (s == null) return; + BigDecimal qty = new BigDecimal(qtyField.getText()); + BigDecimal gross = s.getSalesPrice().multiply(qty); + BigDecimal comm = gross.multiply(new BigDecimal("0.005")); + label.setText("Gross: $" + formatMoney(gross) + " | Commission: $" + formatMoney(comm) + + " | Total: $" + formatMoney(gross.add(comm))); + } catch (Exception e) { + // ignore + } + } + + private void updateStatus() { + statusLabel.setText("Player: " + player.getName() + " | Week: " + exchange.getWeek() + + " | Cash: $" + formatMoney(player.getMoney()) + + " | Net Worth: $" + formatMoney(getNetWorth())); + } + + private void advance() { + exchange.advance(); + updateStatus(); + } + + private BigDecimal getNetWorth() { + return player.getMoney().add(player.getPortfolio().getNetWorth()); + } + + private void showConfirmation(String title, Transaction t) { + StringBuilder msg = new StringBuilder(); + if (t instanceof Purchase) { + Purchase p = (Purchase) t; + msg.append("Bought ").append(formatMoney(p.getShare().getQuantity())) + .append(" @ $").append(formatMoney(p.getShare().getPurchasePrice())) + .append("\nCost: $").append(formatMoney(p.getCalculator().calculateTotal())); + } else { + Sale s = (Sale) t; + msg.append("Sold ").append(formatMoney(s.getShare().getQuantity())) + .append(" @ $").append(formatMoney(s.getShare().getStock().getSalesPrice())) + .append("\nProceeds: $").append(formatMoney(s.getCalculator().calculateTotal())); + } + alert(title, msg.toString()); + } + + private void alert(String title, String msg) { + Alert a = new Alert(Alert.AlertType.INFORMATION); + a.setTitle(title); + a.setContentText(msg); + a.showAndWait(); + } + + private boolean confirm(String title, String msg) { + Alert a = new Alert(Alert.AlertType.CONFIRMATION); + a.setTitle(title); + a.setContentText(msg); + return a.showAndWait().get() == ButtonType.OK; + } + + private String formatMoney(BigDecimal n) { + return n.setScale(2, java.math.RoundingMode.HALF_UP).toString(); + } + + public Scene getScene() { + return scene; + } + + // DATA CLASSES + static class StockRow { + Stock s; + StockRow(Stock s) { this.s = s; } + public String getSymbol() { return s.getSymbol(); } + public String getCompany() { return s.getCompany(); } + public String getPrice() { return "$" + s.getSalesPrice(); } + public String getChange() { return "$" + s.getLatestPriceChange(); } + public String getHigh() { return "$" + s.getHighestPrice(); } + public String getLow() { return "$" + s.getLowestPrice(); } + } + + static class PortfolioRow { + Share s; + PortfolioRow(Share s) { this.s = s; } + public String getSymbol() { return s.getStock().getSymbol(); } + public String getQty() { return s.getQuantity().toString(); } + public String getBuyPrice() { return "$" + s.getPurchasePrice(); } + public String getCurrentPrice() { return "$" + s.getStock().getSalesPrice(); } + public String getGainLoss() { + SaleCalculator calc = new SaleCalculator(s); + BigDecimal val = calc.calculateTotal(); + BigDecimal cost = s.getPurchasePrice().multiply(s.getQuantity()); + return "$" + val.subtract(cost); + } + } + + static class HistoryRow { + Transaction t; + HistoryRow(Transaction t) { this.t = t; } + public String getWeek() { return String.valueOf(t.getWeek()); } + public String getType() { return t instanceof Purchase ? "BUY" : "SELL"; } + public String getSymbol() { return t.getShare().getStock().getSymbol(); } + public String getQty() { return t.getShare().getQuantity().toString(); } + public String getTotal() { return "$" + t.getCalculator().calculateTotal(); } + } +} From c567e2a90925af4b895c7b4daaa5327a93310e58 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Thu, 23 Apr 2026 23:11:59 +0200 Subject: [PATCH 4/8] Added StockTradingGameApp.java class --- src/main/java/View/StockTradingGameApp.java | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/View/StockTradingGameApp.java diff --git a/src/main/java/View/StockTradingGameApp.java b/src/main/java/View/StockTradingGameApp.java new file mode 100644 index 0000000..a2d6543 --- /dev/null +++ b/src/main/java/View/StockTradingGameApp.java @@ -0,0 +1,49 @@ +package View; + +import javafx.application.Application; +import javafx.stage.Stage; +import Controller.StockFileHandler; +import Model.*; + +import java.math.BigDecimal; + +public class StockTradingGameApp extends Application { + private Stage primaryStage; + private Exchange exchange; + private Player player; + private StockFileHandler fileHandler; + + @Override + public void start(Stage primaryStage) { + this.primaryStage = primaryStage; + this.fileHandler = new StockFileHandler(); + + primaryStage.setTitle("Stock Trading Game"); + primaryStage.setWidth(1200); + primaryStage.setHeight(800); + + showGameSetup(); + primaryStage.show(); + } + + private void showGameSetup() { + GameSetupScene setupScene = new GameSetupScene(this::startGame); + primaryStage.setScene(setupScene.getScene()); + } + + private void startGame(GameSetupScene.StartGameData gameData) { + this.exchange = gameData.exchange; + this.player = new Player(gameData.playerName, gameData.startingCapital); + + MainGameScene gameScene = new MainGameScene(exchange, player, this::endGame); + primaryStage.setScene(gameScene.getScene()); + } + + private void endGame() { + primaryStage.close(); + } + + public static void main(String[] args) { + launch(args); + } +} From 4a9644b80bca0f0f381de03dac20977ad8e0050c Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Thu, 23 Apr 2026 23:12:42 +0200 Subject: [PATCH 5/8] Updated TransactionArchive class --- src/main/java/Model/TransactionArchive.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/Model/TransactionArchive.java b/src/main/java/Model/TransactionArchive.java index 92ca3c2..149e2be 100644 --- a/src/main/java/Model/TransactionArchive.java +++ b/src/main/java/Model/TransactionArchive.java @@ -28,11 +28,15 @@ public List getTransactions(int week) { } public List getPurchase(int week) { - return transactions.stream().filter(transaction -> transaction.getWeek() == week).filter(purchase -> transactions instanceof Purchase).map(transaction -> (Purchase) transaction).collect(Collectors.toList()); + return transactions.stream().filter(transaction -> transaction.getWeek() == week).filter(purchase -> purchase instanceof Purchase).map(transaction -> (Purchase) transaction).collect(Collectors.toList()); } public List getSale(int week) { - return transactions.stream().filter(transaction -> transaction.getWeek() == week).filter(sale -> transactions instanceof Sale).map(transaction -> (Sale) transaction).collect(Collectors.toList()); + return transactions.stream().filter(transaction -> transaction.getWeek() == week).filter(sale -> sale instanceof Sale).map(transaction -> (Sale) transaction).collect(Collectors.toList()); + } + + public List getAllTransactions() { + return new ArrayList<>(transactions); } public int countDistinctWeeks() { From ac89aacc076d8f207f4356904f2a099900611719 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Thu, 23 Apr 2026 23:13:30 +0200 Subject: [PATCH 6/8] Updated pom.xml file Because of UI additions and new classes --- pom.xml | 2 +- .../compile/default-compile/createdFiles.lst | 22 +++++++++++++++++++ .../compile/default-compile/inputFiles.lst | 22 ++++++++++++++----- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 0a79553..a74b899 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ javafx-maven-plugin 0.0.8 - your.package.MainApp + View.Launcher diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index e69de29..9926576 100644 --- a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,22 @@ +Model\TransactionCalculator.class +Model\Portfolio.class +Model\Share.class +View\GameSetupScene.class +View\MainGameScene$TransactionInfo.class +Model\TransactionArchive.class +View\GameSetupScene$StartGameData.class +View\MainGameScene$StockInfo.class +View\Launcher.class +Model\Purchase.class +Model\Sale.class +Model\Exchange.class +Model\Transaction.class +View\MainGameScene$ShareInfo.class +Model\PurchaseCalculator.class +Model\SaleCalculator.class +Model\Stock.class +Model\Player.class +View\StockTradingGameApp.class +Controller\StockFileHandler.class +View\MainGameScene.class +View\MainGameScene$ShareListCell.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 69de3f4..fc20747 100644 --- a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,5 +1,17 @@ -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Exchange.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Portfolio.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Share.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Stock.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\TransactionCalculator.java +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\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 +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\PurchaseCalculator.java +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Sale.java +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\SaleCalculator.java +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Share.java +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Stock.java +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Transaction.java +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\TransactionArchive.java +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\TransactionCalculator.java +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\View\GameSetupScene.java +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\View\Launcher.java +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\View\MainGameScene.java +C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\View\StockTradingGameApp.java From ae07d7af1a33014ebd57c52bde2f8684226d97c5 Mon Sep 17 00:00:00 2001 From: Solveig Natvig Date: Sun, 24 May 2026 12:33:45 +0200 Subject: [PATCH 7/8] Add wireframe design and move styling to a separate CSS file for easier updates. --- src/main/Resources/Style/Global.css | 236 +++++++++++++++++ src/main/java/View/GameSetupScene.java | 214 +++++++++------ src/main/java/View/MainGameScene.java | 249 ++++++++++-------- .../compile/default-compile/createdFiles.lst | 22 -- .../compile/default-compile/inputFiles.lst | 34 +-- 5 files changed, 537 insertions(+), 218 deletions(-) create mode 100644 src/main/Resources/Style/Global.css diff --git a/src/main/Resources/Style/Global.css b/src/main/Resources/Style/Global.css new file mode 100644 index 0000000..92c26b0 --- /dev/null +++ b/src/main/Resources/Style/Global.css @@ -0,0 +1,236 @@ +/* This is the CSS design used in the application */ + + +/* Root / Page background */ +.root { + + /* Colour pallette */ + dark-colour: rgb(28, 53, 45); + light-colour: rgb(249, 246, 243); + green-colour: rgb(166, 178, 139); + light-green-colour: rgba(166, 178, 139, 0.2); + red-colour: rgb(245, 201, 176); + dark-red-colour: rgb(53, 28, 32); + + + -fx-font-family: Verdana; + -fx-font-size: 13px; + -fx-background-color: green-colour; +} + +/* All labels */ +.label { + -fx-text-fill: dark-colour; + -fx-font-size: 13px; +} + +/* Title */ +.label.title-label { + -fx-font-size: 35px; + -fx-font-weight: bold; + -fx-padding: 0 0 12 0; +} + +/* Form row labels (setup scene) */ +.label.form-label { + -fx-font-size: 14px; +} + +/* Status bar label */ +.label.status-label { + -fx-font-size: 12px; + -fx-font-weight: bold; +} + +/* File name label */ +.label.file-label { + -fx-font-size: 12px; +} + +/* Card (centered white box in setup scene) */ +.card { + -fx-background-color: light-colour; + -fx-padding: 50 60 50 60; +} + +/* Content area (main game tab/panel background) */ +.content-area { + -fx-background-color: light-colour; + -fx-padding: 10; +} + +/* Top status bar */ +.top-bar { + -fx-background-color: light-green-colour; + -fx-padding: 8 12 8 12; +} + +/* Text fields */ +.text-field { + -fx-background-radius: 4; + -fx-padding: 7 10 7 10; + -fx-background-color: green-colour; + -fx-text-fill: dark-colour; + -fx-prompt-text-fill: dark-colour; + -fx-border-width: 0; +} + +/* Scroll panes (transparent so card shows through) */ +.scroll-pane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +.scroll-pane > .viewport { + -fx-background-color: transparent; +} + +/* Base button */ +.button { + -fx-font-size: 14px; + -fx-padding: 10 28 10 28; + -fx-background-radius: 4; + -fx-cursor: hand; + -fx-font-weight: bold; + -fx-border-style: solid; + -fx-border-width: 2px; + -fx-border-radius: 4; + -fx-background-color: dark-colour; + -fx-text-fill: light-colour; + -fx-border-color: dark-colour; +} + +.button:hover { + -fx-text-fill: dark-colour; + -fx-background-color: green-colour; +} + +/* Exit button */ +.exit-button { + -fx-background-color: dark-red-colour; + -fx-text-fill: light-colour; + -fx-border-color: dark-red-colour; +} + +.exit-button:hover { + -fx-background-color: red-colour; +} + +/* Browse button */ +.browse-button { + -fx-padding: 6 18 6 18; + -fx-font-size: 12px; +} + +/* Table view */ +.table-view { + -fx-background-color: light-colour; + -fx-border-color: dark-colour; + -fx-border-width: 1; +} + +.table-view .column-header-background { + -fx-background-color: green-colour; +} + +.table-view .column-header .label { + -fx-text-fill: dark-colour; + -fx-font-weight: bold; +} + +.table-view .table-row-cell { + -fx-background-color: light-colour; + -fx-border-color: transparent; +} + +.table-view .table-row-cell:odd { + -fx-background-color: light-green-colour; +} + +.table-view .table-row-cell:selected { + -fx-background-color: green-colour; +} + +.table-view .table-row-cell:selected .text { + -fx-fill: dark-colour; + -fx-font-weight: bold; +} + +/* Tab pane */ +.tab-pane { + -fx-background-color: light-colour; +} + +.tab-pane .tab-header-area { + -fx-background-color: green-colour; + -fx-padding: 4 0 0 4; +} + +.tab-pane .tab { + -fx-background-color: green-colour; + -fx-background-radius: 4 4 0 0; + -fx-padding: 5 16 5 16; +} + +.tab-pane .tab:selected { + -fx-background-color: light-colour; +} + +.tab-pane .tab .tab-label { + -fx-text-fill: dark-colour; + -fx-font-weight: bold; + -fx-font-size: 12px; +} + +.tab-pane .tab-content-area { + -fx-background-color: light-colour; + -fx-padding: 0; +} + +/* List view */ +.list-view { + -fx-background-color: light-colour; + -fx-border-color: green-colour; +} + +.list-view .list-cell { + -fx-background-color: light-colour; + -fx-text-fill: dark-colour; + -fx-padding: 6 8; +} + +.list-view .list-cell:odd { + -fx-background-color: green-colour; +} + +.list-view .list-cell:selected { + -fx-background-color: dark-colour; + -fx-text-fill: light-colour; +} + +/* ComboBox */ +.combo-box { + -fx-background-color: green-colour; + -fx-background-radius: 4; + -fx-text-fill: dark-colour; +} + +.combo-box .list-cell { + -fx-text-fill: dark-colour; + -fx-background-color: green-colour; +} + +.combo-box-popup .list-view .list-cell { + -fx-background-color: light-colour; + -fx-text-fill: dark-colour; +} + +.combo-box-popup .list-view .list-cell:selected { + -fx-background-color: green-colour; +} + +/* Separator */ +.separator .line { + -fx-border-color: dark-colour; + -fx-opacity: 0.4; +} diff --git a/src/main/java/View/GameSetupScene.java b/src/main/java/View/GameSetupScene.java index f6c8664..0ed8ded 100644 --- a/src/main/java/View/GameSetupScene.java +++ b/src/main/java/View/GameSetupScene.java @@ -1,12 +1,13 @@ package View; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; -import javafx.scene.layout.VBox; -import javafx.scene.layout.HBox; +import javafx.scene.layout.*; import javafx.stage.FileChooser; import javafx.stage.Stage; + import Controller.StockFileHandler; import Model.Exchange; import Model.Stock; @@ -17,6 +18,7 @@ import java.util.function.Consumer; public class GameSetupScene { + private Scene scene; private Consumer onGameStart; private File selectedFile; @@ -28,114 +30,174 @@ public GameSetupScene(Consumer onGameStart) { } private Scene createScene() { - VBox root = new VBox(15); - root.setPadding(new Insets(20)); - root.setStyle("-fx-font-family: Arial; -fx-font-size: 12;"); - // title + // Card (centered cream box) + VBox card = new VBox(20); + card.getStyleClass().add("card"); + card.setMaxWidth(560); + card.setMinWidth(400); + + // Title Label titleLabel = new Label("Stock Trading Game"); - titleLabel.setStyle("-fx-font-size: 24; -fx-font-weight: bold;"); - root.getChildren().add(titleLabel); + titleLabel.getStyleClass().add("title-label"); + + // Spacer below title + Region titleSpacer = new Region(); + titleSpacer.setMinHeight(6); + + // Player name row + HBox nameBox = new HBox(16); + nameBox.setAlignment(Pos.CENTER_LEFT); + nameBox.setMaxWidth(Double.MAX_VALUE); + + Label nameLabel = new Label("Player:"); + nameLabel.setMinWidth(130); + nameLabel.getStyleClass().add("form-label"); - // player name - HBox nameBox = new HBox(10); - Label nameLabel = new Label("Player Name:"); - nameLabel.setMinWidth(120); TextField nameField = new TextField(); nameField.setPromptText("Enter your name"); + HBox.setHgrow(nameField, Priority.ALWAYS); + nameBox.getChildren().addAll(nameLabel, nameField); - // starting capital - HBox capitalBox = new HBox(10); - Label capitalLabel = new Label("Starting Capital:"); - capitalLabel.setMinWidth(120); + // Starting capital row + HBox capitalBox = new HBox(16); + capitalBox.setAlignment(Pos.CENTER_LEFT); + capitalBox.setMaxWidth(Double.MAX_VALUE); + + Label capitalLabel = new Label("Start Capital:"); + capitalLabel.setMinWidth(130); + capitalLabel.getStyleClass().add("form-label"); + TextField capitalField = new TextField(); - capitalField.setPromptText("Enter amount (e.g., 10000)"); + capitalField.setPromptText("e.g. 10000"); + HBox.setHgrow(capitalField, Priority.ALWAYS); + capitalBox.getChildren().addAll(capitalLabel, capitalField); - // file selection - HBox fileBox = new HBox(10); + // File selection row + HBox fileBox = new HBox(16); + fileBox.setAlignment(Pos.CENTER_LEFT); + fileBox.setMaxWidth(Double.MAX_VALUE); + Label fileSelectLabel = new Label("Stock Data File:"); - fileSelectLabel.setMinWidth(120); + fileSelectLabel.setMinWidth(130); + fileSelectLabel.getStyleClass().add("form-label"); + fileLabel = new Label("No file selected"); - fileLabel.setStyle("-fx-text-fill: #666666;"); + fileLabel.getStyleClass().add("file-label"); + HBox.setHgrow(fileLabel, Priority.ALWAYS); + Button browseButton = new Button("Browse"); + browseButton.getStyleClass().add("browse-button"); browseButton.setOnAction(e -> selectFile()); + fileBox.getChildren().addAll(fileSelectLabel, fileLabel, browseButton); - // button box - HBox buttonBox = new HBox(10); - buttonBox.setPadding(new Insets(20, 0, 0, 0)); - + // Spacer before buttons + Region buttonSpacer = new Region(); + buttonSpacer.setMinHeight(10); + + // Button row — buttons fill full card width equally + HBox buttonBox = new HBox(12); + buttonBox.setAlignment(Pos.CENTER_LEFT); + buttonBox.setMaxWidth(Double.MAX_VALUE); + Button startButton = new Button("Start Game"); - startButton.setStyle("-fx-font-size: 14; -fx-padding: 10 40 10 40;"); - startButton.setOnAction(e -> { - String name = nameField.getText().trim(); - String capitalStr = capitalField.getText().trim(); - - if (name.isEmpty()) { - showAlert("Validation Error", "Please enter a player name"); - return; - } - - if (capitalStr.isEmpty()) { - showAlert("Validation Error", "Please enter starting capital"); + startButton.getStyleClass().add("start-button"); + startButton.setMaxWidth(Double.MAX_VALUE); + startButton.setOnAction(e -> handleStart(nameField, capitalField)); + HBox.setHgrow(startButton, Priority.ALWAYS); + + Button exitButton = new Button("Exit"); + exitButton.getStyleClass().add("exit-button"); + exitButton.setMaxWidth(Double.MAX_VALUE); + exitButton.setOnAction(e -> System.exit(0)); + HBox.setHgrow(exitButton, Priority.ALWAYS); + + buttonBox.getChildren().addAll(startButton, exitButton); + + card.getChildren().addAll( + titleLabel, + titleSpacer, + nameBox, + capitalBox, + fileBox, + buttonSpacer, + buttonBox + ); + + // Center the card without stretching it vertically + VBox root = new VBox(card); + root.setAlignment(Pos.CENTER); + root.setFillWidth(false); + + Scene scene = new Scene(root, 800, 550); + scene.getStylesheets().add( + getClass().getResource("/Style/Global.css").toExternalForm() + ); + + return scene; + } + + private void handleStart(TextField nameField, TextField capitalField) { + String name = nameField.getText().trim(); + String capitalStr = capitalField.getText().trim(); + + if (name.isEmpty()) { + showAlert("Validation Error", "Please enter a player name"); + return; + } + + if (capitalStr.isEmpty()) { + showAlert("Validation Error", "Please enter starting capital"); + return; + } + + if (selectedFile == null) { + showAlert("Validation Error", "Please select a stock data file"); + return; + } + + try { + BigDecimal capital = new BigDecimal(capitalStr); + + if (capital.compareTo(BigDecimal.ZERO) <= 0) { + showAlert("Validation Error", "Starting capital must be greater than 0"); return; } - - if (selectedFile == null) { - showAlert("Validation Error", "Please select a stock data file"); + + StockFileHandler fileHandler = new StockFileHandler(); + List stocks = fileHandler.loadStocksFromFile(selectedFile.getAbsolutePath()); + + if (stocks.isEmpty()) { + showAlert("Error", "No stocks found in the selected file"); return; } - - try { - BigDecimal capital = new BigDecimal(capitalStr); - if (capital.compareTo(BigDecimal.ZERO) <= 0) { - showAlert("Validation Error", "Starting capital must be greater than 0"); - return; - } - - StockFileHandler fileHandler = new StockFileHandler(); - List stocks = fileHandler.loadStocksFromFile(selectedFile.getAbsolutePath()); - - if (stocks.isEmpty()) { - showAlert("Error", "No stocks found in the selected file"); - return; - } - - Exchange exchange = new Exchange("Main Exchange", stocks); - onGameStart.accept(new StartGameData(name, capital, exchange)); - } catch (NumberFormatException ex) { - showAlert("Validation Error", "Starting capital must be a valid number"); - } catch (Exception ex) { - showAlert("Error", "Failed to load file: " + ex.getMessage()); - } - }); - - Button exitButton = new Button("Exit"); - exitButton.setOnAction(e -> System.exit(0)); - buttonBox.getChildren().addAll(startButton, exitButton); - root.getChildren().addAll(nameBox, capitalBox, fileBox, buttonBox); + Exchange exchange = new Exchange("Main Exchange", stocks); + onGameStart.accept(new StartGameData(name, capital, exchange)); - ScrollPane scrollPane = new ScrollPane(root); - scrollPane.setFitToWidth(true); - return new Scene(scrollPane, 600, 400); + } catch (NumberFormatException ex) { + showAlert("Validation Error", "Starting capital must be a valid number"); + } catch (Exception ex) { + showAlert("Error", "Failed to load file: " + ex.getMessage()); + } } private void selectFile() { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Select Stock Data File"); fileChooser.getExtensionFilters().addAll( - new FileChooser.ExtensionFilter("CSV Files", "*.csv"), - new FileChooser.ExtensionFilter("All Files", "*.*") + new FileChooser.ExtensionFilter("CSV Files", "*.csv"), + new FileChooser.ExtensionFilter("All Files", "*.*") ); - + File file = fileChooser.showOpenDialog(new Stage()); + if (file != null) { selectedFile = file; fileLabel.setText(file.getName()); - fileLabel.setStyle("-fx-text-fill: #000000;"); } } diff --git a/src/main/java/View/MainGameScene.java b/src/main/java/View/MainGameScene.java index 5947dd2..8191208 100644 --- a/src/main/java/View/MainGameScene.java +++ b/src/main/java/View/MainGameScene.java @@ -1,6 +1,7 @@ package View; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.*; @@ -26,68 +27,80 @@ public MainGameScene(Exchange exchange, Player player, Runnable onExit) { } private Scene createScene() { - VBox root = new VBox(5); - root.setPadding(new Insets(10)); + VBox root = new VBox(0); // TOP STATUS BAR - HBox topBar = new HBox(15); - topBar.setPadding(new Insets(10)); - topBar.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #999;"); - + HBox topBar = new HBox(12); + topBar.getStyleClass().add("top-bar"); + topBar.setAlignment(Pos.CENTER_LEFT); + statusLabel = new Label(); + statusLabel.getStyleClass().add("status-label"); updateStatus(); - + + Separator sep = new Separator(javafx.geometry.Orientation.VERTICAL); + sep.setPrefHeight(20); + Button nextWeekBtn = new Button("Next week"); - nextWeekBtn.setStyle("-fx-font-size: 12; -fx-padding: 10 20; -fx-font-weight: bold;"); + nextWeekBtn.getStyleClass().addAll("action-button"); nextWeekBtn.setOnAction(e -> advance()); - + Button exitBtn = new Button("Exit"); + exitBtn.getStyleClass().add("exit-button"); exitBtn.setOnAction(e -> { if (confirm("Exit Game?", "Final Net Worth: $" + formatMoney(getNetWorth()))) { onExit.run(); } }); - - topBar.getChildren().addAll(statusLabel, new Separator(javafx.geometry.Orientation.VERTICAL), - nextWeekBtn, exitBtn); - root.getChildren().add(topBar); + + topBar.getChildren().addAll(statusLabel, sep, nextWeekBtn, exitBtn); // MAIN CONTENT TABS TabPane tabs = new TabPane(); tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); - tabs.getTabs().addAll( new Tab("Stocks", createStocksPanel()), new Tab("Portfolio", createPortfolioPanel()), new Tab("Trade", createTradePanel()), new Tab("History", createHistoryPanel()) ); - + VBox.setVgrow(tabs, Priority.ALWAYS); - root.getChildren().add(tabs); + root.getChildren().addAll(topBar, tabs); - return new Scene(root, 1000, 700); + Scene scene = new Scene(root, 1000, 700); + scene.getStylesheets().add( + getClass().getResource("/Style/Global.css").toExternalForm() + ); + return scene; } private VBox createStocksPanel() { VBox panel = new VBox(10); - panel.setPadding(new Insets(10)); - - HBox searchBox = new HBox(10); + panel.getStyleClass().add("content-area"); + + HBox searchBox = new HBox(8); + searchBox.setAlignment(Pos.CENTER_LEFT); + TextField search = new TextField(); search.setPromptText("Search symbol or company..."); + HBox.setHgrow(search, Priority.ALWAYS); + + Button searchBtn = new Button("Search"); + searchBtn.getStyleClass().add("action-button"); + ComboBox filter = new ComboBox<>(); filter.setItems(FXCollections.observableArrayList("All", "Gainers", "Losers")); filter.setValue("All"); - + TableView table = new TableView<>(); addStockColumns(table); - + Runnable loadStocks = () -> { List stocks; String filterVal = filter.getValue(); String searchVal = search.getText().trim(); - + if ("Gainers".equals(filterVal)) { stocks = exchange.getGainers(20); } else if ("Losers".equals(filterVal)) { @@ -95,21 +108,21 @@ private VBox createStocksPanel() { } else { stocks = exchange.findStocks(searchVal); } - + ObservableList data = FXCollections.observableArrayList(); for (Stock s : stocks) { data.add(new StockRow(s)); } table.setItems(data); }; - - Button searchBtn = new Button("Search"); + searchBtn.setOnAction(e -> loadStocks.run()); filter.setOnAction(e -> loadStocks.run()); - + search.setOnAction(e -> loadStocks.run()); + searchBox.getChildren().addAll(search, searchBtn, filter); loadStocks.run(); - + VBox.setVgrow(table, Priority.ALWAYS); panel.getChildren().addAll(searchBox, table); return panel; @@ -117,31 +130,34 @@ private VBox createStocksPanel() { private VBox createPortfolioPanel() { VBox panel = new VBox(10); - panel.setPadding(new Insets(10)); - + panel.getStyleClass().add("content-area"); + + Label heading = new Label("Holdings:"); + TableView table = new TableView<>(); addPortfolioColumns(table); updatePortfolio(table); - + Button refresh = new Button("Refresh"); + refresh.getStyleClass().add("action-button"); refresh.setOnAction(e -> updatePortfolio(table)); - + VBox.setVgrow(table, Priority.ALWAYS); - panel.getChildren().addAll(new Label("Holdings:"), table, refresh); + panel.getChildren().addAll(heading, table, refresh); return panel; } private VBox createTradePanel() { - VBox panel = new VBox(10); - panel.setPadding(new Insets(10)); - + VBox panel = new VBox(0); + panel.getStyleClass().add("content-area"); + TabPane tradeTabs = new TabPane(); tradeTabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); tradeTabs.getTabs().addAll( new Tab("Buy", createBuyTab()), new Tab("Sell", createSellTab()) ); - + VBox.setVgrow(tradeTabs, Priority.ALWAYS); panel.getChildren().add(tradeTabs); return panel; @@ -149,20 +165,32 @@ private VBox createTradePanel() { private VBox createBuyTab() { VBox box = new VBox(10); - box.setPadding(new Insets(10)); - + box.getStyleClass().add("content-area"); + + Label heading = new Label("Buy Stocks:"); + + GridPane form = new GridPane(); + form.setHgap(10); + form.setVgap(10); + TextField stockField = new TextField(); - stockField.setPromptText("Stock symbol (e.g., AAPL)"); - + stockField.setPromptText("Stock symbol (e.g. AAPL)"); + TextField qtyField = new TextField(); qtyField.setPromptText("Quantity"); - + + form.add(new Label("Symbol:"), 0, 0); + form.add(stockField, 1, 0); + form.add(new Label("Quantity:"), 0, 1); + form.add(qtyField, 1, 1); + Label infoLabel = new Label(); - + stockField.textProperty().addListener((obs, old, val) -> updateBuyInfo(val, qtyField, infoLabel)); qtyField.textProperty().addListener((obs, old, val) -> updateBuyInfo(stockField.getText(), qtyField, infoLabel)); - + Button buyBtn = new Button("Buy"); + buyBtn.getStyleClass().add("action-button"); buyBtn.setOnAction(e -> { try { Stock s = exchange.getStock(stockField.getText().toUpperCase()); @@ -185,42 +213,40 @@ private VBox createBuyTab() { alert("Error", ex.getMessage()); } }); - - box.getChildren().addAll( - new Label("Buy Stocks:"), - stockField, qtyField, infoLabel, buyBtn - ); + + box.getChildren().addAll(heading, form, infoLabel, buyBtn); return box; } private VBox createSellTab() { VBox box = new VBox(10); - box.setPadding(new Insets(10)); - + box.getStyleClass().add("content-area"); + + Label heading = new Label("Your Holdings:"); + ListView holdingsList = new ListView<>(); + holdingsList.setPrefHeight(200); + VBox.setVgrow(holdingsList, Priority.ALWAYS); updateHoldingsList(holdingsList); - - TextField qtyField = new TextField(); - qtyField.setPromptText("Quantity to sell"); - + Label infoLabel = new Label(); - - Button sellBtn = new Button("Sell"); + + Button sellBtn = new Button("Sell Selected"); + sellBtn.getStyleClass().add("action-button"); sellBtn.setOnAction(e -> { String selected = holdingsList.getSelectionModel().getSelectedItem(); if (selected == null) { - alert("Error", "Select a holding"); + alert("Error", "Select a holding to sell"); return; } try { String symbol = selected.split(" ")[0]; List shares = player.getPortfolio().getShares(symbol); if (shares.isEmpty()) return; - + Transaction trans = exchange.sell(shares.get(0), player); if (trans != null && trans.isCommitted()) { showConfirmation("Sale successful", trans); - qtyField.clear(); updateHoldingsList(holdingsList); updateStatus(); } @@ -228,63 +254,66 @@ private VBox createSellTab() { alert("Error", ex.getMessage()); } }); - - holdingsList.setPrefHeight(200); - VBox.setVgrow(holdingsList, Priority.ALWAYS); - box.getChildren().addAll( - new Label("Your Holdings:"), - holdingsList, qtyField, infoLabel, sellBtn - ); + + box.getChildren().addAll(heading, holdingsList, infoLabel, sellBtn); return box; } private VBox createHistoryPanel() { VBox panel = new VBox(10); - panel.setPadding(new Insets(10)); - + panel.getStyleClass().add("content-area"); + ComboBox weekFilter = new ComboBox<>(); updateWeekCombo(weekFilter); - + TableView table = new TableView<>(); addHistoryColumns(table); updateHistory(table, null); - - weekFilter.setOnAction(e -> updateHistory(table, weekFilter.getValue() == 0 ? null : weekFilter.getValue())); - + + weekFilter.setOnAction(e -> updateHistory( + table, + weekFilter.getValue() == null || weekFilter.getValue() == 0 ? null : weekFilter.getValue() + )); + + HBox filterRow = new HBox(8); + filterRow.setAlignment(Pos.CENTER_LEFT); + filterRow.getChildren().addAll(new Label("Week:"), weekFilter); + VBox.setVgrow(table, Priority.ALWAYS); - panel.getChildren().addAll(new HBox(10, new Label("Week:"), weekFilter), table); + panel.getChildren().addAll(filterRow, table); return panel; } - // HELPER METHODS + // ---- Column helpers ---- + private void addStockColumns(TableView table) { table.getColumns().addAll( - createCol("Symbol", 80, "symbol"), - createCol("Company", 150, "company"), - createCol("Price", 80, "price"), - createCol("Change", 80, "change"), - createCol("High", 80, "high"), - createCol("Low", 80, "low") + createCol("Symbol", 90, "symbol"), + createCol("Company", 180, "company"), + createCol("Price", 90, "price"), + createCol("Change", 90, "change"), + createCol("High", 90, "high"), + createCol("Low", 90, "low") ); } private void addPortfolioColumns(TableView table) { table.getColumns().addAll( - createCol("Symbol", 80, "symbol"), - createCol("Qty", 60, "qty"), - createCol("Buy Price", 80, "buyPrice"), - createCol("Current Price", 100, "currentPrice"), - createCol("Gain/Loss", 80, "gainLoss") + createCol("Symbol", 90, "symbol"), + createCol("Qty", 70, "qty"), + createCol("Buy Price", 100, "buyPrice"), + createCol("Current Price", 120, "currentPrice"), + createCol("Gain/Loss", 100, "gainLoss") ); } private void addHistoryColumns(TableView table) { table.getColumns().addAll( - createCol("Week", 60, "week"), - createCol("Type", 60, "type"), - createCol("Symbol", 80, "symbol"), - createCol("Qty", 60, "qty"), - createCol("Total", 100, "total") + createCol("Week", 70, "week"), + createCol("Type", 70, "type"), + createCol("Symbol", 90, "symbol"), + createCol("Qty", 70, "qty"), + createCol("Total", 110, "total") ); } @@ -294,7 +323,9 @@ private TableColumn createCol(String header, int width, String pr col.setCellValueFactory(cellData -> { try { return new javafx.beans.property.SimpleStringProperty( - cellData.getValue().getClass().getMethod("get" + capitalize(property)).invoke(cellData.getValue()).toString() + cellData.getValue().getClass() + .getMethod("get" + capitalize(property)) + .invoke(cellData.getValue()).toString() ); } catch (Exception e) { return new javafx.beans.property.SimpleStringProperty(""); @@ -307,6 +338,8 @@ private String capitalize(String s) { return s.substring(0, 1).toUpperCase() + s.substring(1); } + // ---- Data updaters ---- + private void updatePortfolio(TableView table) { ObservableList data = FXCollections.observableArrayList(); for (Share s : player.getPortfolio().getShares()) { @@ -318,15 +351,18 @@ private void updatePortfolio(TableView table) { private void updateHoldingsList(ListView list) { ObservableList items = FXCollections.observableArrayList(); for (Share s : player.getPortfolio().getShares()) { - items.add(s.getStock().getSymbol() + " - " + formatMoney(s.getQuantity()) + " @ $" + formatMoney(s.getStock().getSalesPrice())); + items.add(s.getStock().getSymbol() + " - " + + formatMoney(s.getQuantity()) + " @ $" + + formatMoney(s.getStock().getSalesPrice())); } list.setItems(items); } private void updateHistory(TableView table, Integer week) { ObservableList data = FXCollections.observableArrayList(); - List trans = week == null ? player.getTransactionArchive().getAllTransactions() : - player.getTransactionArchive().getTransactions(week); + List trans = week == null + ? player.getTransactionArchive().getAllTransactions() + : player.getTransactionArchive().getTransactions(week); for (Transaction t : trans) { data.add(new HistoryRow(t)); } @@ -344,23 +380,27 @@ private void updateWeekCombo(ComboBox combo) { private void updateBuyInfo(String symbol, TextField qtyField, Label label) { try { - if (symbol.isEmpty()) return; + if (symbol == null || symbol.isEmpty()) return; Stock s = exchange.getStock(symbol.toUpperCase()); if (s == null) return; BigDecimal qty = new BigDecimal(qtyField.getText()); BigDecimal gross = s.getSalesPrice().multiply(qty); BigDecimal comm = gross.multiply(new BigDecimal("0.005")); - label.setText("Gross: $" + formatMoney(gross) + " | Commission: $" + formatMoney(comm) + - " | Total: $" + formatMoney(gross.add(comm))); + label.setText("Gross: $" + formatMoney(gross) + + " | Commission: $" + formatMoney(comm) + + " | Total: $" + formatMoney(gross.add(comm))); } catch (Exception e) { - // ignore + // ignore partial input } } private void updateStatus() { - statusLabel.setText("Player: " + player.getName() + " | Week: " + exchange.getWeek() + - " | Cash: $" + formatMoney(player.getMoney()) + - " | Net Worth: $" + formatMoney(getNetWorth())); + statusLabel.setText( + "Player: " + player.getName() + + " | Week: " + exchange.getWeek() + + " | Cash: $" + formatMoney(player.getMoney()) + + " | Net Worth: $" + formatMoney(getNetWorth()) + ); } private void advance() { @@ -391,6 +431,7 @@ private void showConfirmation(String title, Transaction t) { private void alert(String title, String msg) { Alert a = new Alert(Alert.AlertType.INFORMATION); a.setTitle(title); + a.setHeaderText(null); a.setContentText(msg); a.showAndWait(); } @@ -398,6 +439,7 @@ private void alert(String title, String msg) { private boolean confirm(String title, String msg) { Alert a = new Alert(Alert.AlertType.CONFIRMATION); a.setTitle(title); + a.setHeaderText(null); a.setContentText(msg); return a.showAndWait().get() == ButtonType.OK; } @@ -410,7 +452,8 @@ public Scene getScene() { return scene; } - // DATA CLASSES + // ---- Data row classes ---- + static class StockRow { Stock s; StockRow(Stock s) { this.s = s; } diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 9926576..e69de29 100644 --- a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,22 +0,0 @@ -Model\TransactionCalculator.class -Model\Portfolio.class -Model\Share.class -View\GameSetupScene.class -View\MainGameScene$TransactionInfo.class -Model\TransactionArchive.class -View\GameSetupScene$StartGameData.class -View\MainGameScene$StockInfo.class -View\Launcher.class -Model\Purchase.class -Model\Sale.class -Model\Exchange.class -Model\Transaction.class -View\MainGameScene$ShareInfo.class -Model\PurchaseCalculator.class -Model\SaleCalculator.class -Model\Stock.class -Model\Player.class -View\StockTradingGameApp.class -Controller\StockFileHandler.class -View\MainGameScene.class -View\MainGameScene$ShareListCell.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index fc20747..8648fde 100644 --- a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,17 +1,17 @@ -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\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 -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\PurchaseCalculator.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Sale.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\SaleCalculator.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Share.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Stock.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\Transaction.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\TransactionArchive.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\Model\TransactionCalculator.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\View\GameSetupScene.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\View\Launcher.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\View\MainGameScene.java -C:\Users\elisa\Downloads\progdel1\Programmering2_mappe_v26\src\main\java\View\StockTradingGameApp.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Controller/StockFileHandler.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/Exchange.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/Player.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/Portfolio.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/Purchase.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/PurchaseCalculator.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/Sale.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/SaleCalculator.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/Share.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/Stock.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/Transaction.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/TransactionArchive.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/Model/TransactionCalculator.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/View/GameSetupScene.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/View/Launcher.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/View/MainGameScene.java +/Users/solveignatvig/Library/CloudStorage/Dropbox/NTNU/Dataingeniør/V2026/IDATT2003 Programmering 2/IDATT2003_Programmering2_mappe_v26/Programmering2_mappe_v26/src/main/java/View/StockTradingGameApp.java From 453959d76975d0c884f1b73c21ea2d64faa532a6 Mon Sep 17 00:00:00 2001 From: Solveig Natvig Date: Sun, 24 May 2026 14:17:08 +0200 Subject: [PATCH 8/8] Update README.md Added wireframes link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a20c7a6..a26fe2d 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -Branch test 3 \ No newline at end of file +Wireframes: https://www.figma.com/proto/hij5HpFYSpCNvGAZTeBcZo/Stock-Trading-Game?node-id=0-1&t=FXjhcz0tGCcKHnMm-1