pokerstats 2.0.1

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.
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