diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java index 0e2d3cb..6162207 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java @@ -9,6 +9,7 @@ import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; import edu.ntnu.idi.idatt2003.g40.mappe.utils.ConfigValues; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewManager; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameController; import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameView; import edu.ntnu.idi.idatt2003.g40.mappe.view.mainmenu.MainMenuController; import edu.ntnu.idi.idatt2003.g40.mappe.view.mainmenu.MainMenuView; @@ -19,12 +20,18 @@ import java.math.BigDecimal; import java.util.List; import java.util.Objects; + +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.WidgetEnum; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.financialsummary.SummaryController; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.financialsummary.SummaryView; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.dashboard.DashBoardController; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.dashboard.DashBoardView; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market.MarketController; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market.MarketView; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.*; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.ClickerGame; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.FindStockGame; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.TimeInputsGame; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.stats.StatsController; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.stats.StatsView; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.topbar.TopBarController; @@ -32,7 +39,6 @@ import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.transactions.TransactionsController; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.transactions.TransactionsView; import javafx.application.Application; -import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.text.Font; @@ -72,7 +78,7 @@ public void start(final Stage stage) throws Exception { ViewManager viewManager = new ViewManager(stage, eventManager); List stocksInFile; - FileParser parser1 = new FileParser("/dummydata.txt"); + FileParser parser1 = new FileParser("/sp500.csv"); FileConverter converter1 = new FileConverter(); stocksInFile = converter1.getStocksFromStrings(parser1.readFile()); @@ -131,6 +137,10 @@ public void start(final Stage stage) throws Exception { // In-game (Change "topBarView" to "topBarView2" if no summary section). // Dashboard er default center-view. InGameView inGameView = new InGameView(topBarView, dashBoardView.getRootPane()); + InGameController inGameController = new InGameController( + inGameView, + eventManager + ); // Transaction history page TransactionsView transactionsView = new TransactionsView(); @@ -139,6 +149,34 @@ public void start(final Stage stage) throws Exception { eventManager, player.getTransactionArchive()); + ClickerGame clickerGame = new ClickerGame(); + FindStockGame findStockGame = new FindStockGame( + stocksInFile.stream() + .map(Stock::getSymbol) + .toList() + ); + TimeInputsGame timeInputsGame = new TimeInputsGame(); + + GameEngineView gameEngineView = new GameEngineView(); + GameEngineController gameEngineController = new GameEngineController( + gameEngineView, + eventManager, + stocksInFile.getFirst() + ); + + MiniGamesView miniGamesView = new MiniGamesView(); + new MiniGamesController( + miniGamesView, + eventManager, + stocksInFile.getFirst(), + gameEngineView, + gameEngineController, + clickerGame, + inGameView, + findStockGame, + timeInputsGame + ); + // Wire top bar buttons til å bytte mellom dashboard / stats / market / // transactions. Stats-knappen tar deg til stats-siden. topBarController.setMarketIntegration( @@ -147,7 +185,9 @@ public void start(final Stage stage) throws Exception { marketView.getRootPane(), statsView.getRootPane(), transactionsView.getRootPane(), - transactionsController::refresh); + transactionsController::refresh, + miniGamesView.getRootPane() + ); // Register all views viewManager.addView(mainMenuView); @@ -156,6 +196,14 @@ public void start(final Stage stage) throws Exception { viewManager.addView(inGameView); viewManager.setScene(mainMenuView); + // Register all widgets + inGameController.addwidget(WidgetEnum.DASHBOARD, dashBoardView.getRootPane()); + inGameController.addwidget(WidgetEnum.MARKET, marketView.getRootPane()); + inGameController.addwidget(WidgetEnum.MINIGAMES_OVERVIEW, miniGamesView.getRootPane()); + inGameController.addwidget(WidgetEnum.MINIGAMES_ENGINE, gameEngineView.getRootPane()); + inGameController.addwidget(WidgetEnum.STATS, statsView.getRootPane()); + inGameController.addwidget(WidgetEnum.TRANSACTIONS, transactionsView.getRootPane()); + stage.show(); } } \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java index 9c5796a..b73f634 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java @@ -208,7 +208,9 @@ public Transaction sell(final Share share, final Player player) * * @throws IllegalArgumentException if any parameter is null, or if player does not have enough shares. * */ - public List sell(BigDecimal amount, final String stockSymbol, final Player player) + public List sell(BigDecimal amount, + final String stockSymbol, + final Player player) throws IllegalArgumentException { if (amount == null || player == null || !Validator.NOT_EMPTY.isValid(stockSymbol)) { throw new IllegalArgumentException("Invalid sell!"); @@ -253,7 +255,8 @@ public void advance() { for (Stock stock : stockMap.values()) { BigDecimal currentPrice = stock.getSalesPrice(); - double change = (random.nextDouble() * 0.10) - 0.05; + double change = ((random.nextDouble() * 0.10) - 0.05) + stock.getFortune(); + stock.setFortune(0); BigDecimal factor = BigDecimal.valueOf(1 + change); BigDecimal newPrice = currentPrice.multiply(factor); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java index 11a444e..1ca9664 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java @@ -26,6 +26,14 @@ public final class Stock { * */ private final List prices = new ArrayList<>(); + /** + * Value indicating this stocks fortune. + * + *

Higher fortune yields a higher + * increase when stock market changes price.

+ * */ + private double fortune; + /** * Creates a new {@code Stock} with an initial sales price. * @@ -42,10 +50,29 @@ public Stock(final String symbol, } else { this.symbol = symbol; this.company = company; + this.fortune = 0; prices.add(salesPrice); } } + /** + * Setter method for fortune. + * + * @param newFortune the new value to set this stocks' fortune. + * */ + public void setFortune(final double newFortune) { + fortune = newFortune; + } + + /** + * Getter method for fortune. + * + * @return fortune. + * */ + public double getFortune() { + return fortune; + } + /** * Returns the stock symbol. * diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java index 4231866..ace4933 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java @@ -22,7 +22,35 @@ public enum EventType implements EventChannel { * @see edu.ntnu.idi.idatt2003.g40.mappe.view.ViewManager * */ - SCENE_CHANGE; + SCENE_CHANGE, + + /** + * Event type representing events that causes the center view in the + * current {@link edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameView} + * object to change. + * + *

Primarily handled by the active + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameController} + * object.

+ * + * @see edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameController + * + */ + CHANGE_INGAME_CENTER, + + /** + * Event type representing events that change the center view to + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesView}, + * and selects a given stock for the minigames. + * + *

Primarily handled by the active + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesController} + * object.

+ * + * @see edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.dashboard.DashBoardController + * + */ + SELECT_STOCK_FOR_MINIGAME; /** * {@inheritDoc} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewController.java index fed6cfa..63a2ec3 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewController.java @@ -82,6 +82,17 @@ protected void changeScene(final ViewEnum viewName) { invoke(eventData, eventManager); } + /** + * Overloaded invoke method to reduce parameters, by using + * internal reference to event manager. + * + * @param the type of data to send. + * @param data the data to send. + * */ + protected void invoke(final EventData data) { + invoke(data, eventManager); + } + @Override public final void invoke(final EventData data, final EventManager eventManager) { diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/InGameController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/InGameController.java index eda5729..6c84780 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/InGameController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/InGameController.java @@ -1,21 +1,43 @@ package edu.ntnu.idi.idatt2003.g40.mappe.view.ingame; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventSubscriber; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.WidgetEnum; +import javafx.scene.Node; +import java.util.EnumMap; -public class InGameController extends ViewController { +public final class InGameController extends ViewController + implements EventSubscriber { + + private final EnumMap widgetMap = new EnumMap<>(WidgetEnum.class); /** * {@inheritDoc}. */ - protected InGameController(final InGameView viewElement, + public InGameController(final InGameView viewElement, final EventManager eventManager) throws IllegalArgumentException { super(viewElement, eventManager); + eventManager.addSubscriber(this, EventType.CHANGE_INGAME_CENTER); + } + + public void addwidget(final WidgetEnum widgetEnum, final Node widgetRoot) { + widgetMap.put(widgetEnum, widgetRoot); } @Override protected void initInteractions() { } + + @Override + public void handleEvent(final EventData data) { + if (!(data.data() instanceof WidgetEnum) || !widgetMap.containsKey(data.data())) { + throw new IllegalArgumentException("Invalid event thrown!"); + } + getViewElement().changeCenterView(widgetMap.get(data.data())); + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/InGameView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/InGameView.java index b3b00b7..29a298d 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/InGameView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/InGameView.java @@ -1,23 +1,12 @@ package edu.ntnu.idi.idatt2003.g40.mappe.view.ingame; -import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewEnum; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.topbar.TopBarView; -import javafx.geometry.Pos; import javafx.scene.Node; -import javafx.scene.chart.LineChart; -import javafx.scene.chart.NumberAxis; -import javafx.scene.chart.XYChart; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.Separator; -import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import java.util.ArrayList; - public class InGameView extends ViewElement { private final TopBarView topBarView; diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/WidgetEnum.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/WidgetEnum.java new file mode 100644 index 0000000..9bf85ac --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/WidgetEnum.java @@ -0,0 +1,39 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets; + +/** + * Enum used to define the various widgets for the application. + * + *

Primarily handled by the {@link edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameController} + * for changing the active section of the game.

+ * */ +public enum WidgetEnum { + /** + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.dashboard.DashBoardView}. + * */ + DASHBOARD, + + /** + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market.MarketView}. + * */ + MARKET, + + /** + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesView}. + * */ + MINIGAMES_OVERVIEW, + + /** + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameEngineView}. + * */ + MINIGAMES_ENGINE, + + /** + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.stats.StatsView}. + * */ + STATS, + + /** + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.transactions.TransactionsView}. + * */ + TRANSACTIONS +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardActions.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardActions.java index 75b6639..1e92502 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardActions.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardActions.java @@ -32,5 +32,10 @@ public enum DashBoardActions { /** * Increasing quantity of shares to buy/sell by five. * */ - INCREASE_5; + INCREASE_5, + + /** + * Selecting viewed stock to boost by playing minigames. + * */ + MINIGAME; } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java index 64280dc..9d31d0d 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java @@ -2,9 +2,12 @@ import edu.ntnu.idi.idatt2003.g40.mappe.engine.Exchange; import edu.ntnu.idi.idatt2003.g40.mappe.model.*; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType; import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.WidgetEnum; import javafx.scene.control.Button; import javafx.scene.control.TextFormatter; import java.math.BigDecimal; @@ -180,6 +183,11 @@ protected void initInteractions() { .add(new BigDecimal("5")).toString()); }); + getViewElement().setOnAction(DashBoardActions.MINIGAME, () -> { + invoke(new EventData<>(EventType.SELECT_STOCK_FOR_MINIGAME, getViewElement().getCurrentStock())); + invoke(new EventData<>(EventType.CHANGE_INGAME_CENTER, WidgetEnum.MINIGAMES_OVERVIEW)); + }); + exchange.weekProperty().addListener((observable,o,n) -> { getViewElement().updateGraph(selectedTimeRange); populateStockList(selectedFilter); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java index 6ba9bb8..d7edb2a 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java @@ -173,6 +173,11 @@ public final class DashBoardView extends ViewElement { * */ private Button sellSharesBtn; + /** + * Button to boost this stock. + * */ + private Button boostStockBtn; + /** * The selected stock symbol label. * */ @@ -268,7 +273,13 @@ protected void initLayout() { stockFullNameLabel = new Label(); ownedQuantityLabel = new Label(); - stockIdentity.getChildren().addAll(selectedStockLabel, stockFullNameLabel, ownedQuantityLabel); + boostStockBtn = new Button("Boost"); + stockIdentity.getChildren().addAll( + selectedStockLabel, + stockFullNameLabel, + ownedQuantityLabel, + boostStockBtn + ); priceStats = new VBox(); selectedStockPriceLabel = new Label(); @@ -334,6 +345,7 @@ protected void initLayout() { registerButton(DashBoardActions.DECREASE_1, m1QtyBtn); registerButton(DashBoardActions.INCREASE_1, p1QtyBtn); registerButton(DashBoardActions.INCREASE_5, p5QtyBtn); + registerButton(DashBoardActions.MINIGAME, boostStockBtn); } private ColumnConstraints makeCol(final float w) { @@ -349,6 +361,7 @@ protected void initStyling() { completeSideBar.getStyleClass().add("dashboard-complete-sidebar"); sidebar.getStyleClass().add("dashboard-sidebar-content"); selectedStockLabel.getStyleClass().add("dashboard-selected-stock-pill"); + boostStockBtn.getStyleClass().add("dashboard-selected-stock-pill"); selectedStockPriceLabel.getStyleClass().add("dashboard-selected-stock-pill"); shareQuantityInputField.getStyleClass().add("dashboard-qtyTextField"); chart.getStyleClass().add("dashboard-chart"); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java index 092e407..9c3b899 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java @@ -89,6 +89,7 @@ protected void initLayout() { navInfo.setAlignment(Pos.TOP_CENTER); nextBtn = new Button("next"); + nextBtn.setFocusTraversable(false); weekLabel = new Label("week: 1"); weekLabel.getStyleClass().add("week-label"); Region spacerR = new Region(); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/GameEngineController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/GameEngineController.java new file mode 100644 index 0000000..206cba1 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/GameEngineController.java @@ -0,0 +1,211 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames; + +import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.WidgetEnum; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.util.Duration; + +/** + * Controller for the {@link GameEngineView}. + * + *

Extends {@link ViewController}

+ * */ +public final class GameEngineController extends ViewController { + + /** + * Current score. + * */ + private int currentScore; + + /** + * Current frame for animation loop. + * */ + private int frameCounter; + + /** + * Current seconds remaining before minigame + * is over. + * */ + private int secondsRemaining; + + /** + * Current rank the player has for this minigame. + * */ + private char currentRank; + + /** + * String shown to describe the effect of the game, + * based on rank given during report. + * */ + private String rankEffectString; + + /** + * The amount of fortune to apply to the chosen stock, + * based on rank gotten. + * */ + private double fortuneToSet; + + /** + * Active {@link GameGimmick} implementation. + * */ + private GameGimmick activeGimmick; + + /** + * {@link Timeline} object functioning as an animation. + * */ + private Timeline engineLoop; + + /** + * Chosen stock to set fortune to based on game results (rank). + * */ + private Stock chosenStock; + + /** + * Whether the report is active or not. + * + *

If report is not active, the player can exit without affecting the market. + * If report is active, exiting will cause effect.

+ * */ + private boolean reportActive = false; + + /** + * Constructor. + * + * @param viewElement the associated {@link GameEngineView}. + * @param chosenStock the chosen {@link Stock} object. + * */ + public GameEngineController(final GameEngineView viewElement, + final EventManager eventManager, + final Stock chosenStock) throws IllegalArgumentException { + this.chosenStock = chosenStock; + super(viewElement, eventManager); + + engineLoop = new Timeline(new KeyFrame(Duration.millis(16.6), e -> { + activeGimmick.updateTick(); + + frameCounter++; + if (frameCounter >= 60) { + secondsRemaining--; + frameCounter = 0; + refreshMetrics(); + } + if (secondsRemaining <= 0) { + reportActive = true; + stopGameSession(reportActive); + } + })); + engineLoop.setCycleCount(Timeline.INDEFINITE); + } + + @Override + protected void initInteractions() { + getViewElement().setOnAction(MiniGamesActions.INGAME_QUIT, this::returnToOverview); + getViewElement().setOnAction(MiniGamesActions.INGAME_RETURN, this::returnToOverview); + } + + /** + * Launches a given {@link GameGimmick} session. + * + * @param gimmick the gimmick to launch. + * */ + public void launchGimmickSession(final GameGimmick gimmick) { + activeGimmick = gimmick; + currentScore = 0; + frameCounter = 0; + currentRank = 'E'; + reportActive = false; + secondsRemaining = 20; + refreshMetrics(); + + getViewElement().setGameGimmickContent(gimmick, (scoreDelta) -> { + if (this.currentScore + scoreDelta >= 0) { + this.currentScore += scoreDelta; + refreshMetrics(); + } + }); + engineLoop.play(); + } + + /** + * Setter method for chosen stock. + * + * @param newStock the new {@link Stock} object to set. + * */ + public void setChosenStock(final Stock newStock) { + chosenStock = newStock; + } + + /** + * Refreshes the metrics shown and updates appropriate values. + * */ + private void refreshMetrics() { + int hours = secondsRemaining / 3600; + int minutes = (secondsRemaining % 3600) / 60; + int seconds = secondsRemaining % 60; + String formattedTime = String.format("%02d:%02d:%02d", hours, minutes, seconds); + + if (currentScore >= 150) { + currentRank = 'S'; + rankEffectString = "Extremely good fortune awaits " + chosenStock.getSymbol(); + fortuneToSet = 0.10; + + } else if (currentScore >= 120) { + currentRank = 'A'; + rankEffectString = "Really good fortune awaits " + chosenStock.getSymbol(); + fortuneToSet = 0.5; + + } else if (currentScore >= 80) { + currentRank = 'B'; + rankEffectString = "Good fortune awaits " + chosenStock.getSymbol(); + fortuneToSet = 0.2; + + } else if (currentScore >= 50) { + currentRank = 'C'; + rankEffectString = "Bad fortune awaits " + chosenStock.getSymbol(); + fortuneToSet = -0.2; + + } else if (currentScore >= 20) { + currentRank = 'D'; + rankEffectString = "Really bad fortune awaits " + chosenStock.getSymbol(); + fortuneToSet = -0.5; + + } else { + currentRank = 'E'; + rankEffectString = "Extremely bad fortune awaits " + chosenStock.getSymbol(); + fortuneToSet = -0.10; + } + + getViewElement().updateMetadataDisplay(currentScore, formattedTime, currentRank); + } + + /** + * Stops the game session. + * + *

If showReport is true, fortune will be set. + * if not, market is unaffected.

+ * + * @param showReport value indicating whether to show report or not. + * */ + private void stopGameSession(final boolean showReport) { + if (engineLoop != null) { + engineLoop.stop(); + if(showReport) { + chosenStock.setFortune(fortuneToSet); + getViewElement().showGameReport(currentRank, rankEffectString); + } + } + } + + /** + * Changes widget to {@link MiniGamesView}. + * */ + private void returnToOverview() { + EventData eventData = new EventData<>(EventType.CHANGE_INGAME_CENTER, WidgetEnum.MINIGAMES_OVERVIEW); + invoke(eventData); + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/GameEngineView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/GameEngineView.java new file mode 100644 index 0000000..4a9a710 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/GameEngineView.java @@ -0,0 +1,164 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames; + +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement; +import java.util.function.IntConsumer; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +/** + * Minigame context class as described in the Strategy pattern. + * + *

Extends {@link ViewElement}

+ * */ +public final class GameEngineView + extends ViewElement { + + /** + * Quit button (does not generate results). + * */ + private Button quitButton; + + /** + * Label for current score. + * */ + private Label scoreLabel; + + /** + * Label for time left. + * */ + private Label timerLabel; + + /** + * Label for current rank. + * */ + private Label rankLabel; + + /** + * Container for score, timer and rank. + * */ + private HBox topDashboardBar; + + /** + * Container for final game status report. + * */ + private VBox reportContainer; + + /** + * Label for report title. + * */ + private Label reportTitleLabel; + + /** + * Label for result rank. + * */ + private Label rankResultLabel; + + /** + * Label for describing what effects on the stock will apply. + * */ + private Label stockEffectLabel; + + /** + * Return button on the report (generates results on the stock market). + * */ + private Button closeReportBtn; + + /** + * Constructor. + * */ + public GameEngineView() { + super(new BorderPane(), MiniGamesActions.class); + } + + /** + * Method for updating the game per tick with score, time and rank. + * + * @param score the score to show. + * @param time the time to show. + * @param rank the rank to show. + * */ + public void updateMetadataDisplay(final int score, + final String time, + final char rank) { + scoreLabel.setText("Score: " + score); + timerLabel.setText(time); + rankLabel.setText("Rank: " + rank); + } + + /** + * Sets the content of this view based on the game provided through the + * {@link GameGimmick} interface. + * + * @param gimmick a concrete implementation of the {@link GameGimmick} + * interface + * */ + public void setGameGimmickContent(final GameGimmick gimmick, final IntConsumer consumer) { + getRootPane().setCenter(gimmick.getCanvasNode()); + gimmick.initialize(consumer); + } + + /** + * Displays the report after a minigames timers is finished. + * + * @param rank the rank the player got. + * @param effect the effect caused by the results. + * */ + public void showGameReport(final char rank, + final String effect) { + getRootPane().setCenter(null); + + reportContainer = new VBox(); + reportContainer.getStyleClass().add("gameEngine-report-box"); + + reportTitleLabel = new Label("GAME OVER - REPORT"); + reportTitleLabel.getStyleClass().add("gameEngine-report-title"); + + rankResultLabel = new Label("Final Rank: " + rank); + rankResultLabel.getStyleClass().add("gameEngine-report-rank-result"); + + stockEffectLabel = new Label(effect); + stockEffectLabel.getStyleClass().add("gameEngine-report-effect"); + + reportContainer.getChildren().addAll(reportTitleLabel, rankResultLabel, stockEffectLabel, closeReportBtn); + getRootPane().setCenter(reportContainer); + } + + @Override + protected void initLayout() { + quitButton = new Button("Quit"); + quitButton.setPrefSize(100, 50); + quitButton.setFocusTraversable(false); + + scoreLabel = new Label("Score: 0"); + timerLabel = new Label("00:00:60"); + rankLabel = new Label("Rank: E"); + + HBox statsDisplay = new HBox(40, scoreLabel, timerLabel, rankLabel); + statsDisplay.setAlignment(Pos.CENTER); + + topDashboardBar = new HBox(20, quitButton, statsDisplay); + topDashboardBar.setAlignment(Pos.CENTER_LEFT); + topDashboardBar.setPadding(new javafx.geometry.Insets(15)); + closeReportBtn = new Button("Return"); + closeReportBtn.setFocusTraversable(false); + + getRootPane().setTop(topDashboardBar); + + registerButton(MiniGamesActions.INGAME_QUIT, quitButton); + registerButton(MiniGamesActions.INGAME_RETURN, closeReportBtn); + } + + @Override + protected void initStyling() { + getRootPane().getStyleClass().add("gameEngine-root"); + topDashboardBar.getStyleClass().add("gameEngine-topBar"); + scoreLabel.getStyleClass().add("gameEngine-topBar-Label"); + timerLabel.getStyleClass().add("gameEngine-topBar-Label"); + rankLabel.getStyleClass().add("gameEngine-topBar-Label"); + closeReportBtn.getStyleClass().add("gameEngine-report-closeButton"); + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/GameGimmick.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/GameGimmick.java new file mode 100644 index 0000000..e1add25 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/GameGimmick.java @@ -0,0 +1,30 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames; + +import java.util.function.IntConsumer; +import javafx.scene.Node; + +/** + * Strategy interface defining the central + * logic and layout for individual minigames. + */ +public interface GameGimmick { + + /** + * Getter method for JavaFx root element of this gimmick. + * + * @return This games root as a {@link Node} + */ + Node getCanvasNode(); + + /** + * Initializes the game behavior, wiring score changes back to the engine. + * + * @param scoreModifier Callback function to adjust the global score. + */ + void initialize(IntConsumer scoreModifier); + + /** + * Called on every frame tick (or second) by the main game loop. + */ + void updateTick(); +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/MiniGamesActions.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/MiniGamesActions.java new file mode 100644 index 0000000..279d17d --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/MiniGamesActions.java @@ -0,0 +1,36 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames; + +/** + * Action set for the associated {@link MiniGamesView}. + * */ +public enum MiniGamesActions { + /** + * Action when selecting clicker game. + * */ + CLICKER_GAME, + + /** + * Action when selecting "find stock" game. + * */ + FIND_STOCK, + + /** + * Action when selecting "time click" game. + * */ + TIME_CLICKS, + + /** + * Action when clicking the question mark box (help section). + * */ + HELP, + + /** + * Action called when quitting a minigame (no reward). + * */ + INGAME_QUIT, + + /** + * Action for when a player returns from a minigame (with rewards). + * */ + INGAME_RETURN; +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/MiniGamesController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/MiniGamesController.java new file mode 100644 index 0000000..acad65a --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/MiniGamesController.java @@ -0,0 +1,144 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames; + +import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventSubscriber; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameView; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.ClickerGame; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.FindStockGame; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.TimeInputsGame; + +/** + * Controller class for the associated {@link MiniGamesView} object. + * + *

Extends {@link ViewController}

+ * */ +public final class MiniGamesController + extends ViewController + implements EventSubscriber { + + /** + * The currently selected stock for the minigame. + * */ + private Stock activeStock; + + /** + * The game engine view to delegate games towards. + * */ + private final GameEngineView gameEngineView; + + /** + * The game engine controller. + * */ + private final GameEngineController gameEngineController; + + /** + * The clicker game instance. + * */ + private final ClickerGame clickerGame; + + /** + * The in game view. + * */ + private final InGameView inGameView; + + /** + * The find stock game instance. + * */ + private final FindStockGame findStockGame; + + /** + * The timed inputs game instance. + * */ + private final TimeInputsGame timeInputsGame; + + /** + * Constructor. + * + * @param viewElement the associated {@link MiniGamesView}. + * @param eventManager the associated {@link EventManager}. + * @param initialStock the initial selected stock. + * @param gameEngineView the game engine view. + * @param gameEngineController the game engine controller. + * @param clickerGame the clicker game instance. + * @param inGameView the in game view instance. + * @param findStockGame the find stock game instance. + * @param timeInputsGame the timed inputs game. + * */ + public MiniGamesController(final MiniGamesView viewElement, + final EventManager eventManager, + final Stock initialStock, + final GameEngineView gameEngineView, + final GameEngineController gameEngineController, + final ClickerGame clickerGame, + final InGameView inGameView, + final FindStockGame findStockGame, + final TimeInputsGame timeInputsGame + ) { + this.activeStock = initialStock; + this.gameEngineView = gameEngineView; + this.gameEngineController = gameEngineController; + this.clickerGame = clickerGame; + this.inGameView = inGameView; + this.findStockGame = findStockGame; + this.timeInputsGame = timeInputsGame; + super(viewElement, eventManager); + eventManager.addSubscriber(this, EventType.SELECT_STOCK_FOR_MINIGAME); + refresh(); + } + + @Override + protected void initInteractions() { + getViewElement().setOnAction(MiniGamesActions.HELP, () -> { + getViewElement().showHelpSection(); + }); + + getViewElement().setOnAction(MiniGamesActions.CLICKER_GAME, () -> { + inGameView.changeCenterView(gameEngineView.getRootPane()); + gameEngineController.launchGimmickSession(clickerGame); + }); + + getViewElement().setOnAction(MiniGamesActions.FIND_STOCK, () -> { + inGameView.changeCenterView(gameEngineView.getRootPane()); + gameEngineController.launchGimmickSession(findStockGame); + }); + + getViewElement().setOnAction(MiniGamesActions.TIME_CLICKS, () -> { + inGameView.changeCenterView(gameEngineView.getRootPane()); + gameEngineController.launchGimmickSession(timeInputsGame); + }); + } + + /** + * Sets the target stock context for the minigames. + * + * @param stock stock to set. + */ + public void setActiveStock(final Stock stock) { + this.activeStock = stock; + getViewElement().setSelectedStockText(stock.getSymbol()); + gameEngineController.setChosenStock(stock); + refresh(); + } + + /** + * Syncs the current model status with view text properties. + */ + public void refresh() { + if (activeStock != null) { + getViewElement().setSelectedStockText(activeStock.getSymbol()); + } else { + getViewElement().setSelectedStockText("None"); + } + } + + @Override + public void handleEvent(EventData data) { + if (data.data() instanceof Stock s) { + setActiveStock(s); + } + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/MiniGamesView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/MiniGamesView.java new file mode 100644 index 0000000..dad45a7 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/MiniGamesView.java @@ -0,0 +1,279 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames; + +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +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; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; + +/** + * Minigames view in the in game section of the application. + * + *

Extends {@link ViewElement}

+ * + *

Functions as an overview page before choosing a game. + * To see the elements creating the minigames themselves, + * see {@link GameEngineView}.

+ * */ +public final class MiniGamesView + extends ViewElement { + + /** + * Main layout container. + * */ + private VBox mainLayoutContainer; + + /** + * Top section of the page (Title, selected stock, question mark block). + * */ + private HBox headerBar; + + /** + * Container for games boxes. + * */ + private HBox gamesContainer; + + /** + * Row containing "Stock Selected:" and current stock. + * */ + private HBox stockSelectionRow; + + /** + * Title (Minigames). + * */ + private Label titleLabel; + + /** + * Selected stock. + * */ + private Label selectedStockLabel; + + /** + * Stock symbol box. + * */ + private Label stockValueLabel; + + /** + * Question mark button (displays a short tutorial). + * */ + private Button helpBtn; + + /** + * Button to start an + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.ClickerGame} instance. + * */ + private Button clickerGameBtn; + + /** + * Button to start an + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.FindStockGame} instance. + * */ + private Button findStockBtn; + + /** + * Button to start an + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games.TimeInputsGame} instance. + * */ + private Button timeInputsBtn; + + /** + * Overlay for help section when pressing help button. + * */ + private StackPane helpOverlay; + + /** + * Constructor. + */ + public MiniGamesView() throws IllegalArgumentException { + super(new StackPane(), MiniGamesActions.class); + } + + /** + * Updates the selected stock text. + * + * @param symbol the symbol representing the stock selected. + */ + public void setSelectedStockText(final String symbol) { + stockValueLabel.setText(symbol); + } + + /** + * Method for showing the help section for minigames. + * */ + public void showHelpSection() { + if (helpOverlay != null) { + return; + } + + helpOverlay = new StackPane(); + helpOverlay.getStyleClass().add("minigames-help-overlay-dimmer"); + + StackPane modalCard = new StackPane(); + modalCard.getStyleClass().add("minigames-help-container"); + + VBox textContainer = new VBox(); + textContainer.getStyleClass().add("minigames-help-text-vbox"); + + textContainer.getChildren().add(createTextParagraph("Minigames", + "Welcome to the minigames section! Here you can boost a selected stock (choose in dashboard page)" + + " by playing minigames! A minigame takes 20 seconds, and you are able to exit before the time runs out." + + " This will not have any negative effects. " + + " When you complete a minigame, you get a rank based on your score. Higher scores yield a higher rank." + + " The higher rank you are at the end of the round, the more fortune the selected stock will get." + + " Every stock can only be boosted one time per week by playing minigames, so play minigames for all your investments!" + + " (Multiple minigames for same stock overrides previous fortune.)")); + + textContainer.getChildren().add(createTextParagraph("What are minigames?", + "A minigame is a short interactive experience where you are able to gain points by performing" + + " task(s) that are differ from minigame to minigame. Each minigame session takes 20 seconds to complete" + + " from start to finish, and you will gain a report based on your performance at the end of the round." + + " You can also choose to exit before a round ends to go back, but note that quitting after the round ends" + + " will still cause the effect.")); + + textContainer.getChildren().add(createTextParagraph("Rank", + "When playing minigames, you gain score. By earning enough score, you increase your rank." + + " At the end of a round, you are given a game report that shows your rank." + + " The higher your rank, the better the fortune for the selected stock will be.")); + + textContainer.getChildren().add(createTextParagraph("Fortune", + "In the minigame page, you can see your selected stock. This stock will then get a" + + " positive or negative flat percent amount added to their next weekly price change, based on your rank." + + " This is called the stocks fortune." + + " Each stock can only have a single fortune active per week, so be sure to" + + " play minigames for all your investments! Playing multiple minigames for the same" + + " stock will override the stocks' fortune.")); + + textContainer.getChildren().add(createTextParagraph("Minigame 1: Clicker Minigame", + "Click the button to earns points. The button changes location after every click!")); + + textContainer.getChildren().add(createTextParagraph("Minigame 2: Find The Stock", + "Scan the choice matrix panel grid and find the symbol matching the target description. Incorrect selections will deduct points.")); + + textContainer.getChildren().add(createTextParagraph("Minigame 3: Timed Inputs.", + "Track the white line that moves across the circle circumference. Fire your strike command inputs using SPACE or ENTER precisely inside the highlighted chartreuse section" + + " to gain points. Incorrect firing will deduct points.")); + + ScrollPane scrollPane = new ScrollPane(textContainer); + scrollPane.getStyleClass().add("minigames-help-scroll-pane"); + scrollPane.setFitToWidth(true); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + + modalCard.getChildren().add(scrollPane); + + Button closeBtn = new Button("X"); + closeBtn.setFocusTraversable(false); + + closeBtn.getStyleClass().add("minigames-help-closeBtn"); + + closeBtn.setOnAction(e -> hideHelpSection()); + + modalCard.getChildren().add(closeBtn); + StackPane.setAlignment(closeBtn, Pos.TOP_RIGHT); + StackPane.setMargin(closeBtn, new Insets(10, 10, 0, 0)); + + helpOverlay.getChildren().add(modalCard); + getRootPane().getChildren().add(helpOverlay); + } + + /** + * Helper method for formatting a {@link TextFlow} + * object with a header and paragraph. + * + * @param headerText the title of the paragraph. + * @param descText the paragraph itself. + * + * @return formatted {@link TextFlow} object. + */ + private TextFlow createTextParagraph(final String headerText, + final String descText) { + TextFlow textFlow = new TextFlow(); + Text header = new Text(headerText + "\n"); + Text desc = new Text(descText); + + textFlow.getStyleClass().add("minigames-help-textflow"); + header.getStyleClass().add("minigames-help-header"); + desc.getStyleClass().add("minigames-help-description"); + + textFlow.getChildren().addAll(header, desc); + return textFlow; + } + + /** + * Cleans down the operational help window layer state. + */ + public void hideHelpSection() { + if (helpOverlay != null) { + getRootPane().getChildren().remove(helpOverlay); + helpOverlay = null; + } + } + + @Override + protected void initLayout() { + mainLayoutContainer = new VBox(); + headerBar = new HBox(); + titleLabel = new Label("Minigames"); + + stockSelectionRow = new HBox(); + selectedStockLabel = new Label("Selected stock: "); + stockValueLabel = new Label(""); + stockSelectionRow.getChildren().addAll(selectedStockLabel, stockValueLabel); + + VBox titleSection = new VBox(); + titleSection.getChildren().addAll(titleLabel, stockSelectionRow); + + Region topSpacer = new Region(); + HBox.setHgrow(topSpacer, Priority.ALWAYS); + + helpBtn = new Button("?"); + headerBar.getChildren().addAll(titleSection, topSpacer, helpBtn); + + gamesContainer = new HBox(); + + clickerGameBtn = new Button("Clicker game"); + findStockBtn = new Button("Find stock"); + timeInputsBtn = new Button("Timed inputs"); + + HBox.setHgrow(clickerGameBtn, Priority.ALWAYS); + HBox.setHgrow(findStockBtn, Priority.ALWAYS); + HBox.setHgrow(timeInputsBtn, Priority.ALWAYS); + + gamesContainer.getChildren().addAll(clickerGameBtn, + findStockBtn, timeInputsBtn); + + VBox.setVgrow(gamesContainer, Priority.ALWAYS); + mainLayoutContainer.getChildren().addAll(headerBar, gamesContainer); + getRootPane().getChildren().add(mainLayoutContainer); + + registerButton(MiniGamesActions.HELP, helpBtn); + registerButton(MiniGamesActions.CLICKER_GAME, clickerGameBtn); + registerButton(MiniGamesActions.FIND_STOCK, findStockBtn); + registerButton(MiniGamesActions.TIME_CLICKS, timeInputsBtn); + } + + @Override + protected void initStyling() { + mainLayoutContainer.getStyleClass().add("minigames-root"); + headerBar.getStyleClass().add("minigames-headerBar"); + stockSelectionRow.getStyleClass().add("minigames-stockSelectionRow"); + titleLabel.getStyleClass().add("minigames-titleLabel"); + selectedStockLabel.getStyleClass().add("minigames-stockLabel"); + stockValueLabel.getStyleClass().add("minigames-stockValue"); + + helpBtn.getStyleClass().add("minigames-helpBtn"); + gamesContainer.getStyleClass().add("minigames-container"); + + clickerGameBtn.getStyleClass().add("minigames-cardBtn"); + findStockBtn.getStyleClass().add("minigames-cardBtn"); + timeInputsBtn.getStyleClass().add("minigames-cardBtn"); + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/ClickerGame.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/ClickerGame.java new file mode 100644 index 0000000..3fe6d86 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/ClickerGame.java @@ -0,0 +1,112 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games; + +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameGimmick; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesActions; +import java.util.function.IntConsumer; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.layout.Pane; + +/** + * Clicker minigame where the goal is to hit the targets as much + * as possible. + * + *

Extends {@link ViewElement}

+ * + *

Implements {@link GameGimmick}

+ * */ +public final class ClickerGame + extends ViewElement + implements GameGimmick { + + /** + * How many points to get after clicking the button. + * */ + private int clickValue; + + /** + * Button pressed to give points. + * */ + private Button clickBtn; + + /** + * Constructor. + * */ + public ClickerGame() { + super(new Pane(), MiniGamesActions.class); + } + + @Override + protected void initLayout() { + clickValue = 6; + clickBtn = new Button("+" + clickValue); + clickBtn.setFocusTraversable(false); + + // Initial position. + clickBtn.setLayoutX(260); + clickBtn.setLayoutY(160); + + getRootPane().getChildren().add(clickBtn); + + // Responsive listener for width. Pushes the button to the edge of the + // screen if it is outside of it. + getRootPane().widthProperty().addListener((obs, oldVal, newVal) -> { + double maxAllowedX = newVal.doubleValue() - clickBtn.getWidth(); + if (clickBtn.getLayoutX() > maxAllowedX) { + clickBtn.setLayoutX(Math.max(10, maxAllowedX)); + } + }); + + // Same responsive listener for height. + getRootPane().heightProperty().addListener((obs, oldVal, newVal) -> { + double maxAllowedY = newVal.doubleValue() - clickBtn.getHeight(); + if (clickBtn.getLayoutY() > maxAllowedY) { + clickBtn.setLayoutY(Math.max(10, maxAllowedY)); + } + }); + } + + @Override + protected void initStyling() { + getRootPane().getStyleClass().add("clicker-minigame-root"); + clickBtn.getStyleClass().add("clicker-minigame-clickBtn"); + } + + @Override + public Node getCanvasNode() { + return getRootPane(); + } + + @Override + public void initialize(final IntConsumer scoreModifier) { + clickBtn.setOnAction(e -> { + scoreModifier.accept(clickValue); + teleportTarget(); + }); + } + + /** + * Teleports the click target to a new random coordinate + * set inside the current view boundaries. + * + *

Calculates maximum allowed x and y position + * based on the view/button width/height.

+ */ + private void teleportTarget() { + + double maxX = getRootPane().getWidth() - clickBtn.getWidth(); + double maxY = getRootPane().getHeight() - clickBtn.getHeight(); + + double randomX = Math.random() * maxX; + double randomY = Math.random() * maxY; + + clickBtn.setLayoutX(Math.max(10, randomX)); + clickBtn.setLayoutY(Math.max(10, randomY)); + } + + @Override + public void updateTick() { + // No need to auto update information for this game. + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/FindStockGame.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/FindStockGame.java new file mode 100644 index 0000000..c70ca90 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/FindStockGame.java @@ -0,0 +1,156 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games; + +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameGimmick; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesActions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.IntConsumer; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; + +/** + * Find correct stock game, found in the + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesView}. + * + *

Extends {@link ViewElement}

+ * + *

Implements {@link GameGimmick}

+ * + * @see edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameEngineView + * @see edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesView + * */ +public final class FindStockGame + extends ViewElement + implements GameGimmick { + + /** + * Target stock to find. + * */ + private Label targetLabel; + + /** + * Grid of stock choices to select. + * */ + private GridPane grid; + + /** + * List of stock symbols to generate selection from. + * */ + private final List availableSymbols; + + /** + * The target stocks' symbol. + * */ + private String targetSymbol; + + /** + * The logic for changing score. + * + *

Gotten from + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameEngineController}

+ * */ + private IntConsumer scoreModifier; + + /** + * Constructor. + * + * @param symbols a list of stocks to use during the game session. + * */ + public FindStockGame(final List symbols) { + super(new VBox(), MiniGamesActions.class); + this.availableSymbols = symbols; + } + + /** + * Generates a new round of "find the stock". + * + *

Gets the target based on the available symbols, and adds it to the + * options for the round. Then generates the remaining options based on + * the available symbols (Duplicate options are allowed). + * When all options are ready, the grid is populated with the options + * as buttons.

+ * */ + private void generateRound() { + grid.getChildren().clear(); + if (availableSymbols.isEmpty()) { + return; + } + + targetSymbol = availableSymbols.get( + (int) (Math.random() * availableSymbols.size()) + ); + targetLabel.setText("FIND: " + targetSymbol); + + List roundOptions = new ArrayList<>(); + roundOptions.add(targetSymbol); + while (roundOptions.size() < 8) { + String randomSymbol = availableSymbols.get( + (int) (Math.random() * availableSymbols.size()) + ); + roundOptions.add(randomSymbol); + } + Collections.shuffle(roundOptions); + + int index = 0; + for (int row = 0; row < 2; row++) { + for (int col = 0; col < 4; col++) { + String symbol = roundOptions.get(index++); + Button optBtn = new Button(symbol); + optBtn.setPrefSize(120, 100); + optBtn.setOnAction(e -> handleChoice(symbol)); + grid.add(optBtn, col, row); + } + } + } + + /** + * Checks if the chosen stock (button press) is the correct stock. + * + *

Accesses the given score modifier consumer to modify + * the score of this session.

+ * + * @param chosen the symbol of the chosen stock. + * */ + private void handleChoice(final String chosen) { + if (chosen.equals(targetSymbol)) { + scoreModifier.accept(14); + } else { + scoreModifier.accept(-7); + } + generateRound(); + } + + @Override + protected void initLayout() { + targetLabel = new Label(); + grid = new GridPane(); + getRootPane().getChildren().addAll(targetLabel, grid); + } + + @Override + protected void initStyling() { + getRootPane().getStyleClass().add("find-stock-minigame-root"); + grid.getStyleClass().add("find-stock-minigame-grid"); + } + + @Override + public Node getCanvasNode() { + return getRootPane(); + } + + @Override + public void initialize(final IntConsumer scoreModifier) { + this.scoreModifier = scoreModifier; + generateRound(); + } + + @Override + public void updateTick() { + // No automatic updates for this game. + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/TimeInputsGame.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/TimeInputsGame.java new file mode 100644 index 0000000..85aabc9 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/minigames/games/TimeInputsGame.java @@ -0,0 +1,168 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games; + +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameGimmick; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesActions; +import java.util.Random; +import java.util.function.IntConsumer; +import javafx.scene.Node; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; + +/** + * Timed clicks game found in the + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesView}. + * + *

Extends {@link edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement}

+ * + *

Implements {@link GameGimmick}

+ * */ +public final class TimeInputsGame + extends ViewElement + implements GameGimmick { + + /** + * Canvas for the game. + * */ + private Canvas canvas; + + /** + * Random generator for angle. + * */ + private Random random; + + /** + * Angle of the indicator (the mark you use to "hit" the green area). + * */ + private double indicatorAngle; + + /** + * Start point of the success zone (green area). + * */ + private double successZoneStart; + + /** + * How "far" the success zone stretches across the circle. + * */ + private final double successZoneExtent = 30; + + /** + * Consumer given by {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameEngineController} + * That determines how to calculate point gains/losses. + * */ + private IntConsumer scoreModifier; + + /** + * Constructor. + * */ + public TimeInputsGame() { + super(new StackPane(), MiniGamesActions.class); + } + + /** + * Evaluates a hit (when pressing space/enter). + * + *

Calculates the normalized angle of the current hit section (white part). + * Then, checks if that angle is within the success zone defined by a start + * and length. Then adds or subtracts points based on result, + * and generates a new success zone.

+ * */ + private void evaluateHit() { + // Due to indicator angle being decreased instead of increased, + // We need to increase it by 360 to normalize it. + double normalizedAngle = (indicatorAngle + 360) % 360; + + // -10 due to some visual latency causing invalid hits to appear valid. + if (normalizedAngle >= successZoneStart - 10 && normalizedAngle <= (successZoneStart + successZoneExtent)) { + scoreModifier.accept(6); + } else { + scoreModifier.accept(-2); + } + + // Changes target area. + this.successZoneStart = generateRandomAngle(); + + // Resets indicator angle. + indicatorAngle = 90; + } + + /** + * Generates a new random number based on the area of a success zone. + */ + private double generateRandomAngle() { + return random.nextDouble() * (360.0 - successZoneExtent); + } + + /** + * Renders the canvas, updating the current indicator and success zone. + * */ + private void renderCanvas() { + GraphicsContext gc = canvas.getGraphicsContext2D(); + gc.clearRect(0, 0, 300, 300); + + double centerX = 150; + double centerY = 150; + double radius = 100; + + // Draws circle. + gc.setStroke(Color.BLACK); + gc.setLineWidth(6); + gc.strokeOval(centerX - radius, centerY - radius, radius * 2, radius * 2); + + // Draws an arc representing the success zone. + gc.setStroke(Color.CHARTREUSE); + gc.setLineWidth(12); + gc.strokeArc(centerX - radius, centerY - radius, radius * 2, radius * 2, successZoneStart, successZoneExtent, javafx.scene.shape.ArcType.OPEN); + + // Calculates and draws the indicator line based on radius and angle. + double rad = Math.toRadians(-indicatorAngle); + double startX = centerX + (radius - 15) * Math.cos(rad); + double startY = centerY + (radius - 15) * Math.sin(rad); + double endX = centerX + (radius + 15) * Math.cos(rad); + double endY = centerY + (radius + 15) * Math.sin(rad); + + gc.setStroke(Color.WHITE); + gc.setLineWidth(4); + gc.strokeLine(startX, startY, endX, endY); + } + + @Override + protected void initLayout() { + canvas = new Canvas(300,300); + random = new Random(); + successZoneStart = generateRandomAngle(); + getRootPane().getChildren().add(canvas); + } + + @Override + protected void initStyling() { + getRootPane().getStyleClass().add("time-inputs-minigame-root"); + } + + @Override + public Node getCanvasNode() { + return getRootPane(); + } + + @Override + public void initialize(final IntConsumer scoreModifier) { + this.scoreModifier = scoreModifier; + + getRootPane().setOnKeyPressed(e -> { + if (e.getCode() == KeyCode.SPACE || e.getCode() == KeyCode.ENTER) { + evaluateHit(); + } + }); + getRootPane().requestFocus(); + renderCanvas(); + } + + @Override + public void updateTick() { + indicatorAngle = (indicatorAngle - 8) % 360; + renderCanvas(); + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarActions.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarActions.java index 9b71268..6f07799 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarActions.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarActions.java @@ -5,5 +5,6 @@ public enum TopBarActions { STATS, MARKET, SETTINGS, - TRANSACTIONS; + TRANSACTIONS, + MINIGAMES; } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java index 86c5d2d..051368d 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java @@ -39,6 +39,17 @@ public class TopBarController extends ViewController { */ private boolean inTransactionsView = false; + /** + * Whether the minigames screen is currently the active center-view. + * + *

+ * When true, the quit/back button returns to the dashboard instead + * of exiting to the main menu. + *

+ */ + private boolean inMinigamesView = false; + + /** * {@inheritDoc}. */ @@ -83,20 +94,27 @@ protected void initInteractions() { * @param statsCenter root pane of the stats widget. * @param transactionsCenter root pane of the transactions widget. * @param onTransactionUpdate callback invoked when entering transactions. + * @param minigamesCenter root pane of the minigames widget. */ public void setMarketIntegration(final Consumer centerSwitcher, final Node dashboardCenter, final Node marketCenter, final Node statsCenter, final Node transactionsCenter, - final Runnable onTransactionUpdate) { + final Runnable onTransactionUpdate, + final Node minigamesCenter + ) { getViewElement().setOnAction(TopBarActions.EXIT, () -> { - if (inMarketView || inStatsView || inTransactionsView) { + if (inMarketView + || inStatsView + || inTransactionsView + || inMinigamesView) { centerSwitcher.accept(dashboardCenter); getViewElement().setQuitText("Quit"); inMarketView = false; inStatsView = false; inTransactionsView = false; + inMinigamesView = false; } else { changeScene(ViewEnum.MAIN_MENU); } @@ -108,6 +126,7 @@ public void setMarketIntegration(final Consumer centerSwitcher, inMarketView = false; inStatsView = true; inTransactionsView = false; + inMinigamesView = false; }); getViewElement().setOnAction(TopBarActions.MARKET, () -> { @@ -116,6 +135,7 @@ public void setMarketIntegration(final Consumer centerSwitcher, inMarketView = true; inStatsView = false; inTransactionsView = false; + inMinigamesView = false; }); getViewElement().setOnAction(TopBarActions.TRANSACTIONS, () -> { @@ -125,6 +145,16 @@ public void setMarketIntegration(final Consumer centerSwitcher, inMarketView = false; inStatsView = false; inTransactionsView = true; + inMinigamesView = false; + }); + + getViewElement().setOnAction(TopBarActions.MINIGAMES, () -> { + centerSwitcher.accept(minigamesCenter); + getViewElement().setQuitText("Back"); + inMarketView = false; + inStatsView = false; + inTransactionsView = false; + inMinigamesView = true; }); } } \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarView.java index 30f0259..471dfbb 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarView.java @@ -17,6 +17,7 @@ public class TopBarView extends ViewElement { private Button marketBtn; private Button settingsBtn; private Button transactionsBtn; + private Button minigamesBtn; private SummaryView summaryView; @@ -48,9 +49,11 @@ protected void initLayout() { marketBtn = new Button("Market"); settingsBtn = new Button("Settings"); transactionsBtn = new Button("Transactions"); + minigamesBtn = new Button("Minigames"); - Stream.of(quitBtn, statsBtn, marketBtn, settingsBtn, transactionsBtn).forEach(b -> { + Stream.of(quitBtn, statsBtn, marketBtn, settingsBtn, transactionsBtn, minigamesBtn).forEach(b -> { HBox.setHgrow(b, Priority.ALWAYS); + b.setFocusTraversable(false); }); navRow.getChildren().addAll( @@ -58,7 +61,8 @@ protected void initLayout() { statsBtn, marketBtn, settingsBtn, - transactionsBtn + transactionsBtn, + minigamesBtn ); if (summaryView != null) { @@ -71,12 +75,13 @@ protected void initLayout() { registerButton(TopBarActions.MARKET, marketBtn); registerButton(TopBarActions.SETTINGS, settingsBtn); registerButton(TopBarActions.TRANSACTIONS, transactionsBtn); + registerButton(TopBarActions.MINIGAMES, minigamesBtn); } @Override protected void initStyling() { getRootPane().getStyleClass().add("top-bar"); - Stream.of(quitBtn, statsBtn, marketBtn, settingsBtn, transactionsBtn) + Stream.of(quitBtn, statsBtn, marketBtn, settingsBtn, transactionsBtn, minigamesBtn) .forEach(b -> b.getStyleClass().add("menu-button")); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsController.java index 3069e8c..7179630 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsController.java @@ -67,7 +67,7 @@ private void filterData(final String searchKeyword, final Integer weekTarget) { t.getShare().getStock().getSymbol(), t.getShare().getStock().getCompany(), sharePrefix + t.getShare().getQuantity().floatValue() + " shares", - moneyPrefix + t.getCalculator().calculateTotal().toString() + " NOK", + moneyPrefix + t.getCalculator().calculateTotal().floatValue() + " NOK", weekTarget.toString()); } }); diff --git a/src/main/resources/dummydata.txt b/src/main/resources/dummydata.csv similarity index 100% rename from src/main/resources/dummydata.txt rename to src/main/resources/dummydata.csv diff --git a/src/main/resources/sp500.csv b/src/main/resources/sp500.csv new file mode 100644 index 0000000..d9cec61 --- /dev/null +++ b/src/main/resources/sp500.csv @@ -0,0 +1,506 @@ +# S&P 500 Companies by Market Cap +# Ticker,Name,Price + +NVDA,Nvidia,191.27 +AAPL,Apple Inc.,276.43 +MSFT,Microsoft,404.68 +AMZN,Amazon,204.62 +GOOGL,Alphabet Inc. (Class A),311.20 +GOOG,Alphabet Inc. (Class C),311.62 +META,Meta Platforms,669.41 +AVGO,Broadcom,343.35 +TSLA,Tesla Inc.,426.52 +BRK.B,Berkshire Hathaway,501.05 +WMT,Walmart,128.75 +LLY,Lilly (Eli),1014.43 +JPM,JPMorgan Chase,311.14 +XOM,ExxonMobil,155.28 +V,Visa Inc.,329.54 +JNJ,Johnson & Johnson,240.70 +MA,Mastercard,539.52 +MU,Micron Technology,411.25 +ORCL,Oracle Corporation,157.08 +COST,Costco,979.71 +BAC,Bank of America,54.06 +ABBV,AbbVie,220.17 +HD,Home Depot (The),389.46 +PG,Procter & Gamble,159.45 +CVX,Chevron Corporation,185.66 +CAT,Caterpillar Inc.,773.53 +AMD,Advanced Micro Devices,213.00 +CSCO,Cisco,85.82 +KO,Coca-Cola Company (The),78.51 +NFLX,Netflix,79.94 +GE,GE Aerospace,314.37 +PLTR,Palantir Technologies,135.59 +LRCX,Lam Research,236.60 +MRK,Merck & Co.,118.79 +PM,Philip Morris International,185.99 +GS,Goldman Sachs,949.29 +MS,Morgan Stanley,176.86 +WFC,Wells Fargo,89.07 +AMAT,Applied Materials,342.19 +RTX,RTX Corporation,197.56 +IBM,IBM,273.86 +UNH,UnitedHealth Group,278.79 +AXP,American Express,355.18 +INTC,Intel,47.85 +TMUS,T-Mobile US,207.62 +PEP,PepsiCo,168.71 +MCD,McDonald's,323.50 +GEV,GE Vernova,822.50 +LIN,Linde plc,466.04 +C,Citigroup,117.93 +TXN,Texas Instruments,226.34 +VZ,Verizon,48.55 +T,AT&T,28.21 +TMO,Thermo Fisher Scientific,525.00 +AMGN,Amgen,364.77 +ABT,Abbott Laboratories,112.97 +KLAC,KLA Corporation,1492.27 +GILD,Gilead Sciences,155.71 +DIS,Walt Disney Company (The),108.30 +NEE,NextEra Energy,91.19 +BA,Boeing,236.98 +ANET,Arista Networks,141.06 +APH,Amphenol,144.60 +ISRG,Intuitive Surgical,496.14 +CRM,Salesforce,184.94 +SCHW,Charles Schwab Corporation,95.50 +BLK,BlackRock,1083.35 +TJX,TJX Companies,150.56 +DE,Deere & Company,610.03 +ADI,Analog Devices,336.71 +LOW,Lowe's,286.57 +PFE,Pfizer,27.77 +UNP,Union Pacific Corporation,261.95 +DHR,Danaher Corporation,219.80 +APP,AppLovin Corporation,459.27 +HON,Honeywell,243.56 +ETN,Eaton Corporation,394.94 +QCOM,Qualcomm,141.90 +UBER,Uber,70.72 +LMT,Lockheed Martin,630.54 +WELL,Welltower,208.65 +ACN,Accenture,230.79 +BKNG,Booking Holdings,4322.85 +SYK,Stryker Corporation,362.53 +COP,ConocoPhillips,110.75 +NEM,Newmont,123.89 +COF,Capital One,214.93 +PLD,Prologis,140.35 +MDT,Medtronic,100.87 +CB,Chubb Limited,328.82 +PH,Parker Hannifin,998.24 +PGR,Progressive Corporation,209.33 +BMY,Bristol Myers Squibb,60.18 +HCA,HCA Healthcare,531.83 +SPGI,S&P Global,396.65 +CMCSA,Comcast,32.53 +VRTX,Vertex Pharmaceuticals,459.93 +MCK,McKesson Corporation,944.20 +PANW,Palo Alto Networks,165.49 +GLW,Corning Inc.,134.16 +SBUX,Starbucks,99.03 +INTU,Intuit,401.05 +MO,Altria,65.72 +BSX,Boston Scientific,73.76 +CME,CME Group,303.44 +NOW,ServiceNow,101.55 +ADBE,Adobe Inc.,258.39 +TT,Trane Technologies,473.75 +CRWD,CrowdStrike,414.94 +BX,Blackstone Inc.,133.29 +UPS,United Parcel Service,119.93 +SO,Southern Company,90.86 +CEG,Constellation Energy,274.37 +DUK,Duke Energy,124.86 +CVS,CVS Health,76.36 +MAR,Marriott International,360.71 +NOC,Northrop Grumman,680.45 +PNC,PNC Financial Services,237.28 +WM,Waste Management,234.67 +GD,General Dynamics,348.79 +WDC,Western Digital,277.26 +KKR,KKR,105.28 +HWM,Howmet Aerospace,233.13 +FCX,Freeport-McMoRan,65.30 +NKE,Nike Inc.,62.43 +USB,U.S. Bancorp,59.25 +MMM,3M,173.46 +SHW,Sherwin-Williams,364.16 +RCL,Royal Caribbean Group,331.76 +SNDK,Sandisk Corporation,607.86 +STX,Seagate Technology,409.41 +EMR,Emerson Electric,156.30 +ADP,Automatic Data Processing,217.59 +WMB,Williams Companies,71.48 +ICE,Intercontinental Exchange,153.60 +FDX,FedEx,368.49 +ITW,Illinois Tool Works,298.50 +JCI,Johnson Controls,140.75 +CRH,CRH plc,127.89 +ECL,Ecolab,301.35 +EQIX,Equinix,863.66 +BK,BNY Mellon,122.83 +MRSH,Marsh & McLennan Companies Inc.,174.09 +AMT,American Tower,179.46 +CMI,Cummins,601.45 +SNPS,Synopsys,433.56 +REGN,Regeneron Pharmaceuticals,780.09 +DELL,Dell Technologies,124.37 +CDNS,Cadence Design Systems,298.74 +CTAS,Cintas,201.10 +ORLY,O'Reilly Auto Parts,93.87 +MNST,Monster Beverage,80.88 +MDLZ,Mondelez International,61.45 +PWR,Quanta Services,523.69 +CI,Cigna,292.46 +CSX,CSX Corporation,41.30 +CL,Colgate-Palmolive,95.04 +SLB,Schlumberger,51.14 +HLT,Hilton Worldwide,327.38 +DASH,DoorDash,175.41 +TDG,TransDigm Group,1325.26 +MCO,Moody's Corporation,415.20 +APO,Apollo Global Management,127.53 +ELV,Elevance Health,329.59 +ABNB,Airbnb,119.56 +GM,General Motors,79.78 +NSC,Norfolk Southern Railway,316.73 +COR,Cencora,365.43 +MSI,Motorola Solutions,423.10 +KMI,Kinder Morgan,31.64 +RSG,Republic Services,226.72 +HOOD,Robinhood Markets Inc.,77.55 +WBD,Warner Bros. Discovery,28.01 +TFC,Truist Financial,54.42 +PCAR,Paccar,129.93 +AON,Aon,314.02 +TEL,TE Connectivity,227.16 +APD,Air Products,293.38 +AEP,American Electric Power,122.18 +FTNT,Fortinet,87.72 +TRV,Travelers Companies (The),299.75 +PSX,Phillips 66,161.13 +LHX,L3Harris,341.14 +EOG,EOG Resources,117.39 +SPG,Simon Property Group,195.66 +NXPI,NXP Semiconductors,249.26 +ROST,Ross Stores,192.31 +VLO,Valero Energy,203.89 +AZO,AutoZone,3733.09 +MPC,Marathon Petroleum,207.85 +BKR,Baker Hughes,61.16 +AFL,Aflac,116.20 +DLR,Digital Realty,174.16 +SRE,Sempra,90.83 +O,Realty Income,64.39 +MPWR,Monolithic Power Systems,1197.55 +GWW,W. W. Grainger,1202.13 +ZTS,Zoetis,128.19 +CARR,Carrier Global,66.80 +D,Dominion Energy,64.61 +F,Ford Motor Company,13.78 +URI,United Rentals,870.17 +AME,Ametek,236.33 +VST,Vistra Corp.,160.43 +FAST,Fastenal,47.14 +ALL,Allstate,205.91 +OKE,ONEOK,85.03 +AJG,Arthur J. Gallagher & Co.,207.61 +CAH,Cardinal Health,225.15 +CVNA,Carvana Co.,365.94 +IDXX,Idexx Laboratories,647.63 +MET,MetLife,78.87 +TGT,Target Corporation,114.12 +PSA,Public Storage,293.33 +BDX,Becton Dickinson,179.62 +CTVA,Corteva,75.48 +TER,Teradyne,323.92 +EA,Electronic Arts,201.72 +ADSK,Autodesk,232.93 +FITB,Fifth Third Bancorp,54.59 +CMG,Chipotle Mexican Grill,37.35 +FANG,Diamondback Energy,168.93 +TRGP,Targa Resources,222.03 +FIX,Comfort Systems USA Inc.,1345.62 +DHI,D. R. Horton,163.35 +HSY,Hershey Company (The),231.46 +OXY,Occidental Petroleum,47.34 +DAL,Delta Air Lines,71.16 +ROK,Rockwell Automation,413.43 +NDAQ,Nasdaq Inc.,81.05 +XEL,Xcel Energy,77.79 +EW,Edwards Lifesciences,78.67 +CCL,Carnival,32.80 +CBRE,CBRE Group,151.76 +ETR,Entergy,100.79 +EXC,Exelon,44.57 +AMP,Ameriprise Financial,489.27 +NUE,Nucor,194.78 +DDOG,Datadog,126.70 +YUM,Yum! Brands,160.35 +MCHP,Microchip Technology,80.90 +WAB,Wabtec,255.15 +KR,Kroger,68.68 +AIG,American International Group,79.36 +VMC,Vulcan Materials Company,321.01 +CIEN,Ciena Corporation,301.11 +SYY,Sysco,88.04 +PEG,Public Service Enterprise Group,83.85 +COIN,Coinbase Global,152.61 +ODFL,Old Dominion,195.74 +KEYS,Keysight Technologies,237.88 +KDP,Keurig Dr Pepper,29.84 +VTR,Ventas,85.29 +MLM,Martin Marietta Materials,663.38 +GRMN,Garmin,206.52 +ED,Consolidated Edison,109.36 +HIG,Hartford (The),142.02 +LVS,Las Vegas Sands,57.75 +CPRT,Copart,39.73 +EL,Estée Lauder Companies (The),106.09 +IR,Ingersoll Rand,96.98 +WDAY,Workday Inc.,145.19 +MSCI,MSCI,519.16 +TTWO,Take-Two Interactive,204.25 +RMD,ResMed,259.29 +EBAY,eBay,82.95 +PCG,PG&E Corporation,17.02 +CCI,Crown Castle,85.66 +PYPL,PayPal,40.26 +PRU,Prudential Financial,105.34 +WEC,WEC Energy Group,113.19 +UAL,United Airlines Holdings,113.50 +STT,State Street Corporation,131.37 +HBAN,Huntington Bancshares,18.02 +A,Agilent Technologies,128.22 +GEHC,GE HealthCare,79.29 +MTB,M&T Bank,235.06 +EME,EMCOR Group Inc.,803.53 +ACGL,Arch Capital Group,98.49 +KMB,Kimberly-Clark,107.35 +ROP,Roper Technologies,333.96 +EQT,EQT Corporation,56.73 +KVUE,Kenvue,18.44 +LYV,Live Nation Entertainment,150.60 +OTIS,Otis Worldwide,89.85 +AXON,Axon Enterprise,436.36 +NRG,NRG Energy,160.11 +CTSH,Cognizant,71.22 +IBKR,Interactive Brokers Group,76.54 +PAYX,Paychex,94.49 +FISV,Fiserv Inc.,62.72 +ADM,Archer Daniels Midland,69.24 +XYZ,Block Inc.,53.87 +FICO,Fair Isaac,1369.86 +DG,Dollar General,147.49 +DOV,Dover Corporation,232.52 +ROL,Rollins Inc.,65.74 +HPE,Hewlett Packard Enterprise,23.62 +RJF,Raymond James Financial,159.60 +TPR,Tapestry Inc.,154.43 +VICI,Vici Properties,29.18 +TDY,Teledyne Technologies,657.92 +XYL,Xylem Inc.,126.71 +CHTR,Charter Communications,241.08 +ARES,Ares Management Corporation,137.90 +ULTA,Ulta Beauty,684.25 +STLD,Steel Dynamics,206.43 +EXR,Extra Space Storage,142.02 +LEN,Lennar,120.76 +IQV,IQVIA,175.80 +IRM,Iron Mountain,99.67 +KHC,Kraft Heinz,24.88 +PPG,PPG Industries,130.53 +HAL,Halliburton,34.84 +ATO,Atmos Energy,175.22 +DTE,DTE Energy,139.17 +TSCO,Tractor Supply,54.53 +EXPE,Expedia Group,235.08 +CFG,Citizens Financial Group,66.87 +AEE,Ameren,106.08 +TPL,Texas Pacific Land Corporation,416.14 +CBOE,Cboe Global Markets,271.99 +ON,ON Semiconductor,70.68 +MTD,Mettler Toledo,1391.33 +STZ,Constellation Brands,163.02 +BIIB,Biogen,191.29 +DVN,Devon Energy,44.78 +FE,FirstEnergy,47.94 +JBL,Jabil,260.92 +NTRS,Northern Trust,147.45 +HUBB,Hubbell Incorporated,513.63 +WTW,Willis Towers Watson,282.86 +WRB,W. R. Berkley Corporation,71.20 +RF,Regions Financial Corporation,30.86 +PHM,PulteGroup,139.04 +CNP,CenterPoint Energy,40.91 +PPL,PPL Corporation,35.97 +DXCM,Dexcom,68.19 +SW,Smurfit WestRock,50.23 +ES,Eversource Energy,69.77 +GIS,General Mills,48.40 +EIX,Edison International,66.94 +IP,International Paper,48.64 +WSM,Williams-Sonoma,214.52 +CINF,Cincinnati Financial,164.11 +LUV,Southwest Airlines,51.66 +AVB,AvalonBay Communities,179.80 +SYF,Synchrony Financial,72.95 +FIS,Fidelity National Information Services,48.73 +KEY,KeyCorp,22.67 +DLTR,Dollar Tree,125.35 +EQR,Equity Residential,65.09 +EXE,Expand Energy,103.09 +DRI,Darden Restaurants,212.58 +FSLR,First Solar,227.60 +DOW,Dow Inc.,33.98 +CPAY,Corpay,346.25 +AWK,American Water Works,123.54 +CHD,Church & Dwight,100.17 +LH,LabCorp,289.08 +VRSK,Verisk Analytics,171.87 +Q,Qnity Electronics,114.06 +CTRA,Coterra,31.48 +STE,Steris,243.09 +EFX,Equifax,197.29 +VLTO,Veralto,95.33 +BG,Bunge Global,121.39 +DGX,Quest Diagnostics,209.53 +CHRW,C.H. Robinson,196.77 +AMCR,Amcor,49.62 +TSN,Tyson Foods,64.52 +L,Loews Corporation,110.15 +CMS,CMS Energy,74.16 +BRO,Brown & Brown,67.07 +LDOS,Leidos,174.83 +PKG,Packaging Corporation of America,244.06 +JBHT,J.B. Hunt,231.62 +OMC,Omnicom Group,69.53 +EXPD,Expeditors International,163.06 +RL,Ralph Lauren Corporation,359.55 +NVR,NVR Inc.,8082.38 +DD,DuPont,51.29 +HUM,Humana,176.34 +NI,NiSource,44.78 +NTAP,NetApp,105.69 +GPC,Genuine Parts Company,149.43 +LULU,Lululemon Athletica,176.88 +ALB,Albemarle Corporation,176.03 +TROW,T. Rowe Price,94.24 +PFG,Principal Financial Group,92.86 +CSGP,CoStar Group,48.09 +GPN,Global Payments,72.67 +SBAC,SBA Communications,190.43 +SNA,Snap-on,383.21 +CNC,Centene Corporation,40.26 +VRSN,Verisign,215.66 +WAT,Waters Corporation,331.52 +IFF,International Flavors & Fragrances,76.65 +BR,Broadridge Financial Solutions,167.88 +WY,Weyerhaeuser,27.09 +INCY,Incyte,99.35 +LII,Lennox International,553.57 +LYB,LyondellBasell,59.29 +SMCI,Supermicro,31.69 +MKC,McCormick & Company,70.30 +ZBH,Zimmer Biomet,95.21 +PTC,PTC Inc.,155.52 +FTV,Fortive,58.93 +VTRS,Viatris,16.02 +EVRG,Evergy,78.94 +BALL,Ball Corporation,67.29 +HPQ,HP Inc.,19.63 +WST,West Pharmaceutical Services,247.87 +PODD,Insulet Corporation,253.09 +APTV,Aptiv,83.64 +CDW,CDW,135.32 +LNT,Alliant Energy,68.17 +TXT,Textron,96.68 +ESS,Essex Property Trust,262.76 +HOLX,Hologic,75.11 +J,Jacobs Solutions,142.70 +INVH,Invitation Homes,27.16 +TKO,TKO Group Holdings,210.88 +NDSN,Nordson Corporation,295.64 +DECK,Deckers Brands,115.15 +PNR,Pentair,99.79 +COO,Cooper Companies (The),82.84 +MAA,Mid-America Apartment Communities,136.74 +FFIV,F5 Inc.,282.22 +MAS,Masco,76.24 +IEX,IDEX Corporation,211.28 +MRNA,Moderna,40.21 +TRMB,Trimble Inc.,65.06 +ALLE,Allegion,179.40 +HII,Huntington Ingalls Industries,392.15 +CLX,Clorox,125.65 +CF,CF Industries,97.23 +GEN,Gen Digital,24.64 +AVY,Avery Dennison,192.77 +KIM,Kimco Realty,21.96 +HAS,Hasbro,105.26 +ERIE,Erie Indemnity,279.84 +TYL,Tyler Technologies,339.51 +UHS,Universal Health Services,231.22 +BEN,Franklin Resources,27.64 +ALGN,Align Technology,197.24 +SOLV,Solventum,81.25 +BBY,Best Buy,66.84 +REG,Regency Centers,76.49 +SWK,Stanley Black & Decker,90.31 +BF.B,Brown–Forman,30.11 +BLDR,Builders FirstSource,125.44 +HST,Host Hotels & Resorts,19.98 +AKAM,Akamai Technologies,95.00 +EG,Everest Group,331.67 +UDR,UDR Inc.,39.84 +TTD,The Trade Desk Inc.,27.19 +HRL,Hormel Foods,23.79 +DPZ,Domino's,385.50 +ZBRA,Zebra Technologies,250.71 +GNRC,Generac,214.94 +FOX,Fox Corporation (Class B),56.02 +FOXA,Fox Corporation (Class A),61.76 +GDDY,GoDaddy,91.44 +PSKY,Paramount Skydance Corp,10.96 +WYNN,Wynn Resorts,115.18 +JKHY,Jack Henry & Associates,165.63 +CPT,Camden Property Trust,111.66 +DOC,Healthpeak Properties,16.95 +SJM,J.M. Smucker Company (The),109.98 +IVZ,Invesco,26.42 +AES,AES Corporation,16.45 +IT,Gartner,160.21 +GL,Globe Life,144.35 +BAX,Baxter International,22.30 +PNW,Pinnacle West,95.57 +RVTY,Revvity,100.89 +AOS,A. O. Smith,80.03 +AIZ,Assurant,216.77 +TAP,Molson Coors Beverage Company,52.98 +NCLH,Norwegian Cruise Line Holdings,22.76 +POOL,Pool Corporation,270.70 +EPAM,EPAM Systems,180.79 +APA,APA Corporation,28.11 +TECH,Bio-Techne,63.46 +MOS,Mosaic Company (The),31.21 +BXP,BXP Inc.,61.61 +DVA,DaVita,144.55 +SWKS,Skyworks Solutions,63.62 +HSIC,Henry Schein,81.21 +CAG,Conagra Brands,19.95 +MGM,MGM Resorts,36.39 +ARE,Alexandria Real Estate Equities,53.92 +FRT,Federal Realty Investment Trust,107.14 +CPB,Campbell Soup Company,29.16 +NWSA,News Corp (Class A),23.30 +CRL,Charles River Laboratories,164.83 +MTCH,Match Group,31.27 +FDS,FactSet,193.29 +LW,Lamb Weston,50.33 +PAYC,Paycom,117.68 +MOH,Molina Healthcare,122.46 +NWS,News Corp (Class B),26.91 diff --git a/src/main/resources/styles.css b/src/main/resources/styles.css index 3b73285..2e10832 100644 --- a/src/main/resources/styles.css +++ b/src/main/resources/styles.css @@ -177,6 +177,8 @@ .summary-container { -fx-border-color: black; -fx-border-width: 1 0 1 0; + -fx-max-height: 100px; + -fx-min-height: 100px; -fx-padding: 10; } @@ -797,4 +799,204 @@ -fx-fit-to-height: true; -fx-background: transparent; -fx-background-color: transparent; -} \ No newline at end of file +} +/* -----------------------------------------------*/ +/* ------------------- MINIGAMES -----------------*/ +.minigames-root { + -fx-padding: 30px; + -fx-background-color: #e0e0e0; + -fx-spacing: 40px; +} + +.minigames-headerBar { + -fx-alignment: TOP_LEFT; + -fx-padding: 10px 0px; +} + +.minigames-stockSelectionRow { + -fx-alignment: CENTER_LEFT; +} + +.minigames-titleLabel { + -fx-font-size: 48px; + -fx-font-family: "Aptos", "Segoe UI", sans-serif; + -fx-text-fill: #000000; +} + +.minigames-stockLabel { + -fx-font-size: 24px; + -fx-text-fill: #000000; +} + +.minigames-stockValue { + -fx-font-size: 24px; + -fx-text-fill: #000000; + -fx-border-color: #000000; + -fx-border-width: 1.5px; + -fx-padding: 2px 10px; + -fx-background-color: #d8d8d8; +} + +.minigames-helpBtn { + -fx-font-size: 28px; + -fx-pref-width: 50px; + -fx-pref-height: 50px; + -fx-background-color: #d8d8d8; + -fx-border-color: #000000; + -fx-border-width: 1.5px; + -fx-text-fill: #000000; +} + +.minigames-container { + -fx-alignment: CENTER; + -fx-spacing: 30px; + -fx-padding: 10px 0px; +} + +.minigames-cardBtn { + -fx-alignment: CENTER; + -fx-background-color: #d8d8d8; + -fx-border-color: #000000; + -fx-border-width: 1.5px; + -fx-font-size: 32px; + -fx-text-fill: #000000; + -fx-text-alignment: center; + -fx-pref-height: 250px; + -fx-min-height: 100px; + -fx-max-width: 350px; +} + +.minigames-cardBtn:hover, .minigames-helpBtn:hover { + -fx-background-color: #c0c0c0; +} + +.minigames-help-overlay-dimmer { + -fx-background-color: rgba(0, 0, 0, 0.6); + -fx-alignment: center; +} + +.minigames-help-container { + -fx-alignment: CENTER; + -fx-max-width: 700px; + -fx-max-height: 500px; + -fx-pref-width: 700px; + -fx-pref-height: 500px; + -fx-background-color: #ffffff; + -fx-border-color: #333333; + -fx-border-width: 2px; + -fx-border-radius: 5px; + -fx-background-radius: 5px; +} + +.minigames-help-text-vbox { + -fx-spacing: 20px; + -fx-alignment: TOP_LEFT; + -fx-padding: 20px, 20px, 20px,20px; + -fx-background-color: transparent; +} + +.minigames-help-scroll-pane { + -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-padding: 10px; +} + +.minigames-help-closeBtn { + -fx-pref-width: 40px; + -fx-pref-height: 40px; + -fx-background-color: #333333; + -fx-text-fill: white; + -fx-font-weight: bold; + -fx-font-size: 18px; + -fx-background-radius: 20px; + -fx-border-radius: 20px; + -fx-cursor: hand; +} + +.minigames-help-textflow { + -fx-pref-width: 600; +} + +.minigames-help-header { + -fx-font-weight: bold; + -fx-font-size: 20px; +} + +.minigames-help-description { + -fx-font-size: 16px; +} + +/* --------------------------------------------- */ +/* --------- MINIGAMES ENGINE CONTEXT ---------- */ +.gameEngine-root { + -fx-background-color: rgba(180, 180, 180, 0.85); +} + +.gameEngine-topBar { + -fx-background-color: transparent; +} + +.gameEngine-topBar-Label { + -fx-font-size: 24px; + -fx-text-fill: black; +} + +.gameEngine-report-box { + -fx-spacing: 25px; + -fx-alignment: CENTER; + -fx-background-color: #ffffff; + -fx-border-color: #000000; + -fx-border-width: 2px; + -fx-padding: 40px; + -fx-max-width: 500px; + -fx-max-height: 350px; + -fx-background-radius: 5px; + -fx-border-radius: 5px; +} + +.gameEngine-report-title { + -fx-font-size: 32px; + -fx-font-weight: bold; +} + +.gameEngine-report-rank-result { + -fx-font-size: 24px; +} + +.gameEngine-report-effect { + -fx-font-size: 18px; +} + +.gameEngine-report-closeButton { + -fx-pref-width: 200; + -fx-pref-height: 50; +} +/* ----------------------------------------------*/ +/* --------------- FIND STOCK GAME ------------- */ +.find-stock-minigame-root { + -fx-spacing: 15; + -fx-alignment: CENTER; +} + +.find-stock-minigame-grid { + -fx-hgap: 15px; + -fx-vgap: 15px; + -fx-alignment: center; +} +/*----------------------------------------------*/ +/*--------------- CLICKER GAME -----------------*/ +.clicker-minigame-root { + -fx-spacing: 20; + -fx-alignment: CENTER; +} + +.clicker-minigame-clickBtn { + -fx-pref-width: 80; + -fx-pref-height: 80; +} +/*--------------- TIMED INPUT GAME -------------*/ +.time-inputs-minigame-root { + -fx-background-color: transparent; + -fx-focus-traversable: true; +} +/*-----------------------------------------------*/ \ No newline at end of file