diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..9f86503
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index a74b899..1b52053 100644
--- a/pom.xml
+++ b/pom.xml
@@ -70,6 +70,27 @@
maven-javadoc-plugin
3.12.0
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.3.1
+
+ checkstyle.xml
+ UTF-8
+ true
+ false
+ false
+
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 10.12.5
+
+
+
\ No newline at end of file
diff --git a/src/main/java/Controller/StockFileHandler.java b/src/main/java/Controller/StockFileHandler.java
index b64f9c0..bf808a7 100644
--- a/src/main/java/Controller/StockFileHandler.java
+++ b/src/main/java/Controller/StockFileHandler.java
@@ -1,37 +1,58 @@
package Controller;
-import java.nio.file.*;
+
+import Model.Stock;
import java.io.IOException;
import java.math.BigDecimal;
+import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
-import Model.Stock;
+/**
+ * Handles loading and saving Stock data from/to CSV files.
+ * The CSV format is: ticker,company_name,price
+ * Lines starting with '#' are treated as comments and ignored.
+ */
public class StockFileHandler {
- // lesing
- public List loadStocksFromFile(String filename) throws IOException {
- return Files.readAllLines(Paths.get(filename)).stream()
- .map(String::trim)
- .filter(line -> !line.isEmpty() && !line.startsWith("#"))
- .map(line -> line.split(","))
- .filter(parts -> parts.length == 3)
- .map(parts -> new Stock(parts[0], parts[1], new BigDecimal(parts[2])))
- .toList();
- }
+ /**
+ * Loads stocks from a CSV file.
+ * Expected format: ticker,company,price
+ * Empty lines and lines starting with '#' are ignored.
+ *
+ * @param filename the path to the CSV file
+ * @return a list of Stock objects loaded from the file
+ * @throws IOException if the file cannot be read
+ */
+ public List loadStocksFromFile(String filename) throws IOException {
+ return Files.readAllLines(Paths.get(filename)).stream()
+ .map(String::trim)
+ .filter(line -> !line.isEmpty() && !line.startsWith("#"))
+ .map(line -> line.split(","))
+ .filter(parts -> parts.length == 3)
+ .map(parts -> new Stock(parts[0], parts[1], new BigDecimal(parts[2])))
+ .toList();
+ }
- // lagring
- public void saveStocksToFile(String filename, List stocks) throws IOException {
- List lines = new ArrayList<>();
- lines.add("# Ticker,Name,Price");
+ /**
+ * Saves stocks to a CSV file.
+ * The file includes a header comment line followed by one stock per line.
+ *
+ * @param filename the path to the CSV file to write
+ * @param stocks the list of stocks to save
+ * @throws IOException if the file cannot be written
+ */
+ public void saveStocksToFile(String filename, List stocks) throws IOException {
+ List lines = new ArrayList<>();
+ lines.add("# Ticker,Name,Price");
- lines.addAll(stocks.stream()
- .map(stock -> String.format("%s,%s,%s",
- stock.getSymbol(),
- stock.getCompany(),
- stock.getSalesPrice().toString()))
- .toList());
+ lines.addAll(stocks.stream()
+ .map(stock -> String.format("%s,%s,%s",
+ stock.getSymbol(),
+ stock.getCompany(),
+ stock.getSalesPrice().toString()))
+ .toList());
- Files.write(Paths.get(filename), lines);
- }
+ Files.write(Paths.get(filename), lines);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/Model/Exchange.java b/src/main/java/Model/Exchange.java
index 753e980..7feb4f2 100644
--- a/src/main/java/Model/Exchange.java
+++ b/src/main/java/Model/Exchange.java
@@ -8,7 +8,10 @@
import java.util.Random;
/**
- * Exchange class.
+ * Represents a stock exchange where players can buy and sell stocks.
+ * The Exchange maintains a collection of stocks, advances through weeks,
+ * and executes trades. It implements the Observer pattern to notify listeners
+ * (typically the UI) whenever its state changes.
*/
public class Exchange {
@@ -20,6 +23,13 @@ public class Exchange {
// Registered observers
private final List observers = new ArrayList<>();
+ /**
+ * Constructs a new Exchange with the given name and initial stocks.
+ *
+ * @param name the exchange name. Cannot be null or blank
+ * @param stocks the initial list of stocks. Cannot be null or contain null entries
+ * @throws IllegalArgumentException if any parameter is invalid
+ */
public Exchange(String name, List stocks) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Exchange name cannot be null or blank");
@@ -43,6 +53,12 @@ public Exchange(String name, List stocks) {
// ---- Observer ----
+ /**
+ * Registers an observer to receive notifications of state changes.
+ *
+ * @param observer the observer to register. Cannot be null
+ * @throws IllegalArgumentException if observer is null
+ */
public void addObserver(ExchangeObserver observer) {
if (observer == null) {
throw new IllegalArgumentException("Observer cannot be null");
@@ -52,24 +68,49 @@ public void addObserver(ExchangeObserver observer) {
}
}
+ /**
+ * Unregisters an observer from receiving notifications.
+ *
+ * @param observer the observer to remove
+ */
public void removeObserver(ExchangeObserver observer) {
observers.remove(observer);
}
+ /**
+ * Notifies all registered observers of a state change.
+ */
private void notifyObservers() {
for (ExchangeObserver observer : observers) {
observer.onExchangeUpdated(this);
}
}
+ /**
+ * Returns the exchange name.
+ *
+ * @return the name
+ */
public String getName() {
return name;
}
+ /**
+ * Returns the current week number.
+ *
+ * @return the week
+ */
public int getWeek() {
return week;
}
+ /**
+ * Checks if a stock with the given symbol is available on this exchange.
+ *
+ * @param symbol the stock symbol. Cannot be null or blank
+ * @return true if the stock exists; false otherwise
+ * @throws IllegalArgumentException if symbol is invalid
+ */
public boolean hasStock(String symbol) {
if (symbol == null || symbol.isBlank()) {
throw new IllegalArgumentException("Symbol cannot be null or blank");
@@ -77,6 +118,13 @@ public boolean hasStock(String symbol) {
return stockMap.containsKey(symbol);
}
+ /**
+ * Returns the stock with the given symbol.
+ *
+ * @param symbol the stock symbol. Cannot be null or blank
+ * @return the Stock, or null if not found
+ * @throws IllegalArgumentException if symbol is invalid
+ */
public Stock getStock(String symbol) {
if (symbol == null || symbol.isBlank()) {
throw new IllegalArgumentException("Symbol cannot be null or blank");
@@ -84,6 +132,13 @@ public Stock getStock(String symbol) {
return stockMap.get(symbol);
}
+ /**
+ * Searches for stocks matching the search term by symbol or company name.
+ *
+ * @param searchTerm the search term. Cannot be null
+ * @return a list of matching stocks (case-insensitive)
+ * @throws IllegalArgumentException if searchTerm is null
+ */
public List findStocks(String searchTerm) {
if (searchTerm == null) {
throw new IllegalArgumentException("Search term cannot be null");
@@ -101,6 +156,16 @@ public List findStocks(String searchTerm) {
return result;
}
+ /**
+ * Executes a buy transaction for the specified stock and quantity.
+ *
+ * @param symbol the stock symbol. Cannot be null or blank
+ * @param quantity the number of shares to buy. Must be greater than zero
+ * @param player the player executing the purchase. Cannot be null
+ * @return the completed Purchase transaction
+ * @throws IllegalArgumentException if any parameter is invalid or stock not found
+ * @throws IllegalStateException if player has insufficient funds
+ */
public Transaction buy(String symbol, BigDecimal quantity, Player player) {
if (symbol == null || symbol.isBlank()) {
throw new IllegalArgumentException("Symbol cannot be null or blank");
@@ -120,7 +185,7 @@ public Transaction buy(String symbol, BigDecimal quantity, Player player) {
throw new IllegalArgumentException("No stock found with symbol: " + symbol);
}
- // lager en ny "andel" basert på nåværende salgspris
+ // Creates a new share based on current selling price
Share shareToBuy = new Share(stock, quantity, stock.getSalesPrice());
Purchase purchase = new Purchase(shareToBuy, this.week);
purchase.commit(player);
@@ -130,19 +195,40 @@ public Transaction buy(String symbol, BigDecimal quantity, Player player) {
return purchase;
}
+ /**
+ * Executes a full sale transaction for all shares in the given holding.
+ *
+ * @param share the share to sell completely. Cannot be null
+ * @param player the player executing the sale. Cannot be null
+ * @return the completed Sale transaction
+ * @throws IllegalArgumentException if any parameter is invalid
+ * @throws IllegalStateException if player doesn't own the share
+ */
public Transaction sell(Share share, Player player) {
return sell(share, share.getQuantity(), player);
}
+ /**
+ * Executes a partial or complete sale transaction.
+ * If the sell quantity is less than the share quantity, creates a partial sale
+ * and leaves the remainder in the portfolio.
+ *
+ * @param originalShare the share to sell from. Cannot be null
+ * @param sellQuantity the quantity to sell. Cannot exceed the original quantity
+ * @param player the player executing the sale. Cannot be null
+ * @return the completed Sale transaction
+ * @throws IllegalArgumentException if any parameter is invalid
+ * @throws IllegalStateException if sell quantity exceeds holding
+ */
public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player player) {
if (originalShare == null || sellQuantity == null) {
throw new IllegalArgumentException("Share and quantity cannot be null");
}
if (player == null) {
- throw new IllegalArgumentException("Player cannot be null");
+ throw new IllegalArgumentException("Player cannot be null");
}
- // Kan ikke selge mer enn man eier
+ // Cannot sell more shares than you have
if (sellQuantity.compareTo(originalShare.getQuantity()) > 0) {
throw new IllegalArgumentException("Cannot sell more shares than owned");
}
@@ -153,8 +239,8 @@ public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player pla
if (sellQuantity.compareTo(originalShare.getQuantity()) < 0) {
- //Delsalg:
- // Original mengde - mengde som selges = gjenværende mengde
+ // Partial sale:
+ // Original quantity - quantity sold = remaining quantity
player.getPortfolio().removeShare(originalShare);
@@ -164,7 +250,7 @@ public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player pla
.getStock(), remainderShare, originalShare.getPurchasePrice()));
- //Legger delmengden midlertidig til i portfolio slik at Sale.commit() finner den
+ // Temporarily adds the subset to the portfolio so that Sale.commit() can find it
shareToSell = new Share(originalShare
.getStock(), sellQuantity, originalShare.getPurchasePrice());
@@ -174,11 +260,11 @@ public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player pla
}
else {
- // Fullstendig salg: hele andelen selges som normalt
+ // Complete sale: the entire share is sold as normal
shareToSell = originalShare;
}
- // Salgstransaksjon
+ // Salestransaction
Transaction sale = TransactionFactory.createSale(shareToSell, this.week);
sale.commit(player);
@@ -187,15 +273,17 @@ public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player pla
}
/**
- * Advances to the next week.
+ * Advances the game to the next week and updates all stock prices.
+ * Each stock's price is randomly adjusted by -10% to +10% per week.
+ * Notifies all observers of the change.
*/
public void advance() {
week++;
- for (Stock stock : stockMap.values()) { // henter stock-objektene
+ for (Stock stock : stockMap.values()) { // Retrieves the stock-objects
- BigDecimal currentPrice = stock.getSalesPrice(); // henter siste pris fra Stock
+ BigDecimal currentPrice = stock.getSalesPrice(); // Retrieves last price from Stock
double changePercent = (random.nextDouble() - 0.5) * 0.1;
@@ -203,7 +291,7 @@ public void advance() {
BigDecimal newPrice = currentPrice.add(change);
- if (newPrice.compareTo(BigDecimal.ZERO) > 0) { // unngå negativ pris
+ if (newPrice.compareTo(BigDecimal.ZERO) > 0) { // avoid negative price
stock.addNewSalesPrice(newPrice);
}
}
@@ -212,12 +300,13 @@ public void advance() {
}
/**
- * Shows the weeks Gainers, the stocks with the best price change.
+ * Returns the top-performing stocks (gainers) for the current week.
+ * Gainers are stocks with the highest positive price change.
*
- * @param limit how many gainers
- * @return returns the gainers
+ * @param limit the maximum number of gainers to return
+ * @return a list of the top gainers, sorted by price change (highest first)
*/
- public List getGainers(int limit) { // viser "vinnerne"
+ public List getGainers(int limit) { // shows the "winners"
return stockMap.values().stream()
.sorted((s1, s2) -> s2.getLatestPriceChange().compareTo(s1.getLatestPriceChange()))
.limit(limit)
@@ -225,12 +314,13 @@ public List getGainers(int limit) { // viser "vinnerne"
}
/**
- * Shows the weeks Losers, the stocks with the worst price change.
+ * Returns the lowest-performing stocks (losers) for the current week.
+ * Losers are stocks with the highest negative price change.
*
- * @param limit how many losers
- * @return returns the losers
+ * @param limit the maximum number of losers to return
+ * @return a list of the top losers, sorted by price change (lowest first)
*/
- public List getLosers(int limit) { // viser "taperne"
+ public List getLosers(int limit) { // shows the "losers"
return stockMap.values().stream()
.sorted((s1, s2) -> s1.getLatestPriceChange().compareTo(s2.getLatestPriceChange()))
.limit(limit)
diff --git a/src/main/java/Model/ExchangeObserver.java b/src/main/java/Model/ExchangeObserver.java
index 145f3cc..7cef281 100644
--- a/src/main/java/Model/ExchangeObserver.java
+++ b/src/main/java/Model/ExchangeObserver.java
@@ -1,14 +1,14 @@
package Model;
/**
- * Observer interface for the Exchange subject.
- * Implement this interface to receive notifications whenever the exchange
- * state changes (week advances, a trade is committed).
+ * Observer interface for receiving notifications from the Exchange.
+ * Implementations are notified whenever the Exchange state changes,
+ * allowing the View and other components to stay synchronized.
*/
public interface ExchangeObserver {
/**
- * Called by the Exchange after its state has changed.
+ * Called by the Exchange when its state changes.
*
* @param exchange the Exchange that triggered the notification
*/
diff --git a/src/main/java/Model/Player.java b/src/main/java/Model/Player.java
index 3293db1..dd9d81e 100644
--- a/src/main/java/Model/Player.java
+++ b/src/main/java/Model/Player.java
@@ -3,8 +3,10 @@
import java.math.BigDecimal;
/**
- * The player class is a description of the person player the game,
- * their starting point and their progress.
+ * Represents a player in the stock trading game.
+ * A Player manages their personal portfolio of shares, cash balance, and transaction
+ * history. Players can buy and sell stocks, and their performance is tracked through
+ * status levels based on trading activity and net worth growth.
*/
public class Player {
private String name;
@@ -14,10 +16,11 @@ public class Player {
private TransactionArchive transactionArchive;
/**
- * Player method that includes the players name and the money they start with.
+ * Constructs a new Player with the given name and starting capital.
*
- * @param name the name of the player
- * @param startingMoney money the player starts with
+ * @param name the player's name. Cannot be null or blank
+ * @param startingMoney the initial cash balance. Cannot be null or negative
+ * @throws IllegalArgumentException if any parameter is invalid
*/
public Player(String name, BigDecimal startingMoney) {
if (name == null || name.isBlank()) {
@@ -37,18 +40,29 @@ public Player(String name, BigDecimal startingMoney) {
this.transactionArchive = new TransactionArchive();
}
+ /**
+ * Returns the player's name.
+ *
+ * @return the player's name
+ */
public String getName() {
return this.name;
}
+ /**
+ * Returns the player's current cash balance.
+ *
+ * @return the current amount of money available
+ */
public BigDecimal getMoney() {
return this.money;
}
/**
- * Amount of money added.
+ * Adds the specified amount to the player's cash balance.
*
- * @param amount amount of money
+ * @param amount the amount to add. Cannot be null or negative
+ * @throws IllegalArgumentException if amount is invalid
*/
public void addMoney(BigDecimal amount) {
if (amount == null) {
@@ -61,9 +75,10 @@ public void addMoney(BigDecimal amount) {
}
/**
- * Amount of money withdrawn.
+ * Removes the specified amount from the player's cash balance.
*
- * @param amount amount of money
+ * @param amount the amount to withdraw. Cannot be null or negative
+ * @throws IllegalArgumentException if amount is invalid
*/
public void withdrawMoney(BigDecimal amount) {
if (amount == null) {
@@ -75,10 +90,20 @@ public void withdrawMoney(BigDecimal amount) {
this.money = this.money.subtract(amount);
}
+ /**
+ * Returns the player's portfolio.
+ *
+ * @return the portfolio containing held shares
+ */
public Portfolio getPortfolio() {
return this.portfolio;
}
+ /**
+ * Returns the player's transaction archive.
+ *
+ * @return the archive of all transactions
+ */
public TransactionArchive getTransactionArchive() {
return this.transactionArchive;
}
diff --git a/src/main/java/Model/PlayerStatus.java b/src/main/java/Model/PlayerStatus.java
index e72a9ef..312e38f 100644
--- a/src/main/java/Model/PlayerStatus.java
+++ b/src/main/java/Model/PlayerStatus.java
@@ -1,12 +1,13 @@
package Model;
/**
- * Enum representing the player's status level based on trading activity and performance.
-
- * Status levels:
- * - NOVICE: Starting level, no requirements
- * - INVESTOR: Traded for at least 10 weeks AND increased net worth by at least 20%
- * - SPECULATOR: Traded for at least 20 weeks AND doubled the net worth (100% increase)
+ * Enum representing the player's trading status level.
+ * Status levels are determined by trading activity (weeks with transactions)
+ * and net worth performance compared to starting capital.
+ * Levels:
+ * - NOVICE: Initial status (default)
+ * - INVESTOR: 10+ weeks of trading AND net worth increased by 20%
+ * - SPECULATOR: 20+ weeks of trading AND net worth doubled
*/
public enum PlayerStatus {
NOVICE("Novice"),
@@ -15,10 +16,20 @@ public enum PlayerStatus {
private final String displayName;
+ /**
+ * Constructs a PlayerStatus with the given display name.
+ *
+ * @param displayName the name for UI display
+ */
PlayerStatus(String displayName) {
this.displayName = displayName;
}
+ /**
+ * Returns the display name for this status.
+ *
+ * @return the display name
+ */
public String getDisplayName() {
return displayName;
}
diff --git a/src/main/java/Model/Portfolio.java b/src/main/java/Model/Portfolio.java
index 902b8b9..7c9e38d 100644
--- a/src/main/java/Model/Portfolio.java
+++ b/src/main/java/Model/Portfolio.java
@@ -5,21 +5,28 @@
import java.util.List;
/**
- * Portfolio class of the player.
+ * Represents a portfolio of shares held by a player.
+ * A Portfolio manages a collection of Share objects and provides methods to add,
+ * remove, and query holdings. It can calculate the total net worth based on current
+ * market prices.
*/
public class Portfolio {
private final List shares;
+ /**
+ * Constructs a new empty Portfolio.
+ */
public Portfolio() {
this.shares = new ArrayList<>();
}
/**
- * Method to add share.
+ * Adds a share to the portfolio.
*
- * @param share the share being added
- * @return returns the added share
+ * @param share the share to add. Cannot be null
+ * @return true if the share was added successfully
+ * @throws IllegalArgumentException if share is null
*/
public boolean addShare(Share share) {
if (share == null) {
@@ -29,10 +36,11 @@ public boolean addShare(Share share) {
}
/**
- * Method to remove share.
+ * Removes a share from the portfolio.
*
- * @param share the share being removed
- * @return returns the removed share
+ * @param share the share to remove. Cannot be null
+ * @return true if the share was removed; false if it was not in the portfolio
+ * @throws IllegalArgumentException if share is null
*/
public boolean removeShare(Share share) {
if (share == null) {
@@ -41,15 +49,21 @@ public boolean removeShare(Share share) {
return shares.remove(share);
}
+ /**
+ * Returns a defensive copy of all shares in the portfolio.
+ *
+ * @return a copy of the shares list
+ */
public List getShares() {
return new ArrayList<>(shares);
}
/**
- * Method to get all shares.
+ * Returns all shares in the portfolio for a specific stock symbol.
*
- * @param symbol the symbol for each stock
- * @return the shares of stocks
+ * @param symbol the stock symbol to filter by. Cannot be null or blank
+ * @return a list of shares matching the symbol
+ * @throws IllegalArgumentException if symbol is null or blank
*/
public List getShares(String symbol) {
if (symbol == null || symbol.isBlank()) {
@@ -61,10 +75,11 @@ public List getShares(String symbol) {
}
/**
- * Method to check if contains share.
+ * Checks if the portfolio contains a specific share.
*
- * @param share the share
- * @return the share if not null
+ * @param share the share to check for. Cannot be null
+ * @return true if the share is in the portfolio; false otherwise
+ * @throws IllegalArgumentException if share is null
*/
public boolean contains(Share share) {
if (share == null) {
@@ -74,9 +89,11 @@ public boolean contains(Share share) {
}
/**
- * Method to get the new worth.
+ * Calculates the total net worth of the portfolio based on current market prices.
+ * The net worth is the sum of all shares valued at their current sales prices,
+ * minus any transaction costs (commissions and taxes).
*
- * @return returns the net worth
+ * @return the total net worth of all holdings
*/
public BigDecimal getNetWorth() {
return shares.stream()
diff --git a/src/main/java/Model/Purchase.java b/src/main/java/Model/Purchase.java
index d367a7b..b81f655 100644
--- a/src/main/java/Model/Purchase.java
+++ b/src/main/java/Model/Purchase.java
@@ -3,13 +3,30 @@
import java.math.BigDecimal;
/**
- * Purchase class whenever the player makes a purchase.
+ * Represents a stock purchase transaction.
+ * A Purchase transaction represents a player buying shares. When committed,
+ * it deducts money from the player's account, adds the shares to their portfolio,
+ * and records the transaction in the archive.
*/
public class Purchase extends Transaction {
+ /**
+ * Constructs a new Purchase transaction.
+ *
+ * @param share the share to purchase. Cannot be null
+ * @param week the week number when the purchase occurs. Must be at least 1
+ */
public Purchase(Share share, int week) {
super(share, week, new PurchaseCalculator(share));
}
+ /**
+ * Commits this purchase transaction, deducting funds and adding shares to the portfolio.
+ *
+ * @param player the player executing the purchase. Cannot be null
+ * @throws IllegalArgumentException if player is null
+ * @throws IllegalStateException if purchase has already been committed or if
+ * the player has insufficient funds
+ */
@Override
public void commit(Player player) {
if (player == null) {
diff --git a/src/main/java/Model/PurchaseCalculator.java b/src/main/java/Model/PurchaseCalculator.java
index e90839f..e2a7482 100644
--- a/src/main/java/Model/PurchaseCalculator.java
+++ b/src/main/java/Model/PurchaseCalculator.java
@@ -3,34 +3,60 @@
import java.math.BigDecimal;
/**
- * PurchaseCalculator class that does calculations.
+ * Calculates costs and totals for purchase transactions.
+ * A PurchaseCalculator computes the gross cost (quantity × price), applies
+ * a commission fee, and produces the total transaction cost.
*/
public class PurchaseCalculator implements TransactionCalculator {
private final BigDecimal purchasePrice;
private final BigDecimal quantity;
-
+
+ /**
+ * Constructs a PurchaseCalculator for the given share.
+ *
+ * @param share the share being purchased. Cannot be null
+ */
public PurchaseCalculator(Share share) {
this.purchasePrice = share.getPurchasePrice();
this.quantity = share.getQuantity();
}
+ /**
+ * Calculates the gross cost without fees (quantity × purchase price).
+ *
+ * @return the gross cost
+ */
@Override
public BigDecimal calculateGross() {
return this.purchasePrice.multiply(this.quantity);
}
+ /**
+ * Calculates the purchase commission (0.5% of gross).
+ *
+ * @return the commission fee
+ */
@Override
public BigDecimal calculateCommission() {
BigDecimal rate = new BigDecimal("0.005");
return calculateGross().multiply(rate);
}
+ /**
+ * Calculates the tax on purchase (always 0 for purchases).
+ *
+ * @return zero
+ */
@Override
public BigDecimal calculateTax() {
- BigDecimal tax = new BigDecimal("0");
- return tax;
+ return BigDecimal.ZERO;
}
+ /**
+ * Calculates the total purchase cost (gross + commission + tax).
+ *
+ * @return the total transaction cost
+ */
@Override
public BigDecimal calculateTotal() {
return calculateGross().add(calculateCommission()).add(calculateTax());
diff --git a/src/main/java/Model/Sale.java b/src/main/java/Model/Sale.java
index e130f41..8b2e05e 100644
--- a/src/main/java/Model/Sale.java
+++ b/src/main/java/Model/Sale.java
@@ -3,13 +3,30 @@
import java.math.BigDecimal;
/**
- * Sale class for whenever the player makes a sale.
+ * Represents a stock sale transaction.
+ * A Sale transaction represents a player selling shares from their portfolio.
+ * When committed, it adds money to the player's account, removes the shares from
+ * their portfolio, and records the transaction in the archive.
*/
public class Sale extends Transaction {
+ /**
+ * Constructs a new Sale transaction.
+ *
+ * @param share the share to sell. Cannot be null
+ * @param week the week number when the sale occurs. Must be at least 1
+ */
public Sale(Share share, int week) {
super(share, week, new SaleCalculator(share));
}
+ /**
+ * Commits this sale transaction, crediting funds and removing shares from the portfolio.
+ *
+ * @param player the player executing the sale. Cannot be null
+ * @throws IllegalArgumentException if player is null
+ * @throws IllegalStateException if sale has already been committed or if the
+ * player does not own the share being sold
+ */
@Override
public void commit(Player player) {
if (player == null) {
diff --git a/src/main/java/Model/SaleCalculator.java b/src/main/java/Model/SaleCalculator.java
index 93b6f93..271225a 100644
--- a/src/main/java/Model/SaleCalculator.java
+++ b/src/main/java/Model/SaleCalculator.java
@@ -3,17 +3,19 @@
import java.math.BigDecimal;
/**
- * SaleCalculator class that calculates gross, commission, tax and total.
+ * Calculates costs, taxes, and proceeds for sale transactions.
+ * A SaleCalculator computes the gross revenue (quantity × sales price), applies
+ * a commission fee, calculates capital gains tax, and produces the net proceeds.
*/
-public class SaleCalculator implements TransactionCalculator{
- private BigDecimal purchasePrice;
- private BigDecimal salesPrice;
- private BigDecimal quantity;
+public class SaleCalculator implements TransactionCalculator {
+ private final BigDecimal purchasePrice;
+ private final BigDecimal salesPrice;
+ private final BigDecimal quantity;
/**
- * Method with parameters.
+ * Constructs a SaleCalculator for the given share.
*
- * @param share share from calculator
+ * @param share the share being sold. Cannot be null
*/
public SaleCalculator(Share share) {
this.purchasePrice = share.getPurchasePrice();
@@ -21,11 +23,21 @@ public SaleCalculator(Share share) {
this.quantity = share.getQuantity();
}
+ /**
+ * Calculates the gross revenue (quantity × sales price).
+ *
+ * @return the gross revenue
+ */
@Override
public BigDecimal calculateGross() {
return this.salesPrice.multiply(this.quantity);
}
+ /**
+ * Calculates the sale commission (1% of gross).
+ *
+ * @return the commission fee
+ */
@Override
public BigDecimal calculateCommission() {
BigDecimal rate = new BigDecimal("0.01");
@@ -33,8 +45,12 @@ public BigDecimal calculateCommission() {
}
/**
- * Method that calculates tax.
+ * Calculates the capital gains tax (30% of profit).
+ * Profit is calculated as: revenue - commission - original purchase cost.
+ *
+ * @return the capital gains tax
*/
+ @Override
public BigDecimal calculateTax() {
BigDecimal sellingCost = this.purchasePrice.multiply(this.quantity);
BigDecimal profit = calculateGross().subtract(calculateCommission()).subtract(sellingCost);
@@ -42,6 +58,11 @@ public BigDecimal calculateTax() {
return profit.multiply(rate);
}
+ /**
+ * Calculates the net proceeds (gross - commission - tax).
+ *
+ * @return the total amount credited to the player
+ */
@Override
public BigDecimal calculateTotal() {
return calculateGross().subtract(calculateCommission()).subtract(calculateTax());
diff --git a/src/main/java/Model/Share.java b/src/main/java/Model/Share.java
index d7fee43..05a4c08 100644
--- a/src/main/java/Model/Share.java
+++ b/src/main/java/Model/Share.java
@@ -3,7 +3,9 @@
import java.math.BigDecimal;
/**
- * Share class.
+ * Represents a share holding of a stock.
+ * A Share encapsulates a quantity of a stock purchased at a specific price.
+ * It provides information about the stock and the purchase terms.
*/
public class Share {
@@ -12,11 +14,12 @@ public class Share {
private final BigDecimal purchasePrice;
/**
- * Share method that has stock, quantity and purchaseprice.
+ * Constructs a new Share with the specified stock, quantity, and purchase price.
*
- * @param stock stock name
- * @param quantity quantity of stock
- * @param purchasePrice price of stock
+ * @param stock the Stock being held. Cannot be null
+ * @param quantity the number of shares held. Must be greater than zero
+ * @param purchasePrice the price per share at purchase. Cannot be null or negative
+ * @throws IllegalArgumentException if any parameter is invalid
*/
public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) {
if (stock == null) {
@@ -40,14 +43,29 @@ public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) {
this.purchasePrice = purchasePrice;
}
+ /**
+ * Returns the Stock associated with this share.
+ *
+ * @return the stock being held
+ */
public Stock getStock() {
return stock;
}
+ /**
+ * Returns the quantity of shares held.
+ *
+ * @return the number of shares
+ */
public BigDecimal getQuantity() {
return quantity;
}
+ /**
+ * Returns the price per share at the time of purchase.
+ *
+ * @return the purchase price
+ */
public BigDecimal getPurchasePrice() {
return purchasePrice;
}
diff --git a/src/main/java/Model/Stock.java b/src/main/java/Model/Stock.java
index ca117b5..3c6866e 100644
--- a/src/main/java/Model/Stock.java
+++ b/src/main/java/Model/Stock.java
@@ -5,7 +5,10 @@
import java.util.List;
/**
- * STock class.
+ * Represents a stock traded on the exchange.
+ * A Stock tracks the company's trading symbol, company name, and historical price
+ * data. Prices are maintained in chronological order, with the latest price being
+ * the current sales price.
*/
public class Stock {
@@ -14,7 +17,12 @@ public class Stock {
private final List prices;
/**
- * Stock method that includes symbol of the stock, company and the salesprice.
+ * Constructs a new Stock with the given symbol, company name, and initial sales price.
+ *
+ * @param symbol the unique ticker symbol (e.g., "AAPL"). Cannot be null or blank
+ * @param company the company name. Cannot be null or blank
+ * @param salesPrice the initial sales price. Cannot be null or negative
+ * @throws IllegalArgumentException if any parameter is invalid
*/
public Stock(String symbol, String company, BigDecimal salesPrice) {
if (symbol == null || symbol.isBlank()) {
@@ -36,20 +44,38 @@ public Stock(String symbol, String company, BigDecimal salesPrice) {
this.prices.add(salesPrice);
}
+ /**
+ * Returns the ticker symbol of this stock.
+ *
+ * @return the stock symbol
+ */
public String getSymbol() {
return symbol;
}
+ /**
+ * Returns the company name associated with this stock.
+ *
+ * @return the company name
+ */
public String getCompany() {
return company;
}
+ /**
+ * Returns the current (most recent) sales price of this stock.
+ *
+ * @return the current sales price
+ */
public BigDecimal getSalesPrice() {
return prices.get(prices.size() - 1);
}
-
+
/**
- * Method that makes new salesprice.
+ * Records a new sales price for this stock.
+ *
+ * @param price the new sales price. Cannot be null or negative
+ * @throws IllegalArgumentException if price is invalid
*/
public void addNewSalesPrice(BigDecimal price) {
if (price == null) {
@@ -62,7 +88,7 @@ public void addNewSalesPrice(BigDecimal price) {
}
public List getHistoricalPrices() {
- return new ArrayList<>(prices); // returnerer en kopi for å beskytte selve listen
+ return new ArrayList<>(prices); // returns a copy to protect the list itself
}
public BigDecimal getHighestPrice() {
diff --git a/src/main/java/Model/Transaction.java b/src/main/java/Model/Transaction.java
index cedd4d2..a8eb4f1 100644
--- a/src/main/java/Model/Transaction.java
+++ b/src/main/java/Model/Transaction.java
@@ -1,7 +1,10 @@
package Model;
/**
- * Transaction class.
+ * Abstract base class for all transactions (purchases and sales).
+ * A Transaction represents a trading action taken by a player on a specific week.
+ * Transactions use the Strategy pattern via TransactionCalculator to compute costs
+ * and can be committed to apply their effects to the player's portfolio.
*/
public abstract class Transaction {
private Share share;
@@ -9,6 +12,14 @@ public abstract class Transaction {
private TransactionCalculator calculator;
protected boolean committed;
+ /**
+ * Constructs a Transaction for the specified share in the given week.
+ *
+ * @param share the share being traded. Cannot be null
+ * @param week the week number when the transaction occurs. Must be at least 1
+ * @param calculator the calculator for transaction costs. Cannot be null
+ * @throws IllegalArgumentException if any parameter is invalid
+ */
protected Transaction(Share share, int week, TransactionCalculator calculator) {
if (share == null) {
throw new IllegalArgumentException("Share cannot be null");
@@ -25,22 +36,49 @@ protected Transaction(Share share, int week, TransactionCalculator calculator) {
this.calculator = calculator;
}
+ /**
+ * Returns the share being traded in this transaction.
+ *
+ * @return the share
+ */
public Share getShare() {
return this.share;
}
+ /**
+ * Returns the week number when this transaction occurs.
+ *
+ * @return the week number
+ */
public int getWeek() {
return this.week;
}
+ /**
+ * Returns the calculator used to compute transaction costs.
+ *
+ * @return the transaction calculator
+ */
public TransactionCalculator getCalculator() {
return this.calculator;
}
+ /**
+ * Returns whether this transaction has been committed.
+ *
+ * @return true if committed; false otherwise
+ */
public boolean isCommitted() {
return this.committed;
}
+ /**
+ * Commits this transaction, applying its effects to the player's portfolio.
+ *
+ * @param player the player executing the transaction. Cannot be null
+ * @throws IllegalArgumentException if player is null
+ * @throws IllegalStateException if the transaction has already been committed
+ */
public abstract void commit(Player player);
}
diff --git a/src/main/java/Model/TransactionArchive.java b/src/main/java/Model/TransactionArchive.java
index db7a50c..4af5bff 100644
--- a/src/main/java/Model/TransactionArchive.java
+++ b/src/main/java/Model/TransactionArchive.java
@@ -5,37 +5,60 @@
import java.util.stream.Collectors;
/**
- * TransactionArchive class.
+ * Maintains a chronological archive of all transactions executed by a player.
+ * The TransactionArchive allows retrieval of transactions by week and type
+ * (purchase or sale), and provides statistics about trading activity.
*/
public class TransactionArchive {
- private List transactions;
+ private final List transactions;
+ /**
+ * Constructs a new empty TransactionArchive.
+ */
public TransactionArchive() {
this.transactions = new ArrayList<>();
}
+ /**
+ * Adds a transaction to the archive.
+ *
+ * @param transaction the transaction to add. Cannot be null
+ * @return true if the transaction was added successfully
+ * @throws IllegalArgumentException if transaction is null
+ */
public boolean add(Transaction transaction) {
if (transaction == null) {
- throw new IllegalArgumentException("Should not be null"); // Eller NullPointerExeption?
+ throw new IllegalArgumentException("Should not be null");
}
return transactions.add(transaction);
}
+ /**
+ * Checks if the archive is empty.
+ *
+ * @return true if no transactions have been recorded
+ */
public boolean isEmpty() {
return transactions.isEmpty();
}
+ /**
+ * Returns all transactions that occurred in a specific week.
+ *
+ * @param week the week number
+ * @return a list of transactions in that week
+ */
public List getTransactions(int week) {
return transactions.stream()
.filter(transaction -> transaction.getWeek() == week).collect(Collectors.toList());
}
/**
- * List of all purchases in one week.
+ * Returns all purchases that occurred in a specific week.
*
- * @param week the week to find purchases for
- * @return returns the purchases
+ * @param week the week number
+ * @return a list of purchases in that week
*/
public List getPurchase(int week) {
return transactions.stream()
@@ -45,10 +68,10 @@ public List getPurchase(int week) {
}
/**
- * Gets sales for that week.
+ * Returns all sales that occurred in a specific week.
*
- * @param week the week to find sales for
- * @return returns the sales
+ * @param week the week number
+ * @return a list of sales in that week
*/
public List getSale(int week) {
return transactions.stream()
@@ -57,10 +80,20 @@ public List getSale(int week) {
.map(transaction -> (Sale) transaction).collect(Collectors.toList());
}
+ /**
+ * Returns a defensive copy of all transactions in the archive.
+ *
+ * @return a copy of the transaction list
+ */
public List getAllTransactions() {
return new ArrayList<>(transactions);
}
+ /**
+ * Counts the number of distinct weeks with trading activity.
+ *
+ * @return the number of unique weeks represented in the archive
+ */
public int countDistinctWeeks() {
return (int) transactions.stream().map(Transaction::getWeek).distinct().count();
}
diff --git a/src/main/java/Model/TransactionCalculator.java b/src/main/java/Model/TransactionCalculator.java
index 73fe750..2691e02 100644
--- a/src/main/java/Model/TransactionCalculator.java
+++ b/src/main/java/Model/TransactionCalculator.java
@@ -3,16 +3,37 @@
import java.math.BigDecimal;
/**
- * TransactionCalculator class.
+ * Strategy interface for calculating transaction costs and proceeds.
+ * Implementations compute fees and totals for different transaction types
+ * (purchases and sales), following the Strategy design pattern.
*/
public interface TransactionCalculator {
- // Methods in interface is automatically public abstract.
+ /**
+ * Calculates the base cost/revenue without fees.
+ *
+ * @return the gross amount
+ */
BigDecimal calculateGross();
+ /**
+ * Calculates the commission fee on the transaction.
+ *
+ * @return the commission amount
+ */
BigDecimal calculateCommission();
+ /**
+ * Calculates any applicable taxes on the transaction.
+ *
+ * @return the tax amount
+ */
BigDecimal calculateTax();
+ /**
+ * Calculates the final total after all fees and taxes.
+ *
+ * @return the total transaction amount
+ */
BigDecimal calculateTotal();
}
diff --git a/src/main/java/Model/TransactionFactory.java b/src/main/java/Model/TransactionFactory.java
index 691d41b..1f799d1 100644
--- a/src/main/java/Model/TransactionFactory.java
+++ b/src/main/java/Model/TransactionFactory.java
@@ -2,18 +2,29 @@
/**
* Factory for creating transaction objects.
+ * Uses the Factory pattern to encapsulate transaction creation logic,
+ * allowing clients to create Purchase and Sale transactions without
+ * knowing implementation details.
*/
public class TransactionFactory {
/**
- * Create purchase transaction.
+ * Creates a new Purchase transaction.
+ *
+ * @param share the share to purchase. Cannot be null
+ * @param week the week when the purchase occurs. Must be at least 1
+ * @return a new Purchase transaction
*/
public static Transaction createPurchase(Share share, int week) {
return new Purchase(share, week);
}
/**
- * Create sale transaction.
+ * Creates a new Sale transaction.
+ *
+ * @param share the share to sell. Cannot be null
+ * @param week the week when the sale occurs. Must be at least 1
+ * @return a new Sale transaction
*/
public static Transaction createSale(Share share, int week) {
return new Sale(share, week);
diff --git a/src/main/java/View/GameSetupScene.java b/src/main/java/View/GameSetupScene.java
index 1e2d66d..44052ca 100644
--- a/src/main/java/View/GameSetupScene.java
+++ b/src/main/java/View/GameSetupScene.java
@@ -18,14 +18,13 @@
/**
* Displays the game setup scene where players configure their game parameters.
- *
* Allows players to enter their name, starting capital, and select a stock data file
* before starting the main game. Notifies listeners when the game starts.
*/
public class GameSetupScene {
- private Scene scene;
- private Consumer onGameStart;
+ private final Scene scene;
+ private final Consumer onGameStart;
private File selectedFile;
private Label fileLabel;
@@ -113,7 +112,7 @@ private Scene createScene() {
Region buttonSpacer = new Region();
buttonSpacer.setMinHeight(10);
- // Button row — buttons fill full card width equally
+ // Button row
HBox buttonBox = new HBox(12);
buttonBox.setAlignment(Pos.CENTER_LEFT);
buttonBox.setMaxWidth(Double.MAX_VALUE);
@@ -142,7 +141,7 @@ private Scene createScene() {
buttonBox
);
- // Center the card without stretching it vertically
+ // Centers the root
VBox root = new VBox(card);
root.setAlignment(Pos.CENTER);
root.setFillWidth(false);
@@ -250,7 +249,6 @@ public Scene getScene() {
/**
* Data class encapsulating game setup parameters.
- *
* Contains the exchange configured with stocks, player name, and starting capital.
*/
public static class StartGameData {
diff --git a/src/main/java/View/Launcher.java b/src/main/java/View/Launcher.java
index b56a4bf..fb7bc49 100644
--- a/src/main/java/View/Launcher.java
+++ b/src/main/java/View/Launcher.java
@@ -1,9 +1,16 @@
package View;
/**
- * Launcher class for the Stock Trading Game.
+ * Entry point for launching the Stock Trading Game application.
+ * This launcher delegates to the main StockTradingGameApp class to start the JavaFX
+ * application. It provides a conventional main method entry point.
*/
public class Launcher {
+ /**
+ * Main method to launch the application.
+ *
+ * @param args command-line arguments (not used)
+ */
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
index fc7bd81..6f4cd27 100644
--- a/src/main/java/View/MainGameScene.java
+++ b/src/main/java/View/MainGameScene.java
@@ -1,585 +1,693 @@
package View;
-import javafx.geometry.Insets;
+import Model.*;
+import java.math.BigDecimal;
+import java.util.List;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
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;
/**
- * Main game UI. Implements ExchangeObserver so the view automatically refreshes
- * whenever the Exchange notifies it of a state change (week advance or trade).
+ * The main game user interface scene.
+ * Displays the game board with stocks, portfolio, trading interface, and transaction
+ * history. Implements ExchangeObserver to automatically refresh UI when the game state changes.
*/
public class MainGameScene implements ExchangeObserver {
- private Scene scene;
- private Exchange exchange;
- private Player player;
- private Runnable onExit;
- private Label statusLabel;
-
- // References to UI components that need refreshing
- private TableView portfolioTable;
- private ListView holdingsList;
- private TableView historyTable;
- private ComboBox weekFilterCombo;
-
- public MainGameScene(Exchange exchange, Player player, Runnable onExit) {
- this.exchange = exchange;
- this.player = player;
- this.onExit = onExit;
-
- // Register this view as an observer of the exchange
- this.exchange.addObserver(this);
-
- this.scene = createScene();
- }
-
- /**
- * Called automatically by Exchange whenever its state changes.
- * Refreshes all UI elements to reflect the latest data.
- */
- @Override
- public void onExchangeUpdated(Exchange exchange) {
- updateStatus();
- refreshAllUI();
- }
-
- 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 -> exchange.advance()); // observer handles the UI update
-
- Button exitBtn = new Button("Exit");
- exitBtn.getStyleClass().add("exit-button");
- exitBtn.setOnAction(e -> {
- if (confirm("Exit Game?", "Final Net Worth: $" + formatMoney(getNetWorth()))) {
- exchange.removeObserver(this); // clean up before closing
- onExit.run();
+ private final Scene scene;
+ private final Exchange exchange;
+ private final Player player;
+ private final Runnable onExit;
+ private Label statusLabel;
+
+ // References to UI components that need refreshing
+ private TableView portfolioTable;
+ private ListView holdingsList;
+ private TableView historyTable;
+ private ComboBox weekFilterCombo;
+
+ /**
+ * Constructs the main game scene.
+ *
+ * @param exchange the Exchange managing the game state. Cannot be null
+ * @param player the Player being displayed. Cannot be null
+ * @param onExit callback to run when exiting the game. Cannot be null
+ */
+ public MainGameScene(Exchange exchange, Player player, Runnable onExit) {
+ this.exchange = exchange;
+ this.player = player;
+ this.onExit = onExit;
+
+ // Register this view as an observer of the exchange
+ this.exchange.addObserver(this);
+
+ this.scene = createScene();
+ }
+
+ /**
+ * Callback invoked when the Exchange notifies observers of state changes.
+ *
+ * @param exchange the Exchange that changed
+ */
+ @Override
+ public void onExchangeUpdated(Exchange exchange) {
+ updateStatus();
+ refreshAllUI();
+ }
+
+ /**
+ * Creates the main game scene layout.
+ *
+ * @return the constructed Scene
+ */
+ 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 -> exchange.advance()); // observer handles the UI update
+
+ Button exitBtn = new Button("Exit");
+ exitBtn.getStyleClass().add("exit-button");
+ exitBtn.setOnAction(e -> {
+ if (confirm("Exit Game?", "Final Net Worth: $" + formatMoney(getNetWorth()))) {
+ exchange.removeObserver(this); // clean up before closing
+ 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;
+ }
+
+ /**
+ * Creates the stocks panel displaying available stocks.
+ *
+ * @return the stocks panel
+ */
+ 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 (null == filterVal) {
+ stocks = exchange.findStocks(searchVal);
+ } else {
+ stocks = switch (filterVal) {
+ case "Gainers" -> exchange.getGainers(20);
+ case "Losers" -> exchange.getLosers(20);
+ default -> 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;
+ }
+
+ /**
+ * Creates the portfolio panel showing held shares and net worth.
+ *
+ * @return the portfolio panel
+ */
+ private VBox createPortfolioPanel() {
+ VBox panel = new VBox(10);
+ panel.getStyleClass().add("content-area");
+
+ Label heading = new Label("Holdings:");
+
+ portfolioTable = new TableView<>();
+ addPortfolioColumns(portfolioTable);
+ updatePortfolio(portfolioTable);
+
+ Button refresh = new Button("Refresh");
+ refresh.getStyleClass().add("action-button");
+ refresh.setOnAction(e -> updatePortfolio(portfolioTable));
+
+ Button sellSelected = new Button("Sell Selected");
+ sellSelected.getStyleClass().add("action-button");
+ sellSelected.setOnAction(e -> {
+ PortfolioRow selected = portfolioTable.getSelectionModel().getSelectedItem();
+ if (selected == null) {
+ alert("Error", "Select a holding to sell.");
+ return;
+ }
+ Transaction trans = exchange.sell(selected.s, player); // observer fires refresh
+ if (trans != null && trans.isCommitted()) {
+ showConfirmation("Sale successful", trans);
+ } else {
+ alert("Failed", "Could not complete the sale.");
+ }
+ });
+
+ HBox buttons = new HBox(10, refresh, sellSelected);
+ buttons.setAlignment(Pos.CENTER_LEFT);
+
+ VBox.setVgrow(portfolioTable, Priority.ALWAYS);
+ panel.getChildren().addAll(heading, portfolioTable, buttons);
+ return panel;
+ }
+
+ /**
+ * Creates the trading panel with buy and sell tabs.
+ *
+ * @return the trading 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;
+ }
+
+ /**
+ * Creates the buy transaction tab.
+ *
+ * @return the buy tab content
+ */
+ 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); // observer fires refresh
+ if (trans != null && trans.isCommitted()) {
+ showConfirmation("Purchase successful", trans);
+ stockField.clear();
+ qtyField.clear();
+ infoLabel.setText("");
+ } else {
+ alert("Failed", "Insufficient funds or error");
+ }
+ } catch (Exception ex) {
+ alert("Error", ex.getMessage());
+ }
+ });
+
+ box.getChildren().addAll(heading, form, infoLabel, buyBtn);
+ return box;
+ }
+
+ /**
+ * Creates the sell transaction tab.
+ *
+ * @return the sell tab content
+ */
+ private VBox createSellTab() {
+ VBox box = new VBox(10);
+ box.getStyleClass().add("content-area");
+
+ Label heading = new Label("Your Holdings:");
+
+ holdingsList = new ListView<>();
+ holdingsList.setPrefHeight(400);
+
+ holdingsList.setCellFactory(lv -> new ListCell() {
+ @Override
+ protected void updateItem(Share s, boolean empty) {
+ super.updateItem(s, empty);
+ if (empty || s == null) {
+ setText(null);
+ } else {
+ setText(
+ s.getStock().getSymbol() + " - "
+ + s.getQuantity() + " @ $"
+ + formatMoney(s.getStock().getSalesPrice())
+ );
}
- });
-
- 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()
+ }
+ });
+
+ updateHoldingsList(holdingsList);
+
+ // Numer of rows
+ HBox quantityRow = new HBox(8);
+ quantityRow.setAlignment(Pos.CENTER_LEFT);
+ TextField quantityField = new TextField();
+ quantityField.setPromptText("Antall");
+ quantityField.setPrefWidth(120);
+ quantityRow.getChildren().addAll(new Label("Quantity to sell:"), quantityField);
+
+ // Fill in the maximum number automatically when selecting a holding
+ holdingsList.getSelectionModel().selectedItemProperty().addListener((obs, old, selected) -> {
+ if (selected != null) {
+ quantityField.setText(selected.getQuantity().toPlainString());
+ }
+ });
+
+ Button sellBtn = new Button("Sell Selected");
+ sellBtn.setOnAction(e -> {
+ Share selected = holdingsList.getSelectionModel().getSelectedItem();
+ if (selected == null) {
+ alert("Error", "Select a holding to sell");
+ return;
+ }
+
+ try {
+ BigDecimal quantity = new BigDecimal(quantityField.getText().trim());
+
+ if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
+ alert("Error", "Quantity must be greater than zero");
+ return;
+ }
+
+ Transaction trans = exchange.sell(selected, quantity, player); // observer fires refresh
+
+ if (trans != null && trans.isCommitted()) {
+ showConfirmation("Sale successful", trans);
+ } else {
+ alert("Failed", "Could not complete sale. Check quantity.");
+ }
+ } catch (NumberFormatException ex) {
+ alert("Error", "Enter a valid number for quantity");
+ }
+ });
+
+ box.getChildren().addAll(heading, holdingsList, quantityRow, sellBtn);
+
+ return box;
+ }
+
+ /**
+ * Creates the transaction history panel.
+ *
+ * @return the history panel
+ */
+ private VBox createHistoryPanel() {
+ VBox panel = new VBox(10);
+ panel.getStyleClass().add("content-area");
+
+ weekFilterCombo = new ComboBox<>();
+ updateWeekCombo(weekFilterCombo);
+
+ historyTable = new TableView<>();
+ addHistoryColumns(historyTable);
+ updateHistory(historyTable, null);
+
+ weekFilterCombo.setOnAction(e -> updateHistory(
+ historyTable,
+ weekFilterCombo.getValue() == null || weekFilterCombo.getValue() == 0
+ ? null : weekFilterCombo.getValue()
+ ));
+
+ HBox filterRow = new HBox(8);
+ filterRow.setAlignment(Pos.CENTER_LEFT);
+ filterRow.getChildren().addAll(new Label("Week:"), weekFilterCombo);
+
+ VBox.setVgrow(historyTable, Priority.ALWAYS);
+ panel.getChildren().addAll(filterRow, historyTable);
+ return panel;
+ }
+
+ // ---- Column helpers ----
+
+ @SuppressWarnings("unchecked")
+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")
+ );
+ }
+
+ @SuppressWarnings("unchecked")
+ 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")
+ );
+ }
+
+ @SuppressWarnings("unchecked")
+ 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()
);
- return scene;
+ } 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));
}
-
- 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;
+ table.setItems(data);
+ }
+
+ private void updateHoldingsList(ListView list) {
+ ObservableList items = FXCollections.observableArrayList();
+ items.addAll(player.getPortfolio().getShares());
+ 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));
}
-
- private VBox createPortfolioPanel() {
- VBox panel = new VBox(10);
- panel.getStyleClass().add("content-area");
-
- Label heading = new Label("Holdings:");
-
- portfolioTable = new TableView<>();
- addPortfolioColumns(portfolioTable);
- updatePortfolio(portfolioTable);
-
- Button refresh = new Button("Refresh");
- refresh.getStyleClass().add("action-button");
- refresh.setOnAction(e -> updatePortfolio(portfolioTable));
-
- Button sellSelected = new Button("Sell Selected");
- sellSelected.getStyleClass().add("action-button");
- sellSelected.setOnAction(e -> {
- PortfolioRow selected = portfolioTable.getSelectionModel().getSelectedItem();
- if (selected == null) {
- alert("Error", "Select a holding to sell.");
- return;
- }
- Transaction trans = exchange.sell(selected.s, player); // observer fires refresh
- if (trans != null && trans.isCommitted()) {
- showConfirmation("Sale successful", trans);
- } else {
- alert("Failed", "Could not complete the sale.");
- }
- });
-
- HBox buttons = new HBox(10, refresh, sellSelected);
- buttons.setAlignment(Pos.CENTER_LEFT);
-
- VBox.setVgrow(portfolioTable, Priority.ALWAYS);
- panel.getChildren().addAll(heading, portfolioTable, buttons);
- return panel;
+ 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);
}
-
- 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;
+ 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 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); // observer fires refresh
- if (trans != null && trans.isCommitted()) {
- showConfirmation("Purchase successful", trans);
- stockField.clear();
- qtyField.clear();
- infoLabel.setText("");
- } else {
- alert("Failed", "Insufficient funds or error");
- }
- } catch (Exception ex) {
- alert("Error", ex.getMessage());
- }
- });
-
- box.getChildren().addAll(heading, form, infoLabel, buyBtn);
- return box;
+ }
+
+ private void updateStatus() {
+ PlayerStatus playerStatus = player.getStatus();
+ statusLabel.setText(
+ "Player: " + player.getName()
+ + " | Status: " + playerStatus.getDisplayName()
+ + " | Week: " + exchange.getWeek()
+ + " | Cash: $" + formatMoney(player.getMoney())
+ + " | Net Worth: $" + formatMoney(getNetWorth())
+ );
+ }
+
+ private void refreshAllUI() {
+ if (portfolioTable != null) {
+ updatePortfolio(portfolioTable);
}
-
- private VBox createSellTab() {
- VBox box = new VBox(10);
- box.getStyleClass().add("content-area");
-
- Label heading = new Label("Your Holdings:");
-
- holdingsList = new ListView<>();
- holdingsList.setPrefHeight(400);
-
- holdingsList.setCellFactory(lv -> new ListCell() {
- @Override
- protected void updateItem(Share s, boolean empty) {
- super.updateItem(s, empty);
- if (empty || s == null) {
- setText(null);
- } else {
- setText(
- s.getStock().getSymbol() + " - " +
- s.getQuantity() + " @ $" +
- formatMoney(s.getStock().getSalesPrice())
- );
- }
- }
- });
-
- updateHoldingsList(holdingsList);
-
- // Antall Rad
- HBox quantityRow = new HBox(8);
- quantityRow.setAlignment(Pos.CENTER_LEFT);
- TextField quantityField = new TextField();
- quantityField.setPromptText("Antall");
- quantityField.setPrefWidth(120);
- quantityRow.getChildren().addAll(new Label("Quantity to sell:"), quantityField);
-
- // Fyll inn maks anntall automatisk når man velger en holding
- holdingsList.getSelectionModel().selectedItemProperty().addListener((obs, old, selected) -> {
- if (selected != null) {
- quantityField.setText(selected.getQuantity().toPlainString());
- }
- });
-
- Button sellBtn = new Button("Sell Selected");
- sellBtn.setOnAction(e -> {
- Share selected = holdingsList.getSelectionModel().getSelectedItem();
- if (selected == null) {
- alert("Error", "Select a holding to sell");
- return;
- }
-
- try {
- BigDecimal quantity = new BigDecimal(quantityField.getText().trim());
-
- if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
- alert("Error", "Quantity must be greater than zero");
- return;
- }
-
- Transaction trans = exchange.sell(selected, quantity, player); // observer fires refresh
-
- if (trans != null && trans.isCommitted()) {
- showConfirmation("Sale successful", trans);
- }
-
- else {
- alert("Failed", "Could not complete sale. Check quantity.");
- }
- }
-
- catch (NumberFormatException ex) {
- alert("Error", "Enter a valid number for quantity");
- }
- });
-
- box.getChildren().addAll(heading, holdingsList, quantityRow, sellBtn);
-
- return box;
+ if (holdingsList != null) {
+ updateHoldingsList(holdingsList);
}
-
- private VBox createHistoryPanel() {
- VBox panel = new VBox(10);
- panel.getStyleClass().add("content-area");
-
- weekFilterCombo = new ComboBox<>();
+ if (historyTable != null) {
+ updateHistory(historyTable,
+ weekFilterCombo != null && weekFilterCombo.getValue() != null
+ ? weekFilterCombo.getValue() : null);
+ if (weekFilterCombo != null) {
updateWeekCombo(weekFilterCombo);
-
- historyTable = new TableView<>();
- addHistoryColumns(historyTable);
- updateHistory(historyTable, null);
-
- weekFilterCombo.setOnAction(e -> updateHistory(
- historyTable,
- weekFilterCombo.getValue() == null || weekFilterCombo.getValue() == 0
- ? null : weekFilterCombo.getValue()
- ));
-
- HBox filterRow = new HBox(8);
- filterRow.setAlignment(Pos.CENTER_LEFT);
- filterRow.getChildren().addAll(new Label("Week:"), weekFilterCombo);
-
- VBox.setVgrow(historyTable, Priority.ALWAYS);
- panel.getChildren().addAll(filterRow, historyTable);
- 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 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()));
}
-
- 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")
- );
+ 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;
}
- 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")
- );
+ public String getSymbol() {
+ return s.getSymbol();
}
- 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;
+ public String getCompany() {
+ return s.getCompany();
}
- private String capitalize(String s) {
- return s.substring(0, 1).toUpperCase() + s.substring(1);
+ public String getPrice() {
+ return "$" + s.getSalesPrice();
}
- // ---- 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);
+ public String getChange() {
+ return "$" + s.getLatestPriceChange();
}
-
- private void updateHoldingsList(ListView list) {
- ObservableList items = FXCollections.observableArrayList();
- items.addAll(player.getPortfolio().getShares());
- list.setItems(items);
+
+ public String getHigh() {
+ return "$" + s.getHighestPrice();
}
- 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);
+ public String getLow() {
+ return "$" + s.getLowestPrice();
}
- 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
- }
- }
+ static class PortfolioRow {
+ Share s;
- private void updateStatus() {
- PlayerStatus playerStatus = player.getStatus();
- statusLabel.setText(
- "Player: " + player.getName() +
- " | Status: " + playerStatus.getDisplayName() +
- " | Week: " + exchange.getWeek() +
- " | Cash: $" + formatMoney(player.getMoney()) +
- " | Net Worth: $" + formatMoney(getNetWorth())
- );
+ PortfolioRow(Share s) {
+ this.s = s;
+ }
+
+ public String getSymbol() {
+ return s.getStock().getSymbol();
}
- private void refreshAllUI() {
- if (portfolioTable != null) {
- updatePortfolio(portfolioTable);
- }
- if (holdingsList != null) {
- updateHoldingsList(holdingsList);
- }
- if (historyTable != null) {
- updateHistory(historyTable,
- weekFilterCombo != null && weekFilterCombo.getValue() != null
- ? weekFilterCombo.getValue() : null);
- if (weekFilterCombo != null) {
- updateWeekCombo(weekFilterCombo);
- }
- }
+ public String getQty() {
+ return s.getQuantity().toString();
}
- private BigDecimal getNetWorth() {
- return player.getMoney().add(player.getPortfolio().getNetWorth());
+ public String getBuyPrice() {
+ return "$" + s.getPurchasePrice();
}
- 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());
+ public String getCurrentPrice() {
+ return "$" + s.getStock().getSalesPrice();
}
- 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();
+ 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;
- 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;
+ HistoryRow(Transaction t) {
+ this.t = t;
}
- private String formatMoney(BigDecimal n) {
- return n.setScale(2, java.math.RoundingMode.HALF_UP).toString();
+ public String getWeek() {
+ return String.valueOf(t.getWeek());
}
- public Scene getScene() {
- return scene;
+ public String getType() {
+ return t instanceof Purchase ? "BUY" : "SELL";
}
- // ---- 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(); }
+ public String getSymbol() {
+ return t.getShare().getStock().getSymbol();
}
- 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);
- }
+ public String getQty() {
+ return t.getShare().getQuantity().toString();
}
- 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(); }
+ public String getTotal() {
+ return "$" + t.getCalculator().calculateTotal();
}
+ }
}
diff --git a/src/main/java/View/StockTradingGameApp.java b/src/main/java/View/StockTradingGameApp.java
index 838df67..b1ab987 100644
--- a/src/main/java/View/StockTradingGameApp.java
+++ b/src/main/java/View/StockTradingGameApp.java
@@ -6,7 +6,9 @@
import javafx.stage.Stage;
/**
- * StockTradingGameApp class.
+ * Main JavaFX application class for the Stock Trading Game.
+ * Manages the application lifecycle, window setup, scene transitions,
+ * and exchanges between the game setup and main game views.
*/
public class StockTradingGameApp extends Application {
private Stage primaryStage;
@@ -14,6 +16,11 @@ public class StockTradingGameApp extends Application {
private Player player;
private StockFileHandler fileHandler;
+ /**
+ * Initializes and displays the primary stage.
+ *
+ * @param primaryStage the primary window stage provided by JavaFX
+ */
@Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
@@ -27,11 +34,19 @@ public void start(Stage primaryStage) {
primaryStage.show();
}
+ /**
+ * Displays the game setup scene where players configure their game.
+ */
private void showGameSetup() {
GameSetupScene setupScene = new GameSetupScene(this::startGame);
primaryStage.setScene(setupScene.getScene());
}
+ /**
+ * Starts the main game scene after setup is complete.
+ *
+ * @param gameData the game configuration data from the setup scene
+ */
private void startGame(GameSetupScene.StartGameData gameData) {
this.exchange = gameData.exchange;
this.player = new Player(gameData.playerName, gameData.startingCapital);
@@ -40,10 +55,18 @@ private void startGame(GameSetupScene.StartGameData gameData) {
primaryStage.setScene(gameScene.getScene());
}
+ /**
+ * Ends the game and closes the application.
+ */
private void endGame() {
primaryStage.close();
}
+ /**
+ * Main method to launch the JavaFX application.
+ *
+ * @param args command-line arguments (not used)
+ */
public static void main(String[] args) {
launch(args);
}
diff --git a/target/test-classes/ExchangeTest.class b/target/test-classes/ExchangeTest.class
index 238ca9d..5b02574 100644
Binary files a/target/test-classes/ExchangeTest.class and b/target/test-classes/ExchangeTest.class differ
diff --git a/target/test-classes/PortfolioTest.class b/target/test-classes/PortfolioTest.class
index 679e0c4..45ae586 100644
Binary files a/target/test-classes/PortfolioTest.class and b/target/test-classes/PortfolioTest.class differ
diff --git a/target/test-classes/ShareTest.class b/target/test-classes/ShareTest.class
index abf202d..65bc1e8 100644
Binary files a/target/test-classes/ShareTest.class and b/target/test-classes/ShareTest.class differ
diff --git a/target/test-classes/StockTest.class b/target/test-classes/StockTest.class
index 0008700..e22183d 100644
Binary files a/target/test-classes/StockTest.class and b/target/test-classes/StockTest.class differ