hand_rank 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 30f8feb7d175b7d18eea2d0ec62b0dfc5be18385
4
+ data.tar.gz: 3dd0df03ee14c44b5c124cfea0e1eeb9b3856446
5
+ SHA512:
6
+ metadata.gz: 583bb9aa4b113c1869cc47328ea3a69aeee27e0ff9f3df01055cb19adfa23403b5b254cbc979046c3ad60b1018304eb4054ac1478fc961ff6325d6f0b222a46c
7
+ data.tar.gz: a1757fc164bfce4d8f2292c74a8e820ddec579f5117db0a0d9ab4a529fc479137dcec80e7b50545cfa1aa73fa853a4e168b04495d2122c4493ef889527828484
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /Gemfile.lock
2
+ /coverage/
3
+ /spec/reports/
4
+ /tmp/
5
+ *.bundle
6
+ *.so
7
+ *.o
8
+ *.a
9
+ mkmf.log
10
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hand_rank.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Jonas Schubert Erlandsson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # WIP
2
+
3
+ Please read this entire readme before using the gem, thank you.
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/hand_rank.svg)](http://badge.fury.io/rb/hand_rank)
6
+ [![Code Climate](https://codeclimate.com/github/replaygaming/hand_rank/badges/gpa.svg)](https://codeclimate.com/github/replaygaming/hand_rank)
7
+ [![Test Coverage](https://codeclimate.com/github/replaygaming/hand_rank/badges/coverage.svg)](https://codeclimate.com/github/replaygaming/hand_rank/coverage)
8
+
9
+
10
+ # HandRank
11
+
12
+ The hand_rank gem is a Ruby, with C extension, implementation of the 2 plus 2
13
+ hand evaluation algorithm for Texam Hold'em hands (or any hands following the
14
+ same ranking order).
15
+
16
+ The algorithm uses a large, 130+MB, lookup table and ranks a hand 5-7 card hand
17
+ by doing 5-7 jumps in hte table and landing on the cell that represents the hand
18
+ you walked to get there, regardless of the order of the steps.
19
+
20
+ Each, final, cell contains the Cactus Kev hands equivalence number, a number
21
+ describing the hand among every possible hand. This number can then be compared
22
+ to any other hands equivalence number to see who is the winner.
23
+
24
+ The 2 plus 2 version also reorders the equivalence classes to allow for getting
25
+ a rank within a category. So you can split the rank into a category and a rank
26
+ within that category.
27
+
28
+ ## Refrence
29
+
30
+ For some not so light reading on the subject I suggest you check out the
31
+ original sources for all the pieces as well as a comparison roundup from, the
32
+ now defunct, codingthewheel site:
33
+
34
+ [Cactus Kev's original Poker Hand Evaluator](http://suffe.cool/poker/evaluator.html)
35
+ describes the ordering of hands in the lookup table and the concept of the hand
36
+ equivalence classes.
37
+
38
+ [Paul Senzee - "Some Perfect Hash"](http://www.paulsenzee.com/2006/06/some-perfect-hash.html)
39
+ describes a hashing algorithm to improve on the size of the Cactus Kev lookup
40
+ table and is the same that is used for the lookup table to the 2 plus 2 solution.
41
+
42
+ [The original thread on the 2 plus 2 forums]() where it all came together. It's
43
+ long and threatens to become a bit flamy at times. But in the end I think it is
44
+ a great example of what you can accomplich on the internet, with strangers if
45
+ you are all prepared to act for the greater good.
46
+
47
+ And finally [the poker evaluator roundup from codingthewheel.com](http://web.archive.org/web/20150113024316/http://codingthewheel.com/archives/poker-hand-evaluator-roundup) unfortunately via the
48
+ waybackmachine since the original site is no longer live :/ Great roundup, with
49
+ code, of all the relevant approaches. The code from the roundup is still
50
+ [available on Github](https://github.com/christophschmalhofer/poker/tree/master/XPokerEval)
51
+ so there is that ...
52
+
53
+ ## Installation
54
+
55
+ > When the gem is finished this is what you will do. Right now you also need to
56
+ > manually extract the `ranks.data.zip` in `lib/hand_rank/` to `ranks.data` in
57
+ > the same folder. This is the lookup table and without it you get nothing :)
58
+
59
+ Add this line to your application's Gemfile:
60
+
61
+ ```ruby
62
+ gem 'hand_rank'
63
+ ```
64
+
65
+ And then execute:
66
+
67
+ $ bundle
68
+
69
+ Or install it yourself as:
70
+
71
+ $ gem install hand_rank
72
+
73
+ ## Usage
74
+ ### Hand and card format
75
+ To create a hand you need the cards absolute values. You somehow have to convert
76
+ your representation of a hand into an array of integers where each integer
77
+ represents a card following this encoding:
78
+
79
+ ```ruby
80
+ ABSOLUTE_CARD_VALUE_LOOKUP = {
81
+ # spacer, A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A
82
+ club: [ nil, 49, 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49 ],
83
+ diamond: [ nil, 50, 2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50 ],
84
+ heart: [ nil, 51, 3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51 ],
85
+ spade: [ nil, 52, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52 ],
86
+ }
87
+ ```
88
+
89
+ This table allows you to lookup and card by suit and value (from 1 to 14,
90
+ allowing for both high and low aces). In our implementation the `Card` class has
91
+ an `abs`method and the `Hand` class impersonates an array. Since this gem will
92
+ actually run `map!(&:abs)` on the input before handing it off to the C code our
93
+ setup allows us to give it a real `Hand` and just leave it to the magic to
94
+ transform that into an array of integers.
95
+
96
+ It will still work fine if you feed it an array of integers though. Arrays
97
+ respond to `map` and fixnum responds to `abs` so it is golden either way.
98
+
99
+ ### Operations
100
+
101
+ ```ruby
102
+ # A hand of AH KH 9D 4C JS QD 10H
103
+ cards_array = [ 51, 47, 30, 9, 40, 42, 35 ]
104
+
105
+ rank = HandRank.get( cards_array )
106
+ # 20490
107
+
108
+ categories = [
109
+ "invalid_hand",
110
+ "high_card",
111
+ "one_pair",
112
+ "two_pairs",
113
+ "three_of_a_kind",
114
+ "straight",
115
+ "flush",
116
+ "full_house",
117
+ "four_of_a_kind",
118
+ "straight_flush"
119
+ ]
120
+
121
+ HandRank.category( rank )
122
+ # 5
123
+
124
+ HandRank.rank_in_category( rank )
125
+ # 10
126
+
127
+ HandRank.category_key( rank )
128
+ # "straight"
129
+
130
+ puts HandRank.explain( rank )
131
+ # => The hand is a straight
132
+ # Rank: 20490 Category: 5 Rank in category: 10
133
+ ```
134
+
135
+ ## Timing
136
+ Some simple timing might be in order.
137
+
138
+ ```ruby
139
+ require 'benchmark'
140
+
141
+ n = 8000000
142
+ a = [1,2,3,4,5,6,7]
143
+ Benchmark.bm(7) do |x|
144
+ x.report('ranking') do
145
+ for i in 1..n
146
+ HandRank.get(a)
147
+ end
148
+ end
149
+ x.report('"s"+"s"') do
150
+ for i in 1..n
151
+ "Hello" + "world!"
152
+ end
153
+ end
154
+ end
155
+ ```
156
+
157
+ Running on a 2015 MacBook pro gives these results:
158
+
159
+ ```
160
+ user system total real
161
+ ranking 0.900000 0.000000 0.900000 ( 0.907518)
162
+ "s"+"s" 1.850000 0.000000 1.850000 ( 1.856820)
163
+ ```
164
+
165
+ So ranking a hand is about twice as fast as a string concatenation. On the test
166
+ machine it can rank over 8 million hands a second, that is one hand every 8 or
167
+ so microseconds.
168
+
169
+ ## Contributing
170
+
171
+ 1. Fork it ( https://github.com/[my-github-username]/hand_rank/fork )
172
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
173
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
174
+ 4. Push to the branch (`git push origin my-new-feature`)
175
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/extensiontask"
3
+
4
+ Rake::ExtensionTask.new "hand_rank" do |ext|
5
+ ext.lib_dir = "lib/hand_rank"
6
+ end
@@ -0,0 +1,3 @@
1
+ require "mkmf"
2
+ $CFLAGS+=" -O3"
3
+ create_makefile "hand_rank/hand_rank"
@@ -0,0 +1,200 @@
1
+ #include <ruby.h>
2
+ #include <unistd.h>
3
+ #include <stdio.h>
4
+ #include <stdlib.h>
5
+
6
+ // The one and only lookup table.
7
+ int HR[32487834];
8
+ const char * const HAND_CATEGORY_KEYS[] = { "invalid_hand", "high_card", "one_pair", "two_pairs", "three_of_a_kind", "straight", "flush", "full_house", "four_of_a_kind", "straight_flush" };
9
+
10
+ /******************************************************************************/
11
+ /* These are pure C functions and should not deal with any Ruby values or */
12
+ /* conversions. */
13
+ /******************************************************************************/
14
+
15
+ char* concat( char* first, char* second ){
16
+ size_t first_length = strlen( first );
17
+ size_t second_length = strlen( second );
18
+ int both_length = first_length + second_length + 1;
19
+ char* both = (char*) malloc( both_length * sizeof( char ));
20
+
21
+ strcpy( both, first );
22
+ strcat( both, second );
23
+
24
+ return both;
25
+ }
26
+
27
+ char* with_path( char* file_name ){
28
+ char* path = get_path_from_ruby();
29
+
30
+ return concat( path, file_name );
31
+ }
32
+
33
+ char* hand_category_key( int index ){
34
+ size_t length = strlen( HAND_CATEGORY_KEYS[ index ]);
35
+ char* name = (char*) malloc( length * sizeof( char ));
36
+
37
+ strcpy( name, HAND_CATEGORY_KEYS[ index ]);
38
+
39
+ return name;
40
+ }
41
+
42
+ char* c_hand_category_key( int index ){
43
+ size_t length = strlen( HAND_CATEGORY_KEYS[ index ]);
44
+ char* key = (char*) malloc( length * sizeof( char ));
45
+
46
+ strcpy( key, HAND_CATEGORY_KEYS[ index ]);
47
+
48
+ return key;
49
+ }
50
+
51
+ void convert_ruby_array_to_c_array( VALUE rb_cards, int *c_cards, int length ){
52
+ int i = 0;
53
+
54
+ for( int i = 0; i < length; i++ ){
55
+ c_cards[ i ] = NUM2INT( rb_ary_entry( rb_cards, i ));
56
+ }
57
+ }
58
+
59
+ int c_rank_7_card_hand( int* cards ){
60
+ int p = HR[53 + *cards++];
61
+ p = HR[p + *cards++];
62
+ p = HR[p + *cards++];
63
+ p = HR[p + *cards++];
64
+ p = HR[p + *cards++];
65
+ p = HR[p + *cards++];
66
+ p = HR[p + *cards++];
67
+ return p;
68
+ }
69
+
70
+ int c_rank_6_card_hand( int* cards ){
71
+ int p = HR[53 + *cards++];
72
+ p = HR[p + *cards++];
73
+ p = HR[p + *cards++];
74
+ p = HR[p + *cards++];
75
+ p = HR[p + *cards++];
76
+ p = HR[p + *cards++];
77
+ return HR[p];
78
+ }
79
+
80
+ int c_rank_5_card_hand( int* cards ){
81
+ int p = HR[53 + *cards++];
82
+ p = HR[p + *cards++];
83
+ p = HR[p + *cards++];
84
+ p = HR[p + *cards++];
85
+ p = HR[p + *cards++];
86
+ return HR[p];
87
+ }
88
+
89
+ int c_rank_hand( int* cards, int length ){
90
+ int result;
91
+
92
+ switch( length ){
93
+ case 5 :
94
+ result = c_rank_5_card_hand( cards );
95
+ break;
96
+ case 6 :
97
+ result = c_rank_6_card_hand( cards );
98
+ break;
99
+ default :
100
+ result = c_rank_7_card_hand( cards );
101
+ }
102
+
103
+ return result;
104
+ }
105
+
106
+ int c_load_lut( char* path ){
107
+ memset( HR, 0, sizeof( HR ));
108
+
109
+ FILE * fin = fopen( path, "rb" );
110
+
111
+ if( fin == NULL ){
112
+ return -1;
113
+ }
114
+
115
+ size_t bytesread = fread( HR, sizeof( HR ), 1, fin );
116
+ fclose( fin );
117
+
118
+ return 0;
119
+ }
120
+
121
+ /******************************************************************************/
122
+ /* These are Ruby functions, in C, and deal with the convertion of values to */
123
+ /* and from the two contexts. */
124
+ /******************************************************************************/
125
+
126
+ VALUE rb_rank( VALUE klass, VALUE rb_num_ary ){
127
+ int length = RARRAY_LEN( rb_num_ary );
128
+ int c_cards[ length ];
129
+
130
+ convert_ruby_array_to_c_array( rb_num_ary, c_cards, length );
131
+
132
+ return INT2NUM( c_rank_hand( c_cards, length ));
133
+ }
134
+
135
+ VALUE rb_category( VALUE klass, VALUE rb_rank ){
136
+ int rank = NUM2INT( rb_rank );
137
+ int category = rank >> 12;
138
+
139
+ return INT2NUM( category );
140
+ }
141
+
142
+ VALUE rb_rank_in_category( VALUE klass, VALUE rb_rank ){
143
+ int rank = NUM2INT( rb_rank );
144
+ int rank_in_category = rank & 0x00000FFF;
145
+
146
+ return INT2NUM( rank_in_category );
147
+ }
148
+
149
+ VALUE rb_category_key( VALUE klass, VALUE rb_rank ){
150
+ int rank = NUM2INT( rb_rank );
151
+ int category = rank >> 12;
152
+
153
+ char* key = c_hand_category_key( category );
154
+
155
+ return rb_str_new2( key );
156
+ }
157
+
158
+ VALUE rb_explain( VALUE klass, VALUE rb_rank ){
159
+ int rank = NUM2INT( rb_rank );
160
+ int category = rank >> 12;
161
+ int rank_in_category = rank & 0x00000FFF;
162
+ VALUE category_key = rb_category_key( klass, rb_rank );
163
+
164
+ VALUE result = rb_sprintf( "The hand is a %"PRIsVALUE"\nRank: %d Category: %d Rank in category: %d", category_key, rank, category, rank_in_category );
165
+
166
+ return result;
167
+ }
168
+
169
+ char* rb_get_path(){
170
+ VALUE cHandRank = rb_const_get(rb_cObject, rb_intern("HandRank"));
171
+ ID sym_mymodule = rb_intern( "HOME" );
172
+ VALUE home = rb_const_get( cHandRank, sym_mymodule );
173
+ return StringValueCStr( home );
174
+ }
175
+
176
+ void rb_load_lut(){
177
+ with_path("ranks.data")
178
+ if( c_load_lut() == -1 ){
179
+ rb_raise( rb_eIOError, "could not open data file.");
180
+ exit( 1 );
181
+ }
182
+ }
183
+
184
+ /******************************************************************************/
185
+ /* This is the "magical" setup function, the main entry point, that is */
186
+ /* responsible for exporting our C function to the Ruby context. */
187
+ /******************************************************************************/
188
+
189
+ void Init_hand_rank( void ){
190
+
191
+ rb_load_lut();
192
+
193
+ VALUE cHandRank = rb_const_get(rb_cObject, rb_intern("HandRank"));
194
+
195
+ rb_define_singleton_method( cHandRank, "rank", rb_rank, 1 );
196
+ rb_define_singleton_method( cHandRank, "category", rb_category, 1 );
197
+ rb_define_singleton_method( cHandRank, "rank_in_category", rb_rank_in_category, 1 );
198
+ rb_define_singleton_method( cHandRank, "category_key", rb_category_key, 1 );
199
+ rb_define_singleton_method( cHandRank, "explain", rb_explain, 1 );
200
+ }
data/hand_rank.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hand_rank/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hand_rank"
8
+ spec.version = HandRank::VERSION
9
+ spec.authors = "Jonas Schubert Erlandsson"
10
+ spec.email = "jonas.schubert.erlandsson@my-codeworks.com"
11
+ spec.summary = "Implements fast poker hand ranking algorithms in C and exposes them through a Ruby API."
12
+ spec.description = "This gem implements the 2 plus 2 forum algorith for ranking \"normal\" poker hands, such that are used in Texas Hold'em for example. Support for more hand types, such as high/low hands, will be added in future versions."
13
+ spec.homepage = "https://github.com/replaygaming/hand_rank"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ # spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.extensions = ["ext/hand_rank/extconf.rb"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rake-compiler", "~> 0.9"
25
+ end
Binary file
@@ -0,0 +1,221 @@
1
+ root_dir = File.expand_path File.dirname(__FILE__)
2
+ lib_dir = File.join( root_dir, 'lib' )
3
+ $LOAD_PATH << lib_dir
4
+
5
+ require 'hand_rank'
6
+
7
+ class Card
8
+ class SuitError < ArgumentError
9
+ def to_s
10
+ "Suit must be one of Replay::Poker::Card::(#{SUIT_NAMES.map{|_,s| s[0..-2].upcase }.join('|') })"
11
+ end
12
+ end
13
+
14
+ class ValueError < ArgumentError
15
+ def to_s
16
+ "Value must be a Fixnum between 1 and 14 or any of the strings: #{ VALUE_NAMES[2..-1].join(', ') }"
17
+ end
18
+ end
19
+
20
+ AceOfSpades = :"🂡"
21
+ TwoOfSpades = :"🂢"
22
+ ThreeOfSpades = :"🂣"
23
+ FourOfSpades = :"🂤"
24
+ FiveOfSpades = :"🂥"
25
+ SixOfSpades = :"🂦"
26
+ SevenOfSpades = :"🂧"
27
+ EightOfSpades = :"🂨"
28
+ NineOfSpades = :"🂩"
29
+ TenOfSpades = :"🂪"
30
+ JackOfSpades = :"🂫"
31
+ QueenOfSpades = :"🂭"
32
+ KingOfSpades = :"🂮"
33
+
34
+ AceOfHearts = :"🂱"
35
+ TwoOfHearts = :"🂲"
36
+ ThreeOfHearts = :"🂳"
37
+ FourOfHearts = :"🂴"
38
+ FiveOfHearts = :"🂵"
39
+ SixOfHearts = :"🂶"
40
+ SevenOfHearts = :"🂷"
41
+ EightOfHearts = :"🂸"
42
+ NineOfHearts = :"🂹"
43
+ TenOfHearts = :"🂺"
44
+ JackOfHearts = :"🂻"
45
+ QueenOfHearts = :"🂽"
46
+ KingOfHearts = :"🂾"
47
+
48
+ AceOfDiamonds = :"🃁"
49
+ TwoOfDiamonds = :"🃂"
50
+ ThreeOfDiamonds = :"🃃"
51
+ FourOfDiamonds = :"🃄"
52
+ FiveOfDiamonds = :"🃅"
53
+ SixOfDiamonds = :"🃆"
54
+ SevenOfDiamonds = :"🃇"
55
+ EightOfDiamonds = :"🃈"
56
+ NineOfDiamonds = :"🃉"
57
+ TenOfDiamonds = :"🃊"
58
+ JackOfDiamonds = :"🃋"
59
+ QueenOfDiamonds = :"🃍"
60
+ KingOfDiamonds = :"🃎"
61
+
62
+ AceOfClubs = :"🃑"
63
+ TwoOfClubs = :"🃒"
64
+ ThreeOfClubs = :"🃓"
65
+ FourOfClubs = :"🃔"
66
+ FiveOfClubs = :"🃕"
67
+ SixOfClubs = :"🃖"
68
+ SevenOfClubs = :"🃗"
69
+ EightOfClubs = :"🃘"
70
+ NineOfClubs = :"🃙"
71
+ TenOfClubs = :"🃚"
72
+ JackOfClubs = :"🃛"
73
+ QueenOfClubs = :"🃝"
74
+ KingOfClubs = :"🃞"
75
+
76
+ Joker = :"🃟"
77
+
78
+ SPADE = :"♠"
79
+ HEART = :"♡"
80
+ DIAMOND = :"♢"
81
+ CLUB = :"♣"
82
+ JOKER = :"🃟"
83
+
84
+ SUITS = [ SPADE, HEART, DIAMOND, CLUB, JOKER ]
85
+ VALUES = [ nil, :"A", :"2", :"3", :"4", :"5", :"6", :"7", :"8", :"9", :"10", :"J", :"Q", :"K", :"A" ]
86
+ ABSOLUTE_VALUE = {
87
+ CLUB => [ nil, 49, 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49 ],
88
+ DIAMOND => [ nil, 50, 2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50 ],
89
+ HEART => [ nil, 51, 3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51 ],
90
+ SPADE => [ nil, 52, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52 ],
91
+ JOKER => [ nil ],
92
+ }
93
+
94
+ CARDS = {
95
+ SPADE => [ nil, AceOfSpades, TwoOfSpades, ThreeOfSpades, FourOfSpades, FiveOfSpades, SixOfSpades, SevenOfSpades, EightOfSpades, NineOfSpades, TenOfSpades, JackOfSpades, QueenOfSpades, KingOfSpades, AceOfSpades ],
96
+ HEART => [ nil, AceOfHearts, TwoOfHearts, ThreeOfHearts, FourOfHearts, FiveOfHearts, SixOfHearts, SevenOfHearts, EightOfHearts, NineOfHearts, TenOfHearts, JackOfHearts, QueenOfHearts, KingOfHearts, AceOfHearts ],
97
+ DIAMOND => [ nil, AceOfDiamonds, TwoOfDiamonds, ThreeOfDiamonds, FourOfDiamonds, FiveOfDiamonds, SixOfDiamonds, SevenOfDiamonds, EightOfDiamonds, NineOfDiamonds, TenOfDiamonds, JackOfDiamonds, QueenOfDiamonds, KingOfDiamonds, AceOfDiamonds ],
98
+ CLUB => [ nil, AceOfClubs, TwoOfClubs, ThreeOfClubs, FourOfClubs, FiveOfClubs, SixOfClubs, SevenOfClubs, EightOfClubs, NineOfClubs, TenOfClubs, JackOfClubs, QueenOfClubs, KingOfClubs, AceOfClubs ],
99
+ JOKER => [ Joker ],
100
+ }
101
+
102
+ SUIT_NAMES = {
103
+ SPADE => 'spades'.freeze,
104
+ HEART => 'hearts'.freeze,
105
+ DIAMOND => 'diamonds'.freeze,
106
+ CLUB => 'clubs'.freeze,
107
+ JOKER => 'joker '.freeze, # The trailing space is a hack to work with SuitError as well as to_str
108
+ }
109
+
110
+ VALUE_NAMES = [ nil, 'ace'.freeze, 'two'.freeze, 'three'.freeze, 'four'.freeze, 'five'.freeze, 'six'.freeze, 'seven'.freeze, 'eight'.freeze, 'nine'.freeze, 'ten'.freeze, 'jack'.freeze, 'queen'.freeze, 'king'.freeze, 'ace'.freeze ]
111
+
112
+ attr_reader :suit, :value, :s, :str, :sym, :abs
113
+ alias :to_s :s
114
+ alias :to_str :str
115
+ alias :to_sym :sym
116
+ alias :to_i :abs
117
+
118
+ def initialize( value:, suit: )
119
+ value = normalize_value( value )
120
+ raise ValueError unless value.instance_of? Fixnum
121
+ raise ValueError unless (1..14).cover? value
122
+
123
+ suit = normalize_suit( suit )
124
+ raise SuitError unless SUIT_NAMES.keys.include? suit
125
+
126
+ value = 0 if suit == JOKER
127
+
128
+ @value = value.to_i
129
+ @suit = suit
130
+
131
+ @s = "#{ VALUES[ value ] }#{ suit }"
132
+
133
+ @sym = CARDS[ suit ][ value ]
134
+
135
+ parts = [ VALUE_NAMES[ value ], SUIT_NAMES[ suit ]]
136
+ @str = parts.compact.join( ' of ' ).strip
137
+
138
+ @abs = ABSOLUTE_VALUE[ suit ][ value ]
139
+
140
+ self.freeze
141
+ end
142
+
143
+ private
144
+
145
+ def normalize_value( value )
146
+ return value if value.instance_of? Fixnum
147
+
148
+ return VALUE_NAMES.index( value )
149
+ end
150
+
151
+ def normalize_suit( suit )
152
+ return suit if SUIT_NAMES.keys.include? suit
153
+
154
+ suit = suit.to_s
155
+ tuple = SUIT_NAMES.find{|k,v| v.start_with? suit.to_s.downcase }
156
+ return suit unless tuple
157
+ tuple.first
158
+ end
159
+
160
+ end
161
+
162
+ require 'forwardable'
163
+
164
+ class Hand
165
+
166
+ extend Forwardable
167
+
168
+ delegate [:map!, :[], :<<, :size, :to_a] => :@cards
169
+
170
+ def initialize( *card_strings )
171
+ @cards = card_strings.map do |string|
172
+ Card.new(
173
+ value: value = string.to_i,
174
+ suit: string.gsub( value.to_s, '' ).to_sym,
175
+ )
176
+ end
177
+ end
178
+
179
+ def cards
180
+ @cards
181
+ end
182
+
183
+ def to_s
184
+ @cards.map(&:to_s).join(', ')
185
+ end
186
+ end
187
+
188
+ # hand = Hand.new( '2C', '2D', '2S', '13D', '8C', '8S', '2H' )
189
+ hand = Hand.new( '2C', '2D', '2S', '13D', '8C', '8S' )
190
+ # hand = Hand.new( '2C', '2D', '2S', '13D', '8C' )
191
+
192
+ puts hand
193
+ rank = HandRank.get( hand )
194
+ p HandRank.explain( rank )
195
+
196
+ require 'benchmark'
197
+
198
+ n = 10#8000000
199
+ a = [1,2,3,4,5,6,7]
200
+ Benchmark.bm(7) do |x|
201
+ x.report('ranking') do
202
+ for i in 1..n
203
+ HandRank.get(a)
204
+ end
205
+ end
206
+ x.report('ranking') do
207
+ for i in 1..n
208
+ HandRank.rb_get(a)
209
+ end
210
+ end
211
+ x.report('"s"+"s"') do
212
+ for i in 1..n
213
+ "Hello" + "world!"
214
+ end
215
+ end
216
+ end
217
+
218
+ # Test with transformation code in Ruby
219
+ # user system total real
220
+ # ranking 5.380000 0.000000 5.380000 ( 5.388186)
221
+ # "s"+"s" 1.850000 0.010000 1.860000 ( 1.858708)
@@ -0,0 +1,3 @@
1
+ module HandRank
2
+ VERSION = "0.0.1"
3
+ end
data/lib/hand_rank.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "hand_rank/version"
2
+
3
+ module HandRank
4
+ HOME = __FILE__.to_s.gsub('.rb','/')
5
+
6
+ def self.get( hand )
7
+ cards = hand.to_a.map{|c| c.to_i }
8
+ self.rank( cards )
9
+ end
10
+ end
11
+
12
+ require "hand_rank/hand_rank"
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hand_rank
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jonas Schubert Erlandsson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-09-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-compiler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.9'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.9'
55
+ description: This gem implements the 2 plus 2 forum algorith for ranking "normal"
56
+ poker hands, such that are used in Texas Hold'em for example. Support for more hand
57
+ types, such as high/low hands, will be added in future versions.
58
+ email: jonas.schubert.erlandsson@my-codeworks.com
59
+ executables: []
60
+ extensions:
61
+ - ext/hand_rank/extconf.rb
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".gitignore"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - ext/hand_rank/extconf.rb
70
+ - ext/hand_rank/hand_rank.c
71
+ - hand_rank.gemspec
72
+ - lib/hand_rank.rb
73
+ - lib/hand_rank/ranks.data.zip
74
+ - lib/hand_rank/test.rb
75
+ - lib/hand_rank/version.rb
76
+ homepage: https://github.com/replaygaming/hand_rank
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.4.5
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Implements fast poker hand ranking algorithms in C and exposes them through
100
+ a Ruby API.
101
+ test_files: []