Axelrod Tournament

Thought this would be a fun thing to code, basically it is a simulation of the prisoners dilemma. I'm trying to improve my coding so any critiques about how to get better would be appreciated. Also if you have any suggestions for other strategies for the game, those would be appreciated as well. The goal is to have the lowest score. I think the part of the codes I am most worried about at the moment is the weird way I take the output from the strategies and put them into the play function, and everyone online says the eval function is dangerous.

from random import randint

class Player(object):
def __init__(self):
pass

class TFT(Player):
def strat():
global lg #last game
global round #round number
global TFTv #TFT output variable
if round==0:
x=0
elif round !=0 and Player1=='TFT' and (lg=="out1" or lg=="out2"):
x=0
elif round !=0 and Player2=='TFT' and (lg=="out1" or lg=="out3"):
x=0
else:
x=1
TFTv=[x]

class Rand1(Player):
def strat():
global Rand1v #Rand1 output variable
x=randint(0,1)
Rand1v=[x]

class Rand2(Player):
def strat():
global Rand2v #Rand2 output variable
x=randint(0,1)
Rand2v=[x]

class Doomsday(Player):
def strat():
global Doomsdayv #doomsday output variable
if round==0:
x=0
elif lg=="out1":
x=0
elif Doomsdayv[0]==1:
x=1
else:
x=1
Doomsdayv=[x]

def open():
global MaxRounds
global round
global p1 #player 1 points
global p2 #player 2 points
MaxRounds=(int(input("How many rounds would you like?\n>  "))+1)
round=0
p1=0
p2=0
global Player1
global Player2
players={'Description':'Player Name','Tit for Tat':'TFT','Random50/50':'Rand1',"Doomsday":'Doomsday'}
print("Possible Players:", players)
Player1=input("Player 1?\n>  ")
Player2=input("Player 2?\n>  ")
return Arena.play()

class Counter(object):
def count():
global p1
global p2
global lg
global MaxRounds
if round==MaxRounds:
print(f"Player 1 score={p1}, and Player 2 score={p2}")
elif lg=="out1":
p1+=0
p2+=0
elif lg=="out2":
p1+=0
p2+=60
elif lg=="out3":
p1+=60
p2+=0
elif lg=="out4":
p1+=30
p2+=30
else:
pass

class Arena(object):
def play():
global lg
global round
global Player1
global Player2
eval(Player1).strat()
eval(Player2).strat()
R1=eval(Player1+'v')[0]
R2=eval(Player2+'v')[0]
round+=1

if round==MaxRounds:
Counter.count()
elif R1==R2==0:
lg="out1"
print("Neither Snitched")
Counter.count()
return Arena.play()
elif R1==1 and R2==0:
print("Player 1 snitched on Player 2")
lg="out2"
Counter.count()
return Arena.play()
elif R1==0 and R2==1:
print("Player 2 snitched on Player 1")
lg="out3"
Counter.count()
return Arena.play()
else:
print("Both Snitched")
lg="out4"
Counter.count()
return Arena.play()



design

1. You seem to have designed your system arround changing a whole mess of global variables. please don't do this. It is impossible to understand what variables trigger what effects.

2. Instead of outputing to a variable, and then getting that variable with eval, why not just return the value from strat()?

3. Why use classes when all they have is one static method? In this exact case functions will do just fine, however, I assume you want to make an object oriented design, so my next points will be on that.

4. Seperate your input/output from your game logic, in order to make the game logic reusable. What if you wanted to make a grpahical interface?

suggested design

1. If a global variable is used only by the class it is in, make it an instance variable. (i.e. Player1 and Player2). Other global variables can be passed as arguments or return values.

2. have the opining menu create an Arena which contains players. leave it up the main program to call Arena.play().

3. Get rid of counter entirly.

other

1. to avoid using eval() you can have a dictionary that maps the player entered strings to stretegies

{"TFT": TFT, "Rand1": Rand1, ...}

2. I don't understand why the counter class exist since it just checks a variable then sets other variables, why not just set the numbers in the first place.

3. def __init__(self): pass and class Something(object): are implicit and do not need to be defined.

4. Why does Arena.play recurse instead of looping over the rounds. Python has no tail call optimization so the all variables from the previous round are still kept in memory. also the Players are re-evald each time.

5. Having two Rand classes is a side effect of your design not instantiating classes.

summary

you might have noticed that most of these critiques are fairly simmilar. you should be able to vastly improve the quality of this code by following these guidelines:

1. Don't use global variables in your classes.

2. Don't use eval().

3. Seperate your game logic and output.

4. use return.

• Never update a question in response to suggestions in an answer. See What to do when someone answers. Commented Jun 28, 2018 at 4:48