Scripts

Poker, deck, cards, evaluation

Met de klassen Card en Deck kan je een deck maken van een x aantal kaarten/decks. Met alleen die twee klassen kan je natuurlijk weinig meer dan kaarten uitdelen, maar je kan er ook een spelletje mee maken. De derde class (PokerTexasHoldem) is om adhv een aantal kaarten de beste hand te evalueren en een score terug te geven. Die score kan (terug) gedraaid worden naar een leesbare hand zoals "Flush of Diamonds - Queens high" of "Two Pairs - Queens and Eights". De score bevat de 5 kaarten in de hand. De leesbare hand vertelt ze echter niet altijd allevijf. De class PokerTexasHolden is statisch aan te roepen met een array met Cards als parameter, als resultaat volgt een string met een float value erin. In het geval van een flush staat er de suit/kleur achter. Als $score is wat je terugkrijgt van de statische functie, is (float)$score altijd een nette float die te vergelijken is met de rest van de spelers. Ik heb een voorbeeld bijgevoegd, maar die is ook precies te vinden op: http://games.hotblocks.nl/131b.php Het voorbeeld bevat: Y spelers die allemaal 2 kaarten krijgen, een flop+turn+river van 5 kaarten. Voor elke speler wordt vervolgens hun eigen set 7 Cards gemaakt en die wordt geevalueerd mbv PokerTexasHolden::score(). Alle scores hoger dan X zijn rood en vet aangegeven. Na het listen van alle spelers' scores, wordt het maximum berekend mbv de standaard PHP max() functie. Het voorbeeld is misschien niet helemaal superduidelijk maar na bestuderen wel. De klassen zouden heel duidelijk moeten zijn. Mocht je fouten zien, of kan het efficienter, of heb ik verkeerde spelregels toegepast, LET ME KNOW. Vragen: stel ze hier. Je kan alle klassen in 1 bestand doen, maar ik zou vooral PokerTexasHolden scheiden vd andere 2, want de Card en Deck klassen kunnen voor praktisch elk kaartspel gebruikt worden. Veel plezier ermee. Uitwerkingen en toepassingen zie ik graag :) Nog een voorbeeldje: http://games.hotblocks.nl/131.php En een toepassing van Card en Deck (in de vorm van blackjack): http://games.hotblocks.nl/101c.php In het voorbeeld van het voorbeeld zijn maar 4 plaatjes. 1 voor elke suit/kleur. Als je 2 of 3 keer op Refresh klikt, kan je ze sowieso allevier downloaden. Ik heb er geen zipje ofzo van en ga ik ook niet maken. Is wel heel makkelijk dit. Je kan in de class het image path veranderen.

poker-deck-cards-evaluation
Klassen (ik heb voor elk een apart bestand):
[code]<?php

class Card {
    const image_path = '/images/__SUIT_____SHORT__.gif';
    public static $tostring = 'image';
    public $id = -1;
    public $name = '';
    public $suit = '';
    public $value = -1;
    public $short = '';
    public $pth = -1;
    public function __construct($f_iCard) {
        $iCard = (int)$f_iCard%52;
        $iSuit = floor($iCard/13);
        $iName = $iCard%13;

        $arrSuits = array('clubs', 'diamonds', 'hearts', 'spades');
        $arrNames = array('ace', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'jack', 'queen', 'king');
        $arrShorts = array('a', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'j', 'q', 'k');
        $arrValues = array(11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10);
        $arrPTHValues = array(14, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);

        $this->id = $iCard;
        $this->suit = $arrSuits[$iSuit];
        $this->name = $arrNames[$iName];
        $this->value = $arrValues[$iName];
        $this->short = $arrShorts[$iName];
        $this->pth = $arrPTHValues[$iName];
    }
    public function __tostring() {
        return call_user_func(array($this, self::$tostring));
        return $this->image();
    }
    public function small_image() {
        return '<img src="'.str_replace('__SUIT_____SHORT__', $this->suit, self::image_path).'" /> '.strtoupper($this->short);
    }
    public function image() {
        $r = array(
            '__SHORT__'    => $this->short,
            '__NAME__'    => $this->name,
            '__SUIT__'    => $this->suit,
            '__CARD__'    => $this->id,
        );
        return '<img title="'.$this->id.'" src="'.strtr(self::image_path, $r).'" />';
    }
    public function html() {
        return strtoupper($this->short) . ' of ' . $this->suit;
    }
    public static function random() {
        return new Card(rand(0,51));
    }
}

class Deck {
    public $iNextCard = 0; # protected
    public $cards = array(); # protected
    public function __construct($f_bFill = true) {
        if ( $f_bFill ) {
            foreach ( range(0, 51) AS $iCard ) {
                array_push($this->cards, new Card($iCard));
            }
        }
    }
    public function next() {
        if ( !isset($this->cards[$this->iNextCard]) ) {
            return null;
        }
        return $this->cards[$this->iNextCard++];
    }
    public function size() {
        return (count($this->cards)-$this->iNextCard);
    }
    public function add_deck(Deck $objDeck) {
        $this->cards = array_merge($this->cards, $objDeck->cards);
        return $this;
    }
    public function add_card(Card $objCard) {
        array_push($this->cards, $objCard);
        return $this;
    }
    public function shuffle() {
        return shuffle($this->cards);
    }
    public function replenish() {
        $this->iNextCard = 0;
        $this->shuffle();
    }
    public function __tostring() {
        return implode("\n", $this->cards);
    }
}

class pokertexasholdem {
    public $cards = array();
    public $values = array();
    public $num_values = array();
    public $suits = array();
    public $num_suits = array();
    public function __construct($f_arrCards) {
        $this->cards = $f_arrCards;
        $tmp = array();
        foreach ( $f_arrCards AS $objCard ) {
            $tmp[] = array(
                'value'    => (int)$objCard->pth,
                'suit'    => (string)$objCard->suit,
            );
        }
        $rev2d = self::flip_2d_array($tmp);

        $this->values = $rev2d['value'];
        arsort($this->values);
        $this->num_values = array_count_values($this->values);

        $this->suits = $rev2d['suit'];
        asort($this->suits);
        $this->num_suits = array_count_values($this->suits);
        arsort($this->num_suits);
    }

    public static function readable_hand($f_fHand) {
        $arrCardsText = array(2 => 'Twos', 'Threes', 'Fours', 'Fives', 'Sixes', 'Sevens', 'Eights', 'Nines', 'Tens', 'Jacks', 'Queens', 'Kings', 'Aces');
        $arrCardsShort = array(2 => '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A');
        $x = explode('.', (string)$f_fHand, 2);
        $szExtra = isset($x[1]) ? $x[1] : '';
        $szHand = '';
        switch ( (int)$x[0] )
        {
            case 9:
                $szHand = 'Royal Flush of '.ucfirst(strtolower($szExtra)).'';
            break;

            case 8:
                $szHand = 'Straight Flush of '.ucfirst(strtolower(substr($szExtra, 2))).' - '.$arrCardsText[(int)substr($szExtra, 0, 2)].' high';
            break;

            case 7:
                $szHand = 'Four Of A Kind - '.$arrCardsText[(int)substr($szExtra, 0, 2)].'';
            break;

            case 6:
                $szHand = 'Full House - '.$arrCardsText[(int)substr($szExtra, 0, 2)].' over '.$arrCardsText[(int)substr($szExtra, 2, 2)].'';
            break;

            case 5:
                $szHand = 'Flush of '.ucfirst(strtolower(substr($szExtra, 10))).' - '.$arrCardsText[(int)substr($szExtra, 0, 2)].' high';
            break;

            case 4:
                $szHand = 'Straight - '.$arrCardsText[(int)substr($szExtra, 0, 2)].' high';
            break;

            case 3:
                $szHand = 'Three Of A Kind - '.$arrCardsText[(int)substr($szExtra, 0, 2)].'';
            break;

            case 2:
                $szHand = 'Two Pairs - '.$arrCardsText[(int)substr($szExtra, 0, 2)].' and '.$arrCardsText[(int)substr($szExtra, 2, 2)].'';
            break;

            case 1:
                $szHand = 'One Pair of '.$arrCardsText[(int)substr($szExtra, 0, 2)].'';
            break;

            case 0:
            default:
                $arrKickers = array($arrCardsShort[(int)substr($szExtra, 0, 2)]);
                if ( 0 < ($c=(int)substr($szExtra, 2, 2)) ) {
                    $arrKickers[] = $arrCardsShort[$c];
                }
                if ( 0 < ($c=(int)substr($szExtra, 4, 2)) ) {
                    $arrKickers[] = $arrCardsShort[$c];
                }
                if ( 0 < ($c=(int)substr($szExtra, 6, 2)) ) {
                    $arrKickers[] = $arrCardsShort[$c];
                }
                if ( 0 < ($c=(int)substr($szExtra, 8, 2)) ) {
                    $arrKickers[] = $arrCardsShort[$c];
                }
                $szHand = 'High Cards '.implode(', ', $arrKickers).'';
            break;
        }
        return $szHand;
    }

    # 9
    public function royal_flush() {
        $szStraigtFlush = $this->straight_flush();
        if ( '14' === substr($szStraigtFlush, 0, 2) ) {
            return substr($szStraigtFlush, 2);
        }
        return null;
    }
    # 8
    public function straight_flush() {
        if ( null === ($szSuit=$this->flush(true)) ) {
            return null;
        }
        $arrCards = array();
        foreach ( $this->values AS $iCard => $iValue ) {
            if ( $szSuit == $this->suits[$iCard] ) {
                $arrCards[] = $iValue;
            }
        }
        if ( null !== ($szHiCard=$this->straight($arrCards)) ) {
            return $szHiCard.$szSuit;
        }
        return null;
    }
    # 7
    public function four_of_a_kind() {
        foreach ( $this->num_values AS $iValue => $iAmount ) {
            if ( 4 <= $iAmount ) {
                $szExtra = self::padleft($iValue);
                foreach ( $this->values AS $v ) {
                    if ( $v != $iValue ) {
                        $szExtra .= self::padleft($v);
                        return $szExtra;
                    }
                }
            }
        }
        return null;
    }
    # 6
    public function full_house() {
        if ( null !== ($szPair=$this->one_pair()) && null !== ($szThreeOfAKind=$this->three_of_a_kind()) ) {
            return substr($szThreeOfAKind, 0, 2).substr($szPair, 0, 2);
        }
        return null;
    }
    # 5
    public function flush($f_bSimple = false) {
        if ( 5 <= reset($this->num_suits) ) {
            $szSuit = key($this->num_suits);
            if ( $f_bSimple ) {
                return $szSuit;
            }
            $szExtra = '';
            foreach ( $this->values AS $iCard => $iValue ) {
                if ( $szSuit == $this->suits[$iCard] && 10 > strlen($szExtra) ) {
                    $szExtra .= self::padleft($iValue);
                }
                if ( 10 <= strlen($szExtra) ) {
                    break;
                }
            }
            return $szExtra.$szSuit;
        }
        return null;
    }
    # 4
    public function straight($f_arrValues = null) {
        $arrValues = is_array($f_arrValues) ? $f_arrValues : array_keys($this->num_values);
        if ( 5 > count($arrValues) ) {
            // Not even 5 different cards
            return null;
        }
        for ( $i=0; $i<=count($arrValues)-5; $i++ ) {
            // loop next 5 cards
            $iHiCard = $iPrevValue = $arrValues[$i];
            $bOk = true;
            for ( $j=$i+1; $j<$i+5; $j++ ) {
                if ( $arrValues[$j] != $iPrevValue-1 ) {
                    $bOk = false;
                    break;
                }
                $iPrevValue = $arrValues[$j];
            }
            if ( $bOk ) {
                return self::padleft($iHiCard);
            }
        }
        # ace to 5
        if ( in_array(14, $arrValues) && in_array(2, $arrValues) && in_array(3, $arrValues) && in_array(4, $arrValues) && in_array(5, $arrValues) ) {
            return '05';
        }
        return null;
    }
    # 3
    public function three_of_a_kind() {
        foreach ( $this->num_values AS $iValue => $iAmount ) {
            if ( 3 == $iAmount ) {
                $szExtras = self::padleft($iValue);
                foreach ( $this->values AS $v ) {
                    if ( $iValue != $v && 6 > strlen($szExtras) ) {
                        $szExtras .= self::padleft($v);
                    }
                    if ( 6 <= strlen($szExtras) ) {
                        break;
                    }
                }
                return $szExtras;
            }
        }
        return null;
    }
    # 2
    public function two_pair() {
        $szExtras = '';
        $iVal1 = $iVal2 = 0;
        foreach ( $this->num_values AS $iValue => $iAmount ) {
            if ( 2 == $iAmount && 4 > strlen($szExtras) ) {
                $szExtras .= self::padleft($iValue);
            }
        }
        if ( 4 == strlen($szExtras) ) {
            foreach ( $this->values AS $v ) {
                if ( $iVal1 != $v && $iVal2 != $v ) {
                    $szExtras .= self::padleft($v);
                    break;
                }
            }
            return $szExtras;
        }
        return null;
    }
    # 1
    public function one_pair() {
        foreach ( $this->num_values AS $iValue => $iAmount ) {
            if ( 2 == $iAmount ) {
                $szExtras = self::padleft($iValue);
                foreach ( $this->values AS $v ) {
                    if ( $iValue != $v && 8 > strlen($szExtras) ) {
                        $szExtras .= self::padleft($v);
                    }
                    if ( 8 <= strlen($szExtras) ) {
                        break;
                    }
                }
                return $szExtras;
                break;
            }
        }
        return null;
    }

    public function _score() {
        $arrCheckingOrder = array(
            'royal_flush',
            'straight_flush',
            'four_of_a_kind',
            'full_house',
            'flush',
            'straight',
            'three_of_a_kind',
            'two_pair',
            'one_pair',
        );
        $iScore = 0;
        foreach ( $arrCheckingOrder AS $iRevHandValue => $szCall ) {
            if ( null !== ($szExtra=call_user_func(array($this, $szCall))) ) {
                $iScore = 9-$iRevHandValue;
                break;
            }
        }
        if ( 0 >= $iScore ) {
            // high card
            $iScore = 0;
            $szExtra = '';
            foreach ( $this->values AS $v ) {
                if ( 10 > strlen($szExtra) ) {
                    $szExtra .= self::padleft($v);
                }
                if ( 10 <= strlen($szExtra) ) {
                    break;
                }
            }
        }
        return ($iScore.'.'.(string)$szExtra);
    }
    public static function padleft($s) {
        return str_pad((string)$s, 2, '0', STR_PAD_LEFT);
    }
    public static function score($c) {
        $o = new self($c);
        return $o->_score();
    }
    public static function flip_2d_array($a) {
        $r = array();
        foreach ( $a AS $k1 => $v1 ) {
            foreach ( $v1 AS $k2 => $v2 ) {
                $r[$k2][$k1] = $v2;
            }
        }
        return $r;
    }
}

?>

Voorbeeld:
<?php

require_once('inc.cls.card.php');
require_once('inc.cls.deck.php');
require_once('inc.cls.pokertexasholdem.php');

Card::$tostring = 'small_image';

$objDeck = new Deck();
$objDeck->shuffle();

$iPlayers = 12;
$iMinScore = 4.0;

$arrPublic = $arrPlayers = array();
for ( $i=0; $i<$iPlayers; $i++ ) {
    $arrPlayers[$i] = array();
    array_push($arrPlayers[$i], $objDeck->next());
    array_push($arrPlayers[$i], $objDeck->next());
}
while ( 5 > count($arrPublic) ) {
    array_push($arrPublic, $objDeck->next());
}

$fUtcStart = microtime(true);

/* test *
$arrPublic = array(
    new Card(8),
    new Card(0),
    new Card(3),
    new Card(7),
    new Card(6),
);
$arrPlayers[0] = array(
    new Card(5),
    new Card(4),
);
/* test */

?>
<html>

<head>
<style type="text/css">
body, table {
    background-color: #444;
    font-family        : verdana, arial;
    font-size        : 10px;
    color            : white;
}
</style>
</head>

<body>
<?php

$bNothingWorthy = true;
$iMaxHand = 0;
$arrHands = array();
echo '<table border="0" cellpadding="2" cellspacing="1">';
foreach ( $arrPlayers AS $k => $arrPlayer ) {
    echo '<tr>';
    if ( 0 == $k ) {
        echo '<td rowspan="'.count($arrPlayers).'">'.implode(', ', $arrPublic).'</td>';
        echo '<td rowspan="'.count($arrPlayers).'">&nbsp;+&nbsp;</td>';
    }
    $fHand = pokertexasholdem::score(array_merge($arrPublic, $arrPlayer));
    $arrHands[] = $fHand;
    if ( (float)$fHand > (float)$iMaxHand ) {
        $iMaxHand = $fHand;
    }
    if ( $iMinScore <= (float)$fHand ) {
        $bNothingWorthy = false;
        $szHand = '<b style="color:red;">'.$fHand.'</b>';
    }
    else {
        $szHand = $fHand;
    }
    echo '<td>'.implode(', ', $arrPlayer).'</td>';
    echo '<td>&nbsp;=&nbsp;</td>';
    echo '<td>'.$szHand.'</td>';
    echo '<td>&nbsp;=&nbsp;</td>';
    echo '<td>'.pokertexasholdem::readable_hand($fHand).'</td></tr>';
}
echo '<tr><td colspan="7" align="center">Winner: '.pokertexasholdem::readable_hand($m=max($arrHands)).' ('.$m.')</td></tr>';
echo '</table>';

if ( $bNothingWorthy ) {
    echo '<meta http-equiv="refresh" content="0" />';
}

echo '<p>'.number_format(($fTime=microtime(true)-$fUtcStart), 4).'</p>';

?>
</body>

<title><?php echo $iMaxHand; ?></title>

</html>[/code]

Reacties

0
Nog geen reacties.