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