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.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/bin/checkps +190 -0
- data/generators/pokerstats/USAGE +1 -0
- data/generators/pokerstats/pokerstats_generator.rb +7 -0
- data/generators/pokerstats/templates/create_pokerstats.rhtml +28 -0
- data/lib/pokerstats.rb +2 -0
- data/lib/pokerstats/.gitignore +0 -0
- data/lib/pokerstats/hand_constants.rb +27 -0
- data/lib/pokerstats/hand_history.rb +38 -0
- data/lib/pokerstats/hand_statistics.rb +225 -0
- data/lib/pokerstats/hand_statistics_api.rb +64 -0
- data/lib/pokerstats/player_statistics.rb +58 -0
- data/lib/pokerstats/plugins/aggression_statistics.rb +57 -0
- data/lib/pokerstats/plugins/blind_attack_statistics.rb +78 -0
- data/lib/pokerstats/plugins/cash_statistics.rb +95 -0
- data/lib/pokerstats/plugins/continuation_bet_statistics.rb +68 -0
- data/lib/pokerstats/plugins/preflop_raise_statistics.rb +50 -0
- data/lib/pokerstats/poker-edge.rb +57 -0
- data/lib/pokerstats/pokerstars_file.rb +100 -0
- data/lib/pokerstats/pokerstars_hand_history_parser.rb +141 -0
- data/pokerstats.gemspec +91 -0
- data/spec/file_empty.txt +0 -0
- data/spec/file_many_hands.txt +564 -0
- data/spec/file_one_hand.txt +52 -0
- data/spec/hand_statistics_spec.rb +846 -0
- data/spec/hand_statistics_spec_helper.rb +89 -0
- data/spec/player_statistics_spec.rb +230 -0
- data/spec/pokerstars_file_spec.rb +160 -0
- data/spec/pokerstars_hand_history_parser_spec.rb +197 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/zpokerstars_hand_history_parser_integration.rb.txt +67 -0
- metadata +125 -0
data/.document
ADDED
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,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
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
|