Merl's Blog

Greed

Complex is better than complicated.

I learned a valuable lesson today.

Going through the Python Koans today I came across this problem to do. It was to create the dice game called “Greed” on Python. Here are the rules I was given:

# Greed is a dice game where you roll up to five dice to accumulate
# points.  The following "score" function will be used to calculate the
# score of a single roll of the dice.
#
# A greed roll is scored as follows:
#
# * A set of three ones is 1000 points
#
# * A set of three numbers (other than ones) is worth 100 times the
#   number. (e.g. three fives is 500 points).
#
# * A one (that is not part of a set of three) is worth 100 points.
#
# * A five (that is not part of a set of three) is worth 50 points.
#
# * Everything else is worth 0 points.
#
#
# Examples:
#
# score([1,1,1,5,1]) => 1150 points
# score([2,3,4,6,2]) => 0 points
# score([3,4,5,3,3]) => 350 points
# score([1,5,1,2,4]) => 250 points
#
# More scoring examples are given in the tests below:
#
# Your goal is to write the score method.

I sat back and began contemplating the process I would go about solving this problem. My philosophy is typically to get started as soon as possible; look for other paths to accomplish your goals as you’re working, and if a better solution arrises follow that route. This is a good principle if followed. As you will soon see, I sometimes become stubborn with accomplishing the goal rather than looking for more elegant solutions.

Besides the stubborness my downfall was thinking in very specific terms instead of general ones. I thought to provide an answer for every scenario, rather than read and analyze what is provided.

Specifically, I used *args to solve for the variability of the lists that could enter my function called score. So I tried to account for every combination possible for rolling 1, 3, 4, or 5 dice. No bueno!

def score(score_list):
      # Behold my monstar(yes it's a star)
    if score_list==[]:
        return 0

    a, *args = score_list
    value=0
    list = [a, *args]
    counter1 =0
    counter5 =0

    if len(list)==3:
        if a==args[0]==args[1]:
            if a ==1:
                value += 1000
                counter1 = -3
            else:
                value += a*100
                if a ==5:
                    counter5 = -3
    if len(list)==4:
        if a==args[0]==args[1] or a==args[0]==args[2] or a==args[1]==args[2]:
            if a ==1:
                value += 1000
                counter1 = -3
            else:
                value += a*100
                if a ==5:
                    counter5 = -3
        elif args[2]==args[0]==args[1]:
            if args[0] ==1:
                value += 1000
                counter1 = -3
            else:
                value += args[0]*100
                if args[0] ==5:
                    counter5 = -3
    if len(list) ==5:
        if a==args[2]==args[3] or a==args[2]==args[0] or a==args[2]==args[1] or a==args[3]==args[0] or a==args[3]==args[1] or a==args[0]==args[1]:
            if a ==1:
                value += 1000
                counter1 = -3

            else:
                value += a*100
                if a ==5:
                    counter5 = -3

        elif args[2]==args[3]==args[0] or args[2]==args[3]==args[1] or args[3]==args[0]==args[1] :
            if args[3] ==1:
                value = 1000
                counter1 = -3
            else:
                value = args[3]*100
                if args[3] ==5:
                    counter5 = -3
        elif args[2]==args[0]==args[1] :
            if args[2] ==1:
                value = 1000
                counter1 = -3
            else:
                value = args[2]*100
                if args[2] ==5:
                    counter5 = -3
    for i in list:
        if i==1:
            counter1 += 1
        if i==5:
            counter5 += 1
    if counter1>0:
        value += counter1*100
    if counter5>0:
        value += counter5*50
    return value

A quarter of the way through I was thinking that there was definitely a simpler and easier way to complete this. However I was in the “zone” and I saw a glimpse of the distant finish line. So I continued down the same path.

Just yesterday when I was writing my blog I wanted to mention some of my favorite tools I was learning in Python. List Comprehensions in particular are what really stood out to me in their usefulness. The ability for Python to ‘intelligently’ look through lists was quite impressive. I bookmarked it in my mind that I should come back to these when I had a future problem. FALSE!

After and unspecified amount of time I completed my creation above. My curiousity got the better of me, so I decided to look up a solution. This is what I found….

def score(dice):
    score = 0
    counts = [0] * 7  # Initialize counts for each dice face (1-6)

    for die in dice:
        counts[die] += 1

    # Calculate the score based on the counts of each dice face
    for face in range(1, 7):
        if counts[face] >= 3:
            if face == 1:
                score += 1000  # A set of three ones is 1000 points
            else:
                score += face * 100  # A set of three numbers (other than ones) is worth 100 times the number

        # Handle individual ones and fives
        if face == 1:
            score += (counts[face] % 3) * 100  # Each one (not part of a set of three) is worth 100 points
        elif face == 5:
            score += (counts[face] % 3) * 50  # Each five (not part of a set of three) is worth 50 points

    return score

Clearly this is much better. My code worked for the solutions it asked for, but no more. This code is more flexible, cleaner, and readable. As the Koan states, complex is better than complicated.

In the future when I hear that voice in my head about there being an easier way. I’m going to listen to it (I hope).

Best, Merl