therewillbecode / poker-maison

🂺 A Multiplayer Poker App Crafted with Haskell and React
309 stars 34 forks source link

Extracting holdem game logic into separate package #20

Open santiweight opened 3 years ago

santiweight commented 3 years ago

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:

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):

Some notable differences:

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 https://github.com/haskell/haskell-language-server/issues/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:

santiweight commented 3 years ago

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"]
therewillbecode commented 3 years ago

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?

santiweight commented 3 years ago

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:

santiweight commented 3 years ago

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:

therewillbecode commented 3 years ago

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?

santiweight commented 3 years ago

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.