thoemo/poker (1.8.3)
Installation
{
"repositories": [{
"type": "composer",
"url": " "
}
]
}composer config repositories.thoemo composer composer require thoemo/poker:1.8.3About this package
thoemo/poker
Poker hand evaluation and a Texas Hold'em round simulation for PHP, built on thoemo/cards.
It ranks five-card hands, compares them, and deals and evaluates a complete Hold'em round from
the hole cards to the showdown. There are no chips and no betting.
Requirements
- PHP >= 8.2
thoemo/cards^1.3.1(installed automatically, bringsthoemo/calc)
Installation
The package is served by the Forgejo Composer registry. Add the repository to your
composer.json:
"repositories": {
"packages.link.thoemo.net/thoemo": {
"type": "composer",
"url": "https://packages.link.thoemo.net/api/packages/thoemo/composer"
}
}
Then require the package:
composer require thoemo/poker
The PHP namespace is elnebuloso\Poker; cards come from elnebuloso\Cards (see the
thoemo/cards README).
Overview
| Class | Description |
|---|---|
Hand |
A CardCollection plus the Ranking the ranker attached to it. |
Ranking |
Hand category (ROYAL_FLUSH = 10 … HIGH_CARD = 1), high card and up to four kickers; beats() and ties() compare two rankings. |
RankerFactory |
create(): Ranker with all ten ranking handlers in the right order. |
Ranker |
evaluateHand(Hand $hand): Ranking for a hand of exactly five cards. |
RankingHandlerInterface |
One handler per hand category in RankingHandler\, chained from Royal Flush down to High Card. |
RankerException |
Thrown for a hand without five cards or a misconfigured handler chain. |
Game\TexasHoldEmFactory |
create(string $id, array $players): TexasHoldEm with a new shuffled StandardDeck. |
Game\TexasHoldEm |
Deals and evaluates one complete round for 2 to 10 players in its constructor. |
Player |
A player of a round: ID, seat position, start, flop, turn and final hand, and rank. |
Usage
Evaluate a hand
use elnebuloso\Cards\Card;
use elnebuloso\Cards\CardCollection;
use elnebuloso\Poker\Hand;
use elnebuloso\Poker\RankerFactory;
$ranker = (new RankerFactory())->create();
$hand = new Hand();
$hand->setCards(new CardCollection(array_map([Card::class, "createByType"], ["KS", "KH", "KD", "3C", "3D"])));
$ranking = $ranker->evaluateHand($hand); // also stored in $hand->getRanking()
$ranking->getName(); // "Full House"
$ranking->getValue(); // 7 (Ranking::FULL_HOUSE)
$ranking->getDesc(); // "07/13/03/00/00/00": category/high card/kicker 1-4, two digits each
A hand with more or fewer than five cards throws a RankerException
(Every hand must contain exactly 5 cards!). Suits never decide between two hands.
Compare hands
$a = new Hand();
$a->setCards(new CardCollection(array_map([Card::class, "createByType"], ["AS", "AD", "KC", "KH", "2D"])));
$b = new Hand();
$b->setCards(new CardCollection(array_map([Card::class, "createByType"], ["AC", "AH", "QS", "QD", "9C"])));
$rankingA = $ranker->evaluateHand($a); // Two Pairs, "03/14/13/02/00/00"
$rankingB = $ranker->evaluateHand($b); // Two Pairs, "03/14/12/09/00/00"
$rankingA->beats($rankingB); // true: kings beat queens as the second pair
$rankingA->ties($rankingB); // false
To find the best five of more cards, rank every combination and keep the best one:
$best = null;
foreach ($sevenCards->combinations(5) as $fiveCards) {
$hand = new Hand();
$hand->setCards($fiveCards);
$ranker->evaluateHand($hand);
if ($best === null || $hand->getRanking()->beats($best->getRanking())) {
$best = $hand;
}
}
Play a Texas Hold'em round
The whole round happens in the constructor: each player in turn gets two cards from the top of the deck, then one card is burned before each of flop (3 cards), turn and river. Every player's best hand is evaluated for the flop (start hand plus flop), the turn (best five of six) and the river (best five of seven), and players are ranked by their final hand.
use elnebuloso\Poker\Game\TexasHoldEmFactory;
$game = (new TexasHoldEmFactory())->create("game-1", ["Alice", "Bob", "Carol"]);
echo $game->getFlop(), " | ", $game->getTurn(), " | ", $game->getRiver(); // e.g. "3H,5C,KH | 3C | JH"
echo $game->getBoard(); // "3H,5C,KH,3C,JH"
foreach ($game->getPlayersByRank() as $player) {
printf(
"%d %s %s -> %s %s\n",
$player->getRank(),
$player->getId(),
$player->getStartHand()->getCards(),
$player->getFinalHand()->getCards(),
$player->getFinalHand()->getRanking()->getName(),
);
}
// e.g. 0 Bob 7S,3D -> 3D,3H,KH,3C,JH Three of a Kind
- Rank
0is the winner. Players with equal final hands share a rank and the next rank is skipped (0, 0, 2), so a split pot is every player with rank0. - Player names are used as IDs and as array keys:
getPlayers()returns the players by seat position,getPlayersByRank()by rank,getPlayerById("Bob")one player ornull. getCardsBurnedBeforeFlop(),getCardsBurnedBeforeTurn()andgetCardsBurnedBeforeRiver()return the burned cards.
new TexasHoldEm($id, $deck, $players) takes a StandardDeck of your own. The deck must be
shuffled and there must be 2 to 10 players, otherwise an InvalidArgumentException is thrown.
To replay a fixed card order (e.g. in a test), mark an empty collection as shuffled, then add a
complete deck in dealing order:
use elnebuloso\Cards\StandardDeck;
use elnebuloso\Poker\Game\TexasHoldEm;
$order = "AC,KD,AS,QH,2C,3H,5D,8C,2D,9S,2H,JH,2S,3C,3D,3S,4C,4D,4H,4S,5C,5H,5S,6C,6D,6H,"
. "6S,7C,7D,7H,7S,8D,8H,8S,9C,9D,9H,10C,10D,10H,10S,JC,JD,JS,KC,KH,KS,QC,QD,QS,AD,AH";
$cards = new CardCollection();
$cards->shuffle();
$cards->addCards(array_map([Card::class, "createByType"], explode(",", $order)));
$game = new TexasHoldEm("game-2", new StandardDeck($cards), ["Alice", "Bob"]);
// Alice: AC,KD Bob: AS,QH board: 3H,5D,8C,9S,JH
$game->getPlayerById("Alice")->getFinalHand()->getRanking()->getDesc(); // "01/14/13/11/09/08"
$game->getPlayerById("Alice")->getRank(); // 0, wins on the king
Background on the hand-ranking rules and the round flow, in German: features/001-overview/PLAN.md
and features/003-regelkonformitaet/PLAN.md in the repository.
Development
Tests and tools run through redo, the Docker-based CI wrapper; build.yml holds the project's
redo properties (default PHP version 8.2).
redo composer:install phpunit # PHPUnit with the PHP version from build.yml
redo composer:install phpunit -Dphp_version=8.4 # PHPUnit with another PHP version
redo phplint -Dphp_version=8.4
The coverage report is written to build/reports/phpunit/all/coverage/html.
Releases
Every push to main runs the GitLab pipeline (phplint, prettier, composer:validate:require
and PHPUnit on PHP 8.2 to 8.5), raises the patch version in VERSION and uploads that version
to the Forgejo registry. The released archive contains only README.md, composer.json and
src/.
Dependencies
Dependencies
| ID | Version |
|---|---|
| php | >=8.2 |
| thoemo/cards | ^1.3.1 |