Search code examples
pythonpython-2.7nested-loops

Table tennis simulator


I edited my previous question because I came up with the code I think is correct. The logic behind this should be: while the set is not over and it's not a tie 10:10: player A starts serving and does it twice regardless he wins points or not, then player B takes serve and does it twice also. It continues until the set is over, except there is a tie 10:10 when servers change each point scored.

Can anyone check if the code is flawless? thank you.

def simOneSet(probA, probB):
    serving = "A"
    scoreA = scoreB = 0
    while not setOver(scoreA, scoreB):
        if scoreA != 10 and scoreB != 10:
            if serving == "A":
                for i in range(2):
                    if random() < probA:
                        scoreA += 1
                    else:
                        scoreB += 1
                serving = "B"
            else:
                for i in range(2):
                    if random() < probB:
                        scoreB +=1
                    else:
                        scoreA += 1
                serving = "A"    
        # when there is a tie 10:10
        else:
            if serving == "A":
                if random() < probA:
                    scoreA += 1
                    serving = "B"
                else:
                    scoreB += 1
                    serving = "B"
            else:
                if random() < probB:
                    scoreB += 1
                    serving = "B"
                else:
                    scoreA += 1
                    serving = "A"
    return scoreA, scoreB

Solution

  • I would use a dict to "switch" between players:

    other = {'A':'B', 'B':'A'}
    

    Then, if serving equals 'A', then other[serving] would equal 'B', and if serving equals 'B', then other[serving] would equal 'A'.


    You could also use a collections.Counter to keep track of the score:

    In [1]: import collections
    
    In [2]: score = collections.Counter()
    
    In [3]: score['A'] += 1
    
    In [4]: score['A'] += 1
    
    In [5]: score['B'] += 1
    
    In [6]: score
    Out[6]: Counter({'A': 2, 'B': 1})
    

    Also notice how in this piece of code

        if serving == "A":
            for i in range(2):
                if random() < probA:
                    scoreA += 1
                else:
                    scoreB += 1
        else:
            for i in range(2):
                if random() < probB:
                    scoreB +=1
                else:
                    scoreA += 1
    

    there are two blocks which are basically the same idea repeated twice. That's a sign that the code can be tightened-up by using a function. For example, we could define a function serve which when given a probability prob and a player (A or B) returns the player who wins:

    def serve(prob, player):
        if random.random() < prob:
            return player
        else:
            return other[player]
    

    then the above code would become

        for i in range(2):
            winner = serve(prob[serving], serving)
            score[winner] += 1
    

    Thus, you can compactify your code quite a bit this way:

    import random
    import collections
    other = {'A':'B', 'B':'A'}
    
    def serve(prob, player):
        if random.random() < prob:
            return player
        else:
            return other[player]
    
    def simOneSet(probA, probB):
        prob = {'A':probA, 'B':probB}
        score = collections.Counter()
    
        serving = "A"
        while not setOver(score['A'], score['B']):
            for i in range(2):
                winner = serve(prob[serving], serving)
                score[winner] += 1
            if score['A'] == 10 and score['B'] == 10:
                winner = serve(prob[serving], serving)
                score[winner] += 1
                serving = winner
    
        return score['A'], score['B']  
    
    def setOver(scoreA, scoreB):
        return max(scoreA, scoreB) >= 21
    
    print(simOneSet(0.5,0.5))