Badugi, Who Wins?

9

1

Inspired by this challenge and related to this one.

Background

Badugi [bæduːɡiː] is a low-ball draw-poker variant.

The Pokerstars World Cup Of Online Poker $1K event starts within 3 hours and I'll need to know how good my hands are!

The game uses a standard deck of 52 cards of four suits and thirteen ranks. The suits are unordered and shall be labelled cdhs; the ranks - ordered from highest K to lowest A - are KQJT98765432A. As such the full deck may be represented as follows (space separated):

Kc Kd Kh Ks Qc Qd Qh Qs Jc Jd Jh Js Tc Td Th Ts 9c 9d 9h 9s 8c 8d 8h 8s 7c 7d 7h 7s 6c 6d 6h 6s 5c 5d 5h 5s 4c 4d 4h 4s 3c 3d 3h 3s 2c 2d 2h 2s Ac Ad Ah As 

Each player is dealt four cards from the deck, there are four betting rounds with three drawing rounds in-between (a player always has four cards, they have the option of changing 0-4 of their cards with new ones from the dealer on each of the three drawing rounds).

If more than one player is still active after all these rounds there is a showdown, whereupon the strongest hand(s) win the wagered bets.

The game is played low-ball, so the lowest hand wins, and as mentioned above A (ace) is low. Furthermore the hand ranking is different from other forms of poker, and can be somewhat confusing for beginners.

The played "hand" is the lowest ranked combination made from the highest number of both "off-suit" (all-different-suits) and "off-rank" (all-different-ranks) cards possible (from the four held cards). That is: if one holds four cards of both distinct suits and distinct ranks one has a 4-card hand (called a "badugi"); if one does not have a 4-card hand but does have some set or sets of three cards of both distinct suits and distinct ranks one has a 3-card hand (one chooses their best); if one has neither a 4-card hand or a 3-card hand one probably has a 2-card hand, but if not one has a 1-card hand.

  • As such the best possible hand is the 4-card hand 4-3-2-A - the lowest off-rank cards of four different suits, often termed a "number-1". The weakest possible hand would be the 1-card hand K and is only possible by holding exactly Kc Kd Kh Ks.

  • Note that 4c 3h 2c As is not a "number-1", since the 4c and 2c are of the same suit, but it is the strongest of the 3-card hands, 3-2-A, it draws with other 3-2-1s (like Kh 3d 2s Ah) and beats all other 3-card hands but loses to all 4-card hands (which could be as weak as K-Q-J-T).

    • The other possible 3-card hand that could be made from 4c 3h 2c As is 4-3-A, but that is weaker (higher) so not chosen.
  • Similarly 8d 6h 3s 2h is a 3-card hand played as 8-3-2 - there are two off-rank off-suit combinations of size 3 and 8-3-2 is better (lower) than 8-6-3 since the three (or "trey") is lower than the six.

Comparing hands against one another follows the same logic - any 4-card beats any 3-card, any 3-card beats any 2-card and any 2-card beats any 1-card, while hands of the same number of used cards are compared from their highest rank down to their lowest (for example: 8-4-2 beats 8-5-A but not any of 8-4-A, 8-3-2 or 7-6-5)

The challenge:

Given two unordered-collections each of four cards, identify the one(s) which win a Badugi showdown (identify both if it is a draw).

The input may be anything reasonable:

  • a single string of all eight cards as labelled above (with or without spaces) with the left four being one hand and the right the other (with an optional separator); or a list of characters in the same fashion
  • a list of two strings - one per hand, or a list of lists of characters in the same fashion
  • two separate strings or list inputs, one per hand
  • the cards within the hands may be separated already too (so a list of lists of lists is fine)

Note, however:

  • the cards may not be arranged into any order prior to input
  • ...and the suits and ranks are fixed as the character labels specified here - If your language does not support such constructs just suggest something reasonable and ask if it is an acceptable alternative given your languages constraints.

The output should either be

  • formatted the same as the input, or a printed representation thereof; or
  • be one of three distinct and consistent results (e.g.: "left", "right", "both"; or 1, 2, 3; etc.)

Really - so long as it is clear which of the two inputs are being identified it should be fine.

Test cases

input                      ->  output
                                   (notes)
----------------------------------------------------------------------------
3c 2s 4d Ah - As 3h 2d 4h  ->  3c 2s 4d Ah
                                   (4-card 4-3-2-A beats 3-card 3-2-A)

3c 2s 4d Ah - As 2c 3d 4h  ->  3c 2s 4d Ah - As 2c 3d 4h
                                   (4-card 4-3-2-A draws with 4-card 4-3-2-A)

2d Ac 4h 3c - Kh Ad 9s 2c  ->  Kh Ad 9s 2c
                                   (3-card 4-2-A loses to 4-card K-9-2-A)

Kc Tc Qc Jc - Ac Ad Ah As  ->  Ac Ad Ah As
                                   (1-card T loses to 1-card A)

9c 9h Qc Qh - Qs Kh Jh Kd  ->  Qs Kh Jh Kd
                                   (2-card Q-9 loses to 3-card K-Q-J)

2d 5h 7c 5s - 2h 3c 8d 6c  ->  2d 5h 7c 5s
                                   (3-card 7-5-2 beats 3-card 8-3-2)

3s 6c 2d Js - 6h Jd 3c 2s  ->  6h Jd 3c 2s
                                   (3-card 6-3-2 loses to 4-card J-6-3-2)

Ah 6d 4d Ac - 3h 2c 3s 2s  ->  3h 2c 3s 2s
                                   (2-card 4-A loses to 2-card 3-2)

2h 8h 6h 4h - 6d 2d 5d 8d  ->  2h 8h 6h 4h - 6d 2d 5d 8d
                                   (1-card 2 = 1-card 2)

This is , so the shortest code in bytes wins per-language, and the shortest code overall wins. Don't let golfing languages put you off submitting in other languages, and ...have fun!

Jonathan Allan

Posted 2017-09-13T15:47:06.403

Reputation: 67 804

Is something like [['3c', '2s', '4d', 'Ah'], ['As', '3h', '2d', '4h']] reasonable? – Erik the Outgolfer – 2017-09-13T18:21:24.767

@EriktheOutgolfer Yes - I was going to say I think you can just add O to the front. – Jonathan Allan – 2017-09-13T18:23:40.490

Answers

2

Perl 6, 128 bytes

{.map({.combinations(1..4).grep({!.join.comb.repeated}).map({-$_,$_».trans('ATK'=>'1BZ')».ord.sort(-*)}).min}).minpairs».key}

Try it online!

Takes a list of hands (also more than two) which are lists of cards which are strings like Kc. Returns the indices of the hands with the lowest score. For two hands this is (0) if the first hand wins, (1) if the second hand wins, and (0, 1) for a draw.

Explained:

{
    # Map hands to best "played hand".
    .map({
        # Generate all combinations of length 1 to 4.
        .combinations(1..4)
        # Remove hands with repeated characters.
        .grep({!.join.comb.repeated})
        # Map to a cmp-arable representation. This works because
        # lists are compared element-wise. Translate some characters
        # so that ASCII order works. Sort in reverse order so that
        # element-wise comparison will go from highest to lowest.
        .map({ -$_, $_».trans('ATK'=>'1BZ')».ord.sort(-*) })
        # Find best hand.
        .min
    })
    # Return indices of lowest scoring hands. It's a shame that
    # `minpairs` doesn't support a filter like `min`.
    .minpairs».key
}

nwellnhof

Posted 2017-09-13T15:47:06.403

Reputation: 10 037

Out of interest how does <...> in your test harness translate to a list of strings? Is it some kind of sugar that says the content should be split on spaces? – Jonathan Allan – 2017-09-13T21:00:40.160

@JonathanAllan This is Perl's word quoting. Perl 6 supports <a b c> in addition to qw(a b c) from Perl 5.

– nwellnhof – 2017-09-13T21:03:37.633

Well that's nice and golfy in of itself :) – Jonathan Allan – 2017-09-13T21:04:20.450

2

JavaScript (ES6), 209 202 192 182 181 bytes

Saved 7 bytes thanks to @Neil

Takes input as an array of arrays of strings. Returns true if the first hand wins, false if the second hand wins, or 2 in the event of a tie.

a=>([a,b]=a.map(a=>a.reduce((a,x)=>[...a,...a.map(y=>[x,...y])],[[]]).map(a=>!/(\w).*\1/.test(a)*a.length+a.map(a=>'KQJT98765432A'.search(a[0])+10).sort()).sort().pop()),a==b?2:a>b)

Test cases

let f =

a=>([a,b]=a.map(a=>a.reduce((a,x)=>[...a,...a.map(y=>[x,...y])],[[]]).map(a=>!/(\w).*\1/.test(a)*a.length+a.map(a=>'KQJT98765432A'.search(a[0])+10).sort()).sort().pop()),a==b?2:a>b)

console.log(f([["3c","2s","4d","Ah"], ["As","3h","2d","4h"]])) // true  (1st hand wins)
console.log(f([["3c","2s","4d","Ah"], ["As","2c","3d","4h"]])) // 2     (tie)
console.log(f([["2d","Ac","4h","3c"], ["Kh","Ad","9s","2c"]])) // false (2nd hand wins)
console.log(f([["Kc","Tc","Qc","Jc"], ["Ac","Ad","Ah","As"]])) // false (2nd hand wins)
console.log(f([["9c","9h","Qc","Qh"], ["Qs","Kh","Jh","Kd"]])) // false (2nd hand wins)
console.log(f([["2d","5h","7c","5s"], ["2h","3c","8d","6c"]])) // true  (1st hand wins)
console.log(f([["3s","6c","2d","Js"], ["6h","Jd","3c","2s"]])) // false (2nd hand wins)
console.log(f([["Ah","6d","4d","Ac"], ["3h","2c","3s","2s"]])) // false (2nd hand wins)
console.log(f([["2h","8h","6h","4h"], ["6d","2d","5d","8d"]])) // 2     (tie)

How?

a => (
  // store the best combination for both hands in a and b respectively
  [a, b] = a.map(a =>
    // compute the powerset of the hand
    a.reduce((a, x) => [...a, ...a.map(y => [x, ...y])], [[]])
    // for each entry:
    .map(a =>
      // invalidate entries that have at least 2 cards of same rank or same value
      !/(\w).*\1/.test(a) *
      // the score of valid entries is based on their length ...
      a.length +
      // ... and their card values, from highest to lowest
      // (we map 'KQJT98765432A' to [10 - 22], so that the resulting
      // strings can be safely sorted in lexicographical order)
      a.map(a => 'KQJT98765432A'.search(a[0]) + 10).sort()
    )
    // keep the best one
    .sort().pop()
  ),
  // compare a with b
  a == b ? 2 : a > b
)

Arnauld

Posted 2017-09-13T15:47:06.403

Reputation: 111 334

Do you need that join? – Neil – 2017-09-14T15:26:56.967

1

Python 3, 207 204 bytes

lambda i,j:L(h(i))-L(h(j))if L(h(i))!=L(h(j))else(h(i)<h(j))-(h(i)>h(j))
L=len
def h(l):s=set();return[x[0]for x in sorted(y.translate({65:49,75:90,84:65})for y in l)if not(s&set(x)or s.update(*x))][::-1]

Try it online!

Saved 3 bytes thanks to Jonathan Frech

Returns 1 if the first hand wins, -1 if the second hand wins and 0 in case of a draw.

The function h computes a list that represents the hand.

The lambda compares two representations of hand. I think it might be shortened, but I wanted to return only three values and didn't find a simpler way to do comparison.

jferard

Posted 2017-09-13T15:47:06.403

Reputation: 1 764

You can save two bytes by defining L=len and replacing all other occurrences of lenwith L. – Jonathan Frech – 2017-09-14T21:10:16.983

Also, you can probably replace s=set() with s={0} and set(x)&s or with s&set(x)or – Jonathan Frech – 2017-09-14T21:13:42.640

1

Jelly, 36 bytes

ẎŒQȦ;L;Ṗ€Ṣ$
“A+KYTE”yḲONŒPÇ€ṢṪµ€⁼€Ṁ$

A monadic link taking a list of two lists of characters
- each being a space separated representation of the hand (e.g. "Ac 2d 4s 3h")
returning a list of two numbers identifying the winner(s) with 1 and any loser with 0
- i.e. [1, 0] = left wins; [0, 1] = right wins; [1, 1] = draw.

Try it online! or see the test-suite.

How?

ẎŒQȦ;L;Ṗ€Ṣ$ - Link 1, sortKey: list of lists of numbers representing some cards (see Main)
Ẏ           - flatten into a single list of numbers
 ŒQ         - distinct sieve (1 at first occurrence of anything, 0 at the rest)
   Ȧ        - Any & All? zero if any are 0 or if empty; 1 otherwise (i.e. playable hand?)
     L      - length of input (number of cards in the hand)
    ;       - concatenate
          $ - last two links as a monad:
       Ṗ€   -   pop each (get just the rank portions)
         Ṣ  -   sort (Main's translation & negation of ordinals ensures A>2>3>...>Q>K)
      ;     - concatenate (now we have [isPlayable; nCards; [lowToHighCards]])

“A+KYTE”yḲONŒPÇ€ṢṪµ€⁼€Ṁ$ - Main link: list of lists of characters, hands
                  µ€     - for €ach of the two hands:
“A+KYTE”                 -   literal list of characters "A+KYTE" (compressing doesn't help - lower case would be “£Ḅṁ⁽>» though -- I'll stick with kyte though it's kind of nice.)
        y                -   translate - change As to +s, Ks to Ys and Ts to Es
                         -               note the ranks are now in ordinal order:
                         -               +<2<3<4<5<6<7<8<9<E<J<Q<Y
         Ḳ               -   split at spaces - split the four cards up
          O              -   to ordinals '+'->43, '2'->50, ...
           N             -   negate - effectively reverse the ordering
            ŒP           -   power-set - get all combinations of 0 to 4 cards inclusive
              Ç€         -   call the last link (1) as a monad for €ach such selection
                Ṣ        -   sort these keys
                 Ṫ       -   tail - get (one of) the maximal keys
                         -                       (the key of a best, playable selection)
                       $ - last two links as a monad:
                      Ṁ  -   maximum (the better of the two best, playable selection keys)
                    ⁼€   -   equals? for €ach (1 if the hand is a winner, 0 if not)

Jonathan Allan

Posted 2017-09-13T15:47:06.403

Reputation: 67 804