# Nothing like a good ol' game of ModTen

Disclaimer: ModTen is a fictional card game which was created for the sole purpose of this challenge.

## The rules of ModTen

ModTen is played with a standard 52-card deck. Because the full rules are yet to be invented, we're going to focus on the hand ranking exclusively.

A winning hand in ModTen. Graphics from Wikipedia.

### Card values

The cards have the following values:

• 2 to 9: worth their face value
• Ten: 0 point
• Jack: 3 points
• Queen or King: 8 points
• Ace: 9 points

### Hand values

• A ModTen hand is made of two cards. The base value of a hand is obtained by multiplying the value of both cards together and keeping the last digit only (i.e. applying a modulo 10).

For instance, the value of 7♥ - Q♣ is "$$\6\$$", because $$\(7\times8)\bmod 10=6\$$.

• The only other rule in ModTen is that suited cards are worth more than unsuited ones. By convention, we are going to append a "s" to the value if both cards are of the same suit.

For instance, the value of 9♠ - 5♠ will be noted as "$$\5\text{s}\$$", because $$\(9\times5)\bmod 10=5\$$ and the cards are suited.

### Hand ranking and winner

The above rules result in 18 distinct hand ranks which are summarized in the following table, from strongest to lowest (or rarest to most common). The probabilities are given for information only.

Given two hands, the hand with the lowest rank wins. If both hands are of the same rank, then it's a draw (there's no tie breaker).

hand rank | hand value(s) | deal probability
-----------+---------------+------------------
1     | 9s            | 0.30%
2     | 3s            | 0.60%
3     | 1s            | 0.90%
4     | 7s            | 1.21%
5     | 5s            | 1.51%
6     | 3             | 1.81%
7     | 9             | 2.26%
8     | 8s            | 2.71%
9     | 6s            | 3.02%
10     | 1 or 7        | 3.62% each
11     | 2s or 4s      | 3.92% each
12     | 5             | 4.98%
13     | 0s            | 5.43%
14     | 8             | 8.14%
15     | 6             | 9.95%
16     | 2             | 11.76%
17     | 4             | 13.57%
18     | 0             | 16.74%

## The challenge

Given two ModTen hands, output one of three consistent values of your choice to tell whether:

• the first player wins
• the second player wins
• it's a draw

The following rules apply:

• A card must be described by its rank in upper case (2, 3, ..., 9, T, J, Q, K or A) followed by its suit in lower case (c, d, h or s, for clubs, diamonds, hearts and spades).
• You may use "10" instead of "T" but any other substitution is prohibited.
• As long as the above rules are followed, you may take the hands in any reasonable and unambiguous format. You are allowed to take the rank and the suit as two distinct characters rather than a single string.

Some valid input formats are:

• "7c Qh 8s Ks"
• [["7c","Qh"], ["8s","Ks"]]
• [[['7','c'], ['Q','h']], [['8','s'], ['K','s']]]
• etc.

• This is .

## Test cases

Player 1 wins

["Js","3s"], ["Ks","Kh"]
["7h","9h"], ["9s","7c"]
["Ah","5s"], ["Ts","8s"]
["Ts","8s"], ["Jh","2s"]
["4h","8s"], ["Qh","Ks"]

Player 2 wins

["Th","8d"], ["6s","Kd"]
["Jc","5c"], ["3s","9s"]
["Jc","Jd"], ["9h","Ah"]
["2d","4d"], ["3h","3s"]
["5c","4c"], ["3c","2c"]

Draw

["Js","3s"], ["3d","Jd"]
["Ah","Ac"], ["3d","9s"]
["Qc","Kc"], ["6d","4d"]
["2d","3d"], ["3s","2s"]
["Ts","9c"], ["4h","5d"]
• What about taking enums as input? Haskell has a pretty powerful type system; I'm fairly sure something like this could be made directly in it. Aug 23 '19 at 13:26
• This isn't Haskell, but would {{J, s}, {3, s}} be okay? Aug 23 '19 at 13:57
• @wizzwizz4 Yes, that's fine. Aug 23 '19 at 13:57
• This might be clearer with "hands of cards with matching suits" instead of "suited cards". Aug 24 '19 at 16:23

# Python 3, 114 110 bytes

lambda m,n:p(*n)-p(*m)
R=b"T 2J45UNK9RL<3SLM;QAK:O>=/678"
v=R.find
p=lambda i,s,j,t:R[s==t::2][v(j)*v(i)%10+3]

Try it online!

@Arnauld proposed the idea of merging the card value and rank table strings. After some attempts I managed to craft a merged string R="T 2J45UNK9RL<3SLM;QAK:O>=/678", which has the same length as the original card value string. The substring R[6:25]="UNK9RL<3SLM;QAK:O>=/" serves as the rank table as well as a card value lookup table for 3, 9, A, K, and Q. The ASCII-value decoding of the new rank table has the same ranking effect as the previous rank table.

Using byte strings as input saves 4 bytes.

Using cmp in Python 2 can reduce the solution to 102 bytes, as shown by @xnor's solution.

# Python 3, 165142130 129 bytes

lambda m,n:p(*n)-p(*m)
v="T 23456789   J    QA        K".find
p=lambda i,s,j,t:ord("HC92FA51GAB4E893D760"[s==t::2][v(j)*v(i)%10])

Try it online!

-23 bytes thanks to @Jonathan Allan

-2 bytes thanks to @ovs

-1 byte thanks to @mypetlion

### Ungolfed:

f = lambda hand1, hand2: get_rank(*hand2) - get_rank(*hand1)
def get_rank(v1, suit1, v2, suit2):
get_card_value = "T 23456789   J    QA        K".find
# rank_table = [[17,9,15,5,16,11,14,9,13,6],[12,2,10,1,10,4,8,3,7,0]]
# rank_table = ("H9F5GBE9D6","C2A1A48370") # Base-18 encoding of ranks
rank_table = "HC92FA51GAB4E893D760" # Interleaved base-18 encoding

# ASCII-value decoding has the same ranking effect as base-18 decoding
return ord(rank_table[suit1 == suit2::2][get_card_value(v2) * get_card_value(v1) % 10])

The function f takes two arguments representing the hand of player 1 and player 2. It returns a positive, negative, or zero value in the case of a player 1 win, a player 2 win, or a draw, correspondingly. Each hand is encoded as a single string, e.g. "7cQh".

• Hi Joel, welcome to CGCC! Very clever idea splitting the hand rank array in two! Keep 'em coming! Aug 23 '19 at 16:13
• @Jonathan Allan Thanks. I've incorporated your idea using slightly different approaches.
– Joel
Aug 23 '19 at 17:26
• You can save 2 bytes by storing the rank table in a single string: "HC92FA51GAB4E893D760"[s==t::2]
– ovs
Aug 23 '19 at 17:41
• And another 4 bytes shorter if you're willing to switch to Python 2. (cmp is not available in Python 3)
– ovs
Aug 23 '19 at 17:52
• You can use str.find instead of str.index to save one byte. The only behaviour difference between the two methods is that index throws an error when the element isn't found, while find returns -1. So it won't be an issue for your code. Aug 23 '19 at 18:22

# x86-16 machine code, 87 83 bytes

Binary:

00000010: 30bb 3501 3af4 7503 bb3f 01e8 0a00 92e8  0.5.:.u..?......
00000020: 0600 f6e2 d40a d7c3 b106 bf49 01f2 aee3  ...........I....
00000030: 038a 4504 c312 0a10 0611 0c0f 0a0e 070d  ..E.............
00000040: 030b 020b 0509 0408 0124 1a21 1b11 0003  .........$.!.... 00000050: 0808 09 ... Unassembled: E8 010A CALL GET_HAND ; score first hand, ranked score into AL 50 PUSH AX ; save score E8 010A CALL GET_HAND ; score second hand 5A POP DX ; restore first hand into DL 3A C2 CMP AL, DL ; compare scores - result in CF, OF and ZF GET_HAND: ; 4 char string to ranked score ("9s7c" -> 6) AD LODSW ; load first card string 2C 30 SUB AL, '0' ; ASCII convert 92 XCHG DX, AX ; store in DX AD LODSW ; load second card string 2C 30 SUB AL, '0' ; ASCII convert BB 0139 MOV BX, OFFSET R ; first, point to non-suited table 3A F4 CMP DH, AH ; is it suited? 75 03 JNZ NO_SUIT BB 0143 MOV BX, OFFSET RS ; point to suited table NO_SUIT: E8 012C CALL GET_VALUE ; get face card value in AL 92 XCHG DX, AX ; swap first and second cards E8 012C CALL GET_VALUE ; get face card value in AL F6 E2 MUL DL ; multiply values of two cards D4 A0 AAM ; AL = AL mod 10 D7 XLAT ; lookup value in rank score table C3 RET ; return to caller GET_VALUE: ; get value of a card (2 -> 2, J -> 3, A -> 9) B1 06 MOV CL, 6 ; loop counter for scan BF 014D MOV DI, OFFSET V ; load lookup table F2/ AE REPNZ SCASB ; scan until match is found E3 03 JCXZ NOT_FOUND ; if not found, keep original numeric value 8A 45 04 MOV AL, BYTE PTR[DI+4] ; if found, get corresponding value NOT_FOUND: C3 RET ; return to program R DB 18, 10, 16, 6, 17, 12, 15, 10, 14, 7 ; unsuited score table RS DB 13, 3, 11, 2, 11, 5, 9, 4, 8, 1 ; suited score table V DB 'J'-'0','Q'-'0','K'-'0','A'-'0','T'-'0' ; face card score table DB 3, 8, 8, 9, 0 Input is as a string such as Js3sKsKh, at pointer in SI. Output is ZF = 0 and SF = OF (test with JG) if player 1 wins, SF ≠ OF (test with JL) if player 2 wins or ZF (test with JE) if a draw. Output using DOS test program: # 05AB1E, 41 37 bytes •V›{₆Ÿ&∊WÍj¸•19вyεøËs‘ßŒQ‘ŽćS‡Pθ«}èÆ -4 bytes thanks to @Grimy. Input as a list of list of list of characters, like the third example input format in the challenge description. I.e. P1 7c Qh & P2 8s Ks would be input as [[["7","c"],["Q","h"]],[["8","s"],["K","s"]]]. (And uses "10" for 10.) Outputs a negative integer if player 1 wins; a positive integer if player 2 wins; or 0 if it's a draw. Explanation: •V›{₆Ÿ&∊WÍj¸• # Push compressed integer 36742512464916394906012008 19в # Convert it to base-19 as list: # [18,10,16,6,17,12,15,10,14,7,13,3,11,2,11,5,9,4,8,1] Iε # Push the input, and map each of its hands to: ø # Zip/transpose the hand; swapping rows/columns # i.e. [["8","s"],["K","s"]] → [[["8","K"],["s","s"]]  # Push them separated to the stack Ë # Check if the two suits in the top list are equal (1/0 for truthy/falsey) s # Swap to get the list with the two values ‘ßŒQ‘ # Push dictionary string "JAKEQ" ŽćS # Push compressed integer 39808 ‡ # Transliterate these characters to these digits P # Now take the product of the two values in the list θ # Only leave the last digit (basically modulo-10) « # And merge it to the 1/0 # (now we have the hand values of both players, # where instead of a trailing "s" we have a leading 1) }è # After the map: index each value into the earlier created integer-list # (now we have the hand rank of both players) Æ # And then reduce the resulting integers by subtracting # (after which the result is output implicitly) See this 05AB1E tip of mine (sections How to use the dictionary? How to compress large integers? and How to compress integer lists?) to understand why •V›{₆Ÿ&∊WÍj¸• is 36742512464916394906012008, •V›{₆Ÿ&∊WÍj¸•19в is [18,10,16,6,17,12,15,10,14,7,13,3,11,2,11,5,9,4,8,1], ‘ßŒQ‘ is "JAKEQ", and ŽćS is 39808. • The question explicitly allows taking input T as 10, so you can just drop the T from JTQKA (and use compressed integer 3889 instead of 30889). Also, T* ... + could be ... «. Aug 26 '19 at 12:16 • @Grimy Ah, I indeed knew 10 instead of T is allowed, but didn't think about $10n\bmod 10 = 0$! And T*...+ being ...« is obvious now that I see it.. >.> Thanks! Aug 26 '19 at 12:21 • 37 (now actually works!) Aug 26 '19 at 12:24 • @Grimy Ah, nice use of the dictionary like that! Aug 26 '19 at 12:29 # PHP, 212185178 149 bytes while($p=$argv[++$x])x=ord(rjpfqlojngmckbkeidha[(($v=[J=>3,Q=>8,K=>8,A=>9])[$p[0]]?:$p[0])*($v[$p[2]]?:$p[2])%10+($p[1]==$p[3])*10]);echo${1}-${2};

Try it online!

• -7 bytes thanks to @Night2!
• -29 bytes by ASCII encoding the table instead of array

Input is via command line. Output to STDOUT is negative if player 1 wins, positive if player 2 wins, 0 if tie. Example:

$php modten.php Js3s KsKh -1 • @Night2 I suppose if I was willing to give us the spaceship operator (I mean, how often do you get to use that?), I could -2 bytes and just return negative, positive or zero, instead of -1, 1 or 0. Aug 24 '19 at 17:29 • I was amazed (in a good way) to see the spaceship operator in previous answer. Aug 24 '19 at 17:47 # Jelly, 46 bytes “T0J3Q8K8A9”yⱮZV€P$Eƭ€)%⁵UḌị“©N¿!Æßvṅ?żṀ’b18¤I

Try it online!

A full program taking as its argument for example ["7h","Ks"],["4s","Ts"] and printing zero if both players draw, positive if player 1 wins and negative if player 2 wins.

# Perl 6, 10110094 88 bytes

-1 byte thanks to Jo King

{[-] .map:{'HC92FA51GAB4E893D76'.ords[[*](.[*;0]>>.&{TR/JQKA/3889/})%10*2+[eq] .[*;1]]}}

Try it online!

Takes input as f(((<J ♠>, <3 ♠>), (<10 ♠>, <K ♥>))) using 10 for Ten. Returns a value < 0 if player 1 wins, > 0 if player 2 wins, 0 if it's a draw.

### Explanation

{
[-]  # subtract values
.map:{  # map both hands
'HC92FA51GAB4E893D76'.ords[  # lookup rank in code point array
[*](  # multiply
.[*;0]  # card ranks
>>.&{TR/JQKA/3889/}  # translate J,Q,K,A to 3,8,8,9
)
%10*2  # mod 10 times 2
+[eq] .[*;1]  # plus 1 if suited
]
}
}

# C (gcc), 172167165164 162 bytes

char*v="T 23456789   J    QA        K";p(char*l){return"A<92?:51@:;4>893=760"[(l[1]==l[3])+(index(v,l[2])-v)*(index(v,*l)-v)%10*2];}f(char*s){return p(s+5)-p(s);}

Try it online!

4 bytes shaved off thanks to the amazing @ceilingcat!

Basically a port of @Joel's Python3 solution, but without the base18 encoding. Expects the input as one string with a space separating the hands of the two players, and outputs an integer that is positive, negative or zero to indicate player 1 wins, player 2 wins or if it's a draw.

# Charcoal, 97 bytes

≔”)¶＆sＮψU↓”ζＦ¹³Ｆ¹³Ｆ⁻⁴⁼ικ⊞υ⁺÷λ³⊗﹪Π⁺§ζι§ζκχ≔”A↘τ[⁵ＰkxτG”ε≔⁰δＦ⟦θη⟧≦⁻№υ⁺⁼§ι¹§ι³⊗﹪Π⁺§ζ⌕ε§ι⁰§ζ⌕ε§ι²χδＩδ

Try it online! Link is to verbose version of code. Takes input as two strings of 4 characters e.g. QcKc 6d4d and outputs a signed integer. Explanation:

≔”)¶＆sＮψU↓”ζ

Compressed string 2345678903889 represents the card values.

Ｆ¹³Ｆ¹³

Loop over each possible pair of values.

Ｆ⁻⁴⁼ικ

Loop over each possible second card suit. Without loss of generality we can assume that the first card has suit 3, so the second card suit can range from 0 to 3 unless the values are the same in which case it can only range from 0 to 2.

⊞υ⁺÷λ³⊗﹪Π⁺§ζι§ζκχ

Compute the modified score of the hand, which is the value of the hand doubled, plus 1 if the suits are the same (i.e. the second card has suit 3).

≔”A↘τ[⁵ＰkxτG”ε

Compressed string 23456789TJQKA represents the card characters. The input cards are looked up in this string and then the position is used to index into the first string to get the card's value.

≔⁰δ

Initialise the result to 0.

Ｆ⟦θη⟧

Loop over the two hands.

≦⁻№υ⁺⁼§ι¹§ι³⊗﹪Π⁺§ζ⌕ε§ι⁰§ζ⌕ε§ι²χδ

Calculate the modified score of the hand, and thus its frequency, and subtract the result from this.

Ｉδ

Output the frequency difference.

# C# (Visual C# Interactive Compiler), 139 bytes

x=>x.Sum(n=>(i++%2*2-1)*(n[1]==n[3]?"":" ")[n.Aggregate(1,(a,b)=>a*(b>85?1:b>83?0:b>74?8:b>73?3:b>64?9:b-48))%10]);int i

Try it online!

# Perl 5-p, 107 bytes

$a=A;y/ATJQK/90388/;${$a++}=substr"IAG6HCFAE7D3B2B59481",($1eq$3).$&*$2%10,1while/.(.) (.)(.)/g;$_=$A cmp$B

Try it online!

### Input:

As 4d,Th 8c

(Actually, the comma can be any character.)

### Output:

-1  Player one wins
0  Draw
1  Player two wins