Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Java implementation #5

Open
HenryRLee opened this issue Oct 14, 2019 · 17 comments
Open

Add Java implementation #5

HenryRLee opened this issue Oct 14, 2019 · 17 comments

Comments

@HenryRLee
Copy link
Owner

HenryRLee commented Oct 14, 2019

Add Java implementation, using the same algorithm, similar to the C++ implementation.

Create a directory named java and add Java source code in it.

@hellraiserinchief
Copy link

I would like to pick this up.
[ also there is a typo, its should be 'add java code' in the description above ]

@HenryRLee
Copy link
Owner Author

Thank you. @hellraiserinchief

@HenryRLee
Copy link
Owner Author

HenryRLee commented Oct 14, 2019

Hi @hellraiserinchief, I've just updated the Contributing file, which specified the code style.

Feel free to ask me if you have any questions related to the implementation. Good luck!

@HenryRLee
Copy link
Owner Author

I have some advice/guide that may help you with this implementation: You can start implementing a 5-card or a 7-card evaluator only, with unit tests, and make a PR for it. Then make consequential PRs for the rest of the evaluators.

It might not be easy to write test codes fully cover all the possible hands (enumerating all possibilities like the C++ implementation, which is using Cactus Kev's evaluator as a reference). To make it easier, you can just write test code only choosing a few examples in each rank category. For example, choose a few hands from Straight Flush, then a few from Four of a Kind, etc.

@hellraiserinchief
Copy link

Makes sense, I will get stated on a 5-card evaluator and take things from there.

@hellraiserinchief
Copy link

hellraiserinchief commented Oct 15, 2019

Hi, seems like we are just returning the ranks using the hash tables after some simple operations. Can you please let me know how these hash tables were created in the first place? I would like to learn how we generated these hash tables.

@HenryRLee
Copy link
Owner Author

Sure, @hellraiserinchief.

The basic idea of the algorithm is, given a hand of more than 5 cards, it will find its strength value in a hash table. Splitting the hands by whether it is a flush, will reduce the total size of the hash table. So there will be two hash tables: flush[8192] covering all 13-bit binaries, and noflush5[6175] (5-card evaluator only) covering all 13-bit quiaries (base 5 numbers). You can find out why in this algorithm documentation.

The way I generated the flush[8192] and the noflush5[6175], was not through calculation. I simply enumerated all possible hands, got the correct value from Cactus Kev's evaluator, ran my hash function, and finally assigned the value to the corresponding slot in the hash table. This part is boring, you can copy these two tables if you like.

As for the other tables, I can give you a little bit more code walk first. The entry is the method evaluate_5cards:

int evaluate_5cards(int a, int b, int c, int d, int e)
{
  int suit_hash = 0;
  int suit_binary[4] = {0};
  unsigned char quinary[13] = {0};
  int hash;

  suit_hash += suitbit_by_id[a];
  suit_hash += suitbit_by_id[b];
  suit_hash += suitbit_by_id[c];
  suit_hash += suitbit_by_id[d];
  suit_hash += suitbit_by_id[e];

  if (suits[suit_hash])
  {
    suit_binary[a & 0x3] |= binaries_by_id[a];
    suit_binary[b & 0x3] |= binaries_by_id[b];
    suit_binary[c & 0x3] |= binaries_by_id[c];
    suit_binary[d & 0x3] |= binaries_by_id[d];
    suit_binary[e & 0x3] |= binaries_by_id[e];

    return flush[suit_binary[suits[suit_hash]-1]];
  }

  quinary[(a >> 2)]++;
  quinary[(b >> 2)]++;
  quinary[(c >> 2)]++;
  quinary[(d >> 2)]++;
  quinary[(e >> 2)]++;

  hash = hash_quinary(quinary, 13, 5);

  return noflush5[hash];
}

The first part is generating a suit hash and looking it up in a table named suits. The suits table will tell us whether the hand is a flush, and if it is, it will tell us which suit the flush is in. The table has five different values: 0, 1, 2, 3, 4. Value 0 indicates it not a flush. Value 1 indicates it is a flush in clubs, 2 for diamonds, 3 for hearts and 4 for spades.

Actually, the suit table is not a necessary part of the algorithm. Maybe the following code would be easier to understand, which is how I originally coded the algorithm.

int evaluate_5cards(int a, int b, int c, int d, int e) {
  int suit_counter[4] = {0};

  // The last two bits is the suit of the card, this will count how many cards are in each suit.
  suit_counter[a & 0x3]++;
  suit_counter[b & 0x3]++;
  suit_counter[c & 0x3]++;
  suit_counter[d & 0x3]++;
  suit_counter[e & 0x3]++;

  for (int i = 0; i < 4; i++) {
    if (suit_counter[i] >= 5) { // One of the suit has more than five cards, making it a flush
      int suit_binary[4] = {0};
      // Dividing by 4 will get rid of the suit information, giving us the rank of the card
      suit_binary[a & 0x3] |= 1 << (a / 4);
      suit_binary[b & 0x3] |= 1 << (b / 4);
      suit_binary[c & 0x3] |= 1 << (c / 4);
      suit_binary[d & 0x3] |= 1 << (d / 4);
      suit_binary[e & 0x3] |= 1 << (e / 4);
      return flush[suit_binary[i]];
    }
  }

  unsigned char quinary[13] = {0};
  quinary[(a >> 2)]++;
  quinary[(b >> 2)]++;
  quinary[(c >> 2)]++;
  quinary[(d >> 2)]++;
  quinary[(e >> 2)]++;

  int hash = hash_quinary(quinary, 13, 5);

  return noflush5[hash];
}

In case you'd still like to know how the suits table generated, it is basically similar to the flush and noflush table, run the hash function, and assign the correct value to the corresponding slot...

Then we can take a look at the hash_quinary method, which used a table called dp[5][14][10]:

int hash_quinary(unsigned char q[], int len, int k)
{
  int sum = 0;

  for (int i=0; i<len; i++)
  {
    sum += dp[q[i]][len-i-1][k];

    k -= q[i];

    if (k <= 0)
    {
      break;
    }
  }

  return sum;
}

The table dp comes from dynamic programming calculation. I once provided the code in #1 and #2. Again, the algorithm behind that is in the third chapter of this documentation. If you find any part of the doc hard to understand, feel free to ask me.

Have fun coding!

@hellraiserinchief
Copy link

Wow!! This super helpful. Thanks for taking time out to explain this in such detail.

@Mkerian10
Copy link

Mkerian10 commented Mar 7, 2021

Have the implementation complete. Adding benchmarking and tests, as well as some other helper methods.

@HenryRLee would you be able to dump the eval values for each hand into a file for me? I don't want to mess with setting up the cpp build and this seemed like a decent way to do so. This way for testing it'll just compare the values to a file instead of setting up another known evaluator into the repo.

@HenryRLee
Copy link
Owner Author

Hi @Mkerian10, thanks for contributing.

Yes, having a language-independent test data makes sense. I'll generate such test data for 5, 6 and 7 card hands.

@Mkerian10
Copy link

@HenryRLee glad to hear it.

For now I have eval5-9 implemented. If you want me to implement Omaha I could but I don't have an interest in it so I don't plan to currently.

Outside of that I want to expand some more evaluations of equity, Preflop equity vs. a full range is very simple to figure out. I'm looking at implementing more solutions but I'm sure you're more in touch with poker programming so if you know more about this stuff feel free to let me know.

@HenryRLee
Copy link
Owner Author

Hi @Mkerian10, I created a pull request, adding 5-card hands test data #29. You are welcome to review it.

I actually generated test data for 6 and 7 card hands, too. But the file sizes are too large to fit in Github. So it's likely I'll only provide a subset of them instead of providing all possible hands. But the format should be consistent.

@Mkerian10
Copy link

Awesome, let me know when you merge it and I'll add the tests and can push my branch.

@HenryRLee
Copy link
Owner Author

Hi @Mkerian10, just added some random test data for 6-card and 7-card hands. Also merged the PR.

Looking forward to your changes.

@Mkerian10
Copy link

I never forked the repo so can I just be listed as a contributor and I'll submit the pr. All tests are passing.

@HenryRLee
Copy link
Owner Author

Hi @Mkerian10. Actually I don't have control of deciding who can be on contributor list. Github manages that. But I am happy to help you to have you listed in the contributors list.

The easiest way is to create a pull request, I review the pull request, and then merge it. But usually you have to fork the repository in order to create a pull request. (You can delete the fork after the PR is merged anyway.)

There are other ways, though they are not easy. For example you can send me the patch and I apply the patch using your name and email address. And then there are a number of approaches: either I push these commits to the master branch directly, or I can push them to another branch, so that you can create a pull request. However, if I push the commits directly, you still need to have some interactions with this repo in order to have these commits counted towards your name, e.g. star the repo, fork the repo, or create a new issue (because Github doesn't want me to create a commit using Linus Torvalds' name, and then say Linus is a contributor of my repo).

You may find this official document useful. In particular, you need to follow the section that how to have your commits counted as your contribution.

@Mkerian10
Copy link

For some reason I thought I needed permissions but my IntelliJ was being rude last night. Restarted and it fixed it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants