jsonMartin
5/23/2016 - 3:25 AM

Blurb Poker Solution (JavaScript ES6)

Blurb Poker Solution (JavaScript ES6)

"use strict";

class Card {
  constructor(cardCode) {
    const faceValues = {'J': 11, 'Q': 12, 'K': 13, 'A': 14}; // Face cards to numeric value dictionary

    //Set internal card variables containing individual card properties
    this.cardCode = cardCode; // Sets Card Code
    this.suit = cardCode.slice(-1); // Sets Suit
    this.face = cardCode.slice(0, cardCode.length - 1); // Sets Value
    this.value = this.face in faceValues ? faceValues[this.face] : parseInt(this.face); // Converts face card to numerical ranking

    //Check if Card has an invalid Suit or Face Value, if so raise error
    if (isNaN(this.value) || (['C', 'D', 'S', 'H'].indexOf(this.suit) === -1)) {
      throw new Error("Card data needs to be in proper format!");
    }
  }

  get value() {
    return this._value;
  }

  set value(val) {
    this._value = val;
  }

  get suit() {
    return this._suit;
  }

  set suit(suit) {
    this._suit = suit;
  }

  get face() {
    return this._face;
  }

  set face(face) {
    this._face = face;
  }

  get cardCode() {
    return this._cardCode;
  }

  set cardCode(cardCode) {
    this._cardCode = cardCode;
  }

  printCard() { // Prints card to console
    console.log("Suit:" + this.suit + " | Face:" + this.face + " | Value:" + this.value);
  }
}

class Hand {
  constructor(cardsStr) {
    this.cards = []; // Initializes empty hand
    this.cardsStr = cardsStr; // Stores card input string

    let parseCardsStr = () => cardsStr.split(' ').forEach((card) => this.cards.push(new Card(card)));

    parseCardsStr(); // Parses string into individual card objects and populates cards array
  }

  static handCheckMethods() { // Returns an object containing all hand configurations to tests
    return {
      highCard: function () { // Check for high card
        this.handStats.highCard = this.cards[this.cards.length - 1]; // Returns last(highest ranked) card
      },
      checkPairs: function () { // Check for pairs
        this.cards.forEach((card) => this.handStats.count[card.value] = (this.handStats.count[card.value] || 0) + 1);

        for (let key in this.handStats.count) { // Iterate through every card grouped in count
          if (this.handStats.count[key] === 2) { // Check for pair
            this.handStats.pairs.push(key);
          }
          else if (this.handStats.count[key] === 3) { //Check for 3 of a kind
            this.handStats.threeOfAKind.push(key);
          }
          else if (this.handStats.count[key] === 4) { //Check for 4 of a kind
            this.handStats.fourOfAKind.push(key);
          }
        }
      },
      checkStraight: function () {
        this.handStats.hasStraight = this.cards.every((card, i) => (i === this.cards.length - 1) || (card.value === this.cards[i + 1].value - 1));
      },
      checkFlush: function () {
        this.handStats.hasFlush = this.cards.every((card, i) => (i === this.cards.length - 1) || (card.suit === this.cards[i + 1].suit));
      }
    };
  }

  calculateHandStats() {
    //Initialize blank stats object
    this.handStats = {
      count: {}, // Counts number of occurrences of face values
      highCard: null, // Reference to the highCard
      pairs: [], // Pair info
      threeOfAKind: [], // Three of a Kind info
      fourOfAKind: [], // Four of a Kind info
      hasStraight: false, // Tracks whether hand is a straight
      hasFlush: false // Tracks whether hand is a flush
    };

    this.cards.sort((a, b) => a.value - b.value); // Sorts hand initially to easily find High Card and Straights

    let buildResultArr = () => { //Builds result array to return based on calculated hand stats
      let result = [];

      //Adds High Card
      result.unshift("High Card:" + this.handStats['highCard'].cardCode);

      //Check for Full House, Pairs, and Three/Four of a Kind
      if (this.handStats.threeOfAKind.length === 1 && this.handStats.pairs.length === 1) {
        result.unshift("Full House");
      }
      else {
        //Checks for Pairs
        if (this.handStats.pairs.length > 0) {
          result.unshift(this.handStats.pairs.length + " pair");
        }

        //Checks for Three of a Kind
        if (this.handStats.threeOfAKind.length > 0) {
          result.unshift("Three of a Kind");
        }

        //Checks for Four of a Kind
        if (this.handStats.fourOfAKind.length > 0) {
          result.unshift("Four of a Kind");
        }
      }

      //Check for Royal Flush, Flush, and Straights
      if (this.handStats.hasFlush || this.handStats.hasStraight) {
        let straightFlushArray = [];

        if (this.handStats.hasFlush && this.handStats.hasStraight && this.handStats.highCard.value === 14) {
          straightFlushArray.push("Royal Flush");
        } else {
          if (this.handStats.hasStraight) {
            straightFlushArray.push("Straight")
          }
          if (this.handStats.hasFlush) {
            straightFlushArray.push("Flush");
          }
        }
        result.unshift(straightFlushArray.join(' ')); // Adds royal/flush/straight info to result array
      }
      return result;
    };

    //Runs a check for every possible hand configuration stored in handCalculations
    let handCheckMethods = Hand.handCheckMethods();
    for (let checkMethod in handCheckMethods) {
      handCheckMethods[checkMethod].call(this);
    }

    return buildResultArr();
  }

  get cards() {
    return this._cards;
  }

  set cards(cards) {
    this._cards = cards;
  }

  get handStats() {
    return this._handStats;
  }

  set handStats(handStats) {
    this._handStats = handStats;
  }

  get cardsStr() { // Returns input card string for display purposes
    return this._cardsStr;
  }

  set cardsStr(cardsStr) {
    this._cardsStr = cardsStr;
  }

  printCards() { // Prints hand to console
    this.cards.forEach((card) => card.printCard());
  }

  printStats() { // Prints out calculated hand stats
    console.log("Hand Stats:", this.handStats);
  }
}

function hands(handStr) {
  let hand = new Hand(handStr);
  console.log("Running test for Hand [" + hand.cardsStr + "]");
  return hand.calculateHandStats();
}

function testHands(tests) {
  for (let method in tests) {
    let test = tests[method];
    console.log("---------------------------------------------");
    console.log("Test Name:", test.testName);
    let result = hands(test.handStr);
    console.log("Test Expected Result:", test.expectedResult);
    console.log("Test Actual Results:", result);
    let passed = (JSON.stringify(test.expectedResult) === JSON.stringify(result));
    console.log("Test Passed? " + passed);
    console.log("---------------------------------------------");
  }
}

let tests = {
  highCard: { //Test for High Card
    testName: "High Card",
    expectedResult: ['High Card:AS'],
    handStr: "AS 2D 4C 7D JD"
  },
  onePair: { //Test for 1 Pair
    testName: "1 Pair",
    expectedResult: ['1 pair', 'High Card:AS'],
    handStr: "AS 2D 4C JC JD"
  },
  twoPair: { //Test for 2 Pair
    testName: "2 Pair",
    expectedResult: ['2 pair', 'High Card:AS'],
    handStr: "AS 2D 2S JC JD"
  },
  threeOfAKind: { // Test for Three of a Kind
    testName: "Three of a Kind",
    expectedResult: ['Three of a Kind', 'High Card:AS'],
    handStr: "AS 2D JS JC JD"
  },
  fourOfAKind: { // Test for Four of a Kind
    testName: "Four of a Kind",
    expectedResult: ['Four of a Kind', 'High Card:AS'],
    handStr: "AS JH JS JC JD"
  },
  fullHouse: { // Test for Full house
    testName: "Full House",
    expectedResult: ['Full House', 'High Card:AH'],
    handStr: "AS AH JS JC JD"
  },
  straight: { // Test for Straight
    testName: "Straight",
    expectedResult: ['Straight', 'High Card:6D'],
    handStr: "2S 3H 4S 5C 6D"
  },
  flush: { // Test for Flush
    testName: "Flush",
    expectedResult: ['Flush', 'High Card:10H'],
    handStr: "10H 3H 4H 5H 6H"
  },
  straightFlush: { // Test for Straight Flush
    testName: "Straight Flush",
    expectedResult: ['Straight Flush', 'High Card:6H'],
    handStr: "2H 3H 4H 5H 6H"
  },
  royalFlush: { // Test for Royal Flush
    testName: "Royal Flush",
    expectedResult: ['Royal Flush', 'High Card:AH'],
    handStr: "AH KH QH 10H JH"
  }
};

testHands(tests);