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
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/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/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() {
diff --git a/src/main/java/View/GameSetupScene.java b/src/main/java/View/GameSetupScene.java
new file mode 100644
index 0000000..0ed8ded
--- /dev/null
+++ b/src/main/java/View/GameSetupScene.java
@@ -0,0 +1,227 @@
+package View;
+
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Scene;
+import javafx.scene.control.*;
+import javafx.scene.layout.*;
+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() {
+
+ // 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.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");
+
+ TextField nameField = new TextField();
+ nameField.setPromptText("Enter your name");
+ HBox.setHgrow(nameField, Priority.ALWAYS);
+
+ nameBox.getChildren().addAll(nameLabel, nameField);
+
+ // 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("e.g. 10000");
+ HBox.setHgrow(capitalField, Priority.ALWAYS);
+
+ capitalBox.getChildren().addAll(capitalLabel, capitalField);
+
+ // 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(130);
+ fileSelectLabel.getStyleClass().add("form-label");
+
+ fileLabel = new Label("No file selected");
+ 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);
+
+ // 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.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;
+ }
+
+ 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());
+ }
+ }
+
+ 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());
+ }
+ }
+
+ 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;
+ }
+ }
+}
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);
+ }
+}
diff --git a/src/main/java/View/MainGameScene.java b/src/main/java/View/MainGameScene.java
new file mode 100644
index 0000000..8191208
--- /dev/null
+++ b/src/main/java/View/MainGameScene.java
@@ -0,0 +1,492 @@
+package View;
+
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+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(0);
+
+ // TOP STATUS BAR
+ 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.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, 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().addAll(topBar, tabs);
+
+ 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.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)) {
+ 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);
+ };
+
+ 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;
+ }
+
+ private VBox createPortfolioPanel() {
+ VBox panel = new VBox(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(heading, table, refresh);
+ return panel;
+ }
+
+ private VBox createTradePanel() {
+ 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;
+ }
+
+ private VBox createBuyTab() {
+ VBox box = new VBox(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)");
+
+ 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());
+ 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(heading, form, infoLabel, buyBtn);
+ return box;
+ }
+
+ private VBox createSellTab() {
+ VBox box = new VBox(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);
+
+ Label infoLabel = new Label();
+
+ 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 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);
+ updateHoldingsList(holdingsList);
+ updateStatus();
+ }
+ } catch (Exception ex) {
+ alert("Error", ex.getMessage());
+ }
+ });
+
+ box.getChildren().addAll(heading, holdingsList, infoLabel, sellBtn);
+ return box;
+ }
+
+ private VBox createHistoryPanel() {
+ VBox panel = new VBox(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() == 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(filterRow, table);
+ return panel;
+ }
+
+ // ---- Column helpers ----
+
+ private void addStockColumns(TableView table) {
+ table.getColumns().addAll(
+ 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", 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", 70, "week"),
+ createCol("Type", 70, "type"),
+ createCol("Symbol", 90, "symbol"),
+ createCol("Qty", 70, "qty"),
+ createCol("Total", 110, "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);
+ }
+
+ // ---- Data updaters ----
+
+ 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 == 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)));
+ } catch (Exception e) {
+ // ignore partial input
+ }
+ }
+
+ 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.setHeaderText(null);
+ a.setContentText(msg);
+ a.showAndWait();
+ }
+
+ 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;
+ }
+
+ private String formatMoney(BigDecimal n) {
+ return n.setScale(2, java.math.RoundingMode.HALF_UP).toString();
+ }
+
+ public Scene getScene() {
+ return scene;
+ }
+
+ // ---- Data row 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(); }
+ }
+}
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);
+ }
+}
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..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,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
+/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