Deployment Link - https://shaepy.github.io/pixeljack/
Pixeljack is a pixel-style Blackjack game where you try to beat the dealer by getting as close to 21 as possible without going over. Both you and the dealer start with two cards and take turns choosing to 'hit' (get another card) or 'stand' (keep your hand). Whoever gets closer to 21 without going over wins.
- HTML
- CSS
- JavaScript
As a user,
- I should be able to place a bet from my wallet to start the game.
- I can see what cards are dealt to me and only one card of the dealer.
- I can
hitto get another card to add to my hand. - I can
standto end my turn. - I should see the result of the round (win, lose, push).
- I can see my new wallet total after each result.
- I should have an option to play again.
- I want to reset my game status when I run out of currency.
- I should be able to see instructions on how to play the game.
- Created a Project Spec to map out MVP requirements for the game
- Game flowchart (image above) to understand order of operations and visualize flow in code
- Mapped out Data Structure needed for card deck
- Pseudo-code
- Translate into actionable tasks on Kanban Board
- Built out data structure for cards and player/dealer hands
cardDeckholds 52 card objects- I started with a single object for the table to hold both dealer and player cards. ie
let table = {dealer: [], player: []}, which is later changed to separate objects ofdealer = {cards: [], total: [], hitCardIdx: 0}for player and splitHand as well
- Scaffolding HTML for a dealer, player, and 2 buttons for
hitandstand - Using JavaScript to deal the cards to each hand, then display it
- Created
getCard()that grabs a random card from thecardDeckarray. It filters through only the cards wherehasBeenPlayed: falseto avoid dealing duplicates. dealCards()adds 2 cards to each hand (player and dealer)addCardTotal()calculates the total of each hand usingreducewith a check for whether an Ace can be changed if hand has busted- Display those cards using
displayCards() - Created a
checkForBlackjack()(ace and 10) which leads to an auto win unless Dealer has 21, which auto stands
- Created
- Attached event listeners and functions to
hitandstandbuttons- Created functions for
hitandstand(as they are reused later)hitdraws another card, callsaddCardTotaldisplayCards, andchecksForBuststandends turn, callsrevealHiddenCard,dealerHit, andcompareResult
- Created functions for
- Dealer's turn logic is to stand at 17 and above and always happens after the player turn
while (dealer.total <= 16) dealerHit()
- Win, lose, or tie logic implemented
compareResultwill check the hand totals between player versus dealer (see Refactoring Code #4) while adjusting the bet/wallet totals, andshowResultScreenwill display or remove the visual elements
- Built out a
resetGame()function that clears results and displays to prep the player to play again after a game ends- In the early builds, the
play againbutton led you back to the bet screen rather than loading up another game. You had to pass through this screen and press 'play' again. Future improvements madeplay againandchange betinto separate options for the user.
- In the early builds, the
- Bet mechanic was added last after the main gameplay was functional and complete.
- Started as a single bet option then added bet selectors to choose the amount
- Then evolved to more options using
data-bet = "10"for values since the bets are images
- Then evolved to more options using
- Added reset funds button that appears when player runs out of currency
- Started as a single bet option then added bet selectors to choose the amount
- Once the MVP requirements were functional and tested, I had a basic Blackjack skin for the game theme and used CSS to make it responsive for mobile and web (before deciding on a pixel theme).
- Refactored from using
.innerHTMLdeclarations ie.display.innerHTML = <img src="${player.cards[0]}">and instead nowcreateCardImg(card)takes a card object and will construct the entire element and I can just append it to the necessarydivelement. - Originally, the temporary user-facing messages ex. "Your Ace value has been changed", "Choose a bet amount to start a game", "Your wallet is too low", etc. were coded into the HTML and hidden. Since the messages all looked the same and appeared in similar spots, I refactored this to be two dynamic pieces so I could reuse it for any string that needed a temporary user-facing message.
handleFadeEffect(element)will take an element and apply or remove the opacity fading CSS classescreateTempMsg(string)will take a string and make apelement with that string, append it to the#temp-msg divthen calls thehandleFadeEffectto add the effect.
- Since the code uses a lot of setting
display: noneanddisplay: flexfor showing or hiding elements on the page, I use two functions that will take an array and set each element in the array to eitherdisplay: flexordisplay: noneturnDisplayToFlex(arr)andturnDisplayToNone(arr)- This is also the method used to setAttribute of
disabledor removeAttribute, for player action buttons. - Future improvement would be to change this to utility classes for displays and applying/removing a separate class but might need to review the current usage of IDs
- Updated
compareResult()andcompareSplitResult()to take all the result cases, including when a player busts. This way,checkForBust()only handles the checking of it, passing any visual elements and changing toisBust: true, and is directed towards thecomparefunctions.- Doing this keeps the functions separate. Previously, the
checkForBustfunction would branch a way to display a bust result for when a player or dealer busts. Now, all result outputs are in eithercompareResultorcompareSplitResult. The only exception is when getting a natural blackjack (a case that does not result in the normal flow of operations).
- Doing this keeps the functions separate. Previously, the
- Using a relative path versus direct path for images in JavaScript for local host versus remote live servers was causing issues. A solution found was to reference the direct path but use a ternary operator to pass in a
${srcUrl}of either the local host IP or/pixeljack. - The Split feature required a lot of areas to refactor since the feature was implemented post-MVP. This led to a lot of things breaking during the process. My takeaway is to keep track of unit testing and listing out existing technical areas the new feature will touch, rather than focusing only on the implementation logic.
- The ability for Aces to be flexible of either 1 or 11 value made Split hands difficult. It opened up a lot of edge cases since having a pair of aces can lead to the total of '22' unless accounting for switches to the value of 1. A large majority of time spent in implementing the Split feature was fixing cases and issues derived from this flexibility.
- Tracking my tasks in a Kanban board helped me in prioritizing features, bugs, and edge cases as they were discovered and built.
- This also helped when I needed to create the post-MVP features: Split and Double Down. I was able to use the Kanban Board tasks similar to JIRA style tickets for myself.
- On each 'ticket', I would do a mini-planning for each feature. This included user stories, psuedo-code, technical challenges to consider, etc. Even some scratch code to think things through.
- By building in a modular design and cleaning up redundant code as I went, building the Double Down feature took only a few hours due to the reusability of previous functions. I also found debugging to go quicker when I understood the flow of operations really well thanks to the ease of readability.
- Double Down is comprised of existing actions and behaviors:
createTempMsgcreates a temporary message that displays with a fade-in/fade-out effect to inform users when they do not have enough coinsdisplayFundswill show the updated wallet and bethitwillgetCardand call to display the card,addCardTotal, andcheckForBust- If player did not bust, call
stand - Return bet to normal
- Double Down is comprised of existing actions and behaviors:
- Card counting mode (currently, the deck resets every game)
- Adjust cards to be stacked in a peek on top of each other rather than displayed fully
- Potentially, allow player to forfeit game and exit to home screen before game has ended
Special thank you to,
- Card assets by Magory.itch.io
- Cursors by Kenney.nl
- Icons
- Audio
- BG Music