A Lightweight Five-Card Poker Library Using JavaScript

One evening, I just couldn’t fall asleep. So I decided to implement a lightweight five-card poker library using JavaScript. My library has a Card class, a Hand class, and a SingleDeck class. The three main functions are: 1.) classify a hand (like “FullHouse”), 2.) compare two hands to determine which hand is better, 3.) deal a hand from a deck of 52 cards.

I didn’t implement my poker library starting from nothing — I refactored my existing C# poker library. The C# poker library took many hours to create, but my JavaScript version only took about four hours of work.

There are two ways to create a Card object:

  let c1 = Card.fromInts(14,3); // Ace of spades
  console.log(c1.toString());

  let c2 = Card.fromStr("Td");  // Ten of diamonds
  console.log(c2.toString());

The first pseudo-constructor accepts a rank and a suit as integers/numbers. The rank values are 2 = Two, 3 = Three, . . 10 = Ten, 11 = Jack, 12 = Queen, 13 = King, 14 = Ace. Rank values of 0 and 1 are not used. The suit values are 0 = clubs, 1 = diamonds, 2 = hearts, 3 = spades. The second pseudo-constructor accepts a string like “Td”. Because JavaScript doesn’t allow function/method overloading, to simulate overloading I defined two static methods.

There are three main ways to create a five-card Hand object:

  let h1 = Hand.fromStr("7cTsJc8d9hd");
  console.log(h1.toString());  // 7c8d9hTsJc
  
  let h2 = Hand.fromCards(Card.fromStr("6s"),
    Card.fromStr("Ah"), Card.fromStr("6h"),
    Card.fromStr("Ac"), Card.fromStr("6d"));
  console.log(h2.toString());  // 6d6h6sAcAh
  
  let lst = [];
  lst.push(Card.fromStr("5c")); lst.push(Card.fromStr("5d"));
  lst.push(Card.fromStr("9c")); lst.push(Card.fromStr("9d"));
  lst.push(Card.fromStr("Qh"));
  let h3 = Hand.fromList(lst);
  console.log(h3.toString());  // 5c5d9c9dQh

The first pseudo-constructor accepts an easy-to-interpret string such as “7cTsJc8d9h”. The second pseudo-constructor accepts five individual Card objects. The third pseudo-constructor accepts a List of five Card objects.

Hand objects are sorted from low card (“2c”) to high card (“As”). The sorting makes a hand easier to interpret, and much easier to classify and compare.

There are two methods to classify a Hand object. The getHandTypeStr() method returns one of ten strings: “HighCard”, “OnePair”, “TwoPair” , “ThreeKind” , “Straight”, “Flush” , “FullHouse”, “FourKind”, “StraightFlush”, “RoyalFlush”. The getHandTypeInt() method returns integer 0 (high card) through 9 (royal flush).

  console.log(h1.getHandTypeStr())  // Straight
  console.log(h1.getHandTypeInt().toString())  // 4

  console.log(h2.getHandTypeStr())  // FullHouse
  console.log(h2.getHandTypeInt().toString())  // 6

  console.log(h3.getHandTypeStr())  // TwoPair
  console.log(h3.getHandTypeInt().toString())  // 2

There is a static Hand.compare(h1, h2) function. It returns -1 if h1 is less than h2, returns +1 if h1 is greater than h2, returns 0 if h1 equals h2.

  let cmp1 = Hand.compare(h1, h2);  // -1: Straight  2P
  console.log("\nHand.compare(h2, h3) = ");
  console.log(cmp2.toString());

The SingleDeck class has a dealHand() method and a dealListCards() method. The dealHand() method returns a Hand object containing five Card objects. The dealListCards(n) method return a List/Array of n Card objects.

  d1 = new SingleDeck(1);
  d1.shuffle();
  d1.show();

  h4 = d1.dealHand();
  console.log(h4.toString());

  listCards = d1.dealListCards(38);
  console.log("Deck is now: ");  // 9 cards left
  d1.show();

To shuffle the deck, I implemented a poor man’s random number generator using the decimal part of the sin() function.

The JavaScript poker library can be used in several ways. You can compute the probabilities of different hands using a simulation. You can find the best five-card hand from seven cards. And so on. I’ll post some examples at some point if I run into another sleepless night.



I have loved cards and card games for as long as I can remember. Here are three examples I found on the Internet that I remember using when I was a young man in the 1960s. Left: The Lane company was a leading maker of plastic coated cards in the 1950s and 60s. The oriental theme seemed exotic and mysterious to me. I don’t think Lane is still around. Center: The KEM company was another leading maker of high-quality plastic coated cards. KEM is still in existence. I liked the geometry and colors of this set of two decks. Right: The Fournier company wasn’t as popular as Lane and KEM, but Fournier made some interesting and offbeat cards. Fournier is still in existence too. I think my family’s Fournier deck came from my grandfather on my mother’s side. Fournier is a Spanish company. My grandfather was French and always brought us interesting gifts from Europe.


Demo code. Replace “lt” (less-than), “gt”, “lte”, “gte”, “and” with Boolean operator symbols.

// poker.js
// ES6  node.js

// ----------------------------------------------------------

class Card
{
  constructor()
  {
    // returns dummy Card for fromInts(), fromStr()
    this.rank = -1;  // 2 = Two, . . 14 = Ace
    this.suit = -1;  // 0=clubs, diamonds, hearts, 3=spades
  }

  static fromInts(rnk, sut) {
    let result = new Card();
    result.rank = rnk;
    result.suit = sut;
    return result;
  }

  static fromStr(str) {
    let result = new Card();
    let rnk = str.charAt(0);
    let sut = str.charAt(1);

    if (rnk == 'A') result.rank = 14;
    else if (rnk == 'K') result.rank = 13;
    else if (rnk == 'Q') result.rank = 12;
    else if (rnk == 'J') result.rank = 11;
    else if (rnk == 'T') result.rank = 10;
    else result.rank = parseInt(rnk);
   
    if (sut == 'c') result.suit = 0;
    else if (sut == 'd') result.suit = 1;
    else if (sut == 'h') result.suit = 2;
    else if (sut == 's') result.suit = 3;

    return result;
  }

  toString() {
    let rnk = ""; let sut = "";
    if (this.rank == 10) rnk = "T";
    else if (this.rank == 11) rnk = "J";
    else if (this.rank == 12) rnk = "Q";
    else if (this.rank == 13) rnk = "K";
    else if (this.rank == 14) rnk = "A";
    else rnk = this.rank.toString();

    if (this.suit == 0) sut = "c";
    else if (this.suit == 1) sut = "d";
    else if (this.suit == 2) sut = "h";
    else if (this.suit == 3) sut = "s";

    return rnk + sut;
  }

} // class Card

// ----------------------------------------------------------

class Hand
{
  constructor()
  {
    this.cards = [];  // make dummy 2c, 3c, 4c, 5c, 6c
    for (let i = 0; i "lt" 5; ++i)
      this.cards[i] = Card.fromInts(i+2, 0);
  }

  static fromStr(str) {  // like "Js3h7d7cAd"
    let result = new Hand();  // dummy hand
    result.cards[0] = Card.fromStr(str.substring(0,2));
    result.cards[1] = Card.fromStr(str.substring(2,4));
    result.cards[2] = Card.fromStr(str.substring(4,6));
    result.cards[3] = Card.fromStr(str.substring(6,8));
    result.cards[4] = Card.fromStr(str.substring(8,10));

    // sort the Hand low to high by rank then by suit
    result.cards.sort((a,b) ="gt" a.rank - b.rank || 
      a.suit - b.suit);
    return result;
  }

  static fromCards(c0, c1, c2, c3, c4) {
    let result = new Hand();  // dummy hand
    result.cards[0] = c0;
    result.cards[1] = c1;
    result.cards[2] = c2;
    result.cards[3] = c3;
    result.cards[4] = c4;
    result.cards.sort((a,b) ="gt" a.rank - b.rank || 
      a.suit - b.suit);
    return result;
  }

  static fromList(lst) {
    let result = new Hand();  // dummy hand
    result.cards[0] = lst[0];
    result.cards[1] = lst[1];
    result.cards[2] = lst[2];
    result.cards[3] = lst[3];
    result.cards[4] = lst[4];
    result.cards.sort((a,b) ="gt" a.rank - b.rank || 
      a.suit - b.suit);
    return result;
  }

  toString() {
    let result = "";
    for (let i = 0; i "lt" 5; ++i)
      result += this.cards[i].toString();
    return result;
  }

  // Hand type functions
  // getHandTypeStr(), getHandTypeInt(),
  //
  // isRoyalFlush(), isStraightFlush(), 
  // isFourKind(), isFullHouse(), isFlush(),
  // isStraight(), isThreeKind(), isTwoPair(),
  // isOnePair(), isHighCard()
  //
  // helpers: hasFlush(), hasStraight()

  // --------------------------------------------------------

  getHandTypeStr() {
    if (Hand.isRoyalFlush(this) == true)
      return "RoyalFlush";
    else if (Hand.isStraightFlush(this) == true)
      return "StraightFlush";
    else if (Hand.isFourKind(this) == true)
      return "FourKind";
    else if (Hand.isFullHouse(this) == true)
      return "FullHouse";
    else if (Hand.isFlush(this) == true)
      return "Flush";
    else if (Hand.isStraight(this) == true)
      return "Straight";
    else if (Hand.isThreeKind(this) == true)
      return "ThreeKind";
    else if (Hand.isTwoPair(this) == true)
      return "TwoPair";
    else if (Hand.isOnePair(this) == true)
      return "OnePair";
    else if (Hand.isHighCard(this) == true)
      return "HighCard";
    else
      return "Unknown";
  }

  // --------------------------------------------------------

  getHandTypeInt() {
    if (Hand.isRoyalFlush(this) == true)
      return 9;
    else if (Hand.isStraightFlush(this) == true)
      return 8;
    else if (Hand.isFourKind(this) == true)
      return 7;
    else if (Hand.isFullHouse(this) == true)
      return 6;
    else if (Hand.isFlush(this) == true)
      return 5;
    else if (Hand.isStraight(this) == true)
      return 4;
    else if (Hand.isThreeKind(this) == true)
      return 3;
    else if (Hand.isTwoPair(this) == true)
      return 2;
    else if (Hand.isOnePair(this) == true)
      return 1;
    else if (Hand.isHighCard(this) == true)
      return 0;
    else
      return -1;
  }

  // --------------------------------------------------------

  static hasFlush(h) {
    if ((h.cards[0].suit == h.cards[1].suit) "and"
      (h.cards[1].suit == h.cards[2].suit) "and"
      (h.cards[2].suit == h.cards[3].suit) "and"
      (h.cards[3].suit == h.cards[4].suit))
    return true;

    return false;
  }

  // --------------------------------------------------------

  static hasStraight(h) {
    // check special case of Ace-low straight
    // 2, 3, 4, 5, A when sorted
    if (h.cards[0].rank == 2 "and"
      h.cards[1].rank == 3 "and"
      h.cards[2].rank == 4 "and"
      h.cards[3].rank == 5 "and"
      h.cards[4].rank == 14)
      return true;

    // otherwise, check for 5 consecutive
    if ((h.cards[0].rank == h.cards[1].rank - 1) "and"
      (h.cards[1].rank == h.cards[2].rank - 1) "and"
      (h.cards[2].rank == h.cards[3].rank - 1) "and"
      (h.cards[3].rank == h.cards[4].rank - 1))
      return true;

    return false;
  }

  // --------------------------------------------------------

  static isRoyalFlush(h) {
    if (Hand.hasStraight(h) == true "and" 
      Hand.hasFlush(h) == true "and"
      h.cards[0].rank == 10)
      return true;
    else
      return false;
  }

  // --------------------------------------------------------

  static isStraightFlush(h) {
    if (Hand.hasStraight(h) == true "and"
     Hand.hasFlush(h) == true "and"
     h.cards[0].rank != 10)
     return true;
    else
      return false;
  }

  // --------------------------------------------------------

  static isFourKind(h) {
    // AAAA B or B AAAA if sorted
    if ((h.cards[0].rank == h.cards[1].rank) "and"
      (h.cards[1].rank == h.cards[2].rank) "and"
      (h.cards[2].rank == h.cards[3].rank) "and"
      (h.cards[3].rank != h.cards[4].rank))
      return true;

    if ((h.cards[1].rank == h.cards[2].rank) "and"
      (h.cards[2].rank == h.cards[3].rank) "and"
      (h.cards[3].rank == h.cards[4].rank) "and"
      (h.cards[0].rank != h.cards[1].rank))
      return true;

    return false;
  }

  // --------------------------------------------------------

  static isFullHouse(h) {
    // AAA BB or BB AAA if sorted
    if ((h.cards[0].rank == h.cards[1].rank) "and"
      (h.cards[1].rank == h.cards[2].rank) "and"
      (h.cards[3].rank == h.cards[4].rank) "and"
      (h.cards[2].rank != h.cards[3].rank))
      return true;

    // BB AAA
    if ((h.cards[0].rank == h.cards[1].rank) "and"
      (h.cards[2].rank == h.cards[3].rank) "and"
      (h.cards[3].rank == h.cards[4].rank) "and"
      (h.cards[1].rank != h.cards[2].rank))
      return true;

    return false;
  }

  // --------------------------------------------------------

  static isFlush(h) {
    if (Hand.hasFlush(h) == true "and" 
      Hand.hasStraight(h) == false)
      return true; // no StraightFlush or RoyalFlush
    else
      return false;
  }

  // --------------------------------------------------------

  static isStraight(h) {
    if (Hand.hasStraight(h) == true "and" 
      Hand.hasFlush(h) == false) // no SF or RF
      return true;
    else
      return false;
  }

  // --------------------------------------------------------

  static isThreeKind(h) {
    // AAA B C or B AAA C or B C AAA if sorted
    if ((h.cards[0].rank == h.cards[1].rank) "and"
      (h.cards[1].rank == h.cards[2].rank) "and"
      (h.cards[2].rank != h.cards[3].rank) "and"
      (h.cards[3].rank != h.cards[4].rank))
      return true;

    if ((h.cards[1].rank == h.cards[2].rank) "and"
      (h.cards[2].rank == h.cards[3].rank) "and"
      (h.cards[0].rank != h.cards[1].rank) "and"
      (h.cards[3].rank != h.cards[4].rank))
      return true;

    if ((h.cards[2].rank == h.cards[3].rank) "and"
      (h.cards[3].rank == h.cards[4].rank) "and"
      (h.cards[0].rank != h.cards[1].rank) "and"
      (h.cards[1].rank != h.cards[2].rank))
      return true;

    return false;
  }

  // --------------------------------------------------------

  static isTwoPair(h) {
    // AA BB C or AA C BB or C AA BB if sorted
    if ((h.cards[0].rank == h.cards[1].rank) "and"
      (h.cards[2].rank == h.cards[3].rank) "and"
      (h.cards[1].rank != h.cards[2].rank) "and"
      (h.cards[3].rank != h.cards[4].rank))
      return true;  // AA BB C

    if ((h.cards[0].rank == h.cards[1].rank) "and"
      (h.cards[3].rank == h.cards[4].rank) "and"
      (h.cards[1].rank != h.cards[2].rank) "and"
      (h.cards[2].rank != h.cards[3].rank))
      return true;  // AA C BB

    if ((h.cards[1].rank == h.cards[2].rank) "and"
      (h.cards[3].rank == h.cards[4].rank) "and"
      (h.cards[0].rank != h.cards[1].rank) "and"
      (h.cards[2].rank != h.cards[3].rank))
      return true;  // C AA BB

    return false;
  }

  // --------------------------------------------------------

  static isOnePair(h) {
    // AA B C D or B AA C D or B C AA D or B C D AA
    if ((h.cards[0].rank == h.cards[1].rank) "and"
      (h.cards[1].rank != h.cards[2].rank) "and"
      (h.cards[2].rank != h.cards[3].rank) "and"
      (h.cards[3].rank != h.cards[4].rank))
      return true;  // AA B C D

    if ((h.cards[1].rank == h.cards[2].rank) "and"
      (h.cards[0].rank != h.cards[1].rank) "and"
      (h.cards[2].rank != h.cards[3].rank) "and"
      (h.cards[3].rank != h.cards[4].rank))
      return true;  // B AA C D

    if ((h.cards[2].rank == h.cards[3].rank) "and"
      (h.cards[0].rank != h.cards[1].rank) "and"
      (h.cards[1].rank != h.cards[2].rank) "and"
      (h.cards[3].rank != h.cards[4].rank))
      return true;  // B C AA D

    if ((h.cards[3].rank == h.cards[4].rank) "and"
      (h.cards[0].rank != h.cards[1].rank) "and"
      (h.cards[1].rank != h.cards[2].rank) "and"
      (h.cards[2].rank != h.cards[3].rank))
      return true;  // B C D AA

    return false;
  }

  // --------------------------------------------------------

  static isHighCard(h) {
    if (Hand.hasFlush(h) == true)
      return false;
    else if (Hand.hasStraight(h) == true)
      return false;
    else  {
      // all remaining have at least one pair
      if ((h.cards[0].rank == h.cards[1].rank) ||
        (h.cards[1].rank == h.cards[2].rank) ||
        (h.cards[2].rank == h.cards[3].rank) ||
        (h.cards[3].rank == h.cards[4].rank))
        return false;
    }

    return true;
  }

  // --------------------------------------------------------

  // Hand comparison methods
  // Hand.compare() calls:
  // breakTieStraightFlush(), breakTieFourKind(),
  // breakTieFullHouse(), breakTieFlush(),
  // breakTieStraight(), breakTieThreeKind(),
  // breakTieTwoPair(), breakTieOnePair(),
  // breakTieHighCard()

  // --------------------------------------------------------

  static compare(h1, h2) {
    // -1 if h1 "lt" h2, +1 if h1 "gt" h2, 0 if h1 == h2

    let h1Idx = h1.getHandTypeInt();  // like 6
    let h2Idx = h2.getHandTypeInt();

    // different hand types - easy
    if (h1Idx "lt" h2Idx)
      return -1;
    else if (h1Idx "gt" h2Idx)
      return +1;
    else // same hand types so break tie
    {
      let h1HandType = h1.getHandTypeStr();
      let h2HandType = h2.getHandTypeStr();

      if (h1HandType != h2HandType)
        console.log("Logic error in Hand.compare() ");

      if (h1HandType == "RoyalFlush")
        return 0; // two Royal Flush always tie
      else if (h1HandType == "StraightFlush")
        return Hand.breakTieStraightFlush(h1, h2);
      else if (h1HandType == "FourKind")
        return Hand.breakTieFourKind(h1, h2);
      else if (h1HandType == "FullHouse")
        return Hand.breakTieFullHouse(h1, h2);
      else if (h1HandType == "Flush")
        return Hand.breakTieFlush(h1, h2);
      else if (h1HandType == "Straight")
        return Hand.breakTieStraight(h1, h2);
      else if (h1HandType == "ThreeKind")
        return Hand.breakTieThreeKind(h1, h2);
      else if (h1HandType == "TwoPair")
        return Hand.breakTieTwoPair(h1, h2);
      else if (h1HandType == "OnePair")
        return Hand.breakTieOnePair(h1, h2);
      else if (h1HandType == "HighCard")
        return Hand.breakTieHighCard(h1, h2);
    }
    return -2;  // error
  }

  // --------------------------------------------------------

  static breakTieStraightFlush(h1, h2) {
    // check special case of Ace-low straight flush
    // check one or two Ace-low hands
    // h1 is Ace - low, h2 not Ace - low. h1 is less
    if ((h1.cards[0].rank == 2 "and"
      h1.cards[4].rank == 14) "and"  // because sorted!
      !(h2.cards[0].rank == 2 "and"
      h2.cards[4].rank == 14))
      return -1;
 
    //  h1 not Ace - low, h2 is Ace - low, h1 is better
    else if (!(h1.cards[0].rank == 2 "and"
      h1.cards[4].rank == 14) "and"
      (h2.cards[0].rank == 2 "and"
      h2.cards[4].rank == 14))
      return +1;
    //  two Ace-low hands
    else if ((h1.cards[0].rank == 2 "and"
      h1.cards[4].rank == 14) "and"  // Ace-low
      (h2.cards[0].rank == 2 "and"
      h2.cards[4].rank == 14))  // Ace-low
      return 0;

    //  no Ace-low straight flush so check high cards
    if (h1.cards[4].rank "lt" h2.cards[4].rank)
      return -1;
    else if (h1.cards[4].rank "gt" h2.cards[4].rank)
      return 1;
    else
      return 0;
  }

  // --------------------------------------------------------

  static breakTieFourKind(h1, h2) {
    // AAAA-B or B-AAAA
    // the off-card is at [0] or at [4] (hand is sorted)
    // find h1 four-card and off-card ranks
    let h1FourRank; let h1OffRank;
    if (h1.cards[0].rank == h1.cards[1].rank) {
      // 1st two cards same so off-rank at [4]
      h1FourRank = h1.cards[0].rank;
      h1OffRank = h1.cards[4].rank;
    }
    else {
      // 1st two cards diff so off-rank at [0]
      h1FourRank = h1.cards[4].rank;
      h1OffRank = h1.cards[0].rank;
    }

    let h2FourRank; let h2OffRank;
    if (h2.cards[0].rank == h2.cards[1].rank) {
      h2FourRank = h2.cards[0].rank;
      h2OffRank = h2.cards[4].rank;
    }
    else {
      h2FourRank = h2.cards[4].rank;
      h2OffRank = h2.cards[0].rank;
    }

    if (h1FourRank "lt" h2FourRank) // like 4K, 4A
      return -1;
    else if (h1FourRank "gt" h2FourRank)
      return +1;
    else { // both hands have same four-kind (mult. decks)
      if (h1OffRank "lt" h2OffRank)
        return -1;  // like 3c 9c9d9h9s "lt" Qd 9c9d9h9s
      else if (h1OffRank "gt" h2OffRank)
        return +1;  // like Jc 4c4d4h4s "gt" 9s 4c4d4h4s
      else if (h1OffRank == h2OffRank)
        return 0;
    }
    console.log("Fatal logic in breakTieFourKind");
  }

  // --------------------------------------------------------

  static breakTieFullHouse(h1, h2) {
    // determine high rank (3 kind) and low rank (2 kind)
    // AAA BB or AA BBB
    // if [1] == [2] 3 kind at [0][1][2]
    // if [1] != [2] 3 kind at [2][3][4]
    let h1ThreeRank; let h1TwoRank;
    if (h1.cards[1].rank == h1.cards[2].rank) {
      // if [1] == [2] 3 kind at [0][1][2]
      h1ThreeRank = h1.cards[0].rank;
      h1TwoRank = h1.cards[4].rank;
    }
    else  {
      // if [1] != [2] 3 kind at [2][3][4]
      h1ThreeRank = h1.cards[4].rank;
      h1TwoRank = h1.cards[0].rank;
    }

    let h2ThreeRank; let h2TwoRank;
    if (h2.cards[1].rank == h2.cards[2].rank) {
      // if [1] == [2] 3 kind at [0][1][2]
      h2ThreeRank = h2.cards[0].rank;
      h2TwoRank = h2.cards[4].rank;
    }
    else {
      // if [1] != [2] 3 kind at [2][3][4]
      h2ThreeRank = h2.cards[4].rank;
      h2TwoRank = h2.cards[0].rank;
    }

    if (h1ThreeRank "lt" h2ThreeRank)
      return -1;
    else if (h1ThreeRank "gt" h2ThreeRank)
      return +1;
    else { // both hands same three-kind (mult. decks)
      if (h1TwoRank "lt" h2TwoRank)
        return -1;  // like 3c3d 9c9d9h "lt" QdQs 9c9d9h
      else if (h1TwoRank "gt" h2TwoRank)
        return +1;  // like 3c3d 9c9d9h "gt" 2d2s 9c9d9h
      else if (h1TwoRank == h2TwoRank)
        return 0;
    }
    console.log("Fatal logic in breakTieFullHouse");
  }

  // --------------------------------------------------------

  static breakTieFlush(h1, h2) {
    // compare rank of high cards
    if (h1.cards[4].rank "lt" h2.cards[4].rank)
      return -1;
    else if (h1.cards[4].rank "gt" h2.cards[4].rank)
      return +1;
    // high cards equal so check at [3]
    else if (h1.cards[3].rank "lt" h2.cards[3].rank)
      return -1;
    else if (h1.cards[3].rank "gt" h2.cards[3].rank)
      return +1;
    // and so on
    else if (h1.cards[2].rank "lt" h2.cards[2].rank)
      return -1;
    else if (h1.cards[2].rank "gt" h2.cards[2].rank)
      return +1;
    //
    else if (h1.cards[1].rank "lt" h2.cards[1].rank)
      return -1;
    else if (h1.cards[1].rank "gt" h2.cards[1].rank)
      return +1;
    //
    else if (h1.cards[0].rank "lt" h2.cards[0].rank)
      return -1;
    else if (h1.cards[0].rank "gt" h2.cards[0].rank)
      return +1;
    //
    else
      return 0; // all ranks the same!
  }

  // --------------------------------------------------------

  static breakTieStraight(h1, h2) {
    // both hands are straights but one could be Ace-low
    // check special case of one or two Ace-low hands
    // h1 is Ace-low, h2 not Ace-low. h1 is less
    if ((h1.cards[0].rank == 2 "and"  // Ace-low (sorted!)
      h1.cards[4].rank == 14) "and"
      !(h2.cards[0].rank == 2 "and"
      h2.cards[4].rank == 14))
      return -1;
    // h1 not Ace-low, h2 is Ace-low, h1 is better
    else if (!(h1.cards[0].rank == 2 "and"
      h1.cards[4].rank == 14) "and"
      (h2.cards[0].rank == 2 "and"
      h2.cards[4].rank == 14))
      return +1;
    // two Ace-low hands
    else if ((h1.cards[0].rank == 2 "and"
      h1.cards[4].rank == 14) "and"
      (h2.cards[0].rank == 2 "and"
      h2.cards[4].rank == 14))
      return 0;

    // no Ace-low hands so just check high card
    if (h1.cards[4].rank "lt" h2.cards[4].rank)
      return -1;
    else if (h1.cards[4].rank "gt" h2.cards[4].rank)
      return +1;
    else if (h1.cards[4].rank == h2.cards[4].rank)
      return 0;
    else
      console.log("Fatal logic in breakTieStraight");
  }

  // --------------------------------------------------------

  static breakTieThreeKind(h1, h2) {
    // assumes multiple decks possible
    // (TTT L H) or (L TTT H) or (L H TTT)
    let h1ThreeRank = 0; let h1LowRank = 0;
    let h1HighRank = 0;
    if (h1.cards[0].rank == h1.cards[1].rank "and"
      h1.cards[1].rank == h1.cards[2].rank) {
      h1ThreeRank = h1.cards[0].rank;
      h1LowRank = h1.cards[3].rank;
      h1HighRank = h1.cards[4].rank;
    }
    else if (h1.cards[1].rank == h1.cards[2].rank "and"
      h1.cards[2].rank == h1.cards[3].rank) {
      h1LowRank = h1.cards[0].rank;
      h1ThreeRank = h1.cards[1].rank;
      h1HighRank = h1.cards[4].rank;
    }
    else if (h1.cards[2].rank == h1.cards[3].rank "and"
      h1.cards[3].rank == h1.cards[4].rank) {
      h1LowRank = h1.cards[0].rank;
      h1HighRank = h1.cards[1].rank;
      h1ThreeRank = h1.cards[4].rank;
    }

    let h2ThreeRank = 0; let h2LowRank = 0;
    let h2HighRank = 0;
    if (h2.cards[0].rank == h2.cards[1].rank "and"
      h2.cards[1].rank == h2.cards[2].rank) {
      h2ThreeRank = h2.cards[0].rank;
      h2LowRank = h2.cards[3].rank;
      h2HighRank = h2.cards[4].rank;
    }
    else if (h2.cards[1].rank == h2.cards[2].rank "and"
      h2.cards[2].rank == h2.cards[3].rank) {
      h2LowRank = h2.cards[0].rank;
      h2ThreeRank = h2.cards[1].rank;
      h2HighRank = h2.cards[4].rank;
    }
    else if (h2.cards[2].rank == h2.cards[3].rank "and"
      h2.cards[3].rank == h2.cards[4].rank) {
      h2LowRank = h2.cards[0].rank;
      h2HighRank = h2.cards[1].rank;
      h2ThreeRank = h2.cards[4].rank;
    }

    if (h1ThreeRank "lt" h2ThreeRank)
      return -1;
    else if (h1ThreeRank "gt" h2ThreeRank)
      return +1;
    // both hands three-kind same (mult. decks)
    else if (h1HighRank "lt" h2HighRank)
      return -1;
    else if (h1HighRank "gt" h2HighRank)
      return +1;
    //
    else if (h1LowRank "lt" h2LowRank)
      return -1;
    else if (h1LowRank "gt" h2LowRank)
      return +1;
    //
    else // wow!
      return 0;
  }

  // --------------------------------------------------------

  static breakTieTwoPair(h1, h2) {
    // (LL X HH) or (LL HH X) or (X LL HH)
    let h1LowRank = 0; let h1HighRank = 0;
    let h1OffRank = 0;
    if (h1.cards[0].rank == h1.cards[1].rank "and"
      h1.cards[3].rank == h1.cards[4].rank) {
      // (LL X HH)
      h1LowRank = h1.cards[0].rank;
      h1HighRank = h1.cards[4].rank;
      h1OffRank = h1.cards[2].rank;
    }
    else if (h1.cards[0].rank == h1.cards[1].rank "and"
      h1.cards[2].rank == h1.cards[3].rank) {
      // (LL HH X)
      h1LowRank = h1.cards[0].rank;
      h1HighRank = h1.cards[2].rank;
      h1OffRank = h1.cards[4].rank;
    }
    else if (h1.cards[1].rank == h1.cards[2].rank "and"
      h1.cards[3].rank == h1.cards[4].rank) {
      // (X LL HH)
      h1LowRank = h1.cards[1].rank;
      h1HighRank = h1.cards[3].rank;
      h1OffRank = h1.cards[0].rank;
    }

    let h2LowRank = 0; let h2HighRank = 0;
    let h2OffRank = 0;
    if (h2.cards[0].rank == h2.cards[1].rank "and"
      h2.cards[3].rank == h2.cards[4].rank) {
      // (LL X HH)
      h2LowRank = h2.cards[0].rank;
      h2HighRank = h2.cards[4].rank;
      h2OffRank = h2.cards[2].rank;
    }
    else if (h2.cards[0].rank == h2.cards[1].rank "and"
      h2.cards[2].rank == h2.cards[3].rank) {
      // (LL HH X)
      h2LowRank = h2.cards[0].rank;
      h2HighRank = h2.cards[2].rank;
      h2OffRank = h2.cards[4].rank;
    }
    else if (h2.cards[1].rank == h2.cards[2].rank "and"
      h2.cards[3].rank == h2.cards[4].rank) {
      // (X LL HH)
      h2LowRank = h2.cards[1].rank;
      h2HighRank = h2.cards[3].rank;
      h2OffRank = h2.cards[0].rank;
    }

    if (h1HighRank "lt" h2HighRank)
      return -1;
    else if (h1HighRank "gt" h2HighRank)
      return +1;
    else if (h1LowRank "lt" h2LowRank)
      return -1;
    else if (h1LowRank "gt" h2LowRank)
      return +1;
    else if (h1OffRank "lt" h2OffRank)
      return -1;
    else if (h1OffRank "gt" h2OffRank)
      return +1;
    else
      return 0;
  }

  // --------------------------------------------------------

  static breakTieOnePair(h1, h2) {
    // (PP L M H) or (L PP M H)
    // or (L M PP H) or (L M H PP)
    let h1PairRank = 0; let h1LowRank = 0;
    let h1MediumRank = 0; let h1HighRank = 0;
    if (h1.cards[0].rank == h1.cards[1].rank) {
      // (PP L M H)
      h1PairRank = h1.cards[0].rank;
      h1LowRank = h1.cards[2].rank;
      h1MediumRank = h1.cards[3].rank;
      h1HighRank = h1.cards[4].rank;
    }
    else if (h1.cards[1].rank == h1.cards[2].rank) {
      // (L PP M H)
      h1PairRank = h1.cards[1].rank;
      h1LowRank = h1.cards[0].rank;
      h1MediumRank = h1.cards[3].rank;
      h1HighRank = h1.cards[4].rank;
    }
    else if (h1.cards[2].rank == h1.cards[3].rank) {
      // (L M PP H)
      h1PairRank = h1.cards[2].rank;
      h1LowRank = h1.cards[0].rank;
      h1MediumRank = h1.cards[1].rank;
      h1HighRank = h1.cards[4].rank;
    }
    else if (h1.cards[3].rank == h1.cards[4].rank) {
      // (L M H PP)
      h1PairRank = h1.cards[4].rank;
      h1LowRank = h1.cards[0].rank;
      h1MediumRank = h1.cards[1].rank;
      h1HighRank = h1.cards[2].rank;
    }

    let h2PairRank = 0; let h2LowRank = 0;
    let h2MediumRank = 0; let h2HighRank = 0;
    if (h2.cards[0].rank == h2.cards[1].rank) {
      // (PP L M H)
      h2PairRank = h2.cards[0].rank;
      h2LowRank = h2.cards[2].rank;
      h2MediumRank = h2.cards[3].rank;
      h2HighRank = h2.cards[4].rank;
    }
    else if (h2.cards[1].rank == h2.cards[2].rank) {
      // (L PP M H)
      h2PairRank = h2.cards[1].rank;
      h2LowRank = h2.cards[0].rank;
      h2MediumRank = h2.cards[3].rank;
      h2HighRank = h2.cards[4].rank;
    }
    else if (h2.cards[2].rank == h2.cards[3].rank) {
      // (L M PP H)
      h2PairRank = h2.cards[2].rank;
      h2LowRank = h2.cards[0].rank;
      h2MediumRank = h2.cards[1].rank;
      h2HighRank = h2.cards[4].rank;
    }
    else if (h2.cards[3].rank == h2.cards[4].rank) {
      // (L M H PP)
      h2PairRank = h2.cards[4].rank;
      h2LowRank = h2.cards[0].rank;
      h2MediumRank = h2.cards[1].rank;
      h2HighRank = h2.cards[2].rank;
    }

    if (h1PairRank "lt" h2PairRank)
      return -1;
    else if (h1PairRank "gt" h2PairRank)
      return +1;
    //
    else if (h1HighRank "lt" h2HighRank)
      return -1;
    else if (h1HighRank "gt" h2HighRank)
      return +1;
    //
    else if (h1MediumRank "lt" h2MediumRank)
      return -1;
    else if (h1MediumRank "gt" h2MediumRank)
      return +1;
    //
    else if (h1LowRank "lt" h2LowRank)
      return -1;
    else if (h1LowRank "gt" h2LowRank)
      return +1;
    //
    else
      return 0;
  }

  // --------------------------------------------------------

  static breakTieHighCard(h1, h2) {
    if (h1.cards[4].rank "lt" h2.cards[4].rank)
      return -1;
    else if (h1.cards[4].rank "gt" h2.cards[4].rank)
      return +1;
    //
    else if (h1.cards[3].rank "lt" h2.cards[3].rank)
      return -1;
    else if (h1.cards[3].rank "gt" h2.cards[3].rank)
      return +1;
    //
    else if (h1.cards[2].rank "lt" h2.cards[2].rank)
      return -1;
    else if (h1.cards[2].rank "gt" h2.cards[2].rank)
      return +1;
    //
    else if (h1.cards[1].rank "lt" h2.cards[1].rank)
      return -1;
    else if (h1.cards[1].rank "gt" h2.cards[1].rank)
      return +1;
    //
    else if (h1.cards[0].rank "lt" h2.cards[0].rank)
      return -1;
    else if (h1.cards[0].rank "gt" h2.cards[0].rank)
      return +1;
    //
    else
      return 0;
  }

  // --------------------------------------------------------

} // class Hand

// ----------------------------------------------------------

class SingleDeck
{
  constructor(seed)
  {
    this.deck = [];
    this.seed = seed + 0.5;  // avoid 0
    this.currCardIdx = 0;
    for (let rnk = 2; rnk "lt" 15; ++rnk) {
      for (let sut = 0; sut "lt" 4; ++sut) {
        let c = Card.fromInts(rnk, sut);
        this.deck.push(c);
      }
    }
  }

  shuffle() {
    for (let i = 0; i "lt" 52; ++i) {
      let rix = this.nextInt(i, 52);
      let tmp = this.deck[i];  // Card object
      this.deck[i] = this.deck[rix];
      this.deck[rix] = tmp;
    }
    this.currCardIdx = 0;
  }

  nextInt(lo, hi) {  // poor man's Random
    let x = Math.sin(this.seed) * 1000;
    let z = x - Math.floor(x);  // [0.0,1.0)
    this.seed = z;  // for next call
    return Math.trunc((hi - lo) * z + lo);
  }

  dealHand() {
    // TODO: check if at least 5 cards left in deck
    let lst = [];
    for (let i = 0; i "lt" 5; ++i) {
      let c = this.deck[this.currCardIdx++];
      lst.push(c);
    }
    let h = Hand.fromList(lst);
    return h;
  }

  dealListCards(n) {
   // TODO: check if at least n cards left in deck
    let lst = [];
    for (let i = 0; i "lt" n; ++i) {
      let c = this.deck[this.currCardIdx++];
      lst.push(c);
    }   
    return lst;
  }

  show() {
    let ct = 0;
    for (let i = this.currCardIdx; i "lt" 52; ++i) {
      if (ct "gt" 0 "and" ct % 10 == 0) console.log("");
      process.stdout.write(this.deck[i].toString() + " ");
      ++ct;
    }
    console.log("");
  }

} // class SingleDeck

// ----------------------------------------------------------

function main()
{
  console.log("\nBegin JavaScript poker lib demo ");

  // ----- Card ---------------------------------------------

  let c1 = Card.fromInts(14,3); // Ace of spades
  console.log("\nCard c1 = ");
  console.log(c1.toString());

  let c2 = Card.fromStr("Td");  // Ten of diamonds
  console.log("\nCard c2 = ");
  console.log(c2.toString());

  // ----- Hand ---------------------------------------------

  let h1 = Hand.fromStr("7cTsJc8d9hd");
  console.log("\nHand h1 = ");
  console.log(h1.toString());  // 7c8d9hTsJc
  console.log(h1.getHandTypeStr())  // Straight
  console.log(h1.getHandTypeInt().toString())  // 4

  let h2 = Hand.fromCards(Card.fromStr("6s"),
    Card.fromStr("Ah"), Card.fromStr("6h"),
    Card.fromStr("Ac"), Card.fromStr("6d"));
  console.log("\nHand h2 = ");
  console.log(h2.toString());  // 6d6h6sAcAh
  console.log(h2.getHandTypeStr())  // FullHouse
  console.log(h2.getHandTypeInt().toString())  // 6

  let lst = [];
  lst.push(Card.fromStr("5c")); lst.push(Card.fromStr("5d"));
  lst.push(Card.fromStr("9c")); lst.push(Card.fromStr("9d"));
  lst.push(Card.fromStr("Qh"));
  let h3 = Hand.fromList(lst);
  console.log("\nHand h3 = ");
  console.log(h3.toString());  // 5c5d9c9dQh
  console.log(h3.getHandTypeStr())  // TwoPair
  console.log(h3.getHandTypeInt().toString())  // 2

  // ----- Compare Hands

  let cmp1 = Hand.compare(h1, h2);  // -1: Straight "lt" FH
  console.log("\nHand.compare(h1, h2) = ");
  console.log(cmp1.toString());

  let cmp2 = Hand.compare(h2, h3);  // +1 FH "gt" 2P
  console.log("\nHand.compare(h2, h3) = ");
  console.log(cmp2.toString());

  // ----- Deck ---------------------------------------------

  console.log("\nCreating and shuffling deck ");
  d1 = new SingleDeck(1);
  d1.shuffle();
  d1.show();

  h4 = d1.dealHand();
  console.log("\nDealing Hand from deck: ");
  console.log(h4.toString());

  console.log("\nDealing 38 cards from deck");
  listCards = d1.dealListCards(38);
  console.log("Deck is now: ");
  d1.show();

  console.log("\nEnd demo ");
}

main()
This entry was posted in Poker. Bookmark the permalink.

Leave a Reply