Euchre bots (card game)

10

0

The idea of this challenge is simple: create a bot to play the card game Euchre.

For those of you who don't already know them, I've written out the rules to Euchre here as they pertain to this challenge.

I recommend using python or something similar, but the only real restriction is that it has to be compatible with the controller code

Input:

Your euchre bot will get different kinds of input depending on the current phase of the game or round. Generally speaking, you'll get the game phase on the first line followed by a comma and a the number of points your team has, and then the relevant data on the following lines.

Chronologically, your bot will get input in the following order:

Ordering Trump:
    js,ah,qc,ts,jc  // the cards in your hand
    2               // number of points your team has
    0               // number of tricks your team has taken
    ordering        // the phase of the game
    th              // the turned up card
    p,p             // each previous player’s decision

Naming Trump:
    js,ah,qc,ts,jc  // the cards in your hand
    2               // number of points your team has
    0               // number of tricks your team has taken
    naming          // the phase of the game
    p               // each previous player’s decision

Dealer Discarding:
    js,ah,qc,ts,jc  // the cards in your hand
    2               // number of points your team has
    0               // number of tricks your team has taken
    discard         // the phase of the game
    th              // the card you will pick up

Going alone:
    js,ah,qc,ts,jc  // the cards in your hand
    2               // number of points your team has
    0               // number of tricks your team has taken
    alone           // the phase of the game
    h               // the trump suit
    n,n             // each previous player’s decision

Your turn:
    js,ah,qc,ts,jc  // the cards in your hand
    2               // number of points your team has
    0               // number of tricks your team has taken
    turn            // the phase of the game
    h               // the trump suit
    td,8h,p         // each previous player’s card

Trick data:
                    // the cards in your hand (none, since this happens at the end of a trick)
    2               // number of points your team has
    1               // number of tricks your team has taken
    trick           // the phase of the game
    0               // the index of the following list that is your card
    js,tc,4d,js     // the cards played during the trick in the order they were played

Output:

Your euchre bot will have different outputs depending on the current phase of the game or round.

Ordering Trump:
    p   //for pass
    OR
    o   //for order up

Naming Trump:
    p           //for pass
    OR ANY OF
    c,s,h,d     //the suit you want to name

Going alone:
    n   // no
    OR
    y   // yes

Your turn:
    js  //the card you want to play

Scoring:

Your bot's score is the total number of games it wins.

Your bot will play against every other bot, and it will always be partnered with a copy of itself.

Notes:

Here's a simple template in python2.7:

#!/usr/bin/python2.7
import sys

data = sys.stdin.readlines()

hand = data[0].strip().split(',')   # Hand as a list of strings
points = int(data[1])       # Number of points
tricks = int(data[2])       # Number of tricks

out = ''

if data[3] == 'ordering':
    card = data[4]              # The upturn card
    prev = data[5].strip().split(',')   # The previous player's decisions as a list
    # Ordering logic
    out =       # 'o' or 'p'
elif data[3] == 'naming':
    prev = data[4].strip().split(',')   # The previous player's decisions as a list
    # Naming logic
    out =       # 'p', 'h', 's', 'c', or 'd'
elif data[3] == 'discard':
    card = data[4]              # The card you'll take
    # Discarding logic
    out =       # The card you want to discard
elif data[3] == 'alone':
    trump = data[4]             # The trump suit
    prev = data[5].strip().split(',')   # The previous player's decisions as a list
    # Alone logic
    out =       # 'y' for yes, 'n' for no
elif data[3] == 'turn':
    trump = data[4]             # The trump suit
    prev = data[5].strip().split(',')
    # Turn logic
    out =       # The card you want to play
elif data[3] == 'trick':
    trump = data[5]
    cards = data[6].strip().split(',')
    my_card = cards[int(data[4])]
    # Data logic

print(out)
  1. There will always be 4 total responses. If someone goes alone, then their partner's response will be "p" on their turn.

  2. I tried to shave down the amount of redundant input, so to be extra clear:

    2a. Both your position relative to the dealer/leader and the card your partner played can be determined by the number of previous outputs. There is 1 player between you and your partner. There For instance, if you get "td,8h,p" as the last line on your turn, you can see that your partner played an 8h, and the other team has a player that is going alone.

  3. If you are curious, the deal is done in the traditional way (in two rounds alternating packets of 2 and 3 cards) but that's not really relevant to your bot, so...

  4. If the second player decides to order up in the trump phase, that phase will continue, but their outputs will pretty much be ignored. In other words, whoever orders up first is on the Namers team regardless of any other output.

  5. The following are the defaults for the various game phases. If you don't output a valid response for that round, then your response is changed to what's below.

    Ordering Trump: p

    Naming Trump: p

    Discarding: (the first card in your hand)

    Going Alone: n

    Your Turn: (the first legal card in your hand)

  6. Here's the controller code for your testing purposes.

    6a. Notice you can pass in either 2 or 4 bot names, if you give it 4 bots then they get partnered up randomly, and with 2 they are partnered up with copies of themselves.

    6b. You need a 'bots' directory in the same directory as the controller code, and your bot code needs to be in the bots directory.

  7. For those that want their bot to remember what cards were played, you are given the opportunity during the "trick" phase, which tells your bot which cards were played. You can write to a file in the bots directory as long as that file doesn't exceed 1kb.

Scoreboard:

Old Stager:  2
Marius:      1
Random 8020: 0

The Beanstalk

Posted 2015-12-03T18:59:33.533

Reputation: 311

2I'd recommend including sample bots to make it easier for people to write their bots. – Nathan Merrill – 2015-12-03T19:30:40.383

@NathanMerrill Added some code for a random bot – The Beanstalk – 2015-12-03T20:13:54.577

3Post it as a submission. However, the problem with that random bot is that it ignores the majority of the input you are giving it. People love to copy/paste (then modify) code, so the more comprehensive your initial bots are, the more submissions (and better submissions) you will recieve. – Nathan Merrill – 2015-12-03T20:23:19.657

@NathanMerrill Added a template instead, thanks for the tips – The Beanstalk – 2015-12-03T20:42:48.233

There is a bug in your controller. After a couple of rounds all players are "sitting out" because the "playing" flag is never set back to "true". – Sleafar – 2015-12-04T11:58:16.047

1Am I right to assume that unless the bot is the last player of the turn, it has no way of knowing what was played in the last turn? – plannapus – 2015-12-04T12:09:18.853

@plannapus I assume it's even worse than that, all bots are affected by a severe case of amnesia, forgetting everything that happened before current turn. – Sleafar – 2015-12-04T12:13:46.763

1@Sleafar well if there was a way of knowing what was played during the current turn, the bot could write it to a file, in order to keep track. – plannapus – 2015-12-04T12:16:05.560

@plannapus Based on the observation you mentioned, I assume this is not allowed. – Sleafar – 2015-12-04T12:20:16.487

One more bug. The decisions of other players are provided as this: ['ad', 'jd', 'qd'], instead of the specified format. – Sleafar – 2015-12-04T12:46:59.493

May I write to my own file? – Not that Charles – 2015-12-04T16:12:05.670

@Sleafar I've fixed the first bug (you'll have to download the controller code again), but the second one I can't reproduce. If you're using the template, then it programmatically breaks up the input into items of a list, but the actual input has the format "9h,qs,as,jc,ks" – The Beanstalk – 2015-12-04T16:34:21.730

1@NotthatCharles I've updated the rules to explicitly allow writing to a file – The Beanstalk – 2015-12-04T16:36:03.813

@plannapus Fixed just now, thanks for catching it – The Beanstalk – 2015-12-04T16:42:58.577

1@TheBeanstalk Writing to a file only makes sense if every player has the information what was played by the other players. Currently the player going first has the least information about each round. The input bug occurs in Python 2.7.8 on Windows. Your template doesn't work as well because the splitted lines contain a '\n' at the end. I've implemented a workaround for both problems in the Random Bot below. – Sleafar – 2015-12-04T16:51:52.220

Yes, as @Sleafar said, it'd be useful (and fair) to give players information about what happened in the previous round of play. – Not that Charles – 2015-12-04T17:16:40.767

@Sleafar It hadn't occurred to me to add that in when first creating this challenge, but I absolutely agree. The challenge has been updated to include a new phase, and I've fixed the input issue – The Beanstalk – 2015-12-07T17:25:32.540

Answers

2

Marius

I wrote that bot in R. I did some tests with your controller and they seem to communicate correctly.

#!/usr/bin/Rscript
options(warn=-1)
infile = file("stdin")
open(infile)
input = readLines(infile,5)
hand = strsplit(input[1],",")[[1]]
phase = input[4]
if(!phase%in%c("discard","naming")) input = c(input,readLines(infile,1))
other_o = c("a","k","q","j","t","9")
alone = "n"
ord = "p"
trumpify = function(color){
    tr_suit = switch(color,
            "c" = c("c","s",rep("c",5)),
            "s" = c("s","c",rep("s",5)),
            "h" = c("h","d",rep("h",5)),
            "d" = c("d","h",rep("d",5)))
    paste(c("j","j","a","k","q","t","9"),tr_suit,sep="")
    }

if(phase%in%c("ordering","alone")){
    flip = input[5]
    if(phase=="ordering") trump = trumpify(substr(flip,2,2))
    if(phase=="alone") trump = trumpify(flip)
    hand_value = sum((7:1)[trump%in%c(hand,flip)])
    if(hand_value>13) ord = "o"
    if(hand_value>18) alone = "y"
    if(phase=="alone") cat(alone)
    if(phase=="ordering") cat(ord)
    }

if(phase=="naming"){
    name = "p"
    colors = unique(substr(hand,2,2))
    col_values = sapply(colors,function(x)sum((7:1)[trumpify(x)%in%hand]))
    if(any(col_values>13)){name = colors[which.max(col_values)]}
    cat(name)
    }

if(phase=="discard"){
    flip = input[5]
    new_hand = c(hand,flip)
    trump = trumpify(substr(flip,2,2))
    discardables = new_hand[!new_hand%in%trump]
    if(length(discardables)){
        val = sapply(substr(discardables,1,1),function(x)(6:1)[other_o==x])
        d = discardables[which.min(val)]
    }else{d = tail(trump[trump%in%new_hand],1)}
    cat(d)
    }

if(phase=="turn"){
    trump = trumpify(input[5])
    fold = strsplit(gsub("[[:punct:]]","",input[6]),",")[[1]]
    if(length(fold)&!any(is.na(fold))){
        fold_c = substr(fold[1],2,2)
        f_suit = if(fold_c!=input[5]){paste(other_o,fold_c,sep="")}else{trump}
        l = length(f_suit)
        current = (l:1)[f_suit%in%fold]
        if(any(hand%in%f_suit)){
            playable = hand[hand%in%f_suit]
            val = sapply(playable,function(x)(l:1)[f_suit==x])
            if(all(max(val)>current)){
                play = playable[which.max(val)]
            }else{play = playable[which.min(val)]}
        }else if(any(hand%in%trump)){
            playable = hand[hand%in%trump]
            val = sapply(playable,function(x)(7:1)[trump==x])
            if(!any(fold%in%trump)){
                play = playable[which.min(val)]
            }else{
                trumped = fold[fold%in%trump]
                val_o = max((7:1)[trump%in%trumped])
                play = ifelse(any(val>val_o), playable[which.min(val[val>val_o])], playable[which.min(val)])
            }
        }else{
            val = sapply(substr(hand,1,1),function(x)(6:1)[other_o==x])
            play = hand[which.min(val)]
            }
    }else{
        col = c("c","s","h","d")
        non_tr = col[col!=input[5]]
        aces = paste("a",non_tr,sep="")
        if(any(hand%in%aces)){
            play = hand[hand%in%aces][1]
        }else if(any(hand%in%trump)){
            playable = hand[hand%in%trump]
            val = sapply(playable,function(x)(7:1)[trump==x])
            play = playable[which.max(val)]
        }else{
            val = sapply(substr(hand,1,1),function(x)(6:1)[other_o==x])
            play = hand[which.max(val)]
        }
    }
    cat(play)   
}

I'll probably modify it later since I didn't implement a "turn" logic for when the bot is defending, but I'm posting it now so that people have another bot to test against.

For now, it only implements very basic strategies such as leading with an ace, a trump or any other high card; following with a higher card when possible or playing the lowest valued card if not; ordering when hand has high value and naming the color in which the hand would have had the highest value; going alone when hand has very high value. The "value" of each card is computed very simply: value of trumps starts from 7 for the first jack and decreases along the trump suit.

plannapus

Posted 2015-12-03T18:59:33.533

Reputation: 8 610

1

Old Stager

This bot follows some simple rules which served him well for a long time:

  • Intuitively assign a score to each card
  • Choose the trump if the hand score is good enough
  • In case of a really good hand play alone
  • Choose best card when playing first
  • Choose a better card than the opponents if they are winning
  • Choose worst card if the partner is winning or if winning is not possible

I increased the target score from 10 to 100 for testing in the controller. The results are still very random, but way more stable than before.

#!/usr/bin/python2.7
from __future__ import print_function
import sys, re, math

base = 1.2
playThreshold = 27.0
aloneThreshold = 36.0
sameColor = { 'd' : 'h', 'h' : 'd', 's' : 'c', 'c' : 's' , '' : '', 'n' : 'n' }
cardValue = { 'p' : 0, '9' : 1, 't' : 2, 'j' : 3, 'q' : 4, 'k' : 5, 'a' : 6 }

class Card(object):
    def __init__(self, name, trump):
        self.name = name
        self.value = cardValue[name[0:1]]
        self.suit = name[1:2]
        self.trump = False
        self.updateScore(trump)
    def updateScore(self, trump):
        self.score = self.value
        if self.suit == trump:
            self.trump = True
            self.score += 6
        if self.value == 3:
            if self.suit == trump:
                self.score = 14
            if self.suit == sameColor[trump]:
                self.trump = True
                self.score = 13

class Cards(object):
    def __init__(self, cards, trump):
        self.list = []
        self.score = 0.0
        if cards:
            for c in cards.split(','):
                self.append(Card(c, trump))
    def append(self, card):
        self.list.append(card)
        self.score += math.pow(base, card.score)
    def updateScore(self, trump):
        self.score = 0.0
        for card in self.list:
            card.updateScore(trump)
            self.score += math.pow(base, card.score)
    def best(self):
        card = self.list[0]
        for i in self.list[1:]:
            if i.score > card.score:
                card = i
        return card
    def worst(self):
        card = self.list[0]
        for i in self.list[1:]:
            if i.score < card.score:
                card = i
        return card
    def better(self, ref):
        card = None
        for i in self.list:
            if i.score > ref.score and (card is None or i.score < card.score):
                card = i
        return card

def ordering(hand, card, decisions):
    if len(decisions) == 3:
        hand.append(card)
    return 'o' if hand.score > playThreshold else 'p'

def naming(hand):
    result = 'p'
    score = playThreshold
    for trump in ['d', 'h', 's', 'c']:
        hand.updateScore(trump)
        if hand.score > score:
            result = trump
            score = hand.score
    return result

def turn(hand, decisions):
    bestIndex = -1
    for i, d in enumerate(decisions.list):
        if d.suit:
            bestIndex = i
            break
    if bestIndex == -1:
        return hand.best()
    else:
        suit = decisions.list[bestIndex].suit
        for i in range(2, len(decisions.list)):
            if (decisions.list[i].suit == suit or decisions.list[i].trump) and decisions.list[i].score > decisions.list[bestIndex].score:
                bestIndex = i
        matching = Cards('', '')
        for card in hand.list:
            if card.suit == suit:
                matching.append(card)
        if not matching.list:
            if bestIndex == len(decisions.list) - 2:
                return hand.worst()
            for card in hand.list:
                if card.trump:
                    matching.append(card)
            if not matching.list:
                return hand.worst()
        if bestIndex == len(decisions.list) - 2:
            return matching.worst()
        card = matching.better(decisions.list[bestIndex])
        if card:
            return card
        return matching.worst()

output = ''
input = re.split('\n', re.sub(r'[^a-z0-9,\n]+', '', sys.stdin.read()))

if input[3] == 'ordering':
    output = ordering(Cards(input[0], input[4][1:2]), Card(input[4], input[4][1:2]), input[5].split(','))
elif input[3] == 'naming':
    output = naming(Cards(input[0], 'n'))
elif input[3] == 'discard':
    output = Cards(input[0], input[4][1:2]).worst().name
elif input[3] == 'alone':
    output = 'y' if Cards(input[0], input[4]).score > aloneThreshold else 'n'
elif input[3] == 'turn':
    output = turn(Cards(input[0], input[4]), Cards(input[5], input[4])).name

print(output)

Sleafar

Posted 2015-12-03T18:59:33.533

Reputation: 2 722

0

Random 8020

A simple random bot, which will pass 80% of the time. Uncomment the last line to see the (cleared up) input and output.

#!/usr/bin/python2.7
from __future__ import print_function
import sys, re, random

output = ''
input = re.split('\n', re.sub(r'[^a-z0-9,\n]+', '', sys.stdin.read()))
hand = input[0].split(',')

if input[3] == 'ordering':
    output = random.choice(['p', 'p', 'p', 'p', 'o'])
elif input[3] == 'naming':
    output = random.choice(['p', 'p', 'p', 'p', random.choice(hand)[1:2]])
elif input[3] == 'discard':
    output = random.choice(hand)
elif input[3] == 'alone':
    output = random.choice(['n', 'n', 'n', 'n', 'y'])
elif input[3] == 'turn':
    output =  random.choice(hand)
    if input[5]:
        suited = filter(lambda x: input[5][1:2] in x, hand)
        if suited:
            output = random.choice(suited)

print(output)
#print(input, " --> ", output, file=sys.stderr)

Sleafar

Posted 2015-12-03T18:59:33.533

Reputation: 2 722