Leaderboard
154 Calculator
144 Taxman
138 Statistician
137 Solver
137 RandoAggroLawyer
136 Gambler
134 Turncoat
119 Lawyer
119 BloodyMurder
113 Bandit
79 Challenger
74 Mask
64 Random
An archive of the latest match, including the log and all output files, is available.
Calculator, by Brilliand, is the winner! His answer is accepted, but that doesn't mean the challenge is over. Feel free to submit new entries or edit your current ones and try to knock him off his throne. Contests will be run with each new entry or edit for the foreseeable future.
Rules of Play
Coup is a card game designed for 2-6 players, which we shall play with two. It consists of a treasury of coins (infinite for our purposes) and a deck of 15 cards, containing 3 each of the following types: Ambassador, Assassin, Captain, Contessa, Duke. At the start of the game, each player is given one coin and dealt two cards at random, which they keep secret until necessary. The object is to be the last player with cards in your hand.
On their turn, a player may take one of the following actions regardless of their cards:
- Income: take 1 coin from the treasury. Unblockable and unchallengeable.
- Foreign Aid: take 2 coins from the treasury. Can be blocked by a player with a Duke. Unchallengeable.
- Coup: Remove a card of one opponent of your choice from play. Costs 7 coins. The victim may choose which card to discard. If a player has 10 or more coins at the start of their turn, they must Coup. Unblockable and unchallengeable.
Depending on their cards, players may also take one of the following actions as their turn:
- Exchange: a player with an Ambassador may take two cards from the deck. Then they may choose from their hand and the drawn cards as many cards as they originally had. (That is, if they had only one card they may exchange it for one of the drawn cards or keep it, and if they had two cards they may choose any two of the four cards.) The two undesired cards are returned to the deck. Unblockable, but challengeable.
- Assassinate: a player with an Assassin may spend 3 coins to remove an opponent's card from the game. The victim may choose which card to discard. Can be blocked by a player with a Contessa, in which case the coins are not returned. Challengeable, in which case the coins are returned.
- Steal: A player with a Captain may take two coins from their opponent. If the opponent has one coin, they will take that one coin. If the opponent has zero coins, they may not Steal. Can be blocked by a player with an Ambassador or a Captain. Challengeable.
- Tax: A player with a Duke may take 3 coins from the treasury. Unblockable, but challengeable.
The tricky part of Coup is that players are allowed to lie about what cards they have! One need not have a card to attempt to perform the action or block associated with it.
When a player performs a card's action, any opponent (even one who is not harmed by that action) may challenge the actor and say they do not believe they have the card for that action. If the challenger is correct, the action is cancelled and the actor must discard one card of their choice (gaining back any coins they spent if applicable). If not, the action is taken, the actor returns the card they were challenged about to the deck and draws a new one, and the challenger must discard one of their cards. Players must be truthful about what cards they hold when challenged.
Cards eliminated from play with Assassinate, Coup, and lost challenges are not returned to the deck, but cards revealed as part of a won challenge are returned to the deck.
Blocks may be challenged just like actions. For example, if player A claims Foreign Aid and player B says "I have a Duke and I block your Foreign Aid", A may say "I don't believe you have a Duke." If that assertion is correct, B loses a card for being caught in a lie and A takes 2 coins; if it is not, A loses a card and gets no coins, and B must return their Duke to the deck and draw a new card.
The way blocks and challenges work with Assassinate must be fleshed out. Suppose Player A says "I have an Assassin, and I Assassinate Player B". If B does not attempt to challenges or blocks A, then the assassination goes through: B loses a card and A pays 3 coins.
Alternatively, B can challenge by saying "I don't believe you have an Assassin". If that is true, then A discards a card and their coins are returned, while B is unaffected, and A's turn ends. If B's belief is incorrect and A holds an Assassin, then B loses both their cards and fails, one for the incorrect challenge and one from the Assassination.
Instead of challenging, B could say "I have a Contessa, and I block the Assassinate". If A believes B, then A's turn ends and their coins are not returned. But A can challenge the block and say "I don't believe you have a Contessa." If B does in fact hold a Contessa, then A loses a card for the incorrect challenge. But if B does not, then B loses one card for being caught in a lie and another from the Assassination.
Similar logic to the above explanation applies to the Captain’s Steal ability, where either the action or the block can be challenged.
It is possible to lose both your cards and be eliminated in one turn, if you unsuccessfully challenge an Assassinate or you are caught falsely claiming you have a Contessa to block an Assassination. You lose one card from the challenge and one card from the Assassination.
Challenge
Your task is to write a program that will play Coup. It will be given as its command line arguments:
- The name of a file containing the list of its and its opponents' actions so far.
- An integer from 0 to 12 indicating the opponent's coin count.
- An integer from 0 to 12 indicating its coin count.
- A string from one to four characters long indicating its cards. Normally this will simply be the one or two cards your program has, but if your program has just succeeded at an Exchange, it will be n + 2 characters long, where n is your number of cards remaining. Your program must then output the n cards it wishes to keep to STDOUT. (Programs must not read or access STDOUT other than for this purpose — if you wish to produce debug output, please write to STDERR.)
- One or more arguments indicating the legal moves it may make.
(Example invocation: yourprogram file.txt 1 7 '~!' a c p q
, meaning "Your opponent has 1 coin. You have 7 coins, an Ambassador, and a Contessa. Write to file.txt your choice of a, c, p, or q given the game history and current game state.")
Your program must append one or (in two specific situations) two characters to the provided file indicating its action. It must not otherwise alter the existing contents of the file. It may create any new files it wishes, but only within the directory in which it is run. Please provide all necessary commands to compile and run your program.
I have provided two example competitors below, written in Go.
The output format is:
I\n
: Income. Legal responses: any turn action (assuming one has the coins for Assassinate/Coup).F
: Foreign aid. Legal responses:d
(block as a Duke),p
(let it pass).C
: Coup. Legal responses: whichever of_
,'
,<
,=
,0
is in your hand.E
: Exchange. Legal responses:q
(challenge, not believing the player has an Ambassador),p
.T
: Tax. Legal responses:q
(challenge, not believing the player has a Duke),p
.A
: Assassinate. Legal responses:s
(block as a Contessa),q
(challenge), and whichever of_
,'
,<
,=
,0
is in your hand.S
: Steal. Legal responses:a
(block as an Ambassador),c
(block as a Captain),q
(challenge, not believing the player has a Captain),p
.d
: block Foreign Aid as a Duke. Legal responses:\n
(accept the block),q
(challenge, not believing the player has a Duke).a
: block a Steal as an Ambassador. Legal responses:\n
(accept the block),q
(challenge, not believing the player has an Ambassador).c
: block a Steal as a Captain.\n
(accept the block),q
(challenge, not believing the player has a Captain).s
: block an Assassinate as a Contessa. Legal responses:\n
(accept the block),q
(challenge, not believing the player has a Contessa).p
: pass challenging an Exchange/Tax/Steal when it is not your turn. Not used withA
; to decline to challenge an Assassination write one of_'<=0
. Legal response:\n
(end your turn), and if you have just succeeded at an Exchange, writing the cards you wish to keep from the fourth command line argument to STDOUT.q
: challenge the most recent action or block. Legal response: if you have the card for the action that was challenged, whichever of~^*!$
it was. If you don't, then whichever of_'<=0
from your hand which you wish to give up, followed by a newline if and only if it is your turn.~
,^
,*
,!
,$
: reveal that you were telling the truth about holding, respectively, an Ambassador, an Assassin, a Captain, a Contessa, and a Duke (also used to represent these cards in command line arguments, and STDOUT output in an Exchange). Legal responses: whichever of_
,'
,<
,=
,0
you have in your hand._
,'
,<
,=
,0
: give up as punishment, respectively, an Ambassador, and Assassin, a Captain, a Contessa, and a Duke because you lost a challenge or were Assassinated/Couped. Legal response:\n
.\n
: end your turn, in doing so declining to challenge a block if applicable. Legal responses: any capital-letter action (assuming one has the coins for Assassinate/Coup and the opponent has the coins for Steal).
The format has the following useful properties:
- Turns begin with a capital letter.
- Lines follow the pattern: uppercase letter, lowercase letters, optionally punctuation marks or 0 for revealed cards, newline.
- A file ending with a newline, or an empty file, indicates that it is the start of your program's turn and it must choose a capital letter action.
- The legal actions you are allowed to take on an invocation are usually uniquely determined by the last character in the file. The exception is
q
, which will have some logic associated with it. See the functionget_legal_actions
in the arbiter to help understand this. Or you can just use the legal actions you're given on the command line. - An even number of characters on a line indicate that the turn is yours and your program is asked to choose an action, challenge a block, or end its turn.
- An odd number of characters on a line indicate that the turn is not yours and your program is asked to block, challenge, or reveal/surrender a card.
I will give an example for every action.
I\n
is the easiest to understand. A program takes one coin of Income, then ends its turn. This is one of the two cases where programs must print two characters, as Income is the only action where the opponent is both unaffected and cannot block or challenge.
Fp\n
means that one program took Foreign Aid, then its opponent declined to block (p
). On its next invocation, the first program noted that by the final lowercase p
and/or the even number of characters on this line it took this turn, which has not ended yet, so it knows to end its current turn by printing a newline.
C=\n
means that one program launched a Coup. Its opponent, knowing that it was called to react by the odd number of letters on the line, gave up a Contessa. Again, the first program knew that this was its incomplete turn on its next invocation by the even number of characters on the line, so it wrote a newline to end its turn.
Eq~<\n
would mean that one program attempted an Exchange (E
) and its opponent challenged (q
). The Exchanging program revealed that it truthfully had an Ambassador (~
) and the challenger gave up a Captain as punishment (<
). After the challenger exits, the Exchanging program is invoked again with a four-character string as its fourth command-line argument (or three characters, if it had only one card). It writes the characters representing the cards it wishes to keep to STDOUT and a newline to the file.
Tq'\n
means that one program attempted an untruthful Tax, was challenged, and gave up an Assassin. It illustrates the other case where two characters are written: if it is your turn and you are forced to give up a card — either from an opponent's correct challenge (as here) or from your incorrect challenge of a block — then you must write both the card you give up and a newline to end your turn.
Asq!'\n
would mean that Player B attempted to Assassinate player A (A
), but A claimed to have a Contessa to block it (s
). B did not believe A and challenged (q
). A revealed that they did, in fact, have a Contessa (!
). B gave up an Assassin as punishment, losing their coins, and ended their turn ('\n
), writing two characters as in that special case. (If A had decided not to block or challenge, it could have written =
, and then its opponent would have seen that the turn was over and written a newline. The line would then have read A=\n
, like the Coup example.)
Sq*0\n
means that one program attempts a Steal; the opponent challenges, not believing the thief has a Captain; and the original program reveals a Captain, so the challenge is unsuccessful and the challenger gives up a Duke as punishment. (Another option for its opponent would be to accept the Steal by writing p
. Its opponent would then detect the end of its turn and write \n
, resulting in a line of Sp\n
.)
The Arbiter
Programs will be invoked by this Python script. It conducts ten rounds, in which every competitor faces every other competitor while going both first and second. It tracks cards and coin counts and determines the loser by the first program to end a line with a punctuation mark twice. Programs that exit with a non-zero status, modify the file, write an illegal move to the file, or attempt an illegal Exchange will automatically forfeit. If each player takes more than 100 actions, including blocks and challenges, with no winner, then both programs lose. A winner is granted one point. The player whose program scores the most points wins.
I suggest you read the Arbiter's source code, especially the get_legal_actions
function. It may help you understand the specification and write your own programs.
import itertools
import os
import random
import subprocess
class Player:
def __init__(self, name, command):
self.name = name
self.command = command
self.score = 0
self.coins = 1
self.cards = ""
actions_dict = {
'E': '_', 'T': '0', 'A': "'", 'S': '<',
'd': '0', 'a': '_', 'c': '<', 's': '='
}
punishment_to_reveal = {'_': '~', "'": '^', '<': '*', '=': '!', '0': '$'}
reveal_to_punishment = {
punishment_to_reveal[k]: k for k in punishment_to_reveal
}
def get_legal_actions(history, player, opponent):
c = history[-1]
result = ""
# Our turn begins; choose an action.
if c == '\n':
if player.coins >= 10:
return ["C"]
ret = ['I\n'] + list("FET")
if player.coins >= 3:
ret.append("A")
if player.coins >= 7:
ret.append('C')
if opponent.coins > 0:
ret.append("S")
return ret
# Opponent attempted foreign aid; can pass or claim Duke to block.
elif c == 'F':
return list('dp')
# We have been Couped; must surrender a card.
elif c == 'C':
return player.cards
# We failed a challenge; must surrender a card and print a newline
# if it is our turn.
elif c in '~^*!$':
if history[-3] in 'acds':
return [card + '\n' for card in player.cards]
return player.cards
# Opponent attempted Exchange or Tax; can pass or challenge.
elif c == 'E' or c == 'T':
return list('pq')
# Opponent attempted an Assassination; can block, challenge, or give in.
elif c == 'A':
return list('sq') + player.cards
# Opponent attempted to Steal; can pass, block as Ambassador/Captain,
# or challenge.
elif c == 'S':
return list('acpq')
# Opponent blocked; can challenge or withdraw.
elif c in 'acds':
return list('q\n')
# Opponent passed on blocking Foreign Aid/Tax/Exchange or they gave up a
# card as punishment, must end turn.
elif c in "p_'<=0":
return ['\n']
# Opponent challenged us.
elif c == 'q':
challenged_action = history[-2]
# If we have the card they challenged us over, must reveal it.
necessary_card = actions_dict[challenged_action]
if necessary_card in player.cards:
return [punishment_to_reveal[necessary_card]]
# Otherwise, we can give up either of our cards, writing a newline
# if it is our turn.
if challenged_action in 'acds':
return list(player.cards)
else:
return [card + '\n' for card in player.cards]
else:
return None
deck = ['_', "'", '<', '=', '0'] * 3
random.shuffle(deck)
def determine_turn_effects(line, output, cards, current_player, opponent):
last_action = line[-2]
# Only operate if the opponent declined to challenge (p) or the
# program successfully challenged their block
if last_action in "p_'<=0":
primary_action = line[0]
# Foreign Aid
if primary_action == 'F':
print current_player.name, "received 2 coins of Foreign Aid"
current_player.coins += 2
# Tax
elif primary_action == 'T':
print current_player.name, "received 3 coins of Tax"
current_player.coins += 3
# Steal
elif primary_action == 'S':
stolen_coins = 1 if opponent.coins == 1 else 2
print current_player.name,\
"stole %d coins from %s" % (stolen_coins, opponent.name)
current_player.coins += stolen_coins
opponent.coins -= stolen_coins
# Exchange, store desired cards and replace undesired ones
elif primary_action == 'E':
print current_player.name, "tried to take %r" % output, "from", cards
legal_outputs = [''.join(p) for p in itertools.permutations(
cards, len(current_player.cards))]
if output not in legal_outputs:
print current_player.name, "forfeits by illegal exchange"
return opponent
current_player.cards = [
reveal_to_punishment[c] for c in output
]
undesired_cards = list(cards)
for c in output:
undesired_cards.remove(c)
for card in undesired_cards:
deck.append(reveal_to_punishment[card])
random.shuffle(deck)
# Coins are not returned from a successful Contessa block
elif last_action == 's':
print current_player.name, "lost 3 coins from a Contessa block"
current_player.coins -= 3
return None
def play_game(player1, player2, round_number, game_number):
outfilename = os.path.abspath(__file__)[:-len(__file__)] + '_'.join([
player1.name, player2.name, str(round_number), str(game_number)
]) + '.txt'
print outfilename
f = open(outfilename, 'w')
f.close()
players_list = [player1, player2]
player1.cards = [deck.pop(), deck.pop()]
player2.cards = [deck.pop(), deck.pop()]
current_player_index = 0
for i in range(200):
current_player = players_list[current_player_index]
opponent = players_list[(current_player_index+1) % 2]
legal_actions = []
original_contents = []
original_contents_joined = ""
with open(outfilename, 'r') as outfile:
original_contents = outfile.readlines()
original_contents_joined = ''.join(original_contents)
if len(original_contents) == 0:
legal_actions = ['I\n'] + list("FEST")
else:
legal_actions = get_legal_actions(
original_contents[-1], current_player, opponent)
if not legal_actions:
print "Error: file ended in invalid character"
return current_player
# Has the player completed an Exchange? Pass them new cards if so.
exchange_cards = ""
old_last_line = original_contents[-1] if len(original_contents) > 0 else '\n'
if old_last_line[-1] != '\n' and old_last_line[0] == 'E' and \
len(old_last_line) % 2 == 0 and old_last_line[-1] in "p_'<=0":
exchange_cards = punishment_to_reveal[deck.pop()] + \
punishment_to_reveal[deck.pop()]
cards = exchange_cards + ''.join(
punishment_to_reveal[card] for card in current_player.cards)
args = current_player.command + [
outfilename,
str(opponent.coins),
str(current_player.coins),
cards
] + legal_actions
print ' '.join(args)
output = ""
os.chdir(current_player.name)
try:
output = subprocess.check_output(args)
# Competitors that fail to execute must forfeit
except subprocess.CalledProcessError:
print current_player.name, "forfeits by non-zero exit status"
return opponent
finally:
os.chdir('..')
new_contents = []
new_contents_joined = ""
with open(outfilename, 'r') as outfile:
new_contents = outfile.readlines()
new_contents_joined = ''.join(new_contents)
if original_contents_joined != new_contents_joined[:-2] and \
original_contents_joined != new_contents_joined[:-1]:
print current_player.name, "forfeits by modifying the file"
print "old:", original_contents
print "new:", new_contents
return opponent
new_last_line = new_contents[-1]
the_move_made = ""
for action in legal_actions:
if new_last_line.endswith(action):
the_move_made = action
break
# Competitors that make an illegal move must forfeit
if not the_move_made:
print current_player.name, "forfeits with an illegal move,",\
"last line: %r" % new_last_line
print opponent.name, "wins!"
return opponent
print current_player.name, "played %r" % the_move_made
# Side effects of moves.
#
# Income, give the current player a coin.
if the_move_made == "I\n":
print current_player.name, "received 1 coin of income"
current_player.coins += 1
# The program surrendered a card on its turn; take it away.
elif len(the_move_made) == 2:
print current_player.name, "lost a card from being challenged"
current_player.cards.remove(the_move_made[0])
# Coins are not returned from a successful Contessa block
if new_last_line[-3] == '!':
print current_player.name, "lost 3 coins from a Contessa block"
current_player.coins -= 3
# The program surrendered a card when it was not its turn.
elif the_move_made in "_'<=0":
print current_player.name, "gave up a", the_move_made
current_player.cards.remove(the_move_made)
if new_last_line[0] == 'C':
opponent.coins -= 7
elif new_last_line[0] == 'A':
opponent.coins -= 3
# Did the program unsuccessfully challenge an Assassination
# (e.g. Aq^0\n)
# or get caught falsely blocking with a Contessa
# (e.g. Asq0\n)?
# If yes, it loses right away.
if new_last_line[0] == 'A' and new_last_line[1] in 'qs' and \
len(new_last_line) == 4:
print current_player.name, "lost both cards in the same turn."
print opponent.name, "wins!"
return opponent
elif the_move_made == 'S':
print current_player.name, "attempted Steal"
elif the_move_made == 'T':
print current_player.name, "attempted Tax"
elif the_move_made == 'A':
print current_player.name, "attempted Assassinate"
elif the_move_made == 'C':
print current_player.name, "launched a Coup"
elif the_move_made == 'F':
print current_player.name, "attempted Foreign Aid"
elif the_move_made == 'E':
print current_player.name, "attempted Exchange"
elif the_move_made == 'q':
print current_player.name, "challenged"
elif the_move_made == 'p':
print current_player.name, "passed"
elif the_move_made == 'a':
print current_player.name, "blocked with an Ambassador"
elif the_move_made == 'c':
print current_player.name, "blocked with a Captain"
elif the_move_made == 's':
print current_player.name, "blocked with a Contessa"
elif the_move_made == 'd':
print current_player.name, "blocked with a Duke"
# The program revealed a card from an opponent's unsuccessful challenge.
# Give it a new card.
# Special case: a program whose Exchange is unsuccessfully challenged
# may keep the Ambassador it revealed in the Exchange, so give a new
# card for a revealed Ambassador only if it was used to block a Steal.
elif the_move_made in '^*!$' or (the_move_made == '~' and
new_last_line[0] == 'S'):
p = reveal_to_punishment[the_move_made]
current_player.cards.remove(p)
current_player.cards.append(deck.pop())
deck.append(p)
random.shuffle(deck)
print current_player.name, "did have a", the_move_made
# The program ended its turn. We must examine the rest of the line to
# determine the side effects.
elif the_move_made == '\n':
potential_winner = determine_turn_effects(
new_last_line, output.strip(), cards, current_player,
opponent)
if potential_winner:
print potential_winner.name,\
"wins because their opponent made an illegal exchange!"
return potential_winner
# One player has lost all their cards. Victory for the opponent!
if current_player.cards == []:
print opponent.name, "wins by eliminating both opponent cards!"
return opponent
current_player_index += 1
current_player_index %= 2
return None
competitors = []
competitors.append(Player("Challenger", ["./challenger"]))
competitors.append(Player("Random", ["./random"]))
# ...More competitors here
for i in range(10):
print "-- Round", i
j = 0
for pairing in itertools.permutations(competitors, 2):
player1, player2 = pairing
print '--- Game', j, ':', player1.name, 'vs.', player2.name
winner = play_game(player1, player2, i, j)
if not winner:
j += 1
continue
winner.score += 1
player1.coins = 1
player1.cards = ""
player2.coins = 1
player2.cards = ""
deck = ['_', "'", '<', '=', '0'] * 3
random.shuffle(deck)
j += 1
competitors.sort(reverse=True, key=lambda player: player.score)
for player in competitors:
print '%5d %s' % (player.score, player.name)
Miscellaneous
One program can not have code specific for another program, and programs can not help each other. (You may have multiple programs, but they can't interact with each other in any way.)
If your program loses both its cards in the same turn, it need only write one. The Arbiter will detect that it has been eliminated.
It is possible and encouraged, but not required, for programs to examine the game's history in the file. By doing so they can determine what cards their opponent has claimed to have and catch them in a lie.
In the real game of Coup, you can challenge an action and then attempt to block it in the same turn. I could not make the specification work if I allowed that, so you may either challenge or block a given action, but not both.
My apologies to @PeterTaylor, who on the previous time I posted this suggested I post it to the sandbox and rework the protocol to pipe output back and forth in STDOUT/STDIN. I tried so, so hard to make that work, spending a full day on it (when I'd already spent a full day writing the original challenge). But Exchanges proved very complicated to implement that way, plus it would have increased the complexity of submissions by requiring them to keep track of their own coin count. So I have posted the challenge more or less as it was originally.
S
, program B blocks by writingc
, A declines to challenge by writing\n
. A successful challenge of a Steal would go: A writesS
, B challenges by writingq
, A concedes the challenge by writing e.g._\n
, You may take only one action per turn, including Exchange. The legal responses to Exchange are pass and challenge. \$\endgroup\$