<?php
//runnable from shell or web

$probabilities = array("zilch"=>       .5012,  // 1/2
               
"one pair"=>    .4226,  
               
"two pair"=>    .0475,  // 1/21
               
"3 of a kind"=> .0211,  // 1/47
               
"straight"=>    .00392, // 1/255
               
"flush"=>       .00197, // 1/507
               
"full house"=>  .00144, // 1/694
               
"4 of a kind"=> .00024, // 1/4166
               
"straight flush"=> .0000154);  // 1/64935

function usage() {
    global
$argv;
    return
"Usage: $argv[0] [-i] [-w] [<#hands>]\n";
}

//cgi/cli call of program:
if (!isset($_SERVER['REQUEST_METHOD'])) {
//*******************************************************
  
$interactive = true;
  
$improvements = false;
  
$find_winner = false;
  
$deals = 0;

  for (
$i=1; $i<$argc; $i++)
    switch (
$argv[$i]) {
    case
'-i':
      
$improvements = true;
      break;
    case
'-w':
      
$find_winner = true;
      break;
    default:
      if (
is_numeric($argv[$i])) {
    
$interactive = false;
    
$deals = $argv[$i];
      }
      else
    die(
usage());
    }
  
//this function call does all the work:
  
deal_score_improve_findwinner($deals,$interactive,$improvements,$find_winner);
}

//**********************************************************
//initial GET of web page
elseif ($_SERVER['REQUEST_METHOD'] == 'GET') {
  
sendFormPage();
  echo
"</body>\n</html>";
  
//exit();  
}

//**********************************************************
//must be POST
else {
  
$deals = $_POST['deals'];
  
$find_winner = $_POST['find_winner']=='find_winner_checked' ? true : false;
  
$improvements = $_POST['improvements']=='improvements_checked' ? true : false;

  
sendFormPage();

  echo
"<pre>\n";
  
deal_score_improve_findwinner($deals,false,$improvements,$find_winner);
  echo
"</pre>\n";
  echo
"</body>\n</html>";
}


//**********************************************************
//called for GET and POST responses
function sendFormPage() {
  global
$deals, $find_winner, $improvements;

  
$serverNamePhpSelf = $_SERVER['SERVER_NAME'].$_SERVER['PHP_SELF'];
  if (isset(
$deals))
    
$dealsValue = $deals;
  else
    
$dealsValue = 10;
  
$find_winnerYesChecked = $find_winner ? "checked" : "";
  
$improvementsYesChecked = $improvements ? "checked" : "";
  
  echo <<< endofgetpage
<html>
<head>
<title>Poker hands</title>

<script language="javascript">
<!--Hide from non-JavaScript browsers
//form submittal verifier.
//Check that #deals entered and is numeric
function verify()
{
  var msg;              //string to contain error msgs
  
  if (document.pokerForm.deals.value.match(/^ *
$/) ||
      document.pokerForm.deals.value.length>0 &&
      !document.pokerForm.deals.value.match(/^ *
[0-9]{1,5} *$/))
    msg = "Number of deals must be a number";

  if (msg.length > 0)
{       //if any error
    alert(msg);                 //inform user of the errors.
    return false;               //prevents form submittal
  
}
  else
    return true;                //form is OK, submit to server
}    
//-->
</script>
</head>
<body>
Generate poker hands, score them, determine best hand, improve them.
<br>
<form name="pokerForm"
action="http://$serverNamePhpSelf"
method="post"
onsubmit="return verify();">
Number of hands to deal:
<input type="text" size="3" maxlength="5"  name="deals" value="$dealsValue">
<br>
  Show winning hand:
<input name="find_winner" value="find_winner_checked" type="checkbox" $find_winnerYesChecked>
&nbsp;&nbsp;&nbsp;&nbsp;
Improve the hands:
<input name="improvements" value="improvements_checked" type="checkbox" $improvementsYesChecked>
<br>
<input value="Deal!" type="submit">
</form>
endofgetpage;}


//**********************************************************
//classes and functions:

//used by both CGI and web
function deal_score_improve_findwinner($deals,$interactive,$improvements,$find_winner) {
  global
$probabilities;

$deck = Card::make_52deck();
shuffle($deck);
//echo Card::toString_array($deck);
//echo "\n";
/*
//test display and sort of a hand
//$hand = array_slice($deck,-5);
$hand = array_splice($deck,-5);   //remove the hand's cards from deck
//echo array_reduce($hand,'Card::handstring');   // error.  callback can't be static??
//echo Card::toString_array($hand);
//echo "\n";
Card::sort_array($hand);
echo Card::toString_array($hand);
//echo "\n";
$handtype = score_hand($hand);
echo "  $handtype\n";

improve_hand($hand,$handtype,$deck);   //remove cards from deck to hand.  sort it
echo Card::toString_array($hand);
$improved_handtype = score_hand($hand);
echo "$improved_handtype\n";
*/



//test the scoring
//??create keyed 2D array.  keys of both dimensions are keys of a 1D array.
//$improveMatrix = array_keys($probabilities);
//foreach ($improveMatrix as $k2)
//   $improveMatrix[$k2] = array_keys($probabilities);
$improveMatrix = array();
foreach (
array_keys($probabilities) as $k) {
  
$improveMatrix[$k] = array();
  foreach (
array_keys($probabilities) as $k2)
    
$improveMatrix[$k][$k2] = 0;
}
//only the zilch, one pair, two pair and 3 of a kind rows will be of interest...

if ($interactive) {
     echo
"Enter number of hands to deal and score";
     echo (
$find_winner ? " and then find winner: " : ": ");
     echo (
$improvements ? " and then improve: " : ": ");
     
$cin = fopen("php://stdin","r");
     
$deals = fread($cin,1024);
}
$handtypes = array();
foreach (
array_keys($probabilities) as $handtype)
     
$handtypes[$handtype] = 0;

if (
$find_winner)
    
$all_hands = array();    //copy of every hand will be added

for ($i=1; $i<=$deals; $i++) {
  
$deck = Card::make_52deck();   //need to recreate if improving each hand  :(
  
shuffle($deck);
  
$hand = array_splice($deck,-5);   //slice faster than splice? for this testing
  //for ($j=0; $j<5; $j++)
  //$hand[$j] = $deck[array_rand($deck)];   //bug: can be dups
  
Card::sort_array($hand);
  
$handtype = score_hand($hand);
  
$handtypes[$handtype]++;
  if (
$find_winner)
    
$all_hands[] = $hand;
  
/*
  //rare hands
  if ($handtype=='4 of a kind' || $handtype=='straight' || $handtype=='flush' ||
      $handtype=='straight flush') {
    echo Card::toString_array($hand)."    $handtype\n";
  }
  */
  
if ($improvements) {
    
improve_hand($hand,$handtype,$deck);   //remove cards from deck to hand.  sort it
    
$improved_handtype = score_hand($hand);
    
$improveMatrix[$handtype][$improved_handtype]++;
  }
}
arsort($handtypes);
echo
"    hand type         #      %      probability\n";
foreach (
$handtypes as $handtype=>$count)
     
printf("%14s: %8d  %6.5f    %6.5f\n",$handtype,$count,$count/$deals,$probabilities[$handtype]);

if (
$find_winner) {
  
$winning_hand =& $all_hands[winning_hand($all_hands)];   
  if (@
$_SERVER['REQUEST_METHOD'] == 'POST') {
    echo
"\nBest hand (maybe): ".(Card::toString_array($winning_hand))."\n\n";
    foreach (
$winning_hand as $card) {
      
$pip = $card->get_pip();
      switch (
$pip) {
      case
1: $card_fname = 'a';  break;
      case
11: $card_fname = 'j';  break;
      case
12: $card_fname = 'q';  break;
      case
13: $card_fname = 'k';  break;
      default:
$card_fname = $pip;    //numeric value
      
}
      
$card_fname .= strtolower($card->get_suit()).'.gif';
      echo
'<img src="Cards/'.$card_fname.'">';
    }
  }
  else    
// cgi
    
echo "\nBest hand (maybe): ".(Card::toString_array($winning_hand))."\n\n";
}

if (
$improvements) {
  echo
"\n\nImproved hands:\n";
  if (
$handtypes['zilch'] > 0) {
    echo
"zilch\n";   //print_r($improveMatrix['zilch']);
    
printf("   ->one pair:    %5d   %6.5f\n",$improveMatrix['zilch']['one pair'],
       
$improveMatrix['zilch']['one pair']/$handtypes['zilch']);
    
printf("   ->two pair:    %5d   %6.5f\n",$improveMatrix['zilch']['two pair'],
       
$improveMatrix['zilch']['two pair']/$handtypes['zilch']);
    
printf("   ->3 of a kind: %5d   %6.5f\n",$improveMatrix['zilch']['3 of a kind'],
       
$improveMatrix['zilch']['3 of a kind']/$handtypes['zilch']);
    
printf("   ->straight:    %5d   %6.5f\n",$improveMatrix['zilch']['straight'],
       
$improveMatrix['zilch']['straight']/$handtypes['zilch']);
    
printf("   ->flush:       %5d   %6.5f\n",$improveMatrix['zilch']['flush'],
       
$improveMatrix['zilch']['flush']/$handtypes['zilch']);
    
printf("   ->full house:  %5d   %6.5f\n",$improveMatrix['zilch']['full house'],
       
$improveMatrix['zilch']['full house']/$handtypes['zilch']);
    
printf("   ->4 of a kind: %5d   %6.5f\n",$improveMatrix['zilch']['4 of a kind'],
       
$improveMatrix['zilch']['4 of a kind']/$handtypes['zilch']);
    
printf("   ->strght flush:%5d   %6.5f\n",$improveMatrix['zilch']['straight flush'],
       
$improveMatrix['zilch']['straight flush']/$handtypes['zilch']);
  }
  if (
$handtypes['one pair'] > 0) {
    echo
"one pair\n";   //print_r($improveMatrix['one pair']);
    
printf("   ->two pair:    %5d   %6.5f\n",$improveMatrix['one pair']['two pair'],
       
$improveMatrix['one pair']['two pair']/$handtypes['one pair']);
    
printf("   ->3 of a kind: %5d   %6.5f\n",$improveMatrix['one pair']['3 of a kind'],
       
$improveMatrix['one pair']['3 of a kind']/$handtypes['one pair']);
    
printf("   ->full house:  %5d   %6.5f\n",$improveMatrix['one pair']['full house'],
       
$improveMatrix['one pair']['full house']/$handtypes['one pair']);
    
printf("   ->4 of a kind: %5d   %6.5f\n",$improveMatrix['one pair']['4 of a kind'],
       
$improveMatrix['one pair']['4 of a kind']/$handtypes['one pair']);
  }
  if (
$handtypes['two pair'] > 0) {
    echo
"two pair\n";   //print_r($improveMatrix['two pair']);
    
printf("   ->full house:  %5d   %6.5f\n",$improveMatrix['two pair']['full house'],
       
$improveMatrix['two pair']['full house']/$handtypes['two pair']);
  }
  if (
$handtypes['3 of a kind'] > 0) {
    echo
"3 of a kind\n";   //print_r($improveMatrix['3 of a kind']);
    
printf("   ->full house:  %5d   %6.5f\n",$improveMatrix['3 of a kind']['full house'],
       
$improveMatrix['3 of a kind']['full house']/$handtypes['3 of a kind']);
    
printf("   ->4 of a kind: %5d   %6.5f\n",$improveMatrix['3 of a kind']['4 of a kind'],
       
$improveMatrix['3 of a kind']['4 of a kind']/$handtypes['3 of a kind']);
  }
}
}


//**********************************************************

class Card {
  var
$suit;
  var
$pip;
  function
Card ($pip,$suit) {
    
$this->pip = $pip;
    
$this->suit = $suit;
  }
  function
get_pip() {
    return
$this->pip;
  }
  function
get_suit() {
    return
$this->suit;
  }
  function
toString() {
    return
Card::pretty_pip($this->pip).$this->suit;
  }
  
//static
  
function pretty_pip($pip_index) {
    
$pips = array(0,'A',2,3,4,5,6,7,8,9,10,'J','Q','K');  //0 is dummy
    
return $pips[$pip_index];
  }

  
//static
  
function make_52deck() {
    
//$suits = array('clubs','hearts','diamonds','spades');
    
$suits = array('C','H','D','S');
    
// $pips = array('A',2,3,4,5,6,7,8,9,10,'J','Q','K');
    
$pips = range(1,13);  //ace is 1
    
$deck = array();
    foreach (
$suits as $suit)
      foreach (
$pips as $pip)
    
$deck[] = new Card($pip,$suit);
    return
$deck;
  }
  
//static for callback of array of Cards.  ???? can't specify class static fucntion in array_reduce
  
function handstring($s,$card) {
    return
$s.$card->toString()." ";
  }
  
//static.  array of Cards to string
  
function toString_array($hand) {
    
$s = "";
    foreach (
$hand as $card)
      
$s .= $card->toString()." ";
    return
$s;
  }
  
//static.  sort array of Cards. by pip
  
function sort_array(&$hand) {
    
usort($hand,'cardcmp');
  }
}
//compare 2 Cards.  can't be member function??
function cardcmp($cardA, $cardB) {
  
$apip = $cardA->get_pip();
  
$bpip = $cardB->get_pip();
  if (
$apip < $bpip)
    return -
1;
  elseif (
$apip > $bpip)
    return
1;
  else
    return
0;
}

//**********************************************************

function score_hand($hand) {
  
$pips = array();
  
$suits = array();
  foreach (
$hand as $card) {
    
$pips[] = $card->get_pip();
    
$suits[] = $card->get_suit();
  }
  if (
has_straight($pips)) {
    
$handtype = "straight";
    if (
has_flush($suits))
      
$handtype .= " flush";
    return
$handtype;
  }
  elseif (
has_flush($suits))
    return
"flush";
  elseif (
has_full_house($pips))
    return
"full house";
  elseif (
has_four_of_a_kind($pips))
    return
"4 of a kind";
  elseif (
has_three_of_a_kind($pips))
    return
"3 of a kind";
  elseif (
has_two_pair($pips))
    return
"two pair";
  elseif (
has_one_pair($pips))
    return
"one pair";
  else
    return
"zilch";
}

function
has_straight ($h) {
   
$run = true;  
   for (
$i=1; $i<5 && $run;  $i++)  // run if all current == prev + 1
          
$run = $h[$i] == $h[$i-1] + 1;

   if (
$h[0]==1 && !$run) // kluge for high ace.  ace is 1
     
$run =
       
$h[1] == 10 &&
       
$h[2] == 11 &&
       
$h[3] == 12 &&
       
$h[4] == 13;
   return
$run;
}


function
has_flush ($h) {   // array of suits
   
$same = true;                          
   for (
$i=1; $i<5 && $same; $i++)
     
$same = $h[$i] == $h[$i-1]; // same true unless suit diff
   
return $same;
}

function
has_four_of_a_kind ($h) {
  if ((
$h[0]==$h[1] && $h[1]==$h[2] && $h[2]==$h[3]) ||  // 0,1,2,3
       
($h[1]==$h[2] && $h[2]==$h[3] && $h[3]==$h[4]))    // 1,2,3,4
     
return true;
   else
     return
false;
}

function
has_full_house ($h) {
  if ((
$h[0]==$h[1] && $h[2]==$h[3] && $h[3]==$h[4]) ||   // 0,1  2,3,4
       
($h[0]==$h[1] && $h[1]==$h[2] && $h[3]==$h[4]))     // 0,1,2  3,4
     
return true;
   else
     return
false;
}

function
has_three_of_a_kind ($h) {
  if ((
$h[0]==$h[1] && $h[1]==$h[2]) ||    // 0,1,2
       
($h[1]==$h[2] && $h[2]==$h[3]) ||    // 1,2,3
       
($h[2]==$h[3] && $h[3]==$h[4]))      // 2,3,4
     
return true;
   else
     return
false;
}

function
has_two_pair ($h) {
  if ((
$h[0]==$h[1] && $h[2]==$h[3]) ||    // 0,1 2,3
       
($h[0]==$h[1] && $h[3]==$h[4]) ||    // 0,1 3,4
       
($h[1]==$h[2] && $h[3]==$h[4]))      // 1,2 3,4
     
return true;
   else
     return
false;
}

function
has_one_pair ($h) {
  if ((
$h[0]==$h[1]) ||    // 0,1
       
($h[1]==$h[2]) ||    // 1,2
       
($h[2]==$h[3]) ||    // 2,3
       
($h[3]==$h[4]))      // 3,4
     
return true;
   else
     return
false;
}

//replace cards of hand with cards from end of deck to improve its known handtype
function improve_hand(&$hand,$handtype,&$deck) {
  
//do nothing if straight, full house, 4 of a kind, flush, straight flush
  //one pair: draw 3
  //two pair: draw 1
  //3 of a kind: draw 2
  //zilch: if 4 of same suit, draw 1 for flush
  //       if insde or outside straight, try for it
  //       else draw 4 low
  
switch ($handtype) {
  case
'one pair':
    if (
$hand[0]->get_pip() == $hand[1]->get_pip()) {
      
$hand[2] = array_pop($deck);
      
$hand[3] = array_pop($deck);
      
$hand[4] = array_pop($deck);
    }
    elseif (
$hand[1]->get_pip() == $hand[2]->get_pip()) {
      
$hand[0] = array_pop($deck);
      
$hand[3] = array_pop($deck);
      
$hand[4] = array_pop($deck);
    }
    elseif (
$hand[2]->get_pip() == $hand[3]->get_pip()) {
      
$hand[0] = array_pop($deck);
      
$hand[1] = array_pop($deck);
      
$hand[4] = array_pop($deck);
    }
    elseif (
$hand[3]->get_pip() == $hand[4]->get_pip()) {
      
$hand[0] = array_pop($deck);
      
$hand[1] = array_pop($deck);
      
$hand[2] = array_pop($deck);
    }
    break;
  case
'two pair':
    if (
$hand[0]->get_pip() == $hand[1]->get_pip())   // 0,1
      
if ($hand[2]->get_pip() == $hand[3]->get_pip())   //     2,3
    
$hand[4] = array_pop($deck);
      else                                              
//     3,4                    
    
$hand[2] = array_pop($deck);
    else                                              
// 1,2 3,4
    
$hand[0] = array_pop($deck);
    break;
  case
'3 of a kind':
    
//0,1,2
    
if ($hand[0]->get_pip()==$hand[1]->get_pip() && $hand[1]->get_pip()==$hand[2]->get_pip()) {
      
$hand[3] = array_pop($deck);
      
$hand[4] = array_pop($deck);
    }
    
//1,2,3
    
elseif ($hand[1]->get_pip()==$hand[2]->get_pip() && $hand[2]->get_pip()==$hand[3]->get_pip()) {
      
$hand[0] = array_pop($deck);
      
$hand[4] = array_pop($deck);
    }
    
//2,3,4
    
else {
      
$hand[0] = array_pop($deck);
      
$hand[1] = array_pop($deck);
    }
    break;
  case
'zilch':
    
//9 possible cards to complete a near-flush
    
if (($discard_index=has_near_flush($hand)) != -1)
      
$hand[$discard_index] = array_pop($deck);

    
//8 possible cards to complete a near outside straight
    //4 possible cards to complete a near inside or border straight
    
elseif (($discard_index=has_near_straight($hand)) != -1)
      
$hand[$discard_index] = array_pop($deck);

    else {   
//discard 4 low cards
      
if ($hand[0]->get_pip() == 1)  //has ace
    
$hand[4] = array_pop($deck);   //discard last card
      
else
    
$hand[0] = array_pop($deck);   //discard first card
      
$hand[1] = array_pop($deck);  //discard 3 in the middle
      
$hand[2] = array_pop($deck);
      
$hand[3] = array_pop($deck);
    }
    break;
  }

  
Card::sort_array($hand);

}

//return index of fifth card of different suit than other four, else -1 if not 4-card flush
function has_near_flush ($h) {   // array of cards
  
$suit_count = array('C'=>0,'H'=>0,'D'=>0,'S'=>0);  //dependent on CHDS
  
for ($i=0; $i<5; $i++)     //count the number of each suit in the hand
    
$suit_count[$h[$i]->get_suit()]++;
  
asort($suit_count);   //sort by count, preserve suit keys
  
if (array_pop($suit_count) == 4) {   //if last has count of 4, second to last will be 1
    
$suit_count = array_flip($suit_count);  //make keys the values
    
for ($i=0; $i<5; $i++)    //find the 1 by its suit in the hand
      
if ($h[$i]->get_suit() == $suit_count[1])  //
    
return $i;
  }
  else
    return -
1;
}

//knowing sorted hand is zilch,
function has_near_straight ($h) {   // array of cards
  //near outside straight:  distance between 4 adjacent cards is 3. discard is either hi or lo.
  
if ($h[3]->get_pip() - $h[0]->get_pip() == 3)  //0,1,2,3,x in order
    
return 4;
  elseif (
$h[4]->get_pip() - $h[1]->get_pip() == 3)  //x,1,2,3,4 in order
    
return 0;
  elseif (
$h[0]->get_pip()==1 && $h[2]->get_pip()==11 && $h[4]->get_pip()==13)  //AxJQK
    
return 1;   //note: x10JQK is case above
  //near inside straight:  distance between 4 adjacent cards is 4. discard is either hi or lo.
  
elseif ($h[3]->get_pip() - $h[0]->get_pip() == 4)  
    return
4;
  elseif (
$h[4]->get_pip() - $h[1]->get_pip() == 4)  
    return
0;
  elseif (
$h[0]->get_pip()==1 && $h[2]->get_pip()==10)  //Ax10[two of JQK]
    
return 1;
  else
    return -
1;
}

//return index of winning hand in array of hands
function winning_hand($hands) {
  global
$probabilities;
  
$ranks = array_keys($probabilities);
  
$hands_ranks = array();
  
//each hand's handtype as numeric index of ordered ranks
  
for ($i=0, $stop=count($hands); $i<$stop; $i++)
    
$hands_ranks[$i] = array_search(score_hand($hands[$i]),$ranks);

  
arsort($hands_ranks);  //preserve (numeric) keys

  //TODO: resolve ties...

  
reset($hands_ranks);
  return
key($hands_ranks);
}



?>