diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..865d244
Binary files /dev/null and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index a2e5120..3c56052 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,12 @@ buildNumber.properties
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
+
+*.DS_Store
+src/.DS_Store
+src/main/.DS_Store
+src/main/java/edu/.DS_Store
+src/main/java/.DS_Store
+src/main/java/edu/sdccd/.DS_Store
+src/main/java/edu/sdccd/cisc190/.DS_Store
+player_data.txt
diff --git a/pom.xml b/pom.xml
index 5643aaf..f5fdb2f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,9 +27,15 @@
3.20.0
3.4.2
3.6.0
+
+ 21
+ 21
+ UTF-8
+ 2.24.1
+
org.openjfx
javafx-base
@@ -46,12 +52,62 @@
${javafx.version}
+
org.junit.jupiter
junit-jupiter
${jupiter.version}
test
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${jupiter.version}
+ test
+
+
+
+
+ org.testfx
+ testfx-core
+ 4.0.15-alpha
+ test
+
+
+ org.testfx
+ testfx-junit5
+ 4.0.15-alpha
+ test
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.16
+
+
+ org.mockito
+ mockito-core
+ 5.5.0
+ test
+
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+ ${log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-slf4j2-impl
+ ${log4j.version}
+
+
@@ -69,11 +125,12 @@
maven-compiler-plugin
${maven-compiler-plugin.version}
- ${compile.java.version}
- ${compile.java.version}
+ 23
+ 23
+ --enable-preview
-
+
org.apache.maven.plugins
maven-dependency-plugin
@@ -83,8 +140,6 @@
copy-dependencies
package
- false
- false
true
@@ -93,11 +148,6 @@
-
- org.apache.maven.plugins
- maven-deploy-plugin
- ${maven-deploy-plugin.version}
-
org.apache.maven.plugins
maven-surefire-plugin
@@ -108,107 +158,13 @@
target
-
-
- org.apache.maven.plugins
- maven-source-plugin
- ${maven-source-plugin.version}
-
-
- attach-sources
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- ${maven-javadoc-plugin.version}
-
- true
-
-
-
- attach-javadocs
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-shade-plugin
- ${maven-shade-plugin.version}
-
-
- package
-
- shade
-
-
-
-
- *.*
-
- module-info.class
- META-INF/MANIFEST.MF
- META-INF/substrate/config/*
-
-
-
-
-
- ${project.main.class}
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- ${maven-jar-plugin.version}
-
-
-
- true
- ${project.main.class}
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-site-plugin
- ${maven-site-plugin.version}
-
-
- default-site
- site
-
- site
-
-
- true
-
-
-
-
-
- cisc191
- CISC191 Maven Repo
- https://maven.pkg.github.com/MiramarCISC/CISC191-TEMPLATE
+ central
+ https://repo.maven.apache.org/maven2
\ No newline at end of file
diff --git a/src/.DS_Store b/src/.DS_Store
new file mode 100644
index 0000000..8220598
Binary files /dev/null and b/src/.DS_Store differ
diff --git a/src/main/.DS_Store b/src/main/.DS_Store
new file mode 100644
index 0000000..f06d6a7
Binary files /dev/null and b/src/main/.DS_Store differ
diff --git a/src/main/java/.DS_Store b/src/main/java/.DS_Store
new file mode 100644
index 0000000..ec43abd
Binary files /dev/null and b/src/main/java/.DS_Store differ
diff --git a/src/main/java/edu/.DS_Store b/src/main/java/edu/.DS_Store
new file mode 100644
index 0000000..9bc7f50
Binary files /dev/null and b/src/main/java/edu/.DS_Store differ
diff --git a/src/main/java/edu/sdccd/.DS_Store b/src/main/java/edu/sdccd/.DS_Store
new file mode 100644
index 0000000..4bd0762
Binary files /dev/null and b/src/main/java/edu/sdccd/.DS_Store differ
diff --git a/src/main/java/edu/sdccd/cisc190/.DS_Store b/src/main/java/edu/sdccd/cisc190/.DS_Store
new file mode 100644
index 0000000..4b5621e
Binary files /dev/null and b/src/main/java/edu/sdccd/cisc190/.DS_Store differ
diff --git a/src/main/java/edu/sdccd/cisc190/Main.java b/src/main/java/edu/sdccd/cisc190/Main.java
index 47e8dff..edf1ae8 100644
--- a/src/main/java/edu/sdccd/cisc190/Main.java
+++ b/src/main/java/edu/sdccd/cisc190/Main.java
@@ -1,43 +1,13 @@
package edu.sdccd.cisc190;
-import javafx.application.Application;
-import javafx.scene.Scene;
-import javafx.scene.control.Label;
-import javafx.scene.control.TitledPane;
-import javafx.scene.layout.VBox;
-import javafx.stage.Stage;
+import edu.sdccd.cisc190.services.SlotMachineManager;
+import edu.sdccd.cisc190.views.SetupView;
-import java.io.*;
-
-public class Main extends Application {
- public static final String APP_NAME_FILE = "AppName.txt";
+public class Main {
public static void main(String[] args) {
- launch(args);
- }
-
- public static String getAppName() throws IOException {
- String appName;
- try (InputStream is = Main.class.getClassLoader().getResourceAsStream(APP_NAME_FILE)) {
- if(is == null) throw new IOException(APP_NAME_FILE + " could not be found!");
- appName = new BufferedReader(new InputStreamReader(is)).readLine();
- }
-
- return appName;
+ SlotMachineManager.main();
+ SetupView.launch(SetupView.class, args);
}
- @Override
- public void start(Stage stage) throws Exception {
- Label label = new Label("The content inside the TitledPane");
- TitledPane titledPane = new TitledPane(getAppName(), label);
- titledPane.setCollapsible(false);
-
- titledPane.setExpanded(true);
- titledPane.setExpanded(false);
-
- Scene scene = new Scene(new VBox(titledPane));
- stage.setScene(scene);
-
- stage.show();
- }
-}
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/cisc190.uml b/src/main/java/edu/sdccd/cisc190/cisc190.uml
new file mode 100644
index 0000000..61e4102
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/cisc190.uml
@@ -0,0 +1,86 @@
+
+
+ JAVA
+ C:/Users/Jayden/Desktop/CISC190-Marauder-Coded/src/main/java/edu/sdccd/cisc190
+
+ edu.sdccd.cisc190.machines.RainbowRiches
+ edu.sdccd.cisc190.machines.MegaMoolah
+ edu.sdccd.cisc190.Main
+ edu.sdccd.cisc190.machines.TreasureSpins
+ edu.sdccd.cisc190.players.bots.Bot
+ edu.sdccd.cisc190.players.bots.ProfessorHuang
+ edu.sdccd.cisc190.players.HumanPlayer
+ edu.sdccd.cisc190.machines.DiamondDash
+ edu.sdccd.cisc190.players.bots.MrBrooks
+ edu.sdccd.cisc190.machines.Slot
+ edu.sdccd.cisc190.machines.HondaTrunk
+ edu.sdccd.cisc190.players.bots.HondaBoyz
+ edu.sdccd.cisc190.players.bots.Chase
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Constructors
+ Fields
+ Inner Classes
+ Methods
+
+ All
+ private
+
+
diff --git a/src/main/java/edu/sdccd/cisc190/machines/DiamondDash.java b/src/main/java/edu/sdccd/cisc190/machines/DiamondDash.java
new file mode 100644
index 0000000..ee9feb2
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/machines/DiamondDash.java
@@ -0,0 +1,32 @@
+package edu.sdccd.cisc190.machines;
+
+/**
+ * Diamond Dash is a type of slot in the casino
+ * Uses the super constructor to set values of attributes inherited from Slots
+ * low risk, varying payout slot
+ */
+public class DiamondDash extends Slot {
+ public DiamondDash() {
+ super(new String[]{"💍", "💠", "💎"}, 1000, 15, 2);
+ }
+
+ /**
+ * Overrides method in Slots
+ * If player does not get full match, they only lose half their bet
+ * @param moneyAmount The amount of money the user currently has
+ * @param spunRow the symbols array the user spun
+ * @param bet The amount of money the user has bet
+ * @return the user's new money after payout
+ */
+ @Override
+ public int calculatePayout(int moneyAmount, String[] spunRow, int bet) {
+ int winningCondition = evaluateWinCondition(spunRow);
+ return switch (winningCondition) {
+ case 0 -> // No match
+ (int) (moneyAmount - bet * 0.5);
+ case 3 -> // Three-symbol match
+ (int) (moneyAmount + Math.floor(bet * returnAmt));
+ default -> moneyAmount;
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/machines/HondaTrunk.java b/src/main/java/edu/sdccd/cisc190/machines/HondaTrunk.java
new file mode 100644
index 0000000..cf0e84a
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/machines/HondaTrunk.java
@@ -0,0 +1,53 @@
+package edu.sdccd.cisc190.machines;
+
+
+/**
+ * Honda Trunk is a type of slot in the casino
+ * Uses the super constructor to set values of attributes inherited from Slots
+ * low risk, varying payout slot
+ */
+public class HondaTrunk extends Slot {
+ public HondaTrunk() {
+ super(new String[]{"🚗", "🛻", "🚕"}, 1000, 1, 1.5);
+ }
+
+ /**
+ * Overrides the evaluateWinCondition() method in Slots
+ * Allows the user to win some money even if they only get a partial match
+ * @param arr Array of random symbols generated from the generateSpunSymbols() method
+ * @return if the user spun a 3 match, 2 match, or no match
+ */
+ @Override
+ public int evaluateWinCondition(String[] arr) {
+ if (arr[0].equals(arr[1]) && arr[1].equals(arr[2])) {
+ return 3; // Full match
+ } else if (arr[0].equals(arr[1]) || arr[1].equals(arr[2]) || arr[0].equals(arr[2])) {
+ return 2;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Overrides method in Slots
+ * If user gets a partial match, they win a quarter of the full match payout
+ * @param moneyAmount The amount of money the user currently has
+ * @param spunRow the symbols array the user spun
+ * @param bet The amount of money the user has bet
+ * @return the user's new money after payout
+ */
+ @Override
+ public int calculatePayout(int moneyAmount, String[] spunRow, int bet) {
+ int winningCondition = evaluateWinCondition(spunRow);
+ return switch (winningCondition) {
+ case 0 -> // No match
+ moneyAmount - bet;
+ case 2 ->
+ (int) (moneyAmount + Math.floor(bet * returnAmt * 0.25));
+ case 3 -> // Three-symbol match
+ (int) (moneyAmount + Math.floor(bet * returnAmt));
+ default -> moneyAmount;
+ };
+ }
+
+}
diff --git a/src/main/java/edu/sdccd/cisc190/machines/MegaMoolah.java b/src/main/java/edu/sdccd/cisc190/machines/MegaMoolah.java
new file mode 100644
index 0000000..a63a627
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/machines/MegaMoolah.java
@@ -0,0 +1,34 @@
+package edu.sdccd.cisc190.machines;
+
+
+/**
+ * Mega Moolah is a type of slot in the casino
+ * Uses the super constructor to set values of attributes inherited from Slots
+ * Medium risk, medium reward slot
+ */
+public class MegaMoolah extends Slot {
+ public MegaMoolah() {
+ super(new String[]{"\uD83D\uDCB0", "\uD83E\uDD11", "\uD83D\uDCB8"}, 1000, 10, 3);
+ }
+
+ /**
+ * Overrides the calculatePayout method in Slots
+ * User only lose $15 if they do not get a full match, else they win 3 times their bet
+ * @param moneyAmount The amount of money the user currently has
+ * @param spunRow The
+ * @param bet The amount of money the user has bet
+ * @return the player's new money after payout
+ */
+ @Override
+ public int calculatePayout(int moneyAmount, String[] spunRow, int bet) {
+ int winningCondition = evaluateWinCondition(spunRow);
+ return switch (winningCondition) {
+ case 0 -> // No match
+ (int) (moneyAmount - Math.floor(minBet * returnAmt * 0.5));
+ case 3 -> // Three-symbol match
+ (int) (moneyAmount + Math.floor(bet * returnAmt));
+ default -> moneyAmount;
+ };
+ }
+
+}
diff --git a/src/main/java/edu/sdccd/cisc190/machines/RainbowRiches.java b/src/main/java/edu/sdccd/cisc190/machines/RainbowRiches.java
new file mode 100644
index 0000000..48b23c0
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/machines/RainbowRiches.java
@@ -0,0 +1,12 @@
+package edu.sdccd.cisc190.machines;
+
+/**
+ * Rainbow Riches is a type of slot in the casino
+ * Uses the super constructor to set values of attributes inherited from Slots
+ * medium to high risk, medium to high reward slot
+ */
+public class RainbowRiches extends Slot {
+ public RainbowRiches() {
+ super(new String[]{"\uD83C\uDF08", "\uD83C\uDF27", "\uD83C\uDF24"}, 1000, 25, 5);
+ }
+}
diff --git a/src/main/java/edu/sdccd/cisc190/machines/Slot.java b/src/main/java/edu/sdccd/cisc190/machines/Slot.java
new file mode 100644
index 0000000..43edda0
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/machines/Slot.java
@@ -0,0 +1,123 @@
+package edu.sdccd.cisc190.machines;
+
+import edu.sdccd.cisc190.players.HumanPlayer;
+import edu.sdccd.cisc190.players.bots.*;
+import java.util.*;
+
+/**
+ * Defines the general behavior of each slot machine
+ * A slot should have a min and max bet, symbols to display, and be able to calculate the player's return amount
+ * Create getters and setters for these attributes for them to be read and used in junit tests and to be displayed for the user
+ */
+abstract public class Slot {
+ protected String[] symbols; // Instance-specific symbols
+ protected int maxBet; // Instance-specific max bet
+ protected int minBet; // Instance-specific min bet
+ protected double returnAmt; // Instance-specific return multiplier
+
+ public Slot(String[] symbols, int maxBet, int minBet, double returnAmt) {
+ this.symbols = symbols;
+ this.maxBet = maxBet;
+ this.minBet = minBet;
+ this.returnAmt = returnAmt;
+ }
+
+ // Returns the symbols for the slot machine
+ public String[] getSymbols() {
+ return symbols;
+ }
+
+ // Returns the maximum bet for the slot machine
+ public int getMaxBet() {
+ return maxBet;
+ }
+
+ // Returns the minimum bet for the slot machine
+ public int getMinBet() {
+ return minBet;
+ }
+
+ // Returns the jackpot's return amount for the slot machine
+ public double getReturnAmt() {
+ return returnAmt;
+ }
+
+ /**
+ * Determines whether the user is able to bet a specific amount given their current balance and machine parameters
+ * @param betAmt How much the user is attempting to bet
+ * @return If the user's bet is within the bounds of their current balance and the minimum and maximum bet of the machine
+ **/
+ public boolean canBet(int betAmt) {
+ int playerMoney = HumanPlayer.getInstance().getMoney();
+ return betAmt <= playerMoney && betAmt >= this.getMinBet() && betAmt <= this.getMaxBet();
+ }
+
+ /**
+ * Generates a random set of three symbols from the machine's symbols array
+ * @return Random symbols from the machine's symbols array
+ **/
+ public String[] generateSpunSymbols() {
+ Random rand = new Random();
+ String[] spunSlots = new String[symbols.length];
+
+ for (int i = 0; i < symbols.length; i++) {
+ spunSlots[i] = symbols[rand.nextInt(symbols.length)];
+ }
+ return spunSlots;
+ }
+
+ /**
+ * Determines whether the user has won a jackpot by checking if all the symbols in the array are the same
+ * @param arr Array of random symbols generated from the generateSpunSymbols() method
+ * @return 3 if there is full match, 0 if there is no match
+ * **/
+ public int evaluateWinCondition(String[] arr) {
+ if (arr[0].equals(arr[1]) && arr[1].equals(arr[2])) {
+ return 3; // Full match
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Updates the user's balance based on the result of their spin
+ * @param moneyAmount The amount of money the user currently has
+ * @param bet The amount of money the user has bet
+ * **/
+ public int calculatePayout(int moneyAmount, String[] spunRow, int bet) {
+ int winningCondition = evaluateWinCondition(spunRow);
+ return switch (winningCondition) {
+ case 0 -> // No match
+ moneyAmount - bet;
+ case 3 -> // Three-symbol match
+ (int) (moneyAmount + Math.floor(bet * returnAmt));
+ default -> moneyAmount;
+ };
+ }
+
+ /**
+ * For bot threads to simulate bots playing the game
+ * @return resultAmt The bot's new money amount
+ * **/
+ public int botPlay(Bot bot) {
+ double betVarianceMultiplier = 0.8 + (Math.random() * 0.4); // Random number between 0.8 and 1.2
+ int bet = (int) (bot.getMoney() * bot.getAura() * betVarianceMultiplier); // Calculate the bot's bet as a function of its current money, aura and variance multiplier
+
+ float randomNumber = (float) (Math.random());
+ int resultAmt;
+
+ /* *
+ * Generate a random number 0.0 - 1.0
+ * If luck is greater than or equal to this variable, the bot wins.
+ * If luck is less than this number, the bot loses
+ * Bot's money amount is then adjusted accordingly
+ * */
+ if (randomNumber <= bot.getLuck()) {
+ resultAmt = bet + bot.getMoney();
+ } else {
+ resultAmt = bot.getMoney() - bet;
+ }
+
+ return resultAmt;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/machines/TreasureSpins.java b/src/main/java/edu/sdccd/cisc190/machines/TreasureSpins.java
new file mode 100644
index 0000000..dfa96a7
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/machines/TreasureSpins.java
@@ -0,0 +1,12 @@
+package edu.sdccd.cisc190.machines;
+
+/**
+ * Treasure Spins is a type of slot in the casino
+ * Uses the super constructor to set values of attributes inherited from Slots
+ * High risk, high reward slot
+ */
+public class TreasureSpins extends Slot {
+ public TreasureSpins() {
+ super(new String[]{"\uD83C\uDF53", "\uD83C\uDF4C", "\uD83C\uDF4A"}, 1000, 50, 10);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/players/HumanPlayer.java b/src/main/java/edu/sdccd/cisc190/players/HumanPlayer.java
new file mode 100644
index 0000000..a27c07b
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/players/HumanPlayer.java
@@ -0,0 +1,46 @@
+package edu.sdccd.cisc190.players;
+
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+
+/**
+ * initializes and defines the attributes of a human player i.e. username and money
+ * Create an instance of a human player for easier implementation to functionality of application
+ * use getters and setters to obtain and update the value of the human player's money, both on the backend and in JavaFX
+ */
+public class HumanPlayer {
+ private static HumanPlayer instance;
+ private String username;
+ private final IntegerProperty money = new SimpleIntegerProperty(this, "money", 1000);
+
+ private HumanPlayer() {}
+
+ public static HumanPlayer getInstance() {
+ // TODO: Add an explanation of why we use a singleton pattern here for HumanPlayer
+
+ if (instance == null) {
+ instance = new HumanPlayer();
+ }
+ return instance;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public final void setMoney(int value) {
+ money.set(value);
+ }
+
+ public final int getMoney() {
+ return money.get();
+ }
+
+ public IntegerProperty moneyProperty() {
+ return money;
+ }
+
+ public String getName() {
+ return username;
+ }
+}
diff --git a/src/main/java/edu/sdccd/cisc190/players/bots/AnitaMaxWynn.java b/src/main/java/edu/sdccd/cisc190/players/bots/AnitaMaxWynn.java
new file mode 100644
index 0000000..582be58
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/players/bots/AnitaMaxWynn.java
@@ -0,0 +1,18 @@
+package edu.sdccd.cisc190.players.bots;
+
+/**
+ * Anita Max Wynn is a bot that will be playing in the background
+ * instantiate a new instance of Anita Max Wynn to implement in the application
+ * High luck, low aura = decent chances of winning
+ */
+public class AnitaMaxWynn extends Bot {
+ private static final AnitaMaxWynn instance = new AnitaMaxWynn();
+
+ private AnitaMaxWynn() {
+ super("Anita Max Wynn", 1000, 0.8, 0.3); // Initial money, luck, and aura values
+ }
+
+ public static AnitaMaxWynn getInstance() {
+ return instance;
+ }
+}
diff --git a/src/main/java/edu/sdccd/cisc190/players/bots/Bot.java b/src/main/java/edu/sdccd/cisc190/players/bots/Bot.java
new file mode 100644
index 0000000..1fb446d
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/players/bots/Bot.java
@@ -0,0 +1,46 @@
+package edu.sdccd.cisc190.players.bots;
+
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+
+/**
+ * Create the behavior of the bots that will be playing in the background
+ * Getters and setters to obtain the bots' name, money, luck, and aura to display and run their play
+ */
+public abstract class Bot {
+ private final String name;
+ private final IntegerProperty money = new SimpleIntegerProperty();
+ private final double luck;
+ private final double aura;
+
+ public Bot(String name, int initialMoney, double luck, double aura) {
+ this.name = name;
+ this.money.set(initialMoney);
+ this.luck = luck;
+ this.aura = aura;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getMoney() {
+ return money.get();
+ }
+
+ public void setMoney(int value) {
+ money.set(value);
+ }
+
+ public IntegerProperty moneyProperty() {
+ return money;
+ }
+
+ public double getLuck() {
+ return luck;
+ }
+
+ public double getAura() {
+ return aura;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/players/bots/Chase.java b/src/main/java/edu/sdccd/cisc190/players/bots/Chase.java
new file mode 100644
index 0000000..71c45c3
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/players/bots/Chase.java
@@ -0,0 +1,21 @@
+package edu.sdccd.cisc190.players.bots;
+
+/**
+ * Chase is a bot that will be playing in the background
+ * instantiate a new instance of Chase to implement in the application
+ * low luck and aura = low capacity for winning
+ */
+public class Chase extends Bot {
+
+ private static final Chase instance = new Chase();
+
+ private Chase() {
+ super("Chase Allan", 1000, 0.25, 0.1); // Initial money, luck, and aura values
+ }
+
+ public static Chase getInstance() {
+ return instance;
+ }
+
+
+}
diff --git a/src/main/java/edu/sdccd/cisc190/players/bots/HondaBoyz.java b/src/main/java/edu/sdccd/cisc190/players/bots/HondaBoyz.java
new file mode 100644
index 0000000..968eb47
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/players/bots/HondaBoyz.java
@@ -0,0 +1,20 @@
+package edu.sdccd.cisc190.players.bots;
+
+/**
+ * Honda Boyz is a bot that will be playing in the background
+ * instantiate a new instance of Honda Boyz to implement in the application
+ * Max luck and min aura = lowest chances of winning
+ */
+public class HondaBoyz extends Bot {
+ private static final HondaBoyz instance = new HondaBoyz();
+
+ private HondaBoyz() {
+ super("HondaBoyz", 1000, 1.0, 0.1); // Initial money, luck, and aura values
+ }
+
+ public static HondaBoyz getInstance() {
+ return instance;
+ }
+
+
+}
diff --git a/src/main/java/edu/sdccd/cisc190/players/bots/MrBrooks.java b/src/main/java/edu/sdccd/cisc190/players/bots/MrBrooks.java
new file mode 100644
index 0000000..2f2556c
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/players/bots/MrBrooks.java
@@ -0,0 +1,20 @@
+package edu.sdccd.cisc190.players.bots;
+
+/**
+ * Mr Brooks is a bot that will be playing in the background
+ * instantiate a new instance of Mr Brooks to implement in the application
+ * Decent luck and solid aura = decent chances for high winnings
+ */
+public class MrBrooks extends Bot {
+ private static final MrBrooks instance = new MrBrooks();
+
+ private MrBrooks() {
+ super("MrBrooks", 1000, 0.5, 0.7); // Initial money, luck, and aura values
+ }
+
+ public static MrBrooks getInstance() {
+ return instance;
+ }
+
+
+}
diff --git a/src/main/java/edu/sdccd/cisc190/players/bots/ProfessorHuang.java b/src/main/java/edu/sdccd/cisc190/players/bots/ProfessorHuang.java
new file mode 100644
index 0000000..bb10f16
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/players/bots/ProfessorHuang.java
@@ -0,0 +1,19 @@
+package edu.sdccd.cisc190.players.bots;
+
+/**
+ * Professor Huang is a bot that will be playing in the background
+ * instantiate a new instance of Professor Huang to implement in the application
+ * High aura and solid luck attributes = greater potential for winning
+ */
+public class ProfessorHuang extends Bot {
+ private static final ProfessorHuang instance = new ProfessorHuang();
+
+ private ProfessorHuang() {
+ super("Professor Huang", 1000, 0.95, 0.6); // Initial money, luck, and aura values
+ }
+
+ public static ProfessorHuang getInstance() {
+ return instance;
+ }
+
+}
diff --git a/src/main/java/edu/sdccd/cisc190/services/BotService.java b/src/main/java/edu/sdccd/cisc190/services/BotService.java
new file mode 100644
index 0000000..187b86a
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/services/BotService.java
@@ -0,0 +1,102 @@
+package edu.sdccd.cisc190.services;
+
+import edu.sdccd.cisc190.machines.Slot;
+import edu.sdccd.cisc190.players.bots.Bot;
+import javafx.application.Platform;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * BotService class manages the behavior of a bot interacting with a slot machine.
+ * It includes functionality to pause, unpause and spin the bot on the slot machine
+ **/
+public class BotService implements Runnable {
+ private static final Logger LOGGER = LoggerFactory.getLogger(BotService.class);
+ private final Bot bot; // The bot instance this service manages
+ private Slot slotMachine; // The slot machine the bot interacts with
+ private volatile boolean spinFlag = false; // Flag to indicate the bot should spin
+ private static final BooleanProperty pauseFlag = new SimpleBooleanProperty(false); // Flag to pause the bot
+ private static final Object lock = new Object(); // Shared static lock object
+
+ /**
+ * Constructor for BotService
+ * @param bot The bot instance managed by this service
+ * @param slotMachine The slot machine this bot interacts with
+ * */
+ public BotService(Bot bot, Slot slotMachine) {
+ this.bot = bot;
+ this.slotMachine = slotMachine;
+ }
+
+ /**
+ * Returns the bot instance managed by this service
+ * @return The bot instance
+ */
+ public Bot getBot() {
+ return bot;
+ }
+
+ /**
+ * Returns the slot machine instance managed by this service
+ * @return the slot machine instance
+ */
+
+ public Slot getSlotMachine() {
+ return slotMachine;
+ }
+
+ /**
+ * Sets the spin flag to true, triggering a spin for the bot
+ * */
+ public void triggerSpin() {
+ spinFlag = true;
+ }
+
+ /**
+ * Changes the slot machine this bot interacts with
+ * @param newSlotMachine The new slot machine to associate with this bot
+ * */
+ public synchronized void setSlotMachine(Slot newSlotMachine) {
+ this.slotMachine = newSlotMachine;
+ }
+
+ /**
+ * Runs the bot service in a separate thread.
+ * The bot performs spins on its slot machine when triggered, and respects the pause flag.
+ **/
+ @SuppressWarnings("BusyWait")
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ synchronized (lock) {
+ while (pauseFlag.get()) {
+ lock.wait(); // Wait if the thread is paused
+ }
+ }
+
+ if (spinFlag) {
+ synchronized (this) { // Ensure thread safety for instance-specific operations
+ int newBalance = slotMachine.botPlay(bot); // Simulate the spin
+ bot.setMoney(newBalance); // Update bot's balance
+ LOGGER.info("{} spun on {} and new balance: {}", bot.getName(), slotMachine.getClass().getSimpleName(), newBalance);
+
+ Platform.runLater(() -> {
+
+ });
+
+ spinFlag = false; // Reset the spin flag
+ }
+ }
+
+ Thread.sleep(500); // Sleep for a short time to prevent busy-waiting
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ LOGGER.warn("Thread interrupted", e);
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/services/PlayerSavesService.java b/src/main/java/edu/sdccd/cisc190/services/PlayerSavesService.java
new file mode 100644
index 0000000..c497237
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/services/PlayerSavesService.java
@@ -0,0 +1,78 @@
+package edu.sdccd.cisc190.services;
+
+import edu.sdccd.cisc190.players.HumanPlayer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+
+@SuppressWarnings("LoggingSimilarMessage")
+public class PlayerSavesService {
+ private static final Logger LOGGER = LoggerFactory.getLogger(PlayerSavesService.class);
+
+ /*
+ * Saves the user's name and money into a player_data.txt file on quit to persist their progress
+ * */
+ public static void saveState() {
+ HumanPlayer player = HumanPlayer.getInstance();
+ String data = "Username: " + player.getName() + ", Money: $" + player.getMoney();
+
+ try {
+ // Delete the file if it exists
+ File file = new File("player_data.txt");
+ if (file.exists()) {
+ if (!file.delete()) {
+ LOGGER.error("Failed to delete existing player_data.txt file.");
+ return;
+ }
+ }
+
+ // Write new data to the file
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
+ writer.write(data);
+ writer.newLine();
+ }
+
+ } catch (IOException e) {
+ LOGGER.error("Error saving player data.", e);
+ }
+ }
+
+ /*
+ * Loads user data from player_data.txt file if available on game open
+ * */
+ public static boolean loadState() {
+ File file = new File("player_data.txt");
+ if (file.exists()) {
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String line = reader.readLine();
+ if (line != null) {
+ String[] data = line.split(", ");
+ String username = data[0].split(": ")[1];
+ int money = Integer.parseInt(data[1].split(": ")[1].replace("$", ""));
+
+ HumanPlayer player = HumanPlayer.getInstance();
+ player.setUsername(username);
+ player.setMoney(money);
+
+ return true; // Data successfully loaded
+ }
+ } catch (IOException | NumberFormatException e) {
+ LOGGER.error("Error reading player data", e);
+ }
+ }
+ return false; // File does not exist or data could not be loaded
+ }
+
+ /*
+ * Deletes user's information in player_data.txt if available
+ * */
+ public static void deleteState() {
+ File file = new File("player_data.txt");
+ if (file.exists()) {
+ if (!file.delete()) {
+ LOGGER.error("Failed to delete existing player_data.txt file.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/services/SlotMachineManager.java b/src/main/java/edu/sdccd/cisc190/services/SlotMachineManager.java
new file mode 100644
index 0000000..2827eb4
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/services/SlotMachineManager.java
@@ -0,0 +1,166 @@
+package edu.sdccd.cisc190.services;
+
+import edu.sdccd.cisc190.machines.*;
+import edu.sdccd.cisc190.players.bots.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages slot machines and bots interacting with them.
+ * Handles the creation, assignment, rotation and control of bot threads interacting with different slot machines
+ * */
+public class SlotMachineManager {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SlotMachineManager.class);
+
+ // Instances of slot machines
+ static DiamondDash diamondDash = new DiamondDash();
+ static HondaTrunk hondaTrunk = new HondaTrunk();
+ static MegaMoolah megaMoolah = new MegaMoolah();
+ static RainbowRiches rainbowRiches = new RainbowRiches();
+ static TreasureSpins treasureSpins = new TreasureSpins();
+
+ // Lists to manage bot threads and services
+ private static volatile boolean stopRequested = false;
+ public static List botThreads = new ArrayList<>();
+ public static List botServices = new ArrayList<>();
+
+ /**
+ * Getter that to obtain the boolean value of stopRequested
+ * @return the value of stopRequested (true or false)
+ */
+
+ public static boolean getStopRequested() {
+ return stopRequested;
+ }
+
+ /**
+ * Main method to initialize and manage bot services and slot machines
+ * Assigns bots to slot machines, starts their threads, and manages periodic tasks
+ * */
+ public static void main() {
+ LOGGER.info("Initializing SlotMachineManager");
+
+ // List of bots
+ List bots = List.of(
+ Chase.getInstance(),
+ HondaBoyz.getInstance(),
+ MrBrooks.getInstance(),
+ ProfessorHuang.getInstance(),
+ AnitaMaxWynn.getInstance()
+ );
+
+ // List of slot machines
+ List slotMachines = List.of(diamondDash, hondaTrunk, megaMoolah, rainbowRiches, treasureSpins);
+
+ // Start a service for each bot
+ for (int i = 0; i < bots.size(); i++) {
+ Bot bot = bots.get(i);
+ Slot machine = slotMachines.get(i % slotMachines.size()); // Assign initial machine
+ BotService botService = new BotService(bot, machine);
+
+ // Wrap botService in a thread and start it
+ Thread botThread = new Thread(botService);
+ botThread.start();
+ botThreads.add(botThread);
+ botServices.add(botService);
+
+ // Log the bot's assignment
+ LOGGER.debug("Assigned {} to {}", bot.getName(), machine.getClass().getSimpleName());
+
+ // Periodically trigger spins for this bot
+ Thread spinThread = getThread(botService);
+ botThreads.add(spinThread);
+ }
+
+ // Start a thread to rotate machines
+ Thread rotationThread = getThread(slotMachines);
+ botThreads.add(rotationThread);
+ }
+
+ @SuppressWarnings("BusyWait")
+ private static Thread getThread(List slotMachines) {
+ Thread rotationThread = new Thread(() -> {
+ try {
+ while (!stopRequested) {
+ Thread.sleep(60000); // Rotate machines every 15 seconds
+ if (stopRequested) break;
+ rotateSlotMachines(slotMachines);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ LOGGER.info("Thread has been interrupted", e);
+ }
+ });
+
+ rotationThread.start();
+ return rotationThread;
+ }
+
+ @SuppressWarnings("BusyWait")
+ private static Thread getThread(BotService botService) {
+ Thread spinThread = new Thread(() -> {
+ try {
+ while (!stopRequested) {
+ Thread.sleep((long) (Math.random() * 6000 + 5000));
+ if (stopRequested) break;
+ botService.triggerSpin();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ LOGGER.info("Thread has been interrupted", e);
+ }
+ });
+
+ spinThread.start();
+ return spinThread;
+ }
+
+ /**
+ * Rotates the slot machines assigned to bots.
+ * Each bot is moved to the next slot machine in the list.
+ * @param slotMachines The list of available slot machines
+ * */
+ private static void rotateSlotMachines(List slotMachines) {
+ for (int i = 0; i < botServices.size(); i++) {
+ BotService botService = botServices.get(i);
+ Slot newMachine = slotMachines.get((i + 1) % slotMachines.size()); // Rotate to the next machine
+ botService.setSlotMachine(newMachine);
+ LOGGER.info("Rotated {} to {}", botService.getBot().getName(), newMachine.getClass().getSimpleName());
+ }
+ }
+
+ /**
+ * Stops all threads managed by this class.
+ * Signals threads to stop and interrupts them to end execution
+ * */
+ public static void stopAllThreads() {
+ stopRequested = true;
+
+ // Interrupt all bot threads
+ for (Thread botThread : botThreads) {
+ if (botThread.isAlive()) {
+ try {
+ botThread.interrupt();
+ botThread.join(1000); //wait for threads to finish
+ } catch (InterruptedException e) {
+ LOGGER.warn("Failed to stop thread: {}", botThread.getName(), e);
+ }
+ }
+ }
+
+ LOGGER.info("All threads have been stopped.");
+ }
+
+ /**
+ * Resets all threads to og state
+ * used for junit testing
+ */
+ public static void reset() {
+ stopRequested = false;
+ botThreads.clear();
+ botServices.clear();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/views/BetView.java b/src/main/java/edu/sdccd/cisc190/views/BetView.java
new file mode 100644
index 0000000..e438d6f
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/views/BetView.java
@@ -0,0 +1,175 @@
+package edu.sdccd.cisc190.views;
+
+import edu.sdccd.cisc190.machines.*;
+import edu.sdccd.cisc190.services.SlotMachineManager;
+import javafx.application.Application;
+import javafx.geometry.Pos;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontWeight;
+import javafx.stage.Stage;
+import javafx.application.Platform;
+
+import static edu.sdccd.cisc190.views.SlotMachineView.slotMachine;
+
+/**
+ * The BetView class represents a JavaFX view for users to place their bets on a selected slot machine.
+ * It allows users to enter a bet amount, displays slot machine limits and return information,
+ * and navigates to the SlotMachineView or MainMenuView.
+ */
+public class BetView extends Application {
+ /**
+ * The amount the user chooses to bet.
+ */
+ static int betAmt;
+
+ // Labels for displaying slot machine betting limits and return amounts
+ private static final Label maxBet = new Label();
+ private static final Label minBet = new Label();
+ private static final Label returnAmount = new Label();
+
+ /**
+ * The entry point for JavaFX applications.
+ * This method is overridden but not used directly in this class.
+ *
+ * @param primaryStage The primary stage for this application.
+ */
+ @Override
+ public void start(Stage primaryStage) {
+ // Placeholder for launching the window
+ }
+
+ /**
+ * Main method for launching the application.
+ *
+ * @param args Command-line arguments.
+ */
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ /**
+ * Displays the betting window where users can place a bet for a selected slot machine.
+ *
+ * @param primaryStage The primary stage for the application.
+ * @param selectedMachine The selected slot machine type.
+ */
+ public static void showWindow(Stage primaryStage, MainMenuView.SlotOptions selectedMachine) {
+ primaryStage.setTitle("Casino - Place Your Bet");
+
+ // Set the onCloseRequest handler to quit the application when the window is closed
+ primaryStage.setOnCloseRequest(_ -> {
+ SlotMachineManager.stopAllThreads();
+ Platform.exit();
+ });
+
+ // Initialize the selected slot machine based on user choice
+ switch (selectedMachine) {
+ case HONDA_TRUNK -> slotMachine = new HondaTrunk();
+ case TREASURE_SPINS -> slotMachine = new TreasureSpins();
+ case MEGA_MOOLAH -> slotMachine = new MegaMoolah();
+ case RAINBOW_RICHES -> slotMachine = new RainbowRiches();
+ default -> slotMachine = new DiamondDash();
+ }
+
+ // Create a styled label prompting the user for their bet amount
+ Label nameLabel = new Label("How much do you want to bet?");
+ nameLabel.setFont(Font.font("Verdana", FontWeight.BOLD, 18));
+ nameLabel.setTextFill(Color.GOLD);
+
+ // Set up labels to display slot machine limits and expected return
+ SlotMachineView.infoSetText(maxBet, minBet, returnAmount);
+
+ // Create a text field for the user to enter their bet amount
+ TextField numericTextField = new TextField();
+ numericTextField.setPromptText("Enter numbers only");
+ numericTextField.setPrefWidth(250);
+ numericTextField.setStyle(
+ "-fx-background-color: #333333; " +
+ "-fx-text-fill: white; " +
+ "-fx-prompt-text-fill: #aaaaaa; " +
+ "-fx-background-radius: 10; " +
+ "-fx-padding: 10px;"
+ );
+
+ // Restrict the input to numeric values only
+ numericTextField.textProperty().addListener((_, _, newValue) -> {
+ if (!newValue.matches("\\d*")) { // Allow only digits
+ numericTextField.setText(newValue.replaceAll("\\D", "")); // Remove non-numeric characters
+ }
+ });
+
+ // Create the Main Menu button and attach an action to return to the MainMenuView
+ Button mainMenu = createStyledButton("Main Menu");
+ mainMenu.setOnAction(_ -> MainMenuView.setupWindow(primaryStage));
+
+ // Create the Place Bet button to submit the user's bet
+ Button submitButton = createStyledButton("Place Bet");
+ submitButton.setOnAction(_ -> {
+ if (!numericTextField.getText().isEmpty()) {
+ betAmt = Integer.parseInt(numericTextField.getText()); // Get the bet amount
+ primaryStage.close();
+
+ // Open the SlotMachineView with the bet amount and selected slot machine
+ Stage newWindow = new Stage();
+ SlotMachineView.showWindow(newWindow, betAmt, selectedMachine);
+ }
+ });
+
+ // Create a horizontal box to display slot machine information (max/min bet and return amount)
+ HBox slotInformation = new HBox(10, maxBet, minBet, returnAmount);
+ slotInformation.setAlignment(Pos.CENTER);
+
+ // Arrange all elements in a vertical layout
+ VBox layout = new VBox(20); // Increased spacing for better visuals
+ layout.getChildren().addAll(nameLabel, slotInformation, numericTextField, submitButton, mainMenu);
+ layout.setAlignment(Pos.CENTER);
+ layout.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #000000, #660000);" +
+ "-fx-padding: 30px;"
+ );
+
+ // Set up the scene and display it on the primary stage
+ Scene scene = new Scene(layout, 400, 300); // Compact layout size
+ primaryStage.setScene(scene);
+ primaryStage.show();
+ }
+ /**
+ * Creates a styled button with hover effects.
+ *
+ * @param text The text to display on the button.
+ * @return A styled Button object.
+ */
+ private static Button createStyledButton(String text) {
+ Button button = new Button(text);
+ button.setFont(Font.font("Arial", FontWeight.BOLD, 16));
+ button.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #ffcc00, #ff9900);" +
+ "-fx-text-fill: black;" +
+ "-fx-background-radius: 10;" +
+ "-fx-padding: 10px 20px;"
+ );
+
+ // Add hover effects for better user interaction
+ button.setOnMouseEntered(_ -> button.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #ff9900, #ff6600);" +
+ "-fx-text-fill: white;" +
+ "-fx-background-radius: 10;" +
+ "-fx-padding: 10px 20px;"
+ ));
+ button.setOnMouseExited(_ -> button.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #ffcc00, #ff9900);" +
+ "-fx-text-fill: black;" +
+ "-fx-background-radius: 10;" +
+ "-fx-padding: 10px 20px;"
+ ));
+
+ return button;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/views/LeaderboardView.java b/src/main/java/edu/sdccd/cisc190/views/LeaderboardView.java
new file mode 100644
index 0000000..4d404c3
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/views/LeaderboardView.java
@@ -0,0 +1,212 @@
+package edu.sdccd.cisc190.views;
+
+import edu.sdccd.cisc190.players.HumanPlayer;
+import edu.sdccd.cisc190.players.bots.*;
+import edu.sdccd.cisc190.services.SlotMachineManager;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.beans.property.IntegerProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontWeight;
+import javafx.scene.text.Text;
+import javafx.stage.Stage;
+
+public class LeaderboardView extends Application {
+
+ public static TableView leaderboardTable;
+ private static final ObservableList entries = FXCollections.observableArrayList();
+
+ @Override
+ public void start(Stage primaryStage) {
+
+ // Listen to human player money changes
+ HumanPlayer.getInstance().moneyProperty().addListener((_, _, _) -> updateLeaderboard());
+
+ // Add listeners for all bot players
+ AnitaMaxWynn.getInstance().moneyProperty().addListener((_, _, _) -> updateLeaderboard());
+ HondaBoyz.getInstance().moneyProperty().addListener((_, _, _) -> updateLeaderboard());
+ MrBrooks.getInstance().moneyProperty().addListener((_, _, _) -> updateLeaderboard());
+ ProfessorHuang.getInstance().moneyProperty().addListener((_, _, _) -> updateLeaderboard());
+ Chase.getInstance().moneyProperty().addListener((_, _, _) -> updateLeaderboard());
+
+ showWindow(primaryStage);
+ }
+
+ /**
+ * Updates the leaderboard by sorting entries based on the amount of money in descending order.
+ */
+ private static void updateLeaderboard() {
+ FXCollections.sort(entries, (entry1, entry2) -> Integer.compare(entry2.money().get(), entry1.money().get()));
+ leaderboardTable.refresh();
+ }
+
+ /**
+ * Displays the leaderboard window with a sorted list of players and their money amounts.
+ *
+ * @param primaryStage The main stage for the application.
+ */
+ public static void showWindow(Stage primaryStage) {
+ VBox layout = createMainLayout();
+ primaryStage.setTitle("Leaderboard");
+
+ // Set the onCloseRequest handler to stop threads and exit the application
+ primaryStage.setOnCloseRequest(_ -> {
+ SlotMachineManager.stopAllThreads();
+ Platform.exit();
+ });
+
+ // Add header to the layout
+ layout.getChildren().add(createHeader());
+
+ // Create and populate TableView
+ leaderboardTable = createLeaderboardTable();
+ layout.getChildren().add(leaderboardTable);
+
+ // Create and style the main menu button
+ Button mainMenu = createStyledButton();
+ mainMenu.setOnAction(_ -> MainMenuView.setupWindow(primaryStage));
+ layout.getChildren().add(mainMenu);
+
+ // Setup and display the scene
+ setupScene(primaryStage, layout);
+ }
+
+ /**
+ * Creates the main layout for the leaderboard window.
+ *
+ * @return A VBox layout with predefined styles and spacing.
+ */
+ private static VBox createMainLayout() {
+ VBox layout = new VBox(20);
+ layout.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #000000, #660000);" +
+ "-fx-padding: 30px;"
+ );
+ layout.setAlignment(javafx.geometry.Pos.CENTER);
+ return layout;
+ }
+
+ /**
+ * Creates a styled header text for the leaderboard window.
+ *
+ * @return A styled Text object representing the header.
+ */
+ private static Text createHeader() {
+ Text header = new Text("Leaderboard");
+ header.setFont(Font.font("Verdana", FontWeight.BOLD, 30));
+ header.setFill(Color.GOLD);
+ return header;
+ }
+
+ /**
+ * Creates the TableView for displaying the leaderboard.
+ *
+ * @return A TableView populated with leaderboard entries, sorted by money.
+ */
+ private static TableView createLeaderboardTable() {
+ TableView table = new TableView<>();
+ table.setPrefHeight(300);
+
+ // Define columns
+ TableColumn nameColumn = new TableColumn<>("Name");
+ nameColumn.setCellValueFactory(cellData -> new javafx.beans.property.SimpleStringProperty(cellData.getValue().name()));
+ nameColumn.setPrefWidth(150);
+
+ TableColumn moneyColumn = new TableColumn<>("Money");
+ moneyColumn.setCellValueFactory(cellData -> cellData.getValue().money().asObject());
+ moneyColumn.setPrefWidth(150);
+
+ // Add columns to the table
+ table.getColumns().addAll(nameColumn, moneyColumn);
+
+ // Populate and sort data
+ table.setItems(getSortedLeaderboardData());
+
+ return table;
+ }
+
+ /**
+ * Gets the sorted data for the leaderboard table.
+ * Initializes the list if it's empty and sorts entries by money.
+ *
+ * @return An ObservableList containing sorted leaderboard entries.
+ */
+ private static ObservableList getSortedLeaderboardData() {
+ if (entries.isEmpty()) {
+ entries.addAll(
+ new LeaderboardEntry(HumanPlayer.getInstance().getName(), HumanPlayer.getInstance().moneyProperty()),
+ new LeaderboardEntry(AnitaMaxWynn.getInstance().getName(), AnitaMaxWynn.getInstance().moneyProperty()),
+ new LeaderboardEntry(Chase.getInstance().getName(), Chase.getInstance().moneyProperty()),
+ new LeaderboardEntry(HondaBoyz.getInstance().getName(), HondaBoyz.getInstance().moneyProperty()),
+ new LeaderboardEntry(MrBrooks.getInstance().getName(), MrBrooks.getInstance().moneyProperty()),
+ new LeaderboardEntry(ProfessorHuang.getInstance().getName(), ProfessorHuang.getInstance().moneyProperty())
+ );
+ }
+ FXCollections.sort(entries, (entry1, entry2) -> Integer.compare(entry2.money().get(), entry1.money().get()));
+ return entries;
+ }
+
+ /**
+ * Creates a styled button for navigation to the main menu.
+ *
+ * @return A styled Button object.
+ */
+ private static Button createStyledButton() {
+ Button button = new Button("Main Menu");
+ button.setFont(Font.font("Arial", FontWeight.BOLD, 16));
+ button.setStyle(createButtonStyle("#ffcc00", "#ff9900", "black"));
+
+ button.setOnMouseEntered(_ -> button.setStyle(createButtonStyle("#ff9900", "#ff6600", "white")));
+ button.setOnMouseExited(_ -> button.setStyle(createButtonStyle("#ffcc00", "#ff9900", "black")));
+
+ return button;
+ }
+
+ /**
+ * Generates a CSS style string for buttons.
+ *
+ * @param topColor The gradient's top color.
+ * @param bottomColor The gradient's bottom color.
+ * @param textColor The text color.
+ * @return A CSS style string.
+ */
+ private static String createButtonStyle(String topColor, String bottomColor, String textColor) {
+ return "-fx-background-color: linear-gradient(to bottom, " + topColor + ", " + bottomColor + ");" +
+ "-fx-text-fill: " + textColor + ";" +
+ "-fx-background-radius: 10;" +
+ "-fx-padding: 10px 20px;";
+ }
+
+ /**
+ * Sets up and displays the scene for the primary stage.
+ *
+ * @param primaryStage The main stage of the application.
+ * @param layout The layout to display on the stage.
+ */
+ private static void setupScene(Stage primaryStage, VBox layout) {
+ Scene scene = new Scene(layout, 600, 600);
+ primaryStage.setScene(scene);
+ primaryStage.show();
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ /**
+ * Represents a single entry in the leaderboard.
+ *
+ * @param name The name of the player.
+ * @param money The money property of the player.
+ */
+ public record LeaderboardEntry(String name, IntegerProperty money) {
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/views/MainMenuView.java b/src/main/java/edu/sdccd/cisc190/views/MainMenuView.java
new file mode 100644
index 0000000..e7c77e8
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/views/MainMenuView.java
@@ -0,0 +1,354 @@
+package edu.sdccd.cisc190.views;
+
+import edu.sdccd.cisc190.players.HumanPlayer;
+import edu.sdccd.cisc190.services.PlayerSavesService;
+import edu.sdccd.cisc190.services.SlotMachineManager;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.scene.Scene;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.layout.*;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontWeight;
+import javafx.scene.text.Text;
+import javafx.stage.Stage;
+import javafx.scene.control.Tooltip;
+
+import java.awt.*;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Random;
+
+/**
+ * MainMenuView is the main menu screen of the Casino application.
+ * It provides navigation to various sections of the game, including
+ * slot machine options, a leaderboard, and motivational resources.
+ * The class also handles pausing bots and managing user data.
+ */
+public class MainMenuView extends Application {
+ /**
+ * A static list of motivational URLs to be shown to users.
+ */
+ private static final String[] MOTIVATIONAL_URLS = {
+ "https://www.instagram.com/reel/C_JDcZVya_1/?igsh=NTc4MTIwNjQ2YQ==", // Add your own motivational URLs
+ "https://www.instagram.com/reel/DAZR6WlSsVk/?igsh=NTc4MTIwNjQ2YQ==",
+ "https://www.instagram.com/reel/DCz7-k5JxLT/?igsh=NTc4MTIwNjQ2YQ==",
+ "https://www.instagram.com/reel/DB1tqWqNWL8/?igsh=NTc4MTIwNjQ2YQ==",
+ "https://www.instagram.com/reel/DB9nUPfS1WC/?igsh=NTc4MTIwNjQ2YQ==",
+ "https://www.instagram.com/reel/DBpDgUVoFcK/?igsh=NTc4MTIwNjQ2YQ==",
+ "https://www.instagram.com/reel/DB8nzu7oW8K/?igsh=NTc4MTIwNjQ2YQ==",
+ "https://www.instagram.com/reel/C7ZnLuWoRbW/?igsh=NTc4MTIwNjQ2YQ==",
+ "https://www.instagram.com/reel/C_8R_SJPOe6/?igsh=NTc4MTIwNjQ2YQ=="
+ };
+
+ /**
+ * The primary stage of the application.
+ */
+ static Stage primaryStage;
+
+
+ /**
+ * Entry point for the JavaFX application.
+ *
+ * @param primaryStage the primary stage for the application.
+ */
+ @Override
+ public void start(Stage primaryStage) {
+ MainMenuView.primaryStage = primaryStage;
+ setupWindow(primaryStage);
+ }
+
+ /**
+ * Configures and displays the main menu interface.
+ *
+ * @param primaryStage the primary stage for the application.
+ */
+ static void setupWindow(Stage primaryStage) {
+ VBox layout = createMainLayout();
+ primaryStage.setTitle("Casino Menu");
+
+ // Add header and user info
+ layout.getChildren().addAll(
+ createHeader(),
+ createUserInfo("Username: %s".formatted(HumanPlayer.getInstance().getName())),
+ createUserInfo("Money: $%d".formatted(HumanPlayer.getInstance().getMoney()))
+ );
+
+ // Add slot option buttons
+ addSlotOptionButtons(layout, primaryStage);
+
+ Button motivationButton = createMotivationButton();
+ layout.getChildren().add(motivationButton);
+
+ // Setup and display the scene
+ setupScene(primaryStage, layout);
+ }
+
+ /**
+ * Creates a button that opens a random motivational URL in the browser.
+ *
+ * @return the motivation button.
+ */
+ private static Button createMotivationButton() {
+ Button motivationButton = createSecondaryButton("Motivation", "Get inspired to keep going!");
+
+ motivationButton.setOnAction(_ -> {
+ Random random = new Random();
+ int randomIndex = random.nextInt(MOTIVATIONAL_URLS.length);
+ String selectedUrl = MOTIVATIONAL_URLS[randomIndex];
+
+ try {
+ Desktop desktop = Desktop.getDesktop();
+ desktop.browse(new URI(selectedUrl));
+ } catch (IOException | URISyntaxException e) {
+ showMessage("Failed to open the link. Please try again.");
+ }
+ });
+
+ return motivationButton;
+ }
+
+ /**
+ * Creates and configures the main layout for the menu.
+ * The layout is styled with padding and a gradient background.
+ *
+ * @return a VBox containing the main layout.
+ */
+ private static VBox createMainLayout() {
+ VBox layout = new VBox(20);
+ layout.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #000000, #660000);" +
+ "-fx-padding: 30px;"
+ );
+ layout.setAlignment(javafx.geometry.Pos.CENTER);
+ return layout;
+ }
+
+ /**
+ * Creates a header text for the main menu.
+ * The header displays the title "Casino" with bold styling.
+ *
+ * @return a Text object representing the header.
+ */
+ private static Text createHeader() {
+ Text header = new Text("Casino");
+ header.setFont(Font.font("Verdana", FontWeight.BOLD, 30));
+ header.setFill(Color.GOLD);
+ return header;
+ }
+
+ /**
+ * Creates a styled text element to display user information.
+ * The text is styled with a semi-bold font and white color.
+ *
+ * @param text the information to display.
+ * @return a Text object representing the user information.
+ */
+ private static Text createUserInfo(String text) {
+ Text userInfo = new Text(text);
+ userInfo.setFont(Font.font("Arial", FontWeight.SEMI_BOLD, 18));
+ userInfo.setFill(Color.WHITE);
+ return userInfo;
+ }
+
+ /**
+ * Creates a styled button with hover effects and an optional tooltip.
+ * The button changes styles on hover for a better user experience.
+ *
+ * @param text the text displayed on the button.
+ * @param tooltipText the tooltip text for the button, or null if none.
+ * @return a styled Button object.
+ */
+ private static Button createStyledButton(String text, String tooltipText) {
+ Button button = new Button(text);
+ button.setFont(Font.font("Arial", FontWeight.BOLD, 16));
+
+ String defaultStyle = createButtonStyle("#ffcc00", "#ff9900", "black");
+ String hoverStyle = createButtonStyle("#784800", "#943b00", "white");
+
+ return getButton(tooltipText, button, defaultStyle, hoverStyle);
+ }
+
+ /**
+ * Creates a secondary styled button with hover effects and an optional tooltip.
+ * The button has a lighter theme and is intended for less prominent actions.
+ *
+ * @param text the text displayed on the button.
+ * @param tooltipText the tooltip text for the button, or null if none.
+ * @return a secondary styled Button object.
+ */
+ private static Button createSecondaryButton(String text, String tooltipText) {
+ Button button = new Button(text);
+ button.setFont(Font.font("Arial", FontWeight.BOLD, 14));
+
+ String defaultStyle = createButtonStyle("#cccccc", "#888888", "black");
+ String hoverStyle = createButtonStyle("#aaaaaa", "#666666", "white");
+
+ return getButton(tooltipText, button, defaultStyle, hoverStyle);
+ }
+
+ private static Button getButton(String tooltipText, Button button, String defaultStyle, String hoverStyle) {
+ button.setStyle(defaultStyle);
+ button.setOnMouseEntered(_ -> button.setStyle(hoverStyle));
+ button.setOnMouseExited(_ -> button.setStyle(defaultStyle));
+
+ if (tooltipText != null) {
+ button.setTooltip(createTooltip(tooltipText));
+ }
+
+ return button;
+ }
+
+ /**
+ * Creates a CSS style string for a button.
+ *
+ * @param topColor the top gradient color.
+ * @param bottomColor the bottom gradient color.
+ * @param textColor the text color for the button.
+ * @return a string representing the CSS style for the button.
+ */
+ private static String createButtonStyle(String topColor, String bottomColor, String textColor) {
+ return "-fx-background-color: linear-gradient(to bottom, %s, %s);-fx-text-fill: %s;-fx-background-radius: 10;-fx-padding: 10px 20px;".formatted(topColor, bottomColor, textColor);
+ }
+
+ /**
+ * Displays an informational message in an alert dialog.
+ *
+ * @param message the message to display.
+ */
+ private static void showMessage(String message) {
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle("Information");
+ alert.setHeaderText(null);
+ alert.setContentText(message);
+ alert.showAndWait();
+ }
+
+ /**
+ * Configures and displays the main scene of the application.
+ *
+ * @param primaryStage the primary stage for the application.
+ * @param layout the layout to be displayed in the scene.
+ */
+ private static void setupScene(Stage primaryStage, VBox layout) {
+ primaryStage.setOnCloseRequest(_ -> {
+ SlotMachineManager.stopAllThreads();
+ Platform.exit();
+ });
+
+ // Adjust the width and height as desired
+ Scene scene = new Scene(layout, 800, 800); // Changed from 600, 600 to 800, 800
+ primaryStage.setScene(scene);
+ primaryStage.show();
+ }
+
+ /**
+ * Adds buttons for slot machine options to the provided layout.
+ * The buttons allow navigation to various game features.
+ *
+ * @param layout the layout to which buttons are added.
+ * @param primaryStage the primary stage for the application.
+ */
+ private static void addSlotOptionButtons(VBox layout, Stage primaryStage) {
+ SlotOptions[] options = SlotOptions.values();
+ for (int i = 0; i < options.length; i++) {
+ SlotOptions option = options[i];
+ Button slotButton;
+
+ String tooltipText = switch (option) {
+ case DIAMOND_DASH -> "Play Diamond Dash for sparkling wins! Min Bet: 15, Max Bet: 1000, Return: 2x";
+ case HONDA_TRUNK -> "Spin the wheels with Honda Trunk. Min Bet: 1, Max Bet: 1000, Return: 1.5x";
+ case MEGA_MOOLAH -> "Massive jackpots in Mega Moolah! Min Bet: 10, Max Bet: 1000, Return: 3x";
+ case RAINBOW_RICHES -> "Discover treasures in Rainbow Riches. Min Bet: 25, Max Bet: 1000, Return: 5x";
+ case TREASURE_SPINS -> "Uncover hidden wealth with Treasure Spins. Min Bet: 50, Max Bet: 1000, Return: 10x";
+ case LEADERBOARD -> "View the current leaderboard standings.";
+ case QUIT -> "Return to the Matrix";
+ };
+
+ if (i >= options.length - 2) { // Use secondary style for last buttons
+ slotButton = createSecondaryButton(option.getDisplayOption(), tooltipText);
+ } else {
+ slotButton = createStyledButton(option.getDisplayOption(), tooltipText);
+ }
+
+ slotButton.setOnAction(_ -> handleSlotOption(primaryStage, option));
+ layout.getChildren().add(slotButton);
+ }
+ }
+
+ /**
+ * Handles actions for slot machine options.
+ *
+ * @param primaryStage the primary stage for the application.
+ * @param option the selected slot option.
+ */
+ private static void handleSlotOption(Stage primaryStage, SlotOptions option) {
+ switch (option) {
+ case DIAMOND_DASH, HONDA_TRUNK, MEGA_MOOLAH, RAINBOW_RICHES, TREASURE_SPINS ->
+ BetView.showWindow(primaryStage, option);
+ case LEADERBOARD -> LeaderboardView.showWindow(primaryStage);
+ case QUIT -> quitApplication();
+ default -> showMessage("Default option selected.");
+ }
+ }
+
+ /**
+ * Exits the application with a goodbye message.
+ */
+ private static void quitApplication() {
+ // Show goodbye message
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle("Goodbye!");
+ alert.setContentText("Come back soon! 99.9% of gamblers quit before hitting it big!");
+ alert.showAndWait();
+ PlayerSavesService.saveState();
+ Platform.exit();
+
+ // Exit the program
+ System.exit(0);
+ }
+
+ /**
+ * Enum representing the slot machine options available in the game.
+ */
+ public enum SlotOptions {
+ DIAMOND_DASH("Diamond Dash"),
+ HONDA_TRUNK("Honda Trunk"),
+ MEGA_MOOLAH("Mega Moolah"),
+ RAINBOW_RICHES("Rainbow Riches"),
+ TREASURE_SPINS("Treasure Spins"),
+ LEADERBOARD("Leaderboard"),
+ QUIT("Quit");
+
+ private final String displayOption;
+
+ SlotOptions(String displayOption) {
+ this.displayOption = displayOption;
+ }
+
+ /**
+ * Gets the display name for the slot option.
+ *
+ * @return the display name.
+ */
+ public String getDisplayOption() {
+ return displayOption;
+ }
+ }
+
+ /**
+ * Creates a tooltip with styled text.
+ *
+ * @param text the text for the tooltip.
+ * @return the styled tooltip.
+ */
+ private static Tooltip createTooltip(String text) {
+ Tooltip tooltip = new Tooltip(text);
+ tooltip.setFont(Font.font("Arial", FontWeight.NORMAL, 12));
+ tooltip.setStyle("-fx-background-color: #444; -fx-text-fill: white; -fx-padding: 5px;");
+ return tooltip;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/views/SetupView.java b/src/main/java/edu/sdccd/cisc190/views/SetupView.java
new file mode 100644
index 0000000..83dcff2
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/views/SetupView.java
@@ -0,0 +1,149 @@
+package edu.sdccd.cisc190.views;
+
+import edu.sdccd.cisc190.players.HumanPlayer;
+import edu.sdccd.cisc190.services.PlayerSavesService;
+import edu.sdccd.cisc190.services.SlotMachineManager;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.geometry.Pos;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontWeight;
+import javafx.stage.Stage;
+
+/**
+ * The SetupView class is the first screen of the Casino Royale application.
+ * It prompts the user to enter their name and serves as the gateway to the Main Menu.
+ * If player data already exists, the application skips this screen and proceeds to the Main Menu.
+ */
+public class SetupView extends Application {
+ /**
+ * The username entered by the user. This is used to identify the player in the game.
+ */
+ static String userName;
+
+ /**
+ * The entry point for the JavaFX application. Determines whether to load existing player data
+ * or show the sign-in window for new players.
+ *
+ * @param primaryStage the primary stage for the application.
+ */
+ @Override
+ public void start(Stage primaryStage) {
+ // Check if player data file exists and load it
+ if (PlayerSavesService.loadState()) {
+ // Proceed directly to the MainMenu if data was loaded
+ Stage mainMenuStage = new Stage();
+ MainMenuView.setupWindow(mainMenuStage);
+ primaryStage.close();
+ } else {
+ // Show sign-in window if no data was loaded
+ showSignInWindow(primaryStage);
+ }
+ }
+
+ /**
+ * Displays the sign-in window for the user to enter their name.
+ * @param primaryStage the primary stage for the sign-in window.
+ */
+ private void showSignInWindow(Stage primaryStage) {
+
+ primaryStage.setOnCloseRequest(_ -> {
+ SlotMachineManager.stopAllThreads();
+ Platform.exit();
+ });
+
+ primaryStage.setTitle("Casino - Sign In");
+
+ // Create labels, text field, and button for the sign-in window
+ Label welcomeLabel = new Label("Welcome to the Casino!");
+ Label nameLabel = new Label("What's your name?");
+ nameLabel.setFont(Font.font("Verdana", FontWeight.BOLD, 16));
+ nameLabel.setTextFill(Color.GOLD);
+
+ TextField nameField = new TextField();
+ nameField.setPromptText("Enter Your Name");
+ nameField.setPrefWidth(250);
+
+ Button submitButton = new Button("Enter the Casino");
+ submitButton.setFont(Font.font("Arial", FontWeight.BOLD, 14));
+
+ // Configure button styles and hover effects
+ submitButton.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #ffcc00, #ff9900);" +
+ "-fx-text-fill: black;" +
+ "-fx-background-radius: 10;" +
+ "-fx-padding: 10px 20px;"
+ );
+
+ submitButton.setOnMouseEntered(_ -> submitButton.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #ff9900, #ff6600);" +
+ "-fx-text-fill: white;" +
+ "-fx-background-radius: 10;" +
+ "-fx-padding: 10px 20px;"
+ ));
+
+ submitButton.setOnMouseExited(_ -> submitButton.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #ffcc00, #ff9900);" +
+ "-fx-text-fill: black;" +
+ "-fx-background-radius: 10;" +
+ "-fx-padding: 10px 20px;"
+ ));
+
+ welcomeLabel.setStyle(
+ "-fx-background-color: #333333; " +
+ "-fx-text-fill: white; " +
+ "-fx-prompt-text-fill: #aaaaaa; " +
+ "-fx-background-radius: 10; " +
+ "-fx-padding: 10px;"
+ );
+
+ nameField.setStyle(
+ "-fx-background-color: #333333; " +
+ "-fx-text-fill: white; " +
+ "-fx-prompt-text-fill: #aaaaaa; " +
+ "-fx-background-radius: 10; " +
+ "-fx-padding: 10px;"
+ );
+
+ // Define action for the submit button
+ submitButton.setOnAction(_ -> {
+ userName = nameField.getText();
+ HumanPlayer tempPlayer = HumanPlayer.getInstance();
+ tempPlayer.setUsername(userName);
+ tempPlayer.setMoney(1000); // Default starting money if no file was loaded
+ primaryStage.close();
+
+ Stage newWindow = new Stage();
+ MainMenuView.setupWindow(newWindow);
+ });
+
+ // Layout and styling for the sign-in window
+ VBox layout = new VBox(20); // Spacing between components
+ layout.getChildren().addAll(welcomeLabel, nameLabel, nameField, submitButton);
+ layout.setAlignment(Pos.CENTER);
+ layout.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #000000, #660000);" + // Casino gradient
+ "-fx-padding: 20px;"
+ );
+
+ // Create and show the scene
+ Scene scene = new Scene(layout, 350, 250); // Compact window size
+ primaryStage.setScene(scene);
+ primaryStage.show();
+ }
+
+ /**
+ * Launches the JavaFX application.
+ *
+ * @param args the command-line arguments (if any).
+ */
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/sdccd/cisc190/views/SlotMachineView.java b/src/main/java/edu/sdccd/cisc190/views/SlotMachineView.java
new file mode 100644
index 0000000..85a3201
--- /dev/null
+++ b/src/main/java/edu/sdccd/cisc190/views/SlotMachineView.java
@@ -0,0 +1,248 @@
+package edu.sdccd.cisc190.views;
+
+import edu.sdccd.cisc190.players.HumanPlayer;
+import edu.sdccd.cisc190.machines.Slot;
+import edu.sdccd.cisc190.machines.*;
+import edu.sdccd.cisc190.services.PlayerSavesService;
+import edu.sdccd.cisc190.services.SlotMachineManager;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.geometry.Pos;
+import javafx.scene.Scene;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontWeight;
+import javafx.stage.Stage;
+
+/**
+ * The SlotMachineView class represents the user interface for the slot machine gameplay.
+ * It displays the slot machine reels, bet information, and controls for spinning the slots,
+ * changing the bet, or returning to the Main Menu.
+ */
+public class SlotMachineView extends Application {
+
+ /**
+ * Labels to display betting and gameplay information.
+ */
+ private static final Label betAmount = new Label();
+ private static final Label maxBet = new Label();
+ private static final Label minBet = new Label();
+ private static final Label returnAmount = new Label();
+ private static final Label slot1 = new Label("❓");
+ private static final Label slot2 = new Label("❓");
+ private static final Label slot3 = new Label("❓");
+ private static final Label won = new Label("Spin to see!");
+ private static final Label money = new Label("Balance: $%d".formatted(HumanPlayer.getInstance().getMoney()));
+
+ /**
+ * Buttons for interacting with the slot machine interface.
+ */
+ static Button spinButton = createStyledButton("Spin");
+ static Button changeBet = createStyledButton("Change Bet");
+ static Button mainMenu = createStyledButton("Return to Main Menu");
+
+ /**
+ * The selected slot machine type and the corresponding slot machine instance.
+ */
+ static MainMenuView.SlotOptions machineSelect;
+ static Slot slotMachine;
+
+ /**
+ * The entry point for the JavaFX application.
+ *
+ * @param primaryStage the primary stage for the slot machine gameplay window.
+ */
+ @Override
+ public void start(Stage primaryStage) {
+ showWindow(primaryStage, 0, MainMenuView.SlotOptions.DIAMOND_DASH);
+ }
+
+ /**
+ * The main method launches the JavaFX application.
+ *
+ * @param args the command-line arguments.
+ */
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ /**
+ * Displays the slot machine gameplay window.
+ *
+ * @param primaryStage the primary stage for the application.
+ * @param betAmt the initial betting amount.
+ * @param selectedMachine the type of slot machine selected.
+ */
+ public static void showWindow(Stage primaryStage, int betAmt, MainMenuView.SlotOptions selectedMachine) {
+
+ primaryStage.setOnCloseRequest(_ -> {
+ SlotMachineManager.stopAllThreads();
+ Platform.exit();
+ });
+
+ machineSelect = selectedMachine;
+ switch (selectedMachine) {
+ case HONDA_TRUNK -> slotMachine = new HondaTrunk();
+ case TREASURE_SPINS -> slotMachine = new TreasureSpins();
+ case MEGA_MOOLAH -> slotMachine = new MegaMoolah();
+ case RAINBOW_RICHES -> slotMachine = new RainbowRiches();
+ default -> slotMachine = new DiamondDash();
+ }
+
+ primaryStage.setTitle("Casino - Slot Machine");
+
+ // Styled Labels
+ betAmount.setText("You're betting: $%d".formatted(betAmt));
+ betAmount.setFont(Font.font("Arial", FontWeight.BOLD, 20));
+ betAmount.setTextFill(Color.LIGHTGOLDENRODYELLOW);
+
+ infoSetText(maxBet, minBet, returnAmount);
+
+
+ slot1.setStyle("-fx-font-size: 60px;");
+ slot2.setStyle("-fx-font-size: 60px;");
+ slot3.setStyle("-fx-font-size: 60px;");
+ slot1.setTextFill(Color.ORANGERED);
+ slot2.setTextFill(Color.ORANGERED);
+ slot3.setTextFill(Color.ORANGERED);
+
+
+ won.setFont(Font.font("Arial", FontWeight.BOLD, 24));
+ won.setTextFill(Color.GOLD);
+
+ money.setFont(Font.font("Arial", FontWeight.BOLD, 20));
+ money.setTextFill(Color.LIGHTGREEN);
+
+ // Button Actions
+ spinButton.setOnAction(_ -> spin(betAmt, primaryStage));
+ changeBet.setOnAction(_ -> {
+ primaryStage.close();
+ BetView.showWindow(primaryStage, machineSelect);
+ });
+ mainMenu.setOnAction(_ -> {
+ primaryStage.close();
+ MainMenuView.setupWindow(primaryStage);
+ });
+
+ // Slot information
+ HBox slotInformation = new HBox(10, maxBet, minBet, returnAmount);
+ slotInformation.setAlignment(Pos.CENTER);
+
+ // Slots Display Row
+ HBox slotsRow = new HBox(20, slot1, slot2, slot3);
+ slotsRow.setAlignment(Pos.CENTER);
+
+ // Main Layout
+ VBox layout = new VBox(20, betAmount, won, money, slotInformation, slotsRow, spinButton, changeBet, mainMenu);
+ layout.setAlignment(Pos.CENTER);
+ layout.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #000000, #660000);" +
+ "-fx-padding: 30px;"
+ );
+
+ // Scene Setup
+ Scene scene = new Scene(layout, 800, 800);
+ primaryStage.setScene(scene);
+ primaryStage.show();
+ }
+
+ static void infoSetText(Label maxBet, Label minBet, Label returnAmount) {
+ maxBet.setText("Max. Bet: %d".formatted(slotMachine.getMaxBet()));
+ maxBet.setFont(Font.font("Arial", FontWeight.SEMI_BOLD, 15));
+ minBet.setText("Min. Bet: %d".formatted(slotMachine.getMinBet()));
+ minBet.setFont(Font.font("Arial", FontWeight.SEMI_BOLD, 15));
+ returnAmount.setText("Return: %s".formatted(slotMachine.getReturnAmt()));
+ returnAmount.setFont(Font.font("Arial", FontWeight.SEMI_BOLD, 15));
+ maxBet.setTextFill(Color.RED);
+ minBet.setTextFill(Color.RED);
+ returnAmount.setTextFill(Color.RED);
+ }
+
+ /**
+ * Handles the spin action for the slot machine.
+ *
+ * @param betAmt the amount of money being bet.
+ * @param primaryStage the primary stage for the application.
+ */
+ private static void spin(int betAmt, Stage primaryStage) {
+ if (!slotMachine.canBet(betAmt)) {
+ showAlert("Invalid Bet", "Your bet is outside the allowed range or exceeds your balance.");
+ primaryStage.close();
+ BetView.showWindow(primaryStage, machineSelect);
+ return;
+ }
+
+ String[] symbols = slotMachine.generateSpunSymbols();
+ slot1.setText(symbols[0]);
+ slot2.setText(symbols[1]);
+ slot3.setText(symbols[2]);
+
+ int newBalance = slotMachine.calculatePayout(HumanPlayer.getInstance().getMoney(), symbols, betAmt);
+ HumanPlayer.getInstance().setMoney(newBalance);
+
+ if (slotMachine.evaluateWinCondition(symbols) >= 2) {
+ won.setText("Wow, you won!");
+ } else {
+ won.setText("You lost :(");
+ }
+
+ money.setText("Balance: $%d".formatted(HumanPlayer.getInstance().getMoney()));
+
+ if (HumanPlayer.getInstance().getMoney() <= 0) {
+ showAlert("Game Over", "You're out of money! Better luck next time.");
+ PlayerSavesService.deleteState();
+ primaryStage.close();
+ }
+ }
+
+ /**
+ * Displays an alert dialog with the specified title and content.
+ *
+ * @param title the title of the alert.
+ * @param content the content of the alert.
+ */
+ private static void showAlert(String title, String content) {
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle(title);
+ alert.setHeaderText(null);
+ alert.setContentText(content);
+ alert.showAndWait();
+ }
+
+ /**
+ * Creates a styled button with the specified text.
+ *
+ * @param text the text to display on the button.
+ * @return a styled Button instance.
+ */
+ private static Button createStyledButton(String text) {
+ Button button = new Button(text);
+ button.setFont(Font.font("Arial", FontWeight.BOLD, 16));
+ button.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #ffcc00, #ff9900);" +
+ "-fx-text-fill: black;" +
+ "-fx-background-radius: 10;" +
+ "-fx-padding: 10px 20px;"
+ );
+
+ button.setOnMouseEntered(_ -> button.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #ff9900, #ff6600);" +
+ "-fx-text-fill: white;" +
+ "-fx-background-radius: 10;" +
+ "-fx-padding: 10px 20px;"
+ ));
+ button.setOnMouseExited(_ -> button.setStyle(
+ "-fx-background-color: linear-gradient(to bottom, #ffcc00, #ff9900);" +
+ "-fx-text-fill: black;" +
+ "-fx-background-radius: 10;" +
+ "-fx-padding: 10px 20px;"
+ ));
+
+ return button;
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/AppName.txt b/src/main/resources/AppName.txt
index 077d56e..d287e29 100644
--- a/src/main/resources/AppName.txt
+++ b/src/main/resources/AppName.txt
@@ -1 +1 @@
-CISC190 Final Project
\ No newline at end of file
+Casino Royale
\ No newline at end of file
diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties
new file mode 100644
index 0000000..f93f9e9
--- /dev/null
+++ b/src/main/resources/log4j2.properties
@@ -0,0 +1,8 @@
+# Root Logger
+rootLogger=DEBUG, STDOUT
+
+# Direct log messages to stdout
+appender.console.type = Console
+appender.console.name = STDOUT
+appender.console.layout.type = PatternLayout
+appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n
\ No newline at end of file
diff --git a/src/test/java/edu/sdccd/cisc190/BotServiceTest.java b/src/test/java/edu/sdccd/cisc190/BotServiceTest.java
new file mode 100644
index 0000000..c03f434
--- /dev/null
+++ b/src/test/java/edu/sdccd/cisc190/BotServiceTest.java
@@ -0,0 +1,61 @@
+package edu.sdccd.cisc190;
+
+import edu.sdccd.cisc190.machines.DiamondDash;
+import edu.sdccd.cisc190.machines.HondaTrunk;
+import edu.sdccd.cisc190.players.bots.Chase;
+import edu.sdccd.cisc190.services.BotService;
+import javafx.application.Platform;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class BotServiceTest {
+ private DiamondDash diamondDash;
+ private HondaTrunk hondaTrunk;
+ private BotService botService;
+ private Chase chase;
+
+ @BeforeAll
+ static void initializeJavaFX() {
+ Platform.startup(() -> {});
+ }
+
+ @BeforeEach
+ void setup() {
+ chase = Chase.getInstance();
+ diamondDash = new DiamondDash();
+ hondaTrunk = new HondaTrunk();
+ botService = new BotService(chase, diamondDash);
+ Thread botThread = new Thread(botService);
+ botThread.start();
+ }
+
+ @Test
+ void testBotAssignmentToMachine() {
+ //Chase bot should be assigned to Diamond Dash slot machine
+ assertEquals(diamondDash, botService.getSlotMachine(), "Chase should be assigned to Diamond Dash.");
+
+ //Reassign Chase to Honda Trunk and verify bot was properly reassigned
+ botService.setSlotMachine(hondaTrunk);
+ assertEquals(hondaTrunk, botService.getSlotMachine(), "Chase should be set to Honda Trunk");
+ }
+
+ @Test
+ void testTriggerSpinUpdatesMoney() throws InterruptedException {
+ //store bot's initial money
+ int initialMoney = chase.getMoney();
+
+ //trigger spin
+ botService.triggerSpin();
+
+ //wait a second for spin to complete
+ Thread.sleep(2000);
+
+ //ensure bot money updates
+ int updatedMoney = chase.getMoney();
+ assertNotEquals(initialMoney, updatedMoney, "Bot's money should update after spin.");
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/edu/sdccd/cisc190/BotTest.java b/src/test/java/edu/sdccd/cisc190/BotTest.java
new file mode 100644
index 0000000..f95bb52
--- /dev/null
+++ b/src/test/java/edu/sdccd/cisc190/BotTest.java
@@ -0,0 +1,71 @@
+package edu.sdccd.cisc190;
+
+import edu.sdccd.cisc190.players.bots.*;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class BotTest {
+
+ @Test
+ void isAnitaMaxWynnInstanceOfBots() {
+ AnitaMaxWynn anitaMaxWynn = AnitaMaxWynn.getInstance();
+
+ //determine if specific bot is a child of Bot class
+ assertInstanceOf(Bot.class, anitaMaxWynn);
+
+ //assert that the values of the attributes are assigned correctly
+ assertEquals("Anita Max Wynn", anitaMaxWynn.getName());
+ assertEquals(1000, anitaMaxWynn.getMoney());
+ assertEquals(0.8, anitaMaxWynn.getLuck());
+ assertEquals(0.3, anitaMaxWynn.getAura());
+ }
+
+ @Test
+ void isChaseInstanceOfBots() {
+ Chase chase = Chase.getInstance();
+
+ assertInstanceOf(Bot.class, chase);
+
+ assertEquals("Chase Allan", chase.getName());
+ assertEquals(1000, chase.getMoney());
+ assertEquals(0.25, chase.getLuck());
+ assertEquals(0.1, chase.getAura());
+ }
+
+ @Test
+ void isHondaBoyzInstanceOfBots() {
+ HondaBoyz hondaBoyz = HondaBoyz.getInstance();
+
+ assertInstanceOf(Bot.class, hondaBoyz);
+
+ assertEquals("HondaBoyz", hondaBoyz.getName());
+ assertEquals(1000, hondaBoyz.getMoney());
+ assertEquals(1.0, hondaBoyz.getLuck());
+ assertEquals(0.1, hondaBoyz.getAura());
+ }
+
+ @Test
+ void isMrBrooksInstanceOfBots() {
+ MrBrooks mrBrooks = MrBrooks.getInstance();
+
+ assertInstanceOf(Bot.class, mrBrooks);
+
+ assertEquals("MrBrooks", mrBrooks.getName());
+ assertEquals(1000, mrBrooks.getMoney());
+ assertEquals(0.5, mrBrooks.getLuck());
+ assertEquals(0.7, mrBrooks.getAura());
+ }
+
+ @Test
+ void isProfessorHuangInstanceOfBots() {
+ ProfessorHuang professorHuang = ProfessorHuang.getInstance();
+
+ assertInstanceOf(Bot.class, professorHuang);
+
+ assertEquals("Professor Huang", professorHuang.getName());
+ assertEquals(1000, professorHuang.getMoney());
+ assertEquals(0.95, professorHuang.getLuck());
+ assertEquals(0.6, professorHuang.getAura());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/sdccd/cisc190/MachineTest.java b/src/test/java/edu/sdccd/cisc190/MachineTest.java
new file mode 100644
index 0000000..a02b971
--- /dev/null
+++ b/src/test/java/edu/sdccd/cisc190/MachineTest.java
@@ -0,0 +1,81 @@
+package edu.sdccd.cisc190;
+
+import edu.sdccd.cisc190.machines.*;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class MachineTest {
+
+ @Test
+ void isDiamondDashChildOfSlot() {
+ DiamondDash diamondDash = new DiamondDash();
+
+ //verify that slot machine game is an instance of a Slot
+ assertInstanceOf(Slot.class, diamondDash);
+
+ //verify that the attributes of the game are valid attributes of parent Slot
+ assertEquals(15, diamondDash.getMinBet());
+ assertEquals(1000, diamondDash.getMaxBet());
+ assertEquals(2, diamondDash.getReturnAmt());
+
+ String[] expectedDDSymbols = {"💍", "💠", "💎"};
+ assertArrayEquals(expectedDDSymbols, diamondDash.getSymbols());
+ }
+
+ @Test
+ void isHondaTrunkChildOfSlot() {
+ HondaTrunk hondaTrunk = new HondaTrunk();
+
+ assertInstanceOf(Slot.class, hondaTrunk);
+
+ assertEquals(1, hondaTrunk.getMinBet());
+ assertEquals(1000, hondaTrunk.getMaxBet());
+ assertEquals(1.5, hondaTrunk.getReturnAmt());
+
+ String[] expectedHTSymbols = {"🚗", "🛻", "🚕"};
+ assertArrayEquals(expectedHTSymbols, hondaTrunk.getSymbols());
+ }
+
+ @Test
+ void isMegaMoolahChildOfSlot() {
+ MegaMoolah megaMoolah = new MegaMoolah();
+
+ assertInstanceOf(Slot.class, megaMoolah);
+
+ assertEquals(10, megaMoolah.getMinBet());
+ assertEquals(1000, megaMoolah.getMaxBet());
+ assertEquals(3, megaMoolah.getReturnAmt());
+
+ String[] expectedMMSymbols = {"\uD83D\uDCB0", "\uD83E\uDD11", "\uD83D\uDCB8"};
+ assertArrayEquals(expectedMMSymbols, megaMoolah.getSymbols());
+ }
+
+ @Test
+ void isRainbowRichesChildOfSlot() {
+ RainbowRiches rainbowRiches = new RainbowRiches();
+
+ assertInstanceOf(Slot.class, rainbowRiches);
+
+ assertEquals(25, rainbowRiches.getMinBet());
+ assertEquals(1000, rainbowRiches.getMaxBet());
+ assertEquals(5, rainbowRiches.getReturnAmt());
+
+ String[] expectedRRSymbols = {"\uD83C\uDF08", "\uD83C\uDF27", "\uD83C\uDF24"};
+ assertArrayEquals(expectedRRSymbols, rainbowRiches.getSymbols());
+ }
+
+ @Test
+ void isTreasureSpinsChildOfSlot() {
+ TreasureSpins treasureSpins = new TreasureSpins();
+
+ assertInstanceOf(Slot.class, treasureSpins);
+
+ assertEquals(50, treasureSpins.getMinBet());
+ assertEquals(1000, treasureSpins.getMaxBet());
+ assertEquals(10, treasureSpins.getReturnAmt());
+
+ String[] expectedTSSymbols = {"\uD83C\uDF53", "\uD83C\uDF4C", "\uD83C\uDF4A"};
+ assertArrayEquals(expectedTSSymbols, treasureSpins.getSymbols());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/sdccd/cisc190/MainTest.java b/src/test/java/edu/sdccd/cisc190/MainTest.java
deleted file mode 100644
index accc83e..0000000
--- a/src/test/java/edu/sdccd/cisc190/MainTest.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package edu.sdccd.cisc190;
-
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class MainTest {
-
- @Test
- void getAppName() throws IOException {
- assertEquals("CISC190 Final Project", Main.getAppName());
- }
-}
\ No newline at end of file
diff --git a/src/test/java/edu/sdccd/cisc190/PlayerSavesServiceTest.java b/src/test/java/edu/sdccd/cisc190/PlayerSavesServiceTest.java
new file mode 100644
index 0000000..bbe60a9
--- /dev/null
+++ b/src/test/java/edu/sdccd/cisc190/PlayerSavesServiceTest.java
@@ -0,0 +1,131 @@
+package edu.sdccd.cisc190;
+
+import edu.sdccd.cisc190.players.HumanPlayer;
+import edu.sdccd.cisc190.services.PlayerSavesService;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class PlayerSavesServiceTest {
+
+ private final File saveFile = new File("player_data.txt");
+
+ @BeforeEach
+ void setUp() {
+ // Ensure a clean environment before each test
+ if (saveFile.exists()) {
+ saveFile.delete();
+ }
+
+ // Set up the HumanPlayer instance
+ HumanPlayer player = HumanPlayer.getInstance();
+ player.setUsername("TestUser");
+ player.setMoney(100);
+ }
+
+ @AfterEach
+ void tearDown() {
+ // Clean up after each test
+ try {
+ if (saveFile.exists()) {
+ Path path = saveFile.toPath();
+ Files.delete(path);
+ }
+ } catch (Exception e) {
+ System.err.printf("Failed to delete the save file: %s%n", e.getMessage());
+ }
+ }
+
+ @Test
+ void testSaveState() {
+ // Call the saveState method
+ PlayerSavesService.saveState();
+
+ // Assert that the file is created
+ assertTrue(saveFile.exists(), "Save file should be created");
+
+ // Assert that the content of the file is correct
+ try (var reader = new java.io.BufferedReader(new java.io.FileReader(saveFile))) {
+ String line = reader.readLine();
+ assertEquals("Username: TestUser, Money: $100", line, "Save file content should match expected format");
+ } catch (Exception e) {
+ fail("Unexpected exception reading the save file: %s".formatted(e.getMessage()));
+ }
+ }
+
+ @Test
+ void testLoadState() {
+ // Create a save file manually
+ try (var writer = new java.io.BufferedWriter(new java.io.FileWriter(saveFile))) {
+ writer.write("Username: TestUser, Money: $100");
+ writer.newLine();
+ } catch (Exception e) {
+ fail("Unexpected exception creating the save file: %s".formatted(e.getMessage()));
+ }
+
+ // Call the loadState method
+ boolean result = PlayerSavesService.loadState();
+
+ // Assert that the method returns true
+ assertTrue(result, "loadState should return true when file exists and is valid");
+
+ // Assert that the HumanPlayer's state is updated
+ HumanPlayer player = HumanPlayer.getInstance();
+ assertEquals("TestUser", player.getName(), "HumanPlayer's name should match the loaded data");
+ assertEquals(100, player.getMoney(), "HumanPlayer's money should match the loaded data");
+ }
+
+ @Test
+ void testLoadStateFileDoesNotExist() {
+ // Ensure the save file does not exist
+ if (saveFile.exists()) {
+ saveFile.delete();
+ }
+
+ // Call the loadState method
+ boolean result = PlayerSavesService.loadState();
+
+ // Assert that the method returns false
+ assertFalse(result, "loadState should return false when file does not exist");
+ }
+
+ @Test
+ void testLoadStateInvalidData() {
+ // Create a save file with invalid data
+ try (var writer = new java.io.BufferedWriter(new java.io.FileWriter(saveFile))) {
+ writer.write("Invalid Data");
+ writer.newLine();
+ } catch (Exception e) {
+ fail("Unexpected exception creating the save file: %s".formatted(e.getMessage()));
+ }
+
+ // Call the loadState method
+ boolean result = PlayerSavesService.loadState();
+
+ // Assert that the method returns false
+ assertFalse(result, "loadState should return false when file contains invalid data");
+ }
+
+ @Test
+ void testDeleteState() {
+ // Create a save file manually
+ try (var writer = new java.io.BufferedWriter(new java.io.FileWriter(saveFile))) {
+ writer.write("Username: TestUser, Money: $100");
+ writer.newLine();
+ } catch (Exception e) {
+ fail("Unexpected exception creating the save file: %s".formatted(e.getMessage()));
+ }
+
+ // Call the deleteState method
+ PlayerSavesService.deleteState();
+
+ // Assert that the file is deleted
+ assertFalse(saveFile.exists(), "Save file should be deleted");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/sdccd/cisc190/SetupViewTest.java b/src/test/java/edu/sdccd/cisc190/SetupViewTest.java
new file mode 100644
index 0000000..d74dbf8
--- /dev/null
+++ b/src/test/java/edu/sdccd/cisc190/SetupViewTest.java
@@ -0,0 +1,31 @@
+package edu.sdccd.cisc190;
+
+import edu.sdccd.cisc190.views.SetupView;
+import javafx.application.Platform;
+import javafx.stage.Stage;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SetupViewTest {
+
+ @Test
+ void testWindowTitleMatchesGame() throws Exception {
+ //initialize JavaFX runtime
+ Platform.startup(() -> {
+
+ });
+
+ Platform.runLater(() -> {
+ try {
+ SetupView setupView = new SetupView();
+ Stage testStage = new Stage();
+
+ setupView.start(testStage);
+ assertEquals("Casino Royale - Sign In", testStage.getTitle());
+ } finally {
+ Platform.exit();
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/sdccd/cisc190/SlotMachineManagerTest.java b/src/test/java/edu/sdccd/cisc190/SlotMachineManagerTest.java
new file mode 100644
index 0000000..8612de2
--- /dev/null
+++ b/src/test/java/edu/sdccd/cisc190/SlotMachineManagerTest.java
@@ -0,0 +1,59 @@
+package edu.sdccd.cisc190;
+
+import edu.sdccd.cisc190.services.SlotMachineManager;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SlotMachineManagerTest {
+ @BeforeEach
+ void setup() {
+ //manager starts with a clean slate before each test
+ SlotMachineManager.reset();
+ }
+
+ @AfterEach
+ void tearDown() {
+ //clean up after each test
+ SlotMachineManager.stopAllThreads();
+ }
+
+ @Test
+ void testGetStopRequested() {
+ assertFalse(SlotMachineManager.getStopRequested(), "stopRequested should initially be set to false");
+ SlotMachineManager.stopAllThreads();
+ assertTrue(SlotMachineManager.getStopRequested(), "stopRequested should be set to true after calling stopAllThreads()");
+ }
+
+ @Test
+ void testMain() {
+ //initialize main() method of SlotMachineManager
+ SlotMachineManager.main();
+
+ //verify that the bot threads and bot services are initialized
+ assertFalse(SlotMachineManager.botThreads.isEmpty(), "Bot threads should be initialized");
+ assertFalse(SlotMachineManager.botServices.isEmpty(), "Bot services should be initialized");
+
+ //ensure that the size of bot services and threads equals to number of bots
+ int numOfBots = 5;
+ assertEquals(numOfBots, SlotMachineManager.botServices.size(), "Number of services should equal number of bots");
+ }
+
+ @Test
+ void testStopAllThreads() throws InterruptedException {
+ //stop the threads and verify that the threads are not running
+ SlotMachineManager.stopAllThreads();
+
+ //ensure that stopRequested is set to true
+ assertTrue(SlotMachineManager.getStopRequested(), "stopRequested should be set to true");
+
+ for(Thread thread : SlotMachineManager.botThreads) {
+ thread.join(1000);
+ assertFalse(thread.isAlive(), "All threads are stopped.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/sdccd/cisc190/SlotMachineViewTest.java b/src/test/java/edu/sdccd/cisc190/SlotMachineViewTest.java
new file mode 100644
index 0000000..7f90466
--- /dev/null
+++ b/src/test/java/edu/sdccd/cisc190/SlotMachineViewTest.java
@@ -0,0 +1,40 @@
+package edu.sdccd.cisc190;
+
+import edu.sdccd.cisc190.views.MainMenuView;
+import edu.sdccd.cisc190.views.SlotMachineView;
+import javafx.application.Platform;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+import org.junit.jupiter.api.Test;
+import org.testfx.framework.junit5.ApplicationTest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SlotMachineViewTest extends ApplicationTest {
+
+ private HBox slotsRow;
+
+ @Override
+ public void start(Stage stage) {
+ //set up SlotMachineView
+ SlotMachineView.showWindow(stage, 10, MainMenuView.SlotOptions.DIAMOND_DASH);
+
+ //after window is displayed, access VBox and HBox
+ Platform.runLater(() -> {
+ VBox layout = (VBox) stage.getScene().getRoot();
+ slotsRow = (HBox) layout.getChildren().get(4);
+ });
+ }
+
+ @Test
+ public void testSlotMachinesPaneDisplayed() {
+
+ //make sure layout is loaded fully
+ Platform.runLater(() -> {
+ //verify that symbols are part of HBox
+ assert slotsRow != null;
+ assertEquals(3, slotsRow.getChildren().size(), "Slot machine display should contain 3 slots");
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/sdccd/cisc190/SlotTest.java b/src/test/java/edu/sdccd/cisc190/SlotTest.java
new file mode 100644
index 0000000..e457c0e
--- /dev/null
+++ b/src/test/java/edu/sdccd/cisc190/SlotTest.java
@@ -0,0 +1,194 @@
+package edu.sdccd.cisc190;
+
+import edu.sdccd.cisc190.machines.*;
+import edu.sdccd.cisc190.players.HumanPlayer;
+import edu.sdccd.cisc190.players.bots.Chase;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SlotTest {
+ private DiamondDash diamondDash;
+ private HondaTrunk hondaTrunk;
+ private MegaMoolah megaMoolah;
+ private RainbowRiches rainbowRiches;
+ private TreasureSpins treasureSpins;
+
+ private int bet;
+ private int initialMoney;
+
+ @BeforeEach
+ void setup() {
+ diamondDash = new DiamondDash();
+ hondaTrunk = new HondaTrunk();
+ megaMoolah = new MegaMoolah();
+ rainbowRiches = new RainbowRiches();
+ treasureSpins = new TreasureSpins();
+
+ bet = 50;
+ initialMoney = 100;
+ }
+
+ @Test
+ void testGenerateSpunSymbols() {
+ //Using diamond dash for this test
+ String[] spunSymbols = diamondDash.generateSpunSymbols();
+
+ //test if spunSymbols has three elements, and that those elements can be found in og diamond dash
+ assertEquals(3, spunSymbols.length, "The spun slot machine must have three elements.");
+
+ for (String symbol : spunSymbols) {
+ boolean isValid = Arrays.asList(diamondDash.getSymbols()).contains(symbol);
+ assertTrue(isValid, "Generated symbols should be predefined in original slot game");
+ }
+ }
+
+ @Test
+ void testEvaluateWinCondition_FullMatch() {
+ String[] fullMatchGame = {"🚗", "🚗", "🚗"};
+ int result = hondaTrunk.evaluateWinCondition(fullMatchGame);
+ assertEquals(3, result, "Full match should return 3.");
+ }
+
+ @Test
+ void testEvaluateWinCondition_PartialMatch() {
+ String[] partialMatchGame = {"🚗", "🚗", "🚕"};
+ int result = hondaTrunk.evaluateWinCondition(partialMatchGame);
+ assertEquals(2, result, "Partial match should return 2.");
+ }
+
+ @Test
+ void testEvaluateWinCondition_NoMatch() {
+ String[] noMatchGame = {"🚕", "🚗", "🛻"};
+ int result = hondaTrunk.evaluateWinCondition(noMatchGame);
+ assertEquals(0, result, "No match should return 0.");
+ }
+
+ @Test
+ void testCalculatePayout_FullMatch() {
+ //diamond dash
+ String[] fullMatchDD = {"💍", "💍", "💍"};
+ int newDDMoney = diamondDash.calculatePayout(initialMoney, fullMatchDD, bet);
+ assertEquals(200, newDDMoney);
+
+ //honda trunk
+ String[] fullMatchHT = {"🚗", "🚗", "🚗"};
+ int newHTMoney = hondaTrunk.calculatePayout(initialMoney, fullMatchHT, bet);
+ assertEquals(175, newHTMoney);
+
+ //mega moolah
+ String[] fullMatchMM = {"\uD83D\uDCB0", "\uD83D\uDCB0", "\uD83D\uDCB0"};
+ int newMMMoney = megaMoolah.calculatePayout(initialMoney, fullMatchMM, bet);
+ assertEquals(250, newMMMoney);
+
+ //rainbow riches
+ String[] fullMatchRR = {"\uD83C\uDF08", "\uD83C\uDF08", "\uD83C\uDF08"};
+ int newRRMoney = rainbowRiches.calculatePayout(initialMoney, fullMatchRR, bet);
+ assertEquals(350, newRRMoney);
+
+ //treasure spins
+ String[] fullMatchTS = {"\uD83C\uDF4A", "\uD83C\uDF4A", "\uD83C\uDF4A"};
+ int newTSMoney = treasureSpins.calculatePayout(initialMoney, fullMatchTS, bet);
+ assertEquals(600, newTSMoney);
+ }
+
+ @Test
+ void testCalculatePayout_PartialMatch() {
+ //diamond dash
+ String[] partialMatchDD = {"💍", "💍", "💠"};
+ int newDDMoney = diamondDash.calculatePayout(initialMoney, partialMatchDD, bet);
+ assertEquals(50, newDDMoney);
+
+ //honda trunk
+ String[] partialMatchHT = {"🚗", "🚗", "🚕"};
+ int newHTMoney = hondaTrunk.calculatePayout(initialMoney, partialMatchHT, bet);
+ assertEquals(118, newHTMoney);
+
+ //mega moolah
+ String[] partialMatchMM = {"\uD83D\uDCB0", "\uD83D\uDCB0", "\uD83E\uDD11"};
+ int newMMMoney = megaMoolah.calculatePayout(initialMoney, partialMatchMM, bet);
+ assertEquals(85, newMMMoney);
+
+ //rainbow riches
+ String[] partialMatchRR = {"\uD83C\uDF08", "\uD83C\uDF08","\uD83C\uDF24"};
+ int newRRMoney = rainbowRiches.calculatePayout(initialMoney, partialMatchRR, bet);
+ assertEquals(50, newRRMoney);
+
+ //treasure spins
+ String[] partialMatchTS = {"\uD83C\uDF4A", "\uD83C\uDF4C", "\uD83C\uDF4A"};
+ int newTSMoney = treasureSpins.calculatePayout(initialMoney, partialMatchTS, bet);
+ assertEquals(50, newTSMoney);
+ }
+
+ @Test
+ void testCalculatePayout_NoMatch() {
+ //diamond dash
+ String[] noMatchDD = diamondDash.getSymbols();
+ int newDDMoney = diamondDash.calculatePayout(initialMoney, noMatchDD, bet);
+ assertEquals(75, newDDMoney);
+
+ //honda trunk
+ String[] noMatchHT = hondaTrunk.getSymbols();
+ int newHTMoney = hondaTrunk.calculatePayout(initialMoney, noMatchHT, bet);
+ assertEquals(50, newHTMoney);
+
+ //mega moolah
+ String[] noMatchMM = megaMoolah.getSymbols();
+ int newMMMoney = megaMoolah.calculatePayout(initialMoney, noMatchMM, bet);
+ assertEquals(85, newMMMoney);
+
+ //rainbow riches
+ String[] noMatchRR = rainbowRiches.getSymbols();
+ int newRRMoney = rainbowRiches.calculatePayout(initialMoney, noMatchRR, bet);
+ assertEquals(50, newRRMoney);
+
+ //treasure spins
+ String[] noMatchTS = treasureSpins.getSymbols();
+ int newTSMoney = treasureSpins.calculatePayout(initialMoney, noMatchTS, bet);
+ assertEquals(50, newTSMoney);
+ }
+
+ @Test
+ void testCanBetWithVariousBets() {
+ //create a new humanPlayer and set its money to 1000
+ HumanPlayer humanPlayer = HumanPlayer.getInstance();
+ humanPlayer.setMoney(1000);
+
+ //Place a valid bet within range of machine max and min bet
+ int betWithinRange = 100;
+ assertTrue(diamondDash.canBet(betWithinRange), "A player should be able to bet $100 on Diamond Dash");
+
+ //Place an invalid bet less than minBet of slot machine
+ int betTooLow = 10;
+ assertFalse(diamondDash.canBet(betTooLow), "A player cannot bet $10 on Diamond Dash! Bet is too low");
+
+ //Place an invalid bet greater than maxBet of slot machine
+ int betTooHigh = 2000;
+ assertFalse(diamondDash.canBet(betTooHigh), "A player cannot bet $2000 on Diamond Dash! Bet is too high");
+
+ //Place a valid bet exactly equal to minBet
+ int betMin = diamondDash.getMinBet();
+ assertTrue(diamondDash.canBet(betMin), "A player should be able to bet the min bet on Diamond Dash");
+
+ //Place a valid bet exactly equal to maxBet
+ int betMax = diamondDash.getMaxBet();
+ assertTrue(diamondDash.canBet(betMax), "A player should be able to bet the max bet on Diamond Dash");
+
+ //Player cannot bet more than what they have
+ int notEnoughMoney = 1200;
+ assertFalse(diamondDash.canBet(notEnoughMoney), "A player cannot bet more than what they have");
+ }
+
+ @Test
+ void testBotPlay() {
+ //instantiate a new bot
+ Chase chase = Chase.getInstance();
+
+ int moneyAfterSpin = diamondDash.botPlay(chase);
+
+ assertNotEquals(moneyAfterSpin, chase.getMoney(), "Chase's money should have changed after playing");
+ }
+}
\ No newline at end of file