2
\$\begingroup\$

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]

class OpeningMenu(object):
    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()

OpeningMenu.open()
\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$

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.