pokerstats 2.0.13 → 2.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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