17
\$\begingroup\$

A mad scientist has just created a new species of bacterium! He has decided to name it Noblus Gentlemanus, after observing its behaviour. However, his bacteria have ran out of food, and have declared war, as they are able to harvest other bacterium's corpses for enough food to create copies of themselves. There are a number of different subspecies of this bacterium, who have different strategies in playing Prisoner's Dilemma, their favourite game. There are five bacteria from each different subspecies. In Prisoner's Dilemma, each of the two players simultaneously chooses either defect or cooperate. If one player chooses cooperate and the other chooses default, the defaulter gets 2 points, and the cooperator loses 3 points. If both player choose cooperate, both players get one 1 point. If both players choose default, both players lose 1 point.

Being Noble Gentlemen, the bacteria have decided to fight this war by playing 200 round long games of Iterated Prisoner's Dilemma. The loser of each duel will commit suicide, allowing the victor to clone itself. In the event of a tie, both bacteria will remain alive, but neither will be able to clone itself. In addition, all bacteria from a match carry over 10% of their points to the next match. A clone carries over the points of the bacterium it was cloned by. Also, there is a one in ten chance each turn that one bacterium will mutate into another subspecies, with 0 bonus points (if I receive complaints about the randomness of this, I can remove it). After the bacteria have played a number of these duels equal to the number of subspecies of bacteria times ten, the mad scientist accidentally drops the petri dish the bacteria reside in, and all of the bacteria acquire new sources of food, ending their duels. This is different from an ordinary iterated prisoner's dilemma contest, as it involves 1v1 duels with carryover points, rather than simply attempting to get the most points overall. This makes a big difference in how effective a given strategy is.

Each bacterium will receive input at the beginning of its turn in the format: (turn number, current points, enemies points, your previous moves [in a string, using the character "c" for cooperate, and the character "d" for defect], enemies previous moves [in the same format]).

Here are four sample strategies that will be entered. I actually think that Defector might win, even though it is extremely simple.

Tit for Tat

def titfortatfunc(counter, mypoints, enpoints, mylist, enlist):
    if counter==0 or enlist[counter-1] == "c":
        return "c"
    else:
        return "d"

RandomPick

from random import choice
def randompickfunc(counter, mypoints, enpoints, mylist, enlist):
    if counter == 199:
        return "d"
    else:
        return choice(["d", "c"])

Cooperator

def cooperatorfunc(counter, mypoints, enpoints, mylist, enlist):
    return "c"

Defector

def defectorfunc(counter, mypoints, enpoints, mylist, enlist):
    return "d"

All submissions must be in the form of a Python 2.7 function, with the name being the name of the submission with no spaces, with func on the end. If someone would like to submit an answer in a different language, please enter it in pseudo-code, for me to convert to Python in an edit to your answer once I have time, or give me instructions on interfacing your language with my controller which is found below set up for all submissions as of June 4.

from titfortat import titfortatfunc
from randompick import randompickfunc
from cooperator import cooperatorfunc
from defector import defectorfunc
from luckytitfortat import luckytitfortatfunc
from randomtitfortat import randomtitfortatfunc
from remorsefulaggressor import remorsefulaggressorfunc
from everyother import everyotherfunc
from niceguy import niceguyfunc
from titfortatbackstab import titfortatbackstabfunc
from gentleDefector import gentleDefectorfunc
from anticapitalist import anticapitalistfunc
from grimtrigger import grimtriggerfunc
from bizzaro import bizzarofunc
from neoanticapitalist import neoanticapitalistfunc
from bittertat import bittertatfunc
from teamer import teamerfunc
from copyfirst import copyfirstfunc
from exploitivetat import exploitativetatfunc
from defectorv2 import defectorv2func
from crazytat import crazytatfunc
from randomchoicev2 import randomchoicev2func
from twotitsforatat import twotitsforatatfunc
from threetitsforatat import threetitsforatatfunc
from fourtitsforatat import fourtitsforatatfunc
from fivetitsforatat import fivetitsforatatfunc
from sixtitsforatat import sixtitsforatatfunc
from tentitsforatat import tentitsforatatfunc
from theelephant import theelephantfunc
from xbittertat import xbittertatfunc
from fifteentitsforatat import fifteentitsfortatfunc
from twentytitsforatat import twentytitsforatatfunc
from fox import foxfunc
from onehundredfortysixtitsforatat import onehundredfourtysixtitsforatatfunc
from gameofthrones import gameofthronesfunc
from boy import boyfunc
from grimace import grimacefunc
from fiftytitsforatat import fiftytitsfortatfunc
from soreloser import soreloserfunc
from everyotherd import everyotherdfunc
from fiftythreetitsfortat import fiftythreetitsfortatfunc
from twentyfivetitsfortat import twentyfivetitsfortatfunc
from handshake import handshakefunc
from anty import antyfunc
from fiftyfourtitsforatat import fiftyfourtitsfortatfunc
from kindatitsfortat import kindatitsfortatfunc

import random

players = 38

rounds = players*10

def runcode(num, points1, points2, history1, history2, cell):
    ans = ""
    if cell == 0:
        ans = titfortatfunc(num, points1, points2, history1, history2)
    elif cell == 1:
        ans = randompickfunc(num, points1, points2, history1, history2)
    elif cell == 2:
        ans = cooperatorfunc(num, points1, points2, history1, history2)
    elif cell == 3:
        ans = defectorfunc(num, points1, points2, history1, history2)
    elif cell == 4:
        ans = luckytitfortatfunc(num, points1, points2, history1, history2)
    elif cell == 5:
        ans = randomtitfortatfunc(num, points1, points2, history1, history2)
    elif cell == 6:
        ans = remorsefulaggressorfunc(num, points1, points2, history1, history2)
    elif cell == 7:
        ans = everyotherfunc(num, points1, points2, history1, history2)
    elif cell == 8:
        ans = niceguyfunc(num, points1, points2, history1, history2)
    elif cell == 9:
        ans = titfortatbackstabfunc(num, points1, points2, history1, history2)
    elif cell == 10:
        ans = gentleDefectorfunc(num, points1, points2, history1, history2)
    elif cell == 11:
        ans = anticapitalistfunc(num, points1, points2, history1, history2)
    elif cell == 12:
        ans = grimtriggerfunc(num, points1, points2, history1, history2)
    elif cell == 13:
        ans = bizzarofunc(num, points1, points2, history1, history2)
    elif cell == 14:
        ans = neoanticapitalistfunc(num, points1, points2, history1, history2)
    elif cell == 15:
        ans = tentitsforatatfunc(num, points1, points2, history1, history2)
    elif cell == 16:
        ans = bittertatfunc(num, points1, points2, history1, history2)
    elif cell == 17:
        ans = copyfirstfunc(num, points1, points2, history1, history2)
    elif cell == 18:
        ans = exploitativetatfunc(num, points1, points2, history1, history2)
    elif cell == 19:
        ans = sixtitsforatatfunc(num, points1, points2, history1, history2)
    elif cell == 20:
        ans = fifteentitsfortatfunc(num, points1, points2, history1, history2)
    elif cell == 21:
        ans = fivetitsforatatfunc(num, points1, points2, history1, history2)
    elif cell == 22:
        ans = twentytitsforatatfunc(num, points1, points2, history1, history2)
    elif cell == 23:
        ans = threetitsforatatfunc(num, points1, points2, history1, history2)
    elif cell == 24:
        ans = fiftyfourtitsfortatfunc(num, points1, points2, history1, history2)
    elif cell == 25:
        ans = theelephantfunc(num, points1, points2, history1, history2)
    elif cell == 26:
        ans = xbittertatfunc(num, points1, points2, history1, history2)
    elif cell == 27:
        ans = foxfunc(num, points1, points2, history1, history2)
    elif cell == 28:
        ans = gameofthronesfunc(num, points1, points2, history1, history2)
    elif cell == 29:
        ans = boyfunc(num, points1, points2, history1, history2)
    elif cell == 30:
        ans = grimacefunc(num, points1, points2, history1, history2)
    elif cell == 31:
        ans = soreloserfunc(num, points1, points2, history1, history2)
    elif cell == 32:
        ans = everyotherdfunc(num, points1, points2, history1, history2)
    elif cell == 33:
        ans = twentyfivetitsfortatfunc(num, points1, points2, history1, history2)
    elif cell == 34:
        ans = fiftythreetitsfortatfunc(num, points1, points2, history1, history2)
    elif cell == 35:
        ans = handshakefunc(num, points1, points2, history1, history2)
    elif cell == 36:
        ans = antyfunc(num, points1, points2, history1, history2)
    elif cell == 37:
        ans = kindatitsfortatfunc(num, points1, points2, history1, history2)


    return ans

def fight(l1,l2):
    num1,num2=l1[0],l2[0]
    points1,points2=l1[1],l2[1]
    history1 = ""
    history2 = ""

    for num in range(200):
        p1 = runcode(num, points1, points2, history1, history2, num1)
        p2 = runcode(num, points2, points1, history2, history1, num2)

        history1+=p1
        history2+=p2

        if p1 == "c" and p2 == "c":
            points1 += 1
            points2 += 1
        elif p1 == "c" and p2 == "d":
            points1 -= 3
            points2 += 2
        elif p1 == "d" and p2 == "c":
            points1 += 2
            points2 -= 3
        elif p1 == "d" and p2 == "d":
            points1 -= 1
            points2 -= 1

    if points1 > points2:
        return [l1[0], points1/10], [l1[0], points1/10]
    elif points1 < points2:
        return [l2[0], points2/10], [l2[0], points2/10]
    else:
        return [l1[0], points1/10], [l2[0], points2/10]

def rounddoer(bots):
    bots2=[]
    for x in range(len(bots)):
        if x%2==0:
            out1, out2 = fight(bots[x], bots[x-1])
            bots2.append(out1)
            bots2.append(out2)

    return bots2

def gamedoer():

    bots=[[0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0],[12,0],[13,0],[14,0],[15,0],[16,0],[17,0],[18,0],[19,0],[20,0],[21,0],[22,0],[23,0],[24,0],[25,0],[26,0],[27,0],[28,0],[29,0],[30,0],[31,0],[32,0],[33,0],[34,0],[35,0],[36,0],[37,0],[0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0],[12,0],[13,0],[14,0],[15,0],[16,0],[17,0],[18,0],[19,0],[20,0],[21,0],[22,0],[23,0],[24,0],[25,0],[26,0],[27,0],[28,0],[29,0],[30,0],[31,0],[32,0],[33,0],[34,0],[35,0],[36,0],[37,0],[0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0],[12,0],[13,0],[14,0],[15,0],[16,0],[17,0],[18,0],[19,0],[20,0],[21,0],[22,0],[23,0],[24,0],[25,0],[26,0],[27,0],[28,0],[29,0],[30,0],[31,0],[32,0],[33,0],[34,0],[35,0],[36,0],[37,0],[0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0],[12,0],[13,0],[14,0],[15,0],[16,0],[17,0],[18,0],[19,0],[20,0],[21,0],[22,0],[23,0],[24,0],[25,0],[26,0],[27,0],[28,0],[29,0],[30,0],[31,0],[32,0],[33,0],[34,0],[35,0],[36,0],[37,0],[0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0],[12,0],[13,0],[14,0],[15,0],[16,0],[17,0],[18,0],[19,0],[20,0],[21,0],[22,0],[23,0],[24,0],[25,0],[26,0],[27,0],[28,0],[29,0],[30,0],[31,0],[32,0],[33,0],[34,0],[35,0],[36,0],[37,0]]
    random.shuffle(bots)
    counter=0

    while counter < rounds:

        counter += 1
        bots = rounddoer(bots)

        if random.randint(0,10) == 9:
            bots[random.randint(0, players*5)-1] = [random.randint(0, players-1), 0]

        random.shuffle(bots)

##        for item in bots:
##            print str(item[0]) + " with " + str(item[1]) + " bonus points."

    return bots

a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,mycounter=0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

while mycounter < 1000:
    mycounter += 1
    bots = gamedoer()

    print "Game: " + str(mycounter)

    for item in bots:
        if item[0]==0:
            a0 += 1
        if item[0]==1:
            a1 += 1
        if item[0]==2:
            a2 += 1
        if item[0]==3:
            a3 += 1
        if item[0]==4:
            a4 += 1
        if item[0]==5:
            a5 += 1
        if item[0]==6:
            a6 += 1
        if item[0]==7:
            a7 += 1
        if item[0]==8:
            a8 += 1
        if item[0]==9:
            a9 += 1
        if item[0]==10:
            a10 += 1
        if item[0]==11:
            a11 += 1
        if item[0]==12:
            a12 += 1
        if item[0]==13:
            a13 += 1
        if item[0]==14:
            a14+=1
        if item[0]==15:
            a15+=1
        if item[0]==16:
            a16+=1
        if item[0]==17:
            a17+=1
        if item[0]==18:
            a18 += 1
        if item[0]==19:
            a19+=1
        if item[0]==20:
            a20+=1
        if item[0]==21:
            a21+=1
        if item[0]==22:
            a22+=1
        if item[0]==23:
            a23+=1
        if item[0]==24:
            a24+=1
        if item[0]==25:
            a25+=1
        if item[0]==26:
            a26+=1
        if item[0]==27:
            a27+=1
        if item[0]==28:
            a28+=1
        if item[0]==29:
            a29+=1
        if item[0]==30:
            a30+=1
        if item[0]==31:
            a31+=1
        if item[0]==32:
            a32+=1
        if item[0]==33:
            a33+=1
        if item[0]==34:

This Contest is now Finished

If you would like to add an answer, I'll see if I can get around to adding a post-challenge scoreboard under the one for the original contestants. I'll be adding that one as soon as the testing program finishes (probably 2-3 more days).

FINAL SCORES!!!!!

Tit for Tat: 18
Random Pick: 28
Cooperator: 19
Defector: 24
Lucky Tit for Tat: 23
Random Tit for Tat: 23
Remorseful Aggressor: 22
Every Other C: 23
Nice Guy: 18
Tit for Tat Backstab: 15
Gentle Defector: 22
Anticapitalist: 27
Grim Trigger: 19
Bizzaro: 21
NeoAnticapitalist: 24
Ten Tits for a Tat: 240
Bitter Tat: 12
Copy First: 30
Exploitative Tat: 19
Six Tits for a Tat: 16
Thirty Tits for Tat: 4129
Five Tits for a Tat: 22
Forty Tits for a Tat: 1972
Three Tits for a Tat: 22
Fifty Four Tits for a Tat: 25805
The Elephant: 31
Extra Bitter Tat: 28
Fox: 35
Game of Thrones: 11297
The Boy: 31
Grimace: 26
Sore Loser: 39
Every Other D: 18
Twenty Five Tits for a Tat: 2399
Fifty Three Tits for a Tat: 5487
Handshake: 28
Anty: 26
Kinda Tits for Tat: 20
Prudent Defector: 154539
Bizzarro Trigger: 25
Young Mathematician: 21
Older Mathematician: 16
Perfect Gentleman: 1953341

So, it appears that Perfect Gentleman is the winner. Congratulations to Draco18, who definitely deserves his green checkmark.

\$\endgroup\$
  • \$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$ – Dennis May 31 '17 at 22:37
  • 1
    \$\begingroup\$ NOTE: IF YOU ARE EDITING YOUR PROGRAM, PLEASE GIVE ME A COMMENT SO I NOTICE, OR IT MAY NOT BE PUT ON THE SCOREBOARD FOR A WHILE!!!!!!!!!!!!!!!!!!!!!!! \$\endgroup\$ – Gryphon Jun 1 '17 at 0:52
  • \$\begingroup\$ Yikes! I just realised how many imports that is. \$\endgroup\$ – Gryphon Jun 4 '17 at 20:56
  • 1
    \$\begingroup\$ Hey Gryphon, you working on those final rankings? ;) \$\endgroup\$ – Draco18s Jul 9 '17 at 15:07
  • \$\begingroup\$ Sorry, I forgot about this. Give me a bit to run it. \$\endgroup\$ – Gryphon Jul 9 '17 at 15:48

24 Answers 24

8
\$\begingroup\$

The Perfect Gentleman

I don't have a good description for this bot. I stumbled into a couple of potential optimizations, tested them, fine tuned, and ended up with a bacteria that utterly destroys the competition. Instead, I've commented the code itself to explain what it does.

import random
def perfectgentlemanfunc(num, i, d, c, en):
    if num>0 and i < 0 and d > 0 and -i%3 == 0 and d%2 == 0 and en[0] == "d":
        #probably very first iteration, probably facing a defector: feed it free points
        #    defector cannot be beaten by *any* bot unless that bot
        #    entered with a point lead. defector does some of our work for us
        if num >= 140:
            #140 threshold restricts how much we feed
            return "d"
        return "c"
    turn_to_betray = 130
    if num > turn_to_betray and en[turn_to_betray -2] == "c" and
     en[turn_to_betray -1] == "c" and en[turn_to_betray] == "d":
        #if self, then sacrifice the lower point bot to raise the points of the higher
        #(better net outcome than "c/c" cooperation)
        #    Handshake independently arrived at this same optimization
        if i == d:
            #max 50% probability of choosing different possible. May as well take it
            #    "ccd" has a 55% chance of choosing the same
            #    better outcomes for splitting early
            return "cd"[random.randint(0,1)]
        if i > d:
            return "d"
        return "c"
    #betray after betray point, or if behind by >200
    #performs 6 percentage points better than not having the condition
    if num >= turn_to_betray or i + 200 < d
        return "d"
    else:
        #be nice the first turn
        if num == 0:
            return "c";
        #finally, be tit-for-tat
        return en[-1]

Several values were arbitrarily chosen with alternatives tested and the values here are near-optimal at this point. Against the current spread of opposing factions, The Perfect Gentleman achieves complete dominance (100% of bacteria population) about 90% of the time (plus or minus 3 percentage points).

I have not added in the Mathematicians to my tests yet, however those two should only serve to feed existing strategies and not alter the outcome greatly.

It does manage a good portion of its control via propping up Defector, but that was allowed per the rules (the example strategies were fair game for targeting). It has a side effect of also propping up Game of Thrones, but that was unintentional as the two are indistinguishable based on the criteria I chose. Those "defector types" then have a point-advantage in round 2 and take out several troublesome neighbors as a result (the N-T4T types) and when they reface The Perfect Gentleman, they've expended their point advantage and are swiftly consumed.

There is an approximate 5% chance that all Perfect Gentlemen end up paired with Defector-types in the first round and end up committing mass suicide. In which case, one of the n-T4t types achieves total domination (196 cells of 196). Very rarely one of the other types (Game of Thrones, Boy, Grimace, Sore Loser...) manage to not go completely extinct and score a point or two.

Current simulation (still in progress towards 200 total games). All entries scoring 0 removed. Looks like Game of Thrones and 54-T4T split a round (195 points between them) after PG was eliminated.

Game: 90

Cooperator: 1
Remorseful Aggressor: 1
Copy First: 1
Six Tits for a Tat: 1
Thirty Tits for Tat: 393
Five Tits for a Tat: 1
Fifty Four Tits for a Tat: 538
Game of Thrones: 248
Perfect Gentleman: 16456 (93.2)%

##Simulation Terminated: Adding new bots

Backstabbing Tit for Tat (with Forgiveness)

This is basically Lucky Tit for Tat (aka Tit for Tat with Forgiveness) which is the "solved" optimal solution (for some value of "lucky"), with a twist. As we know exactly how many rounds the game will last, this bacteria backstabs on the final round thus ensuring a net-beneficial outcome against any other Tit for Tat and Cooperator bacteria (against itself it ends with a net zero, same as if it had cooperated). Due to the 10% carryover, this results in a long-term advantage.

from random import randint
def titfortatbackstabfunc(num, i, d, c, enlist):
    if num == 199:
        return "d";
    lucky = randint(0, 200)
    if lucky == 0:
        return "c"
    if num == 0 or enlist[-1] == "c":
        return "c"
    else:
        return "d"

Bitter Tat

Bitter Tat takes advantage of any attempts of cooperation given by the enemy when the enemy is ahead in points. Most bacteria offer an olive branch at least once during the 200 rounds, and as Bitter Tat is behind overall, it will milk those 5 points in a desperate bid for recovery.

Otherwise, it tit-for-tats per the usual dominant strategy. Also, it's a little bit more of a jerk than its cousin and backstabs a round earlier and offers no forgiveness.

def bittertatfunc(num, i, d, c, enlist):
    if i < d:
        return "d";
    if num >= 198:
        return "d";
    if num == 0 or enlist[-1] == "c":
        return "c"
    else:
        return "d"

Bitter Tat was designed via looking at the behaviors of other bots against Tit for Tat and the patterns expressed in those results, but is not designed to explicitly counter those strategies: it is still a general purpose formula.

Extra Bitter Tat

def xbittertatfunc(num, i, d, c, enlist):
    if i < d:
        return "d";
    if num >= 188:
        return "d";
    if num == 0 or enlist[-1] == "c":
        return "c"
    else:
        return "d"

Extra bitter by defecting extra early.

\$\endgroup\$
  • 1
    \$\begingroup\$ Please change the name of your function, as this one is already taken. \$\endgroup\$ – Gryphon May 23 '17 at 6:24
  • \$\begingroup\$ @Gryphon Whoops, sorry, I didn't realize I'd done that. I don't actually code in Python, I just smashed two bits of code together. \$\endgroup\$ – Draco18s May 23 '17 at 13:47
  • 1
    \$\begingroup\$ I suspect it will outperform NeoAnticapitalist by a small margin. More like by over 30,000 points. \$\endgroup\$ – Gryphon May 26 '17 at 10:35
  • 1
    \$\begingroup\$ Let us continue this discussion in chat. \$\endgroup\$ – Draco18s May 26 '17 at 16:41
  • 2
    \$\begingroup\$ I appreciate that you made a bot to make this koth not a joke anymore \$\endgroup\$ – Destructible Lemon Jun 7 '17 at 5:37
8
\$\begingroup\$

Anticapitalist

Another simple one. For even matches (starting at the same score) behaves pretty much like TitForTat, but the main idea is trying to survive the match.

def anticapitalistfunc(counter, mypoints, enpoints, mylist, enlist):
    if mypoints >= enpoints:
        return "c"
    else:
        return "d"

Gentle Defector

My idea here is to defect except if my enemy is usually cooperating. Though, it begins cooperating.

def gentleDefectorfunc(counter, mypoints, enpoints, mylist, enlist):
    if enlist.count("d") * 4 > len(enlist):
        return "d"
    else:
        return "c"

NeoAnticapitalist

An improvement of the Anticapitalist (or so I think). I see no reason to collaborate on last turn. I also see no reason to collaborate when I am pretty sure my opponent won't.

def neoanticapitalistfunc(counter, mypoints, enpoints, mylist, enlist):
    if mypoints >= enpoints:
        if counter > 1:
            if counter == 199 or (enlist[-1] != "c" and enlist[-2] != "c"):
                return "d"
        return "c"
    else:
        return "d"
\$\endgroup\$
  • \$\begingroup\$ I'm actually surprised I didn't think of this, but it's brilliant. I don't know if it will win, but I think it should work very well. \$\endgroup\$ – Gryphon May 23 '17 at 12:54
  • \$\begingroup\$ @Gryphon added a new bot (and merged my two other ones) \$\endgroup\$ – Masclins May 25 '17 at 9:53
  • \$\begingroup\$ @Gryphon thanks for both editions \$\endgroup\$ – Masclins May 25 '17 at 10:55
  • \$\begingroup\$ No problem, @AlbertMasclans \$\endgroup\$ – Gryphon May 25 '17 at 12:17
  • \$\begingroup\$ From the simulations I've been running, NeoAnticapitalist seems to have taken the lead from Backstabbing Tit for Tat. \$\endgroup\$ – Gryphon May 25 '17 at 13:06
6
\$\begingroup\$

Remorseful Aggressor

from random import randint
def remorsefulaggressorfunc(counter, mypoints, enpoints, mylist, enlist):
    if counter == 0:
        return "d"
    if (counter > 195 and mylist[-1] == "d"):
        return "d"
    if ((counter == 1 or counter > 2) and enlist[-1] == "d"):
        return "d"
    if (counter == 2 and enlist[-1] == "d" and enlist[-2] == "d"):
        return "d"
    if (counter >= 195 and randint(0, 200 - counter) == 0):
        return "d"
    else:
        return "c"

This is designed to "keep up" with Defector, by defecting every time against it, and also to beat tit-for-tat-based strategies.

The basic idea is that we start by defecting, but if the opponent cooperated turn 1, we then cooperate twice to avoid a mutual recrimination cycle, thus avoiding too large a point penalty. (If, however, the opponent defects later on, we don't break the cycle ourself; we'll make them do it and likely lose the game as a result.) Then at the end of the game, we pick a random time within the last 5 turns to backstab the enemy, giving us one more defection than them and thus meaning that as long as we weren't too far behind on carryover points, we end up winning, without sacrificing much in terms of carryover in the process. (Randomizing the time period means that we're very likely to get in first with the backstab, also that this strategy can't be "tuned against" by aiming to backstab it one turn earlier.)

\$\endgroup\$
  • \$\begingroup\$ Congrats on your number 3 spot! +1 \$\endgroup\$ – Gryphon May 27 '17 at 10:59
6
\$\begingroup\$

Grim Trigger

Simplistic bot, to try and fill out the competition

It will cooperate, unless the enemy defects, in which case it defects unforgivingly

def grimtriggerfunc(I, Do, Not, Care, enlist): return "d" if "d" in enlist else "c"

well, seems like this doesn't work because of the ntitsfortat meta of defecting early

\$\endgroup\$
  • \$\begingroup\$ Congrats on your number 5 spot, +1. \$\endgroup\$ – Gryphon May 26 '17 at 18:56
  • \$\begingroup\$ @Sleafar I was wondering who would be so mean ;_; ok \$\endgroup\$ – Destructible Lemon Jun 6 '17 at 7:54
5
\$\begingroup\$

Game of Thrones

def gameofthronesfunc(counter, mypoints, enpoints, mylist, enlist):
    turn_to_betray = 140
    if counter >= turn_to_betray or mypoints > enpoints or "d" in enlist:
        return "d"
    else:
        return "c"

Idea here is that you can never lose by betraying, so the only reason to cooperate is if you're behind. It also has the general framework of the other T4T answers (without any forgiveness, because I'm not sure if there's much point with the other contenders here).

The turn to betray might need to be changed to win, since in an even race, the T4Ter who betrays first will win, but against a very cooperative bot, you will miss out on some lifetime points. I'm not sure the right apex for this hill, so I'm just going for 140. I wouldn't be surprised if it were much earlier, though.

If this ends up in a petri dish with a T4Ter who betrays earlier or a defector (ie 146 T4T), then it depends entirely on if the GoT is already ahead (it will stay ahead) or if they're even/GoT is behind, in which case the early betrayer will win.

\$\endgroup\$
  • \$\begingroup\$ Congrats on your third place finish! +1 \$\endgroup\$ – Gryphon May 31 '17 at 19:58
  • \$\begingroup\$ And now up to second! \$\endgroup\$ – Gryphon Jun 3 '17 at 13:50
  • \$\begingroup\$ Game of Thrones is putting up quite the fight against the bot I'm currently testing. The simple strategy is working well for it. \$\endgroup\$ – Draco18s Jun 6 '17 at 15:00
4
\$\begingroup\$

Lucky Tit For Tat

import os
def luckytitfortatfunc(num, i, d, c, enlist):
    lucky = ord(os.urandom(1))
    lucky = int(round(200 * float(lucky - 0) / 255.0))
    if lucky == 0:
        return "c"
    if num == 0 or enlist[-1] == "c":
        return "c"
    else:
        return "d"

I'm pretty sure I read somewhere that tit for tat was the best strategy. I decided to make it a possibility for other programs to redeem themselves just to add some variety. Now with a proper random number generator (it gives me an advantage right?).

\$\endgroup\$
  • \$\begingroup\$ Basically, this is the winning strategy, hands down, all the time as noted on wikipedia. The only variance is in the probability of breaking out of a tit-for-tat defect-cycle (which depends on the matchup of all the other entries). \$\endgroup\$ – Draco18s May 23 '17 at 3:52
  • 1
    \$\begingroup\$ @Draco18s It's the winning strategy for a different scoring system, the total points scoring system. Basic tit-for-tat can never win a round, if it doesn't carry some points into the round, so it wouldn't do well. \$\endgroup\$ – isaacg May 23 '17 at 14:38
  • \$\begingroup\$ @isaacg is right, which is the reason this strategy is now in 14th out of 18 (although I have no right to blame AH L for this as the 18th program is one of mine.) \$\endgroup\$ – Gryphon May 26 '17 at 17:27
4
\$\begingroup\$

The Elephant

The Elephant never forgets!

import re
def theelephantfunc(counter, mypoints, enpoints, mylist, enlist):
    interwoven = "".join(i for j in zip(mylist, enlist) for i in j)
    backwoven = interwoven[::-1]
    predict = re.match("^((?:..)*).*?(.).\\1(?:..)*$",backwoven)
    if(predict is None):
        return "c"
    predict = predict.groups()[1]
    if(predict == "d"):
        return "d"
    if(mypoints - enpoints >= 6):
        return "c"
    return "d"

The Elephant looks at the history of the fight, and tries to figure out what the enemy has planned. He looks at both his moves and his enemies!

He tries to find the longest concurrent group that matches what's just happened, and takes what the enemy did just after that.

If he can't work it out, the Elephant will just Cooperate, as friendship is always the answer.

If he thinks his opponent will defect, he too will defect, not wanting to lose his hard earned points.

If he thinks his opponent will cooperate, but his less than or exactly 6 points lead, then he will defect, to gain some foothold.

And lastly, if he thinks his opponent will cooperate, and he has a strong lead, he will cooperate.

\$\endgroup\$
  • \$\begingroup\$ I was waiting for something like this that worked better than Nice Guy. However, I won't be able to test it for about 8 hours, so I should be able to update in about 12-13. \$\endgroup\$ – Gryphon May 29 '17 at 12:14
4
\$\begingroup\$

54 Tits for a Tat

def fiftyfourtitsfortatfunc(num, more, fun, me, en):
    tits = 54
    if "d" in en[-tits:] or num >= (200-tits):
        return "d"
    return "c"
\$\endgroup\$
  • \$\begingroup\$ I wonder if this will still win? \$\endgroup\$ – Gryphon May 28 '17 at 23:09
  • \$\begingroup\$ This looks to be winning right now! \$\endgroup\$ – Gryphon May 28 '17 at 23:53
  • \$\begingroup\$ Congrats, you narrowly beat both of my top bots! \$\endgroup\$ – Gryphon May 29 '17 at 2:43
  • \$\begingroup\$ @Gryphon I would make 5 tits for a tat if I wasn't so attached for my other bots :) \$\endgroup\$ – Alex May 29 '17 at 3:20
  • \$\begingroup\$ I think that would be going over the other side of the curve. I might test one myself though! \$\endgroup\$ – Gryphon May 29 '17 at 3:21
3
\$\begingroup\$

Nice Guy

def niceguyfunc(counter, mypoints, enpoints, mylist, enlist):
  if counter < 2:
    return "c"

  mylast = mylist[-1]
  enlast = enlist[-1]
  last_found_index = -1

  for i, item in enumerate(mylist):
    if i == counter - 1:
      break
    if mylist[i] == mylast and enlist[i] == enlast:
      last_found_index = i

  if last_found_index == -1:
    return "c"
  else:
    if enlist[last_found_index + 1] == "c":
      return "c"
    else:
      return "d"

Tries to predict the opponents output by looking at the history. For example, if the last moves were (c, enemy d), it tries to find the last occurrence of the exact same moves.

\$\endgroup\$
3
\$\begingroup\$

Hackman [disqualified as expected]

Ok, this one will probably be ruled out of the contest, but I really feel like trying it out:

def hackmanfunc(counter, mypoints, enpoints, mylist, enlist):
        if enlist.count("#") > 0:
                return "c"
        elif counter >= 2 and enpoints > mypoints:
                return "d"
        elif counter == 198:
                return "d"
        elif counter == 199:
                return "#"
        elif counter == 0 or enlist[-1] == "c":
                return "c"
        elif counter >= 2 and enlist[-2] != "c":
                return "#"
        else:
                return "d"

Here I'm taking as base the BackstabbingTitForTat that proved to be the best in my simulations. Also, it's heavily based in using a non-used symbol "#"(that's why I say it will be probably ruled out).

Now let me explain the conditions here:

1st: Ensure two Hackman cooperate if something went wrong.

2nd: If I'm going to lose against a different bot, at least make him lose as many points as possible, so it's not a huge enemy afterwards.

3rd: Betray one turn before, so wins vs Backstabbing

using "#" instead of "d" makes me get 0 points instead of -1 and also communicate with other Hackman who has less points, so he stops defecting.

\$\endgroup\$
  • 2
    \$\begingroup\$ Sorry, but disqualified. This counts as messing around with the process of judging. You MUST return either "c" or "d" each turn. \$\endgroup\$ – Gryphon May 24 '17 at 10:14
  • 2
    \$\begingroup\$ However, this is pretty inventive, so I'm sorry to have to disqualify it. \$\endgroup\$ – Gryphon May 24 '17 at 10:15
3
\$\begingroup\$

Bizzaro

Does the exact opposite of tit for tat. When someone is kind to him he shows his love by being evil, and when someone is mean he shows revenge by being good. Heavily based on tit for tat.

def bizzarofunc(counter, mypoints, enpoints, mylist, enlist):
    if counter==0 or enlist[counter-1] == "c":
        return "d"
    else:
        return "c"
\$\endgroup\$
  • \$\begingroup\$ Interesting. This is going to get killed by Defector, though. \$\endgroup\$ – Gryphon May 24 '17 at 18:10
  • \$\begingroup\$ @Gryphon Lmao, actually didn't realize that. But hey, Bizzaro no know difference between right and wrong, win and lose. \$\endgroup\$ – TitusLucretius May 24 '17 at 18:20
  • \$\begingroup\$ Yeah, this will be defector towards cooperator and cooperator towards defector, which could make for some interesting possibilities. It can't really co-exist with anything else, including itself. \$\endgroup\$ – Gryphon May 24 '17 at 18:23
  • \$\begingroup\$ @Gryphon, Yeah it should get 0, when it plays itself. Wonder what will happen with Gentle Defector. \$\endgroup\$ – TitusLucretius May 24 '17 at 18:25
  • \$\begingroup\$ Wonder what happens when it faces backstabbing tit for tat, which is what was winning before this submission. \$\endgroup\$ – Gryphon May 24 '17 at 18:28
3
\$\begingroup\$

6 Tits for a Tat

def sixtitsforatatfunc(num, more, fun, me, en):
    if "d" in en[-6:] or num >= 194:
        return "d"
    return "c"

Tit for Tat arms race is happening :)

\$\endgroup\$
  • \$\begingroup\$ I feel we're going to go overboard and Defector will steal top spot. \$\endgroup\$ – Gryphon May 29 '17 at 3:51
3
\$\begingroup\$

Ten Tits for a Tat

def tentitsforatatfunc(num, more, fun, me, en):
    if "d" in en[-10:] or num >= 190:
        return "d"
    return "c"

Defects earlier, and also defects if its opponent has defected in the last ten turns.

CopyFirst

def copyfirstfunc(num, mypoints, enpoints, myhistory, enhistory):        
    if num == 0 or num >= 197:
        return "d"
    else:
        return enhistory[0]

This defects the first round, then does whatever the opponent did the first round, until the 197th round, when it backstabs.

Forty Tits for a Tat

def fourtytitsforatatfunc(num, mypoints, enpoints, myhistory, enhistory):
    if "d" in en[-40:] or num >= 150:
        return "d"
    return "c"

If the opponent defected in the last 40 turns, defect, otherwise cooperate. Backstab on the last 50 turns.

Three Tits for a Tat

If the opponent defected in the last 3 turns, defect, otherwise cooperate. Backstab on the last 5 turns. This program has stolen the lead from Tit for Two Tats by a narrow margin.

def threetitsforatatfunc(num, mypoints, enpoints, myhistory, enhistory):
    if num == 0 or num==1 and enhistory[-1]=="c" or num==2 and enhistory[-1]=="c" and enhistory[-2]=="c":
        return "c"
    if enhistory[-1] == "d" or enhistory[-2] == "d" or enhistory[-3] == "d" or num >= 195:
        return "d"
    else:
        return "c"

Five Tits for a Tat

def fivetitsforatatfunc(num, more, fun, me, en):
    if "d" in en[-5:] or num >= 194:
        return "d"
    return "c"

If you can't figure out what this one does, you're an idiot. Also backstabs one round earlier.

\$\endgroup\$
  • \$\begingroup\$ IMO, feel free to submit your own entry. \$\endgroup\$ – Draco18s May 25 '17 at 14:33
  • \$\begingroup\$ Just wasn't sure if it was generally considered fair. \$\endgroup\$ – Gryphon May 25 '17 at 16:33
  • \$\begingroup\$ What's usually common, though, is that Best Answer is the winning bot. \$\endgroup\$ – Masclins May 26 '17 at 7:00
  • \$\begingroup\$ It will be, I won't just pick my own answer. That would definitely not be fair. \$\endgroup\$ – Gryphon May 26 '17 at 10:24
  • \$\begingroup\$ I apologise, I had not realised I`d accidentally accepted my answer. It is now unaccepted, and I will accept the best answer as of July 1. \$\endgroup\$ – Gryphon May 26 '17 at 10:39
3
\$\begingroup\$

Grimace

def grimacefunc(I, Do, Not, Care, enlist):
    if round < 123: return "d" if "d" in enlist else "c"
    return "d"
\$\endgroup\$
  • \$\begingroup\$ It kind of feels like the "optimal" solution for this hill is to do something like this (T4nT with n>5 is basically this), and just optimize the round when it betrays. Anything more creative will be destroyed. \$\endgroup\$ – Robert Fraser Jun 1 '17 at 8:20
3
\$\begingroup\$

Every Other D

def everyotherdfunc(counter, mypoints, enpoints, mylist, enlist):
    if counter % 2 == 0:
        return "d"
    else:
        return "c"

Every Other C

def everyotherdfunc(counter, mypoints, enpoints, mylist, enlist):
    if counter % 2 == 0:
        return "c"
    else:
        return "d"
\$\endgroup\$
  • \$\begingroup\$ Maybe you should submit another entry starting with cooperate. \$\endgroup\$ – Gryphon Jun 1 '17 at 15:47
  • \$\begingroup\$ Just thought it might be interesting. \$\endgroup\$ – Gryphon Jun 1 '17 at 15:54
3
\$\begingroup\$

Predictable Mathematicians:

Young Mathematician

New to the harshness of the world

import math
def ymathfunc(num, mpoints, enpoints, mlist, enlist):
  if(math.sin(num) + 0.8 > 0):
    return 'c'
  else:
    return 'd'

Older Mathematitian

More experienced in these matters

import math
def omathfunc(num, mpoints, enpoints, mlist, enlist):
  if(math.cos(num) + 0.8 > 0):
    return 'd'
  else:
    return 'c'

I doubt either of these will do well, but at least they'll add ways for others to get points!

\$\endgroup\$
  • \$\begingroup\$ Neither of these are going to do well, heh. All they do is feed the defectors, for the most part. \$\endgroup\$ – Draco18s Jun 6 '17 at 15:16
2
\$\begingroup\$

Randomized Tit For Tat

import os
def randomtitfortatfunc(forgot, ten, var, iables, enlist):
    luck = enlist.count("d") + 1
    choice = ord(os.urandom(1))
    choice = int(round(luck * float(choice - 0) / 255.0))
    if choice == 0:
        return "c"
    return "d"

Tit For Tat, but randomized. This is not going to win any prizes (unless I'm really lucky). Now with random numbers generated from a proper source.

\$\endgroup\$
2
\$\begingroup\$

Exploitative Tat

Exploitative Tat tries to play the following strategies:

  • Defect when behind. It's the only way to catch up.

  • Cooperate against tit-for-tat and similar strategies. It's the only way to get a good long-term score.

  • Defect against always-cooperators and other chumps.

  • Defect 5 rounds early.

Here's the code:

def exploitativetatfunc(num, mypoints, enpoints, mylist, enlist):
    if mypoints < enpoints:
        return "d"
    if num >= 195:
        return "d"
    if num == 0:
        return "c"
    # Test defect, and keep defecting as long as they'll allow
    if (num == 5 or num >= 8) and all(choice == "c" for choice in enlist):
        return "d"
    # Recover if that goes wrong, and they were nice.
    if (num == 6 or num == 7) and all(choice == "c" for choice in enlist[:4]):
        return "c"
    # Otherwise, tit for tat.
    return enlist[-1]
\$\endgroup\$
  • \$\begingroup\$ I'm surprised this didn't do better than tie for 8th, but I think it was just the wrong time for it, and it was unfortunately entered at the same time as Two Tits for a Tat. \$\endgroup\$ – Gryphon May 27 '17 at 11:18
2
\$\begingroup\$

30 Tits for a Tat

def thirtytitsfortatfunc(num, more, fun, me, en):
    tits = 30
    if "d" in en[-tits:] or num >= (200-tits):
        return "d"
    return "c"
\$\endgroup\$
2
\$\begingroup\$

but what if... the next answer was not grim trigger or something for tat

I present

Anty

def antyfunc(counter, mypoints, enpoints, mylist, enlist):
    if counter > 150: return "d"
    if not "c" in enlist[-2:]:
        return "d"
    if enpoints >= mypoints:
        return "d"
    else:
        return "c"
\$\endgroup\$
  • \$\begingroup\$ Intersting, will test when I get home. \$\endgroup\$ – Gryphon Jun 2 '17 at 14:35
2
\$\begingroup\$

Fox

def foxfunc(counter, mypoints, enpoints, mylist, enlist):
    if counter > enpoints:
        return "d"
    return "c"

Defects if the round number is greater than the enemies points, cooperates otherwise.

The Boy

def boyfunc(counter, mypoints, enpoints, mylist, enlist):
    if counter!=0 and enlist[-1]=="c" and counter <= 194 or enpoints+10<mypoints:
        return "c"
    return "d"

Cooperates first round, then acts for tit for tat but backstabs on the last five rounds, and defects if it isn't ten points ahead.

53 tits for a tat

def fiftythreetitsfortatfunc(num, more, fun, me, en):
    tits = 53
    if "d" in en[-tits:] or num >= (200-tits):
        return "d"
    return "c"

You all know what this is :)

\$\endgroup\$
2
\$\begingroup\$

Twentyfivetitsforatat

def twentyfivetitsfortatfunc(num, more, fun, me, en):
    tits = 25
    if "d" in en[-tits:] or num >= (200-tits):
        return "d"
    return "c"

Kinda titsforatat

def kindatitsfortatfunc(num, more, fun, me, en):
    tits = 54  
    if "c" in en[-tits:] or num >= (200-tits):
        return "c"
    return "d"
\$\endgroup\$
  • \$\begingroup\$ Next time you edit to add a program, please also add a comment so I get alerted. Thanks! \$\endgroup\$ – Gryphon Jun 4 '17 at 21:13
  • \$\begingroup\$ @Gryphon oh sorry \$\endgroup\$ – Christopher Jun 4 '17 at 21:24
2
\$\begingroup\$

Prudent Betrayer

def PrudentBetrayer(counter, mypoints, enpoints, mylist, enlist):
    # Am I ahead, even if he betrays first?
    if mypoints > enpoints + 5:
        if counter == 0:
            return "c"
        else:
            return enlist[-1]
    # Can I catch up if I betray first?
    elif mypoints + 5 > enpoints:
        if counter == 0:
            return "c"
        elif counter > 130:
            return "d"
        else:
            return "d" if "d" in enlist else "c"
    # can't win -> kill his score
    else:
        return "d"

Assumes it is fighting an n-tits-for-a-tat bot. If it has the score to be betrayed and still win, it will let the other bot hit it first (playing as tit for tat.) If it can win only when it betrays first, It will betray on round 130, well before any current bot. If it is many points behind its opponent, it will just play defector in an attempt to lower the unsuspecting bots score.


Handshake

import random
def handshakefunc(num, me, him, m, en):
    handshake = "cdccd"
    # For the first few rounds, give the handshake.
    if num < len(handshake):
        if m == en:
            return handshake[num]
        return "d"
    if en[:len(handshake)] == handshake:
        if me > him:
            return "d"
        if me == him:
            return "ccd"[random.randint(0,2)]
        return "c"
    return "d"

Uses the pattern cdccd on the first five rounds, to find out if it is with itself. If so, it will try to max its points by having the bot with more points always defect, while the other always cooperates. If it finds it is fighting an enemy, it will play the defector.

In my tests, I find it dose well if it makes up a significant portion of the population. When it dose not have a chance to fight itself, it will basically be reduced to a defector.

EDIT: Clearly from the scores, their are to many bots for this to work well. It will still win if fighting just the top few...


Bizzaro Trigger

def bizzaroTriggerfunc(round,ms,ts,mm,tm):
  if round==1:return 'c'
  if 'c' in tm:return'd'
  return 'c'

Always cooperate, unless your opponent ever cooperates with you, in which case you defect. Always.

\$\endgroup\$
  • \$\begingroup\$ @Gryphon the sore loser was removed, and a new bot added! \$\endgroup\$ – MegaTom Jun 5 '17 at 17:23
  • \$\begingroup\$ In the test I'm running now on a bot I haven't yet posted, Handshake is in 2nd place after 390 games (5338 surviving bots) outdoing 54-T4T by about 1200. My new bot is much stronger than even that, though. Initial tests had it scoring 196 out of 196, though long-run it's scoring an average of ~124 / game. Interestingly enough, the basis behind Handshake was what I was going to try next, turns out you beat me to it without my realizing it. \$\endgroup\$ – Draco18s Jun 5 '17 at 20:44
0
\$\begingroup\$

FakeShake

takes advantage of handshake- does a handshake then just defects while handshake trusts it. When it meets itself, however, it does the 'real' handshake. If it meets a different bot it plays tit for tat, with a betrayal at the end. This feels kinda mean...

import random
def fakeshakefunc(num, i, d, m, enlist):
      secret_handshake="cdccdd"
      handshake= "cdccd"
      #checks if it is meeting itself
      if enlist[:len(secret_handshake)] == secret_handshake:
          if me > him:
            return "d"
          if me == him:
             return "ccd"[random.randint(0,2)]
          return "c"
      #does handshake's handshake,if the bot is not handshake or fakeshake it plays T4T
      if num < len(handshake):
            if m == enlist:
                return handshake[num]
            if i < d or num>= 198:
                return "d";
            if num == 0 or enlist[-1] == "c":
                return "c"
            else:
                return "d"
            if enlist[:len(handshake)] == handshake:
                return "d"
            if i < d or num>= 198:
                return "d";
            if num == 0 or enlist[-1] == "c":
                return "c"
            else:
                return "d"

One problem with this is that if it meets handshake and it has more points, it thinks its playing itself. I'm a newbie to python and this site (in fact this is my first answer) so make sure to tell me if I've made any stupid mistakes!

\$\endgroup\$
  • \$\begingroup\$ Welcome to PPCG! \$\endgroup\$ – Laikoni Nov 17 '18 at 18:00
  • \$\begingroup\$ @Laikoni Thanks! \$\endgroup\$ – Arkine Nov 17 '18 at 19:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.