diff --git a/10_of_clubs.svg b/10_of_clubs.svg
new file mode 100644
index 00000000..2e7788c1
--- /dev/null
+++ b/10_of_clubs.svg
@@ -0,0 +1,281 @@
+
+
+
+
+
diff --git a/10_of_diamonds.svg b/10_of_diamonds.svg
new file mode 100644
index 00000000..3d0b1c0d
--- /dev/null
+++ b/10_of_diamonds.svg
@@ -0,0 +1,401 @@
+
+
+
+
+
diff --git a/10_of_hearts.svg b/10_of_hearts.svg
new file mode 100644
index 00000000..c57575e2
--- /dev/null
+++ b/10_of_hearts.svg
@@ -0,0 +1,407 @@
+
+
+
+
+
diff --git a/10_of_spades.svg b/10_of_spades.svg
new file mode 100644
index 00000000..a14767c6
--- /dev/null
+++ b/10_of_spades.svg
@@ -0,0 +1,230 @@
+
+
+
+
+
diff --git a/2_of_clubs.svg b/2_of_clubs.svg
new file mode 100644
index 00000000..0334dece
--- /dev/null
+++ b/2_of_clubs.svg
@@ -0,0 +1,216 @@
+
+
+
+
+
diff --git a/2_of_diamonds.svg b/2_of_diamonds.svg
new file mode 100644
index 00000000..49562342
--- /dev/null
+++ b/2_of_diamonds.svg
@@ -0,0 +1,318 @@
+
+
+
+
+
diff --git a/2_of_hearts.svg b/2_of_hearts.svg
new file mode 100644
index 00000000..f6c95407
--- /dev/null
+++ b/2_of_hearts.svg
@@ -0,0 +1,308 @@
+
+
+
+
+
diff --git a/2_of_spades.svg b/2_of_spades.svg
new file mode 100644
index 00000000..759abf6b
--- /dev/null
+++ b/2_of_spades.svg
@@ -0,0 +1,147 @@
+
+
+
+
+
diff --git a/3_of_clubs.svg b/3_of_clubs.svg
new file mode 100644
index 00000000..0fb95b38
--- /dev/null
+++ b/3_of_clubs.svg
@@ -0,0 +1,224 @@
+
+
+
+
+
diff --git a/3_of_diamonds.svg b/3_of_diamonds.svg
new file mode 100644
index 00000000..a562f1f3
--- /dev/null
+++ b/3_of_diamonds.svg
@@ -0,0 +1,319 @@
+
+
+
+
+
diff --git a/3_of_hearts.svg b/3_of_hearts.svg
new file mode 100644
index 00000000..aecd1ff5
--- /dev/null
+++ b/3_of_hearts.svg
@@ -0,0 +1,318 @@
+
+
+
+
+
diff --git a/3_of_spades.svg b/3_of_spades.svg
new file mode 100644
index 00000000..89631953
--- /dev/null
+++ b/3_of_spades.svg
@@ -0,0 +1,154 @@
+
+
+
+
+
diff --git a/4_of_clubs.svg b/4_of_clubs.svg
new file mode 100644
index 00000000..70f89044
--- /dev/null
+++ b/4_of_clubs.svg
@@ -0,0 +1,230 @@
+
+
+
+
+
diff --git a/4_of_diamonds.svg b/4_of_diamonds.svg
new file mode 100644
index 00000000..86777378
--- /dev/null
+++ b/4_of_diamonds.svg
@@ -0,0 +1,324 @@
+
+
+
+
+
diff --git a/4_of_hearts.svg b/4_of_hearts.svg
new file mode 100644
index 00000000..9b7f0a74
--- /dev/null
+++ b/4_of_hearts.svg
@@ -0,0 +1,335 @@
+
+
+
+
+
diff --git a/4_of_spades.svg b/4_of_spades.svg
new file mode 100644
index 00000000..01e27e49
--- /dev/null
+++ b/4_of_spades.svg
@@ -0,0 +1,163 @@
+
+
+
+
+
diff --git a/5_of_clubs.svg b/5_of_clubs.svg
new file mode 100644
index 00000000..864d2f28
--- /dev/null
+++ b/5_of_clubs.svg
@@ -0,0 +1,238 @@
+
+
+
+
+
diff --git a/5_of_diamonds.svg b/5_of_diamonds.svg
new file mode 100644
index 00000000..a8cb2b50
--- /dev/null
+++ b/5_of_diamonds.svg
@@ -0,0 +1,333 @@
+
+
+
+
+
diff --git a/5_of_hearts.svg b/5_of_hearts.svg
new file mode 100644
index 00000000..af7415ab
--- /dev/null
+++ b/5_of_hearts.svg
@@ -0,0 +1,336 @@
+
+
+
+
+
diff --git a/5_of_spades.svg b/5_of_spades.svg
new file mode 100644
index 00000000..d9492ba4
--- /dev/null
+++ b/5_of_spades.svg
@@ -0,0 +1,170 @@
+
+
+
+
+
diff --git a/6_of_clubs.svg b/6_of_clubs.svg
new file mode 100644
index 00000000..306754ea
--- /dev/null
+++ b/6_of_clubs.svg
@@ -0,0 +1,244 @@
+
+
+
+
+
diff --git a/6_of_diamonds.svg b/6_of_diamonds.svg
new file mode 100644
index 00000000..6f12a53c
--- /dev/null
+++ b/6_of_diamonds.svg
@@ -0,0 +1,340 @@
+
+
+
+
+
diff --git a/6_of_hearts.svg b/6_of_hearts.svg
new file mode 100644
index 00000000..9c319ad1
--- /dev/null
+++ b/6_of_hearts.svg
@@ -0,0 +1,344 @@
+
+
+
+
+
diff --git a/6_of_spades.svg b/6_of_spades.svg
new file mode 100644
index 00000000..83e8feb0
--- /dev/null
+++ b/6_of_spades.svg
@@ -0,0 +1,177 @@
+
+
+
+
+
diff --git a/7_of_clubs.svg b/7_of_clubs.svg
new file mode 100644
index 00000000..11329b17
--- /dev/null
+++ b/7_of_clubs.svg
@@ -0,0 +1,252 @@
+
+
+
+
+
diff --git a/7_of_diamonds.svg b/7_of_diamonds.svg
new file mode 100644
index 00000000..d8483a72
--- /dev/null
+++ b/7_of_diamonds.svg
@@ -0,0 +1,349 @@
+
+
+
+
+
diff --git a/7_of_hearts.svg b/7_of_hearts.svg
new file mode 100644
index 00000000..8d5fd98a
--- /dev/null
+++ b/7_of_hearts.svg
@@ -0,0 +1,356 @@
+
+
+
+
+
diff --git a/7_of_spades.svg b/7_of_spades.svg
new file mode 100644
index 00000000..3146b440
--- /dev/null
+++ b/7_of_spades.svg
@@ -0,0 +1,186 @@
+
+
+
+
+
diff --git a/8_of_clubs.svg b/8_of_clubs.svg
new file mode 100644
index 00000000..28ae6fb6
--- /dev/null
+++ b/8_of_clubs.svg
@@ -0,0 +1,260 @@
+
+
+
+
+
diff --git a/8_of_diamonds.svg b/8_of_diamonds.svg
new file mode 100644
index 00000000..d6d1b0eb
--- /dev/null
+++ b/8_of_diamonds.svg
@@ -0,0 +1,358 @@
+
+
+
+
+
diff --git a/8_of_hearts.svg b/8_of_hearts.svg
new file mode 100644
index 00000000..ac8d5ba2
--- /dev/null
+++ b/8_of_hearts.svg
@@ -0,0 +1,364 @@
+
+
+
+
+
diff --git a/8_of_spades.svg b/8_of_spades.svg
new file mode 100644
index 00000000..0e5bd890
--- /dev/null
+++ b/8_of_spades.svg
@@ -0,0 +1,195 @@
+
+
+
+
+
diff --git a/9_of_clubs.svg b/9_of_clubs.svg
new file mode 100644
index 00000000..85cde356
--- /dev/null
+++ b/9_of_clubs.svg
@@ -0,0 +1,254 @@
+
+
+
+
+
diff --git a/9_of_diamonds.svg b/9_of_diamonds.svg
new file mode 100644
index 00000000..05292f2e
--- /dev/null
+++ b/9_of_diamonds.svg
@@ -0,0 +1,367 @@
+
+
+
+
+
diff --git a/9_of_hearts.svg b/9_of_hearts.svg
new file mode 100644
index 00000000..0f6a2115
--- /dev/null
+++ b/9_of_hearts.svg
@@ -0,0 +1,378 @@
+
+
+
+
+
diff --git a/9_of_spades.svg b/9_of_spades.svg
new file mode 100644
index 00000000..2359db59
--- /dev/null
+++ b/9_of_spades.svg
@@ -0,0 +1,198 @@
+
+
+
+
+
diff --git a/A_of_clubs.svg b/A_of_clubs.svg
new file mode 100644
index 00000000..be74fe02
--- /dev/null
+++ b/A_of_clubs.svg
@@ -0,0 +1,258 @@
+
+
+
+
+
diff --git a/A_of_diamonds.svg b/A_of_diamonds.svg
new file mode 100644
index 00000000..e5636a4a
--- /dev/null
+++ b/A_of_diamonds.svg
@@ -0,0 +1,311 @@
+
+
+
+
+
diff --git a/A_of_hearts.svg b/A_of_hearts.svg
new file mode 100644
index 00000000..24702e27
--- /dev/null
+++ b/A_of_hearts.svg
@@ -0,0 +1,324 @@
+
+
+
+
+
diff --git a/A_of_spades.svg b/A_of_spades.svg
new file mode 100644
index 00000000..f95b248d
--- /dev/null
+++ b/A_of_spades.svg
@@ -0,0 +1,279 @@
+
+
+
+
+
diff --git a/Card-flip-sound-effect.mp3 b/Card-flip-sound-effect.mp3
new file mode 100644
index 00000000..484c5b5d
Binary files /dev/null and b/Card-flip-sound-effect.mp3 differ
diff --git a/Card_back_05a.svg b/Card_back_05a.svg
new file mode 100644
index 00000000..f5d3cfc5
--- /dev/null
+++ b/Card_back_05a.svg
@@ -0,0 +1,663 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Insert-coin-sound.mp3 b/Insert-coin-sound.mp3
new file mode 100644
index 00000000..a3519639
Binary files /dev/null and b/Insert-coin-sound.mp3 differ
diff --git a/J_of_clubs.svg b/J_of_clubs.svg
new file mode 100644
index 00000000..a3503059
--- /dev/null
+++ b/J_of_clubs.svg
@@ -0,0 +1,224 @@
+
+
+
+
+
diff --git a/J_of_diamonds.svg b/J_of_diamonds.svg
new file mode 100644
index 00000000..d5315413
--- /dev/null
+++ b/J_of_diamonds.svg
@@ -0,0 +1,338 @@
+
+
+
+
+
diff --git a/J_of_hearts.svg b/J_of_hearts.svg
new file mode 100644
index 00000000..dc95ce1d
--- /dev/null
+++ b/J_of_hearts.svg
@@ -0,0 +1,330 @@
+
+
+
+
+
diff --git a/J_of_spades.svg b/J_of_spades.svg
new file mode 100644
index 00000000..281113fb
--- /dev/null
+++ b/J_of_spades.svg
@@ -0,0 +1,336 @@
+
+
+
+
+
diff --git a/K_of_clubs.svg b/K_of_clubs.svg
new file mode 100644
index 00000000..fe6e6769
--- /dev/null
+++ b/K_of_clubs.svg
@@ -0,0 +1,254 @@
+
+
+
+
+
diff --git a/K_of_diamonds.svg b/K_of_diamonds.svg
new file mode 100644
index 00000000..f983c6a3
--- /dev/null
+++ b/K_of_diamonds.svg
@@ -0,0 +1,351 @@
+
+
+
+
+
diff --git a/K_of_hearts.svg b/K_of_hearts.svg
new file mode 100644
index 00000000..7250745d
--- /dev/null
+++ b/K_of_hearts.svg
@@ -0,0 +1,337 @@
+
+
+
+
+
diff --git a/K_of_spades.svg b/K_of_spades.svg
new file mode 100644
index 00000000..ac4338d9
--- /dev/null
+++ b/K_of_spades.svg
@@ -0,0 +1,329 @@
+
+
+
+
+
diff --git a/MODE7GX3.TTF b/MODE7GX3.TTF
new file mode 100644
index 00000000..c9678aed
Binary files /dev/null and b/MODE7GX3.TTF differ
diff --git a/Q_of_clubs.svg b/Q_of_clubs.svg
new file mode 100644
index 00000000..6a636f74
--- /dev/null
+++ b/Q_of_clubs.svg
@@ -0,0 +1,250 @@
+
+
+
+
+
diff --git a/Q_of_diamonds.svg b/Q_of_diamonds.svg
new file mode 100644
index 00000000..76b62db0
--- /dev/null
+++ b/Q_of_diamonds.svg
@@ -0,0 +1,339 @@
+
+
+
+
+
diff --git a/Q_of_hearts.svg b/Q_of_hearts.svg
new file mode 100644
index 00000000..90ede784
--- /dev/null
+++ b/Q_of_hearts.svg
@@ -0,0 +1,331 @@
+
+
+
+
+
diff --git a/Q_of_spades.svg b/Q_of_spades.svg
new file mode 100644
index 00000000..8dc5540b
--- /dev/null
+++ b/Q_of_spades.svg
@@ -0,0 +1,324 @@
+
+
+
+
+
diff --git a/README.md b/README.md
index 1d3a8a65..03f7ed25 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,41 @@
# Rocket Academy Coding Bootcamp: Video Poker
+
+Technical Review
+"Technical" refers to software logic and syntax.
+What went well? Please share a link to the specific code.
+
+- Animations, sounds, click events working ok
+- calcHandScore and probability logic
+
+What were the biggest challenges you faced? Please share a link to the specific code.
+
+- Scoring table was hard coded with HTML. Probably could have used CSS Grid and some javascript to propagate the score patterns
+- Making the layout responsive. The large score table looks to be making the layout off center in mobile view
+- App hangs when it runs loop with too many iterations. Unexpected behaviour when user clicks around while loop is running. There will be some "click event backlog"
+
+What would you do differently next time?
+
+- Separate UI code and game logic code into different files
+- Use CSS grid or flexbox
+- Optimise the probability calculation algo
+
+Process Review
+"Process" refers to app development steps and strategy.
+What went well?
+
+- The advice to use test hands to make calcHandScore working first. Helps to focus on cleaning up possible edge cases for calcHandScore
+- Build the probability logic in chunks. Have clear pseudo code first
+- Container divs for each section was done up first and outputting correctly. Design was done later.
+
+What could have been better?
+
+- Not sure if UI layout and colour combinations are the best
+
+What would you do differently next time?
+
+- Learn about new features like "click to popup" to show table or prob table separately in mobile layout
+- or hamburger menu for user to toggle between features
+
+https://github.com/rocketacademy/video-poker-bootcamp/pull/44
+
+##End
diff --git a/Slot-machine-jackpot-sound-effect.mp3 b/Slot-machine-jackpot-sound-effect.mp3
new file mode 100644
index 00000000..d77b8d70
Binary files /dev/null and b/Slot-machine-jackpot-sound-effect.mp3 differ
diff --git a/Whomp-whomp.mp3 b/Whomp-whomp.mp3
new file mode 100644
index 00000000..a041313c
Binary files /dev/null and b/Whomp-whomp.mp3 differ
diff --git a/black_joker.svg b/black_joker.svg
new file mode 100644
index 00000000..f4ebef43
--- /dev/null
+++ b/black_joker.svg
@@ -0,0 +1,278 @@
+
+
+
+
+
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..ec9d4fba
--- /dev/null
+++ b/index.html
@@ -0,0 +1,98 @@
+
+
+
+
+ Video Poker
+
+
+
+
+
+
+
+
+
+ | PLAY 1 TO 5 COINS |
+
+
+
+
+ | ROYAL FLUSH |
+ 250 |
+ 500 |
+ 750 |
+ 1000 |
+ 4000 |
+
+
+ | STRAIGHT FLUSH |
+ 50 |
+ 100 |
+ 150 |
+ 200 |
+ 250 |
+
+
+ | 4 OF A KIND |
+ 25 |
+ 50 |
+ 75 |
+ 100 |
+ 125 |
+
+
+ | FULL HOUSE |
+ 9 |
+ 18 |
+ 27 |
+ 36 |
+ 45 |
+
+
+ | FLUSH |
+ 6 |
+ 12 |
+ 18 |
+ 24 |
+ 30 |
+
+
+ | STRAIGHT |
+ 4 |
+ 8 |
+ 12 |
+ 16 |
+ 20 |
+
+
+ | 3 OF A KIND |
+ 3 |
+ 6 |
+ 9 |
+ 12 |
+ 15 |
+
+
+ | 2 PAIRS |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+ 10 |
+
+
+ | PAIR OF JACKS (OR BETTER) |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+
+
+
+
+
+
+
+
+
diff --git a/red_joker.svg b/red_joker.svg
new file mode 100644
index 00000000..f9492277
--- /dev/null
+++ b/red_joker.svg
@@ -0,0 +1,288 @@
+
+
+
+
+
diff --git a/script.js b/script.js
new file mode 100644
index 00000000..c2c4e6e2
--- /dev/null
+++ b/script.js
@@ -0,0 +1,678 @@
+// Get a random index ranging from 0 (inclusive) to max (exclusive).
+const getRandomIndex = (max) => Math.floor(Math.random() * max);
+
+// Shuffle an array of cards
+const shuffleCards = (cards) => {
+ // Loop over the card deck array once
+ for (let currentIndex = 0; currentIndex < cards.length; currentIndex += 1) {
+ // Select a random index in the deck
+ const randomIndex = getRandomIndex(cards.length);
+ // Select the card that corresponds to randomIndex
+ const randomCard = cards[randomIndex];
+ // Select the card that corresponds to currentIndex
+ const currentCard = cards[currentIndex];
+ // Swap positions of randomCard and currentCard in the deck
+ cards[currentIndex] = randomCard;
+ cards[randomIndex] = currentCard;
+ }
+ // Return the shuffled deck
+ return cards;
+};
+
+const makeDeck = () => {
+ // Initialise an empty deck array
+ const newDeck = [];
+ // Initialise an array of the 4 suits in our deck. We will loop over this array.
+ const suits = ["hearts", "diamonds", "clubs", "spades"];
+ const suitSymbols = ["♥", "♦", "♣", "♠"];
+ const suitColours = ["red", "red", "black", "black"];
+
+ // Loop over the suits array
+ for (let suitIndex = 0; suitIndex < suits.length; suitIndex += 1) {
+ // Store the current suit in a variable
+ const currentSuit = suits[suitIndex];
+ const cardSymbol = suitSymbols[suitIndex];
+ const cardColour = suitColours[suitIndex];
+
+ // Loop from 1 to 13 to create all cards for a given suit
+ // Notice rankCounter starts at 1 and not 0, and ends at 13 and not 12.
+ // This is an example of a loop without an array.
+ for (let rankCounter = 1; rankCounter <= 13; rankCounter += 1) {
+ // By default, the card name is the same as rankCounter
+ let cardName = `${rankCounter}`;
+
+ // If rank is 1, 11, 12, or 13, set cardName to the ace or face card's name
+ if (cardName === "1") {
+ cardName = "A";
+ } else if (cardName === "11") {
+ cardName = "J";
+ } else if (cardName === "12") {
+ cardName = "Q";
+ } else if (cardName === "13") {
+ cardName = "K";
+ }
+
+ // Create a new card with the current name, suit, and rank
+ const card = {
+ suitSymbol: cardSymbol,
+ name: cardName,
+ suit: currentSuit,
+ rank: rankCounter,
+ displayName: cardName,
+ colour: cardColour,
+ };
+
+ // Add the new card to the deck
+ newDeck.push(card);
+ }
+ }
+
+ // Return the completed card deck
+ return newDeck;
+};
+/**
+ * @param cardInfo {object} card object
+ * @param tracker {list} user's keep or replace decision
+ * @return {html element} image of the dealt card
+ */
+const createCard = (cardInfo, tracker) => {
+ let img = document.createElement("img");
+ img.className = "card";
+ if (tracker === REPLACE) {
+ cardAudio.play();
+ img.classList.add("card-animate");
+ }
+ img.src = `./${cardInfo.name}_of_${cardInfo.suit}.svg`;
+
+ return img;
+};
+//display a row of 5 card backs
+const showCardBacks = () => {
+ cardContainer.innerHTML = "";
+ const handElement = document.createElement("div");
+ for (let i = 0; i < 5; i += 1) {
+ let img = document.createElement("img");
+ img.className = "card";
+ img.classList.add("card-animate");
+ img.src = "./Card_back_05a.svg";
+ const cardElement = img;
+ handElement.appendChild(cardElement);
+ }
+ cardContainer.appendChild(handElement);
+};
+//push 5 random cards into hand, set decision tracker to all replace
+const dealFirstHands = () => {
+ hand = [];
+ handClickTracker = [REPLACE, REPLACE, REPLACE, REPLACE, REPLACE];
+ deck = shuffleCards(makeDeck());
+ for (let i = 0; i < 5; i += 1) {
+ let card = deck.pop();
+ hand.push(card);
+ }
+};
+
+const calcHandScore = (hand) => {
+ const isFlush = (cardSuitTally) => {
+ for (const suit in cardSuitTally) {
+ if (cardSuitTally[suit] === 5) {
+ return true;
+ }
+ }
+ };
+
+ const isStraight = (hand) => {
+ let rankList = [];
+ for (let i = 0; i < hand.length; i += 1) {
+ rankList.push(hand[i].rank);
+ }
+ rankList.sort(function (a, b) {
+ return a - b;
+ });
+ let isStraightCheck = 0;
+ for (let j = 1; j < rankList.length; j += 1) {
+ if (rankList[j] - j === rankList[0]) {
+ isStraightCheck += 1;
+ }
+ }
+ if (
+ rankList[0] === 1 &&
+ rankList[1] === 10 &&
+ rankList[2] === 11 &&
+ rankList[3] === 12 &&
+ rankList[4] === 13
+ ) {
+ return "BROADWAY";
+ }
+ if (isStraightCheck === 4) {
+ return "STRAIGHT";
+ }
+ };
+
+ const isFourOfKind = (cardNameTally) => {
+ for (const name in cardNameTally) {
+ if (cardNameTally[name] === 4) {
+ return true;
+ }
+ }
+ };
+
+ const isThreeOfKind = (cardNameTally) => {
+ for (const name in cardNameTally) {
+ if (cardNameTally[name] === 3) {
+ return true;
+ }
+ }
+ };
+
+ const numPairs = (cardNameTally) => {
+ let pairsCount = 0;
+ for (const name in cardNameTally) {
+ if (cardNameTally[name] === 2) {
+ pairsCount += 1;
+ }
+ }
+
+ if (pairsCount === 1) {
+ for (const name in cardNameTally) {
+ if (
+ cardNameTally[name] === 2 &&
+ !(name === "J" || name === "Q" || name === "K" || name === "A")
+ ) {
+ pairsCount = 0.5;
+ }
+ }
+ }
+ return pairsCount;
+ };
+
+ const cardNameTally = {};
+ for (let i = 0; i < hand.length; i += 1) {
+ var cardName = hand[i].name;
+ // If we have seen the card name before, increment its count
+ if (cardName in cardNameTally) {
+ cardNameTally[cardName] += 1;
+ }
+ // Else, initialise count of this card name to 1
+ else {
+ cardNameTally[cardName] = 1;
+ }
+ }
+
+ const cardSuitTally = {};
+ for (let i = 0; i < hand.length; i += 1) {
+ var cardSuit = hand[i].suit;
+ // If we have seen the card name before, increment its count
+ if (cardSuit in cardSuitTally) {
+ cardSuitTally[cardSuit] += 1;
+ }
+ // Else, initialise count of this card name to 1
+ else {
+ cardSuitTally[cardSuit] = 1;
+ }
+ }
+
+ if (isFlush(cardSuitTally) && isStraight(hand) === "BROADWAY") {
+ return "ROYAL FLUSH";
+ }
+
+ if (isFlush(cardSuitTally) && isStraight(hand)) {
+ return "STRAIGHT FLUSH";
+ }
+
+ if (isFourOfKind(cardNameTally)) {
+ return "4 OF A KIND";
+ }
+
+ if (
+ isThreeOfKind(cardNameTally) &&
+ (numPairs(cardNameTally) === 1 || numPairs(cardNameTally) === 0.5)
+ ) {
+ return "FULL HOUSE";
+ }
+
+ if (isFlush(cardSuitTally)) {
+ return "FLUSH";
+ }
+
+ if (isStraight(hand) === "BROADWAY" || isStraight(hand) === "STRAIGHT") {
+ return "STRAIGHT";
+ }
+
+ if (isThreeOfKind(cardNameTally)) {
+ return "3 OF A KIND";
+ }
+
+ if (numPairs(cardNameTally) === 2) {
+ return "2 PAIRS";
+ }
+
+ if (numPairs(cardNameTally) === 1) {
+ return "PAIR OF JACKS";
+ }
+
+ return "NIL";
+};
+//display user's keep or replace decision
+const printDecision = () => {
+ decisionContainer.innerHTML = "";
+ for (let i = 0; i < 5; i += 1) {
+ const decision = document.createElement("div");
+ decision.innerText = handClickTracker[i];
+ decision.className = "decision";
+ decisionContainer.appendChild(decision);
+ }
+};
+//display advice to user which to keep or replace
+const revealAdvice = (decisionTracker) => {
+ probContainer.innerHTML = "";
+ for (let i = 0; i < 5; i += 1) {
+ const decision = document.createElement("div");
+ decision.innerText = decisionTracker[i];
+ decision.className = "decision";
+ probContainer.appendChild(decision);
+ }
+};
+
+const printResultHand = () => {
+ //deal random cards into card positions where user chose to replace
+ for (let i = 0; i < 5; i += 1) {
+ if (handClickTracker[i] === REPLACE) {
+ let newCard = deck.pop();
+ hand[i] = newCard;
+ }
+ }
+ cardContainer.innerHTML = "";
+ const handElement = document.createElement("div");
+ //display the result dealt hand
+ for (let i = 0; i < 5; i += 1) {
+ const cardElement = createCard(hand[i], handClickTracker[i]);
+ handElement.appendChild(cardElement);
+ }
+ cardContainer.appendChild(handElement);
+};
+
+const findPayout = (result) => {
+ let payAmount = 0;
+ if (result === "ROYAL FLUSH") {
+ payAmount = 250;
+ if (betAmount === 5) {
+ payAmount = 800;
+ }
+ }
+
+ if (result === "STRAIGHT FLUSH") {
+ payAmount = 50;
+ }
+
+ if (result === "4 OF A KIND") {
+ payAmount = 25;
+ }
+ if (result === "FULL HOUSE") {
+ payAmount = 9;
+ }
+ if (result === "FLUSH") {
+ payAmount = 6;
+ }
+ if (result === "STRAIGHT") {
+ payAmount = 4;
+ }
+ if (result === "3 OF A KIND") {
+ payAmount = 3;
+ }
+ if (result === "2 PAIRS") {
+ payAmount = 2;
+ }
+ if (result === "PAIR OF JACKS") {
+ payAmount = 1;
+ }
+ payAmount *= betAmount;
+
+ return payAmount;
+};
+
+const givePayout = (payAmount) => {
+ credits += payAmount;
+};
+
+const cardClick = (cardElement, i) => {
+ if (handClickTracker[i] === REPLACE && gameMode != "end-round") {
+ cardElement.classList.add("selected-card");
+ handClickTracker[i] = KEEP;
+ printDecision();
+ return;
+ }
+
+ if (handClickTracker[i] === KEEP && gameMode != "end-round") {
+ cardElement.classList.remove("selected-card");
+ handClickTracker[i] = REPLACE;
+ printDecision();
+ return;
+ }
+};
+/**
+ * function that returns all possible ways to pick X elements from a list of size Y
+ * @param numToReplace {number} X no of indices I want to pick from a given list
+ * @param listLength {number} expected length Y of a list
+ * @return {list} each element is a list of possible chosen indices
+ */
+const indexPossibilities = (numToReplace, listLength) => {
+ let combis = [];
+ if (numToReplace === 1) {
+ for (let i = 0; i < listLength; i += 1) {
+ combis.push([i]);
+ }
+ }
+ if (numToReplace === 2) {
+ for (let i = 0; i < listLength; i += 1) {
+ for (let j = i + 1; j < listLength; j += 1) {
+ combis.push([i, j]);
+ }
+ }
+ }
+ if (numToReplace === 3) {
+ for (let i = 0; i < listLength; i += 1) {
+ for (let j = i + 1; j < listLength; j += 1) {
+ for (let k = j + 1; k < listLength; k += 1) {
+ combis.push([i, j, k]);
+ }
+ }
+ }
+ }
+ if (numToReplace === 4) {
+ for (let i = 0; i < listLength; i += 1) {
+ for (let j = i + 1; j < listLength; j += 1) {
+ for (let k = j + 1; k < listLength; k += 1) {
+ for (let l = k + 1; l < listLength; l += 1) {
+ combis.push([i, j, k, l]);
+ }
+ }
+ }
+ }
+ }
+ if (numToReplace === 5) {
+ for (let i = 0; i < listLength; i += 1) {
+ for (let j = i + 1; j < listLength; j += 1) {
+ for (let k = j + 1; k < listLength; k += 1) {
+ for (let l = k + 1; l < listLength; l += 1) {
+ for (let m = l + 1; m < listLength; m += 1) {
+ combis.push([i, j, k, l, m]);
+ }
+ }
+ }
+ }
+ }
+ }
+ return combis;
+};
+
+const findProbabilities = (decisionTracker) => {
+ let numToReplace = 0;
+ let indexToReplace = [];
+ for (let i = 0; i < 5; i += 1) {
+ if (decisionTracker[i] === REPLACE) {
+ indexToReplace.push(i);
+ numToReplace += 1;
+ }
+ }
+ let deckIndices = indexPossibilities(numToReplace, 47);
+ let allPossibleHands = [];
+ if (numToReplace === 0) {
+ allPossibleHands.push(hand);
+ }
+ for (let i = 0; i < deckIndices.length; i += 1) {
+ let possibleHand = [...hand];
+ for (let j = 0; j < indexToReplace.length; j += 1) {
+ possibleHand[indexToReplace[j]] = deck[deckIndices[i][j]];
+ }
+ allPossibleHands.push(possibleHand);
+ }
+ const resultsList = [
+ "ROYAL FLUSH",
+ "STRAIGHT FLUSH",
+ "4 OF A KIND",
+ "FULL HOUSE",
+ "FLUSH",
+ "STRAIGHT",
+ "3 OF A KIND",
+ "2 PAIRS",
+ "PAIR OF JACKS",
+ "NIL",
+ ];
+ let winningHandsTally = {};
+ winningHandsTally["TOTAL"] = allPossibleHands.length;
+
+ for (let i = 0; i < resultsList.length; i += 1) {
+ winningHandsTally[resultsList[i]] = 0;
+ }
+ for (let i = 0; i < allPossibleHands.length; i += 1) {
+ let result = calcHandScore(allPossibleHands[i]);
+ winningHandsTally[result] += 1;
+ }
+
+ return winningHandsTally;
+};
+
+const statsOutput = (probTally) => {
+ const total = probTally["TOTAL"];
+ let expectedPayout = 0;
+ let outputString = `TOTAL POSSIBLE HAND COMBIS: ${total}
`;
+ const resultsList = [
+ "ROYAL FLUSH",
+ "STRAIGHT FLUSH",
+ "4 OF A KIND",
+ "FULL HOUSE",
+ "FLUSH",
+ "STRAIGHT",
+ "3 OF A KIND",
+ "2 PAIRS",
+ "PAIR OF JACKS",
+ "NIL",
+ ];
+ for (let i = 0; i < resultsList.length; i += 1) {
+ let probPct = (100 * probTally[resultsList[i]]) / total;
+ expectedPayout += (probPct * findPayout(resultsList[i])) / 100;
+ outputString += `${resultsList[i]}: ${probPct.toFixed(5)}%
`;
+ }
+ outputString += `
EXPECTED PAYOUT: ${expectedPayout.toFixed(5)}`;
+
+ return [outputString, expectedPayout];
+};
+
+const giveSelectionAdvice = () => {
+ let expectedPayoutList = [];
+ const sampleSelection = [REPLACE, REPLACE, REPLACE, REPLACE, REPLACE];
+ let sampleSelectionList = [];
+ sampleSelectionList.push(sampleSelection);
+ for (let i = 1; i <= 5; i += 1) {
+ let indexToKeep = indexPossibilities(i, 5);
+
+ for (let j = 0; j < indexToKeep.length; j += 1) {
+ let possibleSelection = [...sampleSelection];
+ for (let k = 0; k < indexToKeep[j].length; k += 1) {
+ possibleSelection[indexToKeep[j][k]] = KEEP;
+ }
+ sampleSelectionList.push(possibleSelection);
+ }
+ }
+ for (let i = 0; i < sampleSelectionList.length; i += 1) {
+ expectedPayoutList.push({
+ choices: sampleSelectionList[i],
+ value: statsOutput(findProbabilities(sampleSelectionList[i]))[1],
+ });
+ }
+
+ expectedPayoutList.sort(function (a, b) {
+ return b.value - a.value;
+ });
+
+ return expectedPayoutList[0].choices;
+};
+
+//1. Game info message
+let gameInfo = document.createElement("div");
+gameInfo.innerText = "Place bet and click deal to start";
+gameInfo.className = "game-info";
+document.body.appendChild(gameInfo);
+
+//2. Card container
+let cardContainer = document.createElement("div");
+cardContainer.classList.add("card-container");
+cardContainer.classList.add("gradient-border");
+document.body.appendChild(cardContainer);
+
+//3. Tracks the keep or replace decisions
+let decisionContainer = document.createElement("div");
+decisionContainer.classList.add("decision-container");
+document.body.appendChild(decisionContainer);
+
+//4. Credits tracker container
+let creditsInfo = document.createElement("div");
+creditsInfo.innerText = "BET: 0 CREDITS: 100";
+creditsInfo.className = "credits-tracker";
+document.body.appendChild(creditsInfo);
+
+//5. Buttons
+let buttonsContainer = document.createElement("div");
+buttonsContainer.classList.add("buttons-container");
+let dealButton = document.createElement("button");
+dealButton.innerText = "DEAL";
+
+let betButton = document.createElement("button");
+betButton.innerText = `INSERT COIN`;
+
+let probButton = document.createElement("button");
+probButton.innerText = "CALC PROBS";
+probButton.classList.add("disabled-button");
+
+let adviceButton = document.createElement("button");
+adviceButton.innerText = "ADVISE ME";
+adviceButton.classList.add("disabled-button");
+
+buttonsContainer.appendChild(betButton);
+buttonsContainer.appendChild(dealButton);
+buttonsContainer.appendChild(probButton);
+buttonsContainer.appendChild(adviceButton);
+document.body.appendChild(buttonsContainer);
+
+let probContainer = document.createElement("div");
+probContainer.classList.add("game-info");
+document.body.appendChild(probContainer);
+
+//start of game logic
+const KEEP = "KEEP";
+const REPLACE = "REPLACE";
+let betAmount = 0;
+let credits = 100;
+let deck;
+let hand;
+let handClickTracker = [REPLACE, REPLACE, REPLACE, REPLACE, REPLACE];
+let gameMode = "first-draw"; // "calc-score" //"end-round"
+let adviceResult = 0;
+
+let coinAudio = new Audio("./Insert-coin-sound.mp3");
+let cardAudio = new Audio("./Card-flip-sound-effect.mp3");
+let winAudio = new Audio("./Slot-machine-jackpot-sound-effect.mp3");
+let loseAudio = new Audio("./Whomp-whomp.mp3");
+
+showCardBacks();
+betButton.addEventListener("click", () => {
+ if (betAmount < 5 && gameMode === "first-draw" && credits > 0) {
+ coinAudio.play();
+ betAmount += 1;
+ credits -= 1;
+ creditsInfo.innerText = `BET: ${betAmount} CREDITS: ${credits}`;
+ }
+
+ if (betAmount === 5 || credits === 0) {
+ betButton.classList.add("disabled-button");
+ }
+});
+
+probButton.addEventListener("click", () => {
+ if (gameMode === "calc-score") {
+ probContainer.innerHTML = `Calculating...`;
+
+ setTimeout(() => {
+ let outputList = statsOutput(findProbabilities(handClickTracker));
+ let outputString = outputList[0];
+ probContainer.innerHTML = outputString;
+ }, 1);
+ }
+});
+
+dealButton.addEventListener("click", () => {
+ if (gameMode === "first-draw" && betAmount > 0) {
+ betButton.classList.add("disabled-button");
+ probButton.classList.remove("disabled-button");
+ adviceButton.classList.remove("disabled-button");
+ dealFirstHands();
+ gameInfo.innerText = "Select cards you want to keep then deal again";
+ cardContainer.innerHTML = "";
+ const handElement = document.createElement("div");
+ for (let i = 0; i < 5; i += 1) {
+ const cardElement = createCard(hand[i], handClickTracker[i]);
+ cardElement.addEventListener("click", (event) => {
+ cardClick(event.currentTarget, i);
+ });
+ handElement.appendChild(cardElement);
+ }
+ cardContainer.appendChild(handElement);
+ gameMode = "calc-score";
+ return;
+ }
+ if (gameMode === "calc-score") {
+ decisionContainer.innerHTML = "";
+ probButton.classList.add("disabled-button");
+ adviceButton.classList.add("disabled-button");
+ dealButton.classList.add("disabled-button");
+ gameMode = "end-round";
+ //FOR TEST HANDS
+ // hand = testrf;
+ // handClickTracker = [KEEP, KEEP, KEEP, KEEP, KEEP];
+ printResultHand();
+ let handResult = calcHandScore(hand);
+ givePayout(findPayout(handResult));
+
+ setTimeout(() => {
+ gameInfo.innerText = handResult;
+ if (handResult === "NIL") {
+ loseAudio.play();
+ gameInfo.innerText = "Oops, no luck :(";
+ } else {
+ winAudio.play();
+ gameInfo.classList.add("blinking");
+ }
+ }, 1000);
+
+ betAmount = 0;
+
+ setTimeout(() => {
+ gameInfo.innerText = "Place bet and click deal to start";
+ creditsInfo.innerText = `BET: ${betAmount} CREDIT: ${credits}`;
+ probContainer.innerHTML = "";
+ showCardBacks();
+ gameMode = "first-draw";
+ betButton.classList.remove("disabled-button");
+ dealButton.classList.remove("disabled-button");
+ gameInfo.classList.remove("blinking");
+ adviceResult = 0;
+ }, 5000);
+
+ return;
+ }
+});
+
+adviceButton.addEventListener("click", () => {
+ if (gameMode === "calc-score") {
+ probContainer.innerHTML = `Thinking...`;
+
+ setTimeout(() => {
+ if (adviceResult === 0) {
+ adviceResult = giveSelectionAdvice();
+ revealAdvice(adviceResult);
+ } else {
+ revealAdvice(adviceResult);
+ }
+ }, 1);
+ }
+});
diff --git a/styles.css b/styles.css
new file mode 100644
index 00000000..4ad9376f
--- /dev/null
+++ b/styles.css
@@ -0,0 +1,158 @@
+@font-face {
+ font-family: Mode7;
+ src: url("MODE7GX3.TTF");
+}
+body {
+ background-color: #e5c484;
+}
+
+h1 {
+ text-align: center;
+ font-family: Mode7, monospace, courier, fixed;
+}
+
+.score-table {
+ background-color: #554435;
+ margin: 0 auto;
+ font-family: Mode7, monospace, courier, fixed;
+ border-radius: 8px;
+}
+
+thead {
+ background-color: #333;
+ color: #e5c484;
+}
+
+td {
+ padding-top: 1px;
+ padding-bottom: 1px;
+ padding-left: 30px;
+ padding-right: 40px;
+ color: yellow;
+ text-align: center;
+}
+
+.game-info {
+ background-color: none;
+ margin: 0px auto;
+ margin-top: 5px;
+ /* padding: 20px; */
+ max-width: 900px;
+ font-family: Mode7, monospace, courier, fixed;
+
+ text-align: center;
+ padding: 10px 50px;
+}
+.card-container {
+ margin: 0px auto;
+
+ max-width: 900px;
+ min-height: 250px;
+
+ /* width: 300px;
+ height: 2000px; */
+ text-align: center;
+
+ background-color: #a0be47;
+ border-radius: 8px;
+ border: solid 5px #554435;
+}
+
+.card {
+ margin: 20px 10px;
+ padding: 10px;
+ background-color: white;
+ width: 150px;
+ border-radius: 8px;
+ display: inline-block;
+ box-sizing: border-box;
+}
+
+.card-animate {
+ animation: cardflip 1s;
+}
+
+@keyframes cardflip {
+ 0% {
+ transform: rotateY(90deg);
+ }
+}
+
+.selected-card {
+ background-color: #e5c484;
+}
+
+.decision-container {
+ margin: 0 auto;
+ max-width: 900px;
+ min-height: 20px;
+ text-align: center;
+}
+
+.decision {
+ margin: 0px 10px 0px 10px;
+ padding: 0px 10px 0px 10px;
+ width: 150px;
+ text-align: center;
+ display: inline-block;
+ box-sizing: border-box;
+ font-family: Mode7, monospace, courier, fixed;
+}
+
+.credits-tracker {
+ background-color: none;
+ margin: 0px auto;
+ /* padding: 20px; */
+ max-width: 700px;
+ font-family: "Lucida Console", "Courier New", monospace;
+ text-align: center;
+ padding: 10px 50px;
+ font-family: Mode7, monospace, courier, fixed;
+ font-size: 20px;
+ color: maroon;
+ text-shadow: 2px 2px yellow;
+}
+
+.buttons-container {
+ max-width: 900;
+ text-align: center;
+}
+
+.buttons-container > button {
+ /* background-color: yellow; */
+ margin: 5px 5px;
+ padding: 10px 5px;
+ /* font-family: "Arial Black", sans-serif; */
+ font-family: Mode7, monospace, courier, fixed;
+ font-weight: bold;
+ color: #554435;
+ border: 2px solid #554435;
+ border-radius: 25px;
+}
+
+.disabled-button {
+ background-color: #b79e7d;
+}
+
+.blinking {
+ font-size: 20px;
+ text-shadow: 2px 2px yellow;
+ animation: blinkingText 0.5s infinite;
+}
+@keyframes blinkingText {
+ 0% {
+ color: red;
+ }
+ 49% {
+ color: red;
+ }
+ 60% {
+ color: transparent;
+ }
+ 99% {
+ color: transparent;
+ }
+ 100% {
+ color: red;
+ }
+}
diff --git a/testHands.js b/testHands.js
new file mode 100644
index 00000000..ebef621b
--- /dev/null
+++ b/testHands.js
@@ -0,0 +1,160 @@
+const makeDeckTest = () => {
+ // Initialise an empty deck array
+ const newDeck = [];
+ // Initialise an array of the 4 suits in our deck. We will loop over this array.
+ const suits = ["hearts", "diamonds", "clubs", "spades"];
+ const suitSymbols = ["♥", "♦", "♣", "♠"];
+ const suitColours = ["red", "red", "black", "black"];
+
+ // Loop over the suits array
+ for (let suitIndex = 0; suitIndex < suits.length; suitIndex += 1) {
+ // Store the current suit in a variable
+ const currentSuit = suits[suitIndex];
+ const cardSymbol = suitSymbols[suitIndex];
+ const cardColour = suitColours[suitIndex];
+
+ // Loop from 1 to 13 to create all cards for a given suit
+ // Notice rankCounter starts at 1 and not 0, and ends at 13 and not 12.
+ // This is an example of a loop without an array.
+ for (let rankCounter = 1; rankCounter <= 13; rankCounter += 1) {
+ // By default, the card name is the same as rankCounter
+ let cardName = `${rankCounter}`;
+
+ // If rank is 1, 11, 12, or 13, set cardName to the ace or face card's name
+ if (cardName === "1") {
+ cardName = "A";
+ } else if (cardName === "11") {
+ cardName = "J";
+ } else if (cardName === "12") {
+ cardName = "Q";
+ } else if (cardName === "13") {
+ cardName = "K";
+ }
+
+ // Create a new card with the current name, suit, and rank
+ const card = {
+ suitSymbol: cardSymbol,
+ name: cardName,
+ suit: currentSuit,
+ rank: rankCounter,
+ displayName: cardName,
+ colour: cardColour,
+ };
+
+ // Add the new card to the deck
+ newDeck.push(card);
+ }
+ }
+
+ // Return the completed card deck
+ return newDeck;
+};
+
+const testDeck = makeDeckTest();
+
+const isStraightTest = (hand) => {
+ let rankList = [];
+ for (let i = 0; i < hand.length; i += 1) {
+ rankList.push(hand[i].rank);
+ }
+ rankList.sort(function (a, b) {
+ return a - b;
+ });
+ let isStraightCheck = 0;
+ for (let j = 1; j < rankList.length; j += 1) {
+ if (rankList[j] - j === rankList[0]) {
+ isStraightCheck += 1;
+ }
+ }
+ if (isStraightCheck == 4) {
+ return true;
+ } else return false;
+};
+
+const testsf = [
+ testDeck[32],
+ testDeck[33],
+ testDeck[34],
+ testDeck[35],
+ testDeck[36],
+];
+
+const testrf = [
+ testDeck[9],
+ testDeck[11],
+ testDeck[12],
+ testDeck[10],
+ testDeck[0],
+];
+
+const testfourk = [
+ testDeck[14],
+ testDeck[4],
+ testDeck[17],
+ testDeck[30],
+ testDeck[43],
+];
+
+const testfhse = [
+ testDeck[5],
+ testDeck[18],
+ testDeck[31],
+ testDeck[12],
+ testDeck[25],
+];
+
+const testfhse2 = [
+ testDeck[6],
+ testDeck[18],
+ testDeck[31],
+ testDeck[7],
+ testDeck[21],
+];
+
+const testflush = [
+ testDeck[23],
+ testDeck[21],
+ testDeck[20],
+ testDeck[16],
+ testDeck[15],
+];
+
+const teststraight = [
+ testDeck[1],
+ testDeck[15],
+ testDeck[29],
+ testDeck[43],
+ testDeck[44],
+];
+
+const testthreek = [
+ testDeck[11],
+ testDeck[24],
+ testDeck[37],
+ testDeck[46],
+ testDeck[26],
+];
+
+const testHand_twopair = [
+ testDeck[9],
+ testDeck[22],
+ testDeck[27],
+ testDeck[40],
+ testDeck[4],
+];
+
+const testHand_onepair = [
+ testDeck[4],
+ testDeck[14],
+ testDeck[2],
+ testDeck[3],
+ testDeck[1],
+];
+
+const testHand_onepair_king = [
+ testDeck[4],
+ testDeck[0],
+ testDeck[2],
+ testDeck[3],
+ testDeck[13],
+];