From 4300b8e9489bf28c20b881f55816cf03a1f305e2 Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Tue, 10 Mar 2026 11:54:40 +0100 Subject: [PATCH 001/157] feat: Add methods to Stock for diverse statistics Pt.2 requirements --- .../java/edu/ntnu/idi/idatt/marked/Stock.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java b/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java index 40f071e..1a26383 100644 --- a/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java +++ b/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java @@ -2,6 +2,7 @@ import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; /** @@ -51,6 +52,44 @@ public List getPrices() { return prices; } + public List getHistoricalPrices() { + return prices; + } + + /** + * Method for obtaining highest price. + * + */ + public BigDecimal getHighestPrice() { + return prices.stream().sorted(Comparator.reverseOrder()).toList().getFirst(); + } + + /** + * Method for obtaining lowest price. + * + */ + public BigDecimal getLowestPrice() { + return prices.stream().sorted(Comparator.naturalOrder()).toList().getFirst(); + } + + /** + * Method for obtaining recent price change. + * + *

+ * If prices is empty or contains only one value, + * the change will result to 0. Else, the difference between + * latest and next to latest stock will be calculated. + *

+ * + */ + public BigDecimal getLatestPriceChange() { + if (prices.isEmpty() || prices.size() == 1) { + return BigDecimal.ZERO; + } + int lastIndex = prices.size(); + return prices.get(lastIndex).min(prices.get(lastIndex - 1)); + } + /** * Getter for sale price * From ea3a5b48199b051ecf72e7aa9ab2979f77980671 Mon Sep 17 00:00:00 2001 From: danieskj Date: Tue, 10 Mar 2026 11:58:46 +0100 Subject: [PATCH 002/157] feat:Added csv file containing stock names and prices --- src/main/resources/edu.ntnu.idi.idatt/stocks.csv | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/resources/edu.ntnu.idi.idatt/stocks.csv diff --git a/src/main/resources/edu.ntnu.idi.idatt/stocks.csv b/src/main/resources/edu.ntnu.idi.idatt/stocks.csv new file mode 100644 index 0000000..f9b7ff5 --- /dev/null +++ b/src/main/resources/edu.ntnu.idi.idatt/stocks.csv @@ -0,0 +1,7 @@ +# Ticker,Name,Price + +AMD,Advanced Micro Devices,202.68 +MSI,Micro-Star International,92.80 +INTC,Intel,45.58 +NVDA,Nvidia,182.65 +EQNR,Equinor,32.43 From ca157bcca00cb11704215e413abc345f43f82917 Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Tue, 10 Mar 2026 12:08:38 +0100 Subject: [PATCH 003/157] refactor(Stock): Remove redundant method --- src/main/java/edu/ntnu/idi/idatt/marked/Stock.java | 8 ++++---- src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java b/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java index 1a26383..5beacca 100644 --- a/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java +++ b/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java @@ -48,10 +48,10 @@ public String getCompany() { return company; } - public List getPrices() { - return prices; - } - + /** + * Method for obtaining the whole price history. + * + */ public List getHistoricalPrices() { return prices; } diff --git a/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java b/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java index 7d81482..3292b26 100644 --- a/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java @@ -26,7 +26,7 @@ void constructorTest() { assertEquals("Apple Inc.", stock.getCompany()); assertEquals(List.of(new BigDecimal("46.2"), - new BigDecimal("40.0")), stock.getPrices()); + new BigDecimal("40.0")), stock.getHistoricalPrices()); assertEquals(new BigDecimal("40.0"), stock.getSalesPrice()); } From 5b993c8f061a7c8be130c6b17636230149378fda Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Sun, 15 Mar 2026 19:46:25 +0100 Subject: [PATCH 004/157] feat(Stock): Add new tests for new price methods. --- .../java/edu/ntnu/idi/idatt/marked/Stock.java | 4 +-- .../edu/ntnu/idi/idatt/marked/StockTest.java | 28 +++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java b/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java index 5beacca..0dbf411 100644 --- a/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java +++ b/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java @@ -86,8 +86,8 @@ public BigDecimal getLatestPriceChange() { if (prices.isEmpty() || prices.size() == 1) { return BigDecimal.ZERO; } - int lastIndex = prices.size(); - return prices.get(lastIndex).min(prices.get(lastIndex - 1)); + int size = prices.size(); + return prices.get(size - 1).min(prices.get(size - 2)); } /** diff --git a/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java b/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java index 3292b26..5064a68 100644 --- a/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java @@ -11,11 +11,15 @@ public class StockTest { private Stock stock; + private List prices; @BeforeEach public void PT_setup() { - List prices = List.of(new BigDecimal("46.2"), - new BigDecimal("40.0")); + prices = List.of(new BigDecimal("46.2"), + new BigDecimal("40.0"), + new BigDecimal("39.5"), + new BigDecimal("51.2"), + new BigDecimal("43.4")); stock = new Stock("AAPL", "Apple Inc.", prices); } @@ -24,11 +28,25 @@ public void PT_setup() { void constructorTest() { assertEquals("AAPL", stock.getSymbol()); assertEquals("Apple Inc.", stock.getCompany()); + assertEquals(new BigDecimal("43.4"), stock.getSalesPrice()); + } - assertEquals(List.of(new BigDecimal("46.2"), - new BigDecimal("40.0")), stock.getHistoricalPrices()); + @Test + void PTgetPrices() { + assertEquals(prices, stock.getHistoricalPrices()); + assertEquals(new BigDecimal("39.5"), stock.getLowestPrice()); + assertEquals(new BigDecimal("51.2"), stock.getHighestPrice()); + assertEquals(new BigDecimal("43.4").min(new BigDecimal("51.2")), stock.getLatestPriceChange()); + } + + @Test + void NTgetLatestPriceChange() { + Stock noPrice = new Stock("NVDA", "Nvidia", List.of()); + Stock onePrice = new Stock("TSLA", "Tesla Inc.", List.of( + BigDecimal.TEN)); - assertEquals(new BigDecimal("40.0"), stock.getSalesPrice()); + assertEquals(BigDecimal.ZERO, noPrice.getLatestPriceChange()); + assertEquals(BigDecimal.ZERO, onePrice.getLatestPriceChange()); } @Test From e0e254580ed90a4ebe5b9d2685d81ea7e81ae458 Mon Sep 17 00:00:00 2001 From: danieskj Date: Sun, 15 Mar 2026 20:33:15 +0100 Subject: [PATCH 005/157] feat: Added method for calculating the players current net value --- .../java/edu/ntnu/idi/idatt/gui/javafx.java | 44 ------------------- .../edu/ntnu/idi/idatt/marked/Portfolio.java | 14 ++++++ .../ntnu/idi/idatt/marked/PortfolioTest.java | 12 +++++ 3 files changed, 26 insertions(+), 44 deletions(-) delete mode 100644 src/main/java/edu/ntnu/idi/idatt/gui/javafx.java diff --git a/src/main/java/edu/ntnu/idi/idatt/gui/javafx.java b/src/main/java/edu/ntnu/idi/idatt/gui/javafx.java deleted file mode 100644 index 2e29e2f..0000000 --- a/src/main/java/edu/ntnu/idi/idatt/gui/javafx.java +++ /dev/null @@ -1,44 +0,0 @@ -package edu.ntnu.idi.idatt.gui; - -import javafx.application.Application; -import javafx.scene.Group; -import javafx.scene.Scene; -import javafx.scene.control.Button; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.paint.Color; -import javafx.stage.Stage; - -import java.awt.*; -import java.io.IOException; - -public class javafx extends Application { - @Override - public void start (Stage stage) throws IOException { - Image stonk = new Image(getClass().getResource("/edu.ntnu.idi.idatt/stonks.png").toExternalForm()); - Button week = new Button("Week 5: Play"); - week.setPrefSize(150, 50); - week.setTranslateX(565); - Button logo = new Button(); - Button user = new Button(); - ImageView user_image = new ImageView(new Image(getClass().getResource("/edu.ntnu.idi.idatt/user.png").toExternalForm())); - ImageView logo_image = new ImageView(new Image(getClass().getResource("/edu.ntnu.idi.idatt/logo.png").toExternalForm())); - logo_image.setFitHeight(100); - logo_image.setFitWidth(100); - user_image.setFitHeight(100); - user_image.setFitWidth(100); - logo.setGraphic(logo_image); - user.setGraphic(user_image); - user.setTranslateX(1170); - ImageView stonks = new ImageView(stonk); - stonks.setX(440); - stonks.setY(220); - Group noe = new Group(stonks, week, logo, user); - Scene scene1 = new Scene(noe, 1280, 720, Color.DEEPSKYBLUE); - logo.setOnAction(e -> stage.setScene(new Scene(new Group(stonks,week,logo,user),1280, 720, Color.DEEPSKYBLUE))); - user.setOnAction(e -> stage.setScene(new Scene(new Group(logo,user),1280, 720, Color.BLACK))); - week.setOnAction(e -> stage.close()); - stage.setScene(scene1); - stage.show(); - } -} diff --git a/src/main/java/edu/ntnu/idi/idatt/marked/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt/marked/Portfolio.java index 5b4a147..6fe32fd 100644 --- a/src/main/java/edu/ntnu/idi/idatt/marked/Portfolio.java +++ b/src/main/java/edu/ntnu/idi/idatt/marked/Portfolio.java @@ -1,7 +1,9 @@ package edu.ntnu.idi.idatt.marked; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; /** * Portfolio class @@ -67,4 +69,16 @@ public boolean contains(Share share) { return shares.contains(share); } + /** + * Method for getting the net value of the player. + * + * @return - Net value of the players shares. + */ + public BigDecimal playerNetValue(){ + BigDecimal netValue = shares.stream() + .map(Share-> Share.getPurchasePrice().multiply(Share.getQuantity())) + .reduce(BigDecimal.ZERO, BigDecimal::add); + return netValue; + } + } diff --git a/src/test/java/edu/ntnu/idi/idatt/marked/PortfolioTest.java b/src/test/java/edu/ntnu/idi/idatt/marked/PortfolioTest.java index f0d0913..a2de1de 100644 --- a/src/test/java/edu/ntnu/idi/idatt/marked/PortfolioTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/marked/PortfolioTest.java @@ -78,4 +78,16 @@ void NTremoveShare() { assertThrows(IllegalArgumentException.class, () -> portfolio.removeShare(exception)); } + /** + * Positive test for finding net value of the players shares. + */ + @Test + void PTplayerNetValue(){ + Share share1 = new Share(stock, new BigDecimal("1"), new BigDecimal("135.8")); + Share share2 = new Share(stock, new BigDecimal("2"), new BigDecimal("254")); + portfolio.addShare(share1); + portfolio.addShare(share2); + assertEquals(new BigDecimal("643.8"), portfolio.playerNetValue()); + } + } From 0d5250250c0fb953d01fb27a552da44f3dba305c Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 15 Mar 2026 20:49:13 +0100 Subject: [PATCH 006/157] refactor(README.md) Add summary, packages, how to run sections. --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index b16466c..ab3bf21 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,32 @@ The exam of IDATT2003, "Programming 2" subject. +# Overview + +- [Summary](#summary) +- [Packages](#packages) +- [How to run](#how-to-run) +- [Guide to tests](#guide-to-tests) + + +# Summary +This is a game emulating a evolving stock market where the player is encouraged to take their own risks and go from rags to riches. With strategic planning and unsuspected events how far can you go? + +What you can expect from the game: +- Something 1 +- Something 2 +- Something 3 + +# Packages +- Main package: `/edu/ntnu/idi/idatt/` +- Base classes: `model/` +- Game classes: `game/` +- GUI elements: `view/` + +# How to run +- Running program: `mvn clean javafx:run` +- Running tests: `mvn clean test` + # Guide to tests - The test generally start with a @BeforeEach method that sets up everything necessary for positive testing. From fc7f8fc5bd7893a106d59ba58d857624203bfef5 Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Tue, 24 Mar 2026 23:05:37 +0100 Subject: [PATCH 007/157] fix(Stock): Correction on getLatestPriceChange logic --- src/main/java/edu/ntnu/idi/idatt/marked/Stock.java | 2 +- src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java b/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java index 0dbf411..d84e7bb 100644 --- a/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java +++ b/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java @@ -87,7 +87,7 @@ public BigDecimal getLatestPriceChange() { return BigDecimal.ZERO; } int size = prices.size(); - return prices.get(size - 1).min(prices.get(size - 2)); + return prices.get(size - 1).subtract(prices.get(size - 2)); } /** diff --git a/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java b/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java index 5064a68..941d9c5 100644 --- a/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java @@ -36,7 +36,7 @@ void PTgetPrices() { assertEquals(prices, stock.getHistoricalPrices()); assertEquals(new BigDecimal("39.5"), stock.getLowestPrice()); assertEquals(new BigDecimal("51.2"), stock.getHighestPrice()); - assertEquals(new BigDecimal("43.4").min(new BigDecimal("51.2")), stock.getLatestPriceChange()); + assertEquals(new BigDecimal("43.4").subtract(new BigDecimal("51.2")), stock.getLatestPriceChange()); } @Test From e6bdf7e576ec3690a205dcb88cc7ccb9f6318436 Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Tue, 24 Mar 2026 23:06:03 +0100 Subject: [PATCH 008/157] feat(Exchange): Implement gainers and losers methods --- .../java/edu/ntnu/idi/idatt/Exchange.java | 39 +++++++++++++++++++ .../java/edu/ntnu/idi/idatt/ExchangeTest.java | 18 +++++++++ 2 files changed, 57 insertions(+) diff --git a/src/main/java/edu/ntnu/idi/idatt/Exchange.java b/src/main/java/edu/ntnu/idi/idatt/Exchange.java index 2153bd9..f4a8d79 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt/Exchange.java @@ -8,6 +8,7 @@ import java.math.BigDecimal; import java.util.*; +import java.util.stream.Collectors; /** * Exchange class @@ -99,6 +100,44 @@ public List findStocks(String searchTerm) { return stocksFound; } + /** + * Method for obtaining gainers + * + *

+ * Returns the stocks that have done it the best + * in the latest week. + *

+ * + * @param limit - Amount of stocks to be returned. + * @return A list of stocks sorted in declining order. + */ + public List getGainers(int limit) { + return stockMap.values().stream() + .filter(stock -> stock.getLatestPriceChange().compareTo(BigDecimal.ZERO) > 0) + .sorted(Comparator.comparing(Stock::getLatestPriceChange).reversed()) + .limit(limit) + .toList(); + } + + /** + * Method for obtaining losers + * + *

+ * Returns the stocks that have done it the worst + * in value in the latest week. + *

+ * + * @param limit - Amount of stocks to be returned. + * @return A list of stocks sorted in inclining order. + */ + public List getLosers(int limit) { + return stockMap.values().stream() + .filter(stock -> stock.getLatestPriceChange().compareTo(BigDecimal.ZERO) < 0) + .sorted(Comparator.comparing(Stock::getLatestPriceChange)) + .limit(limit) + .toList(); + } + /** * Method to allow a player to buy a stock. * diff --git a/src/test/java/edu/ntnu/idi/idatt/ExchangeTest.java b/src/test/java/edu/ntnu/idi/idatt/ExchangeTest.java index 743f3be..64c4b35 100644 --- a/src/test/java/edu/ntnu/idi/idatt/ExchangeTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/ExchangeTest.java @@ -120,6 +120,24 @@ void PTAdvance() { } + /** + * Test for obtaining the gainers and loosers. + */ + @Test + void PTgetGainers_Losers() { + // Simulate price change + stocks.get(0).addNewSalesPrice(new BigDecimal("4333")); + stocks.get(1).addNewSalesPrice(new BigDecimal("50")); + stocks.get(2).addNewSalesPrice(new BigDecimal("3")); + stocks.get(3).addNewSalesPrice(new BigDecimal("800")); + + assertEquals(2, exchange.getGainers(2).size()); + assertEquals(2, exchange.getLosers(2).size()); + + assertEquals(List.of(stocks.get(0)), exchange.getGainers(1)); + assertEquals(List.of(stocks.get(2)), exchange.getLosers(1)); + } + /** * Negative tests for stock-related methods. * From 4c6750cd92356a19d52279af0f744cbbbf664958 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Wed, 25 Mar 2026 10:38:33 +0100 Subject: [PATCH 009/157] refactor(Portfolio): playerNetValue -> getNetWorth. Fix logic. --- .../java/edu/ntnu/idi/idatt/marked/Portfolio.java | 15 ++++++++------- .../edu/ntnu/idi/idatt/marked/PortfolioTest.java | 7 +++++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/marked/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt/marked/Portfolio.java index 6fe32fd..e377d5f 100644 --- a/src/main/java/edu/ntnu/idi/idatt/marked/Portfolio.java +++ b/src/main/java/edu/ntnu/idi/idatt/marked/Portfolio.java @@ -5,6 +5,8 @@ import java.util.List; import java.util.stream.Collectors; +import edu.ntnu.idi.idatt.calculator.SaleCalculator; + /** * Portfolio class * @@ -70,15 +72,14 @@ public boolean contains(Share share) { } /** - * Method for getting the net value of the player. + * Method for getting the net value of the portfolio.. * - * @return - Net value of the players shares. + * @return - Net value of the portfolio. */ - public BigDecimal playerNetValue(){ - BigDecimal netValue = shares.stream() - .map(Share-> Share.getPurchasePrice().multiply(Share.getQuantity())) - .reduce(BigDecimal.ZERO, BigDecimal::add); - return netValue; + public BigDecimal getNetWorth() { + return shares.stream() + .map(s -> new SaleCalculator(s).calculateTotal()) + .reduce(BigDecimal.ZERO, BigDecimal::add); } } diff --git a/src/test/java/edu/ntnu/idi/idatt/marked/PortfolioTest.java b/src/test/java/edu/ntnu/idi/idatt/marked/PortfolioTest.java index a2de1de..e3a6898 100644 --- a/src/test/java/edu/ntnu/idi/idatt/marked/PortfolioTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/marked/PortfolioTest.java @@ -11,6 +11,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import edu.ntnu.idi.idatt.calculator.SaleCalculator; + public class PortfolioTest { private Stock stock; @@ -82,12 +84,13 @@ void NTremoveShare() { * Positive test for finding net value of the players shares. */ @Test - void PTplayerNetValue(){ + void PTgetNetWorth() { Share share1 = new Share(stock, new BigDecimal("1"), new BigDecimal("135.8")); Share share2 = new Share(stock, new BigDecimal("2"), new BigDecimal("254")); portfolio.addShare(share1); portfolio.addShare(share2); - assertEquals(new BigDecimal("643.8"), portfolio.playerNetValue()); + assertEquals(new SaleCalculator(share1).calculateTotal().add(new SaleCalculator(share2).calculateTotal()), + portfolio.getNetWorth()); } } From d1ae6aed58174fb97fd6892474a917210376dcf0 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Wed, 25 Mar 2026 10:48:44 +0100 Subject: [PATCH 010/157] feat(TransactionArchive): Implement TODO countDistinctWeeks() and corresponding tests. --- .../idi/idatt/transaction/TransactionArchive.java | 7 +++++-- .../idatt/transaction/TransactionArchiveTest.java | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/transaction/TransactionArchive.java b/src/main/java/edu/ntnu/idi/idatt/transaction/TransactionArchive.java index 0567591..e07e8c9 100644 --- a/src/main/java/edu/ntnu/idi/idatt/transaction/TransactionArchive.java +++ b/src/main/java/edu/ntnu/idi/idatt/transaction/TransactionArchive.java @@ -73,8 +73,11 @@ public List getSales(int week) { * * @return */ - public int countDistinctWeeks() { // TODO: HERE - return -1; + public int countDistinctWeeks() { + return (int) transactions.stream() + .map(Transaction::getWeek) + .distinct() + .count(); } } diff --git a/src/test/java/edu/ntnu/idi/idatt/transaction/TransactionArchiveTest.java b/src/test/java/edu/ntnu/idi/idatt/transaction/TransactionArchiveTest.java index 482a45c..74930fb 100644 --- a/src/test/java/edu/ntnu/idi/idatt/transaction/TransactionArchiveTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/transaction/TransactionArchiveTest.java @@ -87,4 +87,18 @@ void PTgetTransactions() { } + /** + * Tests for countDistinctWeeks(). + */ + @Test + void PTcountDistinctWeeks() { + assertEquals(2, transactionArchive.countDistinctWeeks()); + } + + @Test + void NTcountDistinctWeeks() { + TransactionArchive tArchive = new TransactionArchive(); + assertEquals(0, tArchive.countDistinctWeeks()); + } + } From fe55a8eb0177f948428ad2b707f74e70b38e71bf Mon Sep 17 00:00:00 2001 From: pawelsa Date: Wed, 25 Mar 2026 11:10:47 +0100 Subject: [PATCH 011/157] feat(Player): Implement getNetWorth and getStatus. Add corresponding tests. --- src/main/java/edu/ntnu/idi/idatt/Player.java | 37 ++++++++++++++ .../java/edu/ntnu/idi/idatt/PlayerTest.java | 50 +++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/main/java/edu/ntnu/idi/idatt/Player.java b/src/main/java/edu/ntnu/idi/idatt/Player.java index 3a5e61d..8f798e7 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Player.java +++ b/src/main/java/edu/ntnu/idi/idatt/Player.java @@ -4,6 +4,8 @@ import edu.ntnu.idi.idatt.transaction.TransactionArchive; import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Map; public class Player { @@ -54,4 +56,39 @@ public void addMoney(BigDecimal amount) { public void withdrawMoney(BigDecimal amount) { this.money = this.money.subtract(amount); } + + /** + * Method for obtaining players net worth. + * + * @return players net worth. + */ + public BigDecimal getNetWorth() { + return money.add(portfolio.getNetWorth()); + } + + /** + * Method for obtaining players trading status. + * + *

+ * A symbolic title/rank that describes the way the + * players trading career has been going. + *

+ * + * @return String of corresponding title. TODO: Change to ENUM!!! + */ + public String getStatus() { + int tradingWeeks = transactionArchive.countDistinctWeeks(); + BigDecimal netWorth = this.getNetWorth().divide(this.startingMoney); + + if (tradingWeeks >= 20 && netWorth.compareTo(new BigDecimal("2")) >= 0) { + return "Speculator"; + } + + if (tradingWeeks >= 10 && netWorth.compareTo(new BigDecimal("1.2")) >= 0) { + return "Investor"; + } + + return "Novice"; + } + } diff --git a/src/test/java/edu/ntnu/idi/idatt/PlayerTest.java b/src/test/java/edu/ntnu/idi/idatt/PlayerTest.java index cd6df5d..e292971 100644 --- a/src/test/java/edu/ntnu/idi/idatt/PlayerTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/PlayerTest.java @@ -4,10 +4,15 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import java.math.BigDecimal; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import edu.ntnu.idi.idatt.marked.Share; +import edu.ntnu.idi.idatt.marked.Stock; +import edu.ntnu.idi.idatt.transaction.Purchase; + class PlayerTest { private Player player; @@ -41,4 +46,49 @@ void PTwithdrawMoney() { assertEquals(new BigDecimal("300"), player.getMoney()); } + @Test + void PTgetNetWorth() { + assertEquals(player.getNetWorth(), new BigDecimal("500")); + + // Add to player portfolio + Stock stock = new Stock("A", "A", List.of(new BigDecimal("20"))); + Share share = new Share(stock, new BigDecimal("3.5"), new BigDecimal("30")); + player.getPortfolio().addShare(share); + + BigDecimal sum = player.getMoney().add(player.getPortfolio().getNetWorth()); + assertEquals(sum, player.getNetWorth()); + + } + + @Test + void PTgetStatus() { + assertEquals("Novice", player.getStatus()); + + // Simulate progress + Stock stock = new Stock("A", "A", List.of(new BigDecimal("20"))); + Share share = new Share(stock, new BigDecimal("3.5"), new BigDecimal("30")); + + for (int i = 0; i <= 20; i++) { + player.getTransactionArchive().add(new Purchase(share, i)); + } + + // Check different ifs + player.addMoney(new BigDecimal("90")); // 18% + assertEquals("Novice", player.getStatus()); + player.addMoney(new BigDecimal("10")); // 20% + assertEquals("Investor", player.getStatus()); + player.addMoney(new BigDecimal("400")); // 100% + assertEquals("Speculator", player.getStatus()); + + // new Player to check if Speculator dont happen week < 20 + Player plr = new Player("plr", new BigDecimal("500")); + + for (int i = 0; i <= 15; i++) { + plr.getTransactionArchive().add(new Purchase(share, i)); + } + plr.addMoney(new BigDecimal("600")); // 120% + assertEquals("Investor", plr.getStatus()); + + } + } From 559489f1e03dc4df533d4e572075077b6e80acef Mon Sep 17 00:00:00 2001 From: pawelsa Date: Wed, 25 Mar 2026 16:46:25 +0100 Subject: [PATCH 012/157] feat: Implement file handeling system --- .../ntnu/idi/idatt/file/ExchangeLoader.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java diff --git a/src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java b/src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java new file mode 100644 index 0000000..5f50a6a --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java @@ -0,0 +1,100 @@ +package edu.ntnu.idi.idatt.file; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.math.BigDecimal; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import edu.ntnu.idi.idatt.marked.Stock; + +public class ExchangeLoader { + + private final File file; + + /** + * Constructors for ExchangeLoader + * + *

+ * Utilizes method overloading for different types + * of path formatting. + *

+ * + * @throws IllegalArgumentException if specified path doesn't exist. + */ + protected ExchangeLoader(String path) { + file = new File(path); + if (!file.exists()) { + throw new IllegalArgumentException("File at this path doesn't exist!"); + } + } + + protected ExchangeLoader(URL path) { + file = new File(path.toString()); + if (!file.exists()) { + throw new IllegalArgumentException("File at this path doesn't exist!"); + } + } + + /** + * Method for loading from stocks from file + * + * @return List of loaded stocks. + * @throws IOException on BufferedReader error + */ + protected List load() throws IOException { + + ArrayList stocks = new ArrayList<>(); + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + List stockStringList = new ArrayList<>(reader.readAllLines()); + + // Remove comments + stockStringList.removeIf(s -> s.isBlank()); + stockStringList.removeIf(s -> s.startsWith("#")); + + for (String stockString : stockStringList) { + String[] stockValues = stockString.split(","); + + // TODO: Loading all historical prices not the recent, saved one. + Stock stock = new Stock(stockValues[0], stockValues[1], List.of(new BigDecimal(stockValues[2]))); + stocks.add(stock); + } + + } catch (IOException e) { + throw new IOException("File loading failed!"); + } + + return stocks; + + } + + /** + * Method for saving stocks to file. + * + * @param stocks The destined list to be saved. + * @throws IOException on BufferedWriter error. + */ + protected void save(List stocks) throws IOException { + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + + for (Stock stock : stocks) { + String data = stock.getSymbol() + "," + stock.getCompany() + "," + + stock.getHistoricalPrices().getLast().toString(); + writer.write(data); + writer.newLine(); + } + + } catch (IOException e) { + throw new IOException("File saving failed!"); + } + + } + +} From 42f52af73914f9731a5b3ee395344a7f10c3779a Mon Sep 17 00:00:00 2001 From: pawelsa Date: Wed, 25 Mar 2026 16:47:08 +0100 Subject: [PATCH 013/157] chore(ExchangeLoader): Implement tests. --- .../idi/idatt/file/ExchangeLoaderTest.java | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/test/java/edu/ntnu/idi/idatt/file/ExchangeLoaderTest.java diff --git a/src/test/java/edu/ntnu/idi/idatt/file/ExchangeLoaderTest.java b/src/test/java/edu/ntnu/idi/idatt/file/ExchangeLoaderTest.java new file mode 100644 index 0000000..94f5617 --- /dev/null +++ b/src/test/java/edu/ntnu/idi/idatt/file/ExchangeLoaderTest.java @@ -0,0 +1,103 @@ +package edu.ntnu.idi.idatt.file; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import edu.ntnu.idi.idatt.marked.Stock; + +/** + * Test class for ExchangeLoader + * + *

+ * Tests the loading and saving of stock data. + *

+ */ +class ExchangeLoaderTest { + + private ExchangeLoader loader; + private List exampleStocks; + + @BeforeEach + public void PT_setup() throws IOException { + + InputStream is = getClass() + .getClassLoader() + .getResourceAsStream("stocks.csv"); + + Path tempFile = Files.createTempFile("stocks", ".csv"); + Files.copy(is, tempFile, StandardCopyOption.REPLACE_EXISTING); + + loader = new ExchangeLoader(tempFile.toFile().toPath().toString()); + + Stock AAPL = new Stock("AAPL", "Apple Inc.", List.of(new BigDecimal("32"))); + Stock NVDA = new Stock("NVDA", "NVIDIA", List.of(new BigDecimal("182.81"))); + Stock TSLA = new Stock("TSLA", "Tesla", List.of(new BigDecimal("417.44"))); + Stock AMD = new Stock("AMD", "Advanced Micro Devices", List.of(new BigDecimal("207.32"))); + + exampleStocks = List.of(AAPL, NVDA, TSLA, AMD); + } + + /** + * Positive test for loading/reading stocks + */ + @Test + void PT_load() { + List stocks = null; + try { + stocks = loader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + assertEquals(4, stocks.size()); + + } + + /** + * Positive test for saving stocks. + */ + @Test + void PT_save() { + exampleStocks.get(3).addNewSalesPrice(new BigDecimal("99999")); + + // Save + + try { + loader.save(exampleStocks); + } catch (IOException e) { + e.printStackTrace(); + } + + // Try to read again + List stocks = null; + try { + stocks = loader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + assertEquals(new BigDecimal("99999"), stocks.get(3).getSalesPrice()); + + } + + /** + * Negative tests for constructor + */ + @Test + void NT_IllegalArgumentException_Constructor() { + assertThrows(IllegalArgumentException.class, + () -> new ExchangeLoader("resources/notexistantfile.csv")); + } + +} From f8e88a5b59e03e3055f9517d7cb2c92f2a138e14 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Wed, 25 Mar 2026 16:47:40 +0100 Subject: [PATCH 014/157] refactor: Add a stocks.csv test file to test/resources --- src/test/resources/stocks.csv | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/test/resources/stocks.csv diff --git a/src/test/resources/stocks.csv b/src/test/resources/stocks.csv new file mode 100644 index 0000000..275ddb0 --- /dev/null +++ b/src/test/resources/stocks.csv @@ -0,0 +1,7 @@ +# Ticker,Name,Price +AAPL,Apple Inc,32 +NVDA,NVIDIA,182.81 +TSLA,Tesla,417.44 +AMD,Advanced Micro Devices,207.32 + + From 2c6a8c875b44fa6ce947349972c4df3e08f65906 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Wed, 25 Mar 2026 16:48:21 +0100 Subject: [PATCH 015/157] refactor(Exchange): Implement stock loading from file. Test class refactor. --- .../java/edu/ntnu/idi/idatt/Exchange.java | 24 ++++++++-- .../java/edu/ntnu/idi/idatt/ExchangeTest.java | 44 +++++++++++++------ 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/Exchange.java b/src/main/java/edu/ntnu/idi/idatt/Exchange.java index f4a8d79..7214ffa 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt/Exchange.java @@ -1,11 +1,13 @@ package edu.ntnu.idi.idatt; +import edu.ntnu.idi.idatt.file.ExchangeLoader; import edu.ntnu.idi.idatt.marked.Share; import edu.ntnu.idi.idatt.marked.Stock; import edu.ntnu.idi.idatt.transaction.Purchase; import edu.ntnu.idi.idatt.transaction.Sale; import edu.ntnu.idi.idatt.transaction.Transaction; +import java.io.IOException; import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; @@ -20,7 +22,7 @@ *

* */ -public class Exchange { +public class Exchange extends ExchangeLoader { private final String name; private int week; @@ -33,12 +35,15 @@ public class Exchange { * @param name - Name of the current stock Exchange * @param stocks - List of aviable stocks for this exchange. */ - public Exchange(String name, List stocks) { + public Exchange(String name, String path) { + super(path); this.name = name; this.week = 1; - for (Stock stock : stocks) { - stockMap.put(stock.getSymbol(), stock); + try { + this.load().forEach(stock -> stockMap.put(stock.getSymbol(), stock)); + } catch (IOException e) { + throw new IllegalArgumentException("Problem loading [" + name + "] exchange : " + e); } } @@ -58,6 +63,10 @@ public int getWeek() { return week; } + public List getStocks() { + return stockMap.values().stream().toList(); + } + /** * Method for checking if a specific stock exists in the exchange. * @@ -194,6 +203,13 @@ public Transaction sell(Share share, Player player) { public void advance() { for (Stock stocks : stockMap.values()) { stocks.addNewSalesPrice(BigDecimal.valueOf(random.nextDouble())); + + // TODO: Move this to JavaFx on Window close? + try { + this.save(stockMap.values().stream().toList()); + } catch (IOException e) { + throw new IllegalArgumentException("Problem loading [" + name + "] exchange : " + e); + } } } diff --git a/src/test/java/edu/ntnu/idi/idatt/ExchangeTest.java b/src/test/java/edu/ntnu/idi/idatt/ExchangeTest.java index 64c4b35..02cb3d7 100644 --- a/src/test/java/edu/ntnu/idi/idatt/ExchangeTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/ExchangeTest.java @@ -5,7 +5,12 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; @@ -17,22 +22,37 @@ class ExchangeTest { + /** + *

+ * Caution! + * stocks List is not sorted correctly. + * We parse it with hashMap.values().stream().toList + * which dont properly arrange it in memory. + * Take caution when programming or reading here. + * + * @see PTgetGainers_Losers don't take the get(index) literally + * but focus on the differences of last sale value made. + * + * This is a work around for implementing the ExchangeLoader to Exchange + * class. + *

+ */ private Exchange exchange; private List stocks; private Player player; @BeforeEach - public void PT_setup() { + public void PT_setup() throws IOException { - // Initialize exchange with proper objects - Stock AAPL = new Stock("AAPL", "Apple Inc.", List.of(new BigDecimal("32"))); - Stock NVDA = new Stock("NVDA", "NVIDIA", List.of(new BigDecimal("182.81"))); - Stock TSLA = new Stock("TSLA", "Tesla", List.of(new BigDecimal("417.44"))); - Stock AMD = new Stock("AMD", "Advanced Micro Devices", List.of(new BigDecimal("207.32"))); + InputStream is = getClass() + .getClassLoader() + .getResourceAsStream("stocks.csv"); - stocks = List.of(AAPL, NVDA, TSLA, AMD); + Path tempFile = Files.createTempFile("stocks", ".csv"); + Files.copy(is, tempFile, StandardCopyOption.REPLACE_EXISTING); - exchange = new Exchange("TestExchange", stocks); + exchange = new Exchange("TestExchange", tempFile.toFile().toPath().toString()); + stocks = exchange.getStocks(); player = new Player("TestPlayer", new BigDecimal("500")); } @@ -55,12 +75,10 @@ void PTConstructor() { void PTFindStock() { assertTrue(exchange.hasStock("AAPL")); - assertEquals(stocks.get(0) /* AAPL Stock */, exchange.getStock("AAPL")); + assertTrue(stocks.contains(exchange.getStock("AAPL"))); // FindStocks for letter "n" should be - AAPL, AMD (both symbols and names!) - List expected = List.of(stocks.get(0), stocks.get(3)); - // - assertEquals(expected, exchange.findStocks("n")); + assertEquals(2, exchange.findStocks("n").size()); } @@ -127,7 +145,7 @@ void PTAdvance() { void PTgetGainers_Losers() { // Simulate price change stocks.get(0).addNewSalesPrice(new BigDecimal("4333")); - stocks.get(1).addNewSalesPrice(new BigDecimal("50")); + stocks.get(1).addNewSalesPrice(new BigDecimal("10")); stocks.get(2).addNewSalesPrice(new BigDecimal("3")); stocks.get(3).addNewSalesPrice(new BigDecimal("800")); From f1a18762ab69ca626e3640d92d1854c4db7b3e29 Mon Sep 17 00:00:00 2001 From: danieskj Date: Mon, 13 Apr 2026 12:07:06 +0200 Subject: [PATCH 016/157] Added design pattern MVC --- src/main/java/edu/ntnu/idi/idatt/View/main_page.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/View/main_page.java diff --git a/src/main/java/edu/ntnu/idi/idatt/View/main_page.java b/src/main/java/edu/ntnu/idi/idatt/View/main_page.java new file mode 100644 index 0000000..2ee12f8 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/View/main_page.java @@ -0,0 +1,4 @@ +package edu.ntnu.idi.idatt.View; + +public class main_page { +} From 1ea81de4e631832dd229e3f2186c45ce0922767c Mon Sep 17 00:00:00 2001 From: danieskj Date: Fri, 24 Apr 2026 12:46:41 +0200 Subject: [PATCH 017/157] feat: Added observer, controller and view --- .../ntnu/idi/idatt/Controller/exchangePageController.java | 4 ++++ .../edu/ntnu/idi/idatt/Controller/mainPageController.java | 4 ++++ src/main/java/edu/ntnu/idi/idatt/Observer.java | 6 ++++++ src/main/java/edu/ntnu/idi/idatt/View/exchangePage.java | 4 ++++ .../ntnu/idi/idatt/View/{main_page.java => mainPage.java} | 2 +- 5 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/main/java/edu/ntnu/idi/idatt/Controller/exchangePageController.java create mode 100644 src/main/java/edu/ntnu/idi/idatt/Controller/mainPageController.java create mode 100644 src/main/java/edu/ntnu/idi/idatt/Observer.java create mode 100644 src/main/java/edu/ntnu/idi/idatt/View/exchangePage.java rename src/main/java/edu/ntnu/idi/idatt/View/{main_page.java => mainPage.java} (59%) diff --git a/src/main/java/edu/ntnu/idi/idatt/Controller/exchangePageController.java b/src/main/java/edu/ntnu/idi/idatt/Controller/exchangePageController.java new file mode 100644 index 0000000..4f8c5cc --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/Controller/exchangePageController.java @@ -0,0 +1,4 @@ +package edu.ntnu.idi.idatt.Controller; + +public class exchangePageController { +} diff --git a/src/main/java/edu/ntnu/idi/idatt/Controller/mainPageController.java b/src/main/java/edu/ntnu/idi/idatt/Controller/mainPageController.java new file mode 100644 index 0000000..c1162c7 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/Controller/mainPageController.java @@ -0,0 +1,4 @@ +package edu.ntnu.idi.idatt.Controller; + +public class mainPageController { +} diff --git a/src/main/java/edu/ntnu/idi/idatt/Observer.java b/src/main/java/edu/ntnu/idi/idatt/Observer.java new file mode 100644 index 0000000..24b264b --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/Observer.java @@ -0,0 +1,6 @@ +package edu.ntnu.idi.idatt; + +public interface Observer { + + void update(); +} \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt/View/exchangePage.java b/src/main/java/edu/ntnu/idi/idatt/View/exchangePage.java new file mode 100644 index 0000000..d274afe --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/View/exchangePage.java @@ -0,0 +1,4 @@ +package edu.ntnu.idi.idatt.View; + +public class exchangePage { +} diff --git a/src/main/java/edu/ntnu/idi/idatt/View/main_page.java b/src/main/java/edu/ntnu/idi/idatt/View/mainPage.java similarity index 59% rename from src/main/java/edu/ntnu/idi/idatt/View/main_page.java rename to src/main/java/edu/ntnu/idi/idatt/View/mainPage.java index 2ee12f8..733d0cb 100644 --- a/src/main/java/edu/ntnu/idi/idatt/View/main_page.java +++ b/src/main/java/edu/ntnu/idi/idatt/View/mainPage.java @@ -1,4 +1,4 @@ package edu.ntnu.idi.idatt.View; -public class main_page { +public class mainPage { } From 0a183c53fa855ddeb35791a6eb13f43b40e162d0 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 16:42:37 +0200 Subject: [PATCH 018/157] refactor: Delete redundant dependencies. Add JaCoCo, Site and JavaDocs plugins. --- pom.xml | 95 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/pom.xml b/pom.xml index bff6a52..15e1d8c 100644 --- a/pom.xml +++ b/pom.xml @@ -33,24 +33,6 @@ 25.0.1 - - org.openjfx - javafx-fxml - 25.0.1 - - - - org.openjfx - javafx-web - 25.0.1 - - - - org.openjfx - javafx-swing - 25.0.1 - - org.openjfx javafx-media @@ -62,9 +44,9 @@ junit-jupiter 6.0.1 test - + - + @@ -86,28 +68,57 @@ - - org.codehaus.mojo - exec-maven-plugin - 3.6.3 - - edu.ntnu.idi.idatt.Main - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.3.0 - - - - true - edu.ntnu.idi.idatt.Main - - - - + + org.apache.maven.plugins + maven-javadoc-plugin + 3.12.0 + + + + + org.jacoco + jacoco-maven-plugin + 0.8.14 + + + + + prepare-agent + + prepare-agent + + + + + + report + verify + + report + + + + + + + org.apache.maven.plugins + maven-site-plugin + 4.0.0-M9 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + true + edu.ntnu.idi.idatt.Launcher + + + + From 2f0646e316a36242083cff3e0c5049380abaaf98 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 16:43:21 +0200 Subject: [PATCH 019/157] refactor: A formal entry point for the application. Main.class -> Launcher.class --- .../java/edu/ntnu/idi/idatt/Launcher.java | 30 +++++++++++++++++++ src/main/java/edu/ntnu/idi/idatt/Main.java | 11 ------- 2 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 src/main/java/edu/ntnu/idi/idatt/Launcher.java delete mode 100644 src/main/java/edu/ntnu/idi/idatt/Main.java diff --git a/src/main/java/edu/ntnu/idi/idatt/Launcher.java b/src/main/java/edu/ntnu/idi/idatt/Launcher.java new file mode 100644 index 0000000..601d11e --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/Launcher.java @@ -0,0 +1,30 @@ +package edu.ntnu.idi.idatt; + +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +public final class Launcher { + + /** + * Entry point of application. + */ + static void main() { + Application.launch(StockGame.class); + } + + public static final class StockGame extends Application { + + @Override + public void start(Stage stage) { + stage.setWidth(1200); + stage.setHeight(700); + stage.setTitle("Stock Game"); + stage.setScene(new Scene(new VBox())); + stage.show(); + } + + } + +} diff --git a/src/main/java/edu/ntnu/idi/idatt/Main.java b/src/main/java/edu/ntnu/idi/idatt/Main.java deleted file mode 100644 index a7ee785..0000000 --- a/src/main/java/edu/ntnu/idi/idatt/Main.java +++ /dev/null @@ -1,11 +0,0 @@ -package edu.ntnu.idi.idatt; - -public class Main { - - // Logger system !! - - static void main() { - System.out.println("Hello world!"); - } - -} From fcfcdeb6dbfeacc47231c93bc8435b3afff62921 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 16:44:02 +0200 Subject: [PATCH 020/157] fix: Improper JavaDocs --- src/main/java/edu/ntnu/idi/idatt/Exchange.java | 6 +++--- src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/Exchange.java b/src/main/java/edu/ntnu/idi/idatt/Exchange.java index 7214ffa..74a7dbc 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt/Exchange.java @@ -32,8 +32,8 @@ public class Exchange extends ExchangeLoader { /** * Constructor for Exchange class * - * @param name - Name of the current stock Exchange - * @param stocks - List of aviable stocks for this exchange. + * @param name - Name of the current stock Exchange + * @param path - Path to .csv file. */ public Exchange(String name, String path) { super(path); @@ -180,7 +180,7 @@ public Transaction buy(String symbol, BigDecimal quantity, Player player) { * * @see Sale * - * @param Share - The instance of the sold share. + * @param share - The instance of the sold share. * @param player - which player did this event. * @return The given transaction details. (Transaction). * @see Transaction diff --git a/src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java b/src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java index 5f50a6a..d4d9ea7 100644 --- a/src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java +++ b/src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java @@ -44,7 +44,7 @@ protected ExchangeLoader(URL path) { /** * Method for loading from stocks from file * - * @return List of loaded stocks. + * @return a list of loaded stocks. * @throws IOException on BufferedReader error */ protected List load() throws IOException { From 608ed604678dca631fa132c10b4e22168e5ec2b4 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 16:47:45 +0200 Subject: [PATCH 021/157] chore: Update CD/CI Pipeline for automization for Github Pages deployment. --- .github/workflows/maven.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 71d6ce6..ee9c969 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -25,3 +25,13 @@ jobs: - name: Test with Maven run: mvn -B test --file pom.xml + - name: Build site + JavaDocs & JaCoCo + run: mvn clean verify site + + - name: Deploy to Github Pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: target/site/ + publish_branch: gh-pages + From 89b8c4f20e0cd4f8c835f2eb5dbcce0a3465eb94 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 17:13:07 +0200 Subject: [PATCH 022/157] refactor: Structured packages. Manually remap imports. --- .../Controller/exchangePageController.java | 4 ---- .../idatt/Controller/mainPageController.java | 4 ---- src/main/java/edu/ntnu/idi/idatt/Observer.java | 6 ------ .../edu/ntnu/idi/idatt/View/exchangePage.java | 4 ---- .../java/edu/ntnu/idi/idatt/View/mainPage.java | 4 ---- .../edu/ntnu/idi/idatt/common/Observer.java | 6 ++++++ .../ntnu/idi/idatt/{ => model}/Exchange.java | 18 +++++++++--------- .../idatt/{marked => model/market}/Stock.java | 2 +- .../idi/idatt/{ => model/player}/Player.java | 8 +++----- .../{marked => model/portfolio}/Portfolio.java | 5 ++--- .../{marked => model/portfolio}/Share.java | 4 +++- .../{ => model}/transaction/Purchase.java | 8 ++++---- .../idatt/{ => model}/transaction/Sale.java | 8 ++++---- .../{ => model}/transaction/Transaction.java | 8 ++++---- .../transaction/TransactionArchive.java | 2 +- .../transaction}/PurchaseCalculator.java | 6 +++--- .../transaction}/SaleCalculator.java | 4 ++-- .../transaction}/TransactionCalculator.java | 2 +- .../{file => storage}/ExchangeLoader.java | 4 ++-- 19 files changed, 45 insertions(+), 62 deletions(-) delete mode 100644 src/main/java/edu/ntnu/idi/idatt/Controller/exchangePageController.java delete mode 100644 src/main/java/edu/ntnu/idi/idatt/Controller/mainPageController.java delete mode 100644 src/main/java/edu/ntnu/idi/idatt/Observer.java delete mode 100644 src/main/java/edu/ntnu/idi/idatt/View/exchangePage.java delete mode 100644 src/main/java/edu/ntnu/idi/idatt/View/mainPage.java create mode 100644 src/main/java/edu/ntnu/idi/idatt/common/Observer.java rename src/main/java/edu/ntnu/idi/idatt/{ => model}/Exchange.java (93%) rename src/main/java/edu/ntnu/idi/idatt/{marked => model/market}/Stock.java (98%) rename src/main/java/edu/ntnu/idi/idatt/{ => model/player}/Player.java (91%) rename src/main/java/edu/ntnu/idi/idatt/{marked => model/portfolio}/Portfolio.java (93%) rename src/main/java/edu/ntnu/idi/idatt/{marked => model/portfolio}/Share.java (91%) rename src/main/java/edu/ntnu/idi/idatt/{ => model}/transaction/Purchase.java (79%) rename src/main/java/edu/ntnu/idi/idatt/{ => model}/transaction/Sale.java (79%) rename src/main/java/edu/ntnu/idi/idatt/{ => model}/transaction/Transaction.java (85%) rename src/main/java/edu/ntnu/idi/idatt/{ => model}/transaction/TransactionArchive.java (97%) rename src/main/java/edu/ntnu/idi/idatt/{calculator => service/transaction}/PurchaseCalculator.java (93%) rename src/main/java/edu/ntnu/idi/idatt/{calculator => service/transaction}/SaleCalculator.java (95%) rename src/main/java/edu/ntnu/idi/idatt/{calculator => service/transaction}/TransactionCalculator.java (92%) rename src/main/java/edu/ntnu/idi/idatt/{file => storage}/ExchangeLoader.java (96%) diff --git a/src/main/java/edu/ntnu/idi/idatt/Controller/exchangePageController.java b/src/main/java/edu/ntnu/idi/idatt/Controller/exchangePageController.java deleted file mode 100644 index 4f8c5cc..0000000 --- a/src/main/java/edu/ntnu/idi/idatt/Controller/exchangePageController.java +++ /dev/null @@ -1,4 +0,0 @@ -package edu.ntnu.idi.idatt.Controller; - -public class exchangePageController { -} diff --git a/src/main/java/edu/ntnu/idi/idatt/Controller/mainPageController.java b/src/main/java/edu/ntnu/idi/idatt/Controller/mainPageController.java deleted file mode 100644 index c1162c7..0000000 --- a/src/main/java/edu/ntnu/idi/idatt/Controller/mainPageController.java +++ /dev/null @@ -1,4 +0,0 @@ -package edu.ntnu.idi.idatt.Controller; - -public class mainPageController { -} diff --git a/src/main/java/edu/ntnu/idi/idatt/Observer.java b/src/main/java/edu/ntnu/idi/idatt/Observer.java deleted file mode 100644 index 24b264b..0000000 --- a/src/main/java/edu/ntnu/idi/idatt/Observer.java +++ /dev/null @@ -1,6 +0,0 @@ -package edu.ntnu.idi.idatt; - -public interface Observer { - - void update(); -} \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt/View/exchangePage.java b/src/main/java/edu/ntnu/idi/idatt/View/exchangePage.java deleted file mode 100644 index d274afe..0000000 --- a/src/main/java/edu/ntnu/idi/idatt/View/exchangePage.java +++ /dev/null @@ -1,4 +0,0 @@ -package edu.ntnu.idi.idatt.View; - -public class exchangePage { -} diff --git a/src/main/java/edu/ntnu/idi/idatt/View/mainPage.java b/src/main/java/edu/ntnu/idi/idatt/View/mainPage.java deleted file mode 100644 index 733d0cb..0000000 --- a/src/main/java/edu/ntnu/idi/idatt/View/mainPage.java +++ /dev/null @@ -1,4 +0,0 @@ -package edu.ntnu.idi.idatt.View; - -public class mainPage { -} diff --git a/src/main/java/edu/ntnu/idi/idatt/common/Observer.java b/src/main/java/edu/ntnu/idi/idatt/common/Observer.java new file mode 100644 index 0000000..ba340a4 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/common/Observer.java @@ -0,0 +1,6 @@ +package edu.ntnu.idi.idatt.common; + +public interface Observer { + + void update(); +} diff --git a/src/main/java/edu/ntnu/idi/idatt/Exchange.java b/src/main/java/edu/ntnu/idi/idatt/model/Exchange.java similarity index 93% rename from src/main/java/edu/ntnu/idi/idatt/Exchange.java rename to src/main/java/edu/ntnu/idi/idatt/model/Exchange.java index 74a7dbc..84d2d9d 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/Exchange.java @@ -1,16 +1,16 @@ -package edu.ntnu.idi.idatt; - -import edu.ntnu.idi.idatt.file.ExchangeLoader; -import edu.ntnu.idi.idatt.marked.Share; -import edu.ntnu.idi.idatt.marked.Stock; -import edu.ntnu.idi.idatt.transaction.Purchase; -import edu.ntnu.idi.idatt.transaction.Sale; -import edu.ntnu.idi.idatt.transaction.Transaction; +package edu.ntnu.idi.idatt.model; import java.io.IOException; import java.math.BigDecimal; import java.util.*; -import java.util.stream.Collectors; + +import edu.ntnu.idi.idatt.model.market.Stock; +import edu.ntnu.idi.idatt.model.player.Player; +import edu.ntnu.idi.idatt.model.portfolio.Share; +import edu.ntnu.idi.idatt.model.transaction.Purchase; +import edu.ntnu.idi.idatt.model.transaction.Sale; +import edu.ntnu.idi.idatt.model.transaction.Transaction; +import edu.ntnu.idi.idatt.storage.ExchangeLoader; /** * Exchange class diff --git a/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java b/src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java similarity index 98% rename from src/main/java/edu/ntnu/idi/idatt/marked/Stock.java rename to src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java index d84e7bb..88bdfeb 100644 --- a/src/main/java/edu/ntnu/idi/idatt/marked/Stock.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.marked; +package edu.ntnu.idi.idatt.model.market; import java.math.BigDecimal; import java.util.ArrayList; diff --git a/src/main/java/edu/ntnu/idi/idatt/Player.java b/src/main/java/edu/ntnu/idi/idatt/model/player/Player.java similarity index 91% rename from src/main/java/edu/ntnu/idi/idatt/Player.java rename to src/main/java/edu/ntnu/idi/idatt/model/player/Player.java index 8f798e7..f75b184 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Player.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/player/Player.java @@ -1,11 +1,9 @@ -package edu.ntnu.idi.idatt; +package edu.ntnu.idi.idatt.model.player; -import edu.ntnu.idi.idatt.marked.Portfolio; -import edu.ntnu.idi.idatt.transaction.TransactionArchive; +import edu.ntnu.idi.idatt.model.portfolio.Portfolio; +import edu.ntnu.idi.idatt.model.transaction.TransactionArchive; import java.math.BigDecimal; -import java.util.HashSet; -import java.util.Map; public class Player { diff --git a/src/main/java/edu/ntnu/idi/idatt/marked/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java similarity index 93% rename from src/main/java/edu/ntnu/idi/idatt/marked/Portfolio.java rename to src/main/java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java index e377d5f..0b9e940 100644 --- a/src/main/java/edu/ntnu/idi/idatt/marked/Portfolio.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java @@ -1,11 +1,10 @@ -package edu.ntnu.idi.idatt.marked; +package edu.ntnu.idi.idatt.model.portfolio; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; -import edu.ntnu.idi.idatt.calculator.SaleCalculator; +import edu.ntnu.idi.idatt.service.transaction.SaleCalculator; /** * Portfolio class diff --git a/src/main/java/edu/ntnu/idi/idatt/marked/Share.java b/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Share.java similarity index 91% rename from src/main/java/edu/ntnu/idi/idatt/marked/Share.java rename to src/main/java/edu/ntnu/idi/idatt/model/portfolio/Share.java index 0cc7432..2bbaf0f 100644 --- a/src/main/java/edu/ntnu/idi/idatt/marked/Share.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Share.java @@ -1,7 +1,9 @@ -package edu.ntnu.idi.idatt.marked; +package edu.ntnu.idi.idatt.model.portfolio; import java.math.BigDecimal; +import edu.ntnu.idi.idatt.model.market.Stock; + /** * Share class * diff --git a/src/main/java/edu/ntnu/idi/idatt/transaction/Purchase.java b/src/main/java/edu/ntnu/idi/idatt/model/transaction/Purchase.java similarity index 79% rename from src/main/java/edu/ntnu/idi/idatt/transaction/Purchase.java rename to src/main/java/edu/ntnu/idi/idatt/model/transaction/Purchase.java index ac9152f..6423f5c 100644 --- a/src/main/java/edu/ntnu/idi/idatt/transaction/Purchase.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/transaction/Purchase.java @@ -1,8 +1,8 @@ -package edu.ntnu.idi.idatt.transaction; +package edu.ntnu.idi.idatt.model.transaction; -import edu.ntnu.idi.idatt.Player; -import edu.ntnu.idi.idatt.calculator.PurchaseCalculator; -import edu.ntnu.idi.idatt.marked.Share; +import edu.ntnu.idi.idatt.model.player.Player; +import edu.ntnu.idi.idatt.model.portfolio.Share; +import edu.ntnu.idi.idatt.service.transaction.PurchaseCalculator; /** * Purchase class diff --git a/src/main/java/edu/ntnu/idi/idatt/transaction/Sale.java b/src/main/java/edu/ntnu/idi/idatt/model/transaction/Sale.java similarity index 79% rename from src/main/java/edu/ntnu/idi/idatt/transaction/Sale.java rename to src/main/java/edu/ntnu/idi/idatt/model/transaction/Sale.java index e0b6292..d0debd8 100644 --- a/src/main/java/edu/ntnu/idi/idatt/transaction/Sale.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/transaction/Sale.java @@ -1,8 +1,8 @@ -package edu.ntnu.idi.idatt.transaction; +package edu.ntnu.idi.idatt.model.transaction; -import edu.ntnu.idi.idatt.Player; -import edu.ntnu.idi.idatt.calculator.SaleCalculator; -import edu.ntnu.idi.idatt.marked.Share; +import edu.ntnu.idi.idatt.model.player.Player; +import edu.ntnu.idi.idatt.model.portfolio.Share; +import edu.ntnu.idi.idatt.service.transaction.SaleCalculator; /** * Sale class diff --git a/src/main/java/edu/ntnu/idi/idatt/transaction/Transaction.java b/src/main/java/edu/ntnu/idi/idatt/model/transaction/Transaction.java similarity index 85% rename from src/main/java/edu/ntnu/idi/idatt/transaction/Transaction.java rename to src/main/java/edu/ntnu/idi/idatt/model/transaction/Transaction.java index f28629e..e42a892 100644 --- a/src/main/java/edu/ntnu/idi/idatt/transaction/Transaction.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/transaction/Transaction.java @@ -1,8 +1,8 @@ -package edu.ntnu.idi.idatt.transaction; +package edu.ntnu.idi.idatt.model.transaction; -import edu.ntnu.idi.idatt.Player; -import edu.ntnu.idi.idatt.calculator.TransactionCalculator; -import edu.ntnu.idi.idatt.marked.Share; +import edu.ntnu.idi.idatt.model.player.Player; +import edu.ntnu.idi.idatt.model.portfolio.Share; +import edu.ntnu.idi.idatt.service.transaction.TransactionCalculator; /** * Transaction class diff --git a/src/main/java/edu/ntnu/idi/idatt/transaction/TransactionArchive.java b/src/main/java/edu/ntnu/idi/idatt/model/transaction/TransactionArchive.java similarity index 97% rename from src/main/java/edu/ntnu/idi/idatt/transaction/TransactionArchive.java rename to src/main/java/edu/ntnu/idi/idatt/model/transaction/TransactionArchive.java index e07e8c9..2dd77d4 100644 --- a/src/main/java/edu/ntnu/idi/idatt/transaction/TransactionArchive.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/transaction/TransactionArchive.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.transaction; +package edu.ntnu.idi.idatt.model.transaction; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/edu/ntnu/idi/idatt/calculator/PurchaseCalculator.java b/src/main/java/edu/ntnu/idi/idatt/service/transaction/PurchaseCalculator.java similarity index 93% rename from src/main/java/edu/ntnu/idi/idatt/calculator/PurchaseCalculator.java rename to src/main/java/edu/ntnu/idi/idatt/service/transaction/PurchaseCalculator.java index fd7847f..e43a80f 100644 --- a/src/main/java/edu/ntnu/idi/idatt/calculator/PurchaseCalculator.java +++ b/src/main/java/edu/ntnu/idi/idatt/service/transaction/PurchaseCalculator.java @@ -1,9 +1,9 @@ -package edu.ntnu.idi.idatt.calculator; - -import edu.ntnu.idi.idatt.marked.Share; +package edu.ntnu.idi.idatt.service.transaction; import java.math.BigDecimal; +import edu.ntnu.idi.idatt.model.portfolio.Share; + /** * PurchaseCalculator class * diff --git a/src/main/java/edu/ntnu/idi/idatt/calculator/SaleCalculator.java b/src/main/java/edu/ntnu/idi/idatt/service/transaction/SaleCalculator.java similarity index 95% rename from src/main/java/edu/ntnu/idi/idatt/calculator/SaleCalculator.java rename to src/main/java/edu/ntnu/idi/idatt/service/transaction/SaleCalculator.java index 6c65302..8bbe827 100644 --- a/src/main/java/edu/ntnu/idi/idatt/calculator/SaleCalculator.java +++ b/src/main/java/edu/ntnu/idi/idatt/service/transaction/SaleCalculator.java @@ -1,6 +1,6 @@ -package edu.ntnu.idi.idatt.calculator; +package edu.ntnu.idi.idatt.service.transaction; -import edu.ntnu.idi.idatt.marked.Share; +import edu.ntnu.idi.idatt.model.portfolio.Share; import java.math.BigDecimal; diff --git a/src/main/java/edu/ntnu/idi/idatt/calculator/TransactionCalculator.java b/src/main/java/edu/ntnu/idi/idatt/service/transaction/TransactionCalculator.java similarity index 92% rename from src/main/java/edu/ntnu/idi/idatt/calculator/TransactionCalculator.java rename to src/main/java/edu/ntnu/idi/idatt/service/transaction/TransactionCalculator.java index fdfa9b1..8b75337 100644 --- a/src/main/java/edu/ntnu/idi/idatt/calculator/TransactionCalculator.java +++ b/src/main/java/edu/ntnu/idi/idatt/service/transaction/TransactionCalculator.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.calculator; +package edu.ntnu.idi.idatt.service.transaction; import java.math.BigDecimal; diff --git a/src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java b/src/main/java/edu/ntnu/idi/idatt/storage/ExchangeLoader.java similarity index 96% rename from src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java rename to src/main/java/edu/ntnu/idi/idatt/storage/ExchangeLoader.java index d4d9ea7..71b4e18 100644 --- a/src/main/java/edu/ntnu/idi/idatt/file/ExchangeLoader.java +++ b/src/main/java/edu/ntnu/idi/idatt/storage/ExchangeLoader.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.file; +package edu.ntnu.idi.idatt.storage; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -11,7 +11,7 @@ import java.util.ArrayList; import java.util.List; -import edu.ntnu.idi.idatt.marked.Stock; +import edu.ntnu.idi.idatt.model.market.Stock; public class ExchangeLoader { From 53109c9ffbea34ed6f5401ccd06e360b07ce690d Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 17:20:22 +0200 Subject: [PATCH 023/157] refactor: Test packages. --- .../edu/ntnu/idi/idatt/{ => model}/ExchangeTest.java | 7 ++++--- .../idi/idatt/{marked => model/market}/StockTest.java | 2 +- .../ntnu/idi/idatt/{ => model/player}/PlayerTest.java | 8 ++++---- .../idatt/{marked => model/portfolio}/PortfolioTest.java | 2 +- .../idi/idatt/{marked => model/portfolio}/ShareTest.java | 4 +++- .../idi/idatt/{ => model}/transaction/PurchaseTest.java | 8 ++++---- .../ntnu/idi/idatt/{ => model}/transaction/SaleTest.java | 9 ++++----- .../{ => model}/transaction/TransactionArchiveTest.java | 6 +++--- .../transaction}/PurchaseCalculatorTest.java | 6 +++--- .../transaction}/SaleCalculatorTest.java | 6 +++--- .../idi/idatt/{file => storage}/ExchangeLoaderTest.java | 4 ++-- 11 files changed, 32 insertions(+), 30 deletions(-) rename src/test/java/edu/ntnu/idi/idatt/{ => model}/ExchangeTest.java (96%) rename src/test/java/edu/ntnu/idi/idatt/{marked => model/market}/StockTest.java (97%) rename src/test/java/edu/ntnu/idi/idatt/{ => model/player}/PlayerTest.java (92%) rename src/test/java/edu/ntnu/idi/idatt/{marked => model/portfolio}/PortfolioTest.java (98%) rename src/test/java/edu/ntnu/idi/idatt/{marked => model/portfolio}/ShareTest.java (89%) rename src/test/java/edu/ntnu/idi/idatt/{ => model}/transaction/PurchaseTest.java (90%) rename src/test/java/edu/ntnu/idi/idatt/{ => model}/transaction/SaleTest.java (91%) rename src/test/java/edu/ntnu/idi/idatt/{ => model}/transaction/TransactionArchiveTest.java (95%) rename src/test/java/edu/ntnu/idi/idatt/{calculator => service/transaction}/PurchaseCalculatorTest.java (89%) rename src/test/java/edu/ntnu/idi/idatt/{calculator => service/transaction}/SaleCalculatorTest.java (93%) rename src/test/java/edu/ntnu/idi/idatt/{file => storage}/ExchangeLoaderTest.java (96%) diff --git a/src/test/java/edu/ntnu/idi/idatt/ExchangeTest.java b/src/test/java/edu/ntnu/idi/idatt/model/ExchangeTest.java similarity index 96% rename from src/test/java/edu/ntnu/idi/idatt/ExchangeTest.java rename to src/test/java/edu/ntnu/idi/idatt/model/ExchangeTest.java index 02cb3d7..3c9f878 100644 --- a/src/test/java/edu/ntnu/idi/idatt/ExchangeTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/model/ExchangeTest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt; +package edu.ntnu.idi.idatt.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -17,8 +17,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import edu.ntnu.idi.idatt.marked.Stock; -import edu.ntnu.idi.idatt.transaction.Transaction; +import edu.ntnu.idi.idatt.model.market.Stock; +import edu.ntnu.idi.idatt.model.player.Player; +import edu.ntnu.idi.idatt.model.transaction.Transaction; class ExchangeTest { diff --git a/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java b/src/test/java/edu/ntnu/idi/idatt/model/market/StockTest.java similarity index 97% rename from src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java rename to src/test/java/edu/ntnu/idi/idatt/model/market/StockTest.java index 941d9c5..5b27600 100644 --- a/src/test/java/edu/ntnu/idi/idatt/marked/StockTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/model/market/StockTest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.marked; +package edu.ntnu.idi.idatt.model.market; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/edu/ntnu/idi/idatt/PlayerTest.java b/src/test/java/edu/ntnu/idi/idatt/model/player/PlayerTest.java similarity index 92% rename from src/test/java/edu/ntnu/idi/idatt/PlayerTest.java rename to src/test/java/edu/ntnu/idi/idatt/model/player/PlayerTest.java index e292971..16bbb95 100644 --- a/src/test/java/edu/ntnu/idi/idatt/PlayerTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/model/player/PlayerTest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt; +package edu.ntnu.idi.idatt.model.player; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -9,9 +9,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import edu.ntnu.idi.idatt.marked.Share; -import edu.ntnu.idi.idatt.marked.Stock; -import edu.ntnu.idi.idatt.transaction.Purchase; +import edu.ntnu.idi.idatt.model.market.Stock; +import edu.ntnu.idi.idatt.model.portfolio.Share; +import edu.ntnu.idi.idatt.model.transaction.Purchase; class PlayerTest { diff --git a/src/test/java/edu/ntnu/idi/idatt/marked/PortfolioTest.java b/src/test/java/edu/ntnu/idi/idatt/model/portfolio/PortfolioTest.java similarity index 98% rename from src/test/java/edu/ntnu/idi/idatt/marked/PortfolioTest.java rename to src/test/java/edu/ntnu/idi/idatt/model/portfolio/PortfolioTest.java index e3a6898..f361d0e 100644 --- a/src/test/java/edu/ntnu/idi/idatt/marked/PortfolioTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/model/portfolio/PortfolioTest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.marked; +package edu.ntnu.idi.idatt.model.portfolio; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/edu/ntnu/idi/idatt/marked/ShareTest.java b/src/test/java/edu/ntnu/idi/idatt/model/portfolio/ShareTest.java similarity index 89% rename from src/test/java/edu/ntnu/idi/idatt/marked/ShareTest.java rename to src/test/java/edu/ntnu/idi/idatt/model/portfolio/ShareTest.java index ac69822..cefea8b 100644 --- a/src/test/java/edu/ntnu/idi/idatt/marked/ShareTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/model/portfolio/ShareTest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.marked; +package edu.ntnu.idi.idatt.model.portfolio; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -8,6 +8,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import edu.ntnu.idi.idatt.model.market.Stock; + public class ShareTest { private Share share; diff --git a/src/test/java/edu/ntnu/idi/idatt/transaction/PurchaseTest.java b/src/test/java/edu/ntnu/idi/idatt/model/transaction/PurchaseTest.java similarity index 90% rename from src/test/java/edu/ntnu/idi/idatt/transaction/PurchaseTest.java rename to src/test/java/edu/ntnu/idi/idatt/model/transaction/PurchaseTest.java index 25f9c9e..caf44c1 100644 --- a/src/test/java/edu/ntnu/idi/idatt/transaction/PurchaseTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/model/transaction/PurchaseTest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.transaction; +package edu.ntnu.idi.idatt.model.transaction; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -10,9 +10,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import edu.ntnu.idi.idatt.Player; -import edu.ntnu.idi.idatt.marked.Share; -import edu.ntnu.idi.idatt.marked.Stock; +import edu.ntnu.idi.idatt.model.market.Stock; +import edu.ntnu.idi.idatt.model.player.Player; +import edu.ntnu.idi.idatt.model.portfolio.Share; /** * Testing for Purchase class diff --git a/src/test/java/edu/ntnu/idi/idatt/transaction/SaleTest.java b/src/test/java/edu/ntnu/idi/idatt/model/transaction/SaleTest.java similarity index 91% rename from src/test/java/edu/ntnu/idi/idatt/transaction/SaleTest.java rename to src/test/java/edu/ntnu/idi/idatt/model/transaction/SaleTest.java index 2f5523b..1bd944e 100644 --- a/src/test/java/edu/ntnu/idi/idatt/transaction/SaleTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/model/transaction/SaleTest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.transaction; +package edu.ntnu.idi.idatt.model.transaction; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -10,10 +10,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import edu.ntnu.idi.idatt.Exchange; -import edu.ntnu.idi.idatt.Player; -import edu.ntnu.idi.idatt.marked.Share; -import edu.ntnu.idi.idatt.marked.Stock; +import edu.ntnu.idi.idatt.model.market.Stock; +import edu.ntnu.idi.idatt.model.player.Player; +import edu.ntnu.idi.idatt.model.portfolio.Share; /** * Testing for Sale class diff --git a/src/test/java/edu/ntnu/idi/idatt/transaction/TransactionArchiveTest.java b/src/test/java/edu/ntnu/idi/idatt/model/transaction/TransactionArchiveTest.java similarity index 95% rename from src/test/java/edu/ntnu/idi/idatt/transaction/TransactionArchiveTest.java rename to src/test/java/edu/ntnu/idi/idatt/model/transaction/TransactionArchiveTest.java index 74930fb..32739ae 100644 --- a/src/test/java/edu/ntnu/idi/idatt/transaction/TransactionArchiveTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/model/transaction/TransactionArchiveTest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.transaction; +package edu.ntnu.idi.idatt.model.transaction; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -10,8 +10,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import edu.ntnu.idi.idatt.marked.Share; -import edu.ntnu.idi.idatt.marked.Stock; +import edu.ntnu.idi.idatt.model.market.Stock; +import edu.ntnu.idi.idatt.model.portfolio.Share; class TransactionArchiveTest { diff --git a/src/test/java/edu/ntnu/idi/idatt/calculator/PurchaseCalculatorTest.java b/src/test/java/edu/ntnu/idi/idatt/service/transaction/PurchaseCalculatorTest.java similarity index 89% rename from src/test/java/edu/ntnu/idi/idatt/calculator/PurchaseCalculatorTest.java rename to src/test/java/edu/ntnu/idi/idatt/service/transaction/PurchaseCalculatorTest.java index 5d306c0..7870ea4 100644 --- a/src/test/java/edu/ntnu/idi/idatt/calculator/PurchaseCalculatorTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/service/transaction/PurchaseCalculatorTest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.calculator; +package edu.ntnu.idi.idatt.service.transaction; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -8,8 +8,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import edu.ntnu.idi.idatt.marked.Share; -import edu.ntnu.idi.idatt.marked.Stock; +import edu.ntnu.idi.idatt.model.market.Stock; +import edu.ntnu.idi.idatt.model.portfolio.Share; class PurchaseCalculatorTest { diff --git a/src/test/java/edu/ntnu/idi/idatt/calculator/SaleCalculatorTest.java b/src/test/java/edu/ntnu/idi/idatt/service/transaction/SaleCalculatorTest.java similarity index 93% rename from src/test/java/edu/ntnu/idi/idatt/calculator/SaleCalculatorTest.java rename to src/test/java/edu/ntnu/idi/idatt/service/transaction/SaleCalculatorTest.java index 3672172..eacc8e2 100644 --- a/src/test/java/edu/ntnu/idi/idatt/calculator/SaleCalculatorTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/service/transaction/SaleCalculatorTest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.calculator; +package edu.ntnu.idi.idatt.service.transaction; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -8,8 +8,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import edu.ntnu.idi.idatt.marked.Share; -import edu.ntnu.idi.idatt.marked.Stock; +import edu.ntnu.idi.idatt.model.market.Stock; +import edu.ntnu.idi.idatt.model.portfolio.Share; class SaleCalculatorTest { diff --git a/src/test/java/edu/ntnu/idi/idatt/file/ExchangeLoaderTest.java b/src/test/java/edu/ntnu/idi/idatt/storage/ExchangeLoaderTest.java similarity index 96% rename from src/test/java/edu/ntnu/idi/idatt/file/ExchangeLoaderTest.java rename to src/test/java/edu/ntnu/idi/idatt/storage/ExchangeLoaderTest.java index 94f5617..d3d82d7 100644 --- a/src/test/java/edu/ntnu/idi/idatt/file/ExchangeLoaderTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/storage/ExchangeLoaderTest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.file; +package edu.ntnu.idi.idatt.storage; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -14,7 +14,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import edu.ntnu.idi.idatt.marked.Stock; +import edu.ntnu.idi.idatt.model.market.Stock; /** * Test class for ExchangeLoader From 952f67a1640e7bdea486bfcdd52c7d17c37fc2bd Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 17:26:17 +0200 Subject: [PATCH 024/157] refactor: Cleanup after process. --- pom.xml | 12 ------------ .../idi/idatt/model/portfolio/PortfolioTest.java | 3 ++- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index 15e1d8c..36567cf 100644 --- a/pom.xml +++ b/pom.xml @@ -20,24 +20,12 @@ maven-compiler-plugin 3.14.1 - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.12.0 - org.openjfx javafx-controls 25.0.1 - - - org.openjfx - javafx-media - 25.0.1 - org.junit.jupiter diff --git a/src/test/java/edu/ntnu/idi/idatt/model/portfolio/PortfolioTest.java b/src/test/java/edu/ntnu/idi/idatt/model/portfolio/PortfolioTest.java index f361d0e..743963f 100644 --- a/src/test/java/edu/ntnu/idi/idatt/model/portfolio/PortfolioTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/model/portfolio/PortfolioTest.java @@ -11,7 +11,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import edu.ntnu.idi.idatt.calculator.SaleCalculator; +import edu.ntnu.idi.idatt.model.market.Stock; +import edu.ntnu.idi.idatt.service.transaction.SaleCalculator; public class PortfolioTest { From 15807b61f8b19fb357e38b2e984c803c9c3de71a Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 17:27:27 +0200 Subject: [PATCH 025/157] fix: Old path in pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 36567cf..133a526 100644 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,7 @@ javafx-maven-plugin 0.0.8 - edu.ntnu.idi.idatt.gui.javafx + edu.ntnu.idi.idatt.Launcher From 430375f02b20aa5091476c49cb22e855f73aaf65 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 17:52:57 +0200 Subject: [PATCH 026/157] fix: Add JavaDoc reports to pom.xml --- pom.xml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 133a526..eea0a11 100644 --- a/pom.xml +++ b/pom.xml @@ -109,6 +109,16 @@ - + + + + + + + maven-javadoc-plugin + 3.12.0 + + + From 866d55ae7a8717c440a43b0da16801f89a004a3e Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 20:10:32 +0200 Subject: [PATCH 027/157] feat: Implement a static view manager --- .../edu/ntnu/idi/idatt/view/SceneManager.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/SceneManager.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/SceneManager.java b/src/main/java/edu/ntnu/idi/idatt/view/SceneManager.java new file mode 100644 index 0000000..be87d13 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/SceneManager.java @@ -0,0 +1,55 @@ +package edu.ntnu.idi.idatt.view; + +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +/** + * SceneManager class for managing objects to the stage. + * + *

+ * This class provides methods for changing the root object to + * the scene, providing simple use of components to change between the + * pages + *

+ * + * @author Pawel Sapula + */ +public final class SceneManager { + + // Store scene reference. + private static Scene scene; + + // Disable initialization. + private SceneManager() { + } + + /** + * Method for intialization of the Scene Manager. + * + *

+ * This method enables initialization of the primary scene, + * based on the stage from the program entry point, and a + * page based on the page's root object. + *

+ * + * @param stage - Application's stage + * @param root - The root object / fundamental object of a page + */ + public static void init(Stage stage, Parent root) { + scene = new Scene(root); + // Add styling sheet permamently for the scene. + scene.getStylesheets().add(SceneManager.class.getResource("/themes/default.css").toExternalForm()); + stage.setScene(scene); + } + + /** + * Method for switching the scene's root variable. + * + * @param root - Parent JavaFX object. + */ + public static void switchTo(Parent root) { + scene.setRoot(root); + } + +} From 767d577b4b6864a6fd0cf4174b5ac429e36c2e69 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 20:11:00 +0200 Subject: [PATCH 028/157] feat: Implement an abstract view class --- .../idatt/view/components/AbstractView.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java new file mode 100644 index 0000000..f50ab32 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java @@ -0,0 +1,58 @@ +package edu.ntnu.idi.idatt.view.components; + +import javafx.scene.Parent; +import javafx.scene.layout.Pane; + +/** + * INFO! + * + *

+ * This class is based on the generic Type parameter - T. It's experimental + * for the time of creating the class (24.04.26), since parent don't have access + * to the + * getChildren() method which could provide usefuleness in creating a standalone + * abstract class. + * + * Since most views will be based on the Pane subclasses, it is fully + * functional, but + * may throw errors if the typeparameter isn't of this type. + *

+ * + * Reference: https://docs.oracle.com/javase/8/javafx/api/overview-tree.html + */ + +/** + * AbstractView class + * + *

+ * Provides a simple abstraction of the Pane subclasses, + * making it a useful framework for creating different views. + *

+ */ +public abstract class AbstractView { + + protected T root; + + /** + * Constructor for AbstractView. + */ + public AbstractView(T type) { + root = type; + root.getChildren().add(this.createContent()); + } + + /** + * Getter for current view instance. + * + * @return Instance of current view. + */ + public T getInstance() { + return root; + } + + /** + * Abstract method for creating view content. + */ + public abstract Parent createContent(); + +} From 336291315af8cb5fa7448c3ca9dec1e63c70a4af Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 20:11:25 +0200 Subject: [PATCH 029/157] feat: Create basic start page --- .../java/edu/ntnu/idi/idatt/Launcher.java | 6 +-- .../idi/idatt/view/primary/PrimaryView.java | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java diff --git a/src/main/java/edu/ntnu/idi/idatt/Launcher.java b/src/main/java/edu/ntnu/idi/idatt/Launcher.java index 601d11e..c390dbd 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Launcher.java +++ b/src/main/java/edu/ntnu/idi/idatt/Launcher.java @@ -1,8 +1,8 @@ package edu.ntnu.idi.idatt; +import edu.ntnu.idi.idatt.view.SceneManager; +import edu.ntnu.idi.idatt.view.primary.PrimaryView; import javafx.application.Application; -import javafx.scene.Scene; -import javafx.scene.layout.VBox; import javafx.stage.Stage; public final class Launcher { @@ -21,7 +21,7 @@ public void start(Stage stage) { stage.setWidth(1200); stage.setHeight(700); stage.setTitle("Stock Game"); - stage.setScene(new Scene(new VBox())); + SceneManager.init(stage, new PrimaryView().getInstance()); stage.show(); } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java new file mode 100644 index 0000000..523786f --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java @@ -0,0 +1,45 @@ +package edu.ntnu.idi.idatt.view.primary; + +import java.util.List; + +import edu.ntnu.idi.idatt.view.components.AbstractView; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; + +public class PrimaryView extends AbstractView { + + public PrimaryView() { + super(new StackPane()); + root.getStyleClass().add("dark"); + } + + @Override + public Parent createContent() { + StackPane root = new StackPane(); + root.getStyleClass().add("light"); + StackPane.setAlignment(root, Pos.CENTER); + StackPane.setMargin(root, new Insets(40.0)); + + Label title = new Label("Millions"); + title.getStyleClass().add("title"); + StackPane.setAlignment(title, Pos.TOP_CENTER); + + VBox wrapper = new VBox(); + + Label playerName = new Label("Player name:"); + playerName.getStyleClass().add("big-text-32"); + + Label startBalance = new Label("Start balance:"); + startBalance.getStyleClass().add("big-text-32"); + + wrapper.getChildren().addAll(List.of(playerName, startBalance)); + + root.getChildren().addAll(List.of(title, wrapper)); + return root; + } + +} From 7b0109ef8f3a9bfc94d17c6629c4932d50aa3d15 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 20:11:42 +0200 Subject: [PATCH 030/157] chore: Move .css stylesheet to resources/ --- src/main/resources/themes/default.css | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/main/resources/themes/default.css diff --git a/src/main/resources/themes/default.css b/src/main/resources/themes/default.css new file mode 100644 index 0000000..29bcf92 --- /dev/null +++ b/src/main/resources/themes/default.css @@ -0,0 +1,27 @@ +.dark { + -fx-background-color: #0B2B40; +} + +.light { + -fx-background-color: #1E5959; +} + +.primary { + -fx-background-color: #3B8C6E; +} + +.title { + -fx-font-size: 56px; + -fx-font-weight: 700; + -fx-text-fill: #EEEEEE; + -fx-padding: 30px; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 10, 0.5, 0, 5); +} + +.big-text-32{ + -fx-font-size: 32px; + -fx-font-weight: 700; + -fx-text-fill: #EEEEEE; + -fx-padding: 30px; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 10, 0.5, 0, 5); +} From af9fe84dfbdba336df2594bb0bfa0a61d2aa4fb8 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Fri, 24 Apr 2026 20:14:20 +0200 Subject: [PATCH 031/157] chore: Correct naming conventions and acessibility for safety. --- .../edu/ntnu/idi/idatt/view/components/AbstractView.java | 8 ++++---- .../java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java index f50ab32..18d51a2 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java @@ -31,14 +31,14 @@ */ public abstract class AbstractView { - protected T root; + private T instance; /** * Constructor for AbstractView. */ public AbstractView(T type) { - root = type; - root.getChildren().add(this.createContent()); + instance = type; + instance.getChildren().add(this.createContent()); } /** @@ -47,7 +47,7 @@ public AbstractView(T type) { * @return Instance of current view. */ public T getInstance() { - return root; + return instance; } /** diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java index 523786f..7e94df1 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java @@ -14,7 +14,7 @@ public class PrimaryView extends AbstractView { public PrimaryView() { super(new StackPane()); - root.getStyleClass().add("dark"); + this.getInstance().getStyleClass().add("dark"); } @Override From 3a380ec029c7f90c3beafe65042202855002611c Mon Sep 17 00:00:00 2001 From: danieskj Date: Sat, 25 Apr 2026 15:49:49 +0200 Subject: [PATCH 032/157] feat: Created the main page layout for further use --- .../java/edu/ntnu/idi/idatt/Launcher.java | 3 +- .../ntnu/idi/idatt/view/primary/MainView.java | 34 +++++++++++++++++++ src/main/resources/themes/default.css | 1 + 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java diff --git a/src/main/java/edu/ntnu/idi/idatt/Launcher.java b/src/main/java/edu/ntnu/idi/idatt/Launcher.java index c390dbd..10507a4 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Launcher.java +++ b/src/main/java/edu/ntnu/idi/idatt/Launcher.java @@ -1,6 +1,7 @@ package edu.ntnu.idi.idatt; import edu.ntnu.idi.idatt.view.SceneManager; +import edu.ntnu.idi.idatt.view.primary.MainView; import edu.ntnu.idi.idatt.view.primary.PrimaryView; import javafx.application.Application; import javafx.stage.Stage; @@ -21,7 +22,7 @@ public void start(Stage stage) { stage.setWidth(1200); stage.setHeight(700); stage.setTitle("Stock Game"); - SceneManager.init(stage, new PrimaryView().getInstance()); + SceneManager.init(stage, new MainView().getInstance()); stage.show(); } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java new file mode 100644 index 0000000..cede902 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java @@ -0,0 +1,34 @@ +package edu.ntnu.idi.idatt.view.primary; + +import edu.ntnu.idi.idatt.view.components.AbstractView; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.layout.StackPane; +import javafx.scene.transform.Scale; + + +public class MainView extends AbstractView { + + public MainView() { + super(new StackPane()); + this.getInstance().getStyleClass().add("light"); + } + + @Override + public Parent createContent() { + StackPane menu = new StackPane(); + menu.getStyleClass().add("dark"); + menu.setScaleX(menu.getScaleX()*0.75); + Scale scale = new Scale(-2.0, 1.0, 0, 0); + menu.getTransforms().add(scale); + + StackPane background = new StackPane(); + background.getStyleClass().add("primary"); + StackPane.setAlignment(background, Pos.CENTER); + background.setScaleY(background.getScaleY()*0.80); + StackPane main = new StackPane(background, menu); + + + return main; + } +} diff --git a/src/main/resources/themes/default.css b/src/main/resources/themes/default.css index 29bcf92..a50ca96 100644 --- a/src/main/resources/themes/default.css +++ b/src/main/resources/themes/default.css @@ -25,3 +25,4 @@ -fx-padding: 30px; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 10, 0.5, 0, 5); } + From d80610e220d1c78ed5f0b66d9e0a52a80afc8820 Mon Sep 17 00:00:00 2001 From: danieskj Date: Sat, 25 Apr 2026 16:36:48 +0200 Subject: [PATCH 033/157] feat: Added csv conversion to String --- .../java/edu/ntnu/idi/idatt/Launcher.java | 3 ++ .../edu/ntnu/idi/idatt/storage/CSVtoJSON.java | 26 ++++++++++++++++++ .../{edu.ntnu.idi.idatt => }/logo.png | Bin .../{edu.ntnu.idi.idatt => }/stocks.csv | 0 .../{edu.ntnu.idi.idatt => }/stonks.png | Bin .../{edu.ntnu.idi.idatt => }/user.png | Bin 6 files changed, 29 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java rename src/main/resources/{edu.ntnu.idi.idatt => }/logo.png (100%) rename src/main/resources/{edu.ntnu.idi.idatt => }/stocks.csv (100%) rename src/main/resources/{edu.ntnu.idi.idatt => }/stonks.png (100%) rename src/main/resources/{edu.ntnu.idi.idatt => }/user.png (100%) diff --git a/src/main/java/edu/ntnu/idi/idatt/Launcher.java b/src/main/java/edu/ntnu/idi/idatt/Launcher.java index 10507a4..b77e8d8 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Launcher.java +++ b/src/main/java/edu/ntnu/idi/idatt/Launcher.java @@ -1,5 +1,6 @@ package edu.ntnu.idi.idatt; +import edu.ntnu.idi.idatt.storage.CSVtoJSON; import edu.ntnu.idi.idatt.view.SceneManager; import edu.ntnu.idi.idatt.view.primary.MainView; import edu.ntnu.idi.idatt.view.primary.PrimaryView; @@ -24,6 +25,8 @@ public void start(Stage stage) { stage.setTitle("Stock Game"); SceneManager.init(stage, new MainView().getInstance()); stage.show(); + CSVtoJSON ja = new CSVtoJSON(); + ja.csvFile(); } } diff --git a/src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java b/src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java new file mode 100644 index 0000000..3afdaf5 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java @@ -0,0 +1,26 @@ +package edu.ntnu.idi.idatt.storage; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; + + +public class CSVtoJSON { + + public String csvFile(){ + String csvData; + try (InputStream rawData = CSVtoJSON.class.getResourceAsStream("/stocks.csv")){ + csvData = new BufferedReader(new InputStreamReader(rawData, StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + } catch (IOException e) { + throw new RuntimeException(e); + } + System.out.println(csvData); + return csvData; + } +} + diff --git a/src/main/resources/edu.ntnu.idi.idatt/logo.png b/src/main/resources/logo.png similarity index 100% rename from src/main/resources/edu.ntnu.idi.idatt/logo.png rename to src/main/resources/logo.png diff --git a/src/main/resources/edu.ntnu.idi.idatt/stocks.csv b/src/main/resources/stocks.csv similarity index 100% rename from src/main/resources/edu.ntnu.idi.idatt/stocks.csv rename to src/main/resources/stocks.csv diff --git a/src/main/resources/edu.ntnu.idi.idatt/stonks.png b/src/main/resources/stonks.png similarity index 100% rename from src/main/resources/edu.ntnu.idi.idatt/stonks.png rename to src/main/resources/stonks.png diff --git a/src/main/resources/edu.ntnu.idi.idatt/user.png b/src/main/resources/user.png similarity index 100% rename from src/main/resources/edu.ntnu.idi.idatt/user.png rename to src/main/resources/user.png From 83d2b57a57ed73f57ae3b90aac339c64aae2fd61 Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Sat, 25 Apr 2026 17:11:06 +0200 Subject: [PATCH 034/157] feat: Update css stylesheet. --- src/main/resources/themes/default.css | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/main/resources/themes/default.css b/src/main/resources/themes/default.css index 29bcf92..5afb7cd 100644 --- a/src/main/resources/themes/default.css +++ b/src/main/resources/themes/default.css @@ -25,3 +25,31 @@ -fx-padding: 30px; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 10, 0.5, 0, 5); } + +.med-text-16{ + -fx-font-size: 16px; + -fx-font-weight: 700; + -fx-text-fill: #EEEEEE; + -fx-padding: 30px; +} + +/* ======================= + BUTTON STYLE + ======================= */ +.button { + -fx-font-size: 16px; + -fx-text-fill: black; + -fx-background-color: #FFFFFF; + -fx-background-radius: 20; + -fx-border-radius: 20; + -fx-padding: 10 20 10 20; + -fx-cursor: hand; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 6, 0.3, 0, 2); +} + +.button:hover { + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.4), 8, 0.4, 0, 4); + -fx-translate-y: 1; +} + + From 7ee8b90327a49ba1ad3ea8cef6df072aba7bb46e Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Sat, 25 Apr 2026 17:12:55 +0200 Subject: [PATCH 035/157] feat: Implement model interface --- src/main/java/edu/ntnu/idi/idatt/view/components/Model.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/components/Model.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/Model.java b/src/main/java/edu/ntnu/idi/idatt/view/components/Model.java new file mode 100644 index 0000000..2465fd7 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/Model.java @@ -0,0 +1,4 @@ +package edu.ntnu.idi.idatt.view.components; + +public interface Model { +} From 39ba54d5da2dafeea200b503932050460f4a09a0 Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Sat, 25 Apr 2026 17:13:16 +0200 Subject: [PATCH 036/157] feat: Implement AbstractController class --- .../idi/idatt/view/components/AbstractController.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/components/AbstractController.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractController.java b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractController.java new file mode 100644 index 0000000..a334769 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractController.java @@ -0,0 +1,11 @@ +package edu.ntnu.idi.idatt.view.components; + +public abstract class AbstractController { + + protected final M model; + + public AbstractController(M model) { + this.model = model; + } + +} From 67d51568bfccd950f1ec2604268b8819b0ba7dd2 Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Sat, 25 Apr 2026 17:14:57 +0200 Subject: [PATCH 037/157] feat: Create start page model --- .../idi/idatt/view/primary/PrimaryModel.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryModel.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryModel.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryModel.java new file mode 100644 index 0000000..440ea91 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryModel.java @@ -0,0 +1,30 @@ +package edu.ntnu.idi.idatt.view.primary; + +import edu.ntnu.idi.idatt.view.components.Model; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +public class PrimaryModel implements Model { + + private final StringProperty name = new SimpleStringProperty(); + private final StringProperty balance = new SimpleStringProperty(); + private final StringProperty error = new SimpleStringProperty(); + private final StringProperty fileName = new SimpleStringProperty(); + + public StringProperty getName() { + return name; + } + + public StringProperty getBalance() { + return balance; + } + + public StringProperty getError() { + return error; + } + + public StringProperty getFileName() { + return fileName; + } + +} From 0475d6fa840fdf5f42476bf16c2eb97bb894c802 Mon Sep 17 00:00:00 2001 From: danieskj Date: Sat, 25 Apr 2026 17:15:21 +0200 Subject: [PATCH 038/157] feat: Added a gson parser that writes to a json file --- .github/workflows/maven.yml | 3 +-- pom.xml | 8 +++++++ .../edu/ntnu/idi/idatt/storage/CSVtoJSON.java | 23 ++++++++++++------- src/main/resources/save.json | 1 + 4 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 src/main/resources/save.json diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index ee9c969..8efdb4a 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -12,8 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 25 - uses: actions/setup-java@v4 + -uses: actions/setup-java@v4 with: java-version: '25' distribution: 'temurin' diff --git a/pom.xml b/pom.xml index eea0a11..b0a2e89 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,14 @@ 3.14.1
+ + + com.google.code.gson + gson + 2.13.2 + compile + + org.openjfx javafx-controls diff --git a/src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java b/src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java index 3afdaf5..cf91bec 100644 --- a/src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java +++ b/src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java @@ -1,26 +1,33 @@ package edu.ntnu.idi.idatt.storage; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +import java.io.*; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; public class CSVtoJSON { - public String csvFile(){ + public void csvFile(){ String csvData; try (InputStream rawData = CSVtoJSON.class.getResourceAsStream("/stocks.csv")){ csvData = new BufferedReader(new InputStreamReader(rawData, StandardCharsets.UTF_8)) .lines() - .collect(Collectors.joining("\n")); + .collect(Collectors.joining(" ")); } catch (IOException e) { throw new RuntimeException(e); } - System.out.println(csvData); - return csvData; + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + try (Writer writer = new FileWriter("src/main/resources/save.json")) { + gson.toJson(csvData, writer); + } catch (IOException e) { + e.printStackTrace(); + } } } diff --git a/src/main/resources/save.json b/src/main/resources/save.json new file mode 100644 index 0000000..07089da --- /dev/null +++ b/src/main/resources/save.json @@ -0,0 +1 @@ +"# Ticker,Name,Price AMD,Advanced Micro Devices,202.68 MSI,Micro-Star International,92.80 INTC,Intel,45.58 NVDA,Nvidia,182.65 EQNR,Equinor,32.43" \ No newline at end of file From d0c048a65f83485bd8c651d4e41341738cd45b40 Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Sat, 25 Apr 2026 17:15:22 +0200 Subject: [PATCH 039/157] feat: Create start page controller --- .../idatt/view/primary/PrimaryController.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryController.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryController.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryController.java new file mode 100644 index 0000000..2fe27fa --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryController.java @@ -0,0 +1,51 @@ +package edu.ntnu.idi.idatt.view.primary; + +import java.io.File; +import java.math.BigDecimal; + +import edu.ntnu.idi.idatt.view.components.AbstractController; +import javafx.stage.FileChooser; + +public class PrimaryController extends AbstractController { + + private File csv; + + public PrimaryController(PrimaryModel model) { + super(model); + } + + public void obtainCSVFile() { + csv = new FileChooser().showOpenDialog(null); + if (csv != null) { + model.getFileName().set(csv.getName()); + } + } + + public void initializeGame() { + model.getError().set(" "); // Empty buffers + + if (model.getName().get() == null || model.getBalance().get() == null) { + model.getError().set("Name and/or balance fields can't be empty"); + return; + } + + if (model.getName().get().isBlank() || + model.getBalance().get().isBlank()) { + model.getError().set("Name and/or balance fields can't be empty"); + return; + } + + try { + BigDecimal balance = new BigDecimal(model.getBalance().get()); + } catch (NumberFormatException e) { + model.getError().set("The balance must be a number format!"); + return; + } + + if (csv == null) { + model.getError().set("Specify the stocks .csv file!"); + return; + } + } + +} From bbeef56a974744832f660089757a97bd3108dd1a Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Sat, 25 Apr 2026 17:15:33 +0200 Subject: [PATCH 040/157] feat: Finalize start page UI --- .../idi/idatt/view/primary/PrimaryView.java | 86 +++++++++++++++++-- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java index 7e94df1..c107572 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java @@ -6,12 +6,27 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Parent; +import javafx.scene.control.Button; import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; public class PrimaryView extends AbstractView { + // Globlal variables for model implementation + private TextField nameField; + private TextField balanceField; + private Label fileLabel; + private Label errorLabel; + + // Global buttons for controller implementation + private Button csvButton; + private Button startButton; + public PrimaryView() { super(new StackPane()); this.getInstance().getStyleClass().add("dark"); @@ -19,27 +34,88 @@ public PrimaryView() { @Override public Parent createContent() { - StackPane root = new StackPane(); + VBox root = new VBox(); root.getStyleClass().add("light"); - StackPane.setAlignment(root, Pos.CENTER); + root.setAlignment(Pos.TOP_CENTER); StackPane.setMargin(root, new Insets(40.0)); Label title = new Label("Millions"); title.getStyleClass().add("title"); - StackPane.setAlignment(title, Pos.TOP_CENTER); + // Create and style wrappers VBox wrapper = new VBox(); + HBox playerWrapper = new HBox(); + HBox balanceWrapper = new HBox(); + playerWrapper.setAlignment(Pos.CENTER_LEFT); + balanceWrapper.setAlignment(Pos.CENTER_LEFT); + // Player name label and input field Label playerName = new Label("Player name:"); playerName.getStyleClass().add("big-text-32"); + nameField = new TextField(); + nameField.setPromptText("Leonardo Dicaprio"); + nameField.setFocusTraversable(false); + nameField.getStyleClass().add("button"); + playerWrapper.getChildren().addAll(List.of(playerName, nameField)); + + // Start balance label and input field Label startBalance = new Label("Start balance:"); startBalance.getStyleClass().add("big-text-32"); - wrapper.getChildren().addAll(List.of(playerName, startBalance)); + balanceField = new TextField(); + balanceField.setPromptText("2500"); + balanceField.setFocusTraversable(false); + balanceField.getStyleClass().add("button"); + balanceWrapper.getChildren().addAll(List.of(startBalance, balanceField)); + + // CSV import button and label + + HBox csvWrapper = new HBox(); + csvWrapper.setAlignment(Pos.CENTER_LEFT); + + csvButton = new Button("Import CSV"); + csvButton.getStyleClass().add("button"); + + fileLabel = new Label(); + fileLabel.getStyleClass().add("med-text-16"); + + csvWrapper.getChildren().addAll(csvButton, fileLabel); + VBox.setMargin(csvWrapper, new Insets(0.0, 0.0, 0.0, 50.0)); + + // Start game button and error log + startButton = new Button("Start Game"); + startButton.getStyleClass().add("button"); + startButton.setMaxWidth(200); + VBox.setMargin(startButton, new Insets(40.0)); + + errorLabel = new Label(); + errorLabel.getStyleClass().add("med-text-16"); + errorLabel.setStyle("-fx-text-fill: red;"); + + // Region filler + Region filler = new Region(); + VBox.setVgrow(filler, Priority.ALWAYS); + + // Wrap components together and adjust positioning + wrapper.getChildren().addAll(List.of(playerWrapper, balanceWrapper, csvWrapper)); + wrapper.setAlignment(Pos.BASELINE_LEFT); + + root.getChildren().addAll(List.of(title, wrapper, filler, errorLabel, startButton)); - root.getChildren().addAll(List.of(title, wrapper)); return root; } + public void setModel(PrimaryModel model) { + this.nameField.textProperty().bindBidirectional(model.getName()); + this.balanceField.textProperty().bindBidirectional(model.getBalance()); + this.errorLabel.textProperty().bind(model.getError()); + this.fileLabel.textProperty().bind(model.getFileName()); + } + + public void setController(PrimaryController controller) { + this.csvButton.setOnAction(e -> controller.obtainCSVFile()); + this.startButton.setOnAction(e -> controller.initializeGame()); + } + } From 0fd6a9c80a93d30dcc3660fa248ab35a49c1e17d Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Sat, 25 Apr 2026 17:16:25 +0200 Subject: [PATCH 041/157] feat(Launcher): Implement MVC model wiring for start page. --- src/main/java/edu/ntnu/idi/idatt/Launcher.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/Launcher.java b/src/main/java/edu/ntnu/idi/idatt/Launcher.java index c390dbd..087144b 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Launcher.java +++ b/src/main/java/edu/ntnu/idi/idatt/Launcher.java @@ -1,6 +1,8 @@ package edu.ntnu.idi.idatt; import edu.ntnu.idi.idatt.view.SceneManager; +import edu.ntnu.idi.idatt.view.primary.PrimaryController; +import edu.ntnu.idi.idatt.view.primary.PrimaryModel; import edu.ntnu.idi.idatt.view.primary.PrimaryView; import javafx.application.Application; import javafx.stage.Stage; @@ -21,7 +23,15 @@ public void start(Stage stage) { stage.setWidth(1200); stage.setHeight(700); stage.setTitle("Stock Game"); - SceneManager.init(stage, new PrimaryView().getInstance()); + + PrimaryModel model = new PrimaryModel(); + PrimaryView view = new PrimaryView(); + PrimaryController controller = new PrimaryController(model); + + view.setModel(model); + view.setController(controller); + + SceneManager.init(stage, view.getInstance()); stage.show(); } From 69377c1c0b81b478c49c2b7c2a8b55c2cd9515d2 Mon Sep 17 00:00:00 2001 From: PawelSapula Date: Sat, 25 Apr 2026 17:27:53 +0200 Subject: [PATCH 042/157] chore: fix CD/CI pipeline. --- .github/workflows/maven.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 8efdb4a..8b3c6d3 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -12,11 +12,6 @@ jobs: steps: - uses: actions/checkout@v4 - -uses: actions/setup-java@v4 - with: - java-version: '25' - distribution: 'temurin' - cache: 'maven' - name: Build with Maven run: mvn -B compile --file pom.xml From e1bc41a958e9cf55d80dff06cd9f6b0f1ae53a48 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 25 Apr 2026 19:11:45 +0200 Subject: [PATCH 043/157] refactor: Primary package -> Entry Package. Primary*.java -> Entry*.java --- src/main/java/edu/ntnu/idi/idatt/Launcher.java | 12 ++++++------ .../StartController.java} | 6 +++--- .../PrimaryModel.java => entry/StartModel.java} | 4 ++-- .../PrimaryView.java => entry/StartView.java} | 10 +++++----- 4 files changed, 16 insertions(+), 16 deletions(-) rename src/main/java/edu/ntnu/idi/idatt/view/{primary/PrimaryController.java => entry/StartController.java} (87%) rename src/main/java/edu/ntnu/idi/idatt/view/{primary/PrimaryModel.java => entry/StartModel.java} (88%) rename src/main/java/edu/ntnu/idi/idatt/view/{primary/PrimaryView.java => entry/StartView.java} (94%) diff --git a/src/main/java/edu/ntnu/idi/idatt/Launcher.java b/src/main/java/edu/ntnu/idi/idatt/Launcher.java index 18a6b15..f13f7fd 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Launcher.java +++ b/src/main/java/edu/ntnu/idi/idatt/Launcher.java @@ -2,9 +2,9 @@ import edu.ntnu.idi.idatt.storage.CSVtoJSON; import edu.ntnu.idi.idatt.view.SceneManager; -import edu.ntnu.idi.idatt.view.primary.PrimaryController; -import edu.ntnu.idi.idatt.view.primary.PrimaryModel; -import edu.ntnu.idi.idatt.view.primary.PrimaryView; +import edu.ntnu.idi.idatt.view.entry.StartController; +import edu.ntnu.idi.idatt.view.entry.StartModel; +import edu.ntnu.idi.idatt.view.entry.StartView; import javafx.application.Application; import javafx.stage.Stage; @@ -25,9 +25,9 @@ public void start(Stage stage) { stage.setHeight(700); stage.setTitle("Stock Game"); - PrimaryModel model = new PrimaryModel(); - PrimaryView view = new PrimaryView(); - PrimaryController controller = new PrimaryController(model); + StartModel model = new StartModel(); + StartView view = new StartView(); + StartController controller = new StartController(model); view.setModel(model); view.setController(controller); diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryController.java b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java similarity index 87% rename from src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryController.java rename to src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java index 2fe27fa..a57215c 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryController.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.view.primary; +package edu.ntnu.idi.idatt.view.entry; import java.io.File; import java.math.BigDecimal; @@ -6,11 +6,11 @@ import edu.ntnu.idi.idatt.view.components.AbstractController; import javafx.stage.FileChooser; -public class PrimaryController extends AbstractController { +public class StartController extends AbstractController { private File csv; - public PrimaryController(PrimaryModel model) { + public StartController(StartModel model) { super(model); } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryModel.java b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartModel.java similarity index 88% rename from src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryModel.java rename to src/main/java/edu/ntnu/idi/idatt/view/entry/StartModel.java index 440ea91..e1b0f92 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryModel.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartModel.java @@ -1,10 +1,10 @@ -package edu.ntnu.idi.idatt.view.primary; +package edu.ntnu.idi.idatt.view.entry; import edu.ntnu.idi.idatt.view.components.Model; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -public class PrimaryModel implements Model { +public class StartModel implements Model { private final StringProperty name = new SimpleStringProperty(); private final StringProperty balance = new SimpleStringProperty(); diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartView.java similarity index 94% rename from src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java rename to src/main/java/edu/ntnu/idi/idatt/view/entry/StartView.java index c107572..60180b1 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/PrimaryView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartView.java @@ -1,4 +1,4 @@ -package edu.ntnu.idi.idatt.view.primary; +package edu.ntnu.idi.idatt.view.entry; import java.util.List; @@ -15,7 +15,7 @@ import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -public class PrimaryView extends AbstractView { +public class StartView extends AbstractView { // Globlal variables for model implementation private TextField nameField; @@ -27,7 +27,7 @@ public class PrimaryView extends AbstractView { private Button csvButton; private Button startButton; - public PrimaryView() { + public StartView() { super(new StackPane()); this.getInstance().getStyleClass().add("dark"); } @@ -106,14 +106,14 @@ public Parent createContent() { return root; } - public void setModel(PrimaryModel model) { + public void setModel(StartModel model) { this.nameField.textProperty().bindBidirectional(model.getName()); this.balanceField.textProperty().bindBidirectional(model.getBalance()); this.errorLabel.textProperty().bind(model.getError()); this.fileLabel.textProperty().bind(model.getFileName()); } - public void setController(PrimaryController controller) { + public void setController(StartController controller) { this.csvButton.setOnAction(e -> controller.obtainCSVFile()); this.startButton.setOnAction(e -> controller.initializeGame()); } From af1390625010fb21727a9b6792d1e6f3f635241f Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 25 Apr 2026 21:20:14 +0200 Subject: [PATCH 044/157] feat: Add singleton class for holding user session. --- .../ntnu/idi/idatt/session/UserSession.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/session/UserSession.java diff --git a/src/main/java/edu/ntnu/idi/idatt/session/UserSession.java b/src/main/java/edu/ntnu/idi/idatt/session/UserSession.java new file mode 100644 index 0000000..c8a2863 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/session/UserSession.java @@ -0,0 +1,31 @@ +package edu.ntnu.idi.idatt.session; + +import edu.ntnu.idi.idatt.model.player.Player; + +public class UserSession { + + // Singleton instance + private static UserSession INSTANCE; + + // Disable constructor initialization. + private UserSession() { + } + + public static UserSession getInstance() { + if (INSTANCE == null) { + INSTANCE = new UserSession(); + } + return INSTANCE; + } + + private Player player; + + public Player getPlayer() { + return player; + } + + public void setPlayer(Player player) { + this.player = player; + } + +} From 9a919229b037a95b39f127338e19c500d826aeda Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 25 Apr 2026 21:25:23 +0200 Subject: [PATCH 045/157] feat: Add abstract class for main UI creation --- .../idatt/view/components/AbstractViewUI.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java new file mode 100644 index 0000000..76a5375 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java @@ -0,0 +1,72 @@ +package edu.ntnu.idi.idatt.view.components; + +import javafx.scene.Parent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; + +public abstract class AbstractViewUI extends AbstractView { + + private VBox navigation; + private HBox header; + private HBox toolbar; + + public AbstractViewUI() { + HBox wrapper = new HBox(); + BorderPane layout = new BorderPane(); + + createUIComponents(); + navigation.getChildren().add(createNavigation()); + header.getChildren().add(createHeader()); + toolbar.getChildren().add(createToolbar()); + + layout.setTop(header); + layout.setBottom(toolbar); + layout.setCenter(createContent()); + + HBox.setHgrow(layout, Priority.ALWAYS); + wrapper.getChildren().addAll(navigation, layout); + + this.setInstance(new StackPane()); + this.getInstance().getChildren().add(wrapper); + } + + public void createUIComponents() { + navigation = new VBox(); + navigation.setMaxHeight(Double.MAX_VALUE); + navigation.getStyleClass().add("dark"); + navigation.setPrefWidth(150); + + header = new HBox(); + header.getStyleClass().add("light"); + header.setPrefHeight(80); + + toolbar = new HBox(); + toolbar.getStyleClass().add("light"); + HBox.setHgrow(toolbar, Priority.ALWAYS); + toolbar.setPrefHeight(80); + } + + public abstract Parent createContent(); + + public abstract Parent createNavigation(); + + public abstract Parent createHeader(); + + public abstract Parent createToolbar(); + + public VBox getNavigation() { + return navigation; + } + + public HBox getHeader() { + return header; + } + + public HBox getToolbar() { + return toolbar; + } + +} From dcde2c651d3ce3b6340122c127c19adcfc9c252b Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 25 Apr 2026 21:27:17 +0200 Subject: [PATCH 046/157] refactor(AbstractView): Modify instance initialization methods --- .../idi/idatt/view/components/AbstractView.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java index 18d51a2..b4f96ec 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractView.java @@ -36,11 +36,18 @@ public abstract class AbstractView { /** * Constructor for AbstractView. */ - public AbstractView(T type) { + protected AbstractView(T type) { instance = type; instance.getChildren().add(this.createContent()); } + /** + * Constructor for AbstractView without initialization. + */ + protected AbstractView() { + + } + /** * Getter for current view instance. * @@ -50,6 +57,13 @@ public T getInstance() { return instance; } + /** + * Setter for current view instance. + */ + public void setInstance(T instance) { + this.instance = instance; + } + /** * Abstract method for creating view content. */ From e7da7e24cccfd7f629c353ef559a36f36d0f60d7 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 25 Apr 2026 21:28:50 +0200 Subject: [PATCH 047/157] refactor: Create example of MainView --- .../idi/idatt/view/entry/StartController.java | 11 ++++++- .../ntnu/idi/idatt/view/primary/MainView.java | 31 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java index a57215c..52f7eeb 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java @@ -3,7 +3,11 @@ import java.io.File; import java.math.BigDecimal; +import edu.ntnu.idi.idatt.model.player.Player; +import edu.ntnu.idi.idatt.session.UserSession; +import edu.ntnu.idi.idatt.view.SceneManager; import edu.ntnu.idi.idatt.view.components.AbstractController; +import edu.ntnu.idi.idatt.view.primary.MainView; import javafx.stage.FileChooser; public class StartController extends AbstractController { @@ -35,8 +39,9 @@ public void initializeGame() { return; } + BigDecimal balance = null; try { - BigDecimal balance = new BigDecimal(model.getBalance().get()); + balance = new BigDecimal(model.getBalance().get()); } catch (NumberFormatException e) { model.getError().set("The balance must be a number format!"); return; @@ -46,6 +51,10 @@ public void initializeGame() { model.getError().set("Specify the stocks .csv file!"); return; } + + UserSession.getInstance().setPlayer(new Player(model.getName().get(), balance)); + SceneManager.switchTo(new MainView().getInstance()); + } } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java index cede902..cb0869b 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java @@ -1,12 +1,15 @@ package edu.ntnu.idi.idatt.view.primary; +import edu.ntnu.idi.idatt.session.UserSession; import edu.ntnu.idi.idatt.view.components.AbstractView; +import edu.ntnu.idi.idatt.view.components.AbstractViewUI; import javafx.geometry.Pos; import javafx.scene.Parent; +import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.scene.transform.Scale; - +/* public class MainView extends AbstractView { public MainView() { @@ -32,3 +35,29 @@ public Parent createContent() { return main; } } +*/ + +public class MainView extends AbstractViewUI { + + @Override + public Parent createContent() { + return new Label("Hello " + UserSession.getInstance().getPlayer().getName() + " " + + UserSession.getInstance().getPlayer().getNetWorth()); + } + + @Override + public Parent createNavigation() { + return new Label("Hello from nav"); + } + + @Override + public Parent createHeader() { + return new Label("Header"); + } + + @Override + public Parent createToolbar() { + return new Label("Toolbar"); + } + +} From 9643c68005c04935ff095e0304cf185a5818d47a Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 26 Apr 2026 11:33:51 +0200 Subject: [PATCH 048/157] feat: Add basic icon package --- src/main/resources/icons/portfolio.png | Bin 0 -> 2809 bytes src/main/resources/icons/quit.png | Bin 0 -> 1893 bytes src/main/resources/icons/search.png | Bin 0 -> 1691 bytes src/main/resources/icons/user.png | Bin 0 -> 3189 bytes src/main/resources/logo.png | Bin 8691 -> 0 bytes src/main/resources/stonks.png | Bin 20446 -> 0 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/icons/portfolio.png create mode 100644 src/main/resources/icons/quit.png create mode 100644 src/main/resources/icons/search.png create mode 100644 src/main/resources/icons/user.png delete mode 100644 src/main/resources/logo.png delete mode 100644 src/main/resources/stonks.png diff --git a/src/main/resources/icons/portfolio.png b/src/main/resources/icons/portfolio.png new file mode 100644 index 0000000000000000000000000000000000000000..2f33093aad300d1e6b43aa2303d113e3902b64ce GIT binary patch literal 2809 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/search.png b/src/main/resources/icons/search.png new file mode 100644 index 0000000000000000000000000000000000000000..a206015495c28cbc96d86ea7ad0323873a632c6b GIT binary patch literal 1691 zcmV;M24wk(P)Px*MM*?KRCt{2ol9&LRUC)E3fz)_MNCX|BjFY*CW7?V zM;}y)8)Fqy3&glFAw~_p663-cBe<6~ihxG=^@P#12*YLr4ZxWGqU83mOn&_W{g z_FZ387iSE2&Y9^wmpf<9nS0NdOw!X%@A>`bH*?-|E_gg1kH_Qjc#@~Vwty1W0`0&$ zpe0CO4y**01?S8FMPLf}2{;b?02~d{o+?-hYy;i}P67W`d7KPBYb(&0VjP*L0PF?E zsNRIQV+?o`XiX`mOwt7$0Om1gMAUHvxFd!55+RK`q#1YvxU0;0(};^V3>*uN-w}1j zOmNOJM4fUK7weh=(9OkqCXNB%Dd0rK5!47dU^g&F{M|bWyn=XomQ<0~lHfC54L&n2 zzgb{cpslXR178z=VGbdlm_-KiUIg?6dBo-a6|mYs-cHg7j1zq!J_l}1QA-DKFh)xe z=uJ_J6`lYtpawqzJZMqZ7T~uCUGu;*7Ihh;0eFe%J8>VPgJ+v2U_a3s`*Ew2Ts2W< z%mNSD6cxiGL^m}0ZHh{g$A~VUyKRapLpyK|Rrk|2MI}KWYMpTg*kDtXCL0i1OSNAh z!xjB1;5^}g#(|ACMODZRs6#kKGT0=}q4qOoal4p`<95_KI!>+&dS zJTF*PQ~}MXchm<~6(z%as84*}sv=E3B`o|DZuB#%r3pAqIJN^;6=~3c`e<*pswi3R zM~$z`s-iM{hB`J?YYY;Oalo#ecnYX@PrFq`X6Qs6?{aeHaW7$^gLdT@Wr%RBd+f?l zS&G_$DmMd|WqJt5dLG9XPHZPEa1=9>ORlgOby%e@jf^kTJa3DX-iH|Xsa5^~-j{xN zj}0Ml$m^y$?aE1oZo)DDYE?}MErf+lQQd-stdK*jgei@^*>t8lMV34Vegyd2fEL&I z3;0s{o$+SAjv?!$^tUF2ILfe;zRq-pI)=z1j+qeRC~~Y$7$TRO$4v-v6xon9rZdzr zWTlioYeI;l$g#@TB5`yKk%!i&ObBrlIaVj88U7^P^vl%LW|cG&j{OhQ8S3`bJp$v4 z>2wU4meR{j2yv9ui)$T2oGeFEqDe~2H)rDL7*dqd*O(CEC|67AbEY%YG2|C1-C{zB zqsSdW`C257jv+rvY2&?Bhj7vmxwqP8LWrXjr1bYzR7k8P+!mjqKKde2!|$uCDk|Y5 z8Ct895VsMIc_fWYbvuSbQu@B6g*b>Dqf@JFwo!kJ9L^Gs`ChAvBpOkd0rgl_WR|-L z$2y5v3gr~?Lb*eBZoWIS%K(ll*ab4RYjWQ5wCld3g2&4Q8IjpIy-p1 zRYeuB3-!@{*s7xH*ohj?^HvpANDi^ZQE32kh~b8;u^I6+l*Tel2A9iTi@I^Z1dhIK z6vKMd4(}uxDyn39$?nz~H=@1+GLK_JB$vXIsO4`Fw~MdML~pV-3v99{D>?d6gPTKa zh2$DLQR@u3$a22#QH}_R_f1!~i_rcD8Ooain7MzO!epFGJ{?wCdtZJDPR zQHMkYIY_#%!*!vKO;vJCrmWW(s}Wlq#RYm8F`r^$l4~(yK4WNs{J%pi0IVxha)>RC z`sCq|=MbiCLR@SeQzodPsfIjaSDkV}&mi_rc^TM& zxBz+JO2h@v1wZG5zwHRl{UZ3^a-aVZ>I*k@YYK?Ret>j0*C8b+R*<> zrg75!#)rDao8lne6bJF9I7l1%{U&cpN))Kflw0h`tS`JN4&qI55O0cucvBpt6@62R zz|D4K))(Fs2l1vj$W7??=%2NuvF>Ok`uh<}kKNYP)hQc^H^rM_i6~RN8I~zE#hc*> l1$+hW@pwEQkH_Nx{{hHo(253myc+-j002ovPDHLkV1j910vG@Q literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/user.png b/src/main/resources/icons/user.png new file mode 100644 index 0000000000000000000000000000000000000000..0534bddc47d6b3efb3ba3cacfcc6191f2e2ec3f4 GIT binary patch literal 3189 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 b8N>eo8=(qLl)QVr00000NkvXXu0mjfYrX

cJR;2{X)$ovejy5oJrZvWMbPAxo%G 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 B=uiLv From ff7e3c34b8bf215b916c1b94cfdc91bb27c4f9a6 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 26 Apr 2026 13:28:28 +0200 Subject: [PATCH 049/157] feat: Implement SearchBarComponent --- .../elements/SearchBarComponent.java | 49 +++++++++++++++++++ .../ntnu/idi/idatt/view/primary/MainView.java | 6 ++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java new file mode 100644 index 0000000..0493b21 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java @@ -0,0 +1,49 @@ +package edu.ntnu.idi.idatt.view.components.elements; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.scene.control.Button; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + +public class SearchBarComponent extends HBox { + + private final StringProperty query = new SimpleStringProperty(); + private final Button searchButton; + + public SearchBarComponent(String placeholder) { + TextField searchBar = new TextField(); + searchBar.setPromptText(placeholder); + searchBar.getStyleClass().add("button"); + searchBar.textProperty().bindBidirectional(query); + searchBar.setFocusTraversable(false); + + Image image = new Image(this.getClass().getResource("/icons/search.png/").toExternalForm()); + ImageView iv = new ImageView(); + iv.setImage(image); + + iv.setFitHeight(40); + iv.setFitWidth(40); + + searchButton = new Button("", iv); + searchButton.getStyleClass().clear(); // TODO: Make icon component or fix + + this.getChildren().addAll(searchBar, searchButton); + } + + public String getQuery() { + return query.get(); + } + + public void onSearchQuery(ActionEventHandler handler) { + this.searchButton.setOnAction(e -> handler.handle()); + } + + @FunctionalInterface + public interface ActionEventHandler { + void handle(); + } + +} diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java index cb0869b..09e1e17 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java @@ -3,6 +3,7 @@ import edu.ntnu.idi.idatt.session.UserSession; import edu.ntnu.idi.idatt.view.components.AbstractView; import edu.ntnu.idi.idatt.view.components.AbstractViewUI; +import edu.ntnu.idi.idatt.view.components.elements.SearchBarComponent; import javafx.geometry.Pos; import javafx.scene.Parent; import javafx.scene.control.Label; @@ -52,7 +53,10 @@ public Parent createNavigation() { @Override public Parent createHeader() { - return new Label("Header"); + this.getHeader().setAlignment(Pos.BASELINE_CENTER); + SearchBarComponent bar = new SearchBarComponent("Search after stocks.."); + bar.onSearchQuery(() -> System.out.println(bar.getQuery())); + return bar; } @Override From a3d28017af83b272c31240454eb036db795fea06 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 26 Apr 2026 23:02:41 +0200 Subject: [PATCH 050/157] feat(AbstractViewUI): Implement togglable menu solution --- .../idatt/view/components/AbstractViewUI.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java index 76a5375..1b7b889 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java @@ -1,9 +1,12 @@ package edu.ntnu.idi.idatt.view.components; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.geometry.Pos; import javafx.scene.Parent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; @@ -12,6 +15,7 @@ public abstract class AbstractViewUI extends AbstractView { private VBox navigation; private HBox header; private HBox toolbar; + private SimpleBooleanProperty isMenuVisible = new SimpleBooleanProperty(false); public AbstractViewUI() { HBox wrapper = new HBox(); @@ -30,7 +34,25 @@ public AbstractViewUI() { wrapper.getChildren().addAll(navigation, layout); this.setInstance(new StackPane()); - this.getInstance().getChildren().add(wrapper); + + Parent menu = createMenu(); + StackPane.setAlignment(menu, Pos.CENTER_RIGHT); + menu.setVisible(false); + + Region disableMenu = new Region(); + disableMenu.setVisible(false); + + disableMenu.setOnMouseClicked(e -> { + menu.setVisible(false); + disableMenu.setVisible(false); + }); + + isMenuVisible.addListener((observer, oldVal, newVal) -> { + menu.setVisible(true); + disableMenu.setVisible(true); + }); + + this.getInstance().getChildren().addAll(wrapper, disableMenu, menu); } public void createUIComponents() { @@ -57,6 +79,8 @@ public void createUIComponents() { public abstract Parent createToolbar(); + public abstract Parent createMenu(); + public VBox getNavigation() { return navigation; } @@ -69,4 +93,8 @@ public HBox getToolbar() { return toolbar; } + public void toggleMenu() { + isMenuVisible.set(!isMenuVisible.get()); + } + } From e86dd10fd18f5951e3b748732c51a4e061b51327 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 26 Apr 2026 23:02:58 +0200 Subject: [PATCH 051/157] chore: Update .css stylesheet --- src/main/resources/themes/default.css | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/resources/themes/default.css b/src/main/resources/themes/default.css index 2e79159..a372c2b 100644 --- a/src/main/resources/themes/default.css +++ b/src/main/resources/themes/default.css @@ -30,7 +30,6 @@ -fx-font-size: 16px; -fx-font-weight: 700; -fx-text-fill: #EEEEEE; - -fx-padding: 30px; } /* ======================= @@ -51,3 +50,26 @@ -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.4), 8, 0.4, 0, 4); -fx-translate-y: 1; } + +.icon { + -fx-border-radius: 20; + -fx-padding: 5; + -fx-cursor: hand; +} + +.searchbar { + -fx-background-color: #FFFFFF; + -fx-background-radius: 20; + -fx-border-radius: 20; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 6, 0.3, 0, 2); +} + +.searchbar-field { + -fx-font-size: 16px; + -fx-text-fill: black; + -fx-background-color: #FFFFFF; + -fx-background-radius: 20; + -fx-border-radius: 20; + -fx-cursor: hand; +} + From 3c60893d1de4fd5df59a37ff5b30493e92faef20 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 26 Apr 2026 23:03:31 +0200 Subject: [PATCH 052/157] feat: Add functional interface for event handeling --- .../view/components/primitives/ActionEventHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/components/primitives/ActionEventHandler.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/primitives/ActionEventHandler.java b/src/main/java/edu/ntnu/idi/idatt/view/components/primitives/ActionEventHandler.java new file mode 100644 index 0000000..a42993d --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/primitives/ActionEventHandler.java @@ -0,0 +1,6 @@ +package edu.ntnu.idi.idatt.view.components.primitives; + +@FunctionalInterface +public interface ActionEventHandler { + void handle(); +} From 3f8b74e95e569d8b2e8c1ea20a9ca26d1a7504f6 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 26 Apr 2026 23:04:05 +0200 Subject: [PATCH 053/157] feat: Implement IconComponent class --- .../components/elements/IconComponent.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/components/elements/IconComponent.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/IconComponent.java b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/IconComponent.java new file mode 100644 index 0000000..a3ef67a --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/IconComponent.java @@ -0,0 +1,32 @@ +package edu.ntnu.idi.idatt.view.components.elements; + +import edu.ntnu.idi.idatt.view.components.primitives.ActionEventHandler; +import javafx.scene.control.Button; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.StackPane; + +public class IconComponent extends StackPane { + + private Button icon; + + public IconComponent(Image image, String description, int size) { + + ImageView iv = new ImageView(); + iv.setImage(image); + iv.setFitHeight(size); // TODO: Fix? + iv.setFitWidth(size); + + icon = new Button(description, iv); + icon.getStyleClass().clear(); // Remove parent buffers in case. + icon.getStyleClass().add("icon"); + icon.setMaxHeight(Double.MAX_VALUE); + + this.getChildren().add(icon); + } + + public void onIconClick(ActionEventHandler handler) { + this.icon.setOnAction(e -> handler.handle()); + } + +} From ff5e1bf12b5d8df0315e80441f6c08d9fa24de4c Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 26 Apr 2026 23:04:52 +0200 Subject: [PATCH 054/157] refactor: Update SearchBarComponent class --- .../elements/SearchBarComponent.java | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java index 0493b21..0e56051 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java @@ -1,36 +1,37 @@ package edu.ntnu.idi.idatt.view.components.elements; +import edu.ntnu.idi.idatt.view.components.primitives.ActionEventHandler; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.scene.control.Button; +import javafx.geometry.Pos; import javafx.scene.control.TextField; import javafx.scene.image.Image; -import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; public class SearchBarComponent extends HBox { private final StringProperty query = new SimpleStringProperty(); - private final Button searchButton; + private final IconComponent searchIcon; public SearchBarComponent(String placeholder) { + + HBox wrapper = new HBox(); + wrapper.getStyleClass().add("searchbar"); + wrapper.setAlignment(Pos.CENTER); + TextField searchBar = new TextField(); + searchBar.getStyleClass().add("searchbar-field"); searchBar.setPromptText(placeholder); - searchBar.getStyleClass().add("button"); + searchBar.setMaxHeight(Double.MAX_VALUE); searchBar.textProperty().bindBidirectional(query); searchBar.setFocusTraversable(false); Image image = new Image(this.getClass().getResource("/icons/search.png/").toExternalForm()); - ImageView iv = new ImageView(); - iv.setImage(image); - - iv.setFitHeight(40); - iv.setFitWidth(40); + searchIcon = new IconComponent(image, null, 32); - searchButton = new Button("", iv); - searchButton.getStyleClass().clear(); // TODO: Make icon component or fix + wrapper.getChildren().addAll(searchBar, searchIcon); - this.getChildren().addAll(searchBar, searchButton); + this.getChildren().addAll(wrapper); } public String getQuery() { @@ -38,12 +39,7 @@ public String getQuery() { } public void onSearchQuery(ActionEventHandler handler) { - this.searchButton.setOnAction(e -> handler.handle()); - } - - @FunctionalInterface - public interface ActionEventHandler { - void handle(); + this.searchIcon.onIconClick(() -> handler.handle()); } } From 78971a36a11c2c0b0e9b8b8ff5391de536addee9 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 26 Apr 2026 23:05:16 +0200 Subject: [PATCH 055/157] feat: Implement initial MainView --- .../ntnu/idi/idatt/view/primary/MainView.java | 117 ++++++++++++------ 1 file changed, 82 insertions(+), 35 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java index 09e1e17..d3a151d 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java @@ -1,67 +1,114 @@ package edu.ntnu.idi.idatt.view.primary; import edu.ntnu.idi.idatt.session.UserSession; -import edu.ntnu.idi.idatt.view.components.AbstractView; import edu.ntnu.idi.idatt.view.components.AbstractViewUI; +import edu.ntnu.idi.idatt.view.components.elements.IconComponent; import edu.ntnu.idi.idatt.view.components.elements.SearchBarComponent; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Parent; import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; -import javafx.scene.transform.Scale; - -/* -public class MainView extends AbstractView { - - public MainView() { - super(new StackPane()); - this.getInstance().getStyleClass().add("light"); - } - - @Override - public Parent createContent() { - StackPane menu = new StackPane(); - menu.getStyleClass().add("dark"); - menu.setScaleX(menu.getScaleX()*0.75); - Scale scale = new Scale(-2.0, 1.0, 0, 0); - menu.getTransforms().add(scale); - - StackPane background = new StackPane(); - background.getStyleClass().add("primary"); - StackPane.setAlignment(background, Pos.CENTER); - background.setScaleY(background.getScaleY()*0.80); - StackPane main = new StackPane(background, menu); - - - return main; - } -} -*/ +import javafx.scene.layout.VBox; public class MainView extends AbstractViewUI { @Override public Parent createContent() { - return new Label("Hello " + UserSession.getInstance().getPlayer().getName() + " " - + UserSession.getInstance().getPlayer().getNetWorth()); + StackPane root = new StackPane(); + root.getStyleClass().add("primary"); + return root; } @Override public Parent createNavigation() { - return new Label("Hello from nav"); + VBox navigation = new VBox(); + Label title = new Label("Title"); + title.getStyleClass().add("big-text-32"); + + Label newspaper = new Label(" • Newspaper"); + newspaper.getStyleClass().add("med-text-16"); + + navigation.getChildren().addAll(title, newspaper); + return navigation; } @Override public Parent createHeader() { - this.getHeader().setAlignment(Pos.BASELINE_CENTER); + this.getHeader().setAlignment(Pos.CENTER); SearchBarComponent bar = new SearchBarComponent("Search after stocks.."); bar.onSearchQuery(() -> System.out.println(bar.getQuery())); + HBox.setMargin(bar, new Insets(20)); return bar; } @Override public Parent createToolbar() { - return new Label("Toolbar"); + HBox bar = new HBox(); + bar.setAlignment(Pos.CENTER); + bar.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(bar, Priority.ALWAYS); + + Label balance = new Label(" Money: " + UserSession.getInstance().getPlayer().getMoney().toString() + " USD"); + balance.getStyleClass().add("med-text-16"); + + Region filler = new Region(); + HBox.setHgrow(filler, Priority.ALWAYS); + + VBox infoWrapper = new VBox(); + infoWrapper.setAlignment(Pos.CENTER); + + Label playerStatus = new Label(); + playerStatus.setText("Status:" + UserSession.getInstance().getPlayer().getStatus()); + playerStatus.getStyleClass().add("med-text-16"); + + Label playerNetWorth = new Label(); + playerNetWorth.setText("Net Worth: " + UserSession.getInstance().getPlayer().getNetWorth().toString() + " USD"); + playerNetWorth.getStyleClass().add("med-text-16"); + + infoWrapper.getChildren().addAll(playerStatus, playerNetWorth); + + HBox iconWrapper = new HBox(); + iconWrapper.setAlignment(Pos.CENTER); + + Image userImg = new Image(this.getClass().getResource("/icons/user.png").toExternalForm()); + Image quitImg = new Image(this.getClass().getResource("/icons/quit.png").toExternalForm()); + + IconComponent userIcon = new IconComponent(userImg, null, 44); + IconComponent quitIcon = new IconComponent(quitImg, null, 44); + + userIcon.onIconClick(() -> toggleMenu()); + + iconWrapper.getChildren().addAll(userIcon, quitIcon); + + bar.getChildren().addAll(balance, filler, infoWrapper, iconWrapper); + return bar; + } + + @Override + public Parent createMenu() { + VBox menu = new VBox(); + menu.setMaxSize(300, Double.MAX_VALUE); + menu.getStyleClass().add("dark"); + menu.setAlignment(Pos.TOP_CENTER); + + Image menuImg = new Image(this.getClass().getResource("/icons/user.png").toExternalForm()); + IconComponent menuIcon = new IconComponent(menuImg, "Account", 60); + menuIcon.getStyleClass().add("big-text-32"); + + Label portfolio = new Label(" • Portfolio"); + portfolio.getStyleClass().add("med-text-16"); + + Label transactions = new Label(" • Transactions"); + transactions.getStyleClass().add("med-text-16"); + + menu.getChildren().addAll(menuIcon, portfolio, transactions); + + return menu; } } From b54748ee8f73fb71a8e37847a86b8bcbb478b819 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 9 May 2026 23:37:40 +0200 Subject: [PATCH 056/157] chore: remove unused class. --- .../edu/ntnu/idi/idatt/storage/CSVtoJSON.java | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java diff --git a/src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java b/src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java deleted file mode 100644 index cf91bec..0000000 --- a/src/main/java/edu/ntnu/idi/idatt/storage/CSVtoJSON.java +++ /dev/null @@ -1,33 +0,0 @@ -package edu.ntnu.idi.idatt.storage; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.stream.Collectors; - - -public class CSVtoJSON { - - public void csvFile(){ - String csvData; - try (InputStream rawData = CSVtoJSON.class.getResourceAsStream("/stocks.csv")){ - csvData = new BufferedReader(new InputStreamReader(rawData, StandardCharsets.UTF_8)) - .lines() - .collect(Collectors.joining(" ")); - } catch (IOException e) { - throw new RuntimeException(e); - } - - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - try (Writer writer = new FileWriter("src/main/resources/save.json")) { - gson.toJson(csvData, writer); - } catch (IOException e) { - e.printStackTrace(); - } - } -} - From 8e55e996ba68bffbdbef06241e5eddf96af70ca6 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 9 May 2026 23:41:08 +0200 Subject: [PATCH 057/157] refactor: ExchangeLoader -> StockParser. Uncoupling and small fixes. --- .../edu/ntnu/idi/idatt/model/Exchange.java | 31 ++---- .../idi/idatt/storage/ExchangeLoader.java | 100 ----------------- .../ntnu/idi/idatt/storage/StockParser.java | 76 +++++++++++++ .../ntnu/idi/idatt/model/ExchangeTest.java | 28 ++--- .../idi/idatt/storage/ExchangeLoaderTest.java | 103 ------------------ .../idi/idatt/storage/StockParserTest.java | 68 ++++++++++++ 6 files changed, 159 insertions(+), 247 deletions(-) delete mode 100644 src/main/java/edu/ntnu/idi/idatt/storage/ExchangeLoader.java create mode 100644 src/main/java/edu/ntnu/idi/idatt/storage/StockParser.java delete mode 100644 src/test/java/edu/ntnu/idi/idatt/storage/ExchangeLoaderTest.java create mode 100644 src/test/java/edu/ntnu/idi/idatt/storage/StockParserTest.java diff --git a/src/main/java/edu/ntnu/idi/idatt/model/Exchange.java b/src/main/java/edu/ntnu/idi/idatt/model/Exchange.java index 84d2d9d..d009ed1 100644 --- a/src/main/java/edu/ntnu/idi/idatt/model/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/Exchange.java @@ -1,6 +1,5 @@ package edu.ntnu.idi.idatt.model; -import java.io.IOException; import java.math.BigDecimal; import java.util.*; @@ -10,7 +9,6 @@ import edu.ntnu.idi.idatt.model.transaction.Purchase; import edu.ntnu.idi.idatt.model.transaction.Sale; import edu.ntnu.idi.idatt.model.transaction.Transaction; -import edu.ntnu.idi.idatt.storage.ExchangeLoader; /** * Exchange class @@ -22,29 +20,23 @@ *

* */ -public class Exchange extends ExchangeLoader { +public class Exchange { private final String name; private int week; private HashMap stockMap = new HashMap<>(); - private Random random = new Random(); /** * Constructor for Exchange class * - * @param name - Name of the current stock Exchange - * @param path - Path to .csv file. + * @param name - Name of the current stock Exchange + * @param stocks - List of stocks for this exchange */ - public Exchange(String name, String path) { - super(path); + public Exchange(String name, List stocks) { this.name = name; this.week = 1; - try { - this.load().forEach(stock -> stockMap.put(stock.getSymbol(), stock)); - } catch (IOException e) { - throw new IllegalArgumentException("Problem loading [" + name + "] exchange : " + e); - } + stocks.forEach(stock -> stockMap.put(stock.getSymbol(), stock)); } @@ -164,7 +156,8 @@ public List getLosers(int limit) { * @see Transaction */ public Transaction buy(String symbol, BigDecimal quantity, Player player) { - Share share = new Share(getStock(symbol), quantity, BigDecimal.valueOf(random.nextDouble())); + Stock stock = getStock(symbol); + Share share = new Share(stock, quantity, stock.getSalesPrice()); Purchase purchase = new Purchase(share, this.week); purchase.commit(player); return player.getTransactionArchive().getPurchases(this.week).getLast(); @@ -201,16 +194,6 @@ public Transaction sell(Share share, Player player) { * @see Stock */ public void advance() { - for (Stock stocks : stockMap.values()) { - stocks.addNewSalesPrice(BigDecimal.valueOf(random.nextDouble())); - - // TODO: Move this to JavaFx on Window close? - try { - this.save(stockMap.values().stream().toList()); - } catch (IOException e) { - throw new IllegalArgumentException("Problem loading [" + name + "] exchange : " + e); - } - } } } diff --git a/src/main/java/edu/ntnu/idi/idatt/storage/ExchangeLoader.java b/src/main/java/edu/ntnu/idi/idatt/storage/ExchangeLoader.java deleted file mode 100644 index 71b4e18..0000000 --- a/src/main/java/edu/ntnu/idi/idatt/storage/ExchangeLoader.java +++ /dev/null @@ -1,100 +0,0 @@ -package edu.ntnu.idi.idatt.storage; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.math.BigDecimal; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import edu.ntnu.idi.idatt.model.market.Stock; - -public class ExchangeLoader { - - private final File file; - - /** - * Constructors for ExchangeLoader - * - *

- * Utilizes method overloading for different types - * of path formatting. - *

- * - * @throws IllegalArgumentException if specified path doesn't exist. - */ - protected ExchangeLoader(String path) { - file = new File(path); - if (!file.exists()) { - throw new IllegalArgumentException("File at this path doesn't exist!"); - } - } - - protected ExchangeLoader(URL path) { - file = new File(path.toString()); - if (!file.exists()) { - throw new IllegalArgumentException("File at this path doesn't exist!"); - } - } - - /** - * Method for loading from stocks from file - * - * @return a list of loaded stocks. - * @throws IOException on BufferedReader error - */ - protected List load() throws IOException { - - ArrayList stocks = new ArrayList<>(); - - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - List stockStringList = new ArrayList<>(reader.readAllLines()); - - // Remove comments - stockStringList.removeIf(s -> s.isBlank()); - stockStringList.removeIf(s -> s.startsWith("#")); - - for (String stockString : stockStringList) { - String[] stockValues = stockString.split(","); - - // TODO: Loading all historical prices not the recent, saved one. - Stock stock = new Stock(stockValues[0], stockValues[1], List.of(new BigDecimal(stockValues[2]))); - stocks.add(stock); - } - - } catch (IOException e) { - throw new IOException("File loading failed!"); - } - - return stocks; - - } - - /** - * Method for saving stocks to file. - * - * @param stocks The destined list to be saved. - * @throws IOException on BufferedWriter error. - */ - protected void save(List stocks) throws IOException { - - try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { - - for (Stock stock : stocks) { - String data = stock.getSymbol() + "," + stock.getCompany() + "," - + stock.getHistoricalPrices().getLast().toString(); - writer.write(data); - writer.newLine(); - } - - } catch (IOException e) { - throw new IOException("File saving failed!"); - } - - } - -} diff --git a/src/main/java/edu/ntnu/idi/idatt/storage/StockParser.java b/src/main/java/edu/ntnu/idi/idatt/storage/StockParser.java new file mode 100644 index 0000000..a4936dc --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/storage/StockParser.java @@ -0,0 +1,76 @@ +package edu.ntnu.idi.idatt.storage; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import edu.ntnu.idi.idatt.model.market.Stock; + +/** + * Utility class for parsing stocks. + * + *

+ * Decomponents files into the .csv (comma separated valuus) + * format, and creats stocks out of them. + *

+ */ +public class StockParser { + + // Disable initialization. + private StockParser() { + + } + + /** + * Method for loading from stocks from file + * + * @param path - The path to the .csv file. + * + * @return a list of loaded stocks. + * @throws IOException on BufferedReader error + */ + public static List load(String path) throws IOException { + File file = new File(path.toString()); + if (!file.exists()) { + throw new IOException("File at this path doesn't exist!"); + } + + if (!Files.probeContentType(Paths.get(file.getPath())).equals("text/csv")) { + throw new IOException("Please choose a .csv file!"); + } + + ArrayList stocks = new ArrayList<>(); + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + List stockStringList = new ArrayList<>(reader.readAllLines()); + + // Remove comments and inproper syntax + stockStringList.removeIf(s -> s.isBlank()); + stockStringList.removeIf(s -> s.startsWith("#")); + stockStringList.removeIf(s -> !s.contains(",")); + + for (String stockString : stockStringList) { + String[] stockValues = stockString.split(","); + if (stockValues.length != 3) { + throw new IOException("Invalid CSV format!"); + } + + Stock stock = new Stock(stockValues[0], stockValues[1], List.of(new BigDecimal(stockValues[2]))); + stocks.add(stock); + } + + } catch (IOException e) { + throw new IOException("File loading failed!"); + } + + return stocks; + + } + +} diff --git a/src/test/java/edu/ntnu/idi/idatt/model/ExchangeTest.java b/src/test/java/edu/ntnu/idi/idatt/model/ExchangeTest.java index 3c9f878..af14b62 100644 --- a/src/test/java/edu/ntnu/idi/idatt/model/ExchangeTest.java +++ b/src/test/java/edu/ntnu/idi/idatt/model/ExchangeTest.java @@ -45,14 +45,13 @@ class ExchangeTest { @BeforeEach public void PT_setup() throws IOException { - InputStream is = getClass() - .getClassLoader() - .getResourceAsStream("stocks.csv"); + Stock AAPL = new Stock("AAPL", "Apple Inc", List.of(new BigDecimal("30"))); + Stock NVDA = new Stock("NVDA", "NVIDIA", List.of(new BigDecimal("182.81"))); + Stock TSLA = new Stock("TSLA", "Tesla", List.of(new BigDecimal("417.44"))); + Stock AMD = new Stock("AMD", "Advanced Micro Devices", List.of(new BigDecimal("207.32"))); - Path tempFile = Files.createTempFile("stocks", ".csv"); - Files.copy(is, tempFile, StandardCopyOption.REPLACE_EXISTING); - - exchange = new Exchange("TestExchange", tempFile.toFile().toPath().toString()); + List stockss = List.of(AAPL, NVDA, TSLA, AMD); + exchange = new Exchange("TestExchange", stockss); stocks = exchange.getStocks(); player = new Player("TestPlayer", new BigDecimal("500")); } @@ -114,7 +113,7 @@ void PTBuy() { void PTSell() { // Player has to have a share to sell it. exchange.buy("AAPL", new BigDecimal("1"), player); - stocks.get(0).addNewSalesPrice(new BigDecimal("40")); // Simulate increase of AAPL stock price + exchange.getStock("AAPL").addNewSalesPrice(new BigDecimal("40")); // Simulate increase of AAPL stock price Transaction transaction = exchange.sell(player.getPortfolio().getShares().getLast(), player); assertEquals(transaction, player.getTransactionArchive().getTransactions(1).getLast()); @@ -125,18 +124,7 @@ void PTSell() { @Test void PTAdvance() { - List stockPricesBefore = new ArrayList<>(); - for (Stock stock : stocks) { - stockPricesBefore.add(stocks.indexOf(stock), stock.getSalesPrice()); - } - - exchange.advance(); - - for (Stock stock : stocks) { - assertTrue(stockPricesBefore.get(stocks.indexOf(stock)).compareTo(stock.getSalesPrice()) != 0); - // If compareTo returns 0 then its equal. - } - + // TODO: do } /** diff --git a/src/test/java/edu/ntnu/idi/idatt/storage/ExchangeLoaderTest.java b/src/test/java/edu/ntnu/idi/idatt/storage/ExchangeLoaderTest.java deleted file mode 100644 index d3d82d7..0000000 --- a/src/test/java/edu/ntnu/idi/idatt/storage/ExchangeLoaderTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package edu.ntnu.idi.idatt.storage; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.IOException; -import java.io.InputStream; -import java.math.BigDecimal; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import edu.ntnu.idi.idatt.model.market.Stock; - -/** - * Test class for ExchangeLoader - * - *

- * Tests the loading and saving of stock data. - *

- */ -class ExchangeLoaderTest { - - private ExchangeLoader loader; - private List exampleStocks; - - @BeforeEach - public void PT_setup() throws IOException { - - InputStream is = getClass() - .getClassLoader() - .getResourceAsStream("stocks.csv"); - - Path tempFile = Files.createTempFile("stocks", ".csv"); - Files.copy(is, tempFile, StandardCopyOption.REPLACE_EXISTING); - - loader = new ExchangeLoader(tempFile.toFile().toPath().toString()); - - Stock AAPL = new Stock("AAPL", "Apple Inc.", List.of(new BigDecimal("32"))); - Stock NVDA = new Stock("NVDA", "NVIDIA", List.of(new BigDecimal("182.81"))); - Stock TSLA = new Stock("TSLA", "Tesla", List.of(new BigDecimal("417.44"))); - Stock AMD = new Stock("AMD", "Advanced Micro Devices", List.of(new BigDecimal("207.32"))); - - exampleStocks = List.of(AAPL, NVDA, TSLA, AMD); - } - - /** - * Positive test for loading/reading stocks - */ - @Test - void PT_load() { - List stocks = null; - try { - stocks = loader.load(); - } catch (IOException e) { - e.printStackTrace(); - } - - assertEquals(4, stocks.size()); - - } - - /** - * Positive test for saving stocks. - */ - @Test - void PT_save() { - exampleStocks.get(3).addNewSalesPrice(new BigDecimal("99999")); - - // Save - - try { - loader.save(exampleStocks); - } catch (IOException e) { - e.printStackTrace(); - } - - // Try to read again - List stocks = null; - try { - stocks = loader.load(); - } catch (IOException e) { - e.printStackTrace(); - } - - assertEquals(new BigDecimal("99999"), stocks.get(3).getSalesPrice()); - - } - - /** - * Negative tests for constructor - */ - @Test - void NT_IllegalArgumentException_Constructor() { - assertThrows(IllegalArgumentException.class, - () -> new ExchangeLoader("resources/notexistantfile.csv")); - } - -} diff --git a/src/test/java/edu/ntnu/idi/idatt/storage/StockParserTest.java b/src/test/java/edu/ntnu/idi/idatt/storage/StockParserTest.java new file mode 100644 index 0000000..102cf7b --- /dev/null +++ b/src/test/java/edu/ntnu/idi/idatt/storage/StockParserTest.java @@ -0,0 +1,68 @@ +package edu.ntnu.idi.idatt.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import edu.ntnu.idi.idatt.model.market.Stock; + +/** + * Test class for ExchangeLoader + * + *

+ * Tests the loading and saving of stock data. + *

+ */ +class StockParserTest { + + String file; + + @BeforeEach + public void PT_setup() throws IOException { + + InputStream is = getClass() + .getClassLoader() + .getResourceAsStream("stocks.csv"); + + Path tempFile = Files.createTempFile("stocks", ".csv"); + Files.copy(is, tempFile, StandardCopyOption.REPLACE_EXISTING); + + file = tempFile.toFile().toPath().toString(); + + } + + /** + * Positive test for loading/reading stocks + */ + @Test + void PT_load() { + List stocks = null; + try { + stocks = StockParser.load(file); + } catch (IOException e) { + e.printStackTrace(); + } + + assertEquals(4, stocks.size()); + + } + + /** + * Negative tests reading stocks. + */ + @Test + void NT_IllegalArgumentException_Constructor() { + assertThrows(IOException.class, + () -> StockParser.load("resources/notexistantfile.csv")); + } + +} From d294f512a14ed5452a8ec64a6e3134e6506a7994 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 9 May 2026 23:41:37 +0200 Subject: [PATCH 058/157] chore(Launcher): remove old code. --- src/main/java/edu/ntnu/idi/idatt/Launcher.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/Launcher.java b/src/main/java/edu/ntnu/idi/idatt/Launcher.java index f13f7fd..9fc9145 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Launcher.java +++ b/src/main/java/edu/ntnu/idi/idatt/Launcher.java @@ -1,6 +1,5 @@ package edu.ntnu.idi.idatt; -import edu.ntnu.idi.idatt.storage.CSVtoJSON; import edu.ntnu.idi.idatt.view.SceneManager; import edu.ntnu.idi.idatt.view.entry.StartController; import edu.ntnu.idi.idatt.view.entry.StartModel; @@ -34,8 +33,6 @@ public void start(Stage stage) { SceneManager.init(stage, view.getInstance()); stage.show(); - CSVtoJSON ja = new CSVtoJSON(); - ja.csvFile(); } } From 666b6647beef3c0a2a7b4f9bd917f3ce3093a3f5 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 9 May 2026 23:42:06 +0200 Subject: [PATCH 059/157] chore: JavaDocs cleanup. --- src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java b/src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java index 88bdfeb..923df9f 100644 --- a/src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java @@ -91,7 +91,7 @@ public BigDecimal getLatestPriceChange() { } /** - * Getter for sale price + * Getter for current sale price * * @return - BigDecimal with current (newest in array) stock price. */ From dfd25d7d62dd1fa93b4a63cc42d2e9a1b2a7a8cd Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 9 May 2026 23:42:51 +0200 Subject: [PATCH 060/157] refactor(UserSession): Add Exchange as session information. --- .../ntnu/idi/idatt/session/UserSession.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/main/java/edu/ntnu/idi/idatt/session/UserSession.java b/src/main/java/edu/ntnu/idi/idatt/session/UserSession.java index c8a2863..841a22a 100644 --- a/src/main/java/edu/ntnu/idi/idatt/session/UserSession.java +++ b/src/main/java/edu/ntnu/idi/idatt/session/UserSession.java @@ -1,5 +1,6 @@ package edu.ntnu.idi.idatt.session; +import edu.ntnu.idi.idatt.model.Exchange; import edu.ntnu.idi.idatt.model.player.Player; public class UserSession { @@ -19,6 +20,7 @@ public static UserSession getInstance() { } private Player player; + private Exchange exchange; public Player getPlayer() { return player; @@ -28,4 +30,36 @@ public void setPlayer(Player player) { this.player = player; } + public Exchange getExchange() { + return exchange; + } + + public void setExchange(Exchange exchange) { + this.exchange = exchange; + } + + public SessionBundle getSession() { + return new SessionBundle(player, exchange); + } + + public class SessionBundle { + + private Player player; + private Exchange exchange; + + public SessionBundle(Player player, Exchange exchange) { + this.player = player; + this.exchange = exchange; + } + + public Player getPlayer() { + return player; + } + + public Exchange getExchange() { + return exchange; + } + + } + } From 625e9363961d217a4c6add4738d1a483e666f847 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 9 May 2026 23:43:23 +0200 Subject: [PATCH 061/157] feat(StorageFile): Class for persistent storage file placement. --- .../ntnu/idi/idatt/storage/StorageFile.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/storage/StorageFile.java diff --git a/src/main/java/edu/ntnu/idi/idatt/storage/StorageFile.java b/src/main/java/edu/ntnu/idi/idatt/storage/StorageFile.java new file mode 100644 index 0000000..9fa36e7 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/storage/StorageFile.java @@ -0,0 +1,75 @@ +package edu.ntnu.idi.idatt.storage; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Utility class for system-specific path obtaining. + */ +public class StorageFile { + private static final String STORAGE_FOLDER = "Millions"; + + /** + * Method for obtaining storage file path. + * + * @return Path object of storage file. + */ + public static Path getStorageFile() { + return getAppDataDirectory().resolve("storage.json"); + } + + /** + * Method for ensuring that storage directory exists. + * + *

+ * Attempts to create the application data directory if + * one does not exist. + *

+ */ + public static void ensureAppDataDirectoryExists() { + Path dir = getAppDataDirectory(); + try { + Files.createDirectories(dir); + + Path storageFile = dir.resolve("storage.json"); + if (!Files.exists(storageFile)) { + Files.createFile(storageFile); + } + } catch (IOException e) { + throw new RuntimeException("Could not create app data directory: " + dir, e); + } + } + + /** + * Method for obtaining system-specific directory for application data. + * + * @return Path object of the found directory. + */ + private static Path getAppDataDirectory() { + String userHome = System.getProperty("user.home"); + String os = System.getProperty("os.name").toLowerCase(); + + // AppData folder for windows + if (os.contains("win")) { + String appData = System.getenv("APPDATA"); + + if (appData != null) { + return Paths.get(appData, STORAGE_FOLDER); + } + // If AppData not a environmental variable, sets to user directory. + return Paths.get(userHome, STORAGE_FOLDER); + + } + // Library folder in MacOS + if (os.contains("mac")) { + return Paths.get(userHome, "Library", "Application Support", STORAGE_FOLDER); + } + + // All linux and unix-like systems. + return Paths.get(userHome, ".local", "share", STORAGE_FOLDER); + + } + +} From 302f33c5773c2e863b4238ab87670d52c0960441 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 9 May 2026 23:44:15 +0200 Subject: [PATCH 062/157] feat(SessionManager): Class for managing current session data + persistance. --- .../idi/idatt/storage/SessionManager.java | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/storage/SessionManager.java diff --git a/src/main/java/edu/ntnu/idi/idatt/storage/SessionManager.java b/src/main/java/edu/ntnu/idi/idatt/storage/SessionManager.java new file mode 100644 index 0000000..d2f1e55 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/storage/SessionManager.java @@ -0,0 +1,129 @@ +package edu.ntnu.idi.idatt.storage; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; + +import edu.ntnu.idi.idatt.model.Exchange; +import edu.ntnu.idi.idatt.model.player.Player; +import edu.ntnu.idi.idatt.session.UserSession; +import edu.ntnu.idi.idatt.session.UserSession.SessionBundle; + +/** + * Class for managing user sessions. + * + *

+ * Utilizes gson serialization and deserialization to manage + * player and game state. + */ +public class SessionManager { + + // Gson type to list format. Instead of using wrapper class. + private static Type SESSION_BUNDLE_TYPE = new TypeToken>() { + }.getType(); + + private static Gson gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + + // Static initiator to ensure persistent storage file. + static { + StorageFile.ensureAppDataDirectoryExists(); + } + + /** + * Method for setting new session. + * + * @see UserSession + */ + public static void newSession(Player player, Exchange exchange) { + UserSession.getInstance().setPlayer(player); + UserSession.getInstance().setExchange(exchange); + } + + /** + * Method for saving current session. + */ + public static void saveSession() { + // don't save if current session is null accidentally + if (UserSession.getInstance().getPlayer() == null || UserSession.getInstance().getExchange() == null) { + return; + } + + // Load all sessions + List bundles = loadAllSessions(); + + try (Writer writer = new FileWriter(StorageFile.getStorageFile().toFile())) { + + // Append current session + SessionBundle existing = bundles.stream() + .filter(s -> s.getPlayer().getName().equals(UserSession.getInstance().getPlayer().getName())) + .findFirst().orElse(null); + + if (existing != null) { + bundles.set(bundles.indexOf(existing), UserSession.getInstance().getSession()); + } else { + bundles.add(UserSession.getInstance().getSession()); + } + + gson.toJson(bundles, writer); + + } catch (IOException e) { + throw new RuntimeException("Failed to save current session!", e); + } + } + + /** + * Method for loading a user session by player name. + * + * @return if session was found or not. + */ + public static boolean loadSession(String playerName) { + + List bundles = loadAllSessions(); + + for (SessionBundle session : bundles) { + if (session.getPlayer().getName().equals(playerName)) { + UserSession.getInstance().setPlayer(session.getPlayer()); + UserSession.getInstance().setExchange(session.getExchange()); + return true; + } + } + return false; + } + + /** + * Method for serialization of all sessions. + * + * @return List or empty list if none entires were made. + */ + private static List loadAllSessions() { + try { + if (!Files.exists(StorageFile.getStorageFile()) || Files.size(StorageFile.getStorageFile()) == 0) { + return new ArrayList<>(); + } + } catch (IOException e) { + return new ArrayList<>(); + } + + try { + return gson.fromJson(new FileReader(StorageFile.getStorageFile().toString()), SESSION_BUNDLE_TYPE); + } catch (JsonSyntaxException | JsonIOException | FileNotFoundException e) { + return new ArrayList<>(); + } + + } + +} From 03a918ecd937a6930aa3c138e6a83a8e790238c3 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 9 May 2026 23:45:03 +0200 Subject: [PATCH 063/157] feat(Start-MVC): Added persistent storage to start view. --- .../idi/idatt/view/entry/StartController.java | 48 +++++++++++++++---- .../ntnu/idi/idatt/view/entry/StartModel.java | 8 ++++ .../ntnu/idi/idatt/view/entry/StartView.java | 6 ++- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java index 52f7eeb..04eea82 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java @@ -1,10 +1,15 @@ package edu.ntnu.idi.idatt.view.entry; import java.io.File; +import java.io.IOException; import java.math.BigDecimal; +import java.util.List; +import edu.ntnu.idi.idatt.model.Exchange; +import edu.ntnu.idi.idatt.model.market.Stock; import edu.ntnu.idi.idatt.model.player.Player; -import edu.ntnu.idi.idatt.session.UserSession; +import edu.ntnu.idi.idatt.storage.SessionManager; +import edu.ntnu.idi.idatt.storage.StockParser; import edu.ntnu.idi.idatt.view.SceneManager; import edu.ntnu.idi.idatt.view.components.AbstractController; import edu.ntnu.idi.idatt.view.primary.MainView; @@ -28,14 +33,31 @@ public void obtainCSVFile() { public void initializeGame() { model.getError().set(" "); // Empty buffers - if (model.getName().get() == null || model.getBalance().get() == null) { - model.getError().set("Name and/or balance fields can't be empty"); + if (model.getName().get() == null) { + model.getError().set("Name field can't be empty"); return; } - if (model.getName().get().isBlank() || - model.getBalance().get().isBlank()) { - model.getError().set("Name and/or balance fields can't be empty"); + if (model.getName().get().isBlank()) { + model.getError().set("Name field can't be empty!"); + return; + } + + boolean loadResult = SessionManager.loadSession(model.getName().get()); + if (loadResult) { + SceneManager.switchTo(new MainView().getInstance()); + return; + } else { + model.isNewGame().set(true); + } + + if (model.getBalance().get() == null) { + model.getError().set("Balance field can't be empty"); + return; + } + + if (model.getBalance().get().isBlank()) { + model.getError().set("Balance field can't be empty!"); return; } @@ -52,9 +74,19 @@ public void initializeGame() { return; } - UserSession.getInstance().setPlayer(new Player(model.getName().get(), balance)); - SceneManager.switchTo(new MainView().getInstance()); + List stocks; + try { + stocks = StockParser.load(csv.getPath()); + } catch (IOException e) { + model.getError().set(e.getMessage()); + return; + } + Player player = new Player(model.getName().get(), balance); + Exchange exchange = new Exchange(player.getName(), stocks); + SessionManager.newSession(player, exchange); + SessionManager.saveSession(); + SceneManager.switchTo(new MainView().getInstance()); } } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartModel.java b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartModel.java index e1b0f92..3e4c4ed 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartModel.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartModel.java @@ -1,6 +1,8 @@ package edu.ntnu.idi.idatt.view.entry; import edu.ntnu.idi.idatt.view.components.Model; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -11,6 +13,8 @@ public class StartModel implements Model { private final StringProperty error = new SimpleStringProperty(); private final StringProperty fileName = new SimpleStringProperty(); + private final BooleanProperty newGame = new SimpleBooleanProperty(); + public StringProperty getName() { return name; } @@ -27,4 +31,8 @@ public StringProperty getFileName() { return fileName; } + public BooleanProperty isNewGame() { + return newGame; + } + } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartView.java b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartView.java index 60180b1..f32cc87 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartView.java @@ -24,6 +24,7 @@ public class StartView extends AbstractView { private Label errorLabel; // Global buttons for controller implementation + private VBox newGameWrapper; private Button csvButton; private Button startButton; @@ -44,6 +45,7 @@ public Parent createContent() { // Create and style wrappers VBox wrapper = new VBox(); + newGameWrapper = new VBox(); HBox playerWrapper = new HBox(); HBox balanceWrapper = new HBox(); playerWrapper.setAlignment(Pos.CENTER_LEFT); @@ -98,7 +100,8 @@ public Parent createContent() { VBox.setVgrow(filler, Priority.ALWAYS); // Wrap components together and adjust positioning - wrapper.getChildren().addAll(List.of(playerWrapper, balanceWrapper, csvWrapper)); + newGameWrapper.getChildren().addAll(balanceWrapper, csvWrapper); + wrapper.getChildren().addAll(playerWrapper, newGameWrapper); wrapper.setAlignment(Pos.BASELINE_LEFT); root.getChildren().addAll(List.of(title, wrapper, filler, errorLabel, startButton)); @@ -111,6 +114,7 @@ public void setModel(StartModel model) { this.balanceField.textProperty().bindBidirectional(model.getBalance()); this.errorLabel.textProperty().bind(model.getError()); this.fileLabel.textProperty().bind(model.getFileName()); + this.newGameWrapper.visibleProperty().bind(model.isNewGame()); } public void setController(StartController controller) { From a2d57143f6f4acf61315c19cdd946303a9c4a80e Mon Sep 17 00:00:00 2001 From: Pawel Sapula Date: Sat, 9 May 2026 23:56:06 +0200 Subject: [PATCH 064/157] Update README.md --- README.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ab3bf21..01d477e 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,72 @@ What you can expect from the game: - Something 3 # Packages -- Main package: `/edu/ntnu/idi/idatt/` -- Base classes: `model/` -- Game classes: `game/` -- GUI elements: `view/` +``` +. +├── java +│   └── edu +│   └── ntnu +│   └── idi +│   └── idatt +│   ├── common +│   │   └── Observer.java +│   ├── Launcher.java +│   ├── model +│   │   ├── Exchange.java +│   │   ├── market +│   │   │   └── Stock.java +│   │   ├── player +│   │   │   └── Player.java +│   │   ├── portfolio +│   │   │   ├── Portfolio.java +│   │   │   └── Share.java +│   │   └── transaction +│   │   ├── Purchase.java +│   │   ├── Sale.java +│   │   ├── TransactionArchive.java +│   │   └── Transaction.java +│   ├── service +│   │   └── transaction +│   │   ├── PurchaseCalculator.java +│   │   ├── SaleCalculator.java +│   │   └── TransactionCalculator.java +│   ├── session +│   │   └── UserSession.java +│   ├── storage +│   │   ├── SessionManager.java +│   │   ├── StockParser.java +│   │   └── StorageFile.java +│   └── view +│   ├── components +│   │   ├── AbstractController.java +│   │   ├── AbstractView.java +│   │   ├── AbstractViewUI.java +│   │   ├── elements +│   │   │   ├── IconComponent.java +│   │   │   └── SearchBarComponent.java +│   │   ├── Model.java +│   │   ├── primitives +│   │   │   └── ActionEventHandler.java +│   │   └── ui +│   ├── entry +│   │   ├── StartController.java +│   │   ├── StartModel.java +│   │   └── StartView.java +│   ├── primary +│   │   └── MainView.java +│   └── SceneManager.java +└── resources + ├── icons + │   ├── portfolio.png + │   ├── quit.png + │   ├── search.png + │   └── user.png + ├── save.json + ├── stocks.csv + ├── themes + │   └── default.css + └── user.png +``` # How to run - Running program: `mvn clean javafx:run` From 00765f5e3600d8185af171798d8b5f3dec657e19 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 10 May 2026 13:29:37 +0200 Subject: [PATCH 065/157] feat: add UIElementCompositor class for UI building. --- .../components/ui/UIElementCompositor.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIElementCompositor.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIElementCompositor.java b/src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIElementCompositor.java new file mode 100644 index 0000000..0fed8e6 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIElementCompositor.java @@ -0,0 +1,85 @@ +package edu.ntnu.idi.idatt.view.components.ui; + +import java.util.ArrayList; +import java.util.List; + +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + +public class UIElementCompositor { + + private final Pane parent; + private final List elements; + + public UIElementCompositor(Builder builder) { + this.parent = builder.parent; + this.elements = builder.elements; + } + + public Parent makeUI() { + parent.getChildren().addAll(elements); + return parent; + } + + public static class Builder { + private Pane parent; + private ArrayList elements = new ArrayList<>(); + + public Builder parent(Pane parent) { + this.parent = parent; + return this; + } + + public Builder growWithAlignment(Pos position) { + if (parent instanceof HBox) { + ((HBox) parent).setAlignment(position); + parent.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(parent, Priority.ALWAYS); + } + if (parent instanceof VBox) { + ((VBox) parent).setAlignment(position); + parent.setMaxHeight(Double.MAX_VALUE); + VBox.setVgrow(parent, Priority.ALWAYS); + } + return this; + } + + public Builder setPrefSize(double width, double height) { + parent.setPrefSize(width, height); + return this; + } + + public Builder addContent(Parent parent) { + elements.add(parent); + return this; + } + + public Builder addAllContent(Parent... parents) { + elements.addAll(List.of(parents)); + return this; + } + + public Builder filler() { + Region filler = new Region(); + if (parent instanceof HBox) { + HBox.setHgrow(filler, Priority.ALWAYS); + } + if (parent instanceof VBox) { + VBox.setVgrow(filler, Priority.ALWAYS); + } + elements.add(filler); + return this; + } + + public UIElementCompositor build() { + return new UIElementCompositor(this); + } + + } + +} From 6bf236f8280071cd822293109f79d468df2b3f88 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 10 May 2026 13:30:04 +0200 Subject: [PATCH 066/157] refactor: MainView to utilize the new mechanism. --- .../idatt/view/components/AbstractViewUI.java | 16 ++-- .../ntnu/idi/idatt/view/primary/MainView.java | 95 ++++++++++--------- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java index 1b7b889..1933e3b 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/AbstractViewUI.java @@ -15,6 +15,7 @@ public abstract class AbstractViewUI extends AbstractView { private VBox navigation; private HBox header; private HBox toolbar; + private VBox menu; private SimpleBooleanProperty isMenuVisible = new SimpleBooleanProperty(false); public AbstractViewUI() { @@ -35,8 +36,7 @@ public AbstractViewUI() { this.setInstance(new StackPane()); - Parent menu = createMenu(); - StackPane.setAlignment(menu, Pos.CENTER_RIGHT); + menu.getChildren().add(createMenu()); menu.setVisible(false); Region disableMenu = new Region(); @@ -59,16 +59,20 @@ public void createUIComponents() { navigation = new VBox(); navigation.setMaxHeight(Double.MAX_VALUE); navigation.getStyleClass().add("dark"); - navigation.setPrefWidth(150); + navigation.setMaxWidth(150); header = new HBox(); header.getStyleClass().add("light"); - header.setPrefHeight(80); + header.setMaxHeight(80); toolbar = new HBox(); toolbar.getStyleClass().add("light"); - HBox.setHgrow(toolbar, Priority.ALWAYS); - toolbar.setPrefHeight(80); + toolbar.setMaxHeight(80); + + menu = new VBox(); + StackPane.setAlignment(menu, Pos.CENTER_RIGHT); + menu.getStyleClass().add("dark"); + menu.setMaxWidth(300); } public abstract Parent createContent(); diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java index d3a151d..b6187ec 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/MainView.java @@ -4,14 +4,13 @@ import edu.ntnu.idi.idatt.view.components.AbstractViewUI; import edu.ntnu.idi.idatt.view.components.elements.IconComponent; import edu.ntnu.idi.idatt.view.components.elements.SearchBarComponent; +import edu.ntnu.idi.idatt.view.components.ui.UIElementCompositor; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Parent; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; @@ -26,89 +25,99 @@ public Parent createContent() { @Override public Parent createNavigation() { - VBox navigation = new VBox(); Label title = new Label("Title"); - title.getStyleClass().add("big-text-32"); - Label newspaper = new Label(" • Newspaper"); + + title.getStyleClass().add("big-text-32"); newspaper.getStyleClass().add("med-text-16"); - navigation.getChildren().addAll(title, newspaper); - return navigation; + UIElementCompositor navigation = new UIElementCompositor.Builder() + .parent(new VBox()) + .addAllContent(title, newspaper) + .build(); + + return navigation.makeUI(); } @Override public Parent createHeader() { - this.getHeader().setAlignment(Pos.CENTER); SearchBarComponent bar = new SearchBarComponent("Search after stocks.."); - bar.onSearchQuery(() -> System.out.println(bar.getQuery())); + HBox.setMargin(bar, new Insets(20)); - return bar; + + UIElementCompositor header = new UIElementCompositor.Builder() + .parent(new HBox()) + .growWithAlignment(Pos.CENTER) + .filler() + .addContent(bar) + .filler() + .build(); + + bar.onSearchQuery(() -> System.out.println(bar.getQuery())); + + return header.makeUI(); } @Override public Parent createToolbar() { - HBox bar = new HBox(); - bar.setAlignment(Pos.CENTER); - bar.setMaxWidth(Double.MAX_VALUE); - HBox.setHgrow(bar, Priority.ALWAYS); - Label balance = new Label(" Money: " + UserSession.getInstance().getPlayer().getMoney().toString() + " USD"); - balance.getStyleClass().add("med-text-16"); + Label playerStatus = new Label("Status:" + UserSession.getInstance().getPlayer().getStatus()); + Label playerNetWorth = new Label( + "Net Worth: " + UserSession.getInstance().getPlayer().getNetWorth().toString() + " USD"); - Region filler = new Region(); - HBox.setHgrow(filler, Priority.ALWAYS); - - VBox infoWrapper = new VBox(); - infoWrapper.setAlignment(Pos.CENTER); - - Label playerStatus = new Label(); - playerStatus.setText("Status:" + UserSession.getInstance().getPlayer().getStatus()); + balance.getStyleClass().add("med-text-16"); playerStatus.getStyleClass().add("med-text-16"); - - Label playerNetWorth = new Label(); - playerNetWorth.setText("Net Worth: " + UserSession.getInstance().getPlayer().getNetWorth().toString() + " USD"); playerNetWorth.getStyleClass().add("med-text-16"); + VBox infoWrapper = new VBox(); + infoWrapper.setAlignment(Pos.CENTER); infoWrapper.getChildren().addAll(playerStatus, playerNetWorth); - HBox iconWrapper = new HBox(); - iconWrapper.setAlignment(Pos.CENTER); - Image userImg = new Image(this.getClass().getResource("/icons/user.png").toExternalForm()); Image quitImg = new Image(this.getClass().getResource("/icons/quit.png").toExternalForm()); IconComponent userIcon = new IconComponent(userImg, null, 44); IconComponent quitIcon = new IconComponent(quitImg, null, 44); + HBox iconWrapper = new HBox(); + iconWrapper.setAlignment(Pos.CENTER); + iconWrapper.getChildren().addAll(userIcon, quitIcon); + + UIElementCompositor toolbar = new UIElementCompositor.Builder() + .parent(new HBox()) + .growWithAlignment(Pos.CENTER) + .addContent(balance) + .filler() + .addAllContent(infoWrapper, iconWrapper) + .build(); + userIcon.onIconClick(() -> toggleMenu()); - iconWrapper.getChildren().addAll(userIcon, quitIcon); + return toolbar.makeUI(); - bar.getChildren().addAll(balance, filler, infoWrapper, iconWrapper); - return bar; } @Override public Parent createMenu() { - VBox menu = new VBox(); - menu.setMaxSize(300, Double.MAX_VALUE); - menu.getStyleClass().add("dark"); - menu.setAlignment(Pos.TOP_CENTER); Image menuImg = new Image(this.getClass().getResource("/icons/user.png").toExternalForm()); - IconComponent menuIcon = new IconComponent(menuImg, "Account", 60); - menuIcon.getStyleClass().add("big-text-32"); + IconComponent menuIcon = new IconComponent(menuImg, "Account", 60); Label portfolio = new Label(" • Portfolio"); - portfolio.getStyleClass().add("med-text-16"); - Label transactions = new Label(" • Transactions"); + + menuIcon.getStyleClass().add("big-text-32"); + portfolio.getStyleClass().add("med-text-16"); transactions.getStyleClass().add("med-text-16"); - menu.getChildren().addAll(menuIcon, portfolio, transactions); + UIElementCompositor menu = new UIElementCompositor.Builder() + .parent(new VBox()) + .growWithAlignment(Pos.TOP_CENTER) + .setPrefSize(300, Double.MAX_VALUE) + .addAllContent(menuIcon, portfolio, transactions) + .build(); - return menu; + return menu.makeUI(); } } From 8d113105caacdf4daf83bcb2eb1860969b3e2e60 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 10 May 2026 19:47:09 +0200 Subject: [PATCH 067/157] feat: Implement SceneFactory class for simplyfing scene managing. --- .../edu/ntnu/idi/idatt/view/SceneFactory.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/SceneFactory.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/SceneFactory.java b/src/main/java/edu/ntnu/idi/idatt/view/SceneFactory.java new file mode 100644 index 0000000..c68f6b2 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/SceneFactory.java @@ -0,0 +1,23 @@ +package edu.ntnu.idi.idatt.view; + +import edu.ntnu.idi.idatt.view.entry.StartController; +import edu.ntnu.idi.idatt.view.entry.StartModel; +import edu.ntnu.idi.idatt.view.entry.StartView; +import javafx.scene.Parent; + +public class SceneFactory { + + public static Parent createStartView() { + + StartModel model = new StartModel(); + StartView view = new StartView(); + StartController controller = new StartController(model); + + view.setModel(model); + view.setController(controller); + + return view.getInstance(); + + } + +} From adc899ccc015f62fc0c5a5cdc635b7493eee015a Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 10 May 2026 19:47:35 +0200 Subject: [PATCH 068/157] refactor: Launching of primary scene. --- src/main/java/edu/ntnu/idi/idatt/Launcher.java | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/Launcher.java b/src/main/java/edu/ntnu/idi/idatt/Launcher.java index 9fc9145..18c49a6 100644 --- a/src/main/java/edu/ntnu/idi/idatt/Launcher.java +++ b/src/main/java/edu/ntnu/idi/idatt/Launcher.java @@ -1,9 +1,7 @@ package edu.ntnu.idi.idatt; +import edu.ntnu.idi.idatt.view.SceneFactory; import edu.ntnu.idi.idatt.view.SceneManager; -import edu.ntnu.idi.idatt.view.entry.StartController; -import edu.ntnu.idi.idatt.view.entry.StartModel; -import edu.ntnu.idi.idatt.view.entry.StartView; import javafx.application.Application; import javafx.stage.Stage; @@ -24,14 +22,7 @@ public void start(Stage stage) { stage.setHeight(700); stage.setTitle("Stock Game"); - StartModel model = new StartModel(); - StartView view = new StartView(); - StartController controller = new StartController(model); - - view.setModel(model); - view.setController(controller); - - SceneManager.init(stage, view.getInstance()); + SceneManager.init(stage, SceneFactory.createStartView()); stage.show(); } From 1c3246e86fa661203c8282c5a7ec96fbfef8457c Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sun, 10 May 2026 19:48:21 +0200 Subject: [PATCH 069/157] feat: Implement UIFactory for repetative UI creation. --- .../idatt/view/components/ui/UIFactory.java | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIFactory.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIFactory.java b/src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIFactory.java new file mode 100644 index 0000000..48a08e4 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIFactory.java @@ -0,0 +1,140 @@ +package edu.ntnu.idi.idatt.view.components.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import edu.ntnu.idi.idatt.session.UserSession; +import edu.ntnu.idi.idatt.view.components.elements.IconComponent; +import edu.ntnu.idi.idatt.view.components.elements.SearchBarComponent; +import edu.ntnu.idi.idatt.view.components.primitives.ActionEventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +public class UIFactory { + + public static Parent createHeader(String placeholder, Consumer onSearchQuery) { + SearchBarComponent bar = new SearchBarComponent(placeholder); + + HBox.setMargin(bar, new Insets(20)); + + UIElementCompositor header = new UIElementCompositor.Builder() + .parent(new HBox()) + .growWithAlignment(Pos.CENTER) + .filler() + .addContent(bar) + .filler() + .build(); + + bar.onSearchQuery(() -> onSearchQuery.accept(bar.getQuery())); // Remake? + + return header.makeUI(); + } + + public static Parent createNavigation(String title, List buttonLables, ActionEventHandler... handlers) { + if (buttonLables.size() != handlers.length) { + System.out.println("Failed to build navigation!"); + } + + Label titleLabel = new Label(title); + titleLabel.getStyleClass().add("big-text-32"); + + ArrayList