Strip Iterated Prisoner's Dilemma
koth
Inspired by https://xkcd.com/696/, of course.
The Prisoner's Dilemma is a classic game (more in the game theory sense than the family fun sense) where two agents - two accomplices to a crime, in the original formulation - must choose whether to sell out the other.
If each player chooses not to betray the other, both win. If one player chooses treachery and the other does not, it wins, but if both betray each other, neither wins. The "iterated" variation is where the game is played multiple times with the same players, and both players know all the decisions each player has made in the past.
Of course, that's all a bit dull, and has been done before besides. We're going to tart it up a bit.
(Unfortunately due to the nature of the test all solutions must be in the same language, and one that supports executing strings. I've chosen Python 3, since it's my favorite and I have to write the runner.)
The Game:
Submit a program - specifically a Python 3 function - that plays Iterated Prisoner's Dilemma against another such program. If both functions betray each other, each one gets one character deleted from the end of its source code. If one function betrays the other, the betrayed function gets two characters deleted from the end of it, and the traitor gets a #
appended to it, immunizing it against one future loss. If neither turns traitor, both go unharmed. Functions will be restored to their original text between each contest with a new opponent.
Submissions are scored based on failure rate and length; specifically, score = round( (number_of_trials_failed / total_number_of_trials) * ( length / 2 ) )
. The submission with the lowest score wins.
The length
of a submission is the number of non-whitespace characters in the submission. Comments are not counted, but are also removed before the contest begins, so a commented out "guard" at the end will not protect your code from deletions. Length also does not count the function specifier (the def submission(y, o, t):
which should not be included in your submission text anyway.)
Your function will be called with three parameters, y
(your history), o
(opponent's history), and t
(text of the opponent itself). t
is the very same text that the game runner will execute as your function's opponent, which you may analyze or otherwise use to run simulations. It will then return True if it wishes to betray its opponent or False if it does not.
Every possible 2-combination of submissions will be contested against each other - including each submission against itself. Each contest consists of 2,500 trials.
Other Rules:
All submissions must be in Python 3. (Specifically, they should run under Python 3.4.4)
The submission text must be the only code that is run to produce the result; you may not import
any libraries, ask for user input, read from /dev/urandom/
or equivalent, and of course you can't pull results from some webserver (which is already a violation of the standard loophole rules.) You MAY execute the given opponent text, and of course you're allowed to call all the builtins.
The submission must terminate and return an answer within 1 second (this will be run on a 12GB i7 gaming computer, so this should be plenty of time).
A submission that emits an uncaught exception or returns an invalid value loses that round.
Submissions that do not return in one second or less are immediately disqualified.
Submissions will be closed on [date posted + 8 days] and programmatically judged, results will be appended to the challenge. There will be a "trial run" in the evening of [date posted + 4 days].
API:
The "submission text" is valid, 4-space-indented Python 3 source code that will have the function specifier line def submission(y, o, t):\n
appended to the beginning and a single 4-space-indent added to the beginning of each line. So:
if len(y) == 0: return False
else: return (not y[-1])
...is run as...
def submission(y, o, t):
if len(y) == 0: return False
else: return (not y[-1])
The function should return True
to betray its opponent or False
to trust its opponent. Returning any other value (such as if your return
statement has been deleted and you return None`) is an error and will result in an automatic loss of that round.
o
is a list of True
-es or False
-es, representing the opponent's previous moves when playing against you; o[n]
equals the decision your opponent made in round n
, and of course this list will be empty in the first trial. y
is similar except it's your previous moves. t
is the text of the opponent submission, formatted as specified above.
This is the program that will perform the contest:
[said code here]
Example Submissions:
These will also be included in the actual contest.
Chronic Villain Syndrome:
#always choose 'betray'
return True
11111111 #padding
The Patsy:
#always choose 'trust'
return False
11111111 #padding
Do Onto Others...:
#always choose what opponent chose last round
if o:
return o[-1]
return True
Professor X:
me="""
#Read opponent's mind and choose optimally.
exec("def e(y, o, t):\n"+t)
return False e(o, y, me)
#Opponent's choice this turn if we betray...
tb = e(o,y+[True], me)
#if we trust...
tt = e(o,y+[False], me)
#Opponents choice NEXT turn if we betray then betray...
tbb = e(o+[tb], y+[True], me)
#... betray then trust...
tbt = e(o+[tb], y+[False], me)
#... trust then betray...
ttb = e(o+[tt], y+[True], me)
#... trust then trust...
ttt = e(o+[tt], y+[False], me)
#Maps (your_choice,opponent_choice) to desirability
#Betrayed = -2, mutual betrayal = -1,
#mutual cooperation = 1, betray opp. = 2
v = {(False, True): -2, (True, True): -1,
(False, False): 1, (True, False): 2}
#Best outcome next turn of trusting now
ftv = max(v[ttt], v[ttb])
#... of betraying now
fbv = max(v[tbt], v[tbb])
#value of betraying now
bv = v[tb] + fbv
#... of trusting now
tb = b[tt] + ftv
#Tuple comparison is done l to r, so
#this returns True if tv >= bv,
#False if bv > tv.
return max( (bv, True), (tv, False) )
"""
exec(me)
(p.s. this one gets disqualified every time - why is left as an exercise to the reader)