14
\$\begingroup\$

Pokerface

Introduction

Leo enjoys playing poker, but his job at Tech Inc. is too demanding for him to learn how to play well. Leo, being a computer scientist, is not discouraged. He decides to take more time than it would have taken to just learn poker, and use it to write a poker bot to help him play better. But now Leo has a problem: in order to understand how to play a bit better, Leo needs to observe multiple games of multiple "people," but the "people" need different play styles to improve the quality and reality of the game.

The Challenge

Leo recalls that there is actually a website dedicated to programming challenges, and is enlisting your help! Your job is to write a program which plays "Pokerface" a modified version of 5-card poker. The program will take input as a 5-card hand in whatever format you wish, after which the program will output:

  • Exactly (case-sensitive) "true" "1" or "t" if the player wishes to exchange cards, any other non-empty output otherwise.
  • If true, list of indices of cards and/or card names the player wishes to exchange.
  • A single number between 0 and 3, which specifies how many additional cards the player wants.
  • Print out the hand the player wishes to use.

(See formatting below)

Pokerface rules

  • Since pokerface is a text-based adventure game, cards must be presented in a consistent way. Cards are represented by two character codes, the first character is the suit, and the second is the name of the card.
    • Cards:
      • 2-9 = 2-9
      • 10 = T
      • Jack = J
      • Queen = Q
      • King = K
      • Ace = A
    • Suits:
      • Spades = S
      • Clubs = C
      • Hearts = H
      • Diamond = D

So the ace of spades would be SA, the 10 of hearts is HT, the 4th of diamonds is D4, etc.

  • A single round of Pokerface consists of four steps:
    • The deck is reshuffled and a five card hand is dealt to each player.
    • Each player is given the chance to exchange as many cards as they want.
    • Each player is given the chance to gain up to three more cards.
    • Each player must reveal their best hand.
  • The best hand wins, and gains that player a point. In the event of a tie, both players get a point.
  • In a single game, ten rounds are played and the player with the most points wins and gains a single "win point." In the event of a tie, both players gain a win point.
  • Leo doesn't really have a large amount of money, so your bot can assume that this is a perfect world with no betting.

Hands

  • Hands are exactly 5 cards in length (initial input and final output).
  • Hands are ranked consistent with the rules described here.

Input / Output

  • Leo only knows Java, so your program must be executable via the Process API (command line), and use STDIN and STDOUT for input and output, respectively.
  • For each step of input and output detailed above, the input and output must each exist on one line.
  • There must be at least one trailing new line after the final output. (This is due to the way input is read from STDIN)
  • No extraneous input/output is allowed, other than trailing and leading spaces. The parser simply does not understand things like final_hand=... or draw 0.
  • When drawing, output is a single integer, when exchanging output is a list of integers and/or cards defined below, and when being dealt the original hand, output is a list of cards defined below.
  • All input/output numbers must be positive integers in base 10.
  • You may define the format for card input (see post format below).
  • True is defined as exactly "true," "1" or "t" and false is any other non-empty value.
  • During the exchange step:
    • Card indices must be output with at least one space between them (e.g. 3 4 0)
    • Card names must be output with at least one space between them (e.g. H4 S8)
    • Card names and indices may be mixed in the output (e.g. 0 H7 3 D3)
    • Trailing and leading spaces are allowed.
    • The input as a result of the player outputting the above will be formatted as specified by the bot.jlsc file, in the same order as requested
  • The number of cards a player wants to add to their hand can have leading and trailing spaces.
  • Hands must be output with at least one space between them (e.g. H4 D5 CA), trailing spaces and leading spaces are allowed.
  • Hands do not need to be output in proper order (e.g. H4 D4 C4 DA SA and H4 DA D4 SA C4 both represent 4, 4, 4, Ace, Ace, which is a full house).
  • If you wish to build a strategy by analyzing opponents hands, you may store data in a <botname>/data directory.
    • After competing bots have displayed their hands, they will be written to every bots data directory, in hands.txt, with each hand on a new line (separated by \n). The file will be encoded in US_ASCII.
  • After your bot requests new cards or exchange cards, the cards will be input depending on what format you specify in the bot.jlsc file.

Post Format

  • Every post must include two things:
    • The source code of your bot, or a link to a public facing repository.
    • A zip file containing:
      • The compiled/executable version of your bot (If the file is a .exe or other non-de-compilable file, please just include compilation instructions in your post).
      • A bot.jlsc file, see below (side note: the .jlsc extension is just because of a side project of mine, a configuration format. The file below matches the proper syntax, so don't worry).
    • The .zip file must be named the same as your bot.
  • If you don't have access to windows or some other zipping utility, or can't make a .zip for whatever reason, just include the text of the bot.jlsc file in your post

bot.jlsc file:

name= "Botty"
link= "example.com"
cmd= "java -jar Botty.jar"
input_hand= "${0} ${1} ${2} ${3} ${4}"
input_1= "${0}"
input_2= "${0} ${1}"
input_3= "${0} ${1} ${2}"
input_4= "${0} ${1} ${2} ${3}"

Where:

  • "cmd" is the windows command line command to run your bot. Note that your bot will be in directory <botname>, so adjust the command accordingly.
  • "name" is the name of your bot.
  • "link" is the link to your answer, you'll have to edit this in after posting.
    • "input_hand" is how you want the original dealing to be formatted (with ${#} representing cards 0-4).
  • "input_1" is how you want the input of one additional card to be formatted.
  • "input_2" is how you want the input of two additional cards to be formatted.
  • "input_3" is how you want the input of three additional cards to be formatted.
  • "input_4" is how you want the input of four additional cards to be formatted.

Specifics

  • These loopholes are disallowed (see 'common pitfalls')
  • You may not write a bot the will always output the best hand possible, every time, within the rule set. (i.e. no long-running brute-force bots, nothing should be quite as 'good' as LeoBot)
  • Your bot should run in ~100 ms or less (Lenient on this point, max of ~1 second)
  • Any output of the bot after its chosen hand will be ignored.
  • Standard loopholes are disallowed.
  • Yes, I know linux is better, but I have a windows PC, so be sure the compiled/executable version of your program can be run from the windows command line.
    • I already have python and java installed on my computer, but I'm willing to update to new versions and install other environments, so please specify what type of environment your program requires.
  • You may not write a bot which does the same thing as another bot in every case. Spam bots are allowed, but discouraged.
  • Your bot may only use cards it has. Cards lost through exchange or not dealt to begin with are invalid output in the final hand.
  • Input and output may only contain ASCII characters.

Tournaments

  • Tournaments will be run when I get the time (my schedule is nearly as packed as Leo's, so this my be a little infrequent. Sorry for the inconvenience.).
  • Bots will be pit against eachother in 4 person games, and there will be one game for each possible subset of bots (i.e. lots of games).
    • This process will be repeated five times.
    • Due to the way the tournament handler makes the groups of bots, up to three filler bots will be added to make the number of bots divisible by 4. These bots will simply return the hand they were originally dealt.
  • After every round and game is run, the scores of the bots will be computed based on the number games they won.
    • Multiple bots can share a position (ties for first won by first posted).
  • After a tournament finishes, the scores will be appended to the bottom of this post.

Scoring

Normal KoTH rules. The bot(s) that win the most games win the challenge.

LeoBot

Leo's bot is pretty smart. It doesn't exchange any cards, that's too hard, but it does request the maximum number of additional cards, and it determines the best possible hand it can make, and plays that hand. The main logic of leobot is below.

package com.gmail.socraticphoenix.pokerface.leobot;

import com.gmail.socraticphoenix.pokerface.lib.card.Card;
import com.gmail.socraticphoenix.pokerface.lib.card.Deck;
import com.gmail.socraticphoenix.pokerface.lib.rule.HandRegistry;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class LeoBot {

    public static void main(String[] args) {
        List<Card> hand = new ArrayList<>();

        Scanner scanner = new Scanner(System.in);
        hand.addAll(Card.parseHand(scanner.nextLine()));
        System.out.println(false);

        System.out.println(3);
        hand.addAll(Card.parseHand(scanner.nextLine()));

        List<List<Card>> possibleHands = LeoBot.getSubsets(hand, 5);
        System.out.println(Deck.toString(possibleHands.stream().sorted((a, b) -> HandRegistry.determineWinner(b, a).comparable()).findFirst().get()));
    }

    private static <T> void getSubsets(List<T> superSet, int k, int idx, List<T> current, List<List<T>> solution) {
        if (current.size() == k) {
            solution.add(new ArrayList<>(current));
            return;
        }
        if (idx == superSet.size()) return;
        T x = superSet.get(idx);
        if (!current.contains(x)) {
            current.add(x);
        }
        getSubsets(superSet, k, idx + 1, current, solution);
        current.remove(x);
        getSubsets(superSet, k, idx + 1, current, solution);
    }

    public static <T> List<List<T>> getSubsets(List<T> superSet, int k) {
        List<List<T>> res = new ArrayList<>();
        getSubsets(superSet, k, 0, new ArrayList<T>(), res);
        return res;
    }

}

Note that if LeoBot consistently wins the tournaments, and there are a good amount of entries, I'll stop including him in the running.

Important Links

Disclaimer

Leo and Tech Inc. are story elements and any resemblance to real-life companies or people is purely unintentional. (However, when Leo's 'situation' adds or subtracts conditions from the question, those are actually part of the question...)

\$\endgroup\$
24
  • 1
    \$\begingroup\$ @SocraticPhoenix I'd highly recommend weighting it now or never. It'd be really unfair to players to adjust the scoring after submission are already ranked. \$\endgroup\$ Commented Jul 29, 2016 at 3:29
  • 2
    \$\begingroup\$ @DestructibleWatermelon better? Just FYI, this was on sandbox for like 2-3 days... No one commented. I mean, it's all cool though \$\endgroup\$ Commented Jul 29, 2016 at 5:18
  • 2
    \$\begingroup\$ Also, @NathanMerrill is probably still right about the dumb bot winning. After investigating cjam somwhat, a 5 byte program "f"q+ fulfils minimum requirements. If there are 10 people in competition, this probably beats all non-dumb entries (non-dumb entry probably has >75 chars, 5*10(score of dumb bot, coming last)=50<75 (score of very small smart bot (coming first))). Thusly, you should probably remove codegolf from this challenge \$\endgroup\$ Commented Jul 29, 2016 at 8:05
  • 2
    \$\begingroup\$ even if Cjam cannot be used, the point stands that dumbbots will be a reasonable strategy, and removing codegolf removes all the difficulties of balancing size VS performance \$\endgroup\$ Commented Jul 29, 2016 at 8:14
  • 1
    \$\begingroup\$ Killed code-golf to death.... \$\endgroup\$ Commented Jul 29, 2016 at 12:53

4 Answers 4

1
\$\begingroup\$

(Python), Pairbot, not quite competing (I don't know how to make cmd commands and stuff)

Pairbot will compete as soon as someone assists with the bot.jlsc, and zip files etc.


Pairbot knows that you don't always get good hands. He knows good hands are rare. Pairbot knows pairs and other duplicates are some of the best hands. Pairbot also knows the lowest hand you can get is a seven high, so he knows if he has 6 high, that's actually a straight (pairbot doesn't know why he knows that). He also knows if his lowest card is 10, (with no pairs), that's also a straight (pairbot knows he can get royal flush this way). Pairbot checks mainly for same number dupes, but also checks for two types of straights in special cases.

card_values={"2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8,
             "9":9, "T":10, "J":11, "Q":12, "K":13, "A":14,}
straight=False
def card_valuing(item):
    return card_values[item[1]]

input_list=input().split()
pairs_to_keep=[]
for item in input_list:
    if sum(item[1]==card[1] for card in input_list)>1:
        pairs_to_keep+=[item]
cards_to_remove=input_list
for item in pairs_to_keep:cards_to_remove.remove(item)#we want to remove all non pairs
hand=pairs_to_keep
if pairs_to_keep==[]:
    input_list.sort(key=card_valuing, reverse=True)
    if card_values[input_list[0][1]]==6:
        straight=True
        hand=input_list
    elif card_values[input_list[-1][1]]==10:
        straight=True
        hand=input_list
    else:
        print("true\n"+" ".join(input_list[1:]))
        hand+=input_list[0]+input().split()
elif input_list!=[]:
    print("true\n"+" ".join(input_list))
    hand+=input().split()
else:print(0, end=', ')
if straight:print("0\n0\n"+" ".join(hand))
else:
    print("3")
    hand+=input().split()
    same_number_dict={} #holds the amount of each type (A, 2, 3, etc.)

    def dict_value(item):
        return int(same_number_dict[item[1]])*100+card_values[item[1]]

    for card in hand:
        same_number_dict[card[1]]=sum(card[1] == item[1] for item in hand)

    hand=list(sorted(hand, key=dict_value, reverse=True))
    final_hand =[]
    last_number=hand[0][1]
    hand_taken=0
    while hand_taken < 5:
        if last_number==hand[0][1]:
            final_hand+=[hand[0]]
            hand=hand[1:]
            hand_taken+=1
        else:
            for card in hand:
                if same_number_dict[card[1]]>5-hand_taken:
                    same_number_dict[card[1]]=5-hand_taken
            hand=list(sorted(hand, key=dict_value, reverse=True))
            last_number=hand[0][1]
    print(" ".join(final_hand))

Format for input is the same as in the example: separated by spaces


If Socratic Phoenix could assist with the file stuffs, that would be good

\$\endgroup\$
12
  • \$\begingroup\$ Clever! So the file you'll want is here, I'm going to edit the main post to make the actual .zip optional... \$\endgroup\$ Commented Aug 2, 2016 at 0:52
  • \$\begingroup\$ Also, +1 for FGITW \$\endgroup\$ Commented Aug 2, 2016 at 0:57
  • \$\begingroup\$ More like FGITLOSG (Fastest gun in the land of slow guns). \$\endgroup\$ Commented Aug 2, 2016 at 1:00
  • \$\begingroup\$ True. I'm not sure that the input/output is in the proper form. When I enter a hand, the program prints "True" and then its current hand. I believe you wan to just print "false" as the "True" indicates you want to exchange cards. Secondly, the program needs to print a single integer when drawing, or integers separated by spaces when exchanging. Not "draw 0." I'll try to clarify the main post. \$\endgroup\$ Commented Aug 2, 2016 at 1:03
  • \$\begingroup\$ [So does it count as competing now?] didn't see new messages. I will correct bot right away \$\endgroup\$ Commented Aug 2, 2016 at 1:18
1
\$\begingroup\$

Plumber, Python

Plumber is all about flushes. Plumber also prioritises higher value cards (meaning he can sometimes get straight flushes, especially royal ones (should they occur).) Plumber is pretty much messed if he doesn't get a flush, except that he might be lucky enough for a straight. Plumber will get flushes about 20% of the time, if calculations by Sherlock9 are correct

hand=input().split()
suit_in_hand={"S":0,"C":0,"D":0,"H":0}
card_values={"2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8,
             "9":9, "T":10, "J":11, "Q":12, "K":13, "A":14,}
def value_sort(x):
    return card_values[x[1]]
def suit_sort(x):
    return suit_in_hand[x[0]]

for card in hand:
    suit_in_hand[card[0]]+=1

hand.sort(key=suit_sort, reverse=True)

print(" ".join(hand[suit_in_hand[hand[0][0]]:]))
hand=hand[:suit_in_hand[hand[0][0]]]

for new_card in input().split():
    hand+=[new_card]
    suit_in_hand[new_card[0]]+=1

print(3)

for new_card in input().split():
    hand+=[new_card]
    suit_in_hand[new_card[0]]+=1
hand.sort(key=value_sort, reverse=True)
hand.sort(key=suit_sort, reverse=True)
print(" ".join(hand[:5]))

Also takes input separated by spaces like other my two bots

\$\endgroup\$
1
  • \$\begingroup\$ Note: I've changed the output rules slightly due to a bug in my own tournament program. There must now be at least one trailing new line after your final output. \$\endgroup\$ Commented Aug 3, 2016 at 16:01
1
\$\begingroup\$

LadyGaga, Python 3

  • Is somewhat blind to suits
  • Has a dress full of bugs
  • And likes to play Poker Face once in a while

    from math import ceil as f
    M=lambda A:max(set(A),key=A.count)
    K=lambda A:A.count(M(A))
    O=lambda A:range(len(A))
    J=lambda A:A[0]+str(U(A[1]))
    X={"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9,"T":10,"J":11,"Q":12,"K":13,"A":14}
    def V(A):return([A[0]]+[int(X[A[1]])])
    def U(c):
     if c==10:c='T'
     if c==11:c='J'
     if c==12:c='Q'
     if c==13:c='K'
     if c==14:c='A'
     return(c)
    def P(A):
     S=[];C=[];t=len(A)
     for x in A:S.append(x[0]);C.append(x[1])
     B=[0]*9;q=len(set(C));p=K(C);D=list(set(C));D.sort()
     B[2]=1/f(13**(4-p));B[6]=1/f(13**(3-p));B[8]=1/f(13**(2-p))
     if (p,q)==(2,4):B[3]=1/1100;B[7]=5/34
     if (p,q)==(3,3):B[3]=1/169;B[7]=1/169
     if (p,q)==(4,2):B[3]=1/13;B[7]=1
     if (p,q)==(2,3):B[3]=5/169;B[7]=1
     if (p,q)==(3,2):B[3]=1;B[7]=1
     for x in O(D):D[x]-=x
     j=M(D);h=K(D)-5;B[5]=13**h
     for x in O(D):
      if j+h<D[x]<j-h and D[x]!=j:B[5]*=13
     W=K(S);B[4]=(4**(W-t))*(13-W)/52
     return(B,M(S))
    def E(B,h,u):
     x=0;D=[];C=[]
     while 1:
      k=list(C)
      C=[]
      while 1:
       p=list(B);del p[x]
       if len(D)==3:break
       if P(p)[0][h]>=P(B)[0][h]:C.append(B[x])
       x+=1
       if x>len(p):break
      if len(C)==0:break
      for x in O(C):
       if k==C or not((u in C[x])and(len(C)-1)):D.append(C[x]);del B[B.index(C[x])]
     return(D)
    s=input()
    A=s.split(' ')
    b=list(map(V,A));G,u=P(b);F=[649739,72192,4164,693,508,254,46.3,20,1.4];H=[]
    for x in O(F):F[x]=1-((1-(1/F[x]))**4)
    for x in O(F):H.append(G[x]-F[x])
    Y=H.index(max(H));p=[]
    e=E(list(b),Y,u);g=list(e)
    for x in O(e):e[x]=J(e[x])
    print(' '.join(e)if len(e)else'')
    for x in g:
     if x in b:del b[b.index(x)]
    s=input()
    if len(s):
     A=s.split(' ')
     b+=list(map(V,A))
    print(3)
    s=input()
    A=s.split(' ')
    b+=list(map(V,A));G,u=P(b);H=[]
    for x in O(F):H.append(G[x]-F[x])
    Y=H.index(max(H))
    e=E(list(b),Y,u)
    for x in e:
     if x in b:del b[b.index(x)]
    for x in O(b):b[x]=J(b[x])
    print(' '.join(b[:5]))
    print()
    
    • (I/O) modeled after PlumberBot -Edit: Extensive bugfixes thanks to Destructible Watermelon -Edit: Due to new rules, a trailing newline after the final output
\$\endgroup\$
14
  • \$\begingroup\$ You might want to use a dictionary instead of all that complicated stuff for the card values \$\endgroup\$ Commented Aug 3, 2016 at 6:52
  • \$\begingroup\$ Anything that has been packed into an array already has been to my knowledge. Which section of code could I shorten? \$\endgroup\$
    – Magenta
    Commented Aug 3, 2016 at 6:59
  • \$\begingroup\$ def V(A): b=[A[0]];c=A[1] if c=='T':c=10 if c=='J':c=11 if c=='Q':c=12 if c=='K':c=13 if c=='A':c=14 return (b + [int(c)]) to x={"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9,"T":10,"J":11,"Q":12,"K":13,"A":14,} def V(A):return(A[0] + x[A[1]]) \$\endgroup\$ Commented Aug 3, 2016 at 7:02
  • \$\begingroup\$ Pairbot is only just longer than your program, and that's because it's readable \$\endgroup\$ Commented Aug 3, 2016 at 7:05
  • \$\begingroup\$ I know. Bad codegolfing habits. \$\endgroup\$
    – Magenta
    Commented Aug 3, 2016 at 7:06
0
\$\begingroup\$

LuckyBot, Python

Pairbot invited his buddy Luckybot, who sprang for the chance. Luckybot had watched a lot of fictional poker, and reckoned he'd figured out the secret to poker: luck. Everyone knows the real pros (James Bond, for example) really rely and getting good hands, not skill. Hence, he doesn't look at his cards, and tries to pack as much good luck into them as possible


lucky_number=24 #IMPORTANT

from random import randint as roll


def lucky_shuffle(i):
    return sorted(i, key=lucky_dice)


def lucky_dice(seed):
    return sum(roll(1,6)for i in range(roll(1,6)))


hand=lucky_shuffle(input().split())

throw=lucky_dice(lucky_number)%5
print("true\n"+" ".join(hand[throw:]))

hand=hand[:throw]+lucky_shuffle(input().split())

hand=lucky_shuffle(hand)
hand=lucky_shuffle(hand)
#One more for good luck
hand=lucky_shuffle(hand)
#maybe one more
hand=lucky_shuffle(hand)
#I got a good feeling about this one
hand=lucky_shuffle(hand)

hand=lucky_shuffle(hand)
#I think I'm done
hand=lucky_shuffle(hand)
#for real this time


hand=lucky_shuffle(hand)

print("3")
hand=hand+lucky_shuffle(input().split())
#All right, I got a real good feeling about this,
#let me shuffle some more luck into them cards!


def extra_super_lucky_shuffle(item):
 return lucky_shuffle(lucky_shuffle(lucky_shuffle(\
    lucky_shuffle(lucky_shuffle(lucky_shuffle(\
        lucky_shuffle(lucky_shuffle(lucky_shuffle(item)))))))))


def super_duper_extra_ultra_uber_luckyshuffle(item):
    return extra_super_lucky_shuffle(extra_super_lucky_shuffle(\
        extra_super_lucky_shuffle(extra_super_lucky_shuffle(item))))


hand=super_duper_extra_ultra_uber_luckyshuffle(super_duper_extra_ultra_uber_luckyshuffle(hand))
#cmoooooooooooooooon
print(hand[:5])
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.