/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.cspoker.ai.opponentmodels.weka;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.cspoker.common.elements.cards.Card;
import org.cspoker.common.handeval.spears2p2.StateTableEvaluator;
import com.google.common.collect.Multiset;
import com.google.common.collect.TreeMultiset;
public class Propositionalizer implements Cloneable {
private Map<Object, PlayerData> players = new HashMap<Object, PlayerData>();
private List<PlayerData> activePlayers = new LinkedList<PlayerData>();
private List<PlayerData> allinPlayers = new LinkedList<PlayerData>();
private int bb = 0;
private int maxBet = 0;
private int lastRaise = 0;
private int gameRaiseAmount = 0;
private boolean somebodyActedThisRound = false;
private BetStatistics gameStats = new BetStatistics();
private String round = "preflop";
private int totalPot = 0;
private int nbPlayersDoneThisRound = 0;
private int nbSeatedPlayers = 0;
private EnumSet<Card> cards;
private int minRank=0;
private int maxRank=0;
private int averageRank=0;
private double sigmaRank=0;
public Propositionalizer() {
}
@Override
public Propositionalizer clone() {
try {
Propositionalizer clone = (Propositionalizer)super.clone();
//clone player map (is this needed?)
Map<Object, PlayerData> playersClone = new HashMap<Object, PlayerData>(clone.getPlayers());
//clone the active players at this table. they are mutable
List<PlayerData> activePlayersClone = new LinkedList<PlayerData>();
for (PlayerData player : activePlayers) {
PlayerData playerClone = player.clone();
activePlayersClone.add(playerClone);
playersClone.put(playerClone.getId(), playerClone);
}
//no need to clone contents of allinPlayers, is immutable
//do need to clone the list itself
List<PlayerData> allinplayersClone = new LinkedList<PlayerData>(clone.getAllinPlayers());
clone.players = playersClone;
clone.activePlayers = activePlayersClone;
clone.allinPlayers = allinplayersClone;
clone.gameStats = gameStats.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new IllegalStateException(e);
}
}
public void signalBBAmount(int bb){
this.bb=bb;
for(PlayerData p:activePlayers) p.signalBBAmount(bb);
}
public void signalAllIn(Object id, int chipsMoved) {
if(inPreFlopRound() && maxBet<bb && chipsMoved <= bb){
signalBlind(true, id, chipsMoved);
}else{
PlayerData p = players.get(id);
//call raise or bet
if(maxBet==0){
signalBet(true, id, chipsMoved);
}else if(p.getDeficit(this)<chipsMoved){
signalRaise(true, id, p.getBet()+chipsMoved);
}else{
signalCall(true, id, chipsMoved);
}
}
}
public void signalBet(boolean isAllIn, Object id, int movedAmount) {
PlayerData p = players.get(id);
logBet(id, movedAmount/(double)bb);
if(isAllIn) {
activePlayers.remove(p);
allinPlayers.add(p);
}
// we also consider a raise by big blind here, treat kinda like bet!
// that's why the +=
maxBet += movedAmount;
lastRaise = Math.max(lastRaise, movedAmount);
gameRaiseAmount += movedAmount;
totalPot += movedAmount;
somebodyActedThisRound = true;
gameStats.addBet(this,movedAmount/(double)bb);
p.signalBet(this,movedAmount);
}
public void signalCheck(Object id) {
PlayerData p = players.get(id);
logCheck(id);
++nbPlayersDoneThisRound;
gameStats.addCheck(this);
somebodyActedThisRound = true;
p.signalCheck(this);
}
public void signalRaise(boolean isAllIn, Object id, int maxBetParsed) {
PlayerData p = players.get(id);
int raiseAmount = maxBetParsed-maxBet;
if (p.getDeficit(this) == 0) {
signalBet(isAllIn, id, raiseAmount);
} else {
logRaise(id,raiseAmount/(double)bb);
if(isAllIn) {
activePlayers.remove(p);
allinPlayers.add(p);
}
maxBet = maxBetParsed;
lastRaise = Math.max(lastRaise, raiseAmount);
int movedAmount = maxBet - p.getBet();
gameRaiseAmount += raiseAmount;
totalPot += movedAmount;
somebodyActedThisRound = true;
gameStats.addRaise(this, (movedAmount-raiseAmount)/(double)bb, raiseAmount/(double)bb);
p.signalRaise(this,raiseAmount,movedAmount);
}
nbPlayersDoneThisRound = 0;
}
public void signalCall(boolean isAllIn, Object id) {
PlayerData p = players.get(id);
if(p.getDeficit(this)==0){
signalCheck(id);
}else{
signalCall(isAllIn, id, Math.min(p.getStack(), maxBet-p.getBet()));
}
}
public void signalCall(boolean isAllIn, Object id, int movedAmount) {
PlayerData p = players.get(id);
logCall(id);
if(isAllIn){
activePlayers.remove(p);
allinPlayers.add(p);
}
totalPot += movedAmount;
++nbPlayersDoneThisRound;
somebodyActedThisRound = true;
gameStats.addCall(this, movedAmount/(double)bb);
p.signalCall(this,movedAmount);
}
public void signalFold(Object id) {
PlayerData p = players.get(id);
if(p.getDeficit(this)>0) logFold(id);
activePlayers.remove(p);
somebodyActedThisRound = true;
gameStats.addFold(this);
p.signalFold(this);
}
public void signalBlind(boolean isAllIn, Object playerId, int amount) {
PlayerData p = players.get(playerId);
//TODO change amount when allin
maxBet = amount;
totalPot += amount;
if (isAllIn) {
activePlayers.remove(p);
allinPlayers.add(p);
}
p.signalBlind(amount);
}
public void signalRiver() {
if(!activePlayers.isEmpty()){
round = "river";
startNewRound();
}
}
public void signalTurn() {
if(!activePlayers.isEmpty()){
round = "turn";
startNewRound();
}
}
public void signalFlop() {
if(!activePlayers.isEmpty()){
round = "flop";
startNewRound();
for (PlayerData p : activePlayers) {
p.signalFlop();
}
}
}
public void signalCommunityCards(EnumSet<Card> cardsSet) {
this.cards =cardsSet;
//updateER();
}
public void signalCardShowdown(Object id, Card card1, Card card2) {
// PlayerData p = players.get(id);
if(cards.size()==5){
//showdown after river
Multiset<Integer> ranks = new TreeMultiset<Integer>();
// int minSampleRank = Integer.MAX_VALUE;
// int maxSampleRank = Integer.MIN_VALUE;
// int sum = 0;
int startRank = 53;
for (Card card:cards) {
startRank = handRanks[card.ordinal() + 1 + startRank];
}
//add real rank
int realRank = startRank;
realRank = handRanks[card1.ordinal() + 1 + realRank];
realRank = handRanks[card2.ordinal() + 1 + realRank];
int realType = (realRank >>> 12) - 1;
realRank = realRank & 0xFFF;
realRank = offsets[realType] + realRank - 1;
//take rank samples
int nbBuckets = 6;
int nbSamplesPerBucket = 6;
int nbSamples = nbBuckets*nbSamplesPerBucket;
for(int i=0;i<nbSamples;i++){
int rank = startRank;
Card sampleCard1;
do{
sampleCard1 = Card.values()[random.nextInt(Card.values().length)];
}while(cards.contains(sampleCard1));
rank = handRanks[sampleCard1.ordinal() + 1 + rank];
Card sampleCard2;
do{
sampleCard2 = Card.values()[random.nextInt(Card.values().length)];
}while(cards.contains(sampleCard2) || sampleCard2.equals(sampleCard1));
rank = handRanks[sampleCard2.ordinal() + 1 + rank];
int type = (rank >>> 12) - 1;
rank = rank & 0xFFF;
rank = offsets[type] + rank - 1;
ranks.add(rank);
// if(rank<minSampleRank){
// minSampleRank = rank;
// }
// if(rank>maxSampleRank){
// maxSampleRank = rank;
// }
// sum += rank;
}
// double var = 0;
// double mean = ((double)sum)/nbSamples;
// for (Multiset.Entry<Integer> entry : ranks.entrySet()) {
// double diff = mean - entry.getElement();
// var += diff * diff * entry.getCount();
// }
// var /= (nbSamples-1);
// int averageSampleRank = (int) Math.round(mean);
// int sigmaSampleRank = (int) Math.round(Math.sqrt(var));
int[] bucketCounts = new int[nbBuckets];
Iterator<Integer> iter = ranks.iterator();
double realRankCount = ranks.count(realRank);
// long avgBucket = -1;
double[] bucketDistr = new double[nbBuckets];
if(realRankCount>0){
for(int bucket=0;bucket<nbBuckets;bucket++){
for (int i = 0; i < nbSamplesPerBucket; i++) {
int rank = iter.next();
if(rank==realRank){
++bucketCounts[bucket];
}
}
}
int partitionSum = 0;
for (int i = 0; i < nbBuckets; i++) {
bucketDistr[i] = bucketCounts[i]/realRankCount;
partitionSum += bucketCounts[i]*i;
}
// avgBucket = Math.round(partitionSum/realRankCount);
}else{
boolean found = false;
bucketIteration: for(int bucket=0;bucket<nbBuckets;bucket++){
for (int i = 0; i < nbSamplesPerBucket; i++) {
int rank = iter.next();
if(rank>realRank){
bucketDistr[bucket]=1;
// avgBucket = bucket;
found = true;
break bucketIteration;
}
}
}
if(!found){
bucketCounts[nbBuckets-1]=1;
// avgBucket = nbBuckets-1;
}
}
logShowdown(id,bucketDistr);
}else{
//ignore
//throw new IllegalStateException("everybody went all-in before the river");
}
}
private final static int[] handRanks;
static {
handRanks = StateTableEvaluator.getInstance().handRanks;
}
private static final int[] offsets = new int[] { 0, 1277, 4137, 4995, 5853,
5863, 7140, 7296, 7452 };
private Random random = new Random(0);
//
// private void updateExpectedRank() {
// Multiset<Integer> ranks = new HashMultiset<Integer>();
// minRank = Integer.MAX_VALUE;
// maxRank = Integer.MIN_VALUE;
// int sum = 0;
// int n = 100;
//
// int startRank = 53;
// for (Card card:cards) {
// startRank = handRanks[card.ordinal() + 1 + startRank];
// }
// for(int i=0;i<n;i++){
// EnumSet<Card> sample = EnumSet.copyOf(cards);
// int rank = startRank;
// while(sample.size()<7){
// Card sampleCard;
// do{
// sampleCard = Card.values()[random.nextInt(Card.values().length)];
// }while(sample.contains(sampleCard));
// sample.add(sampleCard);
// rank = handRanks[sampleCard.ordinal() + 1 + rank];
// }
// int type = (rank >>> 12) - 1;
// rank = rank & 0xFFF;
// rank = offsets[type] + rank - 1;
// ranks.add(rank);
// if(rank<minRank){
// minRank = rank;
// }
// if(rank>maxRank){
// maxRank = rank;
// }
// sum += rank;
// }
// double var = 0;
// double mean = ((double)sum)/n;
// for (Multiset.Entry<Integer> entry : ranks.entrySet()) {
// double diff = mean - entry.getElement();
// var += diff * diff * entry.getCount();
// }
// var /= (n-1);
// averageRank = (int)Math.round(mean);
// sigmaRank = Math.round(Math.sqrt(var));
// }
public void signalShowdown() {
if (round.equals("flop") || round.equals("turn")
|| round.equals("river")) {
for (PlayerData p : activePlayers) {
p.signalShowdown();
}
}
}
public void signalSeatedPlayer(int stack, Object id) {
PlayerData p = players.get(id);
if (p == null) {
p = new PlayerData(id);
players.put(id, p);
}
activePlayers.add(p);
p.signalBBAmount(bb);
++nbSeatedPlayers;
p.startNewGame();
p.resetStack(stack);
}
public void signalNewGame() {
round = "preflop";
somebodyActedThisRound = false;
gameStats = new BetStatistics();
nbPlayersDoneThisRound = 0;
nbSeatedPlayers = 0;
activePlayers.clear();
allinPlayers.clear();
gameRaiseAmount = 0;
startNewRound();
}
protected void startNewRound() {
for (PlayerData player : activePlayers) {
player.startNewRound();
}
nbPlayersDoneThisRound = 0;
maxBet = 0;
lastRaise = bb;
somebodyActedThisRound = false;
}
public boolean isSomebodyActedThisRound() {
return somebodyActedThisRound;
}
public List<PlayerData> getActivePlayers() {
return activePlayers;
}
public Map<Object, PlayerData> getPlayers() {
return players;
}
public List<PlayerData> getAllinPlayers() {
return allinPlayers;
}
public int getTotalPot() {
return totalPot;
}
public int getMaxBet() {
return maxBet;
}
public double getRelativeMaxBet() {
return maxBet/(double)bb;
}
public int getAverageRank() {
return averageRank;
}
public int getMaxRank() {
return maxRank;
}
public BetStatistics getTableGameStats() {
return gameStats;
}
public int getMinRank() {
return minRank;
}
public double getSigmaRank() {
return sigmaRank;
}
public int getNbSeatedPlayers() {
return nbSeatedPlayers;
}
public int getNbActivePlayers() {
return activePlayers.size();
}
public double getActivePlayerRatio() {
return (double) getNbActivePlayers() / (double) getNbSeatedPlayers();
}
public int getPotSize() {
return totalPot;
}
public double getRelativePotSize() {
return totalPot/(double)bb;
}
public int getMaxMaxBet(){
int maxBet1 = 0;
int maxBet2 = 0;
for (PlayerData player : activePlayers) {
int maxMaxPlayerBet = player.getStack()+player.getBet();
if(maxMaxPlayerBet>maxBet1){
maxBet2 = maxBet1;
maxBet1 = maxMaxPlayerBet;
}else if(maxMaxPlayerBet > maxBet2){
maxBet2 = maxMaxPlayerBet;
}
}
return maxBet2;
}
public int getMaxRaise(PlayerData p) {
int amountLeftToRaiseWith = p.getStack()-p.getDeficit(this);
int maxRaise = Math.max(0, getMaxMaxBet()-getMaxBet());
return Math.min(amountLeftToRaiseWith, maxRaise);
}
public int getMinRaise(PlayerData p) {
return Math.min(lastRaise, getMaxRaise(p));
}
public int getPlayersToAct() {
if (somebodyActedThisRound) {
return getNbActivePlayers() - nbPlayersDoneThisRound - 1;
}
return getNbActivePlayers() - nbPlayersDoneThisRound;
}
public int getPlayersActed() {
return nbPlayersDoneThisRound;
}
public double getRoundCompletion() {
if (isSomebodyActedThisRound()) {
if (getNbActivePlayers() <= 1) {
return 0;
}
return nbPlayersDoneThisRound / (double) (getNbActivePlayers() - 1);
}
return nbPlayersDoneThisRound / (double) getNbActivePlayers();
}
public double getAverageAF(PlayerData p, int memory) {
List<PlayerData> opponents = activePlayers.size()>1? activePlayers:allinPlayers;
double sum = 0;
int n = 0;
for (PlayerData player : opponents) {
if(!p.equals(player)) {
sum += player.getGlobalStats().getAF(memory);
++n;
}
}
return (sum/n);
}
public double getAverageVPIP(PlayerData p, int memory) {
List<PlayerData> opponents = activePlayers.size()>1? activePlayers:allinPlayers;
double sum = 0;
int n = 0;
for (PlayerData player : opponents) {
if(!p.equals(player)) {
sum += player.getVPIP(memory);
++n;
}
}
return (sum/n);
}
public double getAveragePFR(PlayerData p, int memory) {
List<PlayerData> opponents = activePlayers.size()>1? activePlayers:allinPlayers;
double sum = 0;
int n = 0;
for (PlayerData player : opponents) {
if(!p.equals(player)) {
sum += player.getPFR(memory);
++n;
}
}
return (sum/n);
}
public double getAverageAFq(PlayerData p, int memory) {
List<PlayerData> opponents = activePlayers.size()>1? activePlayers:allinPlayers;
double sum = 0;
int n = 0;
for (PlayerData player : opponents) {
if(!p.equals(player)) {
sum += player.getGlobalStats().getAFq(memory);
++n;
}
}
return (sum/n);
}
public double getAverageAFAmount(PlayerData p, int memory) {
List<PlayerData> opponents = activePlayers.size()>1? activePlayers:allinPlayers;
double sum = 0;
int n = 0;
for (PlayerData player : opponents) {
if(!p.equals(player)) {
sum += Math.log(player.getGlobalStats().getAFAmount(memory));
++n;
}
}
return (sum/n);
}
public double getAverageWtSD(PlayerData p, int memory) {
List<PlayerData> opponents = activePlayers.size()>1? activePlayers:allinPlayers;
double sum = 0;
int n = 0;
for (PlayerData player : opponents) {
if(!p.equals(player)) {
sum += player.getWtSD(memory);
++n;
}
}
return (sum/n);
}
public String getRound() {
return round;
}
public boolean inPreFlopRound() {
return "preflop".equals(round);
}
public boolean inFlopRound() {
return "flop".equals(round);
}
public boolean inTurnRound() {
return "turn".equals(round);
}
public boolean inRiverRound() {
return "river".equals(round);
}
protected void logBet(Object actorId, double raiseAmount){
// no op
}
protected void logRaise(Object actorId, double raiseAmount){
// no op
}
protected void logFold(Object actorId){
// no op
}
protected void logCall(Object actorId){
// no op
}
protected void logCheck(Object actorId){
// no op
}
protected void logShowdown(Object actorId, double[] partitionDistr) {
// no op
}
public double rel(double up, double down) {
if(down==0) return 0;
return up/down;
}
}