Poker Simulator Design Writeup

From CSSEMediaWiki
(Difference between revisions)
Jump to: navigation, search
m (Principles Followed)
Line 215: Line 215:
 
Program to the interface, not to the implementation
 
Program to the interface, not to the implementation
  
I have adhered to the [[427 Design Standard]] on [[Getters and setters]] - All fields are private, getters and setters are only written as needed (in most cases they are in my program), and getters and setters are always used to access a field externally.
+
I have adhered to the [[427 design standard]] on [[Getters and setters]] - All fields are private, getters and setters are only written as needed (in most cases they are in my program), and getters and setters are always used to access a field externally.
  
 
[[Command query separation]] / [[Avoid side effects]] - each method is either a command that performs some action, or a query to retrieve information. None of my methods have unexpected side effects. This makes the program simpler to understand.
 
[[Command query separation]] / [[Avoid side effects]] - each method is either a command that performs some action, or a query to retrieve information. None of my methods have unexpected side effects. This makes the program simpler to understand.

Revision as of 02:36, 1 October 2008

Contents

Intro

For my design study I have chosen to create a poker simulator of sorts. I have started this project specifically for the 427 design study. Cards are dealt to a number of players around a table, the hand played through and the winner of the hand determined. The game can continue for an arbitrary number of hands. Currently the user controls each player in turn and can see all the Player's cards, so it is more of a simulator than a full blown poker game. Ideally if I had more time it would be nice to make it multiplayer with some client server functionality, or to implement some AI to play against.

I have implemented the Texas Hold 'em variant of poker (see A Brief Introduction to Poker below), but have left the design open for the implementation of other poker variants and rules. To illustrate this there are classes for the Draw variant of poker and the Wildcard rule in the design, but without full functionality. I have also implemented the "cash game" type of poker, where the game consists of one table and players can come or go as they wish. Alternately a poker game can be "tournament" style, which consists of one or more tables and players aim to win the tournament by taking other players chips and knocking them out until there is one player left. As players are knocked out the tables are balanced and joined together until there is one table; the "final table." I have created a Tournament class to illustrate this possibility, but there is currently no functionality for it.


Haven't had time to implement: There are also different bet limits in poker, I am not sure how this might effect my design if they are to be implemented in the future. The most common is No Limit where you can bet however much you like at any time, even your entire chip stack. There are also Limit games where bets and raises are limited to a fixed amount, and Pot Limit where bets are limited to the size of the pot.


A Brief Introduction to Poker

Poker is a card game where players make bets on the value of the cards in their hand, which accumulate in a central pot. The winner is the player with the highest ranked hand, or the last player to remain in the hand after all other players have folded. (Another word for folded is mucked). In order to stay in contention for the pot a player must match the bet of every other player in the pot. If a player doesn't wish to match a bet, they may fold their hand at any time, making them out of contention for the pot. Play is commenced clockwise around the table from the left of the player in the dealer position. After the hand is completed, the player to the left of the dealer assumes the dealer position for the next hand.

The poker variant implemented in my design is called Texas Hold 'em, and is the most popular form of poker today. In a game of Texas Hold 'em, each player is dealt only two cards face down (the hole cards). A total of five community cards are dealt face up on the table, and each player must make the best possible five card hand using any combination of community cards and their own cards. The first betting round is performed after the players receive their two hole cards. Once this is complete, three community cards are dealt (the flop), and another betting round performed. After this a fourth community card is dealt (the turn), followed by a betting round, then the last community card is dealt (the river), followed by the last betting round.

Betting rounds consist of players either checking (opting to bet nothing if there is no current bet to match), calling an existing bet by putting out the equivalent amount of chips, or raising an existing bet by putting out a greater amount of chips than the existing bet. The winner of the hand wins the money in the pot, or in the case of a draw (two or more players having a hand of equivalent rank) the money is split between the players.

For more information I will refer the reader to Wikipedia. (I love Wikipedia, Wiki Wiki Pedia).

Source Code

My source code is written in Java and can be found here: media:pokerSimulatorCode.tgz

To run it from a shell, type: java PokerSimulator [-gameType] [-variant]

-gameType is either Cash Game (-c) or Tournament (-t). Currently only (-c) is implemented.

-variant specifies the poker variant. Currently only (-h) for HoldEm is implemented.

Note that the GUI only allows for 4 Players. This was to simplify the GUI design. The actual program design allows for an arbitrary number of Players at a Table. (However there are usually 9 or 10 Players max at a Table in real life).

Class Diagram

Class Diagram

Here is my entire class diagram in all its glory. Note that I have excluded the GUI from the class diagram, as I had to hack it together very quickly before this project was due. Subsequently the GUI is badly designed and is tightly coupled with the rest of the design. In my class diagram $ means static, and # means protected. I have not differentiated between composition and aggregation relationships.

Design Breakdown

In this section I will present the different parts of my design and explain what they do.

PokerGame

PokerGame.png

The PokerGame class is a Singleton. I required it to be subclassed to TournamentGame and CashGame, so have modified the Singleton pattern to allow for this. Note that TournamentGame has not been fully implemented. PokerGame contains the main() method of my program, which takes in command line arguments to determine the PokerGame subclass to create, and the style of poker (eg HoldEm or Draw). The getPokerGame() method uses lazy initialization to create the Singleton PokerGame instance, and is first called in main().

PokerGame stores information about the blinds and ante amounts for the game, and also stores an instance of the Abstract Factory PokerStyleFactory. Subclasses override the init() method which uses the PokerStyleFactory to initialize a game. The run() method is also overridden by subclasses to start the game, and is called by the GUI creating a new Thread.

PokerStyleFactory

The PokerStyleFactory class is reasonably straightforward. It is used to create Players, Dealers, and Tables. The Table class is not abstract which is unusual for the Abstract Factory pattern, but I have included the Table creation as part of the entire creation process because the type of Dealer and type of Players that sit at a Table must be equivalent. Players do not sit directly at a Table, but instead each Player has one PlayerHand which is used to represent them at the Table. So when a Player is created, an equivalent PlayerHand is created for that Player and it is the PlayerHand that is added to the Table. When a Dealer is created they are added to the associated Table and vice versa.

The problem with using an Abstract Factory is that is is hard to later add more products (products are Player, Dealer, Table). If a new product was to be added, the abstract factory base class and all existing subclasses will have to be modified - this is obviously not ideal. However I feel that the benefits of using Abstract Factory outweigh this issue, as I think it is unlikely that more than the existing products will have to be added later.

An alternative implementation would use the Prototype pattern to create products which gives more flexibility if more products need to be added. A single "make()" method would be used to create the products. However this is not as safe as it requires down-casting in Java, which I did not want to use, especially since the added flexibility is not really needed.

PlayerHand

PlayerHand.png

Each PlayerHand represents a Player at a Table. PlayerHands consist of The Dealer deals cards to each PlayerHand. At the end of a hand, the Dealer collects all active PlayerHands (that haven't been mucked), and determines the winner. To determine the winner, PlayerHands are compared to each other, this is achieved by the PlayerHands implementing compareTo(). Within the compareTo() method, a PlayerHand determines its hand rank by sending its Cards to the CardAnalyser class. This returns a RankedHand object containing the rank (0 - 8), and the five cards that make the hand. Note that in Texas Hold 'em a Player has seven possible cards with which to make a five card hand, so CardAnalyser accepts more than five cards.

RankedHands also implement compareTo(), so the PlayerHand compareTo() method effectively returns the result of their equivalent RankedHands compareTo(). RankedHands need to be compared in different ways depending on what rank they are, hence I have used a State pattern to model this. This is because some hand ranks use all five cards (high card, straight, flush, and straight flush) - FullHand subclass, some hand ranks use a subset of the five cards (pair, 3 of a kind, 4 of a kind) - PartialHand subclass, and some hand ranks are combinations of lower hand ranks (two pair, full house) - CombinedHand subclass. For example when a CombinedHand is being compared to a CombinedHand of the same rank, the first part of the hand may be the same for both, but the second part of the hand will determine which is better. When a FullHand is being compared to another FullHand, the hand with the higher cards is better, even if the first four cards of each are equivalent.

Note that I have provided a WildCardAnalyser subclass of CardAnalyer (visible in the overall class diagram above) as an example of how my design can be extended, but it is not implemented it in my code. "Wildcard" is a rule variation where specific cards are "wild" which means they can be designated as any card that the player chooses in making a hand.

Dealer

Dealer.png

A Dealer has one CardDeck. A CardDeck is composed of 52 unique Cards. There are four different card suits, and card values range from 2 - 14, Ace = 14, King = 13, Queen = 12, Jack = 11. Cards are compared on their value using the compareTo() method. There is no comparison on suit - ie a five of hearts is equivalent to a 5 of spades.

Each Dealer belongs to one Table, and each Table has one Dealer.

The startHand() method of Dealer first gets a new, full CardDeck and shuffles it using the Collections.shuffle() method (this way we don't have to bother recollecting all the Cards from the last round, we simply clear them). Then the blinds and antes are collected from the relevant Players by calling getBlinds() and getAntes(). Finally, cards are dealt one at a time to each PlayerHand in correct deal order (starting from the left of the button). The startHand() method is overridden by HoldEmDealer to specify that only two cards are dealt to each PlayerHand. The default implementation in Dealer deals five cards to each PlayerHand.

When a hand is complete, the endHand() method is called. This is where the Dealer calls determineWinners(), and resets the Table so it is ready for the next hand.

Note that the Dealer does not actually play at the Table, but there is a button position which specifies the Player position at the Table from which the Cards are dealt from. The button position is incremented after each hand.

TablePot

TablePot.png

This part of the design is probably the most complicated and the part that I am least happy about. I have implemented it last and have run out of time to design it better. In saying that dealing with multiple pots in a poker game can be quite complicated in real life, even for professional dealers at the casino! With this in mind, please forgive me as I continue...

A Table has a List of TablePots. A TablePot represents a pot in poker which is the combination of all bets for that hand, over the various betting rounds. Most hands usually only require one pot, but when a Player has bet all their chips into the pot and there are two or more other Players remaining in the pot (with more chips), these other Players may wish to continue betting against each other. In this case the Player who is "all in" can only win the pot which contains their all in bet plus the matched bets of the other Players. When the other Players have matched the all in and begin betting more, a side pot is created for them. A side pot can only be won by the Players who have contributed to it, even if the original all in Player has a better hand than a side pot Player.

Depending on how many Players go all in, there can be multiple side pots, and it gets very confusing. I have implemented most of the side pot functionality but haven't had time to complete it, and Dealers currently cannot determine individual winners of multiple pots - however with the current design implementing this should be relatively easy.

A PotContribution is another name for a bet. A bet is forced if it is a blind or ante bet, in other words the Player has been forced to make the bet by the Dealer, and they still get a chance to make a move before the round is complete. When a Player makes a bet, it is added to any existing bets they have made in that betting round. Bets from previous betting rounds are set to complete. The Dealer determines that the round is complete by calling contributionsMatched() in the TablePot class, which checks that all Players have had a turn (forced is false), or have already folded/mucked, and that all bets have been equaled (or a Player is all in with a smaller bet). Whew.

At the end of a betting round the Dealer sets all contributions for that round to complete, so that new PotContributions can be added for the next betting round. If at the end of a round a player is all in and a sidepot needs to be created, a new TablePot is created with the relevant bets and added to the pots List in Table. If a sidepot is created, the old TablePot is set to closed indicating that no more bets can be accepted. The getActivePot() method in Table returns the last TablePot in the pots List, which should always be open for bets.

Trust me, I'm wishing I hadn't implemented that stuff too.

Critique

Some classes seem to exhibit the Large class smell, namely Table and TablePot. However it is important to note that they are large because of the number methods in them and not the number of fields, if they had too many fields I might have to apply the Extract Class refactoring method. The large number of methods in these classes are required partly because the GUI needed them and partly to satisfy the Law of demeter.

I do not like the relationship between Table, TablePot, PotContribution, Player and PlayerHand. There seems to be some duplicated data in that a Table contains a direct reference to PlayerHands, but also contains a reference to the same PlayerHands though the TablePot - PotContribution - Player - PlayerHand. This problem has been caused by the TablePot and PotContribution classes which have been tacked on last to add the betting/calling/folding functionality. If I had more time on this project my next step would be to refactor these classes, possibly adding a PlayerAction class to deal with betting, calling and folding.

Design Patterns

Observer

I have used the Observer pattern to model the relationship between the Dealer and the TablePot. The Dealer needs to know when the TablePot is modified so he can determine if the betting round is complete or not. Note that the Abstract Subject class is the Java Observable class which implements this pattern, and Dealer implements the Java Observer interface by defining an update() method.

Abstract Subject Observable
Concrete Subject TablePot
Observer Dealer
Concrete Observer HoldEmDealer

Template Method

The analyseCards() method in CardAnalyser is a Template Method. It defines the structure of the algorithm that determines the hand rank of a set of cards. The primitive operations correspond to a check for each possible rank, and subclasses of CardAnalyser can define these operations differently for different rule variations. The algorithms structure is set in the base class because the hand ranks must be checked in order from worst to best so that the best hand rank is returned. (ie a flush might also contain a pair, but it is the flush rank that counts).

Abstract Class CardAnalyser
Concrete Template Method analyseCards()
Abstract primitiveOperation1 checkHighCard()
Abstract primitiveOperation2 checkPair()
Abstract primitiveOperation3 checkTwoPair()
Abstract primitiveOperation4 checkTrips()
Abstract primitiveOperation5 checkStraight()
Abstract primitiveOperation6 checkFlush()
Abstract primitiveOperation7 checkFullHouse()
Abstract primitiveOperation8 checkFourOfKind()
Abstract primitiveOperation9 checkStraightFlush()
Concrete Class1 RegularCardAnalyser
Concrete Class2 WildCardAnalyser

State

The RankedHand class is a State pattern, with PlayerHand as the context. The behaviour of the compareTo() method varies depending on the type of RankedHand, as it is necessary to compare different types of hands in different ways. PlayerHand does not actually contain a RankedHand, but instead creates a new RankedHand and uses it within it's own compareTo() method to determine if another PlayerHand is better.

Context PlayerHand
Abstract State RankedHand
Abstract handle() compareTo()
Concrete State1 FullHand
Concrete State2 PartialHand
Concrete State3 CombinedHand

Abstract Factory

The PokerStyleFactory class provides an Abstract Factory pattern. It is used for creating the related objects for different poker styles - Dealer, Player and Table. I used it to enforce the constraint that only objects of one style of poker may be used at one time, so that, for example, a HoldEmDealer doesn't end up sitting at a Table with DrawPlayers. It is also useful in that it allows for several different styles of poker to be implemented.

Abstract Factory PokerStyleFactory
Concrete Factory1 HoldEmFactory
Concrete Factory2 DrawFactory
Abstract Product1 Dealer
Abstract Product2 Player
Concrete Product Table
Client PokerGame

Singleton

PokerGame is a Singleton class, and is used to ensure that only one instance of a PokerGame may exist. I have modified the Singleton pattern to allow PokerGame to be subclassed to a CashGame or a TournamentGame (not yet implemented). This is dicussed in more depth above in "Design Breakdown"

Iterator

I have used Iterators throughout RegularCardAnalyser in order to iterate though Sets of Cards.

Principles Followed

Avoid concrete base classes - All of my base classes are abstract, so Riel would (hopefully) be happy about that.

Program to the interface, not to the implementation

I have adhered to the 427 design standard on Getters and setters - All fields are private, getters and setters are only written as needed (in most cases they are in my program), and getters and setters are always used to access a field externally.

Command query separation / Avoid side effects - each method is either a command that performs some action, or a query to retrieve information. None of my methods have unexpected side effects. This makes the program simpler to understand.

Principles Violated

Tell, don't ask - I have used getters for a lot of fields.

I haven't always used Iterator to iterate though Collections - found it simpler to write for loops to iterate though Lists.

Eliminate irrelevant classes - I have broken this rule with the HoldEmPlayer class, which adds no further functionality to the Player class. The alternative was to make the Player class concrete and use it for creating HoldEmPlayers, but I favoured the Avoid concrete base classes principle instead. I felt that it was more important to make the distinction between HoldEmPlayers and DrawPlayers, to enforce the constraints introduced by the Abstract Factory pattern (discussed in "Design Patterns" earlier).

Beware singletons - I have one Singleton class: PokerGame. It has subclasses and this required making the constructors protected instead of private, so unfortunately in Java it is possible for more than one PokerGame to be created by another class in the same package. This could be problematic, but hopefully is not too big a deal as it is fairly clear that it should remain a Singleton.

Beware value switches - I thought about how the "suit" attribute of a Card breaks this heuristic, in that when checking for a flush hand, the suit value will be used in case analysis. However I thought it was a bit extreme to decompose Card into an inheritance hierarchy of different suits, because there will only ever be 4 suits to test - it is highly unlikely that further extension will be required.

Parting Comments

Personal tools