hand_rank 0.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.
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: []