Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output head-to-head results in a headToHead.csv file for quick comparison #31

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/__pycache__
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ Nicky Case's "The Evolution of Trust" is also super fascinating, but it's not ne
How this works:
When you run code/prisonersDilemma.py, it will search through all the Python strategy files in code/exampleStrats. Then, it will simulate "Iterated Prisoner's Dilemma" with every possible pairing. (There are (n choose 2) pairings.) After all simulations are done, it will average each strategies' overall score. It will then produce a leaderboard of strategies based on their average performance, and save it to results.txt.

It will also produce headToHead.csv, which tracks the head-to-head performance
of every strategy against every other strategy. You can import it to Google
Sheets or Excel and use it to compare how well your strategies score points
against other strategies. This may be especially useful if you are testing with
many strategies and do not want to parse results.txt to understand what's
driving their different performances and defining your meta.

If you'd like to add your own strategy, all you have to do is create a new .py file in the code/exampleStrats folder that follows the same format as the others. Then, when you run code/prisonersDilemma.py, it should automatically include your strategy into the tournament!

# Details
Expand Down
10 changes: 10 additions & 0 deletions code/headToHead.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
,alwaysCooperate,alwaysDefect,detective,ftft,grimTrigger,joss,random,simpleton,titForTat
alwaysCooperate,,0,0.045,3,3,2.640845070422535211267605634,1.463414634146341463414634146,3,3
alwaysDefect,5,,1.053333333333333333333333333,1.033755274261603375527426160,1.016949152542372881355932203,1,2.855072463768115942028985507,3.005698005698005698005698006,1.018691588785046728971962617
detective,4.97,0.9866666666666666666666666667,,1.08,1.017937219730941704035874439,1.288888888888888888888888889,2.228310502283105022831050228,1.997118155619596541786743516,2.995495495495495495495495495
ftft,3,0.9915611814345991561181434599,1.013333333333333333333333333,,3,2.702439024390243902439024390,1.792452830188679245283018868,3,3
grimTrigger,3,0.9957627118644067796610169492,1.040358744394618834080717489,3,,1.164179104477611940298507463,3.078703703703703703703703704,3,3
joss,3.239436619718309859154929577,1,1.307407407407407407407407407,3.190243902439024390243902439,1.164179104477611940298507463,,2.245614035087719298245614035,2.142241379310344827586206897,1.066465256797583081570996979
random,4.024390243902439024390243902,0.5362318840579710144927536232,2.251141552511415525114155251,2.900943396226415094339622642,0.5324074074074074074074074074,2.092105263157894736842105263,,2.294930875576036866359447005,2.262910798122065727699530516
simpleton,3,0.4985754985754985754985754986,2.011527377521613832853025937,3,3,1.926724137931034482758620690,2.133640552995391705069124424,,3
titForTat,3,0.9953271028037383177570093458,2.995495495495495495495495495,3,3,1.051359516616314199395770393,2.239436619718309859154929577,3,
148 changes: 100 additions & 48 deletions code/prisonersDilemma.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import os
import itertools
import csv
import importlib
import numpy as np
import itertools
import os
import random

import numpy as np
from decimal import Decimal # precise fixed-point arithmetic

STRATEGY_FOLDER = "exampleStrats"
RESULTS_FILE = "results.txt"
H2H_FILE = "headToHead.csv"

pointsArray = [[1,5],[0,3]] # The i-j-th element of this array is how many points you receive if you do play i, and your opponent does play j.
moveLabels = ["D","C"]
pointsArray = [
[1, 5],
[0, 3],
] # The i-j-th element of this array is how many points you receive if you do play i, and your opponent does play j.
moveLabels = ["D", "C"]
# D = defect, betray, sabotage, free-ride, etc.
# C = cooperate, stay silent, comply, upload files, etc.

Expand All @@ -23,98 +30,143 @@
# if there have been 3 turns, and we have defected twice then cooperated once,
# and our opponent has cooperated all three times.
def getVisibleHistory(history, player, turn):
historySoFar = history[:,:turn].copy()
historySoFar = history[:, :turn].copy()
if player == 1:
historySoFar = np.flip(historySoFar,0)
historySoFar = np.flip(historySoFar, 0)
return historySoFar


def strategyMove(move):
if type(move) is str:
defects = ["defect","tell truth"]
defects = ["defect", "tell truth"]
return 0 if (move in defects) else 1
else:
return move


def runRound(pair):
moduleA = importlib.import_module(STRATEGY_FOLDER+"."+pair[0])
moduleB = importlib.import_module(STRATEGY_FOLDER+"."+pair[1])
moduleA = importlib.import_module(STRATEGY_FOLDER + "." + pair[0])
moduleB = importlib.import_module(STRATEGY_FOLDER + "." + pair[1])
memoryA = None
memoryB = None

LENGTH_OF_GAME = int(200-40*np.log(1-random.random())) # The games are a minimum of 200 turns long. The np.log here guarantees that every turn after the 200th has an equal (low) chance of being the final turn.
history = np.zeros((2,LENGTH_OF_GAME),dtype=int)


LENGTH_OF_GAME = int(
200 - 40 * np.log(random.random())
) # The games are a minimum of 200 turns long. The np.log here guarantees that every turn after the 200th has an equal (low) chance of being the final turn.
history = np.zeros((2, LENGTH_OF_GAME), dtype=int)

for turn in range(LENGTH_OF_GAME):
playerAmove, memoryA = moduleA.strategy(getVisibleHistory(history,0,turn),memoryA)
playerBmove, memoryB = moduleB.strategy(getVisibleHistory(history,1,turn),memoryB)
history[0,turn] = strategyMove(playerAmove)
history[1,turn] = strategyMove(playerBmove)

playerAmove, memoryA = moduleA.strategy(
getVisibleHistory(history, 0, turn), memoryA
)
playerBmove, memoryB = moduleB.strategy(
getVisibleHistory(history, 1, turn), memoryB
)
history[0, turn] = strategyMove(playerAmove)
history[1, turn] = strategyMove(playerBmove)

return history



def tallyRoundScores(history):
scoreA = 0
scoreB = 0
ROUND_LENGTH = history.shape[1]
for turn in range(ROUND_LENGTH):
playerAmove = history[0,turn]
playerBmove = history[1,turn]
playerAmove = history[0, turn]
playerBmove = history[1, turn]
scoreA += pointsArray[playerAmove][playerBmove]
scoreB += pointsArray[playerBmove][playerAmove]
return scoreA/ROUND_LENGTH, scoreB/ROUND_LENGTH

return Decimal(scoreA) / ROUND_LENGTH, Decimal(scoreB) / ROUND_LENGTH


def outputRoundResults(f, pair, roundHistory, scoresA, scoresB):
f.write(pair[0]+" (P1) VS. "+pair[1]+" (P2)\n")
f.write(pair[0] + " (P1) VS. " + pair[1] + " (P2)\n")
for p in range(2):
for t in range(roundHistory.shape[1]):
move = roundHistory[p,t]
f.write(moveLabels[move]+" ")
move = roundHistory[p, t]
f.write(moveLabels[move] + " ")
f.write("\n")
f.write("Final score for "+pair[0]+": "+str(scoresA)+"\n")
f.write("Final score for "+pair[1]+": "+str(scoresB)+"\n")
f.write("Final score for " + pair[0] + ": " + str(scoresA) + "\n")
f.write("Final score for " + pair[1] + ": " + str(scoresB) + "\n")
f.write("\n")



def pad(stri, leng):
result = stri
for i in range(len(stri),leng):
result = result+" "
for i in range(len(stri), leng):
result = result + " "
return result

def runFullPairingTournament(inFolder, outFile):
print("Starting tournament, reading files from "+inFolder)


def insertIntoNestedDict(nestedDict, keyA, keyB, value):
if keyA not in nestedDict:
nestedDict[keyA] = dict()
nestedDict[keyA][keyB] = value


def runFullPairingTournament(inFolder, outFile, h2hFile):
print("Starting tournament, reading files from " + inFolder)
scoreKeeper = {}
headToHead = {}
STRATEGY_LIST = []
for file in os.listdir(inFolder):
if file.endswith(".py"):
STRATEGY_LIST.append(file[:-3])



for strategy in STRATEGY_LIST:
scoreKeeper[strategy] = 0
f = open(outFile,"w+")

f = open(outFile, "w+")
for pair in itertools.combinations(STRATEGY_LIST, r=2):
roundHistory = runRound(pair)
scoresA, scoresB = tallyRoundScores(roundHistory)
outputRoundResults(f, pair, roundHistory, scoresA, scoresB)
scoreKeeper[pair[0]] += scoresA
scoreKeeper[pair[1]] += scoresB

insertIntoNestedDict(headToHead, pair[0], pair[1], scoresA)
insertIntoNestedDict(headToHead, pair[1], pair[0], scoresB)

scoresNumpy = np.zeros(len(scoreKeeper))
for i in range(len(STRATEGY_LIST)):
scoresNumpy[i] = scoreKeeper[STRATEGY_LIST[i]]
rankings = np.argsort(scoresNumpy)

f.write("\n\nTOTAL SCORES\n")
for rank in range(len(STRATEGY_LIST)):
i = rankings[-1-rank]
i = rankings[-1 - rank]
score = scoresNumpy[i]
scorePer = score/(len(STRATEGY_LIST)-1)
f.write("#"+str(rank+1)+": "+pad(STRATEGY_LIST[i]+":",16)+' %.3f'%score+' (%.3f'%scorePer+" average)\n")

scorePer = score / (len(STRATEGY_LIST) - 1)
f.write(
"#"
+ str(rank + 1)
+ ": "
+ pad(STRATEGY_LIST[i] + ":", 16)
+ " %.3f" % score
+ " (%.3f" % scorePer
+ " average)\n"
)

f.flush()
f.close()
print("Done with everything! Results file written to "+RESULTS_FILE)


runFullPairingTournament(STRATEGY_FOLDER, RESULTS_FILE)
print("Done with results tallying! Results file written to " + RESULTS_FILE)

with open(h2hFile, "w", newline="") as csvfile:
h2hwriter = csv.writer(csvfile) # defaults to Excel dialect
h2hwriter.writerow(
[
"",
]
+ STRATEGY_LIST
) # column header
for strategy in STRATEGY_LIST:
row = [
strategy,
]
for otherStrategy in STRATEGY_LIST:
row.append(str(headToHead.get(strategy, dict()).get(otherStrategy, "")))
h2hwriter.writerow(row)
print("Done with head-to-head results-tracking! CSV written to " + h2hFile)


runFullPairingTournament(STRATEGY_FOLDER, RESULTS_FILE, H2H_FILE)
Loading