hand_rank 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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,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: []
|