package edu.villanova.studs.poker.calcengine;
import com.javaflair.pokerprophesier.api.adapter.PokerProphesierAdapter;
import com.javaflair.pokerprophesier.api.card.Card;
import com.javaflair.pokerprophesier.api.card.CommunityCards;
import com.javaflair.pokerprophesier.api.card.Hand;
import com.javaflair.pokerprophesier.api.card.HoleCards;
import com.javaflair.pokerprophesier.api.exception.SimulatorException;
import com.javaflair.pokerprophesier.api.helper.MyGameStatsHelper;
import com.javaflair.pokerprophesier.api.helper.MyHandHelper;
import com.javaflair.pokerprophesier.api.helper.MyHandStatsHelper;
import com.javaflair.pokerprophesier.api.helper.MyOutsHelper;
import com.javaflair.pokerprophesier.api.helper.OppHandStatsHelper;
import com.javaflair.pokerprophesier.api.helper.PlayerGameStatsHelper;
import edu.villanova.studs.poker.exceptions.CalcEngineException;
import edu.villanova.studs.poker.exceptions.PokerMessages;
import edu.villanova.studs.poker.transport.PlayerResults;
import edu.villanova.studs.poker.transport.TableDataIntf;
import edu.villanova.studs.poker.utils.TransportUtils;
import java.util.Hashtable;
/**
* This is the abstract parent class for the family of classes that implement
* the calc engine interface using the Poker Prophesier hand calculating API.
* The API is specific to Texas Hold'em poker games, meaning implementations
* should use TableDataImpl class extensions that support community cards.
* It implements the public method to get calculation results required by the
* interface definition, but leaves the setup methods abstract. This allows
* concrete extensions to only have to implement logic to setup the game data
* while leaving the result generation logic unchanged.
* @unit CalcEngineImplProph.java
* @package edu.villanova.studs.poker.calcengine
* @author Christopher Salembier
* @date 22 Oct 2008
*/
public abstract class CalcEngineImplProph implements CalcEngineIntf {
/*------------------------------------------------------------------------*/
/*---------------------------PRIVATE CLASS MEMBERS------------------------*/
/*------------------------------------------------------------------------*/
//Private members used by the calculation engine to generate results
//These do not need to be visible to child classes as their implementations
//only deal with setup of hand data, not processing...
//Multiplier for storing percent values: use one to store as a decimal 0 - 1
private final static float PCT_MULT = 1;
//This is the API to the Poker Prophesier calculation engine
private PokerProphesierAdapter adapter;
//Helper classes for generating probabilties and statistics. These are
//never directly instantiated, but rather references are set via the adapter
private MyHandHelper myHandHelper;
private MyOutsHelper myOutsHelper;
private MyHandStatsHelper myHandStatsHelper;
private OppHandStatsHelper oppHandStatsHelper;
private MyGameStatsHelper myGameStatsHelper;
private PlayerGameStatsHelper playerGameStatsHelper;
//Members used to return hand results to clients...
//Holds the results for one player as they are being calculated
private PlayerResults currResult;
//An array of all player results to be returned to the client
private PlayerResults[] playerResults;
//The protected members are populated by concrete implementations. They
//represent the cards players, hand & table information needed by the calc
//engine to generate results...
//Array of hole cards each player is holding (each Hole Cards object
//contains 2 cards and a folded flag). Positions in this array can be null.
protected HoleCards[] playerCards;
//List of community cards on the table - can contains 0, 3, 4 or 5 cards
protected CommunityCards communityCards;
//Hash of other options that can be set through the UI and processed using
//the setOtherOptions() method implementation
protected Hashtable<String, Object> options;
//Stores the enumerated street representation (not the # of actual cards)
protected int cardsDealt;
//# of active players in the hand (not folded and have hole cards)
protected int activePlayers;
//Total # of players at the table, dont have to have cards or be in the hand
protected int totalPlayers;
//# of times the API runs its internal simulations to get the hand results
protected int simulations;
/*------------------------------------------------------------------------*/
/*----------------------------METHOD CONSTRUCTORS-------------------------*/
/*------------------------------------------------------------------------*/
/**
* General CalcEngineImplProph constructor. It sets default values for
* numeric fields, but does not instantiate the dynamic members. This
* action is left to setGameData() & setOtherOptions() implementations
* because the setup method may vary. Child classes may override as needed.
*/
public CalcEngineImplProph() {
cardsDealt = 0;
activePlayers = 0;
totalPlayers = 0;
simulations = 0;
}
/*------------------------------------------------------------------------*/
/*----------------------------PUBLIC CLASS METHODS------------------------*/
/*------------------------------------------------------------------------*/
/**
* Declare this method abstract, leaving logical decisions on how to
* populate Hole & Community information to implementing classes.
*/
public abstract void setGameData( TableDataIntf td ) throws CalcEngineException;
/**
* Declare this method abstract, leaving logical decisions on how to
* set & process other API options to implementing classes.
*/
public abstract void setOtherOptions( Hashtable<String, Object> opts );
/**
* This implementation of the getCalculationResults() method will be the
* same for all child classes. It uses the protected members, populated
* using implementations of the abstract set methods, to generate hand
* calculations and return the results using a standard PlayerResults
* array. It drives the result generation using a series of helper methods.
* @return an array of expected results & stats for each player in the hand
* @throws CalcEngineException if there is any error thrown by the calc
* engine. The exception handling block builds an informative
* error message with a linked cause and re-throws the error for
* the calling method to handle.
*/
public PlayerResults[] getCalculationResults( ) throws CalcEngineException {
//Stores a message of the current action for informative error reporting
String errMsg = "";
//Catch any processing errors here and re-throw as CalcEngineExceptions
try {
//Start by setting up the Prophisier adapter to run the simulations
errMsg = PokerMessages.CALC_ADAPTER;
setupProphesierAdapter();
//If there is more than one player then run the multi-player
//simulation to generate perfect hand result data.
errMsg = PokerMessages.CALC_MULTI;
if ( activePlayers > 1 )
runMultiPlayerSimulation();
//Run simulations for each individual player to generate imperfect
//hand results
errMsg = PokerMessages.CALC_INDIV;
runSinglePlayerSimulations();
} catch ( SimulatorException e ) {
e.printStackTrace(System.out);
//There was a processing error somewhere. Build a new exception
//with a message indicating where the failure occurred and the root
//cause. Re-throw the error for the calling method to handle
throw new CalcEngineException( PokerMessages.CALC_FAILED,
new Object[]
{ PokerMessages.TRANSLATE_ARG,
errMsg}, e );
}
//No errors, return the player results array
return playerResults;
}
/*------------------------------------------------------------------------*/
/*-------------------------PROTECTED CLASS METHODS------------------------*/
/*------------------------------------------------------------------------*/
/**
* This method is used to generate a new PlayerResults instance to hold
* calculation results for each player in the hand. It is used by the
* private method runSinglePlayerSimulations(), but is declared abstract
* so individual implementations can configure the results object. This is
* because even though PlayerResults is concrete it does contain a method
* for dynamically setting other data that may not pertain directly to
* hand calculations. This is useful in testing or random hand generation.
* @param playerID is a Unique number to assign the new player results
* @return a new PlayerResults instance with its other values hash table
* populated according to the needs of the implementation.
*/
protected abstract PlayerResults getNewPlayerResult( int playerID );
/*------------------------------------------------------------------------*/
/*---------------------------PRIVATE CLASS METHODS------------------------*/
/*------------------------------------------------------------------------*/
/**
* This method is used by the public getCalculationResults() method to setup
* the calculation engine. It creates an adapter to the simulator engine
* and sets the configurable options. The concrete implementations will
* determine how these options are set.
*/
private void setupProphesierAdapter() {
//Create an adapter to communicate with the hand simulator engine
adapter = new PokerProphesierAdapter();
//Set the # of simulations to run when generating hand results. How
//this value gets set depends on the concrete implementation
adapter.setNumSimulations( simulations );
//Set other options used by the adapter
//TODO: Pass these in through the options hash
adapter.setOppHoleCardsRealistic( true );
adapter.setOppProbMyHandSensitive( true );
adapter.setMyOutsHoleCardSensitive( true );
}
/**
* This method is called to generate the perfect hand result data if there
* is more than 1 active player in the hand. After running the player
* simulations is gets a playerGameStatsHelper reference to read the results
* @throws SimulatorException if there is an error thrown by the adapter.
* Most likely because of invalid player or community configurations
*/
private void runMultiPlayerSimulation() throws SimulatorException {
// Run the simulator
adapter.runPlayerSimulations( playerCards, communityCards, cardsDealt );
// Get the PlayerGameStatsHelper
playerGameStatsHelper = adapter.getPlayerGameStatsHelper();
}
/**
* This is the driving method for generating imperfect calculations for
* each player in the hand. It is responsible for generating a Player
* Results object for each player in the hand and building the array to
* return from the getCalculationResults() method.
* @throws SimulatorException if there is an error thrown by the adapter.
* Most likely because of invalid player or community configurations
*/
private void runSinglePlayerSimulations() throws SimulatorException {
HoleCards cards;
//Create the results array. All players have result records created
//even if they are inactive or folded, so use total, not active Players
playerResults = new PlayerResults[ totalPlayers ];
//Process each player in a loop. Create a results object to store the
//current results, check if the player is inactive or folded, then if
//they are active run the single player simulation using just their
//HoleCards. Finally, add the current player result to the results
//array, regardless of the player state.
for ( int ii = 0; ii < totalPlayers; ii++ ) {
//Create a new Player Results object to store info for the current player
currResult = getNewPlayerResult( ii+1 );
//Get the next set of hole cards in the player array
cards = playerCards[ii];
//If there were multiple active players in the hand set the perfect
//win percent using the player game stats helper
if ( activePlayers > 1 )
currResult.setPerfectWinPct( playerGameStatsHelper.getPlayerProb(ii) * PCT_MULT );
//Make sure the player is active before calculating their results
if ( cards == null )
//The player was never assigned cards, they are just a place
//holder for calculating player vs player results
currResult.setPlayerState( TransportUtils.PLAYER_EMPTY );
else if ( cards.isFolded() )
//The player was assigned known cards, but folded. This
//changes the perfect win % calculations, but it treated
//the same as an empty player for imperfect calculations
currResult.setPlayerState( TransportUtils.PLAYER_FOLDED );
else
//Player is active with cards, calculate imperfect data
runCurrentPlayerSimulation( cards );
//Add the current player info to the reuslts array
playerResults[ii] = currResult;
}
}
/**
* This is the driving method for generating the imperfect hand calculations
* for a single player. It uses the adapter to run the player simulations
* then uses a series of helper methods to populate the current PlayerResults
* @param cards are the current players hole cards - they cannot be empty
* @throws SimulatorException if there is an error thrown by the adapter.
* Most likely because of invalid card or community configurations
*/
private void runCurrentPlayerSimulation( HoleCards cards ) throws SimulatorException {
//Run the simulation for the current player using imperfect info
adapter.runMySimulations( cards, communityCards, totalPlayers, cardsDealt );
//Set all the data for imperfect calculations...
//Set the know info about the cards & hand
setMyHandData();
//Set data about outs and potential opponent hands
setAllHandData();
//Set the imperfect win/tie/loss percents
setImperfectStats();
}
/**
* This method is used to set the information that is known about the
* current players hand. That is the cards it contains and its strength.
*/
private void setMyHandData() {
//Get a hand helper reference
myHandHelper = adapter.getMyHandHelper();
//Set the player's poker hand (pair, flush, full house, etc)
currResult.setCurrentHand( myHandHelper.getHand().getHandRank() );
//Get the array of cards that make up the players hand
Card[] cards = myHandHelper.getHand().getCards();
//Since the getHand() method does not have a toString() implementatin
//loop through the card array and build a single comma delimited string
String strCards = "" + cards[0];
for ( int jj = 1; jj < cards.length; jj++ )
strCards += ", " + cards[jj];
//Set the player's card string
currResult.setCurrentCards( strCards );
}
/**
* This method is used to set information about the chances of the player
* making particular poker hands and that an opponent may be holding a
* better hand already. Since all this info can be set for each potential
* poker hand (High Card through Strait Flush) it is all set in one loop.*
*/
private void setAllHandData() {
//Local processing variables to store results from helper methods
Card[] cards;
float myPct;
float oppPct;
//Get the adapter helpers needed to get outs and player stats
myOutsHelper = adapter.getMyOutsHelper();
myHandStatsHelper = adapter.getMyHandStatsHelper();
oppHandStatsHelper = adapter.getOppHandStatsHelper();
//Process each potential poker hand by looping through the enumerated
//values that represent each hand/
for ( int ii = Hand.HIGH_CARD; ii <= Hand.STRAIGHT_FLUSH; ii++ ) {
//Get an array of cards that can make the current hand
cards = getOutsArray( ii );
//Get the % chance that the current hand will be made by the river
myPct = getMyHandStats( ii ) * PCT_MULT;
//Get the % chance that an opponent is holding the current hand
oppPct = getOppHandStats( ii ) * PCT_MULT;
//Check for outs to make the current hand
if ( cards.length > 0 )
//There are outs, add each card individually
for ( int jj = 0; jj < cards.length; jj++ )
currResult.setOutCard( ii, cards[jj].toString() );
//Check if there is any chance of making the current hand
if ( myPct > 0 )
//Set the % to make the hand
currResult.setOutPct( ii, myPct );
//Check if there is any chance an opponent already has the hand
if ( oppPct > 0 )
//Set the % that an opponent is holding the current hand
currResult.setOppBeatPct( ii, oppPct );
}
//Null outs helper means pre-flop or river
if ( myOutsHelper != null )
//Set the total # of calculated out cards
currResult.setImperfectOuts( myOutsHelper.getTotalNumOuts() );
//Null hand stats helper means the river has been dealt (no chance to improve)
if ( myHandStatsHelper != null )
//Set the total % chance the hand will improve by the river
currResult.setImperfectImprovePct( myHandStatsHelper.getTotalProb() * PCT_MULT );
//The opponent hand help is always created, regardless of street, but
//may be null if there are no opponents
if ( oppHandStatsHelper != null )
//Set the total % chance that an opponent will hold a better hand
currResult.setOppBestHandPct( oppHandStatsHelper.getTotalProb() * PCT_MULT );
}
/**
* This method is used by setAllHandData() to get the cards that can make
* given poker hand for the current player.
* @param outType an enumerated representation of a poker hand
* @return an array of cards that can hit to make the specified hand
*/
private Card[] getOutsArray( int outType ) {
//The outs helper will be null pre-flop and on the river
if ( myOutsHelper == null )
//Out cards cannot be calculated, just return an empty array
return new Card[0];
//On the Flop or Turn, get the out card array for the specified hand type
switch ( outType ) {
case Hand.HIGH_CARD: return myOutsHelper.getHighCardOuts();
case Hand.PAIR: return myOutsHelper.getPairOuts();
case Hand.TWO_PAIRS: return myOutsHelper.getTwoPairsOuts();
case Hand.THREE_OF_A_KIND: return myOutsHelper.getThreeOfAKindOuts();
case Hand.STRAIGHT: return myOutsHelper.getStraightOuts();
case Hand.FLUSH: return myOutsHelper.getFlushOuts();
case Hand.FULL_HOUSE: return myOutsHelper.getFullHouseOuts();
case Hand.FOUR_OF_A_KIND: return myOutsHelper.getFourOfAKindOuts();
case Hand.STRAIGHT_FLUSH: return myOutsHelper.getStraightFlushOuts();
default: return new Card[0];
}
}
/**
* This method is used by setAllHandData() to get the % chances that any
* given poker hand will be made by the for the current player.
* @param outType an enumerated representation of a poker hand
* @return the % chance that the specified hand will be made by the river
*/
private float getMyHandStats( int outType ) {
//The outs helper will be null and on the river
if ( myHandStatsHelper == null )
//Alaways a 0% chance to improve on the river
return 0;
//Get the percent chance to make the specified hand type
switch ( outType ) {
case Hand.HIGH_CARD: return myHandStatsHelper.getHighCardProb();
case Hand.PAIR: return myHandStatsHelper.getPairProb();
case Hand.TWO_PAIRS: return myHandStatsHelper.getTwoPairsProb();
case Hand.THREE_OF_A_KIND: return myHandStatsHelper.getThreeOfAKindProb();
case Hand.STRAIGHT: return myHandStatsHelper.getStraightProb();
case Hand.FLUSH: return myHandStatsHelper.getFlushProb();
case Hand.FULL_HOUSE: return myHandStatsHelper.getFullHouseProb();
case Hand.FOUR_OF_A_KIND: return myHandStatsHelper.getFourOfAKindProb();
case Hand.STRAIGHT_FLUSH: return myHandStatsHelper.getStraightFlushProb();
default: return 0;
}
}
/**
* This method is used by setAllHandData() to get the % chances that an
* opponent may already be holding any given poker hand.
* @param outType an enumerated representation of a poker hand
* @return the % chance that an opponent is already holding the specified hand
*/
private float getOppHandStats( int outType ) {
//If the opponent outs helper is null just return 0% for the hand
if ( oppHandStatsHelper == null )
return 0;
//Get the percent chance an opponent is already holding the specified hand
switch ( outType ) {
case Hand.HIGH_CARD: return oppHandStatsHelper.getHighCardProb();
case Hand.PAIR: return oppHandStatsHelper.getPairProb();
case Hand.TWO_PAIRS: return oppHandStatsHelper.getTwoPairsProb();
case Hand.THREE_OF_A_KIND: return oppHandStatsHelper.getThreeOfAKindProb();
case Hand.STRAIGHT: return oppHandStatsHelper.getStraightProb();
case Hand.FLUSH: return oppHandStatsHelper.getFlushProb();
case Hand.FULL_HOUSE: return oppHandStatsHelper.getFullHouseProb();
case Hand.FOUR_OF_A_KIND: return oppHandStatsHelper.getFourOfAKindProb();
case Hand.STRAIGHT_FLUSH: return oppHandStatsHelper.getStraightFlushProb();
default: return 0;
}
}
/**
* This method is used to set imperfect win/loss/tie percents. Because all
* the data is based on imperfect information, these #s may not be in line
* with the improve percents or opponent best hand chances.
*/
private void setImperfectStats() {
//Get a stats helper reference to get the imperfect probabilities
myGameStatsHelper = adapter.getMyGameStatsHelper();
//Get the imperfect win probability
currResult.setImperfectWinPct( myGameStatsHelper.getWinProb() * PCT_MULT );
//Get the imperfect tie probability
currResult.setImperfectTiePct( myGameStatsHelper.getTieProb() * PCT_MULT );
//Get the imperfect loss probability
currResult.setImperfectLosePct( myGameStatsHelper.getLoseProb() * PCT_MULT );
}
}