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 +7 -0
- data/.gitignore +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +175 -0
- data/Rakefile +6 -0
- data/ext/hand_rank/extconf.rb +3 -0
- data/ext/hand_rank/hand_rank.c +200 -0
- data/hand_rank.gemspec +25 -0
- data/lib/hand_rank/ranks.data.zip +0 -0
- data/lib/hand_rank/test.rb +221 -0
- data/lib/hand_rank/version.rb +3 -0
- data/lib/hand_rank.rb +12 -0
- metadata +101 -0
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
data/Gemfile
ADDED
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
|
+
[](http://badge.fury.io/rb/hand_rank)
|
6
|
+
[](https://codeclimate.com/github/replaygaming/hand_rank)
|
7
|
+
[](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,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)
|
data/lib/hand_rank.rb
ADDED
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: []
|