fulltiltanalysis-0.1/0000755000076500007650000000000011270643212013053 5ustar alfalffulltiltanalysis-0.1/fulltilt.py0000644000076500007650000002470411270643202015272 0ustar alfalf#!/usr/bin/env python
__author__ = "Alfred Whitehead"
__copyright__ = "Copyright (C) 2009 Alfred Whitehead"
__license__ = "BSD (http://creativecommons.org/licenses/BSD/)"
__version__ = "0.1"
"""
Program for parsing Full Tilt Poker log files, and generating a list of
probabilities that observed events actually occurred. Limited to looking at
hands where two plays are "heads-up" (no other players, at least one player
all-in) before the flop. All other hands are filtered out.
"""
# Copyright (c) 2009, Alfred Whitehead
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
# * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import re, glob
import cPickle as pickle
from pokereval import PokerEval
pokerev = PokerEval()
card_order = ['A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2']
hand_class_order = []
probability_cache = {}
def get_headsup_probabilities(hand1, hand2):
""" Returns a tuple of (best_hand_index, p_best_wins, p_worst_wins, p_tie).
If the result is in the cache, the cached value is returned. If not, the
probabilities are calculated using exhaustive search and then added to the
cache. """
global probability_cache
prob_tuple = False
if probability_cache.has_key(hand1):
if probability_cache[hand1].has_key(hand2):
prob_tuple = probability_cache[hand1][hand2]
elif probability_cache.has_key(hand2):
if probability_cache[hand2].has_key(hand1):
prob_tuple = probability_cache[hand2][hand1]
if not prob_tuple:
global pokerev
hands_pokereval = []
for hand in [hand1, hand2]:
hands_pokereval.append(hand.split())
ev = pokerev.poker_eval(game='holdem', pockets=hands_pokereval,
board=['__','__','__','__','__'])
N = ev['info'][0]
hand_stats = ev['eval']
favoured_hand = False
if hand_stats[0]['winhi'] >= hand_stats[1]['winhi']:
favoured_hand = 0
else:
favoured_hand = 1
unfavoured_hand = abs(favoured_hand-1)
p_favoured_wins = float(hand_stats[favoured_hand]['winhi']) / float(N)
p_favoured_loses = float(hand_stats[favoured_hand]['losehi']) / float(N)
p_tie = float(hand_stats[favoured_hand]['tiehi']) / float(N)
prob_tuple = (favoured_hand, p_favoured_wins, p_favoured_loses, p_tie)
if probability_cache.has_key(hand1):
assert not probability_cache[hand1].has_key(hand2), "Generated a probability for something we already knew (h2)."
probability_cache[hand1][hand2] = prob_tuple
elif probability_cache.has_key(hand2):
assert not probability_cache[hand2].has_key(hand1), "Generated a probability for something we already knew (h1)."
probability_cache[hand2][hand1] = prob_tuple
else:
if favoured_hand == 0:
probability_cache[hand1] = {}
probability_cache[hand1][hand2] = prob_tuple
else:
probability_cache[hand2] = {}
probability_cache[hand2][hand1] = prob_tuple
return prob_tuple
def load_probability_cache(filename='probability_cache.pickle'):
""" Loads up the probability cache from a given Pickle file. This operation
destroys any data already in the probability cache. If the file doesn't
exist, the probability cache is unchanged. """
global probability_cache
try:
f = open(filename, 'r')
probability_cache = pickle.load(f)
f.close()
except:
pass
def save_probability_cache(filename='probability_cache.pickle'):
""" Saves the contents of the probability cache to disk. Overwrites the
file. """
global probability_cache
f = open(filename, 'w')
pickle.dump(probability_cache, f)
f.close()
def card_rank(card):
""" Returns an integer for the card's numerical rank. Ace = 13, Duece = 1.
"""
return 13 - card_order.index(card)
def classify_hand(hand):
""" Classifies the hand by its more generic category (ie: Ah Kh -> AK s) """
card1 = hand[0]
suit1 = hand[1]
card2 = hand[3]
suit2 = hand[4]
classification = ''
if card_rank(card1) >= card_rank(card2):
classification = '%s%s' % (card1, card2)
else:
classification = '%s%s' % (card2, card1)
if suit1 == suit2:
classification += ' s'
return classification
def compare_hands(h1, h2):
""" Compares two hands by their hand class. Returns -1, 0, or 1 if the left
hand is better, equal to, worse than the right hand. "Better" is determined
by the single-hand odds for its hand class (ie: AA > AK s) """
return compare_hand_classes(classify_hand(h1), classify_hand(h2))
def compare_hand_classes(hc1, hc2):
""" Compares two hand classes (ie: AA vs AK s) and returns -1, 0, or 1 if
the left hand class is favoured over the right. Favoured determined by
single-hand odds. """
hc1i = hand_class_order.index(hc1)
hc2i = hand_class_order.index(hc2)
if hc1i < hc2i:
return -1
elif hc1i == hc2i:
return 0
else:
return 1
def sort_hands(hands):
""" Sort an array of hands by single-hand win probability. """
hands.sort(compare_hands)
def evaluate_result(hands, winners, winning_hand):
""" Prints CSV a line describing the analysis of these hands. """
if winners == len(hands):
winning_hand = "Draw"
out_s = ""
out_s += "%s, " % (winning_hand)
out_s += str(winners) + ", "
if len(hands) == 2:
(favoured_hand, p_favoured_wins, p_favoured_loses, p_tie) = \
get_headsup_probabilities(hands[0], hands[1])
unfavoured_hand = abs(favoured_hand-1)
fhcl = classify_hand(hands[favoured_hand])
uhcl = classify_hand(hands[unfavoured_hand])
favoured_won = 0
if winning_hand == hands[favoured_hand]:
favoured_won = 1
p_this_outcome = 0.0
if favoured_won:
p_this_outcome = p_favoured_wins
elif winning_hand == 'Draw':
p_this_outcome = p_tie
else:
p_this_outcome = p_favoured_loses
sum_ps = p_favoured_wins + p_favoured_loses + p_tie
out_s += '%s,%s,%s,%s,%d,%.12f,%.12f,%.12f,%.12f,%.12f,' % (hands[favoured_hand], \
hands[unfavoured_hand], fhcl, uhcl, favoured_won, p_favoured_wins, \
p_favoured_loses, p_tie, p_this_outcome, sum_ps)
else:
out_s += ',,,,,,,,,,,'
sort_hands(hands)
out_s += ", ".join(hands)
print out_s
return out_s
def main():
""" Main program flow here. """
f = open('pokerhands.csv', 'r')
for line in f:
if line != '':
hand_class_order.append(line.strip())
f.close()
load_probability_cache()
file_list = glob.glob('*.txt')
print "WinningHand,NumWinners,FavouredHand,UnfavouredHand,FavouredHandClass,UnfavouredHandClass,FavouredWon,P(FavouredWins),P(FavouredLoses),P(Tie),P(ThisOutcome),Sum(Ps),AllHands"
for filename in file_list:
f = open(filename, 'r')
in_opening = False
in_resolution = False
hand_of_interest = False
someone_all_in = False
someone_calls = False
hands = []
winning_hand = ''
winners = 0
for line in f:
if re.search(r"\*\*\* HOLE CARDS \*\*\*", line):
in_opening = True
in_resolution = False
if in_opening and re.search(r"all in", line) and not re.search(r":", line):
if someone_all_in:
hand_of_interest = True
someone_all_in = True
if in_opening and re.search(r"calls", line) and not re.search(r":", line):
if someone_all_in:
someone_calls = True
if re.search(r"\*\*\* FLOP \*\*\*", line):
if someone_all_in and someone_calls:
hand_of_interest = True
in_opening = False
if re.search(r"\*\*\* SUMMARY \*\*\*", line):
in_opening = False
if someone_all_in and someone_calls:
hand_of_interest = True
in_resolution = True
if re.search(r"Full Tilt Poker Game", line):
# Evaluate this hand
if hand_of_interest and len(hands) == 2:
evaluate_result(hands, winners, winning_hand)
in_opening = False
in_resolution = False
hand_of_interest = False
someone_all_in = False
someone_calls = False
hands = []
winning_hand = ''
winners = 0
if in_resolution and re.search(r"\[(\w\w \w\w)\]", line):
matchdata = re.search(r"\[(\w\w \w\w)\]", line)
hand = matchdata.group(1)
hands.append(hand)
if re.search(r"won", line):
winning_hand = hand
winners += 1
# Parse last hand
if hand_of_interest and len(hands) == 2:
evaluate_result(hands, winners, winning_hand)
f.close()
save_probability_cache()
if __name__ == "__main__":
main()
fulltiltanalysis-0.1/pokerhands.csv0000644000076500007650000000122711270643212015730 0ustar alfalfAA
KK
QQ
JJ
AK s
AQ s
TT
AK
AJ s
KQ s
99
AT s
AQ
KJ s
88
QJ s
KT s
A9 s
AJ
QT s
KQ
77
JT s
A8 s
K9 s
AT
A5 s
A7 s
KJ
66
T9 s
A4 s
Q9 s
J9 s
QJ
A6 s
55
A3 s
K8 s
KT
98 s
T8 s
K7 s
A2 s
87 s
QT
Q8 s
44
A9
J8 s
76 s
JT
97 s
K6 s
K5 s
K4 s
T7 s
Q7 s
K9
65 s
T9
86 s
A8
J7 s
33
54 s
Q6 s
K3 s
Q9
75 s
22
J9
64 s
Q5 s
K2 s
96 s
Q3 s
J8
98
T8
97
A7
T7
Q4 s
Q8
J5 s
T6
75
J4 s
74 s
K8
86
53 s
K7
63 s
J6 s
85
T6 s
76
A6
T2
95 s
84
62
T5 s
95
A5
Q7
T5
87
83
65
Q2 s
94
74
54
A4
T4
82
64
42
J7
93
85 s
73
53
T3
63
K6
J6
96
92
72
52
Q4
K5
J5
43 s
Q3
43
K4
J4
T4 s
Q6
Q2
J3 s
J3
T3 s
A3
Q5
J2
84 s
82 s
42 s
93 s
73 s
K3
J2 s
92 s
52 s
K2
T2 s
62 s
32
A2
83 s
94 s
72 s
32 s