package bots.demobots;
import java.util.ArrayList;
import util.Utils;
import com.biotools.meerkat.Action;
import com.biotools.meerkat.Card;
import com.biotools.meerkat.GameInfo;
import com.biotools.meerkat.Hand;
import com.biotools.meerkat.Holdem;
import com.biotools.meerkat.Player;
import com.biotools.meerkat.util.Preferences;
import common.handeval.klaatu.PartialStageFastEval;
/**
* A Simple example bot based on the 'SimpleBot' embedded with the Meerkat-API.<br>
* This bot doesn't make use of the Meerkat-HandEvaluator-class is this one doesn't work within the Testbet
* (Randomizer-class is missing in the meerkat.jar)<br>
* Furthermore some changes were added to also place proper No-Limit-Bets and not
* to fall into preflop reraise wars with other bots.
* <br>
* Some tests show that SimpleBot beats default XenBot from Poker-Academy with 40bb/100 but loses agains
* the Sklansky2-Bot by about 50bb/100. This is interesting as XenBot wins angainst Sklansky-Bot<br>
* So every bot can exploit some behaviour of another - and it shows how important it is
* to test against different types of bots, to find one most robust against all other strategies.
* (This doesn't necessarily imply SimpleBot is 'better' (against real humans), as this bot
* is very predictable and both other bots have much more variation in their plays.
*
*/
public class SimpleBot implements Player {
private int ourSeat; // our seat for the current hand
private Card c1, c2; // our hole cards
private GameInfo gi; // general game information
private Preferences prefs; // the configuration options for this bot
public SimpleBot() {
}
/**
* An event called to tell us our hole cards and seat number
* @param c1 your first hole card
* @param c2 your second hole card
* @param seat your seat number at the table
*/
public void holeCards(Card c1, Card c2, int seat) {
this.c1 = c1;
this.c2 = c2;
this.ourSeat = seat;
}
/**
* Requests an Action from the player
* Called when it is the Player's turn to act.
*/
public Action getAction() {
if (gi.isPreFlop()) {
return preFlopAction();
} else {
return postFlopAction();
}
}
/**
* Get the current settings for this bot.
*/
public Preferences getPreferences() {
return prefs;
}
/**
* Load the current settings for this bot.
*/
public void init(Preferences playerPrefs) {
this.prefs = playerPrefs;
}
/**
* @return true if debug mode is on.
*/
public boolean getDebug() {
return prefs.getBooleanPreference("DEBUG", false);
}
/**
* print a debug statement.
*/
public void debug(String str) {
if (getDebug()) {
System.out.println(str);
}
}
/**
* print a debug statement with no end of line character
*/
public void debugb(String str) {
if (getDebug()) {
System.out.print(str);
}
}
/**
* A new betting round has started.
*/
public void stageEvent(int stage) {
}
/**
* A showdown has occurred.
* @param pos the position of the player showing
* @param c1 the first hole card shown
* @param c2 the second hole card shown
*/
public void showdownEvent(int seat, Card c1, Card c2) {
}
/**
* A new game has been started.
* @param gi the game stat information
*/
public void gameStartEvent(GameInfo gInfo) {
this.gi = gInfo;
}
/**
* An event sent when all players are being dealt their hole cards
*/
public void dealHoleCardsEvent() {
}
/**
* An action has been observed.
*/
public void actionEvent(int pos, Action act) {
}
/**
* The game info state has been updated
* Called after an action event has been fully processed
*/
public void gameStateChanged() {
}
/**
* The hand is now over.
*/
public void gameOverEvent() {
}
/**
* A player at pos has won amount with the hand handName
*/
public void winEvent(int pos, double amount, String handName) {
}
/**
* Decide what to do for a pre-flop action
*
* Uses a really simple hand selection, as a silly example.
*/
private Action preFlopAction() {
debug(gi.getPlayerName(ourSeat) + " Hand: [" + c1.toString() + "-" + c2.toString() + "] ");
double toCall = gi.getAmountToCall(ourSeat);
// play all pocket-pairs
if (c1.getRank() == c2.getRank()) {
if ((c1.getRank() >= Card.TEN || c1.getRank() == Card.TWO) && gi.getNumRaises() < 2) {
return Action.raiseAction(gi);
}
if (gi.getNumRaises() < 3) {
return Action.callAction(toCall);
}
}
// play all cards where both cards are bigger than Tens
// and raise if they are suited
if (c1.getRank() >= Card.TEN && c2.getRank() >= Card.TEN) {
if (c1.getSuit() == c2.getSuit() && gi.getNumRaises() < 2) {
return Action.raiseAction(gi);
}
if (gi.getNumRaises() < 3) {
return Action.callAction(toCall);
}
}
// play all suited connectors
if ((c1.getSuit() == c2.getSuit())) {
if (Math.abs(c1.getRank() - c2.getRank()) == 1) {
return Action.callAction(toCall);
}
// raise A2 suited
if ((c1.getRank() == Card.ACE && c2.getRank() == Card.TWO) || (c2.getRank() == Card.ACE && c1.getRank() == Card.TWO)) {
if (gi.getNumRaises() == 0) {
return Action.raiseAction(gi);
}
if (gi.getNumRaises() < 1) {
return Action.callAction(gi);
}
}
// call any suited ace
if ((c1.getRank() == Card.ACE || c2.getRank() == Card.ACE)) {
return Action.callAction(toCall);
}
}
// play anything 5% of the time
if (gi.getAmountToCall(ourSeat) <= gi.getBigBlindSize()) {
if (Math.random() < 0.05) {
return Action.callAction(toCall);
}
}
// check or fold
return Action.checkOrFoldAction(toCall);
}
/**
* Decide what to do for a post-flop action
*/
private Action postFlopAction() {
// number of players left in the hand (including us)
int np = gi.getNumActivePlayers();
// amount to call
double toCall = gi.getAmountToCall(ourSeat);
// immediate pot odds
double PO = toCall / (double) (gi.getEligiblePot(ourSeat) + toCall);
EnumerateResult result = enumerateHands(c1, c2, gi.getBoard());
// compute our current hand rank
double HRN = Math.pow(result.HR, np - 1);
// compute a fast approximation of our hand potential
double PPOT = 0.0;
if (gi.getStage() < Holdem.RIVER) {
PPOT = result.PPot;
}
debug(gi.getBoard() + " | HRn = " + Math.round(HRN * 10) / 10.0 + " PPot = " + Math.round(PPOT * 10) / 10.0 + " PotOdds = " + Math.round(PO * 10)
/ 10.0);
if (HRN == 1.0) {
// dah nuts -- raise the roof!
return betOrRaisePot();
}
// consider checking or betting:
if (toCall == 0) {
if (Math.random() < HRN * HRN) {
debug(gi.getPlayerName(ourSeat) + ":valuebet-bet");
return betOrRaisePot(); // bet a hand in proportion to it's strength
}
if (Math.random() < PPOT) {
debug(gi.getPlayerName(ourSeat) + ":semibluff-bet");
return betOrRaisePot(); // semi-bluff
}
// just check
debug(gi.getPlayerName(ourSeat) + ": check");
return Action.checkAction();
} else {
// consider folding, calling or raising:
if (Math.random() < Math.pow(HRN, 1 + gi.getNumRaises())) {
// raise in proportion to the strength of our hand
debug(gi.getPlayerName(ourSeat) + ": valuebet-raise");
return betOrRaisePot();
}
if (HRN * HRN * gi.getEligiblePot(ourSeat) > toCall || PPOT > PO) {
// if we have draw odds or a strong enough hand to call
debug(gi.getPlayerName(ourSeat) + ":potodds-call");
return Action.callAction(toCall);
}
debug(gi.getPlayerName(ourSeat) + ": checkOrFold");
return Action.checkOrFoldAction(toCall);
}
}
/**
* if fixed-limit: just bets or raises<br>
* in no-limit:<br>
* - bets 2/3 pot<br>
* - or raises a 2/3*(pot + toCall) if someone bet before<br>
* thus always giving 1:2.5 pot odds to the villain.<br>
* <br>
* if stack is lower than to call, just bets the stack
* if stack remaining after the raise is lower than the bet/raise goes
* all-in
* @return
*/
private Action betOrRaisePot() {
if (gi.isFixedLimit()) {
return Action.raiseAction(gi);
} else {
if (gi.getAmountToCall(ourSeat) > 0) {
if (gi.getBankRoll(ourSeat) > gi.getAmountToCall(ourSeat)) {
double wantedRaiseAmount = Utils.roundToCents((gi.getMainPotSize() + gi.getAmountToCall(ourSeat)) / 3 * 2);
double maxPossibleRaise = Utils.roundToCents(gi.getBankRoll(ourSeat) - gi.getAmountToCall(ourSeat));
if (maxPossibleRaise < wantedRaiseAmount) {
wantedRaiseAmount = maxPossibleRaise;
}
return Action.raiseAction(gi, wantedRaiseAmount);
} else {
return Action.callAction(gi);
}
} else {
double betAmount = Utils.roundToCents(gi.getMainPotSize() / 3 * 2);
//TODO check: is this even correct?
if (gi.getBankRoll(ourSeat) - betAmount < betAmount) {
betAmount = gi.getBankRoll(ourSeat);
}
return Action.betAction(betAmount);
}
}
}
/**
* Calculate the raw (unweighted) PPot1 and NPot1 of a hand. (Papp 1998, 5.3)
* Does a one-card look ahead.
*
* @param c1 the first hole card
* @param c2 the second hole card
* @param bd the board cards
* @return
*/
public EnumerateResult enumerateHands(Card c1, Card c2, Hand bd) {
double[][] HP = new double[3][3];
double[] HPTotal = new double[3];
int ourrank7, opprank;
int index;
int[] boardIndexes = new int[bd.size()];
int[] boardIndexes2 = new int[bd.size() + 1];
int c1Index;
int c2Index;
ArrayList<Integer> deck = new ArrayList<Integer>();
for (int i = 0; i < 52; i++) {
deck.add(Integer.valueOf(i));
}
for (int i = 0; i < bd.size(); i++) {
Card card = bd.getCard(i + 1);
boardIndexes[i] = PartialStageFastEval.encode(card.getRank(), card.getSuit());
boardIndexes2[i] = PartialStageFastEval.encode(card.getRank(), card.getSuit());
deck.remove(Integer.valueOf(boardIndexes[i]));
}
c1Index = PartialStageFastEval.encode(c1.getRank(), c1.getSuit());
c2Index = PartialStageFastEval.encode(c2.getRank(), c2.getSuit());
deck.remove(Integer.valueOf(c1Index));
deck.remove(Integer.valueOf(c2Index));
int ourrank5 = eval(boardIndexes, c1Index, c2Index);
// pick first opponent card
for (int i = 0; i < deck.size(); i++) {
int o1Card = deck.get(i);
// pick second opponent card
for (int j = i + 1; j < deck.size(); j++) {
int o2Card = deck.get(j);
opprank = eval(boardIndexes, o1Card, o2Card);
if (ourrank5 > opprank)
index = AHEAD;
else if (ourrank5 == opprank)
index = TIED;
else
index = BEHIND;
HPTotal[index]++;
if (bd.size() < 5) {
// tally all possiblities for next board card
for (int k = 0; k < deck.size(); k++) {
if (i == k || j == k)
continue;
boardIndexes2[boardIndexes2.length - 1] = deck.get(k);
ourrank7 = eval(boardIndexes2, c1Index, c2Index);
opprank = eval(boardIndexes2, o1Card, o2Card);
if (ourrank7 > opprank)
HP[index][AHEAD]++;
else if (ourrank7 == opprank)
HP[index][TIED]++;
else
HP[index][BEHIND]++;
}
}
}
} /* end of possible opponent hands */
double den1 = (45 * (HPTotal[BEHIND] + (HPTotal[TIED] / 2.0)));
double den2 = (45 * (HPTotal[AHEAD] + (HPTotal[TIED] / 2.0)));
EnumerateResult result = new EnumerateResult();
if (den1 > 0) {
result.PPot = (HP[BEHIND][AHEAD] + (HP[BEHIND][TIED] / 2.0) + (HP[TIED][AHEAD] / 2.0)) / (double) den1;
}
if (den2 > 0) {
result.NPot = (HP[AHEAD][BEHIND] + (HP[AHEAD][TIED] / 2.0) + (HP[TIED][BEHIND] / 2.0)) / (double) den2;
}
result.HR = (HPTotal[AHEAD] + (HPTotal[TIED] / 2)) / (HPTotal[AHEAD] + HPTotal[TIED] + HPTotal[BEHIND]);
return result;
}
private int eval(int[] boardIndexes, int c1Index, int c2Index) {
if (boardIndexes.length == 5) {
return PartialStageFastEval.eval7(boardIndexes[0], boardIndexes[1], boardIndexes[2], boardIndexes[3], boardIndexes[4], c1Index, c2Index);
} else if (boardIndexes.length == 4) {
return PartialStageFastEval.eval6(boardIndexes[0], boardIndexes[1], boardIndexes[2], boardIndexes[3], c1Index, c2Index);
} else {
return PartialStageFastEval.eval5(boardIndexes[0], boardIndexes[1], boardIndexes[2], c1Index, c2Index);
}
}
// constants used in above method:
private final static int AHEAD = 0;
private final static int TIED = 1;
private final static int BEHIND = 2;
class EnumerateResult {
double HR;
double PPot;
double NPot;
}
}