pokerstats 2.0.13 → 2.0.14

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.13
1
+ 2.0.14
@@ -1,6 +1,5 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + "/hand_constants")
2
- module Pokerstats
3
-
2
+ module Pokerstats
4
3
  def class_index_from_hand_string(hand_string)
5
4
  return class_index_from_hand_string!(hand_string)
6
5
  rescue ArgumentError
@@ -8,10 +7,16 @@ module Pokerstats
8
7
  end
9
8
 
10
9
  def class_index_from_hand_string!(hand_string)
11
- class_index_from_class_string(class_string_from_hand_string(hand_string))
10
+ class_index_from_class_string!(class_string_from_hand_string!(hand_string))
12
11
  end
13
12
 
14
13
  def class_string_from_hand_string(hand_string)
14
+ class_string_from_hand_string!(hand_string)
15
+ rescue
16
+ nil
17
+ end
18
+
19
+ def class_string_from_hand_string!(hand_string)
15
20
  raise ArgumentError, "hand_string '#{hand_string}' must be a String" unless hand_string.kind_of?(String)
16
21
  hand_string = hand_string.gsub(/ /,'')
17
22
  hand_string = hand_string.upcase
@@ -37,6 +42,12 @@ module Pokerstats
37
42
  end
38
43
 
39
44
  def class_index_from_class_string(class_string)
45
+ class_index_from_class_string!(class_string)
46
+ rescue
47
+ nil
48
+ end
49
+
50
+ def class_index_from_class_string!(class_string)
40
51
  raise ArgumentError, "class_string #{class_string.inspect} must be a String" unless class_string.kind_of?(String)
41
52
  class_string.upcase!
42
53
  first = Pokerstats::HandConstants::CARDS.index(class_string[0..0])
@@ -52,8 +63,15 @@ module Pokerstats
52
63
  end
53
64
  end
54
65
 
55
- def class_string_from_class_index(classIndex)
56
- row, col = row_from_class_index(classIndex), col_from_class_index(classIndex)
66
+ def class_string_from_class_index(class_index)
67
+ class_string_from_class_index!(class_index)
68
+ rescue ArgumentError
69
+ nil
70
+ end
71
+
72
+ def class_string_from_class_index!(class_index)
73
+ raise ArgumentError, "class_index must be an integer" unless class_index.kind_of? Integer
74
+ row, col = row_from_class_index(class_index), col_from_class_index(class_index)
57
75
  case row <=> col
58
76
  when 1
59
77
  Pokerstats::HandConstants::CARDS[col..col] + Pokerstats::HandConstants::CARDS[row..row] + "o"
@@ -1,18 +1,24 @@
1
1
  module Pokerstats
2
2
  module HandConstants
3
3
  HAND_REPORT_SPECIFICATION = [
4
- # [key, sql_type, function]
5
4
  [:session_filename, 'string'],
6
5
  [:starting_at, 'datetime'],
7
6
  [:name, 'string'],
7
+ [:table_name, 'string'],
8
8
  [:description, 'string'],
9
+ [:ante, 'decimal'],
9
10
  [:sb, 'decimal'],
10
11
  [:bb, 'decimal'],
11
12
  [:board, 'string'],
12
13
  [:total_pot, 'decimal'],
13
14
  [:rake, 'decimal'],
14
15
  [:played_at, 'datetime'],
15
- [:tournament, 'string']
16
+ [:tournament, 'string'],
17
+ [:max_players, 'integer'],
18
+ [:number_players, 'integer'],
19
+ [:game_type, 'string'],
20
+ [:limit_type, 'string'],
21
+ [:stakes_type, 'decimal']
16
22
  ]
17
23
  HAND_INFORMATION_KEYS = HAND_REPORT_SPECIFICATION.map{|each| each.first}
18
24
  HAND_RECORD_INCOMPLETE_MESSAGE = "hand record is incomplete"
@@ -12,11 +12,12 @@ module Pokerstats
12
12
  plugin_include_module HandStatisticsAPI
13
13
  def initialize
14
14
  install_plugins self
15
- @hand_information = {}
15
+ @hand_information = {:number_players => 0, :ante => "0.0".to_d}
16
16
  @player_hashes = []
17
17
  @button_player_index = nil
18
18
  @cached_player_position = nil
19
19
  @street_state = nil
20
+ @last_street_state = nil
20
21
  street_transition(:prelude)
21
22
  end
22
23
 
@@ -30,6 +31,10 @@ module Pokerstats
30
31
  hash.merge!(key => @hand_information[key])
31
32
  end
32
33
  end
34
+
35
+ def hand_information(field)
36
+ @hand_information[field]
37
+ end
33
38
 
34
39
  def update_hand update
35
40
  street_transition(update[:street]) unless update[:street] == @street_state
@@ -65,7 +70,8 @@ module Pokerstats
65
70
  raise Pokerstats::HandHistoryParseError, "#{PLAYER_RECORDS_DUPLICATE_PLAYER_NAME}: #{screen_name.inspect}" if players.member?(screen_name)
66
71
  @cached_player_position = nil
67
72
  @player_hashes << player
68
- plugins.each{|each| each.register_player(screen_name, @street_state)} #why the second parameter?
73
+ @hand_information[:number_players]+=1
74
+ plugins.each{|each| each.register_player(screen_name, @street_state, player)} #why the second parameter?
69
75
  street_transition_for_player(@street_state, screen_name)
70
76
  end
71
77
 
@@ -76,8 +82,13 @@ module Pokerstats
76
82
  def street
77
83
  @street_state
78
84
  end
85
+
86
+ def last_street
87
+ @last_street_state
88
+ end
79
89
 
80
90
  def street_transition street
91
+ @last_street_state = @street_state
81
92
  @street_state = street
82
93
  plugins.each{|each| each.street_transition(street)}
83
94
  players.each {|player| street_transition_for_player(street, player)}
@@ -116,6 +127,17 @@ module Pokerstats
116
127
  def position screen_name
117
128
  (@cached_player_position && @cached_player_position[screen_name]) || calculate_player_position(screen_name)
118
129
  end
130
+
131
+ # player screen_name_first goes before player screen_name_second
132
+ def betting_order?(screen_name_first, screen_name_second)
133
+ if button?(screen_name_first)
134
+ false
135
+ elsif button?(screen_name_second)
136
+ true
137
+ else
138
+ position(screen_name_first) < position(screen_name_second)
139
+ end
140
+ end
119
141
 
120
142
  def button?(screen_name)
121
143
  position(screen_name) && position(screen_name).zero?
@@ -146,8 +168,7 @@ module Pokerstats
146
168
  def attacker?(screen_name)
147
169
  (number_players > 2) && (button?(screen_name) || cutoff?(screen_name))
148
170
  end
149
-
150
-
171
+
151
172
  ##
152
173
  # Action Information
153
174
  ##
@@ -12,7 +12,7 @@ module Pokerstats
12
12
  automatic_report screen_name
13
13
  end
14
14
 
15
- def register_player screen_name, street
15
+ def register_player screen_name, street, player_hash
16
16
  end
17
17
 
18
18
  def street_transition street
@@ -35,7 +35,7 @@ module Pokerstats
35
35
  @postflop_aggressive[screen_name]
36
36
  end
37
37
 
38
- def register_player screen_name, street
38
+ def register_player screen_name, street, player
39
39
  @preflop_passive[screen_name] = 0
40
40
  @preflop_aggressive[screen_name] = 0
41
41
  @postflop_passive[screen_name] = 0
@@ -2,14 +2,25 @@ module Pokerstats
2
2
  class CashStatistics < HandStatistics::Plugin
3
3
  def initialize handstatistics
4
4
  super handstatistics
5
+ @seat = {}
5
6
  @posted = {}
6
7
  @paid = {}
7
8
  @paid_this_round = {}
8
9
  @won = {}
9
10
  @cards = {}
11
+ @starting_stack = {}
10
12
  @stats = {:posted => @posted, :paid => @paid, :won => @won, :cards => @cards}
11
13
  end
12
14
 
15
+
16
+ def position(player)
17
+ @hand_statistics.position player
18
+ end
19
+
20
+ def seat(player)
21
+ @seat[player]
22
+ end
23
+
13
24
  def profit(player)
14
25
  return nil unless won(player) && posted(player) && paid(player)
15
26
  won(player) - posted(player) - paid(player)
@@ -34,7 +45,7 @@ module Pokerstats
34
45
  def divided_by_bb(value)
35
46
  bb = @hand_statistics.report_hand_information[:bb]
36
47
  return nil if bb.nil? || bb.zero?
37
- value / bb
48
+ value / (2 * bb)
38
49
  end
39
50
 
40
51
  def profit_in_bb(player)
@@ -54,7 +65,45 @@ module Pokerstats
54
65
  end
55
66
 
56
67
  def cards(player)
57
- @cards[player]
68
+ @cards[player]
69
+ end
70
+
71
+ def starting_stack(player)
72
+ @starting_stack[player]
73
+ end
74
+
75
+ def starting_stack_in_bb(player)
76
+ divided_by_bb(starting_stack(player))
77
+ end
78
+
79
+ def starting_pot
80
+ sb = @hand_statistics.report_hand_information[:sb]
81
+ bb = @hand_statistics.report_hand_information[:bb]
82
+ ante = @hand_statistics.report_hand_information[:ante]
83
+ number_players = @hand_statistics.report_hand_information[:number_players]
84
+ sb && bb && ante && number_players &&
85
+ (sb + bb + ante * number_players)
86
+ end
87
+
88
+ def starting_stack_as_M(player)
89
+ starting_stack(player) && starting_pot && (starting_pot > 0) &&
90
+ ("1.0".to_d * starting_stack(player) / starting_pot)
91
+ end
92
+
93
+ def starting_stack_as_M_class(player)
94
+ starting_stack_as_M(player) &&
95
+ case starting_stack_as_M(player)
96
+ when 0 .. 2.99
97
+ "darkred"
98
+ when 2.99 .. 5.99
99
+ "red"
100
+ when 5.99 .. 9.99
101
+ "orange"
102
+ when 9.99 .. 19.99
103
+ "yellow"
104
+ else
105
+ "green"
106
+ end
58
107
  end
59
108
 
60
109
  def card_category_index(player)
@@ -62,29 +111,38 @@ module Pokerstats
62
111
  end
63
112
 
64
113
  def self.report_specification
65
- [
66
- # [key, sql_type, function]
67
- [:posted, 'decimal', :posted],
68
- [:paid, 'decimal', :paid],
69
- [:won, 'decimal', :won],
70
- [:profit, 'decimal', :profit],
71
- [:posted_in_bb, 'string', :posted_in_bb],
72
- [:paid_in_bb, 'string', :paid_in_bb],
73
- [:won_in_bb, 'string', :won_in_bb],
74
- [:profit_in_bb, 'string', :profit_in_bb],
75
- [:cards, 'string', :cards],
76
- [:card_category_index, 'integer', :card_category_index]
77
- ]
114
+ [
115
+ # [key, sql_type, function]
116
+ [:seat, 'integer', :seat],
117
+ [:position, 'integer', :position],
118
+ [:posted, 'decimal', :posted],
119
+ [:paid, 'decimal', :paid],
120
+ [:won, 'decimal', :won],
121
+ [:profit, 'decimal', :profit],
122
+ [:posted_in_bb, 'decimal', :posted_in_bb],
123
+ [:paid_in_bb, 'decimal', :paid_in_bb],
124
+ [:won_in_bb, 'decimal', :won_in_bb],
125
+ [:profit_in_bb, 'decimal', :profit_in_bb],
126
+ [:cards, 'string', :cards],
127
+ [:card_category_index, 'integer', :card_category_index],
128
+ [:starting_stack, 'decimal', :starting_stack],
129
+ [:starting_stack_in_bb, 'decimal', :starting_stack_in_bb],
130
+ [:starting_stack_as_M, 'decimal', :starting_stack_as_M],
131
+ [:starting_stack_as_M_class, 'decimal', :starting_stack_as_M_class]
132
+ ]
78
133
  end
79
134
 
80
135
  def stats(player=nil)
81
136
  return @stats unless player
82
137
  end
83
138
 
84
- def register_player(screen_name, street)
139
+ def register_player(screen_name, street, player_hash)
140
+ # puts "register_player(#{screen_name.inspect}, #{street.inspect}, #{player_hash.inspect})"
85
141
  @posted[screen_name] = 0
86
142
  @paid[screen_name] = 0
87
143
  @won[screen_name] = 0
144
+ @starting_stack[screen_name] = player_hash[:starting_stack]
145
+ @seat[screen_name] = player_hash[:seat]
88
146
  end
89
147
 
90
148
  def street_transition_for_player street, player
@@ -0,0 +1,219 @@
1
+ module Pokerstats
2
+ class StreetBetStatistics < HandStatistics::Plugin
3
+ def self.report_specification
4
+ [
5
+ [:preflop_2bet, "boolean", :preflop_2bet],
6
+ [:preflop_3bet, "boolean", :preflop_3bet],
7
+ [:preflop_4bet, "boolean", :preflop_4bet],
8
+ [:flop_1bet, "boolean", :flop_1bet],
9
+ [:flop_2bet, "boolean", :flop_2bet],
10
+ [:flop_3bet, "boolean", :flop_3bet],
11
+ [:flop_4bet, "boolean", :flop_4bet],
12
+ [:turn_1bet, "boolean", :turn_1bet],
13
+ [:turn_2bet, "boolean", :turn_2bet],
14
+ [:turn_3bet, "boolean", :turn_3bet],
15
+ [:turn_4bet, "boolean", :turn_4bet],
16
+ [:river_1bet, "boolean", :river_1bet],
17
+ [:river_2bet, "boolean", :river_2bet],
18
+ [:river_3bet, "boolean", :river_3bet],
19
+ [:river_4bet, "boolean", :river_4bet],
20
+
21
+ [:fold_to_preflop_1bet, "boolean", :fold_to_preflop_1bet],
22
+ [:fold_to_preflop_2bet, "boolean", :fold_to_preflop_2bet],
23
+ [:fold_to_preflop_3bet, "boolean", :fold_to_preflop_3bet],
24
+ [:fold_to_preflop_4bet, "boolean", :fold_to_preflop_4bet],
25
+ [:fold_to_flop_1bet, "boolean", :fold_to_flop_1bet],
26
+ [:fold_to_flop_2bet, "boolean", :fold_to_flop_2bet],
27
+ [:fold_to_flop_3bet, "boolean", :fold_to_flop_3bet],
28
+ [:fold_to_flop_4bet, "boolean", :fold_to_flop_4bet],
29
+ [:fold_to_turn_1bet, "boolean", :fold_to_turn_1bet],
30
+ [:fold_to_turn_2bet, "boolean", :fold_to_turn_2bet],
31
+ [:fold_to_turn_3bet, "boolean", :fold_to_turn_3bet],
32
+ [:fold_to_turn_4bet, "boolean", :fold_to_turn_4bet],
33
+ [:fold_to_river_1bet, "boolean", :fold_to_river_1bet],
34
+ [:fold_to_river_2bet, "boolean", :fold_to_river_2bet],
35
+ [:fold_to_river_3bet, "boolean", :fold_to_river_3bet],
36
+ [:fold_to_river_4bet, "boolean", :fold_to_river_4bet],
37
+
38
+ [:call_preflop_1bet, "boolean", :call_preflop_1bet],
39
+ [:call_preflop_2bet, "boolean", :call_preflop_2bet],
40
+ [:call_preflop_3bet, "boolean", :call_preflop_3bet],
41
+ [:call_preflop_4bet, "boolean", :call_preflop_4bet],
42
+ [:call_flop_1bet, "boolean", :call_flop_1bet],
43
+ [:call_flop_2bet, "boolean", :call_flop_2bet],
44
+ [:call_flop_3bet, "boolean", :call_flop_3bet],
45
+ [:call_flop_4bet, "boolean", :call_flop_4bet],
46
+ [:call_turn_1bet, "boolean", :call_turn_1bet],
47
+ [:call_turn_2bet, "boolean", :call_turn_2bet],
48
+ [:call_turn_3bet, "boolean", :call_turn_3bet],
49
+ [:call_turn_4bet, "boolean", :call_turn_4bet],
50
+ [:call_river_1bet, "boolean", :call_river_1bet],
51
+ [:call_river_2bet, "boolean", :call_river_2bet],
52
+ [:call_river_3bet, "boolean", :call_river_3bet],
53
+ [:call_river_4bet, "boolean", :call_river_4bet],
54
+
55
+ [:last_aggr_preflop, 'boolean', :last_aggr_preflop],
56
+ [:last_aggr_flop, 'boolean', :last_aggr_flop],
57
+ [:last_aggr_turn, 'boolean', :last_aggr_turn],
58
+ [:last_aggr_river, 'boolean', :last_aggr_river],
59
+
60
+ [:cbet_flop, 'boolean', :cbet_flop],
61
+ [:cbet_turn, 'boolean', :cbet_turn],
62
+ [:cbet_river, 'boolean', :cbet_river],
63
+ [:fold_to_cbet_flop, 'boolean', :fold_to_cbet_flop],
64
+ [:fold_to_cbet_turn, 'boolean', :fold_to_cbet_turn],
65
+ [:fold_to_cbet_river, 'boolean', :fold_to_cbet_river],
66
+
67
+ [:dbet_flop, 'boolean', :dbet_flop],
68
+ [:dbet_turn, 'boolean', :dbet_turn],
69
+ [:dbet_river, 'boolean', :dbet_river],
70
+ [:fold_to_dbet_flop, 'boolean', :fold_to_dbet_flop],
71
+ [:fold_to_dbet_turn, 'boolean', :fold_to_dbet_turn],
72
+ [:fold_to_dbet_river, 'boolean', :fold_to_dbet_river]
73
+ ]
74
+ end
75
+
76
+ attr_accessor :street_bets, :fold_to_street_bets, :last_aggr_player
77
+
78
+ #
79
+ # These functions return one of three, not two values, and hence do not end with a "?".
80
+ # A nil value indicates that the player had no opportunity to make the described bet.
81
+ # For example:
82
+ # cbet_flop(player)
83
+ # nil -- player did not have an opportunity to make a cbet on the flop
84
+ # true -- player made a cbet on the flop
85
+ # false -- player had an opportunity to make a cbet on the flop, but did not
86
+ #
87
+ # fold_to_flop_2bet(player)
88
+ # nil -- player did not have an opportunity to fold to a 2bet on the flop
89
+ # true -- player folded to a 2bet on the flop
90
+ # false -- player had an opportunity to fold to a 2bet on the flop, but did not
91
+ #
92
+ # Some care must be taken in the code and testing to assure the consistency of the nil/false dichotomy
93
+ #
94
+ # They are defined dynamically due to their number and similarities
95
+ #
96
+
97
+ for street in [:preflop, :flop, :turn, :river]
98
+
99
+ #
100
+ # last agresssion functions
101
+ # true only if player made the last aggressive move on the street
102
+ # nil otherwise
103
+ # never false
104
+ #
105
+ class_eval <<-LAST_AGGR_FUNCTIONS
106
+ def last_aggr_#{street} player
107
+ @last_aggr_player[:#{street}] && (player==@last_aggr_player[:#{street}] ? true : nil)
108
+ end
109
+ LAST_AGGR_FUNCTIONS
110
+
111
+ #
112
+ # make_, call_, and fold_to_ functions, by bet and street street
113
+ #
114
+ for bet in 1..4
115
+ class_eval <<-STREET_AND_BET_FUNCTIONS
116
+ def #{street}_#{bet}bet(player)
117
+ @street_bets[#{street.inspect}] && @street_bets[#{street.inspect}][#{bet}][player]
118
+ end
119
+ def call_#{street}_#{bet}bet(player)
120
+ @call_street_bets[#{street.inspect}] && @call_street_bets[#{street.inspect}][#{bet}][player]
121
+ end
122
+ def fold_to_#{street}_#{bet}bet(player)
123
+ @fold_to_street_bets[#{street.inspect}] && @fold_to_street_bets[#{street.inspect}][#{bet}][player]
124
+ end
125
+ STREET_AND_BET_FUNCTIONS
126
+ end
127
+ end
128
+
129
+ for street in [:flop, :turn, :river]
130
+ last_street = case street
131
+ when :flop then :preflop
132
+ when :turn then :flop
133
+ when :river then :turn
134
+ end
135
+
136
+ #
137
+ # make, call_ and fold_to_ cbet and dbet functions, by street
138
+ #
139
+ # cbets (continuation bets) are first-in bets after making last agression on the previous street
140
+ #
141
+ # dbets (donk bets) are first-in bets, made after calling another player's aggression out of position on the previous street
142
+ #
143
+
144
+ class_eval <<-FTR_FUNCTIONS
145
+ def cbet_#{street}(player)
146
+ last_aggr_#{last_street}(player) && #{street}_1bet(player)
147
+ end
148
+ def fold_to_cbet_#{street}(player)
149
+ @first_aggr_player[:#{street}] && cbet_#{street}(@first_aggr_player[:#{street}]) && fold_to_#{street}_1bet(player)
150
+ # fold_to_#{street}_1bet(player) && @last_aggr_player[:#{last_street}] && cbet_#{street}(@last_aggr_player[:#{last_street}])
151
+ end
152
+ def call_cbet_#{street}(player)
153
+ @first_aggr_player[:#{street}] && cbet_#{street}(@first_aggr_player[:#{street}]) && call_#{street}_1bet(player)
154
+ end
155
+ def dbet_#{street}(player)
156
+ return nil unless @last_aggr_player[:#{last_street}] && @hand_statistics.betting_order?(player, @last_aggr_player[:#{last_street}])
157
+ #{street}_1bet(player)
158
+ end
159
+ def fold_to_dbet_#{street}(player)
160
+ return nil unless @first_aggr_player[:#{street}] && dbet_#{street}(@first_aggr_player[:#{street}])
161
+ fold_to_#{street}_1bet(player)
162
+ end
163
+ def call_dbet_#{street}(player)
164
+ return nil unless @first_aggr_player[:#{street}] && dbet_#{street}(@first_aggr_player[:#{street}])
165
+ call_#{street}_1bet(player)
166
+ end
167
+ FTR_FUNCTIONS
168
+ end
169
+
170
+ def initialize handstatistics
171
+ @street_bets = {}
172
+ @call_street_bets = {}
173
+ @fold_to_street_bets = {}
174
+ @first_aggr_player = {}
175
+ @last_aggr_player = {}
176
+ [:preflop, :flop, :turn, :river].each do|each|
177
+ @street_bets[each] = [{}, {}, {}, {}, {}]
178
+ @call_street_bets[each] = [{}, {}, {}, {}, {}]
179
+ @fold_to_street_bets[each] = [{}, {}, {}, {}, {}]
180
+ end
181
+ super handstatistics
182
+ end
183
+
184
+ def register_player screen_name, street, player
185
+ end
186
+
187
+ def street_transition street
188
+ case street
189
+ when :preflop
190
+ @last_bet = 1
191
+ when :flop, :turn, :river
192
+ @last_bet = 0
193
+ else
194
+ @last_bet = nil
195
+ end
196
+ end
197
+
198
+ def street_transition_for_player street, player
199
+ end
200
+
201
+ def apply_action action, street
202
+ unless @last_bet.nil?
203
+ # puts "apply_action(#{action[:aggression]}, #{street}) with @last_bet == #{@last_bet}"
204
+ if @last_bet.between?(0,4)
205
+ @fold_to_street_bets[street][@last_bet][action[:screen_name]] = action[:description] == "folds"
206
+ @call_street_bets[street][@last_bet][action[:screen_name]] = action[:description] == "calls"
207
+ end
208
+ if @last_bet.between?(0,3)
209
+ @street_bets[street][@last_bet+1][action[:screen_name]] = action[:aggression] == :aggressive
210
+ end
211
+ if action[:aggression] == :aggressive
212
+ @last_bet+=1
213
+ @first_aggr_player[street] ||= action[:screen_name]
214
+ @last_aggr_player[street] = action[:screen_name]
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end