Iterated Prisoner's Trilemma











up vote
18
down vote

favorite
6












CHALLENGE STATUS: OPEN



Comment, open a PR, or otherwise yell at me 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-15 13:30 EST



27 bots, 729 games.

name | score/bot/round
----------------|----------------
PatternFinder | 3.196
EvaluaterBot | 3.021
DirichletDice | 2.851
Nash2 | 2.674
Ensemble | 2.667
FastGrudger | 2.615
LastOptimalBot | 2.592
Number6 | 2.582
WeightedAverage | 2.549
HandshakeBot | 2.540
HistoricAverage | 2.529
OldTitForTat | 2.456
Tetragram | 2.412
TitForTat | 2.291
Nash | 2.195
AllD | 2.106
CopyCat | 2.081
RandomBot | 2.049
Jade | 2.023
TatForTit | 1.762
NeverCOOP | 1.662
AllC | 1.639
AllN | 1.470









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
    Nov 12 at 17:52








  • 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
    Nov 12 at 17:54






  • 1




    Done. This was on the sandbox for like a month!
    – Blacksilver
    Nov 12 at 17:56








  • 2




    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
    2 days ago






  • 1




    Another version of this someday might pass the entire interaction history rather than just the last move. It doesn't change much although it simplifies user code slightly. But it would allow for extensions, such as noisy communication channels that clarify over time: "Really, a D, even though I've said C four times in a row? No, I didn't say D; what do you take me for? Oh, sorry, can we just forget that round?"
    – Scott Sauyet
    2 days ago















up vote
18
down vote

favorite
6












CHALLENGE STATUS: OPEN



Comment, open a PR, or otherwise yell at me 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-15 13:30 EST



27 bots, 729 games.

name | score/bot/round
----------------|----------------
PatternFinder | 3.196
EvaluaterBot | 3.021
DirichletDice | 2.851
Nash2 | 2.674
Ensemble | 2.667
FastGrudger | 2.615
LastOptimalBot | 2.592
Number6 | 2.582
WeightedAverage | 2.549
HandshakeBot | 2.540
HistoricAverage | 2.529
OldTitForTat | 2.456
Tetragram | 2.412
TitForTat | 2.291
Nash | 2.195
AllD | 2.106
CopyCat | 2.081
RandomBot | 2.049
Jade | 2.023
TatForTit | 1.762
NeverCOOP | 1.662
AllC | 1.639
AllN | 1.470









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
    Nov 12 at 17:52








  • 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
    Nov 12 at 17:54






  • 1




    Done. This was on the sandbox for like a month!
    – Blacksilver
    Nov 12 at 17:56








  • 2




    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
    2 days ago






  • 1




    Another version of this someday might pass the entire interaction history rather than just the last move. It doesn't change much although it simplifies user code slightly. But it would allow for extensions, such as noisy communication channels that clarify over time: "Really, a D, even though I've said C four times in a row? No, I didn't say D; what do you take me for? Oh, sorry, can we just forget that round?"
    – Scott Sauyet
    2 days ago













up vote
18
down vote

favorite
6









up vote
18
down vote

favorite
6






6





CHALLENGE STATUS: OPEN



Comment, open a PR, or otherwise yell at me 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-15 13:30 EST



27 bots, 729 games.

name | score/bot/round
----------------|----------------
PatternFinder | 3.196
EvaluaterBot | 3.021
DirichletDice | 2.851
Nash2 | 2.674
Ensemble | 2.667
FastGrudger | 2.615
LastOptimalBot | 2.592
Number6 | 2.582
WeightedAverage | 2.549
HandshakeBot | 2.540
HistoricAverage | 2.529
OldTitForTat | 2.456
Tetragram | 2.412
TitForTat | 2.291
Nash | 2.195
AllD | 2.106
CopyCat | 2.081
RandomBot | 2.049
Jade | 2.023
TatForTit | 1.762
NeverCOOP | 1.662
AllC | 1.639
AllN | 1.470









share|improve this question















CHALLENGE STATUS: OPEN



Comment, open a PR, or otherwise yell at me 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-15 13:30 EST



27 bots, 729 games.

name | score/bot/round
----------------|----------------
PatternFinder | 3.196
EvaluaterBot | 3.021
DirichletDice | 2.851
Nash2 | 2.674
Ensemble | 2.667
FastGrudger | 2.615
LastOptimalBot | 2.592
Number6 | 2.582
WeightedAverage | 2.549
HandshakeBot | 2.540
HistoricAverage | 2.529
OldTitForTat | 2.456
Tetragram | 2.412
TitForTat | 2.291
Nash | 2.195
AllD | 2.106
CopyCat | 2.081
RandomBot | 2.049
Jade | 2.023
TatForTit | 1.762
NeverCOOP | 1.662
AllC | 1.639
AllN | 1.470






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 Nov 12 at 17:43









Blacksilver

425314




425314








  • 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
    Nov 12 at 17:52








  • 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
    Nov 12 at 17:54






  • 1




    Done. This was on the sandbox for like a month!
    – Blacksilver
    Nov 12 at 17:56








  • 2




    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
    2 days ago






  • 1




    Another version of this someday might pass the entire interaction history rather than just the last move. It doesn't change much although it simplifies user code slightly. But it would allow for extensions, such as noisy communication channels that clarify over time: "Really, a D, even though I've said C four times in a row? No, I didn't say D; what do you take me for? Oh, sorry, can we just forget that round?"
    – Scott Sauyet
    2 days ago














  • 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
    Nov 12 at 17:52








  • 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
    Nov 12 at 17:54






  • 1




    Done. This was on the sandbox for like a month!
    – Blacksilver
    Nov 12 at 17:56








  • 2




    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
    2 days ago






  • 1




    Another version of this someday might pass the entire interaction history rather than just the last move. It doesn't change much although it simplifies user code slightly. But it would allow for extensions, such as noisy communication channels that clarify over time: "Really, a D, even though I've said C four times in a row? No, I didn't say D; what do you take me for? Oh, sorry, can we just forget that round?"
    – Scott Sauyet
    2 days ago








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
Nov 12 at 17:52






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
Nov 12 at 17:52






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
Nov 12 at 17:54




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
Nov 12 at 17:54




1




1




Done. This was on the sandbox for like a month!
– Blacksilver
Nov 12 at 17:56






Done. This was on the sandbox for like a month!
– Blacksilver
Nov 12 at 17:56






2




2




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
2 days ago




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
2 days ago




1




1




Another version of this someday might pass the entire interaction history rather than just the last move. It doesn't change much although it simplifies user code slightly. But it would allow for extensions, such as noisy communication channels that clarify over time: "Really, a D, even though I've said C four times in a row? No, I didn't say D; what do you take me for? Oh, sorry, can we just forget that round?"
– Scott Sauyet
2 days ago




Another version of this someday might pass the entire interaction history rather than just the last move. It doesn't change much although it simplifies user code slightly. But it would allow for extensions, such as noisy communication channels that clarify over time: "Really, a D, even though I've said C four times in a row? No, I didn't say D; what do you take me for? Oh, sorry, can we just forget that round?"
– Scott Sauyet
2 days ago










17 Answers
17






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
    Nov 12 at 19:43










  • Scratch that, PatternFinder beats it by a bit.
    – Blacksilver
    2 days ago


















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
      Nov 13 at 3:18












    • Thank you fixed it
      – Omer Faruk Yatkin
      2 days 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
      2 days 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
      2 days ago






    • 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
      2 days ago








    • 1




      Maybe using a highly composite number (i.e., 12) would score better?
      – Blacksilver
      2 days 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
          Nov 12 at 19:09






        • 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
          Nov 12 at 19:31










        • ahh, I see. None in "ND" errors.
          – Blacksilver
          Nov 12 at 19:38










        • Because if last and last in "ND": was too complicated?
          – immibis
          Nov 12 at 21:29


















        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
          Nov 12 at 18:11










        • 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
          Nov 12 at 18:30










        • return last would be a better T4T for this challenge, I think
          – Sparr
          Nov 12 at 18:43










        • Just tried -- the if(last): return last; else: return "C" is worse.
          – Blacksilver
          Nov 12 at 18:59










        • Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
          – Spitemaster
          Nov 12 at 19:00


















        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













          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
            2 days ago






          • 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
            2 days ago










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










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










          • @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
            2 days ago


















          up vote
          2
          down vote













          Improved Dirichlet Dice



          import random

          class DirichletDice2:
          def __init__(self):

          self.alpha = dict(
          C = {'C' : 1, 'N' : 1, 'D' : 1},
          N = {'C' : 1, 'N' : 1, 'D' : 1},
          D = {'C' : 1, 'N' : 1, 'D' : 1}
          )
          self.myLast = [None, None]
          self.payoff = dict(
          C = { "C": 0, "N": 3, "D": -5 },
          N = { "C": -3, "N": 0, "D": 1 },
          D = { "C": 5, "N": -1, "D": 0 }
          )

          def DirichletDraw(self, key):
          alpha = self.alpha[key].values()
          mu = [random.gammavariate(a,1) for a in alpha]
          mu = [m / sum(mu) for m in mu]
          return mu

          def ExpectedPayoff(self, probs):
          expectedPayoff = {}
          for val in ['C','N','D']:
          payoff = sum([p * v for p,v in zip(probs, self.payoff[val].values())])
          expectedPayoff[val] = payoff
          return expectedPayoff

          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

          #draw probs for my opponent's roll from Dirichlet distribution and then return the optimal response
          mu = self.DirichletDraw(self.myLast[0])
          expectedPayoff = self.ExpectedPayoff(mu)
          res = max(expectedPayoff, key=expectedPayoff.get)

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

          return res


          This is an improved version of Dirichlet Dice. Instead of taking the expected multinomial distribution from the Dirichlet distribution, it draws a Multinomial distribution randomly from the Dirichlet distribution. Then, instead of drawing from the Multinomial and giving the optimal response to that, it gives the optimal expected response to the given Multinomial using the points. So the randomness has essentially been shifted from the Multinomial draw to the Dirichlet draw. Also, the priors are more flat now, to encourage exploration.



          It's "improved" because it now accounts for the points system by giving the best expected value against the probabilities, while maintaining its randomness by drawing the probabilities themselves. Before, I tried simply doing the best expected payoff from the expected probabilities, but that did badly because it just got stuck, and didn't explore enough to update its dice. Also it was more predictable and exploitable.





          Original submission:



          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
            2
            down vote













            Kevin



            class Kevin:
            def round(self, last):
            return {"C":"N","N":"D","D":"C",None:"N"} [last]


            Picks the worst choice. The worst bot made.



            Useless



            import random

            class Useless:
            def __init__(self):
            self.lastLast = None

            def round(self, last):
            tempLastLast = self.lastLast
            self.lastLast = last

            if(last == "D" and tempLastLast == "N"):
            return "C"
            if(last == "D" and tempLastLast == "C"):
            return "N"

            if(last == "N" and tempLastLast == "D"):
            return "C"
            if(last == "N" and tempLastLast == "C"):
            return "D"

            if(last == "C" and tempLastLast == "D"):
            return "N"
            if(last == "C" and tempLastLast == "N"):
            return "D"

            return random.choice("CND")


            It looks at the last two moves done by the opponent and picks the most not done else it picks something random. There is probably a better way of doing this.






            share|improve this answer










            New contributor




            Link1J 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













              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
                    2 days ago










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










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


















                  up vote
                  1
                  down vote













                  ShiftingOptimalBot



                  class ShiftingOptimalBot:
                  def __init__(self):
                  # wins, draws, losses
                  self.history = [0,0,0]
                  self.lastMove = None
                  self.state = 0
                  def round(self, last):
                  if last == None:
                  self.lastMove = "C"
                  return self.lastMove
                  if last == self.lastMove:
                  self.history[1] += 1
                  elif (last == "C" and self.lastMove == "D") or (last == "D" and self.lastMove == "N") or (last == "N" and self.lastMove == "C"):
                  self.history[0] += 1
                  else:
                  self.history[2] += 1

                  if self.history[0] + 1 < self.history[2] or self.history[2] > 5:
                  self.state = (self.state + 1) % 3
                  self.history = [0,0,0]
                  if self.history[1] > self.history[0] + self.history[2] + 2:
                  self.state = (self.state + 2) % 3
                  self.history = [0,0,0]

                  if self.state == 0:
                  self.lastMove = "N" if last == "D" else ("D" if last == "C" else "C")
                  elif self.state == 1:
                  self.lastMove = last
                  else:
                  self.lastMove = "C" if last == "D" else ("N" if last == "C" else "D")
                  return self.lastMove


                  This bot uses LastOptimalBot's algorithm as long as it's winning. If the other bot starts predicting it, however, it will start playing whichever move its opponent played last (which is the move that beats the move that would beat LastOptimalBot). It cycles through simple transpositions of those algorithms as long as it continues to lose (or when it gets bored by drawing a lot).



                  Honestly, I'm surprised that LastOptimalBot is sitting in 5th as I post this. I'm fairly certain this will do better, assuming that I wrote this python correctly.






                  share|improve this answer






























                    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.payoffsum = {"C":0, "N":0, "D":0}
                    def round(this, last):
                    if(last != None):
                    for x in this.payoffsum:
                    this.payoffsum[x] += HistoricAverage.PAYOFFS[last][x]
                    return max(this.payoffsum, key=this.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
                      2 days ago










                    • @Sparr true. I edited it so it does now.
                      – MegaTom
                      yesterday











                    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















                    Required, but never shown

























                    17 Answers
                    17






                    active

                    oldest

                    votes








                    17 Answers
                    17






                    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
                      Nov 12 at 19:43










                    • Scratch that, PatternFinder beats it by a bit.
                      – Blacksilver
                      2 days ago















                    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
                      Nov 12 at 19:43










                    • Scratch that, PatternFinder beats it by a bit.
                      – Blacksilver
                      2 days ago













                    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 Nov 12 at 19:31

























                    answered Nov 12 at 19:24









                    Black Owl Kai

                    5217




                    5217












                    • Yep, beats everything.
                      – Blacksilver
                      Nov 12 at 19:43










                    • Scratch that, PatternFinder beats it by a bit.
                      – Blacksilver
                      2 days ago


















                    • Yep, beats everything.
                      – Blacksilver
                      Nov 12 at 19:43










                    • Scratch that, PatternFinder beats it by a bit.
                      – Blacksilver
                      2 days ago
















                    Yep, beats everything.
                    – Blacksilver
                    Nov 12 at 19:43




                    Yep, beats everything.
                    – Blacksilver
                    Nov 12 at 19:43












                    Scratch that, PatternFinder beats it by a bit.
                    – Blacksilver
                    2 days ago




                    Scratch that, PatternFinder beats it by a bit.
                    – Blacksilver
                    2 days ago










                    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 2 days ago

























                        answered Nov 12 at 18:04









                        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
                              Nov 13 at 3:18












                            • Thank you fixed it
                              – Omer Faruk Yatkin
                              2 days 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
                              2 days 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
                              Nov 13 at 3:18












                            • Thank you fixed it
                              – Omer Faruk Yatkin
                              2 days 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
                              2 days 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 2 days ago









                            Blacksilver

                            425314




                            425314






                            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 Nov 12 at 21:28









                            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
                              Nov 13 at 3:18












                            • Thank you fixed it
                              – Omer Faruk Yatkin
                              2 days 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
                              2 days ago














                            • 1




                              NashEquilibrium.round needs to take arguments even if it doesn't use them, so as to fit the expected function prototype.
                              – Ray
                              Nov 13 at 3:18












                            • Thank you fixed it
                              – Omer Faruk Yatkin
                              2 days 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
                              2 days 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
                            Nov 13 at 3:18






                            NashEquilibrium.round needs to take arguments even if it doesn't use them, so as to fit the expected function prototype.
                            – Ray
                            Nov 13 at 3:18














                            Thank you fixed it
                            – Omer Faruk Yatkin
                            2 days ago




                            Thank you fixed it
                            – Omer Faruk Yatkin
                            2 days 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
                            2 days 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
                            2 days 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
                              2 days ago






                            • 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
                              2 days ago








                            • 1




                              Maybe using a highly composite number (i.e., 12) would score better?
                              – Blacksilver
                              2 days 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
                              2 days ago






                            • 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
                              2 days ago








                            • 1




                              Maybe using a highly composite number (i.e., 12) would score better?
                              – Blacksilver
                              2 days 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 2 days ago

























                            answered Nov 12 at 23:56









                            Sparr

                            5,0081633




                            5,0081633












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






                            • 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
                              2 days ago








                            • 1




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


















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






                            • 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
                              2 days ago








                            • 1




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
















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




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




                            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
                            2 days ago






                            @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
                            2 days ago






                            1




                            1




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




                            Maybe using a highly composite number (i.e., 12) would score better?
                            – Blacksilver
                            2 days 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 Nov 12 at 20:56









                                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 Nov 12 at 21:53









                                        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
                                              Nov 12 at 19:09






                                            • 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
                                              Nov 12 at 19:31










                                            • ahh, I see. None in "ND" errors.
                                              – Blacksilver
                                              Nov 12 at 19:38










                                            • Because if last and last in "ND": was too complicated?
                                              – immibis
                                              Nov 12 at 21:29















                                            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
                                              Nov 12 at 19:09






                                            • 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
                                              Nov 12 at 19:31










                                            • ahh, I see. None in "ND" errors.
                                              – Blacksilver
                                              Nov 12 at 19:38










                                            • Because if last and last in "ND": was too complicated?
                                              – immibis
                                              Nov 12 at 21:29













                                            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 Nov 12 at 18:17









                                            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
                                              Nov 12 at 19:09






                                            • 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
                                              Nov 12 at 19:31










                                            • ahh, I see. None in "ND" errors.
                                              – Blacksilver
                                              Nov 12 at 19:38










                                            • Because if last and last in "ND": was too complicated?
                                              – immibis
                                              Nov 12 at 21:29


















                                            • What's the try/except for?
                                              – Blacksilver
                                              Nov 12 at 19:09






                                            • 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
                                              Nov 12 at 19:31










                                            • ahh, I see. None in "ND" errors.
                                              – Blacksilver
                                              Nov 12 at 19:38










                                            • Because if last and last in "ND": was too complicated?
                                              – immibis
                                              Nov 12 at 21:29
















                                            What's the try/except for?
                                            – Blacksilver
                                            Nov 12 at 19:09




                                            What's the try/except for?
                                            – Blacksilver
                                            Nov 12 at 19:09




                                            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
                                            Nov 12 at 19:31




                                            @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
                                            Nov 12 at 19:31












                                            ahh, I see. None in "ND" errors.
                                            – Blacksilver
                                            Nov 12 at 19:38




                                            ahh, I see. None in "ND" errors.
                                            – Blacksilver
                                            Nov 12 at 19:38












                                            Because if last and last in "ND": was too complicated?
                                            – immibis
                                            Nov 12 at 21:29




                                            Because if last and last in "ND": was too complicated?
                                            – immibis
                                            Nov 12 at 21:29










                                            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
                                              Nov 12 at 18:11










                                            • 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
                                              Nov 12 at 18:30










                                            • return last would be a better T4T for this challenge, I think
                                              – Sparr
                                              Nov 12 at 18:43










                                            • Just tried -- the if(last): return last; else: return "C" is worse.
                                              – Blacksilver
                                              Nov 12 at 18:59










                                            • Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                              – Spitemaster
                                              Nov 12 at 19:00















                                            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
                                              Nov 12 at 18:11










                                            • 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
                                              Nov 12 at 18:30










                                            • return last would be a better T4T for this challenge, I think
                                              – Sparr
                                              Nov 12 at 18:43










                                            • Just tried -- the if(last): return last; else: return "C" is worse.
                                              – Blacksilver
                                              Nov 12 at 18:59










                                            • Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                              – Spitemaster
                                              Nov 12 at 19:00













                                            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 Nov 12 at 22:43

























                                            answered Nov 12 at 18:05









                                            Spitemaster

                                            3315




                                            3315












                                            • oof. Maybe T4T would do better as return last.
                                              – Blacksilver
                                              Nov 12 at 18:11










                                            • 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
                                              Nov 12 at 18:30










                                            • return last would be a better T4T for this challenge, I think
                                              – Sparr
                                              Nov 12 at 18:43










                                            • Just tried -- the if(last): return last; else: return "C" is worse.
                                              – Blacksilver
                                              Nov 12 at 18:59










                                            • Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                              – Spitemaster
                                              Nov 12 at 19:00


















                                            • oof. Maybe T4T would do better as return last.
                                              – Blacksilver
                                              Nov 12 at 18:11










                                            • 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
                                              Nov 12 at 18:30










                                            • return last would be a better T4T for this challenge, I think
                                              – Sparr
                                              Nov 12 at 18:43










                                            • Just tried -- the if(last): return last; else: return "C" is worse.
                                              – Blacksilver
                                              Nov 12 at 18:59










                                            • Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                              – Spitemaster
                                              Nov 12 at 19:00
















                                            oof. Maybe T4T would do better as return last.
                                            – Blacksilver
                                            Nov 12 at 18:11




                                            oof. Maybe T4T would do better as return last.
                                            – Blacksilver
                                            Nov 12 at 18:11












                                            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
                                            Nov 12 at 18:30




                                            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
                                            Nov 12 at 18:30












                                            return last would be a better T4T for this challenge, I think
                                            – Sparr
                                            Nov 12 at 18:43




                                            return last would be a better T4T for this challenge, I think
                                            – Sparr
                                            Nov 12 at 18:43












                                            Just tried -- the if(last): return last; else: return "C" is worse.
                                            – Blacksilver
                                            Nov 12 at 18:59




                                            Just tried -- the if(last): return last; else: return "C" is worse.
                                            – Blacksilver
                                            Nov 12 at 18:59












                                            Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                            – Spitemaster
                                            Nov 12 at 19:00




                                            Right, but as @Sparr was saying, it might be more appropriate. Up to you, I suppose.
                                            – Spitemaster
                                            Nov 12 at 19:00










                                            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 2 days ago









                                                Blacksilver

                                                425314




                                                425314






                                                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 Nov 13 at 2:46









                                                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
                                                    3
                                                    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
                                                      2 days ago






                                                    • 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
                                                      2 days ago










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










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










                                                    • @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
                                                      2 days ago















                                                    up vote
                                                    3
                                                    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
                                                      2 days ago






                                                    • 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
                                                      2 days ago










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










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










                                                    • @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
                                                      2 days ago













                                                    up vote
                                                    3
                                                    down vote










                                                    up vote
                                                    3
                                                    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 2 days ago

























                                                    answered 2 days ago









                                                    Ray

                                                    1,448511




                                                    1,448511












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






                                                    • 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
                                                      2 days ago










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










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










                                                    • @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
                                                      2 days ago


















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






                                                    • 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
                                                      2 days ago










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










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










                                                    • @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
                                                      2 days ago
















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




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




                                                    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
                                                    2 days ago




                                                    @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
                                                    2 days ago












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




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












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




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












                                                    @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
                                                    2 days ago




                                                    @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
                                                    2 days ago










                                                    up vote
                                                    2
                                                    down vote













                                                    Improved Dirichlet Dice



                                                    import random

                                                    class DirichletDice2:
                                                    def __init__(self):

                                                    self.alpha = dict(
                                                    C = {'C' : 1, 'N' : 1, 'D' : 1},
                                                    N = {'C' : 1, 'N' : 1, 'D' : 1},
                                                    D = {'C' : 1, 'N' : 1, 'D' : 1}
                                                    )
                                                    self.myLast = [None, None]
                                                    self.payoff = dict(
                                                    C = { "C": 0, "N": 3, "D": -5 },
                                                    N = { "C": -3, "N": 0, "D": 1 },
                                                    D = { "C": 5, "N": -1, "D": 0 }
                                                    )

                                                    def DirichletDraw(self, key):
                                                    alpha = self.alpha[key].values()
                                                    mu = [random.gammavariate(a,1) for a in alpha]
                                                    mu = [m / sum(mu) for m in mu]
                                                    return mu

                                                    def ExpectedPayoff(self, probs):
                                                    expectedPayoff = {}
                                                    for val in ['C','N','D']:
                                                    payoff = sum([p * v for p,v in zip(probs, self.payoff[val].values())])
                                                    expectedPayoff[val] = payoff
                                                    return expectedPayoff

                                                    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

                                                    #draw probs for my opponent's roll from Dirichlet distribution and then return the optimal response
                                                    mu = self.DirichletDraw(self.myLast[0])
                                                    expectedPayoff = self.ExpectedPayoff(mu)
                                                    res = max(expectedPayoff, key=expectedPayoff.get)

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

                                                    return res


                                                    This is an improved version of Dirichlet Dice. Instead of taking the expected multinomial distribution from the Dirichlet distribution, it draws a Multinomial distribution randomly from the Dirichlet distribution. Then, instead of drawing from the Multinomial and giving the optimal response to that, it gives the optimal expected response to the given Multinomial using the points. So the randomness has essentially been shifted from the Multinomial draw to the Dirichlet draw. Also, the priors are more flat now, to encourage exploration.



                                                    It's "improved" because it now accounts for the points system by giving the best expected value against the probabilities, while maintaining its randomness by drawing the probabilities themselves. Before, I tried simply doing the best expected payoff from the expected probabilities, but that did badly because it just got stuck, and didn't explore enough to update its dice. Also it was more predictable and exploitable.





                                                    Original submission:



                                                    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
                                                      2
                                                      down vote













                                                      Improved Dirichlet Dice



                                                      import random

                                                      class DirichletDice2:
                                                      def __init__(self):

                                                      self.alpha = dict(
                                                      C = {'C' : 1, 'N' : 1, 'D' : 1},
                                                      N = {'C' : 1, 'N' : 1, 'D' : 1},
                                                      D = {'C' : 1, 'N' : 1, 'D' : 1}
                                                      )
                                                      self.myLast = [None, None]
                                                      self.payoff = dict(
                                                      C = { "C": 0, "N": 3, "D": -5 },
                                                      N = { "C": -3, "N": 0, "D": 1 },
                                                      D = { "C": 5, "N": -1, "D": 0 }
                                                      )

                                                      def DirichletDraw(self, key):
                                                      alpha = self.alpha[key].values()
                                                      mu = [random.gammavariate(a,1) for a in alpha]
                                                      mu = [m / sum(mu) for m in mu]
                                                      return mu

                                                      def ExpectedPayoff(self, probs):
                                                      expectedPayoff = {}
                                                      for val in ['C','N','D']:
                                                      payoff = sum([p * v for p,v in zip(probs, self.payoff[val].values())])
                                                      expectedPayoff[val] = payoff
                                                      return expectedPayoff

                                                      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

                                                      #draw probs for my opponent's roll from Dirichlet distribution and then return the optimal response
                                                      mu = self.DirichletDraw(self.myLast[0])
                                                      expectedPayoff = self.ExpectedPayoff(mu)
                                                      res = max(expectedPayoff, key=expectedPayoff.get)

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

                                                      return res


                                                      This is an improved version of Dirichlet Dice. Instead of taking the expected multinomial distribution from the Dirichlet distribution, it draws a Multinomial distribution randomly from the Dirichlet distribution. Then, instead of drawing from the Multinomial and giving the optimal response to that, it gives the optimal expected response to the given Multinomial using the points. So the randomness has essentially been shifted from the Multinomial draw to the Dirichlet draw. Also, the priors are more flat now, to encourage exploration.



                                                      It's "improved" because it now accounts for the points system by giving the best expected value against the probabilities, while maintaining its randomness by drawing the probabilities themselves. Before, I tried simply doing the best expected payoff from the expected probabilities, but that did badly because it just got stuck, and didn't explore enough to update its dice. Also it was more predictable and exploitable.





                                                      Original submission:



                                                      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
                                                        2
                                                        down vote










                                                        up vote
                                                        2
                                                        down vote









                                                        Improved Dirichlet Dice



                                                        import random

                                                        class DirichletDice2:
                                                        def __init__(self):

                                                        self.alpha = dict(
                                                        C = {'C' : 1, 'N' : 1, 'D' : 1},
                                                        N = {'C' : 1, 'N' : 1, 'D' : 1},
                                                        D = {'C' : 1, 'N' : 1, 'D' : 1}
                                                        )
                                                        self.myLast = [None, None]
                                                        self.payoff = dict(
                                                        C = { "C": 0, "N": 3, "D": -5 },
                                                        N = { "C": -3, "N": 0, "D": 1 },
                                                        D = { "C": 5, "N": -1, "D": 0 }
                                                        )

                                                        def DirichletDraw(self, key):
                                                        alpha = self.alpha[key].values()
                                                        mu = [random.gammavariate(a,1) for a in alpha]
                                                        mu = [m / sum(mu) for m in mu]
                                                        return mu

                                                        def ExpectedPayoff(self, probs):
                                                        expectedPayoff = {}
                                                        for val in ['C','N','D']:
                                                        payoff = sum([p * v for p,v in zip(probs, self.payoff[val].values())])
                                                        expectedPayoff[val] = payoff
                                                        return expectedPayoff

                                                        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

                                                        #draw probs for my opponent's roll from Dirichlet distribution and then return the optimal response
                                                        mu = self.DirichletDraw(self.myLast[0])
                                                        expectedPayoff = self.ExpectedPayoff(mu)
                                                        res = max(expectedPayoff, key=expectedPayoff.get)

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

                                                        return res


                                                        This is an improved version of Dirichlet Dice. Instead of taking the expected multinomial distribution from the Dirichlet distribution, it draws a Multinomial distribution randomly from the Dirichlet distribution. Then, instead of drawing from the Multinomial and giving the optimal response to that, it gives the optimal expected response to the given Multinomial using the points. So the randomness has essentially been shifted from the Multinomial draw to the Dirichlet draw. Also, the priors are more flat now, to encourage exploration.



                                                        It's "improved" because it now accounts for the points system by giving the best expected value against the probabilities, while maintaining its randomness by drawing the probabilities themselves. Before, I tried simply doing the best expected payoff from the expected probabilities, but that did badly because it just got stuck, and didn't explore enough to update its dice. Also it was more predictable and exploitable.





                                                        Original submission:



                                                        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.









                                                        Improved Dirichlet Dice



                                                        import random

                                                        class DirichletDice2:
                                                        def __init__(self):

                                                        self.alpha = dict(
                                                        C = {'C' : 1, 'N' : 1, 'D' : 1},
                                                        N = {'C' : 1, 'N' : 1, 'D' : 1},
                                                        D = {'C' : 1, 'N' : 1, 'D' : 1}
                                                        )
                                                        self.myLast = [None, None]
                                                        self.payoff = dict(
                                                        C = { "C": 0, "N": 3, "D": -5 },
                                                        N = { "C": -3, "N": 0, "D": 1 },
                                                        D = { "C": 5, "N": -1, "D": 0 }
                                                        )

                                                        def DirichletDraw(self, key):
                                                        alpha = self.alpha[key].values()
                                                        mu = [random.gammavariate(a,1) for a in alpha]
                                                        mu = [m / sum(mu) for m in mu]
                                                        return mu

                                                        def ExpectedPayoff(self, probs):
                                                        expectedPayoff = {}
                                                        for val in ['C','N','D']:
                                                        payoff = sum([p * v for p,v in zip(probs, self.payoff[val].values())])
                                                        expectedPayoff[val] = payoff
                                                        return expectedPayoff

                                                        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

                                                        #draw probs for my opponent's roll from Dirichlet distribution and then return the optimal response
                                                        mu = self.DirichletDraw(self.myLast[0])
                                                        expectedPayoff = self.ExpectedPayoff(mu)
                                                        res = max(expectedPayoff, key=expectedPayoff.get)

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

                                                        return res


                                                        This is an improved version of Dirichlet Dice. Instead of taking the expected multinomial distribution from the Dirichlet distribution, it draws a Multinomial distribution randomly from the Dirichlet distribution. Then, instead of drawing from the Multinomial and giving the optimal response to that, it gives the optimal expected response to the given Multinomial using the points. So the randomness has essentially been shifted from the Multinomial draw to the Dirichlet draw. Also, the priors are more flat now, to encourage exploration.



                                                        It's "improved" because it now accounts for the points system by giving the best expected value against the probabilities, while maintaining its randomness by drawing the probabilities themselves. Before, I tried simply doing the best expected payoff from the expected probabilities, but that did badly because it just got stuck, and didn't explore enough to update its dice. Also it was more predictable and exploitable.





                                                        Original submission:



                                                        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 yesterday





















                                                        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 2 days ago









                                                        Bridgeburners

                                                        1212




                                                        1212




                                                        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.






















                                                            up vote
                                                            2
                                                            down vote













                                                            Kevin



                                                            class Kevin:
                                                            def round(self, last):
                                                            return {"C":"N","N":"D","D":"C",None:"N"} [last]


                                                            Picks the worst choice. The worst bot made.



                                                            Useless



                                                            import random

                                                            class Useless:
                                                            def __init__(self):
                                                            self.lastLast = None

                                                            def round(self, last):
                                                            tempLastLast = self.lastLast
                                                            self.lastLast = last

                                                            if(last == "D" and tempLastLast == "N"):
                                                            return "C"
                                                            if(last == "D" and tempLastLast == "C"):
                                                            return "N"

                                                            if(last == "N" and tempLastLast == "D"):
                                                            return "C"
                                                            if(last == "N" and tempLastLast == "C"):
                                                            return "D"

                                                            if(last == "C" and tempLastLast == "D"):
                                                            return "N"
                                                            if(last == "C" and tempLastLast == "N"):
                                                            return "D"

                                                            return random.choice("CND")


                                                            It looks at the last two moves done by the opponent and picks the most not done else it picks something random. There is probably a better way of doing this.






                                                            share|improve this answer










                                                            New contributor




                                                            Link1J 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













                                                              Kevin



                                                              class Kevin:
                                                              def round(self, last):
                                                              return {"C":"N","N":"D","D":"C",None:"N"} [last]


                                                              Picks the worst choice. The worst bot made.



                                                              Useless



                                                              import random

                                                              class Useless:
                                                              def __init__(self):
                                                              self.lastLast = None

                                                              def round(self, last):
                                                              tempLastLast = self.lastLast
                                                              self.lastLast = last

                                                              if(last == "D" and tempLastLast == "N"):
                                                              return "C"
                                                              if(last == "D" and tempLastLast == "C"):
                                                              return "N"

                                                              if(last == "N" and tempLastLast == "D"):
                                                              return "C"
                                                              if(last == "N" and tempLastLast == "C"):
                                                              return "D"

                                                              if(last == "C" and tempLastLast == "D"):
                                                              return "N"
                                                              if(last == "C" and tempLastLast == "N"):
                                                              return "D"

                                                              return random.choice("CND")


                                                              It looks at the last two moves done by the opponent and picks the most not done else it picks something random. There is probably a better way of doing this.






                                                              share|improve this answer










                                                              New contributor




                                                              Link1J 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










                                                                up vote
                                                                2
                                                                down vote









                                                                Kevin



                                                                class Kevin:
                                                                def round(self, last):
                                                                return {"C":"N","N":"D","D":"C",None:"N"} [last]


                                                                Picks the worst choice. The worst bot made.



                                                                Useless



                                                                import random

                                                                class Useless:
                                                                def __init__(self):
                                                                self.lastLast = None

                                                                def round(self, last):
                                                                tempLastLast = self.lastLast
                                                                self.lastLast = last

                                                                if(last == "D" and tempLastLast == "N"):
                                                                return "C"
                                                                if(last == "D" and tempLastLast == "C"):
                                                                return "N"

                                                                if(last == "N" and tempLastLast == "D"):
                                                                return "C"
                                                                if(last == "N" and tempLastLast == "C"):
                                                                return "D"

                                                                if(last == "C" and tempLastLast == "D"):
                                                                return "N"
                                                                if(last == "C" and tempLastLast == "N"):
                                                                return "D"

                                                                return random.choice("CND")


                                                                It looks at the last two moves done by the opponent and picks the most not done else it picks something random. There is probably a better way of doing this.






                                                                share|improve this answer










                                                                New contributor




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









                                                                Kevin



                                                                class Kevin:
                                                                def round(self, last):
                                                                return {"C":"N","N":"D","D":"C",None:"N"} [last]


                                                                Picks the worst choice. The worst bot made.



                                                                Useless



                                                                import random

                                                                class Useless:
                                                                def __init__(self):
                                                                self.lastLast = None

                                                                def round(self, last):
                                                                tempLastLast = self.lastLast
                                                                self.lastLast = last

                                                                if(last == "D" and tempLastLast == "N"):
                                                                return "C"
                                                                if(last == "D" and tempLastLast == "C"):
                                                                return "N"

                                                                if(last == "N" and tempLastLast == "D"):
                                                                return "C"
                                                                if(last == "N" and tempLastLast == "C"):
                                                                return "D"

                                                                if(last == "C" and tempLastLast == "D"):
                                                                return "N"
                                                                if(last == "C" and tempLastLast == "N"):
                                                                return "D"

                                                                return random.choice("CND")


                                                                It looks at the last two moves done by the opponent and picks the most not done else it picks something random. There is probably a better way of doing this.







                                                                share|improve this answer










                                                                New contributor




                                                                Link1J 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





















                                                                New contributor




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









                                                                answered yesterday









                                                                Link1J

                                                                192




                                                                192




                                                                New contributor




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





                                                                New contributor





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






                                                                Link1J 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













                                                                    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 2 days ago









                                                                        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 2 days 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
                                                                                      2 days ago










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










                                                                                    • I've added an extra clause saying that you can only submit max five bots.
                                                                                      – Blacksilver
                                                                                      2 days 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
                                                                                      2 days ago










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










                                                                                    • I've added an extra clause saying that you can only submit max five bots.
                                                                                      – Blacksilver
                                                                                      2 days 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 2 days ago









                                                                                    Sparr

                                                                                    5,0081633




                                                                                    5,0081633












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










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










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


















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










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










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
















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




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












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




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












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




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










                                                                                    up vote
                                                                                    1
                                                                                    down vote













                                                                                    ShiftingOptimalBot



                                                                                    class ShiftingOptimalBot:
                                                                                    def __init__(self):
                                                                                    # wins, draws, losses
                                                                                    self.history = [0,0,0]
                                                                                    self.lastMove = None
                                                                                    self.state = 0
                                                                                    def round(self, last):
                                                                                    if last == None:
                                                                                    self.lastMove = "C"
                                                                                    return self.lastMove
                                                                                    if last == self.lastMove:
                                                                                    self.history[1] += 1
                                                                                    elif (last == "C" and self.lastMove == "D") or (last == "D" and self.lastMove == "N") or (last == "N" and self.lastMove == "C"):
                                                                                    self.history[0] += 1
                                                                                    else:
                                                                                    self.history[2] += 1

                                                                                    if self.history[0] + 1 < self.history[2] or self.history[2] > 5:
                                                                                    self.state = (self.state + 1) % 3
                                                                                    self.history = [0,0,0]
                                                                                    if self.history[1] > self.history[0] + self.history[2] + 2:
                                                                                    self.state = (self.state + 2) % 3
                                                                                    self.history = [0,0,0]

                                                                                    if self.state == 0:
                                                                                    self.lastMove = "N" if last == "D" else ("D" if last == "C" else "C")
                                                                                    elif self.state == 1:
                                                                                    self.lastMove = last
                                                                                    else:
                                                                                    self.lastMove = "C" if last == "D" else ("N" if last == "C" else "D")
                                                                                    return self.lastMove


                                                                                    This bot uses LastOptimalBot's algorithm as long as it's winning. If the other bot starts predicting it, however, it will start playing whichever move its opponent played last (which is the move that beats the move that would beat LastOptimalBot). It cycles through simple transpositions of those algorithms as long as it continues to lose (or when it gets bored by drawing a lot).



                                                                                    Honestly, I'm surprised that LastOptimalBot is sitting in 5th as I post this. I'm fairly certain this will do better, assuming that I wrote this python correctly.






                                                                                    share|improve this answer



























                                                                                      up vote
                                                                                      1
                                                                                      down vote













                                                                                      ShiftingOptimalBot



                                                                                      class ShiftingOptimalBot:
                                                                                      def __init__(self):
                                                                                      # wins, draws, losses
                                                                                      self.history = [0,0,0]
                                                                                      self.lastMove = None
                                                                                      self.state = 0
                                                                                      def round(self, last):
                                                                                      if last == None:
                                                                                      self.lastMove = "C"
                                                                                      return self.lastMove
                                                                                      if last == self.lastMove:
                                                                                      self.history[1] += 1
                                                                                      elif (last == "C" and self.lastMove == "D") or (last == "D" and self.lastMove == "N") or (last == "N" and self.lastMove == "C"):
                                                                                      self.history[0] += 1
                                                                                      else:
                                                                                      self.history[2] += 1

                                                                                      if self.history[0] + 1 < self.history[2] or self.history[2] > 5:
                                                                                      self.state = (self.state + 1) % 3
                                                                                      self.history = [0,0,0]
                                                                                      if self.history[1] > self.history[0] + self.history[2] + 2:
                                                                                      self.state = (self.state + 2) % 3
                                                                                      self.history = [0,0,0]

                                                                                      if self.state == 0:
                                                                                      self.lastMove = "N" if last == "D" else ("D" if last == "C" else "C")
                                                                                      elif self.state == 1:
                                                                                      self.lastMove = last
                                                                                      else:
                                                                                      self.lastMove = "C" if last == "D" else ("N" if last == "C" else "D")
                                                                                      return self.lastMove


                                                                                      This bot uses LastOptimalBot's algorithm as long as it's winning. If the other bot starts predicting it, however, it will start playing whichever move its opponent played last (which is the move that beats the move that would beat LastOptimalBot). It cycles through simple transpositions of those algorithms as long as it continues to lose (or when it gets bored by drawing a lot).



                                                                                      Honestly, I'm surprised that LastOptimalBot is sitting in 5th as I post this. I'm fairly certain this will do better, assuming that I wrote this python correctly.






                                                                                      share|improve this answer

























                                                                                        up vote
                                                                                        1
                                                                                        down vote










                                                                                        up vote
                                                                                        1
                                                                                        down vote









                                                                                        ShiftingOptimalBot



                                                                                        class ShiftingOptimalBot:
                                                                                        def __init__(self):
                                                                                        # wins, draws, losses
                                                                                        self.history = [0,0,0]
                                                                                        self.lastMove = None
                                                                                        self.state = 0
                                                                                        def round(self, last):
                                                                                        if last == None:
                                                                                        self.lastMove = "C"
                                                                                        return self.lastMove
                                                                                        if last == self.lastMove:
                                                                                        self.history[1] += 1
                                                                                        elif (last == "C" and self.lastMove == "D") or (last == "D" and self.lastMove == "N") or (last == "N" and self.lastMove == "C"):
                                                                                        self.history[0] += 1
                                                                                        else:
                                                                                        self.history[2] += 1

                                                                                        if self.history[0] + 1 < self.history[2] or self.history[2] > 5:
                                                                                        self.state = (self.state + 1) % 3
                                                                                        self.history = [0,0,0]
                                                                                        if self.history[1] > self.history[0] + self.history[2] + 2:
                                                                                        self.state = (self.state + 2) % 3
                                                                                        self.history = [0,0,0]

                                                                                        if self.state == 0:
                                                                                        self.lastMove = "N" if last == "D" else ("D" if last == "C" else "C")
                                                                                        elif self.state == 1:
                                                                                        self.lastMove = last
                                                                                        else:
                                                                                        self.lastMove = "C" if last == "D" else ("N" if last == "C" else "D")
                                                                                        return self.lastMove


                                                                                        This bot uses LastOptimalBot's algorithm as long as it's winning. If the other bot starts predicting it, however, it will start playing whichever move its opponent played last (which is the move that beats the move that would beat LastOptimalBot). It cycles through simple transpositions of those algorithms as long as it continues to lose (or when it gets bored by drawing a lot).



                                                                                        Honestly, I'm surprised that LastOptimalBot is sitting in 5th as I post this. I'm fairly certain this will do better, assuming that I wrote this python correctly.






                                                                                        share|improve this answer














                                                                                        ShiftingOptimalBot



                                                                                        class ShiftingOptimalBot:
                                                                                        def __init__(self):
                                                                                        # wins, draws, losses
                                                                                        self.history = [0,0,0]
                                                                                        self.lastMove = None
                                                                                        self.state = 0
                                                                                        def round(self, last):
                                                                                        if last == None:
                                                                                        self.lastMove = "C"
                                                                                        return self.lastMove
                                                                                        if last == self.lastMove:
                                                                                        self.history[1] += 1
                                                                                        elif (last == "C" and self.lastMove == "D") or (last == "D" and self.lastMove == "N") or (last == "N" and self.lastMove == "C"):
                                                                                        self.history[0] += 1
                                                                                        else:
                                                                                        self.history[2] += 1

                                                                                        if self.history[0] + 1 < self.history[2] or self.history[2] > 5:
                                                                                        self.state = (self.state + 1) % 3
                                                                                        self.history = [0,0,0]
                                                                                        if self.history[1] > self.history[0] + self.history[2] + 2:
                                                                                        self.state = (self.state + 2) % 3
                                                                                        self.history = [0,0,0]

                                                                                        if self.state == 0:
                                                                                        self.lastMove = "N" if last == "D" else ("D" if last == "C" else "C")
                                                                                        elif self.state == 1:
                                                                                        self.lastMove = last
                                                                                        else:
                                                                                        self.lastMove = "C" if last == "D" else ("N" if last == "C" else "D")
                                                                                        return self.lastMove


                                                                                        This bot uses LastOptimalBot's algorithm as long as it's winning. If the other bot starts predicting it, however, it will start playing whichever move its opponent played last (which is the move that beats the move that would beat LastOptimalBot). It cycles through simple transpositions of those algorithms as long as it continues to lose (or when it gets bored by drawing a lot).



                                                                                        Honestly, I'm surprised that LastOptimalBot is sitting in 5th as I post this. I'm fairly certain this will do better, assuming that I wrote this python correctly.







                                                                                        share|improve this answer














                                                                                        share|improve this answer



                                                                                        share|improve this answer








                                                                                        edited yesterday









                                                                                        Blacksilver

                                                                                        425314




                                                                                        425314










                                                                                        answered yesterday









                                                                                        Spitemaster

                                                                                        3315




                                                                                        3315






















                                                                                            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.payoffsum = {"C":0, "N":0, "D":0}
                                                                                            def round(this, last):
                                                                                            if(last != None):
                                                                                            for x in this.payoffsum:
                                                                                            this.payoffsum[x] += HistoricAverage.PAYOFFS[last][x]
                                                                                            return max(this.payoffsum, key=this.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
                                                                                              2 days ago










                                                                                            • @Sparr true. I edited it so it does now.
                                                                                              – MegaTom
                                                                                              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.payoffsum = {"C":0, "N":0, "D":0}
                                                                                            def round(this, last):
                                                                                            if(last != None):
                                                                                            for x in this.payoffsum:
                                                                                            this.payoffsum[x] += HistoricAverage.PAYOFFS[last][x]
                                                                                            return max(this.payoffsum, key=this.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
                                                                                              2 days ago










                                                                                            • @Sparr true. I edited it so it does now.
                                                                                              – MegaTom
                                                                                              yesterday













                                                                                            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.payoffsum = {"C":0, "N":0, "D":0}
                                                                                            def round(this, last):
                                                                                            if(last != None):
                                                                                            for x in this.payoffsum:
                                                                                            this.payoffsum[x] += HistoricAverage.PAYOFFS[last][x]
                                                                                            return max(this.payoffsum, key=this.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.payoffsum = {"C":0, "N":0, "D":0}
                                                                                            def round(this, last):
                                                                                            if(last != None):
                                                                                            for x in this.payoffsum:
                                                                                            this.payoffsum[x] += HistoricAverage.PAYOFFS[last][x]
                                                                                            return max(this.payoffsum, key=this.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








                                                                                            edited yesterday

























                                                                                            answered Nov 12 at 23:33









                                                                                            MegaTom

                                                                                            3,4421324




                                                                                            3,4421324












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










                                                                                            • @Sparr true. I edited it so it does now.
                                                                                              – MegaTom
                                                                                              yesterday


















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










                                                                                            • @Sparr true. I edited it so it does now.
                                                                                              – MegaTom
                                                                                              yesterday
















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




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












                                                                                            @Sparr true. I edited it so it does now.
                                                                                            – MegaTom
                                                                                            yesterday




                                                                                            @Sparr true. I edited it so it does now.
                                                                                            – MegaTom
                                                                                            yesterday


















                                                                                             

                                                                                            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















                                                                                            Required, but never shown





















































                                                                                            Required, but never shown














                                                                                            Required, but never shown












                                                                                            Required, but never shown







                                                                                            Required, but never shown

































                                                                                            Required, but never shown














                                                                                            Required, but never shown












                                                                                            Required, but never shown







                                                                                            Required, but never shown







                                                                                            Popular posts from this blog

                                                                                            Biblatex bibliography style without URLs when DOI exists (in Overleaf with Zotero bibliography)

                                                                                            ComboBox Display Member on multiple fields

                                                                                            Is it possible to collect Nectar points via Trainline?