pokerstats 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +18 -0
  5. data/Rakefile +50 -0
  6. data/VERSION +1 -0
  7. data/bin/checkps +190 -0
  8. data/generators/pokerstats/USAGE +1 -0
  9. data/generators/pokerstats/pokerstats_generator.rb +7 -0
  10. data/generators/pokerstats/templates/create_pokerstats.rhtml +28 -0
  11. data/lib/pokerstats.rb +2 -0
  12. data/lib/pokerstats/.gitignore +0 -0
  13. data/lib/pokerstats/hand_constants.rb +27 -0
  14. data/lib/pokerstats/hand_history.rb +38 -0
  15. data/lib/pokerstats/hand_statistics.rb +225 -0
  16. data/lib/pokerstats/hand_statistics_api.rb +64 -0
  17. data/lib/pokerstats/player_statistics.rb +58 -0
  18. data/lib/pokerstats/plugins/aggression_statistics.rb +57 -0
  19. data/lib/pokerstats/plugins/blind_attack_statistics.rb +78 -0
  20. data/lib/pokerstats/plugins/cash_statistics.rb +95 -0
  21. data/lib/pokerstats/plugins/continuation_bet_statistics.rb +68 -0
  22. data/lib/pokerstats/plugins/preflop_raise_statistics.rb +50 -0
  23. data/lib/pokerstats/poker-edge.rb +57 -0
  24. data/lib/pokerstats/pokerstars_file.rb +100 -0
  25. data/lib/pokerstats/pokerstars_hand_history_parser.rb +141 -0
  26. data/pokerstats.gemspec +91 -0
  27. data/spec/file_empty.txt +0 -0
  28. data/spec/file_many_hands.txt +564 -0
  29. data/spec/file_one_hand.txt +52 -0
  30. data/spec/hand_statistics_spec.rb +846 -0
  31. data/spec/hand_statistics_spec_helper.rb +89 -0
  32. data/spec/player_statistics_spec.rb +230 -0
  33. data/spec/pokerstars_file_spec.rb +160 -0
  34. data/spec/pokerstars_hand_history_parser_spec.rb +197 -0
  35. data/spec/spec_helper.rb +9 -0
  36. data/spec/zpokerstars_hand_history_parser_integration.rb.txt +67 -0
  37. metadata +125 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Andrew C. Greenberg
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,18 @@
1
+ = pokerstats
2
+
3
+ A library for extracting, computing and reporting statistics of poker hands parsed from hand history files.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but
13
+ bump version in a commit by itself I can ignore when I pull)
14
+ * Send me a pull request. Bonus points for topic branches.
15
+
16
+ == Copyright
17
+
18
+ Copyright (c) 2009 Andrew C. Greenberg. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "pokerstats"
8
+ gem.summary = %Q{poker hand history statistics library}
9
+ gem.description = %Q{a library for extracting, computing and reporting statistics of poker hands parsed from hand history files}
10
+ gem.email = "wizardwerdna@gmail.com"
11
+ gem.homepage = "http://github.com/wizardwerdna/pokerstats"
12
+ gem.authors = ["Andrew C. Greenberg"]
13
+ gem.add_development_dependency "rspec"
14
+ gem.add_development_dependency "wizardwerdna-pluggable"
15
+ gem.add_dependency "wizardwerdna-pluggable"
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :spec
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ if File.exist?('VERSION')
41
+ version = File.read('VERSION')
42
+ else
43
+ version = ""
44
+ end
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "pokerstats #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.0.1
data/bin/checkps ADDED
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/ruby
2
+ require "getoptlong"
3
+ require "rubygems"
4
+ require "activesupport"
5
+ require "open-uri"
6
+ # ENV['RAILS_ENV'] = ENV['RAILS_ENV'] || 'development'
7
+ # require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
8
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/pokerstats/pokerstars_file")
9
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/pokerstats/player_statistics')
10
+ # require 'pokerstats/pokerstars_file'
11
+ # require 'pokerstats/player_statistics'
12
+
13
+ def search_poker_edge(playername, players_shown = {})
14
+ return if players_shown[playername]
15
+ escaped_playername = URI.escape(playername).gsub(/["'\[\]]/,'\\\\\&').gsub(/[\[\]]/,'\\\\\\\\\&')
16
+ result = `curl -s http://www.poker-edge.com/whoami.php?site=Stars\\&name=#{escaped_playername}`
17
+ if result =~ /(Pre-Flop Tend.*\n)/
18
+ verbose = $1.gsub(/<\/?[^>]*>/, "")
19
+ if verbose =~ /Pre-Flop Tendency: ([^-]*) -/
20
+ preflop = $1
21
+ else
22
+ preflop = "N/A"
23
+ end
24
+ else
25
+ preflop = "N/A (data error)"
26
+ end
27
+ if result =~ /(Player Type.*\n)/
28
+ verbose = $1.gsub(/<\/?[^>]*>/, "")
29
+ if verbose =~ /[Yy]ou are a ([^(]* \(.*\))/
30
+ player_type = $1
31
+ else
32
+ player_type = ""
33
+ end
34
+ else
35
+ player_type = ""
36
+ end
37
+ players_shown[playername] = preflop
38
+ players_shown[playername] += " " + player_type unless player_type.empty?
39
+ end
40
+
41
+ def display_ratio numerator, denominator
42
+ if numerator.nil? or denominator.nil?
43
+ return "***"
44
+ elsif denominator < 9
45
+ return "#{numerator}/#{denominator}"
46
+ else
47
+ return "#{(100.0 * numerator / denominator).to_i}%"
48
+ end
49
+ end
50
+
51
+ def dopsfile(file, players_shown)
52
+ return if File.directory?(file)
53
+ players = {}
54
+ last = nil
55
+ statistics = Pokerstats::PlayerStatistics.new
56
+ Pokerstats::PokerstarsFile.open(file).each do |handrecord|
57
+ begin
58
+ handrecord.parse
59
+ statistics.record(handrecord)
60
+ last = handrecord
61
+ rescue Exception => e
62
+ puts e.message
63
+ end
64
+ end
65
+ return if last.nil?
66
+ players = last.stats.players
67
+ puts
68
+ puts "=" * file.size
69
+ puts file
70
+ puts "=" * file.size
71
+ STDOUT.sync = true
72
+ printf("Searching Poker-Edge: ")
73
+ players.each {|each| printf("%s ", each); search_poker_edge(each, players_shown)}
74
+ printf("\n")
75
+ STDOUT.sync = false
76
+ puts "=" * file.size
77
+ reports = statistics.reports
78
+ printf "%-20s %3s %4s %4s %5s %5s %5s %5s %s\n", "Screen Name", "Num", "VP$%", "PFR%", "Pre/Pos", "BAtt%", "BDef%", "CBet%", "Poker-Edge Description"
79
+ printf "%-20s %-39s %s\n", "-"*20, "-"*39, "-"*47
80
+ players.each do |each|
81
+ report = reports[each]
82
+ # puts report.to_yaml
83
+ t_hands = report[:t_hands]
84
+ vpi_p = display_ratio report[:t_vpip], report[:t_hands]
85
+ pfr_p = display_ratio report[:t_pfr_opportunity_taken], report[:t_pfr_opportunity]
86
+ prefa = report[:t_preflop_passive]. zero? ? 0.0 : 1.0 * report[:t_preflop_aggressive] / report[:t_preflop_passive]
87
+ posfa = report[:t_postflop_passive]. zero? ? 0.0 : 1.0 * report[:t_postflop_aggressive] / report[:t_postflop_passive]
88
+ batt_p = display_ratio report[:t_blind_attack_opportunity_taken], report[:t_blind_attack_opportunity]
89
+ bdef_p = display_ratio report[:t_blind_defense_opportunity_taken], report[:t_blind_defense_opportunity]
90
+ cbet_p = display_ratio report[:t_cbet_opportunity_taken], report[:t_cbet_opportunity]
91
+ description = players_shown[each][/\(.*\)/]
92
+ description ||= ""
93
+ description.gsub!("Passive", "P")
94
+ description.gsub!("Aggressive", "A")
95
+ description.gsub!("Tight", "T")
96
+ description.gsub!("Loose", "L")
97
+ players_shown[each].gsub!(/\(.*\)/, description)
98
+ printf "%-20s %3d %4s %4s %2.1f/%2.1f %5s %5s %5s %s\n", each, t_hands, vpi_p, pfr_p, prefa, posfa, batt_p, bdef_p, cbet_p, players_shown[each]
99
+ end
100
+ puts "=" * file.size
101
+ GC.start
102
+ # puts last.reports.keys.inspect
103
+ # puts
104
+ # # puts
105
+ # # puts "=" * 90
106
+ # # puts last.path
107
+ # # players.each {|each| display(each, players_shown)}
108
+ # # puts
109
+ # # puts "=" * 90
110
+ # # puts "PLAYERS NOW AT THIS TABLE"
111
+ # # puts "=" * 90
112
+ # # printf "%-20s %3s %4s %4s %5s %s\n", "Screen Name", "Num", "VP$%", "PFR%", "Pre/Pos", "Poker-Edge Description"
113
+ # # printf "%-20s %-14s %s\n", "-"*20, "-"*21, "-"*47
114
+ # # players.each do |each|
115
+ # # description = players_shown[each][/\(.*\)/]
116
+ # # description ||= ""
117
+ # # description.gsub!("Passive", "P")
118
+ # # description.gsub!("Aggressive", "A")
119
+ # # description.gsub!("Tight", "T")
120
+ # # description.gsub!("Loose", "L")
121
+ # # players_shown[each].gsub!(/\(.*\)/, description)
122
+ # # printf "%-20s %3d %3d%% %3d%% %2.1f/%2.1f %s\n", each,
123
+ # # hands[each], (100.0 * vpip[each])/hands[each], (100.0 * pfr[each])/hands[each],
124
+ # # preflop_passive[each].zero? ? 0.0 : (1.0 * preflop_aggressive[each]) / preflop_passive[each],
125
+ # # postflop_passive[each].zero? ? 0.0 : (1.0 * postflop_aggressive[each]) / postflop_passive[each],
126
+ # # players_shown[each]
127
+ # # end
128
+ # # puts "=" * 90
129
+ # # puts "information on #{hands.size} players collected"
130
+ # # hands = vpip = pfr = sawflop = preflop_aggressive = preflop_passive = nil
131
+
132
+ # # puts
133
+ end
134
+
135
+ def newpsfiles(user, time)
136
+ Dir["/Users/#{user}/Library/Application Support/PokerStars/HandHistory/**/*"].select{|each| File.mtime(each) > time}
137
+ end
138
+
139
+ def getpsdata(user, time, players_shown)
140
+ puts "Loading PokerStars HandHistories that have changed since #{time}"
141
+ while (files = newpsfiles(user, time)).empty?
142
+ sleep 1
143
+ end
144
+ puts files.inspect
145
+ files.each {|each| dopsfile(each, players_shown)}
146
+ end
147
+
148
+ def display_recent_pokerstars_results user
149
+ players_shown = {}
150
+ getpsdata(user, Time.now - 3000, players_shown)
151
+ loop {getpsdata(user, Time.now, players_shown)}
152
+ end
153
+
154
+ def display_poker_edge_results
155
+ players_shown = {}
156
+ $*.each do |playername|
157
+ puts "Poker Edge Search for #{playername}"
158
+ search_poker_edge(playername, players_shown)
159
+ puts "="*80
160
+ printf "%-20s %s\n", playername, players_shown[playername]
161
+ puts "="*80
162
+ end
163
+ end
164
+
165
+ opts = GetoptLong.new(
166
+ [ "--help", "-h", GetoptLong::NO_ARGUMENT],
167
+ [ "--version", "-v", GetoptLong::NO_ARGUMENT],
168
+ [ "--user", "-u", GetoptLong::OPTIONAL_ARGUMENT]
169
+ )
170
+
171
+ version_file = File.expand_path(File.dirname(__FILE__) + "/../VERSION")
172
+ print "Pokerstars HandHistory Statistics, v#{File.read(version_file).chop}\n"
173
+ print "Copyright (c) 2009 Andrew C. Greenberg All Rights Reserved\n"
174
+ user = `whoami`.chop
175
+ opts.each do |opt, arg|
176
+ case opt
177
+ when "--help", "--usage"
178
+ print "#{$0} playername {--user username} {--player playername} {--help } {--version}\n"
179
+ when "--version"
180
+ print "Pokerstars version #{File.read(version_file).chop}\n"
181
+ when "--user"
182
+ user = arg unless arg.empty?
183
+ end
184
+ end
185
+
186
+ if $*.empty?
187
+ display_recent_pokerstars_results user
188
+ else
189
+ display_poker_edge_results
190
+ end
@@ -0,0 +1 @@
1
+ foo bie doobie
@@ -0,0 +1,7 @@
1
+ class PokerstatsGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template "create_pokerstats.rhtml", "db/migrate", :migration_file_name => "create_pokerstats"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ <%
2
+ require 'rubygems'
3
+ require 'pokerstats/hand_statistics'
4
+ %>
5
+
6
+ class CreatePokerstats < ActiveRecord::Migration
7
+ def self.up
8
+ create_table :hand_statistics do |t|
9
+ <%= Pokerstats::HandStatistics.hand_statistics_migration_data -%>
10
+ t.timestamps
11
+ end
12
+ create_table :player_statistics do |t|
13
+ t.integer :hand_statistic_id
14
+ t.integer :player_id
15
+ <%= Pokerstats::HandStatistics.player_statistics_migration_data -%>
16
+ t.timestamps
17
+ end
18
+ create_table :players do |t|
19
+ t.string :name
20
+ end
21
+ end
22
+
23
+ def self.down
24
+ drop_table :players
25
+ drop_table :player_statistics
26
+ drop_table :hand_statistics
27
+ end
28
+ end
data/lib/pokerstats.rb ADDED
@@ -0,0 +1,2 @@
1
+ module Pokerstats
2
+ end
File without changes
@@ -0,0 +1,27 @@
1
+ module Pokerstats
2
+ module HandConstants
3
+ HAND_REPORT_SPECIFICATION = [
4
+ # [key, sql_type, function]
5
+ [:session_filename, 'string'],
6
+ [:starting_at, 'datetime'],
7
+ [:name, 'string'],
8
+ [:description, 'string'],
9
+ [:sb, 'decimal'],
10
+ [:bb, 'decimal'],
11
+ [:board, 'string'],
12
+ [:total_pot, 'decimal'],
13
+ [:rake, 'decimal'],
14
+ [:played_at, 'datetime'],
15
+ [:tournament, 'string']
16
+ ]
17
+ HAND_INFORMATION_KEYS = HAND_REPORT_SPECIFICATION.map{|each| each.first}
18
+ HAND_RECORD_INCOMPLETE_MESSAGE = "hand record is incomplete"
19
+ PLAYER_RECORDS_NO_PLAYER_REGISTERED = "no players have been registered"
20
+ PLAYER_RECORDS_DUPLICATE_PLAYER_NAME = "player screen_name has been registered twice"
21
+ PLAYER_RECORDS_NO_BUTTON_REGISTERED = "no button has been registered"
22
+ PLAYER_RECORDS_UNREGISTERED_PLAYER = "player has not been registered"
23
+ PLAYER_RECORDS_OUT_OF_BALANCE = "hand record is out of balance"
24
+
25
+ MAX_SEATS = 12
26
+ end
27
+ end
@@ -0,0 +1,38 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/hand_constants")
2
+ require File.expand_path(File.dirname(__FILE__) + "/pokerstars_hand_history_parser")
3
+
4
+ module Pokerstats
5
+ class HandHistory
6
+ attr_accessor :lines, :source, :position, :stats
7
+ def initialize lines, source, position, parser_class = PokerstarsHandHistoryParser
8
+ @lines = lines
9
+ @source = source
10
+ @position = position
11
+ @parsed = false
12
+ @parser_class = parser_class
13
+ @stats = HandStatistics.new
14
+ end
15
+
16
+ def parsed?
17
+ @parsed
18
+ end
19
+
20
+ def parse
21
+ @parser = @parser_class.new(@stats)
22
+ @lines.each do |each_line|
23
+ begin
24
+ @parser.parse(each_line)
25
+ rescue => e
26
+ raise "#{@source}:#{position}: #{e.message}"
27
+ end
28
+ end
29
+ @stats.update_hand :session_filename => source, :starting_at => position
30
+ @parsed = true
31
+ end
32
+
33
+ def reports
34
+ parse unless parsed?
35
+ @stats.reports
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,225 @@
1
+ require 'rubygems'
2
+ require 'pluggable'
3
+ require 'bigdecimal'
4
+ require 'bigdecimal/util'
5
+ require 'activesupport'
6
+ require File.expand_path(File.dirname(__FILE__) + '/hand_constants')
7
+ require File.expand_path(File.dirname(__FILE__) + '/hand_statistics_api')
8
+ module Pokerstats
9
+ class HandStatistics
10
+ include Pluggable
11
+ include HandConstants
12
+ plugin_include_module HandStatisticsAPI
13
+ def initialize
14
+ install_plugins self
15
+ @hand_information = {}
16
+ @player_hashes = []
17
+ @button_player_index = nil
18
+ @cached_player_position = nil
19
+ @street_state = nil
20
+ street_transition(:prelude)
21
+ end
22
+
23
+ ##
24
+ # Hand Information
25
+ ##
26
+
27
+ def hand_record
28
+ raise "#{HAND_RECORD_INCOMPLETE_MESSAGE}: #{(HAND_INFORMATION_KEYS - @hand_information.keys).inspect}" unless (HAND_INFORMATION_KEYS - @hand_information.keys).empty?
29
+ HAND_INFORMATION_KEYS.inject({}) do |hash, key|
30
+ hash.merge!(key => @hand_information[key])
31
+ end
32
+ end
33
+
34
+ def update_hand update
35
+ street_transition(update[:street]) unless update[:street] == @street_state
36
+ @hand_information.update(update)
37
+ self
38
+ end
39
+
40
+ ##
41
+ # Player Information
42
+ ##
43
+
44
+ def player_records_without_validation
45
+ @player_hashes
46
+ end
47
+
48
+ def player_records
49
+ raise PLAYER_RECORDS_NO_PLAYER_REGISTERED if players.empty?
50
+ raise PLAYER_RECORDS_NO_BUTTON_REGISTERED if button.nil?
51
+ raise PLAYER_RECORDS_OUT_OF_BALANCE if out_of_balance
52
+ self.player_records_without_validation
53
+ end
54
+
55
+ def players
56
+ @player_hashes.sort{|a, b| a[:seat] <=> b[:seat]}.collect{|each| each[:screen_name]}
57
+ end
58
+
59
+ def number_players
60
+ @player_hashes.size
61
+ end
62
+
63
+ def register_player player
64
+ screen_name = player[:screen_name]
65
+ raise "#{PLAYER_RECORDS_DUPLICATE_PLAYER_NAME}: #{screen_name.inspect}" if players.member?(screen_name)
66
+ @cached_player_position = nil
67
+ @player_hashes << player
68
+ plugins.each{|each| each.register_player(screen_name, @street_state)} #why the second parameter?
69
+ street_transition_for_player(@street_state, screen_name)
70
+ end
71
+
72
+ ###
73
+ # Street state information
74
+ ##
75
+
76
+ def street
77
+ @street_state
78
+ end
79
+
80
+ def street_transition street
81
+ @street_state = street
82
+ plugins.each{|each| each.street_transition(street)}
83
+ players.each {|player| street_transition_for_player(street, player)}
84
+ end
85
+
86
+ def street_transition_for_player street, screen_name
87
+ plugins.each{|each| each.street_transition_for_player(street, screen_name)}
88
+ end
89
+
90
+ ##
91
+ # Button and Position Information
92
+ ##
93
+
94
+ def register_button button_index
95
+ @cached_player_position = nil
96
+ @button_player_index = button_index
97
+ end
98
+
99
+ def button
100
+ @button_player_index
101
+ end
102
+
103
+ def button_relative_seat(player_hash)
104
+ (player_hash[:seat] + MAX_SEATS - @button_player_index) % MAX_SEATS
105
+ end
106
+
107
+ # long computation is cached, which cache is cleared every time a new player is registered
108
+ def calculate_player_position screen_name
109
+ @cached_player_position = {}
110
+ @player_hashes.sort!{|a,b| button_relative_seat(a) <=> button_relative_seat(b)}
111
+ @player_hashes = [@player_hashes.pop] + @player_hashes unless @player_hashes.first[:seat] == @button_player_index
112
+ @player_hashes.each_with_index{|player, index| player[:position] = index, @cached_player_position[player[:screen_name]] = index}
113
+ @cached_player_position[screen_name]
114
+ end
115
+
116
+ def position screen_name
117
+ (@cached_player_position && @cached_player_position[screen_name]) || calculate_player_position(screen_name)
118
+ end
119
+
120
+ def button?(screen_name)
121
+ position(screen_name) && position(screen_name).zero?
122
+ end
123
+
124
+ # The cutoff position is defined as the player to the left of the button if there are three players, otherwise nil
125
+ def cutoff_position
126
+ # formerly: (number_players > 3) && (-1 % number_players)
127
+ -1 % number_players if number_players > 3
128
+ end
129
+
130
+ def cutoff?(screen_name)
131
+ position(screen_name) == cutoff_position
132
+ end
133
+
134
+ def blind?(screen_name)
135
+ (sbpos?(screen_name) || bbpos?(screen_name)) and !posted(screen_name).zero?
136
+ end
137
+
138
+ def sbpos?(screen_name)
139
+ (number_players > 2) && position(screen_name) == 1
140
+ end
141
+
142
+ def bbpos?(screen_name)
143
+ (number_players > 2) && position(screen_name) == 2
144
+ end
145
+
146
+ def attacker?(screen_name)
147
+ (number_players > 2) && (button?(screen_name) || cutoff?(screen_name))
148
+ end
149
+
150
+
151
+ ##
152
+ # Action Information
153
+ ##
154
+ def aggression(description)
155
+ case description
156
+ when /call/
157
+ :passive
158
+ when /raise/
159
+ :aggressive
160
+ when /bet/
161
+ :aggressive
162
+ when /fold/
163
+ :fold
164
+ when /check/
165
+ :check
166
+ else
167
+ :neutral
168
+ end
169
+ end
170
+
171
+ def register_action(screen_name, description, options={})
172
+ raise "#{PLAYER_RECORDS_UNREGISTERED_PLAYER}: #{screen_name.inspect}" unless players.member?(screen_name)
173
+ plugins.each do |each|
174
+ each.apply_action(
175
+ {:screen_name => screen_name, :description => description, :aggression => aggression(description)}.update(options),
176
+ @street_state)
177
+ end
178
+ end
179
+
180
+ ##
181
+ # Reporting Information
182
+ ##
183
+
184
+ def report_player(player)
185
+ result = {}
186
+ plugins.each {|each| result.merge!(each.report(player))}
187
+ result
188
+ end
189
+
190
+ def reports
191
+ result = {}
192
+ players.each{|each| result[each] = report_player(each)}
193
+ result
194
+ end
195
+
196
+ def report_hand_information
197
+ @hand_information
198
+ end
199
+
200
+ def self.hand_statistics_migration_data
201
+ HAND_REPORT_SPECIFICATION.inject("") do |string, each|
202
+ string + "t.#{each[1]}\t#{each[0].inspect}\n"
203
+ end
204
+ end
205
+
206
+ def self.player_statistics_migration_data
207
+ plugin_factory.inject("") do |str, each_plugin|
208
+ tstr = "# from #{each_plugin.inspect}\n"
209
+ each_plugin.report_specification.each do |each_datum|
210
+ tstr += "t.#{each_datum[1]}\t#{each_datum[0].inspect}\n"
211
+ end
212
+ str = str + tstr
213
+ end
214
+ end
215
+ private
216
+ def method_missing symbol, *args
217
+ plugins.send symbol, *args
218
+ end
219
+ end
220
+ Dir[File.dirname(__FILE__) + "/plugins/*_statistics.rb"].each {|filename| require File.expand_path(filename)}
221
+ HandStatistics.delegate_plugin_public_methods_except HandStatisticsAPI.public_methods
222
+ end
223
+ # Load Plugins and Delegate non-api public methods to plugins
224
+ # puts Pokerstats::HandStatistics.hand_statistics_migration_data
225
+ # puts Pokerstats::HandStatistics.player_statistics_migration_data