Evaluating  

Evaluating a poker hand




The core of the game, and the most difficult logic, lies in the evaluation of the poker hands. I borrowed some of the logic from the C code posted on Paul Hsieh's Game Algorithms site, under A fast poker hand evaluation program. The elegant bit-logic code is his. The "brute force" code is mine. We have also added a new form and a function to display the results of the hand.

New functions are shown below in red. The three forms are shown in blue.

Click the "Deal" button to see the hand re-deal.
Click the "Hold" buttons to hold individual cards.
Click the "Draw" button to replace the unheld cards.
Click the "Play again" button to turn over the cards.

Card images from Oxymoron.
Hand evaluation logic from Game Algorithms.

Code in the page header

<script language="JavaScript" type="text/javascript">
<!--

// define the contructor for Card objects
function Card(s, r, c, ns, nr, cm ) {
  this.suit = s ;
  this.rank = r ;
  this.color = c ;
  this.numsuit = ns ;
  this.numrank = nr ;
  this.cardmask = cm ;
  this.held = false ;
}

// define a copy function for cards
function CopyCard(cardfrom, cardto) {
  var i ;
	
  for (i in cardfrom)
    cardto[i] = cardfrom[i] ;
}

// define a swap function for cards
function SwapCard(c1, c2) {
var c0 = new Card(null, null, null) ;

  CopyCard(c1, c0) ;
  CopyCard(c2, c1) ;
  CopyCard(c0, c2) ;
}

// define a shuffle method for the deck
function ShuffleCards() {
var i = 0 , r1 = 0 ;

  for (i = 0 ; i < this.length ; i++ ) {
    r1 = Math.floor(Math.random() * this.length) ;
    SwapCard(this[i], this[r1]) ;
  }
}

// extend the definition of arrays by adding a new method
Array.prototype.shuffle = ShuffleCards ;

// shuffle the deck and deal a hand
function ShuffleDeal() {
var i ;
var vImageSrc = null ;
var vCardImg = null ;
var vHold = null ;

  Deck.shuffle() ;
  for (i = 0 ; i < 5 ; i++ ) {
    CopyCard(Deck[i], Hand[i]) ;
    vImageSrc = "graphics/cards/" + Hand[i].rank + Hand[i].suit + ".gif" ;
    vCardImg = "CardImg" + i ;
    vHold = "Hold" + i ;
    document[vCardImg].src = vImageSrc ;
    Hand[i].held = false ;
    document.PForm1[vHold].value = "Hold"
  }		
  document.PForm2.btDeal.value = " ... " ;
  document.PForm2.btDraw.value = " Draw " ;
  document.PForm2.btCardReset.value = " ... " ;
}

// reset the card backs
function ResetCards() {
var i ;
var vImageSrc = "graphics/cards/b.gif" ;
var vCardImg = null ;

  for (i = 0 ; i < 5 ; i++ ) {
    vCardImg = "CardImg" + i ;
    vHold = "Hold" + i ;
    document[vCardImg].src = vImageSrc ;
    document.PForm1[vHold].value = " ... "
  }	
  document.PForm2.btDeal.value = " Deal " ;
  document.PForm2.btDraw.value = " ... " ;
  document.PForm2.btCardReset.value = " ... " ;
  document.PForm3.txtresult.value = "" ;
}

// hold or unhold a card
function HoldCard(pWhich) {
var vHold = "Hold" + pWhich ;
var vValue = null ;

  if (GameState != 1) return ;

  if (Hand[pWhich].held) {
    vValue = " Hold " ;
  }
  else {
    vValue = "[HELD]" ;	
  }
  Hand[pWhich].held = ! Hand[pWhich].held ;
  document.PForm1[vHold].value = vValue ;
}

// fill in unheld cards
function Redeal() {
var i ;
var j = 5 ;
var vImageSrc = null ;
var vHold = null ;
var vCardImg = null ;

  for (i = 0 ; i < 5 ; i++ ) {
    if (! Hand[i].held) {
      CopyCard(Deck[j], Hand[i]) ;
      vImageSrc = "graphics/cards/" + Hand[i].rank + Hand[i].suit + ".gif" ;
      vCardImg = "CardImg" + i ;
      document[vCardImg].src = vImageSrc ;
      j++ ;		
    }
    vHold = "Hold" + i ;
    document.PForm1[vHold].value = " ... " ;
    Hand[i].held = true ;
  }
  document.PForm2.btDeal.value = " ... " ;
  document.PForm2.btDraw.value = " ... " ;
  document.PForm2.btCardReset.value = " Play again " ;
}

// evaluate the hand
function ScoreHand() {
var i, j, u, c0, c1, c2, c3, c4, m1, m2, m3, m4 ;
var vFlush = false ;
var vMaxnum = 0 ;
var vDiffnum = 0 ;
var vJacksPlus = false ;

  // check for flushes by making suits powers of two
  u  = Hand[0].numsuit ; 
  u |= Hand[1].numsuit ; 
  u |= Hand[2].numsuit ; 
  u |= Hand[3].numsuit ; 
  u |= Hand[4].numsuit ; 
  u = u & (u-1) ;   // zero if it's a flush
	vFlush = (u == 0) ;
	
  // make cards powers of two
  c0 = Hand[0].cardmask ;
  c1 = Hand[1].cardmask ;
  c2 = Hand[2].cardmask ;
  c3 = Hand[3].cardmask ;
  c4 = Hand[4].cardmask ;

  // check for 1, 2, 3 and 4 of a kind using the card
  m1 = c0 | c1 ;
  m2 = c1 & c0 ;
  m2 |= c2 & m1 ;
  m1 |= c2 ;
  m2 |= c3 & m1 ;
  m1 |= c3 ;
  m2 |= c4 & m1 ;
  m1 |= c4 ;

  // m1 has bits for each rank represented
  // m2 has bits for each rank represented more than once
  
  // no pairs?
  if (m2 == 0) {

    // test for straights

    switch (m1) {
      case (31) :    // A-5
      case (62) :    // 2-6
      case (124) :   // 3-7
      case (248) :   // 4-8
      case (496) :   // 5-9
      case (992) :   // 6-10
      case (1984) :  // 7-J
      case (3968) :  // 8-Q
      case (7936) :  // 9-K
        if (vFlush) return 8 ;  // Straight Flush
        else return 4 ;         // Straight
        break ;
      case (7681) :  // 10-A
        if (vFlush) return 9 ;  // Royal Flush
        else return 4 ;         // Straight
        break ;
      default :
        if (vFlush) return 5 ;  // Flush
        else return 0 ;         // RAZGU
        break ;	
    }  
  }
  else {
    // pairs or better
		
    vMaxnum = 0 ;
    vDiffnum = 0 ;
    for (i = 0 ; i < 13 ; i++) {
      aRanks[i] = 0 ;
    }
		
    // how many cards of each rank do we have?
    for (i = 0 ; i < 5 ; i++) {
      j = Hand[i].numrank ;
      aRanks[j]++ ;
      if (aRanks[j] > vMaxnum)  
        vMaxnum = aRanks[j] ; 
    }
		
    // what's the maximum number we have of one rank?
    for (i = 0 ; i < 13 ; i++ ) {
      if (aRanks[i] > 0) { 
        vDiffnum++ ;
      }
    }
		
    switch(vMaxnum) {
      case 4 :
        return 7 ;                       // Four of a kind
        break ;
      case 3 :
        if (vDiffnum == 2) return 6 ;    // Full House
        else return 3 ;                  // Three of a kind
        break ;
      case 2 :
        if (vDiffnum == 3) {
          return 2 ;                     // Two pairs
        }
        else {
          for (i = 10 ; i < 13 ; i++) {
            if (aRanks[i] == 2 ) { 
              vJacksPlus = true ;
            } 
          }
          if (vJacksPlus || (aRanks[0] == 2)) {  
            return 1 ;                   // Jacks or Better 
          }
          else {
            return 0 ;                   // RAZGU
          }
        } 	
        break ;
      default:
        return 99 ;
        break;	
    }
  }
}

// show results
function ShowResults(pWhich) {

  switch (pWhich) {
    case 0 :
      document.PForm3.txtresult.value = "(nothing)" ;
      break ;
    case 1 : 	
      document.PForm3.txtresult.value = "Jacks or Better" ;
      break ;
    case 2 : 	
      document.PForm3.txtresult.value = "Two pairs" ;
      break ;
    case 3 : 	
      document.PForm3.txtresult.value = "Three of a kind" ;
      break ;
    case 4 : 	
      document.PForm3.txtresult.value = "Straight" ;
      break ;
    case 5 : 	
      document.PForm3.txtresult.value = "Flush" ;
      break ;
    case 6 : 	
      document.PForm3.txtresult.value = "Full House" ;
      break ;
    case 7 : 	
      document.PForm3.txtresult.value = "Four of a kind" ;
      break ;
    case 8 : 	
      document.PForm3.txtresult.value = "Straight Flush" ;
      break ;
    case 9 : 	
      document.PForm3.txtresult.value = "Royal Flush" ;
      break ;
    default : 	
      document.PForm3.txtresult.value = "Unexpected result" ;
    break ;
  }
}


// Set game state
function SetGameState(pWhich) {
var x ;
	
  switch(pWhich) {
    case 0 :
      if (GameState != 2 ) return ;
      ResetCards() ; break ;
    case 1 : 
      if (GameState != 0 ) return ;	
      ShuffleDeal() ; break ;
    case 2 :
      if (GameState != 1 ) return ;
      Redeal() ; 
      x = ScoreHand() ;
      ShowResults(x) ;
      break ;	
    default :
      alert(pWhich) ;
    break ;	
  }
  GameState = pWhich ;
}

/* 
 * -- end of functions -- 
 */

var vRank = null, vSuit = null, vColor = null, vNumRank = 0, vNumSuit = 0, vCardMask = 0 ;
var GameState = 0 ; 

// scoring arrays
var aRanks = new Array(13) ;
for (i = 0 ; i < 13 ; i++) {
  aRanks[i] = 0 ;  
}

// create and initialize our deck	
var Deck = new Array(52) ;

for (i = 0 ; i < 52 ; i++ ) {

  switch (Math.floor(i / 13) ) {
    case 0: vSuit = "Spades" ; vColor = "black" ; vNumSuit = 1 ; break ;
    case 1: vSuit = "Hearts" ; vColor = "red" ; vNumSuit = 2; break ;
    case 2: vSuit = "Diamonds" ; vColor = "red" ; vNumSuit = 4; break ;
    case 3: vSuit = "Clubs" ; vColor = "black" ; vNumSuit = 8; break ;
    default: vSuit = "Error" ; vColor = "green" ; break ;						
  } 
	
  switch (i % 13) {
    case 0: vRank = "Ace"    ; vCardMask = 1 ; break ;
    case 1: vRank = "Two"    ; vCardMask = 2 ; break ;
    case 2: vRank = "Three"  ; vCardMask = 4 ; break ;
    case 3: vRank = "Four"   ; vCardMask = 8 ; break ;
    case 4: vRank = "Five"   ; vCardMask = 0x10 ; break ;
    case 5: vRank = "Six"    ; vCardMask = 0x20 ; break ;
    case 6: vRank = "Seven"  ; vCardMask = 0x40 ; break ;
    case 7: vRank = "Eight"  ; vCardMask = 0x80 ; break ;
    case 8: vRank = "Nine"   ; vCardMask = 0x100 ; break ;
    case 9: vRank = "Ten"    ; vCardMask = 0x200 ; break ;
    case 10: vRank = "Jack"  ; vCardMask = 0x400 ; break ;
    case 11: vRank = "Queen" ; vCardMask = 0x800 ; break ;
    case 12: vRank = "King"  ; vCardMask = 0x1000 ; break ;
    default: vRank = "Error" ; break ;						
  } 
	
  vNumRank = (i % 13) ;
	
  Deck[i] = new Card(vSuit, vRank, vColor, vNumSuit, vNumRank, vCardMask) ;
}	

// create and initialize our poker hand
var Hand = new Array(5) ;

for ( i = 0 ; i < 5 ; i++ ) {
  Hand[i] = new Card(0, 0, 0) ;
}

ResetCards() ;
	
//-->
</script>


Code in the page

<table border=0 cellpadding=5 cellspacing=2><tr>

<td align=center colspan=5>

<FORM name="PForm3">
<input type="text" name="txtresult" length=10 value=""><br>
</FORM>

</td>

</tr><tr>

<td align=center>
<img name="CardImg0" src="graphics/cards/b.gif" border=0>
</td><td align=center>
<img name="CardImg1" src="graphics/cards/b.gif" border=0>
</td><td align=center>
<img name="CardImg2" src="graphics/cards/b.gif" border=0>
</td><td align=center>
<img name="CardImg3" src="graphics/cards/b.gif" border=0>
</td><td align=center>
<img name="CardImg4" src="graphics/cards/b.gif" border=0>
</td>

</tr><tr>
<FORM name="PForm1">

<td align=center>
<input type="button" name="Hold0" value=" ... " onClick="HoldCard(0)">
</td><td align=center>
<input type="button" name="Hold1" value=" ... " onClick="HoldCard(1)">
</td><td align=center>
<input type="button" name="Hold2" value=" ... " onClick="HoldCard(2)">
</td><td align=center>
<input type="button" name="Hold3" value=" ... " onClick="HoldCard(3)">
</td><td align=center>
<input type="button" name="Hold4" value=" ... " onClick="HoldCard(4)">

</td>
</FORM>

</tr><tr>

<td colspan=5 align=center>

<FORM name="PForm2">

<input type="button" name="btDeal" value=" Deal " onClick="SetGameState(1)">
<input type="button" name="btDraw" value=" ... " onClick="SetGameState(2)">
<input type="button" name="btCardReset" value=" ... " onClick="SetGameState(0)">

</FORM>

</td>

</tr></tr></table>