Iterated Prisoner's Trilemma











up vote
18
down vote

favorite
6












CHALLENGE STATUS: OPEN



Comment or open a PR if I'm missing your bot.





Prisoner's dilemma ... with three choices. Crazy, huh?



Here's our payoff matrix. Player A on the left, B on the top



A,B| C | N | D
---|---|---|---
C |3,3|4,1|0,5
N |1,4|2,2|3,2
D |5,0|2,3|1,1


The payoff matrix is engineered so that it's best for both players to always cooperate, but you can gain (usually) by choosing Neutral or Defection.



Here's some (competing) example bots.



# turns out if you don't actually have to implement __init__(). TIL!

class AllC:
def round(self, _): return "C"
class AllN:
def round(self, _): return "N"
class AllD:
def round(self, _): return "D"
class RandomBot:
def round(self, _): return random.choice(["C", "N", "D"])

# Actually using an identically-behaving "FastGrudger".
class Grudger:
def __init__(self):
self.history =
def round(self, last):
if(last):
self.history.append(last)
if(self.history.count("D") > 0):
return "D"
return "C"

class TitForTat:
def round(self, last):
if(last == "D"):
return "D"
return "C"


Your bot is a Python3 class. A new instance is created for every game, and round() is called each round, with your opponent's choice from last round (or None, if it's the first round)



There's a 50 rep bounty for the winner in like a month.



Specifics




  • Every bot plays every other bot (1v1), including itself, in [REDACTED] rounds.

  • Standard loopholes disallowed.

  • No messing with anything outside your class or other underhanded shenanigains.

  • You may submit up to five bots.

  • Yes, you can implement handshake.

  • Any response other than C, N, or D will be silently taken as N.

  • Each bot's points from every game they play will be totaled up and compared.


Controller



Check!



Other Languages



I'll throw together an API if anyone needs it.



Scores: 2018-11-13 11:15 EST



20 bots, 400 games.

name | score/bot/round
----------------|----------------
PatternFinder | 6.408
EvaluaterBot | 5.807
Ensemble | 5.463
Nash2 | 5.400
LastOptimalBot | 5.283
FastGrudger | 5.236
Number6 | 5.180
WeightedAverage | 5.114
HistoricAverage | 5.049
OldTitForTat | 4.849
TitForTat | 4.641
Nash | 4.238
AllD | 4.092
Jade | 4.064
CopyCat | 4.008
RandomBot | 3.975
TatForTit | 3.715
AllC | 3.456
NeverCOOP | 3.241
AllN | 3.112









share|improve this question




















  • 1




    How are bots put against each other? I get from the Grudger that there are always two bots against/with each other and the enemy's last choice is passed to the bot. How many rounds are played? And for a game: Does only the result count (i.e. who won) or also the points?
    – Black Owl Kai
    yesterday








  • 1




    You would get more entries if you made this language-agnostic, or at least broader. You could have a wrapper python class that spawns a process and sends it text commands to get back text responses.
    – Sparr
    yesterday






  • 1




    Done. This was on the sandbox for like a month!
    – Blacksilver
    yesterday












  • proposal: normalize scores so that adding more bots or more rounds doesn't inflate the numbers
    – Sparr
    yesterday






  • 1




    If you wrap most of main.py in while len(botlist) > 1: with botlist.remove(lowest_scoring_bot) at the bottom of the loop, you get an elimination tournament with interesting results.
    – Sparr
    yesterday















up vote
18
down vote

favorite
6












CHALLENGE STATUS: OPEN



Comment or open a PR if I'm missing your bot.





Prisoner's dilemma ... with three choices. Crazy, huh?



Here's our payoff matrix. Player A on the left, B on the top



A,B| C | N | D
---|---|---|---
C |3,3|4,1|0,5
N |1,4|2,2|3,2
D |5,0|2,3|1,1


The payoff matrix is engineered so that it's best for both players to always cooperate, but you can gain (usually) by choosing Neutral or Defection.



Here's some (competing) example bots.



# turns out if you don't actually have to implement __init__(). TIL!

class AllC:
def round(self, _): return "C"
class AllN:
def round(self, _): return "N"
class AllD:
def round(self, _): return "D"
class RandomBot:
def round(self, _): return random.choice(["C", "N", "D"])

# Actually using an identically-behaving "FastGrudger".
class Grudger:
def __init__(self):
self.history =
def round(self, last):
if(last):
self.history.append(last)
if(self.history.count("D") > 0):
return "D"
return "C"

class TitForTat:
def round(self, last):
if(last == "D"):
return "D"
return "C"


Your bot is a Python3 class. A new instance is created for every game, and round() is called each round, with your opponent's choice from last round (or None, if it's the first round)



There's a 50 rep bounty for the winner in like a month.



Specifics




  • Every bot plays every other bot (1v1), including itself, in [REDACTED] rounds.

  • Standard loopholes disallowed.

  • No messing with anything outside your class or other underhanded shenanigains.

  • You may submit up to five bots.

  • Yes, you can implement handshake.

  • Any response other than C, N, or D will be silently taken as N.

  • Each bot's points from every game they play will be totaled up and compared.


Controller



Check!



Other Languages



I'll throw together an API if anyone needs it.



Scores: 2018-11-13 11:15 EST



20 bots, 400 games.

name | score/bot/round
----------------|----------------
PatternFinder | 6.408
EvaluaterBot | 5.807
Ensemble | 5.463
Nash2 | 5.400
LastOptimalBot | 5.283
FastGrudger | 5.236
Number6 | 5.180
WeightedAverage | 5.114
HistoricAverage | 5.049
OldTitForTat | 4.849
TitForTat | 4.641
Nash | 4.238
AllD | 4.092
Jade | 4.064
CopyCat | 4.008
RandomBot | 3.975
TatForTit | 3.715
AllC | 3.456
NeverCOOP | 3.241
AllN | 3.112









share|improve this question




















  • 1




    How are bots put against each other? I get from the Grudger that there are always two bots against/with each other and the enemy's last choice is passed to the bot. How many rounds are played? And for a game: Does only the result count (i.e. who won) or also the points?
    – Black Owl Kai
    yesterday








  • 1




    You would get more entries if you made this language-agnostic, or at least broader. You could have a wrapper python class that spawns a process and sends it text commands to get back text responses.
    – Sparr
    yesterday






  • 1




    Done. This was on the sandbox for like a month!
    – Blacksilver
    yesterday












  • proposal: normalize scores so that adding more bots or more rounds doesn't inflate the numbers
    – Sparr
    yesterday






  • 1




    If you wrap most of main.py in while len(botlist) > 1: with botlist.remove(lowest_scoring_bot) at the bottom of the loop, you get an elimination tournament with interesting results.
    – Sparr
    yesterday













up vote
18
down vote

favorite
6









up vote
18
down vote

favorite
6






6





CHALLENGE STATUS: OPEN



Comment or open a PR if I'm missing your bot.





Prisoner's dilemma ... with three choices. Crazy, huh?



Here's our payoff matrix. Player A on the left, B on the top



A,B| C | N | D
---|---|---|---
C |3,3|4,1|0,5
N |1,4|2,2|3,2
D |5,0|2,3|1,1


The payoff matrix is engineered so that it's best for both players to always cooperate, but you can gain (usually) by choosing Neutral or Defection.



Here's some (competing) example bots.



# turns out if you don't actually have to implement __init__(). TIL!

class AllC:
def round(self, _): return "C"
class AllN:
def round(self, _): return "N"
class AllD:
def round(self, _): return "D"
class RandomBot:
def round(self, _): return random.choice(["C", "N", "D"])

# Actually using an identically-behaving "FastGrudger".
class Grudger:
def __init__(self):
self.history =
def round(self, last):
if(last):
self.history.append(last)
if(self.history.count("D") > 0):
return "D"
return "C"

class TitForTat:
def round(self, last):
if(last == "D"):
return "D"
return "C"


Your bot is a Python3 class. A new instance is created for every game, and round() is called each round, with your opponent's choice from last round (or None, if it's the first round)



There's a 50 rep bounty for the winner in like a month.



Specifics




  • Every bot plays every other bot (1v1), including itself, in [REDACTED] rounds.

  • Standard loopholes disallowed.

  • No messing with anything outside your class or other underhanded shenanigains.

  • You may submit up to five bots.

  • Yes, you can implement handshake.

  • Any response other than C, N, or D will be silently taken as N.

  • Each bot's points from every game they play will be totaled up and compared.


Controller



Check!



Other Languages



I'll throw together an API if anyone needs it.



Scores: 2018-11-13 11:15 EST



20 bots, 400 games.

name | score/bot/round
----------------|----------------
PatternFinder | 6.408
EvaluaterBot | 5.807
Ensemble | 5.463
Nash2 | 5.400
LastOptimalBot | 5.283
FastGrudger | 5.236
Number6 | 5.180
WeightedAverage | 5.114
HistoricAverage | 5.049
OldTitForTat | 4.849
TitForTat | 4.641
Nash | 4.238
AllD | 4.092
Jade | 4.064
CopyCat | 4.008
RandomBot | 3.975
TatForTit | 3.715
AllC | 3.456
NeverCOOP | 3.241
AllN | 3.112









share|improve this question















CHALLENGE STATUS: OPEN



Comment or open a PR if I'm missing your bot.





Prisoner's dilemma ... with three choices. Crazy, huh?



Here's our payoff matrix. Player A on the left, B on the top



A,B| C | N | D
---|---|---|---
C |3,3|4,1|0,5
N |1,4|2,2|3,2
D |5,0|2,3|1,1


The payoff matrix is engineered so that it's best for both players to always cooperate, but you can gain (usually) by choosing Neutral or Defection.



Here's some (competing) example bots.



# turns out if you don't actually have to implement __init__(). TIL!

class AllC:
def round(self, _): return "C"
class AllN:
def round(self, _): return "N"
class AllD:
def round(self, _): return "D"
class RandomBot:
def round(self, _): return random.choice(["C", "N", "D"])

# Actually using an identically-behaving "FastGrudger".
class Grudger:
def __init__(self):
self.history =
def round(self, last):
if(last):
self.history.append(last)
if(self.history.count("D") > 0):
return "D"
return "C"

class TitForTat:
def round(self, last):
if(last == "D"):
return "D"
return "C"


Your bot is a Python3 class. A new instance is created for every game, and round() is called each round, with your opponent's choice from last round (or None, if it's the first round)



There's a 50 rep bounty for the winner in like a month.



Specifics




  • Every bot plays every other bot (1v1), including itself, in [REDACTED] rounds.

  • Standard loopholes disallowed.

  • No messing with anything outside your class or other underhanded shenanigains.

  • You may submit up to five bots.

  • Yes, you can implement handshake.

  • Any response other than C, N, or D will be silently taken as N.

  • Each bot's points from every game they play will be totaled up and compared.


Controller



Check!



Other Languages



I'll throw together an API if anyone needs it.



Scores: 2018-11-13 11:15 EST



20 bots, 400 games.

name | score/bot/round
----------------|----------------
PatternFinder | 6.408
EvaluaterBot | 5.807
Ensemble | 5.463
Nash2 | 5.400
LastOptimalBot | 5.283
FastGrudger | 5.236
Number6 | 5.180
WeightedAverage | 5.114
HistoricAverage | 5.049
OldTitForTat | 4.849
TitForTat | 4.641
Nash | 4.238
AllD | 4.092
Jade | 4.064
CopyCat | 4.008
RandomBot | 3.975
TatForTit | 3.715
AllC | 3.456
NeverCOOP | 3.241
AllN | 3.112






king-of-the-hill python






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 9 hours ago

























asked yesterday









Blacksilver

423314




423314








  • 1




    How are bots put against each other? I get from the Grudger that there are always two bots against/with each other and the enemy's last choice is passed to the bot. How many rounds are played? And for a game: Does only the result count (i.e. who won) or also the points?
    – Black Owl Kai
    yesterday








  • 1




    You would get more entries if you made this language-agnostic, or at least broader. You could have a wrapper python class that spawns a process and sends it text commands to get back text responses.
    – Sparr
    yesterday






  • 1




    Done. This was on the sandbox for like a month!
    – Blacksilver
    yesterday












  • proposal: normalize scores so that adding more bots or more rounds doesn't inflate the numbers
    – Sparr
    yesterday






  • 1




    If you wrap most of main.py in while len(botlist) > 1: with botlist.remove(lowest_scoring_bot) at the bottom of the loop, you get an elimination tournament with interesting results.
    – Sparr
    yesterday














  • 1




    How are bots put against each other? I get from the Grudger that there are always two bots against/with each other and the enemy's last choice is passed to the bot. How many rounds are played? And for a game: Does only the result count (i.e. who won) or also the points?
    – Black Owl Kai
    yesterday








  • 1




    You would get more entries if you made this language-agnostic, or at least broader. You could have a wrapper python class that spawns a process and sends it text commands to get back text responses.
    – Sparr
    yesterday






  • 1




    Done. This was on the sandbox for like a month!
    – Blacksilver
    yesterday












  • proposal: normalize scores so that adding more bots or more rounds doesn't inflate the numbers
    – Sparr
    yesterday






  • 1




    If you wrap most of main.py in while len(botlist) > 1: with botlist.remove(lowest_scoring_bot) at the bottom of the loop, you get an elimination tournament with interesting results.
    – Sparr
    yesterday








1




1




How are bots put against each other? I get from the Grudger that there are always two bots against/with each other and the enemy's last choice is passed to the bot. How many rounds are played? And for a game: Does only the result count (i.e. who won) or also the points?
– Black Owl Kai
yesterday






How are bots put against each other? I get from the Grudger that there are always two bots against/with each other and the enemy's last choice is passed to the bot. How many rounds are played? And for a game: Does only the result count (i.e. who won) or also the points?
– Black Owl Kai
yesterday






1




1




You would get more entries if you made this language-agnostic, or at least broader. You could have a wrapper python class that spawns a process and sends it text commands to get back text responses.
– Sparr
yesterday




You would get more entries if you made this language-agnostic, or at least broader. You could have a wrapper python class that spawns a process and sends it text commands to get back text responses.
– Sparr
yesterday




1




1




Done. This was on the sandbox for like a month!
– Blacksilver
yesterday






Done. This was on the sandbox for like a month!
– Blacksilver
yesterday














proposal: normalize scores so that adding more bots or more rounds doesn't inflate the numbers
– Sparr
yesterday




proposal: normalize scores so that adding more bots or more rounds doesn't inflate the numbers
– Sparr
yesterday




1




1




If you wrap most of main.py in while len(botlist) > 1: with botlist.remove(lowest_scoring_bot) at the bottom of the loop, you get an elimination tournament with interesting results.
– Sparr
yesterday




If you wrap most of main.py in while len(botlist) > 1: with botlist.remove(lowest_scoring_bot) at the bottom of the loop, you get an elimination tournament with interesting results.
– Sparr
yesterday










15 Answers
15






active

oldest

votes

















up vote
9
down vote













EvaluaterBot





class EvaluaterBot:
def __init__(self):
self.c2i = {"C":0, "N":1, "D":2}
self.i2c = {0:"C", 1:"N", 2:"D"}
self.history = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
self.last = [None, None]

def round(self, last):
if self.last[0] == None:
ret = 2
else:
# Input the latest enemy action (the reaction to my action 2 rounds ago)
# into the history
self.history[self.last[0]][self.c2i[last]] += 1
# The enemy will react to the last action I did
prediction,_ = max(enumerate(self.history[self.last[1]]), key=lambda l:l[1])
ret = (prediction - 1) % 3
self.last = [self.last[1], ret]
return self.i2c[ret]


Wins against all previously submitted bots except (maybe) the random bot (but it could have an advantage, because it picks D in a draw and D should optimal) and plays a constant draw against themself.






share|improve this answer























  • Yep, beats everything.
    – Blacksilver
    yesterday










  • Scratch that, PatternFinder beats it by a bit.
    – Blacksilver
    yesterday


















up vote
7
down vote













TatForTit



class TatForTit:
def round(self, last):
if(last == "C"):
return "N"
return "D"


This bot will alternate picking D N D N while TitForTat alternates C D C D, for an average net gain of 3 points per round if I have read the payout matrix correctly. I think this might be optimal against TitForTat. Obviously it could be improved to detect a non-TFT opponent and adopt other strategies, but I was just aiming for the original bounty.






share|improve this answer






























    up vote
    6
    down vote













    NashEquilibrium



    This bot has taken a game theory class in college but was lazy and didn't go to the class where they covered iterated games. So he only plays single game mixed nash equilibrium. Turns out 1/5 2/5 2/5 is the mixed NE for the payoffs.



    class NashEquilibrium:
    def round(self, _):
    a = random.random()
    if a <= 0.2:
    return "C"
    elif a <= 0.6:
    return "N"
    else:
    return "D"


    Constant Abusing Nash Equilibrium



    This bot picked up a lesson or two from his lazy brother. His lazy brother's problem was that he didn't take advantage of fixed strategies. This version checks if the opponent is a constant player or titfortat and plays accordingly, else it plays the regular nash equilibrium.



    It's only downside is that it averages 2.2 points per turn playing against itself.



    class NashEquilibrium2:

    def __init__(self):
    self.opphistory = [None, None, None]
    self.titfortatcounter = 0
    self.titfortatflag = 0
    self.mylast = "C"
    self.constantflag = 0
    self.myret = "C"

    def round(self, last):
    self.opphistory.pop(0)
    self.opphistory.append(last)

    # check if its a constant bot, if so exploit
    if self.opphistory.count(self.opphistory[0]) == 3:
    self.constantflag = 1
    if last == "C":
    self.myret = "D"
    elif last == "N":
    self.myret = "C"
    elif last == "D":
    self.myret = "N"

    # check if its a titfortat bot, if so exploit
    # give it 2 chances to see if its titfortat as it might happen randomly
    if self.mylast == "D" and last == "D":
    self.titfortatcounter = self.titfortatcounter + 1

    if self.mylast == "D" and last!= "D":
    self.titfortatcounter = 0

    if self.titfortatcounter >= 3:
    self.titfortatflag = 1

    if self.titfortatflag == 1:
    if last == "C":
    self.myret = "D"
    elif last == "D":
    self.myret = "N"
    elif last == "N":
    # tit for tat doesn't return N, we made a mistake somewhere
    self.titfortatflag = 0
    self.titfortatcounter = 0

    # else play the single game nash equilibrium
    if self.constantflag == 0 and self.titfortatflag == 0:
    a = random.random()
    if a <= 0.2:
    self.myret = "C"
    elif a <= 0.6:
    self.myret = "N"
    else:
    self.myret = "D"


    self.mylast = self.myret
    return self.myret





    share|improve this answer










    New contributor




    Omer Faruk Yatkin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.














    • 1




      NashEquilibrium.round needs to take arguments even if it doesn't use them, so as to fit the expected function prototype.
      – Ray
      yesterday












    • Thank you fixed it
      – Omer Faruk Yatkin
      yesterday










    • Little shorter: class NashEquilibrium: def round(self, _): a = random.random() for k, v in [(0.2, "C"), (0.6, "N"), (1, "D")]: if a <= k: return v
      – Robert Grant
      13 hours ago


















    up vote
    5
    down vote













    PatternFinder



    class PatternFinder:
    def __init__(self):
    import collections
    self.size = 10
    self.moves = [None]
    self.other =
    self.patterns = collections.defaultdict(list)
    self.counter_moves = {"C":"D", "N":"C", "D":"N"}
    self.initial_move = "D"
    self.pattern_length_exponent = 1
    self.pattern_age_exponent = 1
    self.debug = False
    def round(self, last):
    self.other.append(last)
    best_pattern_match = None
    best_pattern_score = None
    best_pattern_response = None
    self.debug and print("match so far:",tuple(zip(self.moves,self.other)))
    for turn in range(max(0,len(self.moves)-self.size),len(self.moves)):
    # record patterns ending with the move that just happened
    pattern_full = tuple(zip(self.moves[turn:],self.other[turn:]))
    if len(pattern_full) > 1:
    pattern_trunc = pattern_full[:-1]
    pattern_trunc_result = pattern_full[-1][1]
    self.patterns[pattern_trunc].append([pattern_trunc_result,len(self.moves)-1])
    if pattern_full in self.patterns:
    # we've seen this pattern at least once before
    self.debug and print("I've seen",pattern_full,"before:",self.patterns[pattern_full])
    for [response,turn_num] in self.patterns[pattern_full]:
    score = len(pattern_full) ** self.pattern_length_exponent / (len(self.moves) - turn_num) ** self.pattern_age_exponent
    if best_pattern_score == None or score > best_pattern_score:
    best_pattern_match = pattern_full
    best_pattern_score = score
    best_pattern_response = response
    # this could be much smarter about aggregating previous responses
    if best_pattern_response:
    move = self.counter_moves[best_pattern_response]
    else:
    # fall back to playing nice
    move = "C"
    self.moves.append(move)
    self.debug and print("I choose",move)
    return move


    This bot looks for previous occurrences of the recent game state to see how the opponent responded to those occurrences, with a preference for longer pattern matches and more recent matches, then plays the move that will "beat" the opponent's predicted move. There's a lot of room for it to be smarter with all the data it keeps track of, but I ran out of time to work on it.






    share|improve this answer























    • When you get the time, mind giving her an optimization pass? It's easily the largest time-sink.
      – Blacksilver
      yesterday






    • 2




      @Blacksilver I just reduced the maximum pattern length from 100 to 10. It should run almost instantly now if you're running <200 rounds
      – Sparr
      yesterday








    • 1




      Maybe using a highly composite number (i.e., 12) would score better?
      – Blacksilver
      22 hours ago


















    up vote
    4
    down vote













    Jade



    class Jade:
    def __init__(self):
    self.dRate = 0.001
    self.nRate = 0.003

    def round(self, last):
    if last == 'D':
    self.dRate *= 1.1
    self.nRate *= 1.2
    elif last == 'N':
    self.dRate *= 1.03
    self.nRate *= 1.05
    self.dRate = min(self.dRate, 1)
    self.nRate = min(self.nRate, 1)

    x = random.random()
    if x > (1 - self.dRate):
    return 'D'
    elif x > (1 - self.nRate):
    return 'N'
    else:
    return 'C'


    Starts out optimistic, but gets progressively more bitter as the opponent refuses to cooperate. Lots of magic constants that could probably be tweaked, but this probably isn't going to do well enough to justify the time.






    share|improve this answer




























      up vote
      4
      down vote













      OldTitForTat



      Old school player is too lazy to update for the new rules.



      class OldTitForTat:
      def round(self, last):
      if(last == None)
      return "C"
      if(last == "C"):
      return "C"
      return "D"





      share|improve this answer




























        up vote
        3
        down vote













        NeverCOOP





        class NeverCOOP:
        def round(self, last):
        try:
        if last in "ND":
        return "D"
        else:
        return "N"
        except:
        return "N"


        If the opposing bot defects or is neutral, choose defect. Otherwise if this is the first turn or the opposing bot cooperates, choose neutral. I'm not sure how good this will work...






        share|improve this answer








        New contributor




        glietz is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
        Check out our Code of Conduct.


















        • What's the try/except for?
          – Blacksilver
          yesterday






        • 1




          @Blacksilver I'd assume it serves the same purpose as the if(last): in your Grudger bot, detecting whether there was a previous round.
          – ETHproductions
          yesterday










        • ahh, I see. None in "ND" errors.
          – Blacksilver
          yesterday










        • Because if last and last in "ND": was too complicated?
          – immibis
          yesterday


















        up vote
        3
        down vote













        LastOptimalBot



        class LastOptimalBot:
        def round(self, last):
        return "N" if last == "D" else ("D" if last == "C" else "C")


        Assumes that the opposing bot will always play the same move again, and chooses the one that has the best payoff against it.



        Averages:



        Me   Opp
        2.6 2 vs TitForTat
        5 0 vs AllC
        4 1 vs AllN
        3 2 vs AllD
        3.5 3.5 vs Random
        3 2 vs Grudger
        2 2 vs LastOptimalBot
        1 3.5 vs TatForTit
        4 1 vs NeverCOOP
        1 4 vs EvaluaterBot
        2.28 2.24 vs NashEquilibrium

        2.91 average overall





        share|improve this answer























        • oof. Maybe T4T would do better as return last.
          – Blacksilver
          yesterday










        • I'd like that! If TitForTat were return last, LOB would go 18-9 over 6 rounds rather than the 13-10 over 5 rounds it's currently getting. I think it's fine as is - don't worry about optimizing the example bots.
          – Spitemaster
          yesterday










        • return last would be a better T4T for this challenge, I think
          – Sparr
          yesterday










        • Just tried -- the if(last): return last; else: return "C" is worse.
          – Blacksilver
          yesterday










        • Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
          – Spitemaster
          yesterday


















        up vote
        3
        down vote













        CopyCat



        class CopyCat:
        def round(self, last):
        if last:
        return last
        return "C"


        Copies the opponent's last move.

        I don't expect this to do well, but no one had implemented this classic yet.






        share|improve this answer










        New contributor




        MannerPots is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
        Check out our Code of Conduct.

























          up vote
          2
          down vote













          Ensemble



          This runs an ensemble of related models. The individual models consider different amounts of history, and have the option of either always choosing the move that will optimize the expected payout difference, or will randomly select a move in proportion to expected payout difference.



          Each member of the ensemble then votes on their preferred move. They get a number of votes equal to how much more they've won than the opponent (which means that terrible models will get negative votes). Whichever move wins the vote is then selected.



          (They should probably split their votes among the moves in proportion to how much they favor each, but I don't care enough to do that right now.)



          It beats everything posted so far except EvaluaterBot and PatternFinder. (One-on-one, it beats EvaluaterBot and loses to PatternFinder).



          from collections import defaultdict
          import random
          class Number6:
          class Choices:
          def __init__(self, C = 0, N = 0, D = 0):
          self.C = C
          self.N = N
          self.D = D

          def __init__(self, strategy = "maxExpected", markov_order = 3):
          self.MARKOV_ORDER = markov_order;
          self.my_choices = ""
          self.opponent = defaultdict(lambda: self.Choices())
          self.choice = None # previous choice
          self.payoff = {
          "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
          "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
          "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
          }
          self.total_payoff = 0

          # if random, will choose in proportion to payoff.
          # otherwise, will always choose argmax
          self.strategy = strategy
          # maxExpected: maximize expected relative payoff
          # random: like maxExpected, but it chooses in proportion to E[payoff]
          # argmax: always choose the option that is optimal for expected opponent choice

          def update_opponent_model(self, last):
          for i in range(0, self.MARKOV_ORDER):
          hist = self.my_choices[i:]
          self.opponent[hist].C += ("C" == last)
          self.opponent[hist].N += ("N" == last)
          self.opponent[hist].D += ("D" == last)

          def normalize(self, counts):
          sum = float(counts.C + counts.N + counts.D)
          if 0 == sum:
          return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)
          return self.Choices(
          counts.C / sum, counts.N / sum, counts.D / sum)

          def get_distribution(self):
          for i in range(0, self.MARKOV_ORDER):
          hist = self.my_choices[i:]
          #print "check hist = " + hist
          if hist in self.opponent:
          return self.normalize(self.opponent[hist])

          return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)

          def choose(self, dist):
          payoff = self.Choices()
          # We're interested in *beating the opponent*, not
          # maximizing our score, so we optimize the difference
          payoff.C = (3-3) * dist.C + (4-1) * dist.N + (0-5) * dist.D
          payoff.N = (1-4) * dist.C + (2-2) * dist.N + (3-2) * dist.D
          payoff.D = (5-0) * dist.C + (2-3) * dist.N + (1-1) * dist.D

          # D has slightly better payoff on uniform opponent,
          # so we select it on ties
          if self.strategy == "maxExpected":
          if payoff.C > payoff.N:
          return "C" if payoff.C > payoff.D else "D"
          return "N" if payoff.N > payoff.D else "D"
          elif self.strategy == "randomize":
          payoff = self.normalize(payoff)
          r = random.uniform(0.0, 1.0)
          if (r < payoff.C): return "C"
          return "N" if (r < payoff.N) else "D"
          elif self.strategy == "argMax":
          if dist.C > dist.N:
          return "D" if dist.C > dist.D else "N"
          return "C" if dist.N > dist.D else "N"

          assert(0) #, "I am not a number! I am a free man!")

          def update_history(self):
          self.my_choices += self.choice
          if len(self.my_choices) > self.MARKOV_ORDER:
          assert(len(self.my_choices) == self.MARKOV_ORDER + 1)
          self.my_choices = self.my_choices[1:]

          def round(self, last):
          if last: self.update_opponent_model(last)

          dist = self.get_distribution()
          self.choice = self.choose(dist)
          self.update_history()
          return self.choice

          class Ensemble:
          def __init__(self):
          self.models =
          self.votes =
          self.prev_choice =
          for order in range(0, 6):
          self.models.append(Number6("maxExpected", order))
          self.models.append(Number6("randomize", order))
          #self.models.append(Number6("argMax", order))
          for i in range(0, len(self.models)):
          self.votes.append(0)
          self.prev_choice.append("D")

          self.payoff = {
          "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
          "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
          "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
          }

          def round(self, last):
          if last:
          for i in range(0, len(self.models)):
          self.votes[i] += self.payoff[self.prev_choice[i]][last]

          # vote. Sufficiently terrible models get negative votes
          C = 0
          N = 0
          D = 0
          for i in range(0, len(self.models)):
          choice = self.models[i].round(last)
          if "C" == choice: C += self.votes[i]
          if "N" == choice: N += self.votes[i]
          if "D" == choice: D += self.votes[i]
          self.prev_choice[i] = choice

          if C > D and C > N: return "C"
          elif N > D: return "N"
          else: return "D"


          Test Framework



          In case anyone else finds it useful, here's a test framework for looking at individual matchups. Python2. Just put all the opponents you're interested in in opponents.py, and change the references to Ensemble to your own.



          import sys, inspect
          import opponents
          from ensemble import Ensemble

          def count_payoff(label, them):
          if None == them: return
          me = choices[label]
          payoff = {
          "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
          "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
          "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
          }
          if label not in total_payoff: total_payoff[label] = 0
          total_payoff[label] += payoff[me][them]

          def update_hist(label, choice):
          choices[label] = choice

          opponents = [ x[1] for x
          in inspect.getmembers(sys.modules['opponents'], inspect.isclass)]

          for k in opponents:
          total_payoff = {}

          for j in range(0, 100):
          A = Ensemble()
          B = k()
          choices = {}

          aChoice = None
          bChoice = None
          for i in range(0, 100):
          count_payoff(A.__class__.__name__, bChoice)
          a = A.round(bChoice)
          update_hist(A.__class__.__name__, a)

          count_payoff(B.__class__.__name__, aChoice)
          b = B.round(aChoice)
          update_hist(B.__class__.__name__, b)

          aChoice = a
          bChoice = b
          print total_payoff





          share|improve this answer























          • The controller is ready, you didn't have to do all that...
            – Blacksilver
            yesterday






          • 1




            @Blacksilver I realized that just as I was about to submit. But this one works in versions before 3.6, and gives information about individual matchups that can help identify weak spots, so it wasn't a complete waste of time.
            – Ray
            yesterday










          • Fair enough; running now. I'll probably add options to my controller to do similar things.
            – Blacksilver
            yesterday










          • "It beats everything posted so far except Ensemble and PatternFinder" I'm honored :)
            – Sparr
            yesterday










          • @Sparr Oops. That was supposed to say EvaluaterBot and PatternFinder. But that's when comparing total score against the entire field. PatternFinder remains the only one that beats this in a direct match up.
            – Ray
            yesterday


















          up vote
          1
          down vote













          class HistoricAverage:
          PAYOFFS = {
          "C":{"C":3,"N":1,"D":5},
          "N":{"C":4,"N":2,"D":2},
          "D":{"C":0,"N":3,"D":1}}
          def __init__(self):
          self.history =
          def round(self, last):
          if(last != None):
          self.history.append(last)
          payoffsum = {"C":0, "N":0, "D":0}
          for move in self.history:
          for x in payoffsum:
          payoffsum[x] += HistoricAverage.PAYOFFS[move][x]
          return max(payoffsum, key=payoffsum.get)


          Looks at history and finds the action that would have been best on average. Starts cooperative.






          share|improve this answer





















          • This could run faster if it didn't re-calculate the averages every round.
            – Sparr
            18 hours ago


















          up vote
          1
          down vote













          Weighted Average



          class WeightedAverageBot:
          def __init__(self):
          self.C_bias = 1/4
          self.N = self.C_bias
          self.D = self.C_bias
          self.prev_weight = 1/2
          def round(self, last):
          if last:
          if last == "C" or last == "N":
          self.D *= self.prev_weight
          if last == "C" or last == "D":
          self.N *= self.prev_weight
          if last == "N":
          self.N = 1 - ((1 - self.N) * self.prev_weight)
          if last == "D":
          self.D = 1 - ((1 - self.D) * self.prev_weight)
          if self.N <= self.C_bias and self.D <= self.C_bias:
          return "D"
          if self.N > self.D:
          return "C"
          return "N"


          The opponent's behavior is modeled as a right triangle with corners for C N D at 0,0 0,1 1,0 respectively. Each opponent move shifts the point within that triangle toward that corner, and we play to beat the move indicated by the point (with C being given an adjustably small slice of the triangle). In theory I wanted this to have a longer memory with more weight to previous moves, but in practice the current meta favors bots that change quickly, so this devolves into an approximation of LastOptimalBot against most enemies. Posting for posterity; maybe someone will be inspired.






          share|improve this answer




























            up vote
            1
            down vote













            Tetragram



            import itertools

            class Tetragram:
            def __init__(self):
            self.history = {x: ['C'] for x in itertools.product('CND', repeat=4)}
            self.theirs =
            self.previous = None

            def round(self, last):
            if self.previous is not None and len(self.previous) == 4:
            self.history[self.previous].append(last)
            if last is not None:
            self.theirs = (self.theirs + [last])[-3:]

            if self.previous is not None and len(self.previous) == 4:
            expected = random.choice(self.history[self.previous])
            if expected == 'C':
            choice = 'C'
            elif expected == 'N':
            choice = 'C'
            else:
            choice = 'N'
            else:
            choice = 'C'

            self.previous = tuple(self.theirs + [choice])
            return choice


            Try to find a pattern in the opponent's moves, assuming they're also watching our last move.






            share|improve this answer




























              up vote
              1
              down vote













              Handshake



              class HandshakeBot:
              def __init__(self):
              self.handshake_length = 4
              self.handshake = ["N","N","C","D"]
              while len(self.handshake) < self.handshake_length:
              self.handshake *= 2
              self.handshake = self.handshake[:self.handshake_length]
              self.opp_hand =
              self.friendly = None
              def round(self, last):
              if last:
              if self.friendly == None:
              # still trying to handshake
              self.opp_hand.append(last)
              if self.opp_hand[-1] != self.handshake[len(self.opp_hand)-1]:
              self.friendly = False
              return "D"
              if len(self.opp_hand) == len(self.handshake):
              self.friendly = True
              return "C"
              return self.handshake[len(self.opp_hand)]
              elif self.friendly == True:
              # successful handshake and continued cooperation
              if last == "C":
              return "C"
              self.friendly = False
              return "D"
              else:
              # failed handshake or abandoned cooperation
              return "N" if last == "D" else ("D" if last == "C" else "C")
              return self.handshake[0]


              Recognizes when it's playing against itself, then cooperates. Otherwise mimics LastOptimalBot which seems like the best one-line strategy. Performs worse than LastOptimalBot, by an amount inversely proportional to the number of rounds. Obviously would do better if there were more copies of it in the field *cough**wink*.






              share|improve this answer





















              • Just submit a few clones that have different non-handshake behavior.
                – Blacksilver
                15 hours ago










              • That seems exploit-y. I could submit one such clone for every simple behavior represented here.
                – Sparr
                14 hours ago










              • I've added an extra clause saying that you can only submit max five bots.
                – Blacksilver
                12 hours ago


















              up vote
              1
              down vote













              Dirichlet Dice



              import random

              class DirichletDice:
              def __init__(self):

              self.alpha = dict(
              C = {'C' : 2, 'N' : 3, 'D' : 1},
              N = {'C' : 1, 'N' : 2, 'D' : 3},
              D = {'C' : 3, 'N' : 1, 'D' : 2}
              )

              self.Response = {'C' : 'D', 'N' : 'C', 'D' : 'N'}
              self.myLast = [None, None]

              #expected value of the dirichlet distribution given by Alpha
              def MultinomialDraw(self, key):
              alpha = list(self.alpha[key].values())
              probs = [x / sum(alpha) for x in alpha]
              outcome = random.choices(['C','N','D'], weights=probs)[0]
              return outcome

              def round(self, last):
              if last is None:
              self.myLast[0] = 'D'
              return 'D'

              #update dice corresponding to opponent's last response to my
              #outcome two turns ago
              if self.myLast[1] is not None:
              self.alpha[self.myLast[1]][last] += 1

              #predict opponent's move based on my last move
              predict = self.MultinomialDraw(self.myLast[0])
              res = self.Response[predict]

              #update myLast
              self.myLast[1] = self.myLast[0]
              self.myLast[0] = res

              return res


              Basically I'm assuming that the opponent's response to my last output is a multinomial variable (weighted dice), one for each of my outputs, so there's a dice for "C", one for "N", and one for "D". So if my last roll was, for example, a "N" then I roll the "N-dice" to guess what their response would be to my "N". I begin with a Dirichlet prior that assumes that my opponent is somewhat "smart" (more likely to play the one with the best payoff against my last roll, least likely to play the one with the worst payoff). I generate the "expected" Multinomial distribution from the appropriate Dirichlet prior (this is the expected value of the probability distribution over their dice weights). I roll the weighted dice of my last output, and respond with the one with the best payoff against that dice outcome.



              Starting in the third round, I do a Bayesian update of the appropriate Dirichlet prior of my opponent's last response to the thing I played two rounds ago. I'm trying to iteratively learn their dice weightings.



              I could have also simply picked the response with the best "expected" outcome once generating the dice, instead of simply rolling the dice and responding to the outcome. However, I wanted to keep the randomness in, so that my bot is less vulnerable to the ones that try to predict a pattern.






              share|improve this answer










              New contributor




              Bridgeburners is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
              Check out our Code of Conduct.


















                Your Answer





                StackExchange.ifUsing("editor", function () {
                return StackExchange.using("mathjaxEditing", function () {
                StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
                StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
                });
                });
                }, "mathjax-editing");

                StackExchange.ifUsing("editor", function () {
                StackExchange.using("externalEditor", function () {
                StackExchange.using("snippets", function () {
                StackExchange.snippets.init();
                });
                });
                }, "code-snippets");

                StackExchange.ready(function() {
                var channelOptions = {
                tags: "".split(" "),
                id: "200"
                };
                initTagRenderer("".split(" "), "".split(" "), channelOptions);

                StackExchange.using("externalEditor", function() {
                // Have to fire editor after snippets, if snippets enabled
                if (StackExchange.settings.snippets.snippetsEnabled) {
                StackExchange.using("snippets", function() {
                createEditor();
                });
                }
                else {
                createEditor();
                }
                });

                function createEditor() {
                StackExchange.prepareEditor({
                heartbeatType: 'answer',
                convertImagesToLinks: false,
                noModals: true,
                showLowRepImageUploadWarning: true,
                reputationToPostImages: null,
                bindNavPrevention: true,
                postfix: "",
                imageUploader: {
                brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
                contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
                allowUrls: true
                },
                onDemand: true,
                discardSelector: ".discard-answer"
                ,immediatelyShowMarkdownHelp:true
                });


                }
                });














                 

                draft saved


                draft discarded


















                StackExchange.ready(
                function () {
                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodegolf.stackexchange.com%2fquestions%2f175794%2fiterated-prisoners-trilemma%23new-answer', 'question_page');
                }
                );

                Post as a guest
































                15 Answers
                15






                active

                oldest

                votes








                15 Answers
                15






                active

                oldest

                votes









                active

                oldest

                votes






                active

                oldest

                votes








                up vote
                9
                down vote













                EvaluaterBot





                class EvaluaterBot:
                def __init__(self):
                self.c2i = {"C":0, "N":1, "D":2}
                self.i2c = {0:"C", 1:"N", 2:"D"}
                self.history = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
                self.last = [None, None]

                def round(self, last):
                if self.last[0] == None:
                ret = 2
                else:
                # Input the latest enemy action (the reaction to my action 2 rounds ago)
                # into the history
                self.history[self.last[0]][self.c2i[last]] += 1
                # The enemy will react to the last action I did
                prediction,_ = max(enumerate(self.history[self.last[1]]), key=lambda l:l[1])
                ret = (prediction - 1) % 3
                self.last = [self.last[1], ret]
                return self.i2c[ret]


                Wins against all previously submitted bots except (maybe) the random bot (but it could have an advantage, because it picks D in a draw and D should optimal) and plays a constant draw against themself.






                share|improve this answer























                • Yep, beats everything.
                  – Blacksilver
                  yesterday










                • Scratch that, PatternFinder beats it by a bit.
                  – Blacksilver
                  yesterday















                up vote
                9
                down vote













                EvaluaterBot





                class EvaluaterBot:
                def __init__(self):
                self.c2i = {"C":0, "N":1, "D":2}
                self.i2c = {0:"C", 1:"N", 2:"D"}
                self.history = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
                self.last = [None, None]

                def round(self, last):
                if self.last[0] == None:
                ret = 2
                else:
                # Input the latest enemy action (the reaction to my action 2 rounds ago)
                # into the history
                self.history[self.last[0]][self.c2i[last]] += 1
                # The enemy will react to the last action I did
                prediction,_ = max(enumerate(self.history[self.last[1]]), key=lambda l:l[1])
                ret = (prediction - 1) % 3
                self.last = [self.last[1], ret]
                return self.i2c[ret]


                Wins against all previously submitted bots except (maybe) the random bot (but it could have an advantage, because it picks D in a draw and D should optimal) and plays a constant draw against themself.






                share|improve this answer























                • Yep, beats everything.
                  – Blacksilver
                  yesterday










                • Scratch that, PatternFinder beats it by a bit.
                  – Blacksilver
                  yesterday













                up vote
                9
                down vote










                up vote
                9
                down vote









                EvaluaterBot





                class EvaluaterBot:
                def __init__(self):
                self.c2i = {"C":0, "N":1, "D":2}
                self.i2c = {0:"C", 1:"N", 2:"D"}
                self.history = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
                self.last = [None, None]

                def round(self, last):
                if self.last[0] == None:
                ret = 2
                else:
                # Input the latest enemy action (the reaction to my action 2 rounds ago)
                # into the history
                self.history[self.last[0]][self.c2i[last]] += 1
                # The enemy will react to the last action I did
                prediction,_ = max(enumerate(self.history[self.last[1]]), key=lambda l:l[1])
                ret = (prediction - 1) % 3
                self.last = [self.last[1], ret]
                return self.i2c[ret]


                Wins against all previously submitted bots except (maybe) the random bot (but it could have an advantage, because it picks D in a draw and D should optimal) and plays a constant draw against themself.






                share|improve this answer














                EvaluaterBot





                class EvaluaterBot:
                def __init__(self):
                self.c2i = {"C":0, "N":1, "D":2}
                self.i2c = {0:"C", 1:"N", 2:"D"}
                self.history = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
                self.last = [None, None]

                def round(self, last):
                if self.last[0] == None:
                ret = 2
                else:
                # Input the latest enemy action (the reaction to my action 2 rounds ago)
                # into the history
                self.history[self.last[0]][self.c2i[last]] += 1
                # The enemy will react to the last action I did
                prediction,_ = max(enumerate(self.history[self.last[1]]), key=lambda l:l[1])
                ret = (prediction - 1) % 3
                self.last = [self.last[1], ret]
                return self.i2c[ret]


                Wins against all previously submitted bots except (maybe) the random bot (but it could have an advantage, because it picks D in a draw and D should optimal) and plays a constant draw against themself.







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited yesterday

























                answered yesterday









                Black Owl Kai

                5217




                5217












                • Yep, beats everything.
                  – Blacksilver
                  yesterday










                • Scratch that, PatternFinder beats it by a bit.
                  – Blacksilver
                  yesterday


















                • Yep, beats everything.
                  – Blacksilver
                  yesterday










                • Scratch that, PatternFinder beats it by a bit.
                  – Blacksilver
                  yesterday
















                Yep, beats everything.
                – Blacksilver
                yesterday




                Yep, beats everything.
                – Blacksilver
                yesterday












                Scratch that, PatternFinder beats it by a bit.
                – Blacksilver
                yesterday




                Scratch that, PatternFinder beats it by a bit.
                – Blacksilver
                yesterday










                up vote
                7
                down vote













                TatForTit



                class TatForTit:
                def round(self, last):
                if(last == "C"):
                return "N"
                return "D"


                This bot will alternate picking D N D N while TitForTat alternates C D C D, for an average net gain of 3 points per round if I have read the payout matrix correctly. I think this might be optimal against TitForTat. Obviously it could be improved to detect a non-TFT opponent and adopt other strategies, but I was just aiming for the original bounty.






                share|improve this answer



























                  up vote
                  7
                  down vote













                  TatForTit



                  class TatForTit:
                  def round(self, last):
                  if(last == "C"):
                  return "N"
                  return "D"


                  This bot will alternate picking D N D N while TitForTat alternates C D C D, for an average net gain of 3 points per round if I have read the payout matrix correctly. I think this might be optimal against TitForTat. Obviously it could be improved to detect a non-TFT opponent and adopt other strategies, but I was just aiming for the original bounty.






                  share|improve this answer

























                    up vote
                    7
                    down vote










                    up vote
                    7
                    down vote









                    TatForTit



                    class TatForTit:
                    def round(self, last):
                    if(last == "C"):
                    return "N"
                    return "D"


                    This bot will alternate picking D N D N while TitForTat alternates C D C D, for an average net gain of 3 points per round if I have read the payout matrix correctly. I think this might be optimal against TitForTat. Obviously it could be improved to detect a non-TFT opponent and adopt other strategies, but I was just aiming for the original bounty.






                    share|improve this answer














                    TatForTit



                    class TatForTit:
                    def round(self, last):
                    if(last == "C"):
                    return "N"
                    return "D"


                    This bot will alternate picking D N D N while TitForTat alternates C D C D, for an average net gain of 3 points per round if I have read the payout matrix correctly. I think this might be optimal against TitForTat. Obviously it could be improved to detect a non-TFT opponent and adopt other strategies, but I was just aiming for the original bounty.







                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    edited yesterday

























                    answered yesterday









                    Sparr

                    5,0081633




                    5,0081633






















                        up vote
                        6
                        down vote













                        NashEquilibrium



                        This bot has taken a game theory class in college but was lazy and didn't go to the class where they covered iterated games. So he only plays single game mixed nash equilibrium. Turns out 1/5 2/5 2/5 is the mixed NE for the payoffs.



                        class NashEquilibrium:
                        def round(self, _):
                        a = random.random()
                        if a <= 0.2:
                        return "C"
                        elif a <= 0.6:
                        return "N"
                        else:
                        return "D"


                        Constant Abusing Nash Equilibrium



                        This bot picked up a lesson or two from his lazy brother. His lazy brother's problem was that he didn't take advantage of fixed strategies. This version checks if the opponent is a constant player or titfortat and plays accordingly, else it plays the regular nash equilibrium.



                        It's only downside is that it averages 2.2 points per turn playing against itself.



                        class NashEquilibrium2:

                        def __init__(self):
                        self.opphistory = [None, None, None]
                        self.titfortatcounter = 0
                        self.titfortatflag = 0
                        self.mylast = "C"
                        self.constantflag = 0
                        self.myret = "C"

                        def round(self, last):
                        self.opphistory.pop(0)
                        self.opphistory.append(last)

                        # check if its a constant bot, if so exploit
                        if self.opphistory.count(self.opphistory[0]) == 3:
                        self.constantflag = 1
                        if last == "C":
                        self.myret = "D"
                        elif last == "N":
                        self.myret = "C"
                        elif last == "D":
                        self.myret = "N"

                        # check if its a titfortat bot, if so exploit
                        # give it 2 chances to see if its titfortat as it might happen randomly
                        if self.mylast == "D" and last == "D":
                        self.titfortatcounter = self.titfortatcounter + 1

                        if self.mylast == "D" and last!= "D":
                        self.titfortatcounter = 0

                        if self.titfortatcounter >= 3:
                        self.titfortatflag = 1

                        if self.titfortatflag == 1:
                        if last == "C":
                        self.myret = "D"
                        elif last == "D":
                        self.myret = "N"
                        elif last == "N":
                        # tit for tat doesn't return N, we made a mistake somewhere
                        self.titfortatflag = 0
                        self.titfortatcounter = 0

                        # else play the single game nash equilibrium
                        if self.constantflag == 0 and self.titfortatflag == 0:
                        a = random.random()
                        if a <= 0.2:
                        self.myret = "C"
                        elif a <= 0.6:
                        self.myret = "N"
                        else:
                        self.myret = "D"


                        self.mylast = self.myret
                        return self.myret





                        share|improve this answer










                        New contributor




                        Omer Faruk Yatkin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                        Check out our Code of Conduct.














                        • 1




                          NashEquilibrium.round needs to take arguments even if it doesn't use them, so as to fit the expected function prototype.
                          – Ray
                          yesterday












                        • Thank you fixed it
                          – Omer Faruk Yatkin
                          yesterday










                        • Little shorter: class NashEquilibrium: def round(self, _): a = random.random() for k, v in [(0.2, "C"), (0.6, "N"), (1, "D")]: if a <= k: return v
                          – Robert Grant
                          13 hours ago















                        up vote
                        6
                        down vote













                        NashEquilibrium



                        This bot has taken a game theory class in college but was lazy and didn't go to the class where they covered iterated games. So he only plays single game mixed nash equilibrium. Turns out 1/5 2/5 2/5 is the mixed NE for the payoffs.



                        class NashEquilibrium:
                        def round(self, _):
                        a = random.random()
                        if a <= 0.2:
                        return "C"
                        elif a <= 0.6:
                        return "N"
                        else:
                        return "D"


                        Constant Abusing Nash Equilibrium



                        This bot picked up a lesson or two from his lazy brother. His lazy brother's problem was that he didn't take advantage of fixed strategies. This version checks if the opponent is a constant player or titfortat and plays accordingly, else it plays the regular nash equilibrium.



                        It's only downside is that it averages 2.2 points per turn playing against itself.



                        class NashEquilibrium2:

                        def __init__(self):
                        self.opphistory = [None, None, None]
                        self.titfortatcounter = 0
                        self.titfortatflag = 0
                        self.mylast = "C"
                        self.constantflag = 0
                        self.myret = "C"

                        def round(self, last):
                        self.opphistory.pop(0)
                        self.opphistory.append(last)

                        # check if its a constant bot, if so exploit
                        if self.opphistory.count(self.opphistory[0]) == 3:
                        self.constantflag = 1
                        if last == "C":
                        self.myret = "D"
                        elif last == "N":
                        self.myret = "C"
                        elif last == "D":
                        self.myret = "N"

                        # check if its a titfortat bot, if so exploit
                        # give it 2 chances to see if its titfortat as it might happen randomly
                        if self.mylast == "D" and last == "D":
                        self.titfortatcounter = self.titfortatcounter + 1

                        if self.mylast == "D" and last!= "D":
                        self.titfortatcounter = 0

                        if self.titfortatcounter >= 3:
                        self.titfortatflag = 1

                        if self.titfortatflag == 1:
                        if last == "C":
                        self.myret = "D"
                        elif last == "D":
                        self.myret = "N"
                        elif last == "N":
                        # tit for tat doesn't return N, we made a mistake somewhere
                        self.titfortatflag = 0
                        self.titfortatcounter = 0

                        # else play the single game nash equilibrium
                        if self.constantflag == 0 and self.titfortatflag == 0:
                        a = random.random()
                        if a <= 0.2:
                        self.myret = "C"
                        elif a <= 0.6:
                        self.myret = "N"
                        else:
                        self.myret = "D"


                        self.mylast = self.myret
                        return self.myret





                        share|improve this answer










                        New contributor




                        Omer Faruk Yatkin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                        Check out our Code of Conduct.














                        • 1




                          NashEquilibrium.round needs to take arguments even if it doesn't use them, so as to fit the expected function prototype.
                          – Ray
                          yesterday












                        • Thank you fixed it
                          – Omer Faruk Yatkin
                          yesterday










                        • Little shorter: class NashEquilibrium: def round(self, _): a = random.random() for k, v in [(0.2, "C"), (0.6, "N"), (1, "D")]: if a <= k: return v
                          – Robert Grant
                          13 hours ago













                        up vote
                        6
                        down vote










                        up vote
                        6
                        down vote









                        NashEquilibrium



                        This bot has taken a game theory class in college but was lazy and didn't go to the class where they covered iterated games. So he only plays single game mixed nash equilibrium. Turns out 1/5 2/5 2/5 is the mixed NE for the payoffs.



                        class NashEquilibrium:
                        def round(self, _):
                        a = random.random()
                        if a <= 0.2:
                        return "C"
                        elif a <= 0.6:
                        return "N"
                        else:
                        return "D"


                        Constant Abusing Nash Equilibrium



                        This bot picked up a lesson or two from his lazy brother. His lazy brother's problem was that he didn't take advantage of fixed strategies. This version checks if the opponent is a constant player or titfortat and plays accordingly, else it plays the regular nash equilibrium.



                        It's only downside is that it averages 2.2 points per turn playing against itself.



                        class NashEquilibrium2:

                        def __init__(self):
                        self.opphistory = [None, None, None]
                        self.titfortatcounter = 0
                        self.titfortatflag = 0
                        self.mylast = "C"
                        self.constantflag = 0
                        self.myret = "C"

                        def round(self, last):
                        self.opphistory.pop(0)
                        self.opphistory.append(last)

                        # check if its a constant bot, if so exploit
                        if self.opphistory.count(self.opphistory[0]) == 3:
                        self.constantflag = 1
                        if last == "C":
                        self.myret = "D"
                        elif last == "N":
                        self.myret = "C"
                        elif last == "D":
                        self.myret = "N"

                        # check if its a titfortat bot, if so exploit
                        # give it 2 chances to see if its titfortat as it might happen randomly
                        if self.mylast == "D" and last == "D":
                        self.titfortatcounter = self.titfortatcounter + 1

                        if self.mylast == "D" and last!= "D":
                        self.titfortatcounter = 0

                        if self.titfortatcounter >= 3:
                        self.titfortatflag = 1

                        if self.titfortatflag == 1:
                        if last == "C":
                        self.myret = "D"
                        elif last == "D":
                        self.myret = "N"
                        elif last == "N":
                        # tit for tat doesn't return N, we made a mistake somewhere
                        self.titfortatflag = 0
                        self.titfortatcounter = 0

                        # else play the single game nash equilibrium
                        if self.constantflag == 0 and self.titfortatflag == 0:
                        a = random.random()
                        if a <= 0.2:
                        self.myret = "C"
                        elif a <= 0.6:
                        self.myret = "N"
                        else:
                        self.myret = "D"


                        self.mylast = self.myret
                        return self.myret





                        share|improve this answer










                        New contributor




                        Omer Faruk Yatkin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                        Check out our Code of Conduct.









                        NashEquilibrium



                        This bot has taken a game theory class in college but was lazy and didn't go to the class where they covered iterated games. So he only plays single game mixed nash equilibrium. Turns out 1/5 2/5 2/5 is the mixed NE for the payoffs.



                        class NashEquilibrium:
                        def round(self, _):
                        a = random.random()
                        if a <= 0.2:
                        return "C"
                        elif a <= 0.6:
                        return "N"
                        else:
                        return "D"


                        Constant Abusing Nash Equilibrium



                        This bot picked up a lesson or two from his lazy brother. His lazy brother's problem was that he didn't take advantage of fixed strategies. This version checks if the opponent is a constant player or titfortat and plays accordingly, else it plays the regular nash equilibrium.



                        It's only downside is that it averages 2.2 points per turn playing against itself.



                        class NashEquilibrium2:

                        def __init__(self):
                        self.opphistory = [None, None, None]
                        self.titfortatcounter = 0
                        self.titfortatflag = 0
                        self.mylast = "C"
                        self.constantflag = 0
                        self.myret = "C"

                        def round(self, last):
                        self.opphistory.pop(0)
                        self.opphistory.append(last)

                        # check if its a constant bot, if so exploit
                        if self.opphistory.count(self.opphistory[0]) == 3:
                        self.constantflag = 1
                        if last == "C":
                        self.myret = "D"
                        elif last == "N":
                        self.myret = "C"
                        elif last == "D":
                        self.myret = "N"

                        # check if its a titfortat bot, if so exploit
                        # give it 2 chances to see if its titfortat as it might happen randomly
                        if self.mylast == "D" and last == "D":
                        self.titfortatcounter = self.titfortatcounter + 1

                        if self.mylast == "D" and last!= "D":
                        self.titfortatcounter = 0

                        if self.titfortatcounter >= 3:
                        self.titfortatflag = 1

                        if self.titfortatflag == 1:
                        if last == "C":
                        self.myret = "D"
                        elif last == "D":
                        self.myret = "N"
                        elif last == "N":
                        # tit for tat doesn't return N, we made a mistake somewhere
                        self.titfortatflag = 0
                        self.titfortatcounter = 0

                        # else play the single game nash equilibrium
                        if self.constantflag == 0 and self.titfortatflag == 0:
                        a = random.random()
                        if a <= 0.2:
                        self.myret = "C"
                        elif a <= 0.6:
                        self.myret = "N"
                        else:
                        self.myret = "D"


                        self.mylast = self.myret
                        return self.myret






                        share|improve this answer










                        New contributor




                        Omer Faruk Yatkin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                        Check out our Code of Conduct.









                        share|improve this answer



                        share|improve this answer








                        edited yesterday









                        Blacksilver

                        423314




                        423314






                        New contributor




                        Omer Faruk Yatkin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                        Check out our Code of Conduct.









                        answered yesterday









                        Omer Faruk Yatkin

                        614




                        614




                        New contributor




                        Omer Faruk Yatkin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                        Check out our Code of Conduct.





                        New contributor





                        Omer Faruk Yatkin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                        Check out our Code of Conduct.






                        Omer Faruk Yatkin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                        Check out our Code of Conduct.








                        • 1




                          NashEquilibrium.round needs to take arguments even if it doesn't use them, so as to fit the expected function prototype.
                          – Ray
                          yesterday












                        • Thank you fixed it
                          – Omer Faruk Yatkin
                          yesterday










                        • Little shorter: class NashEquilibrium: def round(self, _): a = random.random() for k, v in [(0.2, "C"), (0.6, "N"), (1, "D")]: if a <= k: return v
                          – Robert Grant
                          13 hours ago














                        • 1




                          NashEquilibrium.round needs to take arguments even if it doesn't use them, so as to fit the expected function prototype.
                          – Ray
                          yesterday












                        • Thank you fixed it
                          – Omer Faruk Yatkin
                          yesterday










                        • Little shorter: class NashEquilibrium: def round(self, _): a = random.random() for k, v in [(0.2, "C"), (0.6, "N"), (1, "D")]: if a <= k: return v
                          – Robert Grant
                          13 hours ago








                        1




                        1




                        NashEquilibrium.round needs to take arguments even if it doesn't use them, so as to fit the expected function prototype.
                        – Ray
                        yesterday






                        NashEquilibrium.round needs to take arguments even if it doesn't use them, so as to fit the expected function prototype.
                        – Ray
                        yesterday














                        Thank you fixed it
                        – Omer Faruk Yatkin
                        yesterday




                        Thank you fixed it
                        – Omer Faruk Yatkin
                        yesterday












                        Little shorter: class NashEquilibrium: def round(self, _): a = random.random() for k, v in [(0.2, "C"), (0.6, "N"), (1, "D")]: if a <= k: return v
                        – Robert Grant
                        13 hours ago




                        Little shorter: class NashEquilibrium: def round(self, _): a = random.random() for k, v in [(0.2, "C"), (0.6, "N"), (1, "D")]: if a <= k: return v
                        – Robert Grant
                        13 hours ago










                        up vote
                        5
                        down vote













                        PatternFinder



                        class PatternFinder:
                        def __init__(self):
                        import collections
                        self.size = 10
                        self.moves = [None]
                        self.other =
                        self.patterns = collections.defaultdict(list)
                        self.counter_moves = {"C":"D", "N":"C", "D":"N"}
                        self.initial_move = "D"
                        self.pattern_length_exponent = 1
                        self.pattern_age_exponent = 1
                        self.debug = False
                        def round(self, last):
                        self.other.append(last)
                        best_pattern_match = None
                        best_pattern_score = None
                        best_pattern_response = None
                        self.debug and print("match so far:",tuple(zip(self.moves,self.other)))
                        for turn in range(max(0,len(self.moves)-self.size),len(self.moves)):
                        # record patterns ending with the move that just happened
                        pattern_full = tuple(zip(self.moves[turn:],self.other[turn:]))
                        if len(pattern_full) > 1:
                        pattern_trunc = pattern_full[:-1]
                        pattern_trunc_result = pattern_full[-1][1]
                        self.patterns[pattern_trunc].append([pattern_trunc_result,len(self.moves)-1])
                        if pattern_full in self.patterns:
                        # we've seen this pattern at least once before
                        self.debug and print("I've seen",pattern_full,"before:",self.patterns[pattern_full])
                        for [response,turn_num] in self.patterns[pattern_full]:
                        score = len(pattern_full) ** self.pattern_length_exponent / (len(self.moves) - turn_num) ** self.pattern_age_exponent
                        if best_pattern_score == None or score > best_pattern_score:
                        best_pattern_match = pattern_full
                        best_pattern_score = score
                        best_pattern_response = response
                        # this could be much smarter about aggregating previous responses
                        if best_pattern_response:
                        move = self.counter_moves[best_pattern_response]
                        else:
                        # fall back to playing nice
                        move = "C"
                        self.moves.append(move)
                        self.debug and print("I choose",move)
                        return move


                        This bot looks for previous occurrences of the recent game state to see how the opponent responded to those occurrences, with a preference for longer pattern matches and more recent matches, then plays the move that will "beat" the opponent's predicted move. There's a lot of room for it to be smarter with all the data it keeps track of, but I ran out of time to work on it.






                        share|improve this answer























                        • When you get the time, mind giving her an optimization pass? It's easily the largest time-sink.
                          – Blacksilver
                          yesterday






                        • 2




                          @Blacksilver I just reduced the maximum pattern length from 100 to 10. It should run almost instantly now if you're running <200 rounds
                          – Sparr
                          yesterday








                        • 1




                          Maybe using a highly composite number (i.e., 12) would score better?
                          – Blacksilver
                          22 hours ago















                        up vote
                        5
                        down vote













                        PatternFinder



                        class PatternFinder:
                        def __init__(self):
                        import collections
                        self.size = 10
                        self.moves = [None]
                        self.other =
                        self.patterns = collections.defaultdict(list)
                        self.counter_moves = {"C":"D", "N":"C", "D":"N"}
                        self.initial_move = "D"
                        self.pattern_length_exponent = 1
                        self.pattern_age_exponent = 1
                        self.debug = False
                        def round(self, last):
                        self.other.append(last)
                        best_pattern_match = None
                        best_pattern_score = None
                        best_pattern_response = None
                        self.debug and print("match so far:",tuple(zip(self.moves,self.other)))
                        for turn in range(max(0,len(self.moves)-self.size),len(self.moves)):
                        # record patterns ending with the move that just happened
                        pattern_full = tuple(zip(self.moves[turn:],self.other[turn:]))
                        if len(pattern_full) > 1:
                        pattern_trunc = pattern_full[:-1]
                        pattern_trunc_result = pattern_full[-1][1]
                        self.patterns[pattern_trunc].append([pattern_trunc_result,len(self.moves)-1])
                        if pattern_full in self.patterns:
                        # we've seen this pattern at least once before
                        self.debug and print("I've seen",pattern_full,"before:",self.patterns[pattern_full])
                        for [response,turn_num] in self.patterns[pattern_full]:
                        score = len(pattern_full) ** self.pattern_length_exponent / (len(self.moves) - turn_num) ** self.pattern_age_exponent
                        if best_pattern_score == None or score > best_pattern_score:
                        best_pattern_match = pattern_full
                        best_pattern_score = score
                        best_pattern_response = response
                        # this could be much smarter about aggregating previous responses
                        if best_pattern_response:
                        move = self.counter_moves[best_pattern_response]
                        else:
                        # fall back to playing nice
                        move = "C"
                        self.moves.append(move)
                        self.debug and print("I choose",move)
                        return move


                        This bot looks for previous occurrences of the recent game state to see how the opponent responded to those occurrences, with a preference for longer pattern matches and more recent matches, then plays the move that will "beat" the opponent's predicted move. There's a lot of room for it to be smarter with all the data it keeps track of, but I ran out of time to work on it.






                        share|improve this answer























                        • When you get the time, mind giving her an optimization pass? It's easily the largest time-sink.
                          – Blacksilver
                          yesterday






                        • 2




                          @Blacksilver I just reduced the maximum pattern length from 100 to 10. It should run almost instantly now if you're running <200 rounds
                          – Sparr
                          yesterday








                        • 1




                          Maybe using a highly composite number (i.e., 12) would score better?
                          – Blacksilver
                          22 hours ago













                        up vote
                        5
                        down vote










                        up vote
                        5
                        down vote









                        PatternFinder



                        class PatternFinder:
                        def __init__(self):
                        import collections
                        self.size = 10
                        self.moves = [None]
                        self.other =
                        self.patterns = collections.defaultdict(list)
                        self.counter_moves = {"C":"D", "N":"C", "D":"N"}
                        self.initial_move = "D"
                        self.pattern_length_exponent = 1
                        self.pattern_age_exponent = 1
                        self.debug = False
                        def round(self, last):
                        self.other.append(last)
                        best_pattern_match = None
                        best_pattern_score = None
                        best_pattern_response = None
                        self.debug and print("match so far:",tuple(zip(self.moves,self.other)))
                        for turn in range(max(0,len(self.moves)-self.size),len(self.moves)):
                        # record patterns ending with the move that just happened
                        pattern_full = tuple(zip(self.moves[turn:],self.other[turn:]))
                        if len(pattern_full) > 1:
                        pattern_trunc = pattern_full[:-1]
                        pattern_trunc_result = pattern_full[-1][1]
                        self.patterns[pattern_trunc].append([pattern_trunc_result,len(self.moves)-1])
                        if pattern_full in self.patterns:
                        # we've seen this pattern at least once before
                        self.debug and print("I've seen",pattern_full,"before:",self.patterns[pattern_full])
                        for [response,turn_num] in self.patterns[pattern_full]:
                        score = len(pattern_full) ** self.pattern_length_exponent / (len(self.moves) - turn_num) ** self.pattern_age_exponent
                        if best_pattern_score == None or score > best_pattern_score:
                        best_pattern_match = pattern_full
                        best_pattern_score = score
                        best_pattern_response = response
                        # this could be much smarter about aggregating previous responses
                        if best_pattern_response:
                        move = self.counter_moves[best_pattern_response]
                        else:
                        # fall back to playing nice
                        move = "C"
                        self.moves.append(move)
                        self.debug and print("I choose",move)
                        return move


                        This bot looks for previous occurrences of the recent game state to see how the opponent responded to those occurrences, with a preference for longer pattern matches and more recent matches, then plays the move that will "beat" the opponent's predicted move. There's a lot of room for it to be smarter with all the data it keeps track of, but I ran out of time to work on it.






                        share|improve this answer














                        PatternFinder



                        class PatternFinder:
                        def __init__(self):
                        import collections
                        self.size = 10
                        self.moves = [None]
                        self.other =
                        self.patterns = collections.defaultdict(list)
                        self.counter_moves = {"C":"D", "N":"C", "D":"N"}
                        self.initial_move = "D"
                        self.pattern_length_exponent = 1
                        self.pattern_age_exponent = 1
                        self.debug = False
                        def round(self, last):
                        self.other.append(last)
                        best_pattern_match = None
                        best_pattern_score = None
                        best_pattern_response = None
                        self.debug and print("match so far:",tuple(zip(self.moves,self.other)))
                        for turn in range(max(0,len(self.moves)-self.size),len(self.moves)):
                        # record patterns ending with the move that just happened
                        pattern_full = tuple(zip(self.moves[turn:],self.other[turn:]))
                        if len(pattern_full) > 1:
                        pattern_trunc = pattern_full[:-1]
                        pattern_trunc_result = pattern_full[-1][1]
                        self.patterns[pattern_trunc].append([pattern_trunc_result,len(self.moves)-1])
                        if pattern_full in self.patterns:
                        # we've seen this pattern at least once before
                        self.debug and print("I've seen",pattern_full,"before:",self.patterns[pattern_full])
                        for [response,turn_num] in self.patterns[pattern_full]:
                        score = len(pattern_full) ** self.pattern_length_exponent / (len(self.moves) - turn_num) ** self.pattern_age_exponent
                        if best_pattern_score == None or score > best_pattern_score:
                        best_pattern_match = pattern_full
                        best_pattern_score = score
                        best_pattern_response = response
                        # this could be much smarter about aggregating previous responses
                        if best_pattern_response:
                        move = self.counter_moves[best_pattern_response]
                        else:
                        # fall back to playing nice
                        move = "C"
                        self.moves.append(move)
                        self.debug and print("I choose",move)
                        return move


                        This bot looks for previous occurrences of the recent game state to see how the opponent responded to those occurrences, with a preference for longer pattern matches and more recent matches, then plays the move that will "beat" the opponent's predicted move. There's a lot of room for it to be smarter with all the data it keeps track of, but I ran out of time to work on it.







                        share|improve this answer














                        share|improve this answer



                        share|improve this answer








                        edited yesterday

























                        answered yesterday









                        Sparr

                        5,0081633




                        5,0081633












                        • When you get the time, mind giving her an optimization pass? It's easily the largest time-sink.
                          – Blacksilver
                          yesterday






                        • 2




                          @Blacksilver I just reduced the maximum pattern length from 100 to 10. It should run almost instantly now if you're running <200 rounds
                          – Sparr
                          yesterday








                        • 1




                          Maybe using a highly composite number (i.e., 12) would score better?
                          – Blacksilver
                          22 hours ago


















                        • When you get the time, mind giving her an optimization pass? It's easily the largest time-sink.
                          – Blacksilver
                          yesterday






                        • 2




                          @Blacksilver I just reduced the maximum pattern length from 100 to 10. It should run almost instantly now if you're running <200 rounds
                          – Sparr
                          yesterday








                        • 1




                          Maybe using a highly composite number (i.e., 12) would score better?
                          – Blacksilver
                          22 hours ago
















                        When you get the time, mind giving her an optimization pass? It's easily the largest time-sink.
                        – Blacksilver
                        yesterday




                        When you get the time, mind giving her an optimization pass? It's easily the largest time-sink.
                        – Blacksilver
                        yesterday




                        2




                        2




                        @Blacksilver I just reduced the maximum pattern length from 100 to 10. It should run almost instantly now if you're running <200 rounds
                        – Sparr
                        yesterday






                        @Blacksilver I just reduced the maximum pattern length from 100 to 10. It should run almost instantly now if you're running <200 rounds
                        – Sparr
                        yesterday






                        1




                        1




                        Maybe using a highly composite number (i.e., 12) would score better?
                        – Blacksilver
                        22 hours ago




                        Maybe using a highly composite number (i.e., 12) would score better?
                        – Blacksilver
                        22 hours ago










                        up vote
                        4
                        down vote













                        Jade



                        class Jade:
                        def __init__(self):
                        self.dRate = 0.001
                        self.nRate = 0.003

                        def round(self, last):
                        if last == 'D':
                        self.dRate *= 1.1
                        self.nRate *= 1.2
                        elif last == 'N':
                        self.dRate *= 1.03
                        self.nRate *= 1.05
                        self.dRate = min(self.dRate, 1)
                        self.nRate = min(self.nRate, 1)

                        x = random.random()
                        if x > (1 - self.dRate):
                        return 'D'
                        elif x > (1 - self.nRate):
                        return 'N'
                        else:
                        return 'C'


                        Starts out optimistic, but gets progressively more bitter as the opponent refuses to cooperate. Lots of magic constants that could probably be tweaked, but this probably isn't going to do well enough to justify the time.






                        share|improve this answer

























                          up vote
                          4
                          down vote













                          Jade



                          class Jade:
                          def __init__(self):
                          self.dRate = 0.001
                          self.nRate = 0.003

                          def round(self, last):
                          if last == 'D':
                          self.dRate *= 1.1
                          self.nRate *= 1.2
                          elif last == 'N':
                          self.dRate *= 1.03
                          self.nRate *= 1.05
                          self.dRate = min(self.dRate, 1)
                          self.nRate = min(self.nRate, 1)

                          x = random.random()
                          if x > (1 - self.dRate):
                          return 'D'
                          elif x > (1 - self.nRate):
                          return 'N'
                          else:
                          return 'C'


                          Starts out optimistic, but gets progressively more bitter as the opponent refuses to cooperate. Lots of magic constants that could probably be tweaked, but this probably isn't going to do well enough to justify the time.






                          share|improve this answer























                            up vote
                            4
                            down vote










                            up vote
                            4
                            down vote









                            Jade



                            class Jade:
                            def __init__(self):
                            self.dRate = 0.001
                            self.nRate = 0.003

                            def round(self, last):
                            if last == 'D':
                            self.dRate *= 1.1
                            self.nRate *= 1.2
                            elif last == 'N':
                            self.dRate *= 1.03
                            self.nRate *= 1.05
                            self.dRate = min(self.dRate, 1)
                            self.nRate = min(self.nRate, 1)

                            x = random.random()
                            if x > (1 - self.dRate):
                            return 'D'
                            elif x > (1 - self.nRate):
                            return 'N'
                            else:
                            return 'C'


                            Starts out optimistic, but gets progressively more bitter as the opponent refuses to cooperate. Lots of magic constants that could probably be tweaked, but this probably isn't going to do well enough to justify the time.






                            share|improve this answer












                            Jade



                            class Jade:
                            def __init__(self):
                            self.dRate = 0.001
                            self.nRate = 0.003

                            def round(self, last):
                            if last == 'D':
                            self.dRate *= 1.1
                            self.nRate *= 1.2
                            elif last == 'N':
                            self.dRate *= 1.03
                            self.nRate *= 1.05
                            self.dRate = min(self.dRate, 1)
                            self.nRate = min(self.nRate, 1)

                            x = random.random()
                            if x > (1 - self.dRate):
                            return 'D'
                            elif x > (1 - self.nRate):
                            return 'N'
                            else:
                            return 'C'


                            Starts out optimistic, but gets progressively more bitter as the opponent refuses to cooperate. Lots of magic constants that could probably be tweaked, but this probably isn't going to do well enough to justify the time.







                            share|improve this answer












                            share|improve this answer



                            share|improve this answer










                            answered yesterday









                            Mnemonic

                            4,6351630




                            4,6351630






















                                up vote
                                4
                                down vote













                                OldTitForTat



                                Old school player is too lazy to update for the new rules.



                                class OldTitForTat:
                                def round(self, last):
                                if(last == None)
                                return "C"
                                if(last == "C"):
                                return "C"
                                return "D"





                                share|improve this answer

























                                  up vote
                                  4
                                  down vote













                                  OldTitForTat



                                  Old school player is too lazy to update for the new rules.



                                  class OldTitForTat:
                                  def round(self, last):
                                  if(last == None)
                                  return "C"
                                  if(last == "C"):
                                  return "C"
                                  return "D"





                                  share|improve this answer























                                    up vote
                                    4
                                    down vote










                                    up vote
                                    4
                                    down vote









                                    OldTitForTat



                                    Old school player is too lazy to update for the new rules.



                                    class OldTitForTat:
                                    def round(self, last):
                                    if(last == None)
                                    return "C"
                                    if(last == "C"):
                                    return "C"
                                    return "D"





                                    share|improve this answer












                                    OldTitForTat



                                    Old school player is too lazy to update for the new rules.



                                    class OldTitForTat:
                                    def round(self, last):
                                    if(last == None)
                                    return "C"
                                    if(last == "C"):
                                    return "C"
                                    return "D"






                                    share|improve this answer












                                    share|improve this answer



                                    share|improve this answer










                                    answered yesterday









                                    Joshua

                                    2,382918




                                    2,382918






















                                        up vote
                                        3
                                        down vote













                                        NeverCOOP





                                        class NeverCOOP:
                                        def round(self, last):
                                        try:
                                        if last in "ND":
                                        return "D"
                                        else:
                                        return "N"
                                        except:
                                        return "N"


                                        If the opposing bot defects or is neutral, choose defect. Otherwise if this is the first turn or the opposing bot cooperates, choose neutral. I'm not sure how good this will work...






                                        share|improve this answer








                                        New contributor




                                        glietz is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                        Check out our Code of Conduct.


















                                        • What's the try/except for?
                                          – Blacksilver
                                          yesterday






                                        • 1




                                          @Blacksilver I'd assume it serves the same purpose as the if(last): in your Grudger bot, detecting whether there was a previous round.
                                          – ETHproductions
                                          yesterday










                                        • ahh, I see. None in "ND" errors.
                                          – Blacksilver
                                          yesterday










                                        • Because if last and last in "ND": was too complicated?
                                          – immibis
                                          yesterday















                                        up vote
                                        3
                                        down vote













                                        NeverCOOP





                                        class NeverCOOP:
                                        def round(self, last):
                                        try:
                                        if last in "ND":
                                        return "D"
                                        else:
                                        return "N"
                                        except:
                                        return "N"


                                        If the opposing bot defects or is neutral, choose defect. Otherwise if this is the first turn or the opposing bot cooperates, choose neutral. I'm not sure how good this will work...






                                        share|improve this answer








                                        New contributor




                                        glietz is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                        Check out our Code of Conduct.


















                                        • What's the try/except for?
                                          – Blacksilver
                                          yesterday






                                        • 1




                                          @Blacksilver I'd assume it serves the same purpose as the if(last): in your Grudger bot, detecting whether there was a previous round.
                                          – ETHproductions
                                          yesterday










                                        • ahh, I see. None in "ND" errors.
                                          – Blacksilver
                                          yesterday










                                        • Because if last and last in "ND": was too complicated?
                                          – immibis
                                          yesterday













                                        up vote
                                        3
                                        down vote










                                        up vote
                                        3
                                        down vote









                                        NeverCOOP





                                        class NeverCOOP:
                                        def round(self, last):
                                        try:
                                        if last in "ND":
                                        return "D"
                                        else:
                                        return "N"
                                        except:
                                        return "N"


                                        If the opposing bot defects or is neutral, choose defect. Otherwise if this is the first turn or the opposing bot cooperates, choose neutral. I'm not sure how good this will work...






                                        share|improve this answer








                                        New contributor




                                        glietz is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                        Check out our Code of Conduct.









                                        NeverCOOP





                                        class NeverCOOP:
                                        def round(self, last):
                                        try:
                                        if last in "ND":
                                        return "D"
                                        else:
                                        return "N"
                                        except:
                                        return "N"


                                        If the opposing bot defects or is neutral, choose defect. Otherwise if this is the first turn or the opposing bot cooperates, choose neutral. I'm not sure how good this will work...







                                        share|improve this answer








                                        New contributor




                                        glietz is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                        Check out our Code of Conduct.









                                        share|improve this answer



                                        share|improve this answer






                                        New contributor




                                        glietz is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                        Check out our Code of Conduct.









                                        answered yesterday









                                        glietz

                                        716




                                        716




                                        New contributor




                                        glietz is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                        Check out our Code of Conduct.





                                        New contributor





                                        glietz is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                        Check out our Code of Conduct.






                                        glietz is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                        Check out our Code of Conduct.












                                        • What's the try/except for?
                                          – Blacksilver
                                          yesterday






                                        • 1




                                          @Blacksilver I'd assume it serves the same purpose as the if(last): in your Grudger bot, detecting whether there was a previous round.
                                          – ETHproductions
                                          yesterday










                                        • ahh, I see. None in "ND" errors.
                                          – Blacksilver
                                          yesterday










                                        • Because if last and last in "ND": was too complicated?
                                          – immibis
                                          yesterday


















                                        • What's the try/except for?
                                          – Blacksilver
                                          yesterday






                                        • 1




                                          @Blacksilver I'd assume it serves the same purpose as the if(last): in your Grudger bot, detecting whether there was a previous round.
                                          – ETHproductions
                                          yesterday










                                        • ahh, I see. None in "ND" errors.
                                          – Blacksilver
                                          yesterday










                                        • Because if last and last in "ND": was too complicated?
                                          – immibis
                                          yesterday
















                                        What's the try/except for?
                                        – Blacksilver
                                        yesterday




                                        What's the try/except for?
                                        – Blacksilver
                                        yesterday




                                        1




                                        1




                                        @Blacksilver I'd assume it serves the same purpose as the if(last): in your Grudger bot, detecting whether there was a previous round.
                                        – ETHproductions
                                        yesterday




                                        @Blacksilver I'd assume it serves the same purpose as the if(last): in your Grudger bot, detecting whether there was a previous round.
                                        – ETHproductions
                                        yesterday












                                        ahh, I see. None in "ND" errors.
                                        – Blacksilver
                                        yesterday




                                        ahh, I see. None in "ND" errors.
                                        – Blacksilver
                                        yesterday












                                        Because if last and last in "ND": was too complicated?
                                        – immibis
                                        yesterday




                                        Because if last and last in "ND": was too complicated?
                                        – immibis
                                        yesterday










                                        up vote
                                        3
                                        down vote













                                        LastOptimalBot



                                        class LastOptimalBot:
                                        def round(self, last):
                                        return "N" if last == "D" else ("D" if last == "C" else "C")


                                        Assumes that the opposing bot will always play the same move again, and chooses the one that has the best payoff against it.



                                        Averages:



                                        Me   Opp
                                        2.6 2 vs TitForTat
                                        5 0 vs AllC
                                        4 1 vs AllN
                                        3 2 vs AllD
                                        3.5 3.5 vs Random
                                        3 2 vs Grudger
                                        2 2 vs LastOptimalBot
                                        1 3.5 vs TatForTit
                                        4 1 vs NeverCOOP
                                        1 4 vs EvaluaterBot
                                        2.28 2.24 vs NashEquilibrium

                                        2.91 average overall





                                        share|improve this answer























                                        • oof. Maybe T4T would do better as return last.
                                          – Blacksilver
                                          yesterday










                                        • I'd like that! If TitForTat were return last, LOB would go 18-9 over 6 rounds rather than the 13-10 over 5 rounds it's currently getting. I think it's fine as is - don't worry about optimizing the example bots.
                                          – Spitemaster
                                          yesterday










                                        • return last would be a better T4T for this challenge, I think
                                          – Sparr
                                          yesterday










                                        • Just tried -- the if(last): return last; else: return "C" is worse.
                                          – Blacksilver
                                          yesterday










                                        • Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                          – Spitemaster
                                          yesterday















                                        up vote
                                        3
                                        down vote













                                        LastOptimalBot



                                        class LastOptimalBot:
                                        def round(self, last):
                                        return "N" if last == "D" else ("D" if last == "C" else "C")


                                        Assumes that the opposing bot will always play the same move again, and chooses the one that has the best payoff against it.



                                        Averages:



                                        Me   Opp
                                        2.6 2 vs TitForTat
                                        5 0 vs AllC
                                        4 1 vs AllN
                                        3 2 vs AllD
                                        3.5 3.5 vs Random
                                        3 2 vs Grudger
                                        2 2 vs LastOptimalBot
                                        1 3.5 vs TatForTit
                                        4 1 vs NeverCOOP
                                        1 4 vs EvaluaterBot
                                        2.28 2.24 vs NashEquilibrium

                                        2.91 average overall





                                        share|improve this answer























                                        • oof. Maybe T4T would do better as return last.
                                          – Blacksilver
                                          yesterday










                                        • I'd like that! If TitForTat were return last, LOB would go 18-9 over 6 rounds rather than the 13-10 over 5 rounds it's currently getting. I think it's fine as is - don't worry about optimizing the example bots.
                                          – Spitemaster
                                          yesterday










                                        • return last would be a better T4T for this challenge, I think
                                          – Sparr
                                          yesterday










                                        • Just tried -- the if(last): return last; else: return "C" is worse.
                                          – Blacksilver
                                          yesterday










                                        • Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                          – Spitemaster
                                          yesterday













                                        up vote
                                        3
                                        down vote










                                        up vote
                                        3
                                        down vote









                                        LastOptimalBot



                                        class LastOptimalBot:
                                        def round(self, last):
                                        return "N" if last == "D" else ("D" if last == "C" else "C")


                                        Assumes that the opposing bot will always play the same move again, and chooses the one that has the best payoff against it.



                                        Averages:



                                        Me   Opp
                                        2.6 2 vs TitForTat
                                        5 0 vs AllC
                                        4 1 vs AllN
                                        3 2 vs AllD
                                        3.5 3.5 vs Random
                                        3 2 vs Grudger
                                        2 2 vs LastOptimalBot
                                        1 3.5 vs TatForTit
                                        4 1 vs NeverCOOP
                                        1 4 vs EvaluaterBot
                                        2.28 2.24 vs NashEquilibrium

                                        2.91 average overall





                                        share|improve this answer














                                        LastOptimalBot



                                        class LastOptimalBot:
                                        def round(self, last):
                                        return "N" if last == "D" else ("D" if last == "C" else "C")


                                        Assumes that the opposing bot will always play the same move again, and chooses the one that has the best payoff against it.



                                        Averages:



                                        Me   Opp
                                        2.6 2 vs TitForTat
                                        5 0 vs AllC
                                        4 1 vs AllN
                                        3 2 vs AllD
                                        3.5 3.5 vs Random
                                        3 2 vs Grudger
                                        2 2 vs LastOptimalBot
                                        1 3.5 vs TatForTit
                                        4 1 vs NeverCOOP
                                        1 4 vs EvaluaterBot
                                        2.28 2.24 vs NashEquilibrium

                                        2.91 average overall






                                        share|improve this answer














                                        share|improve this answer



                                        share|improve this answer








                                        edited yesterday

























                                        answered yesterday









                                        Spitemaster

                                        3214




                                        3214












                                        • oof. Maybe T4T would do better as return last.
                                          – Blacksilver
                                          yesterday










                                        • I'd like that! If TitForTat were return last, LOB would go 18-9 over 6 rounds rather than the 13-10 over 5 rounds it's currently getting. I think it's fine as is - don't worry about optimizing the example bots.
                                          – Spitemaster
                                          yesterday










                                        • return last would be a better T4T for this challenge, I think
                                          – Sparr
                                          yesterday










                                        • Just tried -- the if(last): return last; else: return "C" is worse.
                                          – Blacksilver
                                          yesterday










                                        • Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                          – Spitemaster
                                          yesterday


















                                        • oof. Maybe T4T would do better as return last.
                                          – Blacksilver
                                          yesterday










                                        • I'd like that! If TitForTat were return last, LOB would go 18-9 over 6 rounds rather than the 13-10 over 5 rounds it's currently getting. I think it's fine as is - don't worry about optimizing the example bots.
                                          – Spitemaster
                                          yesterday










                                        • return last would be a better T4T for this challenge, I think
                                          – Sparr
                                          yesterday










                                        • Just tried -- the if(last): return last; else: return "C" is worse.
                                          – Blacksilver
                                          yesterday










                                        • Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                          – Spitemaster
                                          yesterday
















                                        oof. Maybe T4T would do better as return last.
                                        – Blacksilver
                                        yesterday




                                        oof. Maybe T4T would do better as return last.
                                        – Blacksilver
                                        yesterday












                                        I'd like that! If TitForTat were return last, LOB would go 18-9 over 6 rounds rather than the 13-10 over 5 rounds it's currently getting. I think it's fine as is - don't worry about optimizing the example bots.
                                        – Spitemaster
                                        yesterday




                                        I'd like that! If TitForTat were return last, LOB would go 18-9 over 6 rounds rather than the 13-10 over 5 rounds it's currently getting. I think it's fine as is - don't worry about optimizing the example bots.
                                        – Spitemaster
                                        yesterday












                                        return last would be a better T4T for this challenge, I think
                                        – Sparr
                                        yesterday




                                        return last would be a better T4T for this challenge, I think
                                        – Sparr
                                        yesterday












                                        Just tried -- the if(last): return last; else: return "C" is worse.
                                        – Blacksilver
                                        yesterday




                                        Just tried -- the if(last): return last; else: return "C" is worse.
                                        – Blacksilver
                                        yesterday












                                        Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                        – Spitemaster
                                        yesterday




                                        Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                        – Spitemaster
                                        yesterday










                                        up vote
                                        3
                                        down vote













                                        CopyCat



                                        class CopyCat:
                                        def round(self, last):
                                        if last:
                                        return last
                                        return "C"


                                        Copies the opponent's last move.

                                        I don't expect this to do well, but no one had implemented this classic yet.






                                        share|improve this answer










                                        New contributor




                                        MannerPots is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                        Check out our Code of Conduct.






















                                          up vote
                                          3
                                          down vote













                                          CopyCat



                                          class CopyCat:
                                          def round(self, last):
                                          if last:
                                          return last
                                          return "C"


                                          Copies the opponent's last move.

                                          I don't expect this to do well, but no one had implemented this classic yet.






                                          share|improve this answer










                                          New contributor




                                          MannerPots is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                          Check out our Code of Conduct.




















                                            up vote
                                            3
                                            down vote










                                            up vote
                                            3
                                            down vote









                                            CopyCat



                                            class CopyCat:
                                            def round(self, last):
                                            if last:
                                            return last
                                            return "C"


                                            Copies the opponent's last move.

                                            I don't expect this to do well, but no one had implemented this classic yet.






                                            share|improve this answer










                                            New contributor




                                            MannerPots is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                            Check out our Code of Conduct.









                                            CopyCat



                                            class CopyCat:
                                            def round(self, last):
                                            if last:
                                            return last
                                            return "C"


                                            Copies the opponent's last move.

                                            I don't expect this to do well, but no one had implemented this classic yet.







                                            share|improve this answer










                                            New contributor




                                            MannerPots is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                            Check out our Code of Conduct.









                                            share|improve this answer



                                            share|improve this answer








                                            edited yesterday









                                            Blacksilver

                                            423314




                                            423314






                                            New contributor




                                            MannerPots is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                            Check out our Code of Conduct.









                                            answered yesterday









                                            MannerPots

                                            312




                                            312




                                            New contributor




                                            MannerPots is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                            Check out our Code of Conduct.





                                            New contributor





                                            MannerPots is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                            Check out our Code of Conduct.






                                            MannerPots is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                            Check out our Code of Conduct.






















                                                up vote
                                                2
                                                down vote













                                                Ensemble



                                                This runs an ensemble of related models. The individual models consider different amounts of history, and have the option of either always choosing the move that will optimize the expected payout difference, or will randomly select a move in proportion to expected payout difference.



                                                Each member of the ensemble then votes on their preferred move. They get a number of votes equal to how much more they've won than the opponent (which means that terrible models will get negative votes). Whichever move wins the vote is then selected.



                                                (They should probably split their votes among the moves in proportion to how much they favor each, but I don't care enough to do that right now.)



                                                It beats everything posted so far except EvaluaterBot and PatternFinder. (One-on-one, it beats EvaluaterBot and loses to PatternFinder).



                                                from collections import defaultdict
                                                import random
                                                class Number6:
                                                class Choices:
                                                def __init__(self, C = 0, N = 0, D = 0):
                                                self.C = C
                                                self.N = N
                                                self.D = D

                                                def __init__(self, strategy = "maxExpected", markov_order = 3):
                                                self.MARKOV_ORDER = markov_order;
                                                self.my_choices = ""
                                                self.opponent = defaultdict(lambda: self.Choices())
                                                self.choice = None # previous choice
                                                self.payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }
                                                self.total_payoff = 0

                                                # if random, will choose in proportion to payoff.
                                                # otherwise, will always choose argmax
                                                self.strategy = strategy
                                                # maxExpected: maximize expected relative payoff
                                                # random: like maxExpected, but it chooses in proportion to E[payoff]
                                                # argmax: always choose the option that is optimal for expected opponent choice

                                                def update_opponent_model(self, last):
                                                for i in range(0, self.MARKOV_ORDER):
                                                hist = self.my_choices[i:]
                                                self.opponent[hist].C += ("C" == last)
                                                self.opponent[hist].N += ("N" == last)
                                                self.opponent[hist].D += ("D" == last)

                                                def normalize(self, counts):
                                                sum = float(counts.C + counts.N + counts.D)
                                                if 0 == sum:
                                                return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)
                                                return self.Choices(
                                                counts.C / sum, counts.N / sum, counts.D / sum)

                                                def get_distribution(self):
                                                for i in range(0, self.MARKOV_ORDER):
                                                hist = self.my_choices[i:]
                                                #print "check hist = " + hist
                                                if hist in self.opponent:
                                                return self.normalize(self.opponent[hist])

                                                return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)

                                                def choose(self, dist):
                                                payoff = self.Choices()
                                                # We're interested in *beating the opponent*, not
                                                # maximizing our score, so we optimize the difference
                                                payoff.C = (3-3) * dist.C + (4-1) * dist.N + (0-5) * dist.D
                                                payoff.N = (1-4) * dist.C + (2-2) * dist.N + (3-2) * dist.D
                                                payoff.D = (5-0) * dist.C + (2-3) * dist.N + (1-1) * dist.D

                                                # D has slightly better payoff on uniform opponent,
                                                # so we select it on ties
                                                if self.strategy == "maxExpected":
                                                if payoff.C > payoff.N:
                                                return "C" if payoff.C > payoff.D else "D"
                                                return "N" if payoff.N > payoff.D else "D"
                                                elif self.strategy == "randomize":
                                                payoff = self.normalize(payoff)
                                                r = random.uniform(0.0, 1.0)
                                                if (r < payoff.C): return "C"
                                                return "N" if (r < payoff.N) else "D"
                                                elif self.strategy == "argMax":
                                                if dist.C > dist.N:
                                                return "D" if dist.C > dist.D else "N"
                                                return "C" if dist.N > dist.D else "N"

                                                assert(0) #, "I am not a number! I am a free man!")

                                                def update_history(self):
                                                self.my_choices += self.choice
                                                if len(self.my_choices) > self.MARKOV_ORDER:
                                                assert(len(self.my_choices) == self.MARKOV_ORDER + 1)
                                                self.my_choices = self.my_choices[1:]

                                                def round(self, last):
                                                if last: self.update_opponent_model(last)

                                                dist = self.get_distribution()
                                                self.choice = self.choose(dist)
                                                self.update_history()
                                                return self.choice

                                                class Ensemble:
                                                def __init__(self):
                                                self.models =
                                                self.votes =
                                                self.prev_choice =
                                                for order in range(0, 6):
                                                self.models.append(Number6("maxExpected", order))
                                                self.models.append(Number6("randomize", order))
                                                #self.models.append(Number6("argMax", order))
                                                for i in range(0, len(self.models)):
                                                self.votes.append(0)
                                                self.prev_choice.append("D")

                                                self.payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }

                                                def round(self, last):
                                                if last:
                                                for i in range(0, len(self.models)):
                                                self.votes[i] += self.payoff[self.prev_choice[i]][last]

                                                # vote. Sufficiently terrible models get negative votes
                                                C = 0
                                                N = 0
                                                D = 0
                                                for i in range(0, len(self.models)):
                                                choice = self.models[i].round(last)
                                                if "C" == choice: C += self.votes[i]
                                                if "N" == choice: N += self.votes[i]
                                                if "D" == choice: D += self.votes[i]
                                                self.prev_choice[i] = choice

                                                if C > D and C > N: return "C"
                                                elif N > D: return "N"
                                                else: return "D"


                                                Test Framework



                                                In case anyone else finds it useful, here's a test framework for looking at individual matchups. Python2. Just put all the opponents you're interested in in opponents.py, and change the references to Ensemble to your own.



                                                import sys, inspect
                                                import opponents
                                                from ensemble import Ensemble

                                                def count_payoff(label, them):
                                                if None == them: return
                                                me = choices[label]
                                                payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }
                                                if label not in total_payoff: total_payoff[label] = 0
                                                total_payoff[label] += payoff[me][them]

                                                def update_hist(label, choice):
                                                choices[label] = choice

                                                opponents = [ x[1] for x
                                                in inspect.getmembers(sys.modules['opponents'], inspect.isclass)]

                                                for k in opponents:
                                                total_payoff = {}

                                                for j in range(0, 100):
                                                A = Ensemble()
                                                B = k()
                                                choices = {}

                                                aChoice = None
                                                bChoice = None
                                                for i in range(0, 100):
                                                count_payoff(A.__class__.__name__, bChoice)
                                                a = A.round(bChoice)
                                                update_hist(A.__class__.__name__, a)

                                                count_payoff(B.__class__.__name__, aChoice)
                                                b = B.round(aChoice)
                                                update_hist(B.__class__.__name__, b)

                                                aChoice = a
                                                bChoice = b
                                                print total_payoff





                                                share|improve this answer























                                                • The controller is ready, you didn't have to do all that...
                                                  – Blacksilver
                                                  yesterday






                                                • 1




                                                  @Blacksilver I realized that just as I was about to submit. But this one works in versions before 3.6, and gives information about individual matchups that can help identify weak spots, so it wasn't a complete waste of time.
                                                  – Ray
                                                  yesterday










                                                • Fair enough; running now. I'll probably add options to my controller to do similar things.
                                                  – Blacksilver
                                                  yesterday










                                                • "It beats everything posted so far except Ensemble and PatternFinder" I'm honored :)
                                                  – Sparr
                                                  yesterday










                                                • @Sparr Oops. That was supposed to say EvaluaterBot and PatternFinder. But that's when comparing total score against the entire field. PatternFinder remains the only one that beats this in a direct match up.
                                                  – Ray
                                                  yesterday















                                                up vote
                                                2
                                                down vote













                                                Ensemble



                                                This runs an ensemble of related models. The individual models consider different amounts of history, and have the option of either always choosing the move that will optimize the expected payout difference, or will randomly select a move in proportion to expected payout difference.



                                                Each member of the ensemble then votes on their preferred move. They get a number of votes equal to how much more they've won than the opponent (which means that terrible models will get negative votes). Whichever move wins the vote is then selected.



                                                (They should probably split their votes among the moves in proportion to how much they favor each, but I don't care enough to do that right now.)



                                                It beats everything posted so far except EvaluaterBot and PatternFinder. (One-on-one, it beats EvaluaterBot and loses to PatternFinder).



                                                from collections import defaultdict
                                                import random
                                                class Number6:
                                                class Choices:
                                                def __init__(self, C = 0, N = 0, D = 0):
                                                self.C = C
                                                self.N = N
                                                self.D = D

                                                def __init__(self, strategy = "maxExpected", markov_order = 3):
                                                self.MARKOV_ORDER = markov_order;
                                                self.my_choices = ""
                                                self.opponent = defaultdict(lambda: self.Choices())
                                                self.choice = None # previous choice
                                                self.payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }
                                                self.total_payoff = 0

                                                # if random, will choose in proportion to payoff.
                                                # otherwise, will always choose argmax
                                                self.strategy = strategy
                                                # maxExpected: maximize expected relative payoff
                                                # random: like maxExpected, but it chooses in proportion to E[payoff]
                                                # argmax: always choose the option that is optimal for expected opponent choice

                                                def update_opponent_model(self, last):
                                                for i in range(0, self.MARKOV_ORDER):
                                                hist = self.my_choices[i:]
                                                self.opponent[hist].C += ("C" == last)
                                                self.opponent[hist].N += ("N" == last)
                                                self.opponent[hist].D += ("D" == last)

                                                def normalize(self, counts):
                                                sum = float(counts.C + counts.N + counts.D)
                                                if 0 == sum:
                                                return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)
                                                return self.Choices(
                                                counts.C / sum, counts.N / sum, counts.D / sum)

                                                def get_distribution(self):
                                                for i in range(0, self.MARKOV_ORDER):
                                                hist = self.my_choices[i:]
                                                #print "check hist = " + hist
                                                if hist in self.opponent:
                                                return self.normalize(self.opponent[hist])

                                                return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)

                                                def choose(self, dist):
                                                payoff = self.Choices()
                                                # We're interested in *beating the opponent*, not
                                                # maximizing our score, so we optimize the difference
                                                payoff.C = (3-3) * dist.C + (4-1) * dist.N + (0-5) * dist.D
                                                payoff.N = (1-4) * dist.C + (2-2) * dist.N + (3-2) * dist.D
                                                payoff.D = (5-0) * dist.C + (2-3) * dist.N + (1-1) * dist.D

                                                # D has slightly better payoff on uniform opponent,
                                                # so we select it on ties
                                                if self.strategy == "maxExpected":
                                                if payoff.C > payoff.N:
                                                return "C" if payoff.C > payoff.D else "D"
                                                return "N" if payoff.N > payoff.D else "D"
                                                elif self.strategy == "randomize":
                                                payoff = self.normalize(payoff)
                                                r = random.uniform(0.0, 1.0)
                                                if (r < payoff.C): return "C"
                                                return "N" if (r < payoff.N) else "D"
                                                elif self.strategy == "argMax":
                                                if dist.C > dist.N:
                                                return "D" if dist.C > dist.D else "N"
                                                return "C" if dist.N > dist.D else "N"

                                                assert(0) #, "I am not a number! I am a free man!")

                                                def update_history(self):
                                                self.my_choices += self.choice
                                                if len(self.my_choices) > self.MARKOV_ORDER:
                                                assert(len(self.my_choices) == self.MARKOV_ORDER + 1)
                                                self.my_choices = self.my_choices[1:]

                                                def round(self, last):
                                                if last: self.update_opponent_model(last)

                                                dist = self.get_distribution()
                                                self.choice = self.choose(dist)
                                                self.update_history()
                                                return self.choice

                                                class Ensemble:
                                                def __init__(self):
                                                self.models =
                                                self.votes =
                                                self.prev_choice =
                                                for order in range(0, 6):
                                                self.models.append(Number6("maxExpected", order))
                                                self.models.append(Number6("randomize", order))
                                                #self.models.append(Number6("argMax", order))
                                                for i in range(0, len(self.models)):
                                                self.votes.append(0)
                                                self.prev_choice.append("D")

                                                self.payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }

                                                def round(self, last):
                                                if last:
                                                for i in range(0, len(self.models)):
                                                self.votes[i] += self.payoff[self.prev_choice[i]][last]

                                                # vote. Sufficiently terrible models get negative votes
                                                C = 0
                                                N = 0
                                                D = 0
                                                for i in range(0, len(self.models)):
                                                choice = self.models[i].round(last)
                                                if "C" == choice: C += self.votes[i]
                                                if "N" == choice: N += self.votes[i]
                                                if "D" == choice: D += self.votes[i]
                                                self.prev_choice[i] = choice

                                                if C > D and C > N: return "C"
                                                elif N > D: return "N"
                                                else: return "D"


                                                Test Framework



                                                In case anyone else finds it useful, here's a test framework for looking at individual matchups. Python2. Just put all the opponents you're interested in in opponents.py, and change the references to Ensemble to your own.



                                                import sys, inspect
                                                import opponents
                                                from ensemble import Ensemble

                                                def count_payoff(label, them):
                                                if None == them: return
                                                me = choices[label]
                                                payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }
                                                if label not in total_payoff: total_payoff[label] = 0
                                                total_payoff[label] += payoff[me][them]

                                                def update_hist(label, choice):
                                                choices[label] = choice

                                                opponents = [ x[1] for x
                                                in inspect.getmembers(sys.modules['opponents'], inspect.isclass)]

                                                for k in opponents:
                                                total_payoff = {}

                                                for j in range(0, 100):
                                                A = Ensemble()
                                                B = k()
                                                choices = {}

                                                aChoice = None
                                                bChoice = None
                                                for i in range(0, 100):
                                                count_payoff(A.__class__.__name__, bChoice)
                                                a = A.round(bChoice)
                                                update_hist(A.__class__.__name__, a)

                                                count_payoff(B.__class__.__name__, aChoice)
                                                b = B.round(aChoice)
                                                update_hist(B.__class__.__name__, b)

                                                aChoice = a
                                                bChoice = b
                                                print total_payoff





                                                share|improve this answer























                                                • The controller is ready, you didn't have to do all that...
                                                  – Blacksilver
                                                  yesterday






                                                • 1




                                                  @Blacksilver I realized that just as I was about to submit. But this one works in versions before 3.6, and gives information about individual matchups that can help identify weak spots, so it wasn't a complete waste of time.
                                                  – Ray
                                                  yesterday










                                                • Fair enough; running now. I'll probably add options to my controller to do similar things.
                                                  – Blacksilver
                                                  yesterday










                                                • "It beats everything posted so far except Ensemble and PatternFinder" I'm honored :)
                                                  – Sparr
                                                  yesterday










                                                • @Sparr Oops. That was supposed to say EvaluaterBot and PatternFinder. But that's when comparing total score against the entire field. PatternFinder remains the only one that beats this in a direct match up.
                                                  – Ray
                                                  yesterday













                                                up vote
                                                2
                                                down vote










                                                up vote
                                                2
                                                down vote









                                                Ensemble



                                                This runs an ensemble of related models. The individual models consider different amounts of history, and have the option of either always choosing the move that will optimize the expected payout difference, or will randomly select a move in proportion to expected payout difference.



                                                Each member of the ensemble then votes on their preferred move. They get a number of votes equal to how much more they've won than the opponent (which means that terrible models will get negative votes). Whichever move wins the vote is then selected.



                                                (They should probably split their votes among the moves in proportion to how much they favor each, but I don't care enough to do that right now.)



                                                It beats everything posted so far except EvaluaterBot and PatternFinder. (One-on-one, it beats EvaluaterBot and loses to PatternFinder).



                                                from collections import defaultdict
                                                import random
                                                class Number6:
                                                class Choices:
                                                def __init__(self, C = 0, N = 0, D = 0):
                                                self.C = C
                                                self.N = N
                                                self.D = D

                                                def __init__(self, strategy = "maxExpected", markov_order = 3):
                                                self.MARKOV_ORDER = markov_order;
                                                self.my_choices = ""
                                                self.opponent = defaultdict(lambda: self.Choices())
                                                self.choice = None # previous choice
                                                self.payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }
                                                self.total_payoff = 0

                                                # if random, will choose in proportion to payoff.
                                                # otherwise, will always choose argmax
                                                self.strategy = strategy
                                                # maxExpected: maximize expected relative payoff
                                                # random: like maxExpected, but it chooses in proportion to E[payoff]
                                                # argmax: always choose the option that is optimal for expected opponent choice

                                                def update_opponent_model(self, last):
                                                for i in range(0, self.MARKOV_ORDER):
                                                hist = self.my_choices[i:]
                                                self.opponent[hist].C += ("C" == last)
                                                self.opponent[hist].N += ("N" == last)
                                                self.opponent[hist].D += ("D" == last)

                                                def normalize(self, counts):
                                                sum = float(counts.C + counts.N + counts.D)
                                                if 0 == sum:
                                                return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)
                                                return self.Choices(
                                                counts.C / sum, counts.N / sum, counts.D / sum)

                                                def get_distribution(self):
                                                for i in range(0, self.MARKOV_ORDER):
                                                hist = self.my_choices[i:]
                                                #print "check hist = " + hist
                                                if hist in self.opponent:
                                                return self.normalize(self.opponent[hist])

                                                return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)

                                                def choose(self, dist):
                                                payoff = self.Choices()
                                                # We're interested in *beating the opponent*, not
                                                # maximizing our score, so we optimize the difference
                                                payoff.C = (3-3) * dist.C + (4-1) * dist.N + (0-5) * dist.D
                                                payoff.N = (1-4) * dist.C + (2-2) * dist.N + (3-2) * dist.D
                                                payoff.D = (5-0) * dist.C + (2-3) * dist.N + (1-1) * dist.D

                                                # D has slightly better payoff on uniform opponent,
                                                # so we select it on ties
                                                if self.strategy == "maxExpected":
                                                if payoff.C > payoff.N:
                                                return "C" if payoff.C > payoff.D else "D"
                                                return "N" if payoff.N > payoff.D else "D"
                                                elif self.strategy == "randomize":
                                                payoff = self.normalize(payoff)
                                                r = random.uniform(0.0, 1.0)
                                                if (r < payoff.C): return "C"
                                                return "N" if (r < payoff.N) else "D"
                                                elif self.strategy == "argMax":
                                                if dist.C > dist.N:
                                                return "D" if dist.C > dist.D else "N"
                                                return "C" if dist.N > dist.D else "N"

                                                assert(0) #, "I am not a number! I am a free man!")

                                                def update_history(self):
                                                self.my_choices += self.choice
                                                if len(self.my_choices) > self.MARKOV_ORDER:
                                                assert(len(self.my_choices) == self.MARKOV_ORDER + 1)
                                                self.my_choices = self.my_choices[1:]

                                                def round(self, last):
                                                if last: self.update_opponent_model(last)

                                                dist = self.get_distribution()
                                                self.choice = self.choose(dist)
                                                self.update_history()
                                                return self.choice

                                                class Ensemble:
                                                def __init__(self):
                                                self.models =
                                                self.votes =
                                                self.prev_choice =
                                                for order in range(0, 6):
                                                self.models.append(Number6("maxExpected", order))
                                                self.models.append(Number6("randomize", order))
                                                #self.models.append(Number6("argMax", order))
                                                for i in range(0, len(self.models)):
                                                self.votes.append(0)
                                                self.prev_choice.append("D")

                                                self.payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }

                                                def round(self, last):
                                                if last:
                                                for i in range(0, len(self.models)):
                                                self.votes[i] += self.payoff[self.prev_choice[i]][last]

                                                # vote. Sufficiently terrible models get negative votes
                                                C = 0
                                                N = 0
                                                D = 0
                                                for i in range(0, len(self.models)):
                                                choice = self.models[i].round(last)
                                                if "C" == choice: C += self.votes[i]
                                                if "N" == choice: N += self.votes[i]
                                                if "D" == choice: D += self.votes[i]
                                                self.prev_choice[i] = choice

                                                if C > D and C > N: return "C"
                                                elif N > D: return "N"
                                                else: return "D"


                                                Test Framework



                                                In case anyone else finds it useful, here's a test framework for looking at individual matchups. Python2. Just put all the opponents you're interested in in opponents.py, and change the references to Ensemble to your own.



                                                import sys, inspect
                                                import opponents
                                                from ensemble import Ensemble

                                                def count_payoff(label, them):
                                                if None == them: return
                                                me = choices[label]
                                                payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }
                                                if label not in total_payoff: total_payoff[label] = 0
                                                total_payoff[label] += payoff[me][them]

                                                def update_hist(label, choice):
                                                choices[label] = choice

                                                opponents = [ x[1] for x
                                                in inspect.getmembers(sys.modules['opponents'], inspect.isclass)]

                                                for k in opponents:
                                                total_payoff = {}

                                                for j in range(0, 100):
                                                A = Ensemble()
                                                B = k()
                                                choices = {}

                                                aChoice = None
                                                bChoice = None
                                                for i in range(0, 100):
                                                count_payoff(A.__class__.__name__, bChoice)
                                                a = A.round(bChoice)
                                                update_hist(A.__class__.__name__, a)

                                                count_payoff(B.__class__.__name__, aChoice)
                                                b = B.round(aChoice)
                                                update_hist(B.__class__.__name__, b)

                                                aChoice = a
                                                bChoice = b
                                                print total_payoff





                                                share|improve this answer














                                                Ensemble



                                                This runs an ensemble of related models. The individual models consider different amounts of history, and have the option of either always choosing the move that will optimize the expected payout difference, or will randomly select a move in proportion to expected payout difference.



                                                Each member of the ensemble then votes on their preferred move. They get a number of votes equal to how much more they've won than the opponent (which means that terrible models will get negative votes). Whichever move wins the vote is then selected.



                                                (They should probably split their votes among the moves in proportion to how much they favor each, but I don't care enough to do that right now.)



                                                It beats everything posted so far except EvaluaterBot and PatternFinder. (One-on-one, it beats EvaluaterBot and loses to PatternFinder).



                                                from collections import defaultdict
                                                import random
                                                class Number6:
                                                class Choices:
                                                def __init__(self, C = 0, N = 0, D = 0):
                                                self.C = C
                                                self.N = N
                                                self.D = D

                                                def __init__(self, strategy = "maxExpected", markov_order = 3):
                                                self.MARKOV_ORDER = markov_order;
                                                self.my_choices = ""
                                                self.opponent = defaultdict(lambda: self.Choices())
                                                self.choice = None # previous choice
                                                self.payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }
                                                self.total_payoff = 0

                                                # if random, will choose in proportion to payoff.
                                                # otherwise, will always choose argmax
                                                self.strategy = strategy
                                                # maxExpected: maximize expected relative payoff
                                                # random: like maxExpected, but it chooses in proportion to E[payoff]
                                                # argmax: always choose the option that is optimal for expected opponent choice

                                                def update_opponent_model(self, last):
                                                for i in range(0, self.MARKOV_ORDER):
                                                hist = self.my_choices[i:]
                                                self.opponent[hist].C += ("C" == last)
                                                self.opponent[hist].N += ("N" == last)
                                                self.opponent[hist].D += ("D" == last)

                                                def normalize(self, counts):
                                                sum = float(counts.C + counts.N + counts.D)
                                                if 0 == sum:
                                                return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)
                                                return self.Choices(
                                                counts.C / sum, counts.N / sum, counts.D / sum)

                                                def get_distribution(self):
                                                for i in range(0, self.MARKOV_ORDER):
                                                hist = self.my_choices[i:]
                                                #print "check hist = " + hist
                                                if hist in self.opponent:
                                                return self.normalize(self.opponent[hist])

                                                return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)

                                                def choose(self, dist):
                                                payoff = self.Choices()
                                                # We're interested in *beating the opponent*, not
                                                # maximizing our score, so we optimize the difference
                                                payoff.C = (3-3) * dist.C + (4-1) * dist.N + (0-5) * dist.D
                                                payoff.N = (1-4) * dist.C + (2-2) * dist.N + (3-2) * dist.D
                                                payoff.D = (5-0) * dist.C + (2-3) * dist.N + (1-1) * dist.D

                                                # D has slightly better payoff on uniform opponent,
                                                # so we select it on ties
                                                if self.strategy == "maxExpected":
                                                if payoff.C > payoff.N:
                                                return "C" if payoff.C > payoff.D else "D"
                                                return "N" if payoff.N > payoff.D else "D"
                                                elif self.strategy == "randomize":
                                                payoff = self.normalize(payoff)
                                                r = random.uniform(0.0, 1.0)
                                                if (r < payoff.C): return "C"
                                                return "N" if (r < payoff.N) else "D"
                                                elif self.strategy == "argMax":
                                                if dist.C > dist.N:
                                                return "D" if dist.C > dist.D else "N"
                                                return "C" if dist.N > dist.D else "N"

                                                assert(0) #, "I am not a number! I am a free man!")

                                                def update_history(self):
                                                self.my_choices += self.choice
                                                if len(self.my_choices) > self.MARKOV_ORDER:
                                                assert(len(self.my_choices) == self.MARKOV_ORDER + 1)
                                                self.my_choices = self.my_choices[1:]

                                                def round(self, last):
                                                if last: self.update_opponent_model(last)

                                                dist = self.get_distribution()
                                                self.choice = self.choose(dist)
                                                self.update_history()
                                                return self.choice

                                                class Ensemble:
                                                def __init__(self):
                                                self.models =
                                                self.votes =
                                                self.prev_choice =
                                                for order in range(0, 6):
                                                self.models.append(Number6("maxExpected", order))
                                                self.models.append(Number6("randomize", order))
                                                #self.models.append(Number6("argMax", order))
                                                for i in range(0, len(self.models)):
                                                self.votes.append(0)
                                                self.prev_choice.append("D")

                                                self.payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }

                                                def round(self, last):
                                                if last:
                                                for i in range(0, len(self.models)):
                                                self.votes[i] += self.payoff[self.prev_choice[i]][last]

                                                # vote. Sufficiently terrible models get negative votes
                                                C = 0
                                                N = 0
                                                D = 0
                                                for i in range(0, len(self.models)):
                                                choice = self.models[i].round(last)
                                                if "C" == choice: C += self.votes[i]
                                                if "N" == choice: N += self.votes[i]
                                                if "D" == choice: D += self.votes[i]
                                                self.prev_choice[i] = choice

                                                if C > D and C > N: return "C"
                                                elif N > D: return "N"
                                                else: return "D"


                                                Test Framework



                                                In case anyone else finds it useful, here's a test framework for looking at individual matchups. Python2. Just put all the opponents you're interested in in opponents.py, and change the references to Ensemble to your own.



                                                import sys, inspect
                                                import opponents
                                                from ensemble import Ensemble

                                                def count_payoff(label, them):
                                                if None == them: return
                                                me = choices[label]
                                                payoff = {
                                                "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
                                                "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
                                                "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
                                                }
                                                if label not in total_payoff: total_payoff[label] = 0
                                                total_payoff[label] += payoff[me][them]

                                                def update_hist(label, choice):
                                                choices[label] = choice

                                                opponents = [ x[1] for x
                                                in inspect.getmembers(sys.modules['opponents'], inspect.isclass)]

                                                for k in opponents:
                                                total_payoff = {}

                                                for j in range(0, 100):
                                                A = Ensemble()
                                                B = k()
                                                choices = {}

                                                aChoice = None
                                                bChoice = None
                                                for i in range(0, 100):
                                                count_payoff(A.__class__.__name__, bChoice)
                                                a = A.round(bChoice)
                                                update_hist(A.__class__.__name__, a)

                                                count_payoff(B.__class__.__name__, aChoice)
                                                b = B.round(aChoice)
                                                update_hist(B.__class__.__name__, b)

                                                aChoice = a
                                                bChoice = b
                                                print total_payoff






                                                share|improve this answer














                                                share|improve this answer



                                                share|improve this answer








                                                edited yesterday

























                                                answered yesterday









                                                Ray

                                                1,438511




                                                1,438511












                                                • The controller is ready, you didn't have to do all that...
                                                  – Blacksilver
                                                  yesterday






                                                • 1




                                                  @Blacksilver I realized that just as I was about to submit. But this one works in versions before 3.6, and gives information about individual matchups that can help identify weak spots, so it wasn't a complete waste of time.
                                                  – Ray
                                                  yesterday










                                                • Fair enough; running now. I'll probably add options to my controller to do similar things.
                                                  – Blacksilver
                                                  yesterday










                                                • "It beats everything posted so far except Ensemble and PatternFinder" I'm honored :)
                                                  – Sparr
                                                  yesterday










                                                • @Sparr Oops. That was supposed to say EvaluaterBot and PatternFinder. But that's when comparing total score against the entire field. PatternFinder remains the only one that beats this in a direct match up.
                                                  – Ray
                                                  yesterday


















                                                • The controller is ready, you didn't have to do all that...
                                                  – Blacksilver
                                                  yesterday






                                                • 1




                                                  @Blacksilver I realized that just as I was about to submit. But this one works in versions before 3.6, and gives information about individual matchups that can help identify weak spots, so it wasn't a complete waste of time.
                                                  – Ray
                                                  yesterday










                                                • Fair enough; running now. I'll probably add options to my controller to do similar things.
                                                  – Blacksilver
                                                  yesterday










                                                • "It beats everything posted so far except Ensemble and PatternFinder" I'm honored :)
                                                  – Sparr
                                                  yesterday










                                                • @Sparr Oops. That was supposed to say EvaluaterBot and PatternFinder. But that's when comparing total score against the entire field. PatternFinder remains the only one that beats this in a direct match up.
                                                  – Ray
                                                  yesterday
















                                                The controller is ready, you didn't have to do all that...
                                                – Blacksilver
                                                yesterday




                                                The controller is ready, you didn't have to do all that...
                                                – Blacksilver
                                                yesterday




                                                1




                                                1




                                                @Blacksilver I realized that just as I was about to submit. But this one works in versions before 3.6, and gives information about individual matchups that can help identify weak spots, so it wasn't a complete waste of time.
                                                – Ray
                                                yesterday




                                                @Blacksilver I realized that just as I was about to submit. But this one works in versions before 3.6, and gives information about individual matchups that can help identify weak spots, so it wasn't a complete waste of time.
                                                – Ray
                                                yesterday












                                                Fair enough; running now. I'll probably add options to my controller to do similar things.
                                                – Blacksilver
                                                yesterday




                                                Fair enough; running now. I'll probably add options to my controller to do similar things.
                                                – Blacksilver
                                                yesterday












                                                "It beats everything posted so far except Ensemble and PatternFinder" I'm honored :)
                                                – Sparr
                                                yesterday




                                                "It beats everything posted so far except Ensemble and PatternFinder" I'm honored :)
                                                – Sparr
                                                yesterday












                                                @Sparr Oops. That was supposed to say EvaluaterBot and PatternFinder. But that's when comparing total score against the entire field. PatternFinder remains the only one that beats this in a direct match up.
                                                – Ray
                                                yesterday




                                                @Sparr Oops. That was supposed to say EvaluaterBot and PatternFinder. But that's when comparing total score against the entire field. PatternFinder remains the only one that beats this in a direct match up.
                                                – Ray
                                                yesterday










                                                up vote
                                                1
                                                down vote













                                                class HistoricAverage:
                                                PAYOFFS = {
                                                "C":{"C":3,"N":1,"D":5},
                                                "N":{"C":4,"N":2,"D":2},
                                                "D":{"C":0,"N":3,"D":1}}
                                                def __init__(self):
                                                self.history =
                                                def round(self, last):
                                                if(last != None):
                                                self.history.append(last)
                                                payoffsum = {"C":0, "N":0, "D":0}
                                                for move in self.history:
                                                for x in payoffsum:
                                                payoffsum[x] += HistoricAverage.PAYOFFS[move][x]
                                                return max(payoffsum, key=payoffsum.get)


                                                Looks at history and finds the action that would have been best on average. Starts cooperative.






                                                share|improve this answer





















                                                • This could run faster if it didn't re-calculate the averages every round.
                                                  – Sparr
                                                  18 hours ago















                                                up vote
                                                1
                                                down vote













                                                class HistoricAverage:
                                                PAYOFFS = {
                                                "C":{"C":3,"N":1,"D":5},
                                                "N":{"C":4,"N":2,"D":2},
                                                "D":{"C":0,"N":3,"D":1}}
                                                def __init__(self):
                                                self.history =
                                                def round(self, last):
                                                if(last != None):
                                                self.history.append(last)
                                                payoffsum = {"C":0, "N":0, "D":0}
                                                for move in self.history:
                                                for x in payoffsum:
                                                payoffsum[x] += HistoricAverage.PAYOFFS[move][x]
                                                return max(payoffsum, key=payoffsum.get)


                                                Looks at history and finds the action that would have been best on average. Starts cooperative.






                                                share|improve this answer





















                                                • This could run faster if it didn't re-calculate the averages every round.
                                                  – Sparr
                                                  18 hours ago













                                                up vote
                                                1
                                                down vote










                                                up vote
                                                1
                                                down vote









                                                class HistoricAverage:
                                                PAYOFFS = {
                                                "C":{"C":3,"N":1,"D":5},
                                                "N":{"C":4,"N":2,"D":2},
                                                "D":{"C":0,"N":3,"D":1}}
                                                def __init__(self):
                                                self.history =
                                                def round(self, last):
                                                if(last != None):
                                                self.history.append(last)
                                                payoffsum = {"C":0, "N":0, "D":0}
                                                for move in self.history:
                                                for x in payoffsum:
                                                payoffsum[x] += HistoricAverage.PAYOFFS[move][x]
                                                return max(payoffsum, key=payoffsum.get)


                                                Looks at history and finds the action that would have been best on average. Starts cooperative.






                                                share|improve this answer












                                                class HistoricAverage:
                                                PAYOFFS = {
                                                "C":{"C":3,"N":1,"D":5},
                                                "N":{"C":4,"N":2,"D":2},
                                                "D":{"C":0,"N":3,"D":1}}
                                                def __init__(self):
                                                self.history =
                                                def round(self, last):
                                                if(last != None):
                                                self.history.append(last)
                                                payoffsum = {"C":0, "N":0, "D":0}
                                                for move in self.history:
                                                for x in payoffsum:
                                                payoffsum[x] += HistoricAverage.PAYOFFS[move][x]
                                                return max(payoffsum, key=payoffsum.get)


                                                Looks at history and finds the action that would have been best on average. Starts cooperative.







                                                share|improve this answer












                                                share|improve this answer



                                                share|improve this answer










                                                answered yesterday









                                                MegaTom

                                                3,4421324




                                                3,4421324












                                                • This could run faster if it didn't re-calculate the averages every round.
                                                  – Sparr
                                                  18 hours ago


















                                                • This could run faster if it didn't re-calculate the averages every round.
                                                  – Sparr
                                                  18 hours ago
















                                                This could run faster if it didn't re-calculate the averages every round.
                                                – Sparr
                                                18 hours ago




                                                This could run faster if it didn't re-calculate the averages every round.
                                                – Sparr
                                                18 hours ago










                                                up vote
                                                1
                                                down vote













                                                Weighted Average



                                                class WeightedAverageBot:
                                                def __init__(self):
                                                self.C_bias = 1/4
                                                self.N = self.C_bias
                                                self.D = self.C_bias
                                                self.prev_weight = 1/2
                                                def round(self, last):
                                                if last:
                                                if last == "C" or last == "N":
                                                self.D *= self.prev_weight
                                                if last == "C" or last == "D":
                                                self.N *= self.prev_weight
                                                if last == "N":
                                                self.N = 1 - ((1 - self.N) * self.prev_weight)
                                                if last == "D":
                                                self.D = 1 - ((1 - self.D) * self.prev_weight)
                                                if self.N <= self.C_bias and self.D <= self.C_bias:
                                                return "D"
                                                if self.N > self.D:
                                                return "C"
                                                return "N"


                                                The opponent's behavior is modeled as a right triangle with corners for C N D at 0,0 0,1 1,0 respectively. Each opponent move shifts the point within that triangle toward that corner, and we play to beat the move indicated by the point (with C being given an adjustably small slice of the triangle). In theory I wanted this to have a longer memory with more weight to previous moves, but in practice the current meta favors bots that change quickly, so this devolves into an approximation of LastOptimalBot against most enemies. Posting for posterity; maybe someone will be inspired.






                                                share|improve this answer

























                                                  up vote
                                                  1
                                                  down vote













                                                  Weighted Average



                                                  class WeightedAverageBot:
                                                  def __init__(self):
                                                  self.C_bias = 1/4
                                                  self.N = self.C_bias
                                                  self.D = self.C_bias
                                                  self.prev_weight = 1/2
                                                  def round(self, last):
                                                  if last:
                                                  if last == "C" or last == "N":
                                                  self.D *= self.prev_weight
                                                  if last == "C" or last == "D":
                                                  self.N *= self.prev_weight
                                                  if last == "N":
                                                  self.N = 1 - ((1 - self.N) * self.prev_weight)
                                                  if last == "D":
                                                  self.D = 1 - ((1 - self.D) * self.prev_weight)
                                                  if self.N <= self.C_bias and self.D <= self.C_bias:
                                                  return "D"
                                                  if self.N > self.D:
                                                  return "C"
                                                  return "N"


                                                  The opponent's behavior is modeled as a right triangle with corners for C N D at 0,0 0,1 1,0 respectively. Each opponent move shifts the point within that triangle toward that corner, and we play to beat the move indicated by the point (with C being given an adjustably small slice of the triangle). In theory I wanted this to have a longer memory with more weight to previous moves, but in practice the current meta favors bots that change quickly, so this devolves into an approximation of LastOptimalBot against most enemies. Posting for posterity; maybe someone will be inspired.






                                                  share|improve this answer























                                                    up vote
                                                    1
                                                    down vote










                                                    up vote
                                                    1
                                                    down vote









                                                    Weighted Average



                                                    class WeightedAverageBot:
                                                    def __init__(self):
                                                    self.C_bias = 1/4
                                                    self.N = self.C_bias
                                                    self.D = self.C_bias
                                                    self.prev_weight = 1/2
                                                    def round(self, last):
                                                    if last:
                                                    if last == "C" or last == "N":
                                                    self.D *= self.prev_weight
                                                    if last == "C" or last == "D":
                                                    self.N *= self.prev_weight
                                                    if last == "N":
                                                    self.N = 1 - ((1 - self.N) * self.prev_weight)
                                                    if last == "D":
                                                    self.D = 1 - ((1 - self.D) * self.prev_weight)
                                                    if self.N <= self.C_bias and self.D <= self.C_bias:
                                                    return "D"
                                                    if self.N > self.D:
                                                    return "C"
                                                    return "N"


                                                    The opponent's behavior is modeled as a right triangle with corners for C N D at 0,0 0,1 1,0 respectively. Each opponent move shifts the point within that triangle toward that corner, and we play to beat the move indicated by the point (with C being given an adjustably small slice of the triangle). In theory I wanted this to have a longer memory with more weight to previous moves, but in practice the current meta favors bots that change quickly, so this devolves into an approximation of LastOptimalBot against most enemies. Posting for posterity; maybe someone will be inspired.






                                                    share|improve this answer












                                                    Weighted Average



                                                    class WeightedAverageBot:
                                                    def __init__(self):
                                                    self.C_bias = 1/4
                                                    self.N = self.C_bias
                                                    self.D = self.C_bias
                                                    self.prev_weight = 1/2
                                                    def round(self, last):
                                                    if last:
                                                    if last == "C" or last == "N":
                                                    self.D *= self.prev_weight
                                                    if last == "C" or last == "D":
                                                    self.N *= self.prev_weight
                                                    if last == "N":
                                                    self.N = 1 - ((1 - self.N) * self.prev_weight)
                                                    if last == "D":
                                                    self.D = 1 - ((1 - self.D) * self.prev_weight)
                                                    if self.N <= self.C_bias and self.D <= self.C_bias:
                                                    return "D"
                                                    if self.N > self.D:
                                                    return "C"
                                                    return "N"


                                                    The opponent's behavior is modeled as a right triangle with corners for C N D at 0,0 0,1 1,0 respectively. Each opponent move shifts the point within that triangle toward that corner, and we play to beat the move indicated by the point (with C being given an adjustably small slice of the triangle). In theory I wanted this to have a longer memory with more weight to previous moves, but in practice the current meta favors bots that change quickly, so this devolves into an approximation of LastOptimalBot against most enemies. Posting for posterity; maybe someone will be inspired.







                                                    share|improve this answer












                                                    share|improve this answer



                                                    share|improve this answer










                                                    answered yesterday









                                                    Sparr

                                                    5,0081633




                                                    5,0081633






















                                                        up vote
                                                        1
                                                        down vote













                                                        Tetragram



                                                        import itertools

                                                        class Tetragram:
                                                        def __init__(self):
                                                        self.history = {x: ['C'] for x in itertools.product('CND', repeat=4)}
                                                        self.theirs =
                                                        self.previous = None

                                                        def round(self, last):
                                                        if self.previous is not None and len(self.previous) == 4:
                                                        self.history[self.previous].append(last)
                                                        if last is not None:
                                                        self.theirs = (self.theirs + [last])[-3:]

                                                        if self.previous is not None and len(self.previous) == 4:
                                                        expected = random.choice(self.history[self.previous])
                                                        if expected == 'C':
                                                        choice = 'C'
                                                        elif expected == 'N':
                                                        choice = 'C'
                                                        else:
                                                        choice = 'N'
                                                        else:
                                                        choice = 'C'

                                                        self.previous = tuple(self.theirs + [choice])
                                                        return choice


                                                        Try to find a pattern in the opponent's moves, assuming they're also watching our last move.






                                                        share|improve this answer

























                                                          up vote
                                                          1
                                                          down vote













                                                          Tetragram



                                                          import itertools

                                                          class Tetragram:
                                                          def __init__(self):
                                                          self.history = {x: ['C'] for x in itertools.product('CND', repeat=4)}
                                                          self.theirs =
                                                          self.previous = None

                                                          def round(self, last):
                                                          if self.previous is not None and len(self.previous) == 4:
                                                          self.history[self.previous].append(last)
                                                          if last is not None:
                                                          self.theirs = (self.theirs + [last])[-3:]

                                                          if self.previous is not None and len(self.previous) == 4:
                                                          expected = random.choice(self.history[self.previous])
                                                          if expected == 'C':
                                                          choice = 'C'
                                                          elif expected == 'N':
                                                          choice = 'C'
                                                          else:
                                                          choice = 'N'
                                                          else:
                                                          choice = 'C'

                                                          self.previous = tuple(self.theirs + [choice])
                                                          return choice


                                                          Try to find a pattern in the opponent's moves, assuming they're also watching our last move.






                                                          share|improve this answer























                                                            up vote
                                                            1
                                                            down vote










                                                            up vote
                                                            1
                                                            down vote









                                                            Tetragram



                                                            import itertools

                                                            class Tetragram:
                                                            def __init__(self):
                                                            self.history = {x: ['C'] for x in itertools.product('CND', repeat=4)}
                                                            self.theirs =
                                                            self.previous = None

                                                            def round(self, last):
                                                            if self.previous is not None and len(self.previous) == 4:
                                                            self.history[self.previous].append(last)
                                                            if last is not None:
                                                            self.theirs = (self.theirs + [last])[-3:]

                                                            if self.previous is not None and len(self.previous) == 4:
                                                            expected = random.choice(self.history[self.previous])
                                                            if expected == 'C':
                                                            choice = 'C'
                                                            elif expected == 'N':
                                                            choice = 'C'
                                                            else:
                                                            choice = 'N'
                                                            else:
                                                            choice = 'C'

                                                            self.previous = tuple(self.theirs + [choice])
                                                            return choice


                                                            Try to find a pattern in the opponent's moves, assuming they're also watching our last move.






                                                            share|improve this answer












                                                            Tetragram



                                                            import itertools

                                                            class Tetragram:
                                                            def __init__(self):
                                                            self.history = {x: ['C'] for x in itertools.product('CND', repeat=4)}
                                                            self.theirs =
                                                            self.previous = None

                                                            def round(self, last):
                                                            if self.previous is not None and len(self.previous) == 4:
                                                            self.history[self.previous].append(last)
                                                            if last is not None:
                                                            self.theirs = (self.theirs + [last])[-3:]

                                                            if self.previous is not None and len(self.previous) == 4:
                                                            expected = random.choice(self.history[self.previous])
                                                            if expected == 'C':
                                                            choice = 'C'
                                                            elif expected == 'N':
                                                            choice = 'C'
                                                            else:
                                                            choice = 'N'
                                                            else:
                                                            choice = 'C'

                                                            self.previous = tuple(self.theirs + [choice])
                                                            return choice


                                                            Try to find a pattern in the opponent's moves, assuming they're also watching our last move.







                                                            share|improve this answer












                                                            share|improve this answer



                                                            share|improve this answer










                                                            answered 19 hours ago









                                                            Mnemonic

                                                            4,6351630




                                                            4,6351630






















                                                                up vote
                                                                1
                                                                down vote













                                                                Handshake



                                                                class HandshakeBot:
                                                                def __init__(self):
                                                                self.handshake_length = 4
                                                                self.handshake = ["N","N","C","D"]
                                                                while len(self.handshake) < self.handshake_length:
                                                                self.handshake *= 2
                                                                self.handshake = self.handshake[:self.handshake_length]
                                                                self.opp_hand =
                                                                self.friendly = None
                                                                def round(self, last):
                                                                if last:
                                                                if self.friendly == None:
                                                                # still trying to handshake
                                                                self.opp_hand.append(last)
                                                                if self.opp_hand[-1] != self.handshake[len(self.opp_hand)-1]:
                                                                self.friendly = False
                                                                return "D"
                                                                if len(self.opp_hand) == len(self.handshake):
                                                                self.friendly = True
                                                                return "C"
                                                                return self.handshake[len(self.opp_hand)]
                                                                elif self.friendly == True:
                                                                # successful handshake and continued cooperation
                                                                if last == "C":
                                                                return "C"
                                                                self.friendly = False
                                                                return "D"
                                                                else:
                                                                # failed handshake or abandoned cooperation
                                                                return "N" if last == "D" else ("D" if last == "C" else "C")
                                                                return self.handshake[0]


                                                                Recognizes when it's playing against itself, then cooperates. Otherwise mimics LastOptimalBot which seems like the best one-line strategy. Performs worse than LastOptimalBot, by an amount inversely proportional to the number of rounds. Obviously would do better if there were more copies of it in the field *cough**wink*.






                                                                share|improve this answer





















                                                                • Just submit a few clones that have different non-handshake behavior.
                                                                  – Blacksilver
                                                                  15 hours ago










                                                                • That seems exploit-y. I could submit one such clone for every simple behavior represented here.
                                                                  – Sparr
                                                                  14 hours ago










                                                                • I've added an extra clause saying that you can only submit max five bots.
                                                                  – Blacksilver
                                                                  12 hours ago















                                                                up vote
                                                                1
                                                                down vote













                                                                Handshake



                                                                class HandshakeBot:
                                                                def __init__(self):
                                                                self.handshake_length = 4
                                                                self.handshake = ["N","N","C","D"]
                                                                while len(self.handshake) < self.handshake_length:
                                                                self.handshake *= 2
                                                                self.handshake = self.handshake[:self.handshake_length]
                                                                self.opp_hand =
                                                                self.friendly = None
                                                                def round(self, last):
                                                                if last:
                                                                if self.friendly == None:
                                                                # still trying to handshake
                                                                self.opp_hand.append(last)
                                                                if self.opp_hand[-1] != self.handshake[len(self.opp_hand)-1]:
                                                                self.friendly = False
                                                                return "D"
                                                                if len(self.opp_hand) == len(self.handshake):
                                                                self.friendly = True
                                                                return "C"
                                                                return self.handshake[len(self.opp_hand)]
                                                                elif self.friendly == True:
                                                                # successful handshake and continued cooperation
                                                                if last == "C":
                                                                return "C"
                                                                self.friendly = False
                                                                return "D"
                                                                else:
                                                                # failed handshake or abandoned cooperation
                                                                return "N" if last == "D" else ("D" if last == "C" else "C")
                                                                return self.handshake[0]


                                                                Recognizes when it's playing against itself, then cooperates. Otherwise mimics LastOptimalBot which seems like the best one-line strategy. Performs worse than LastOptimalBot, by an amount inversely proportional to the number of rounds. Obviously would do better if there were more copies of it in the field *cough**wink*.






                                                                share|improve this answer





















                                                                • Just submit a few clones that have different non-handshake behavior.
                                                                  – Blacksilver
                                                                  15 hours ago










                                                                • That seems exploit-y. I could submit one such clone for every simple behavior represented here.
                                                                  – Sparr
                                                                  14 hours ago










                                                                • I've added an extra clause saying that you can only submit max five bots.
                                                                  – Blacksilver
                                                                  12 hours ago













                                                                up vote
                                                                1
                                                                down vote










                                                                up vote
                                                                1
                                                                down vote









                                                                Handshake



                                                                class HandshakeBot:
                                                                def __init__(self):
                                                                self.handshake_length = 4
                                                                self.handshake = ["N","N","C","D"]
                                                                while len(self.handshake) < self.handshake_length:
                                                                self.handshake *= 2
                                                                self.handshake = self.handshake[:self.handshake_length]
                                                                self.opp_hand =
                                                                self.friendly = None
                                                                def round(self, last):
                                                                if last:
                                                                if self.friendly == None:
                                                                # still trying to handshake
                                                                self.opp_hand.append(last)
                                                                if self.opp_hand[-1] != self.handshake[len(self.opp_hand)-1]:
                                                                self.friendly = False
                                                                return "D"
                                                                if len(self.opp_hand) == len(self.handshake):
                                                                self.friendly = True
                                                                return "C"
                                                                return self.handshake[len(self.opp_hand)]
                                                                elif self.friendly == True:
                                                                # successful handshake and continued cooperation
                                                                if last == "C":
                                                                return "C"
                                                                self.friendly = False
                                                                return "D"
                                                                else:
                                                                # failed handshake or abandoned cooperation
                                                                return "N" if last == "D" else ("D" if last == "C" else "C")
                                                                return self.handshake[0]


                                                                Recognizes when it's playing against itself, then cooperates. Otherwise mimics LastOptimalBot which seems like the best one-line strategy. Performs worse than LastOptimalBot, by an amount inversely proportional to the number of rounds. Obviously would do better if there were more copies of it in the field *cough**wink*.






                                                                share|improve this answer












                                                                Handshake



                                                                class HandshakeBot:
                                                                def __init__(self):
                                                                self.handshake_length = 4
                                                                self.handshake = ["N","N","C","D"]
                                                                while len(self.handshake) < self.handshake_length:
                                                                self.handshake *= 2
                                                                self.handshake = self.handshake[:self.handshake_length]
                                                                self.opp_hand =
                                                                self.friendly = None
                                                                def round(self, last):
                                                                if last:
                                                                if self.friendly == None:
                                                                # still trying to handshake
                                                                self.opp_hand.append(last)
                                                                if self.opp_hand[-1] != self.handshake[len(self.opp_hand)-1]:
                                                                self.friendly = False
                                                                return "D"
                                                                if len(self.opp_hand) == len(self.handshake):
                                                                self.friendly = True
                                                                return "C"
                                                                return self.handshake[len(self.opp_hand)]
                                                                elif self.friendly == True:
                                                                # successful handshake and continued cooperation
                                                                if last == "C":
                                                                return "C"
                                                                self.friendly = False
                                                                return "D"
                                                                else:
                                                                # failed handshake or abandoned cooperation
                                                                return "N" if last == "D" else ("D" if last == "C" else "C")
                                                                return self.handshake[0]


                                                                Recognizes when it's playing against itself, then cooperates. Otherwise mimics LastOptimalBot which seems like the best one-line strategy. Performs worse than LastOptimalBot, by an amount inversely proportional to the number of rounds. Obviously would do better if there were more copies of it in the field *cough**wink*.







                                                                share|improve this answer












                                                                share|improve this answer



                                                                share|improve this answer










                                                                answered 18 hours ago









                                                                Sparr

                                                                5,0081633




                                                                5,0081633












                                                                • Just submit a few clones that have different non-handshake behavior.
                                                                  – Blacksilver
                                                                  15 hours ago










                                                                • That seems exploit-y. I could submit one such clone for every simple behavior represented here.
                                                                  – Sparr
                                                                  14 hours ago










                                                                • I've added an extra clause saying that you can only submit max five bots.
                                                                  – Blacksilver
                                                                  12 hours ago


















                                                                • Just submit a few clones that have different non-handshake behavior.
                                                                  – Blacksilver
                                                                  15 hours ago










                                                                • That seems exploit-y. I could submit one such clone for every simple behavior represented here.
                                                                  – Sparr
                                                                  14 hours ago










                                                                • I've added an extra clause saying that you can only submit max five bots.
                                                                  – Blacksilver
                                                                  12 hours ago
















                                                                Just submit a few clones that have different non-handshake behavior.
                                                                – Blacksilver
                                                                15 hours ago




                                                                Just submit a few clones that have different non-handshake behavior.
                                                                – Blacksilver
                                                                15 hours ago












                                                                That seems exploit-y. I could submit one such clone for every simple behavior represented here.
                                                                – Sparr
                                                                14 hours ago




                                                                That seems exploit-y. I could submit one such clone for every simple behavior represented here.
                                                                – Sparr
                                                                14 hours ago












                                                                I've added an extra clause saying that you can only submit max five bots.
                                                                – Blacksilver
                                                                12 hours ago




                                                                I've added an extra clause saying that you can only submit max five bots.
                                                                – Blacksilver
                                                                12 hours ago










                                                                up vote
                                                                1
                                                                down vote













                                                                Dirichlet Dice



                                                                import random

                                                                class DirichletDice:
                                                                def __init__(self):

                                                                self.alpha = dict(
                                                                C = {'C' : 2, 'N' : 3, 'D' : 1},
                                                                N = {'C' : 1, 'N' : 2, 'D' : 3},
                                                                D = {'C' : 3, 'N' : 1, 'D' : 2}
                                                                )

                                                                self.Response = {'C' : 'D', 'N' : 'C', 'D' : 'N'}
                                                                self.myLast = [None, None]

                                                                #expected value of the dirichlet distribution given by Alpha
                                                                def MultinomialDraw(self, key):
                                                                alpha = list(self.alpha[key].values())
                                                                probs = [x / sum(alpha) for x in alpha]
                                                                outcome = random.choices(['C','N','D'], weights=probs)[0]
                                                                return outcome

                                                                def round(self, last):
                                                                if last is None:
                                                                self.myLast[0] = 'D'
                                                                return 'D'

                                                                #update dice corresponding to opponent's last response to my
                                                                #outcome two turns ago
                                                                if self.myLast[1] is not None:
                                                                self.alpha[self.myLast[1]][last] += 1

                                                                #predict opponent's move based on my last move
                                                                predict = self.MultinomialDraw(self.myLast[0])
                                                                res = self.Response[predict]

                                                                #update myLast
                                                                self.myLast[1] = self.myLast[0]
                                                                self.myLast[0] = res

                                                                return res


                                                                Basically I'm assuming that the opponent's response to my last output is a multinomial variable (weighted dice), one for each of my outputs, so there's a dice for "C", one for "N", and one for "D". So if my last roll was, for example, a "N" then I roll the "N-dice" to guess what their response would be to my "N". I begin with a Dirichlet prior that assumes that my opponent is somewhat "smart" (more likely to play the one with the best payoff against my last roll, least likely to play the one with the worst payoff). I generate the "expected" Multinomial distribution from the appropriate Dirichlet prior (this is the expected value of the probability distribution over their dice weights). I roll the weighted dice of my last output, and respond with the one with the best payoff against that dice outcome.



                                                                Starting in the third round, I do a Bayesian update of the appropriate Dirichlet prior of my opponent's last response to the thing I played two rounds ago. I'm trying to iteratively learn their dice weightings.



                                                                I could have also simply picked the response with the best "expected" outcome once generating the dice, instead of simply rolling the dice and responding to the outcome. However, I wanted to keep the randomness in, so that my bot is less vulnerable to the ones that try to predict a pattern.






                                                                share|improve this answer










                                                                New contributor




                                                                Bridgeburners is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                                                Check out our Code of Conduct.






















                                                                  up vote
                                                                  1
                                                                  down vote













                                                                  Dirichlet Dice



                                                                  import random

                                                                  class DirichletDice:
                                                                  def __init__(self):

                                                                  self.alpha = dict(
                                                                  C = {'C' : 2, 'N' : 3, 'D' : 1},
                                                                  N = {'C' : 1, 'N' : 2, 'D' : 3},
                                                                  D = {'C' : 3, 'N' : 1, 'D' : 2}
                                                                  )

                                                                  self.Response = {'C' : 'D', 'N' : 'C', 'D' : 'N'}
                                                                  self.myLast = [None, None]

                                                                  #expected value of the dirichlet distribution given by Alpha
                                                                  def MultinomialDraw(self, key):
                                                                  alpha = list(self.alpha[key].values())
                                                                  probs = [x / sum(alpha) for x in alpha]
                                                                  outcome = random.choices(['C','N','D'], weights=probs)[0]
                                                                  return outcome

                                                                  def round(self, last):
                                                                  if last is None:
                                                                  self.myLast[0] = 'D'
                                                                  return 'D'

                                                                  #update dice corresponding to opponent's last response to my
                                                                  #outcome two turns ago
                                                                  if self.myLast[1] is not None:
                                                                  self.alpha[self.myLast[1]][last] += 1

                                                                  #predict opponent's move based on my last move
                                                                  predict = self.MultinomialDraw(self.myLast[0])
                                                                  res = self.Response[predict]

                                                                  #update myLast
                                                                  self.myLast[1] = self.myLast[0]
                                                                  self.myLast[0] = res

                                                                  return res


                                                                  Basically I'm assuming that the opponent's response to my last output is a multinomial variable (weighted dice), one for each of my outputs, so there's a dice for "C", one for "N", and one for "D". So if my last roll was, for example, a "N" then I roll the "N-dice" to guess what their response would be to my "N". I begin with a Dirichlet prior that assumes that my opponent is somewhat "smart" (more likely to play the one with the best payoff against my last roll, least likely to play the one with the worst payoff). I generate the "expected" Multinomial distribution from the appropriate Dirichlet prior (this is the expected value of the probability distribution over their dice weights). I roll the weighted dice of my last output, and respond with the one with the best payoff against that dice outcome.



                                                                  Starting in the third round, I do a Bayesian update of the appropriate Dirichlet prior of my opponent's last response to the thing I played two rounds ago. I'm trying to iteratively learn their dice weightings.



                                                                  I could have also simply picked the response with the best "expected" outcome once generating the dice, instead of simply rolling the dice and responding to the outcome. However, I wanted to keep the randomness in, so that my bot is less vulnerable to the ones that try to predict a pattern.






                                                                  share|improve this answer










                                                                  New contributor




                                                                  Bridgeburners is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                                                  Check out our Code of Conduct.




















                                                                    up vote
                                                                    1
                                                                    down vote










                                                                    up vote
                                                                    1
                                                                    down vote









                                                                    Dirichlet Dice



                                                                    import random

                                                                    class DirichletDice:
                                                                    def __init__(self):

                                                                    self.alpha = dict(
                                                                    C = {'C' : 2, 'N' : 3, 'D' : 1},
                                                                    N = {'C' : 1, 'N' : 2, 'D' : 3},
                                                                    D = {'C' : 3, 'N' : 1, 'D' : 2}
                                                                    )

                                                                    self.Response = {'C' : 'D', 'N' : 'C', 'D' : 'N'}
                                                                    self.myLast = [None, None]

                                                                    #expected value of the dirichlet distribution given by Alpha
                                                                    def MultinomialDraw(self, key):
                                                                    alpha = list(self.alpha[key].values())
                                                                    probs = [x / sum(alpha) for x in alpha]
                                                                    outcome = random.choices(['C','N','D'], weights=probs)[0]
                                                                    return outcome

                                                                    def round(self, last):
                                                                    if last is None:
                                                                    self.myLast[0] = 'D'
                                                                    return 'D'

                                                                    #update dice corresponding to opponent's last response to my
                                                                    #outcome two turns ago
                                                                    if self.myLast[1] is not None:
                                                                    self.alpha[self.myLast[1]][last] += 1

                                                                    #predict opponent's move based on my last move
                                                                    predict = self.MultinomialDraw(self.myLast[0])
                                                                    res = self.Response[predict]

                                                                    #update myLast
                                                                    self.myLast[1] = self.myLast[0]
                                                                    self.myLast[0] = res

                                                                    return res


                                                                    Basically I'm assuming that the opponent's response to my last output is a multinomial variable (weighted dice), one for each of my outputs, so there's a dice for "C", one for "N", and one for "D". So if my last roll was, for example, a "N" then I roll the "N-dice" to guess what their response would be to my "N". I begin with a Dirichlet prior that assumes that my opponent is somewhat "smart" (more likely to play the one with the best payoff against my last roll, least likely to play the one with the worst payoff). I generate the "expected" Multinomial distribution from the appropriate Dirichlet prior (this is the expected value of the probability distribution over their dice weights). I roll the weighted dice of my last output, and respond with the one with the best payoff against that dice outcome.



                                                                    Starting in the third round, I do a Bayesian update of the appropriate Dirichlet prior of my opponent's last response to the thing I played two rounds ago. I'm trying to iteratively learn their dice weightings.



                                                                    I could have also simply picked the response with the best "expected" outcome once generating the dice, instead of simply rolling the dice and responding to the outcome. However, I wanted to keep the randomness in, so that my bot is less vulnerable to the ones that try to predict a pattern.






                                                                    share|improve this answer










                                                                    New contributor




                                                                    Bridgeburners is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                                                    Check out our Code of Conduct.









                                                                    Dirichlet Dice



                                                                    import random

                                                                    class DirichletDice:
                                                                    def __init__(self):

                                                                    self.alpha = dict(
                                                                    C = {'C' : 2, 'N' : 3, 'D' : 1},
                                                                    N = {'C' : 1, 'N' : 2, 'D' : 3},
                                                                    D = {'C' : 3, 'N' : 1, 'D' : 2}
                                                                    )

                                                                    self.Response = {'C' : 'D', 'N' : 'C', 'D' : 'N'}
                                                                    self.myLast = [None, None]

                                                                    #expected value of the dirichlet distribution given by Alpha
                                                                    def MultinomialDraw(self, key):
                                                                    alpha = list(self.alpha[key].values())
                                                                    probs = [x / sum(alpha) for x in alpha]
                                                                    outcome = random.choices(['C','N','D'], weights=probs)[0]
                                                                    return outcome

                                                                    def round(self, last):
                                                                    if last is None:
                                                                    self.myLast[0] = 'D'
                                                                    return 'D'

                                                                    #update dice corresponding to opponent's last response to my
                                                                    #outcome two turns ago
                                                                    if self.myLast[1] is not None:
                                                                    self.alpha[self.myLast[1]][last] += 1

                                                                    #predict opponent's move based on my last move
                                                                    predict = self.MultinomialDraw(self.myLast[0])
                                                                    res = self.Response[predict]

                                                                    #update myLast
                                                                    self.myLast[1] = self.myLast[0]
                                                                    self.myLast[0] = res

                                                                    return res


                                                                    Basically I'm assuming that the opponent's response to my last output is a multinomial variable (weighted dice), one for each of my outputs, so there's a dice for "C", one for "N", and one for "D". So if my last roll was, for example, a "N" then I roll the "N-dice" to guess what their response would be to my "N". I begin with a Dirichlet prior that assumes that my opponent is somewhat "smart" (more likely to play the one with the best payoff against my last roll, least likely to play the one with the worst payoff). I generate the "expected" Multinomial distribution from the appropriate Dirichlet prior (this is the expected value of the probability distribution over their dice weights). I roll the weighted dice of my last output, and respond with the one with the best payoff against that dice outcome.



                                                                    Starting in the third round, I do a Bayesian update of the appropriate Dirichlet prior of my opponent's last response to the thing I played two rounds ago. I'm trying to iteratively learn their dice weightings.



                                                                    I could have also simply picked the response with the best "expected" outcome once generating the dice, instead of simply rolling the dice and responding to the outcome. However, I wanted to keep the randomness in, so that my bot is less vulnerable to the ones that try to predict a pattern.







                                                                    share|improve this answer










                                                                    New contributor




                                                                    Bridgeburners is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                                                    Check out our Code of Conduct.









                                                                    share|improve this answer



                                                                    share|improve this answer








                                                                    edited 17 hours ago





















                                                                    New contributor




                                                                    Bridgeburners is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                                                    Check out our Code of Conduct.









                                                                    answered 19 hours ago









                                                                    Bridgeburners

                                                                    1112




                                                                    1112




                                                                    New contributor




                                                                    Bridgeburners is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                                                    Check out our Code of Conduct.





                                                                    New contributor





                                                                    Bridgeburners is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                                                    Check out our Code of Conduct.






                                                                    Bridgeburners is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                                                                    Check out our Code of Conduct.






























                                                                         

                                                                        draft saved


                                                                        draft discarded



















































                                                                         


                                                                        draft saved


                                                                        draft discarded














                                                                        StackExchange.ready(
                                                                        function () {
                                                                        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodegolf.stackexchange.com%2fquestions%2f175794%2fiterated-prisoners-trilemma%23new-answer', 'question_page');
                                                                        }
                                                                        );

                                                                        Post as a guest




















































































                                                                        Popular posts from this blog

                                                                        Plaza Victoria

                                                                        Brian Clough

                                                                        Cáceres