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