therewillbecode/poker-maison

Extracting holdem game logic into separate package

santiweight opened this issue · 6 comments

We've talked in the past (dunno if you remember - 3 years ago maybe?).

Summary

Thanks for the great project :) Incoming wall of text, but I hope it is all to the point. I would like to migrate poker-maison to use a shared library for core poker type declarations (in src/Poker.Types) and for holdem game logic (src/Poker.Game.*). In general there is no need for each programmer to reimplement poker types and holdem (or any poker game) logic, and so if we get one solid and collaborated-upon set of libraries going, we'll have a better haskell-poker ecosystem! :)

My goal for these libraries is to be a good example of Haskell code. Nothing too fancy, but also nothing too simple, using the most common haskell features such as lens and mtl, so that an intermediate haskeller would feel comfortable and a beginner could come in without being overwhelmed.

I have already implemented the lion's share of the library, so I am here to check if you would be okay with migrating poker-maison to use my code. I have tested poker-maison with some of the migration, and it all looks good as far as I can tell.

What it would look like for poker-maison

poker-maison would use the new library for stuff like:

  • core poker types
  • next available actions for main street actions (preflop thru river)
  • stack sizes
  • basic poker types

Some things are not yet implemented not yet support (but would be added at a later date, likely by using your logic, if that's okay with you):

  • blind posting
  • showdown

Some notable differences:

  • I, as a rule, don't override Show or Read instances, since the libraries are aimed for all to use. Instead I implement pretty-simple's Pretty class, which I see you already use anyway.
  • I don't override Eq/Ord anywhere, since these instances can be used sometimes for Map and occasionally for serialisation. This only caused one minor issue with migrating poker-maison (see Poker.Game.Hands.sortByLength), which is easily fixed.
  • My goal is to have hand phase separation, so the game state type will be unique for each of blind-posting, post-card-deal (preflop thru river), and showdown (3 game state types in total).
  • My library is polymorphic in the bet type, but this would hidden from your code almost completely.
  • My library code uses State monad lens combinators extensively, but no other fanciness, and isn't too long (around 1000 lines excluding tests).
  • My test coverage is not yet a superset of your tests. Before merging, I would translate all your tests into my codebase (I already started the process locally). My code is tested and was tested by running around 10,000ish hands from my Bovada database through my holdem engine. The tests test both that all actions in the DB were accepted by my holdem engine, and that test-runner-generated invalid actions returned an error for all acts for all hands. I will also be augmenting these tests with a 1,000,000 hand database (including heads-up hands) from PokerStars once I have a parser for PokerStars hand histories.
  • I don't account for table actions such as Timeouts and LeaveSeats, which would be translated to my "BetAction", which are the in-a-vacuum available actions to a player. For example, your "Action" type might look something like the following:
data Action
  = SitDown Player -- doesnt progress the game
  | LeaveSeat' -- doesnt progress the game
  | PostBlind Blind
  | ShowHand
  | MuckHand
  | SitOut
  | SitIn
  | Timeout
  | FromBetAction BetAction

The migration process

I would be happy to do this migration all myself, and do so incrementally to make it more reviewable for you! I don't expect the patches to be too involved/arduous.

If you're open to the process, the only thing that I would ask from you would be whether I could make changes to the build system for poker-maison. In particular I was having trouble with having a good IDE setup, likely because of the following github issue haskell/haskell-language-server#1822. Would you be okay with a cabal based build instead of stack? The trade off would be slightly more verbose builds (you have to add each file to the .cabal file) which is definitely annoying, but as a reward we don't get an infuriating bug that means you have to persistently restart the IDE each time you edit library code while working on a test.

The process would overall look like this:

  • integrate with poker-base, which is here. This code will definitely experience more cleanups, and I'd like to get to 90+% test coverage (up from ~70%)
  • integrate with poker-game which lives here. This would be the harder part, but I can show in a PR maybe this weekend?

Something I forgot to mention:

There appears to be a build failure on HEAD. Not sure what's up with that, but for the meantime I went with undefined calls :)

~/poker/maison/server/src/Socket/Table.hs:143:9: error:
    • Couldn't match expected type ‘Consumer Game IO () -> IO b0’
                  with actual type ‘[[Char]]’
    • In the first argument of ‘runBots’, namely ‘["bot1", "bot2"]’
      In the second argument of ‘($)’, namely ‘runBots ["bot1", "bot2"]’
      In the second argument of ‘($)’, namely
        ‘when (botCount > 0) $ runBots ["bot1", "bot2"]’
    |
143 |         ["bot1", "bot2"]

Hey @santiweight, great to hear from you!

I really like your suggestions, especially the one about integration tests with real hand histories.

I feel as though if we extracted the holdem game logic from this library then there wouldn't be much left, as you would be left with simply the networking logic and authentication.

What do you think?

Hmm. That wasn't my impression as I was writing the code to migrate, but I certainly see your point. Much of your logic is to do with seating, blind posting, whether someone can time out - stuff like that. All that logic would remain the same. But I do see your concern - I'll think about that. Ultimately, you would still have heaps of logic, such as bots, the UI, a lot of non-game specific logic. But it's certainly up to you whether you want to lessen the code in your library, I don't want to push you to remove your code :)

Let me try to motivate a little more why I think the extraction is a win for everyone, not just us.

A lot of decisions to do with setting up a game of poker are arbitrary, such as how blind are posted, whether someone can post, whether there are antes, whether someone can straddle how time outs work. These questions make up a poker site/poker game, since you can make plenty of different decisions, but once the cards are dealt - from preflop to showdown - there is only one set of rules, even across different poker games. This would a no-limit (easily extendable to limit) poker betting-logic library, not a holdem library. The new library wouldn't include players' hands, which would mean that integrating with omaha or stud would be trivial, and correct for free, as-it-were.

I think either way, if your site were to support omaha and mixed games, that logic would want to be extracted out, and then if you want to add tournament support, you just changed the pre-deal logic, and don't touch the betting logic. Plus if someone else wants to code their own completely logic on top of the new poker library, they can do so. That in particular is my reasoning - I am using the game logic to get available actions to do stuff relating to game tree construction, but my players don't have hands in my use-case, there are no timeouts etc. so I sadly can't integrate with your code! Similarly, if someone wants to write a website with slightly different time out logic than yours, then they would be hard pressed to do so, and would need to copy-and-paste your entire code, committing to a sad fracture in the ecosystem :(

I'm not in love with my code, and I would be happy to just use your code (maybe refactoring players to in maps lol), but since there is a lot of actual-poker-game related logic, such as sat-out players, I can't manage integrating :( So in that way, if we want to unify the ecosystem, we have to go towards smaller logical pieces.

I think the extraction therefore is good to do for a bunch of reasons:

  • You'll get an extra developer on your core poker logic, with a (hopefully helpful) refactoring for free from me :) and more tests
  • Other people will be able to write libraries based on the new poker logic including us
  • It makes it very pleasant to add different logic for tournaments as well as different games besides omaha
  • Tests based on other websites could be integrated. I think this is the kind of step that we could separately - perhaps you could generate tests for your specific game structure from my parsed output. I'll link you to the libraries hopefully at some point this week to give you an idea of how to integrate the pokerstars/bovada tests with your game logic. But at the very least, we would want to unify our core types, otherwise integrating would be a little too grating...
  • There has been a good amount of interest, as far as I can see, regarding poker in haskell. I think unifying the ecosystem is a good_thing since we can get people working together. I already to one other person who would be interested in the unification for their library. I think right now there are around 4 libraries on hackage that each define Rank and Suit, for example.

I realise in hindsight that I didn't directly address your question, hopefully what I wrote isn't a waste of time though...

In direct answer: the code that would be left over would still be quite substantial imo. There would still be anything related to a player and their username: depositing, sitting down at a table, wait-lists, sitting out, time outs, min/max buy-ins.

I personally find that to be quite a substantial amount of logic, so I think your code would get simpler but certainly not trivial.

The specific code that would get extracted would be:

  • most of Poker.Poker
  • a lot of Poker.ActionValidation minus any logic do with time outs and the like
  • hopefully all of Poker.Types
  • Some of Poker.Game would be extracted, but not that much in the short term. Much of that extraction would happen later down the line if we extracted code for handling blinds, which is not on my immediate radar.

I really appreciate this. However I am not seeing enough value in extracting out the logic you mention from the library.

It is just my opinion but the logic you are talking about extracting forms the core of this library, and I don't feel comfortable removing it.

Did you have any other ideas about ways we could collaborate as you are also working on poker libraries?

I won't argue with any of that - I totally get that.

Let's still ensure that we merge the underlying representation of Rank, Suit, Card etc.

Some great work was done here to get that uber-fast representation of Cards. I think it would be good to combine that with types like Range. Again - at the very least let's unify the types. At least then we can work off the same basis and allow people to use these libraries together.

The main thing I'd need is for poker-maison to compile fully, that is if you would prefer that I do the PR. The PR would mostly be a minus-diff that removes types like Rank, Suit etc. and importing them instead. It would be quite a non-invasive change. The biggest change might be deciding whether Card should be represented as:

data Card = Card !Rank !Suit
--- or:
newtype Card = Card Word8

I've always used the former, but the fast representation of Cards does generally seem like a good thing and workable with pattern synonyms.