Skip to content

Commit

Permalink
Merge pull request #128 from Team-40-IDATT2003/113-refactoring-to-red…
Browse files Browse the repository at this point in the history
…uce-coupling

113 refactoring to reduce coupling
  • Loading branch information
etsorens authored May 25, 2026
2 parents f7b2825 + d6d29ed commit 71c48d6
Show file tree
Hide file tree
Showing 44 changed files with 1,933 additions and 927 deletions.
8 changes: 4 additions & 4 deletions src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import edu.ntnu.idi.idatt2003.g40.mappe.engine.Exchange;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Player;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
import edu.ntnu.idi.idatt2003.g40.mappe.service.FileConverter;
import edu.ntnu.idi.idatt2003.g40.mappe.service.FileParser;
import edu.ntnu.idi.idatt2003.g40.mappe.service.StockFileParser;
import edu.ntnu.idi.idatt2003.g40.mappe.service.StockFileManager;
import edu.ntnu.idi.idatt2003.g40.mappe.service.SaveGameService;
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager;
import edu.ntnu.idi.idatt2003.g40.mappe.utils.ConfigValues;
Expand Down Expand Up @@ -86,9 +86,9 @@ public void start(final Stage stage) throws Exception {
ViewManager viewManager = new ViewManager(stage, eventManager);

List<Stock> stocksInFile;
FileParser parser1 = new FileParser("/sp500.csv");
StockFileManager parser1 = new StockFileManager("src/main/resources/sp500.csv");

FileConverter converter1 = new FileConverter();
StockFileParser converter1 = new StockFileParser();
stocksInFile = converter1.getStocksFromStrings(parser1.readFile());

Exchange exchange = new Exchange("Exchange", stocksInFile);
Expand Down
190 changes: 108 additions & 82 deletions src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
package edu.ntnu.idi.idatt2003.g40.mappe.engine;

import edu.ntnu.idi.idatt2003.g40.mappe.model.Player;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Purchase;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Share;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction;
import edu.ntnu.idi.idatt2003.g40.mappe.service.*;
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData;
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager;
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventPublisher;
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType;
import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionFactory;
import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionType;
import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;

/**
* Represents a stock exchange where stocks can be traded.
*
* <p>Holds a map of stocks where stock symbol is key and stock is value.</p>
* <p>Holds a map of {@link Stock} objects where stock symbol is key and
* stock is value.</p>
*
* <p>Delegates buying and selling to player elements using calculators</p>
*
* <p>Advances week.</p>
*
* @see Player
* @see TransactionCalculator
* @see edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator
*
* @version 1.1.0
* */
public final class Exchange {

Expand All @@ -44,7 +41,7 @@ public final class Exchange {
/**
* Current week (set to 1 in constructor).
* */
private final IntegerProperty week = new SimpleIntegerProperty(1);
private final ReadOnlyIntegerWrapper week = new ReadOnlyIntegerWrapper(1);

/**
* Map of {@link Stock} objects. Key is stock symbol. Value is stock.
Expand All @@ -59,13 +56,16 @@ public final class Exchange {
/**
* Constructor.
*
* @param name name of exchange.
* @param stocks list of {@link Stock} objects.
* @param name name of exchange.
* @param stocks list of {@link Stock} objects.
*
* @throws IllegalArgumentException if name or stocks are empty/null.
* */
public Exchange(final String name, final List<Stock> stocks) throws IllegalArgumentException {
if (!Validator.NOT_EMPTY.isValid(name) || stocks == null || stocks.isEmpty()) {
public Exchange(final String name, final List<Stock> stocks)
throws IllegalArgumentException {
if (!Validator.NOT_EMPTY.isValid(name)
|| stocks == null
|| stocks.isEmpty()) {
throw new IllegalArgumentException("Invalid exchange parameters!");
}
this.name = name;
Expand Down Expand Up @@ -95,20 +95,20 @@ public int getWeek() {
}

/**
* Getter method for the {@link IntegerProperty} object of week.
* Getter method for the {@link ReadOnlyIntegerProperty} object of week.
*
* @return week.
* */
public IntegerProperty weekProperty() {
return week;
public ReadOnlyIntegerProperty weekProperty() {
return week.getReadOnlyProperty();
}

/**
* Method for checking whether exchange has a stock.
*
* @param symbol the stock symbol.
*
* @return true or false.
* @return true or false.
* */
public boolean hasStock(final String symbol) {
return stockMap.containsKey(symbol);
Expand All @@ -117,16 +117,18 @@ public boolean hasStock(final String symbol) {
/**
* Getter method for stock element.
*
* @param symbol the symbol of the stock to get.
* @param symbol the symbol of the stock to get.
*
* @return {@link Stock} element gotten.
* @return {@link Stock} element gotten.
*
* @throws IllegalArgumentException if symbol is invalid.
* @throws IllegalArgumentException if symbol is invalid or not in exchange.
* */
public Stock getStock(final String symbol) throws IllegalArgumentException {
if (!Validator.VALID_STOCK_NAME.isValid(symbol)) {
public Stock getStock(final String symbol)
throws IllegalArgumentException {
if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)
|| !stockMap.containsKey(symbol)) {
throw new IllegalArgumentException(
Validator.VALID_STOCK_NAME.getErrorMessage());
Validator.VALID_STOCK_SYMBOL.getErrorMessage());
}
return stockMap.get(symbol);
}
Expand All @@ -136,7 +138,7 @@ public Stock getStock(final String symbol) throws IllegalArgumentException {
*
* @param searchTerm the term to search for.
*
* @return a list of {@link Stock} objects.
* @return a list of {@link Stock} objects.
* */
public List<Stock> findStocks(final String searchTerm) {
List<Stock> result = new ArrayList<>();
Expand All @@ -154,35 +156,38 @@ public List<Stock> findStocks(final String searchTerm) {
/**
* Method called when a player buys a stock.
*
* @param symbol the stock this player buys.
* @param quantity the amount of stock to buy.
* @param player the player buying stock.
* @param symbol the stock this player buys.
* @param quantity the amount of stock to buy.
* @param player the player buying stock.
*
* @return Transaction representing the transaction.
* @return Transaction representing the transaction.
*
* @throws IllegalArgumentException if symbol or player is invalid.
* */
public Transaction buy(final String symbol,
final BigDecimal quantity,
final Player player) throws IllegalArgumentException {
if (!Validator.VALID_STOCK_NAME.isValid(symbol) || player == null) {
if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)
|| quantity == null
|| player == null) {
throw new IllegalArgumentException("Invalid purchase!");
}
Stock stock = stockMap.get(symbol);
Stock stock = getStock(symbol);
Share share = new Share(stock, quantity, stock.getSalesPrice());
TransactionCalculator calculator = new PurchaseCalculator(share);
Purchase purchase = new Purchase(share, week.get(), calculator);
Transaction purchase = TransactionFactory.createTransaction(
TransactionType.PURCHASE, share, getWeek()
);
player.handleTransaction(purchase);
return purchase;
}

/**
* Method called when a player sells share.
*
* @param share the share to sell.
* @param player the player buying stock.
* @param share the share to sell.
* @param player the player buying stock.
*
* @return Transaction representing the transaction.
* @return Transaction representing the transaction.
*
* @throws IllegalArgumentException if share or player is null.
* */
Expand All @@ -191,75 +196,84 @@ public Transaction sell(final Share share, final Player player)
if (share == null || player == null) {
throw new IllegalArgumentException("Invalid sell!");
}
TransactionCalculator calculator = new SaleCalculator(share);
Sale sale = new Sale(share, week.get(), calculator);
Transaction sale = TransactionFactory.createTransaction(
TransactionType.SALE, share, getWeek()
);
player.handleTransaction(sale);
return sale;
}

/**
* Method called when a player sells share.
* Method called when a player sells share,
* defined by an amount instead of specific {@link Share} object.
*
* <p>{@link edu.ntnu.idi.idatt2003.g40.mappe.model.Portfolio}</p>
*
* @param amount the amount of "shares" to sell.
* @param amount the amount of "shares" to sell.
* @param stockSymbol the stock to sell shares in.
* @param player the player buying stock.
* @param player the player buying stock.
*
* @return Transaction representing the transaction.
* @return List of transactions commited to sell the shares.
*
* @throws IllegalArgumentException if any parameter is null, or if player does not have enough shares.
* @throws IllegalArgumentException if any parameter is null,
* or if player does not have enough shares,
* or if player does not own any shares
* of the given stock.
* */
public List<Transaction> sell(BigDecimal amount,
public Transaction sell(BigDecimal amount,
final String stockSymbol,
final Player player)
throws IllegalArgumentException {
if (amount == null || player == null || !Validator.NOT_EMPTY.isValid(stockSymbol)) {
throw new IllegalArgumentException("Invalid sell!");
} else {
if (amount == null
|| player == null
|| !Validator.VALID_STOCK_SYMBOL.isValid(stockSymbol)) {
throw new IllegalArgumentException("Invalid sell parameters!");
}

List<Share> sharesOfStock = player.getPortfolio().getShares().stream()
.filter(s -> s.getStock().getSymbol().equals(stockSymbol))
.toList();
List<Share> matchingShares = player.getPortfolio().getShares(stockSymbol);

BigDecimal totalOwned = player.getPortfolio().getTotalSharesBySymbol(stockSymbol);
if (matchingShares.isEmpty()) {
throw new IllegalArgumentException("Player does not own any shares of this stock!");
}

if (amount.compareTo(totalOwned) > 0) {
amount = totalOwned;
}
ArrayList<Transaction> transactions = new ArrayList<>();
BigDecimal remainingToSell = amount;
Share ownedPosition = matchingShares.getFirst();
BigDecimal totalOwned = ownedPosition.getQuantity();

for (Share share : sharesOfStock) {
if (remainingToSell.compareTo(BigDecimal.ZERO) <= 0) {
break;
}
if (amount.compareTo(totalOwned) > 0) {
amount = totalOwned;
}

BigDecimal shareQty = share.getQuantity();
Stock stock = ownedPosition.getStock();
Share shareToSell = new Share(stock, amount, stock.getSalesPrice());

if (shareQty.compareTo(remainingToSell) <= 0) {
remainingToSell = remainingToSell.subtract(shareQty);
transactions.add(sell(share, player));
} else {
Share newShare = player.getPortfolio().splitShare(share, remainingToSell);
remainingToSell = BigDecimal.ZERO;
transactions.add(sell(newShare, player));
}
}
return transactions;
}
Transaction sale = TransactionFactory.createTransaction(
TransactionType.SALE, shareToSell, getWeek()
);
player.handleTransaction(sale);

return sale;
}

/**
* Method for advancing time, increasing the amount of weeks.
*
* <p>Applies a random price change from -5% to 5% to every stock,
* plus a flat percent determined by their fortune, that can range from
* -10% to +10%.</p>
*
* @see edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameEngineView
* */
public void advance() {
for (Stock stock : stockMap.values()) {
BigDecimal currentPrice = stock.getSalesPrice();

double change = ((random.nextDouble() * 0.10) - 0.05) + stock.getFortune();
BigDecimal change = BigDecimal.valueOf(random.nextDouble() * 0.10 - 0.05)
.add(BigDecimal.valueOf(stock.getFortune()));
stock.setFortune(0);
BigDecimal factor = BigDecimal.valueOf(1 + change);
BigDecimal factor = BigDecimal.ONE.add(change);

BigDecimal newPrice = currentPrice.multiply(factor);
BigDecimal newPrice = currentPrice.multiply(factor)
.setScale(2, java.math.RoundingMode.HALF_UP);
stock.addNewSalesPrice(newPrice);
}
week.set(week.get() + 1);
Expand All @@ -269,11 +283,17 @@ public void advance() {
* Method for getting the stocks with the most
* amount of increase since last week.
*
* @param limit the maximum amount of stocks returned
* @param limit the maximum amount of stocks returned
*
* @return list of {@link Stock} objects.
*
* @throws IllegalArgumentException if limit is invalid (negative or zero).
* */
public List<Stock> getGainers(final int limit) {
public List<Stock> getGainers(final int limit)
throws IllegalArgumentException {
if (limit < 1) {
throw new IllegalArgumentException("Invalid limit for getting gainers!");
}
return stockMap.entrySet().stream()
// We only want the stocks with a positive price change.
.filter(e ->
Expand All @@ -294,11 +314,17 @@ public List<Stock> getGainers(final int limit) {
* Method for getting the stocks with the highest
* loss of price since last week.
*
* @param limit the maximum amount of stocks returned
* @param limit the maximum amount of stocks returned
*
* @return list of {@link Stock} objects.
*
* @throws IllegalArgumentException if limit is invalid (negative or zero).
* */
public List<Stock> getLosers(final int limit) {
public List<Stock> getLosers(final int limit)
throws IllegalArgumentException {
if (limit < 1) {
throw new IllegalArgumentException("Invalid limit for getting losers!");
}
return stockMap.entrySet().stream()
// Only get entries with negative price change.
.filter(e ->
Expand Down
Loading

0 comments on commit 71c48d6

Please sign in to comment.