USING STRUCTURED LISTS AND DICTIONARIES TO DETERMINE BUYING POWER IN MONOPOLY

This project was created for an introductory programming course at Penn State in the fall of 2021.
Learning Outcomes:

  • Utilize multiple types of 1D data structures
  • Clean and handle data using built-in string and list functions
  • Apply nested if-else statements in series to handle increasing complexity
  • Incorporate a dictionary to translate between numerals and text numbers

Monopoly property values are an effective tool for practicing logical complexity because of their unique rules:

  1. (Traditionally) Monopoly has 8 color sets of properties
  2. Each color set contains 2 or 3 individual properties
  3. Each color has a different purchase cost
  4. All properties in the set must be owned by the same person before building
  5. All properties must be upgraded evenly, ie you cannot build a second house on any property until all properties in the set have their first
  6. A hotel can only be built by upgrading from four houses (rule #5 still applies)

This creates two problems:

Problem #1: How best to organize the data for retrieval when each property has multiple attributes?

There are several ways this can be done, but for this project I chose to use a simple list structure similar to that of a .csv (comma separated value) file. By keeping the attributes listed in a consistent order, I can utilize list index functions to find related attribute values. In this case, I listed the data as (“color”, #properties, #cost) for the eight standard monopoly colors. The benefit to this structure is that it can be easily modified to change number values or add/remove colors and minimizes code redundancy.

I’m also using a dictionary in order to convert numerals to text numbers for use in the output. Because of the structure of (traditional) monopoly, the highest possible number of total houses & hotels on one set is twelve and the lowest is zero). So am able to restrict my dictionary to this range for efficiency.

#Monopoly Property Table (color, properties, cost)
value_table = ("violet", 2, 50, "pale blue", 3, 50, "maroon", 3, 100, "orange", 3, 100,
               "red", 3, 150, "yellow", 3, 150, "green", 3, 200, "dark blue", 2, 200)

#dictionary to convert numbers to words
number_dictionary = {0:"zero", 1:"one", 2:"two", 3:"three", 4:"four", 5:"five", 6:"six",
                     7:"seven", 8:"eight", 9:"nine", 10:"ten", 11:"eleven", 12:"twelve"}


I then utilized list and string functions to display property options, clean keyboard inputs, loop back for invalid inputs, and report the correct property cost.

#displays color choices, which are every 3rd item in the list
color_list = value_table[::3]
print("The block colors in Monopoly are:")
print(", ".join(color_list))

#Take & clean keyboard input
print("")
color = input("Which color block will you be building on? ")
color = color.strip()
color = color.lower()

#Loop invalid inputs
while color not in value_table:
    if color == "blue":
        color = input("Would that be pale blue or dark blue? ")
        color = color.strip()
        color = color.lower()
    else:
        color = input("There is no block by that color.  Choose another? ")
        color = color.strip()
        color = color.lower()

#Extract relevant data from list for convenience
color_index = value_table.index(color)
properties = value_table[color_index + 1]
cost = value_table[color_index + 2]

print("")
print(f"There are {number_dictionary[properties]} properties and each house costs {cost}")


Next, after taking keyboard input regarding the amount of available money, we must determine how many houses and hotels can be built. Since hotels are built only after building four houses, we can comfortably focus our attention on the houses for now.

money = int(input("How much money do you have to spend? "))

#calculate max possible houses
number_houses = money // cost

#determine how houses will be distributed
#first upgrade all properties equally
minimum = number_houses // properties
#second, determine how many properties get extra house
extra = number_houses % properties


Now that we’ve distributed the houses evenly, we’ve come to our second main problem.

Problem #2: How to ensure that all properties are upgraded evenly and follow the rules for building hotels?

In this case, there are four types of outcomes we must consider as each type will require a differently formatted output:

  1. Build nothing
  2. Build all hotels
  3. Build some hotels
  4. Build no hotels

The first two cases are easiest to handle since we’ve already calculated the minimum number of houses per property. Remember too that hotels are built after four houses and nothing can be built after a hotel. Therefore, any property with five or more houses becomes a hotel. If the calculated minimum is five or above, all properties get hotels.

if number_houses == 0:
    print("You can't afford even one house :(")
elif minimum >= 5:
    print(f"{number_dictionary[properties].capitalize()} will have a hotel.")


Next, we should consider the cases where only some properties get hotels. Because of the even development rule, we know that if even one property has a hotel, all properties must have built at least four houses (and at least one property received an extra).
Therefore, minimum = 4 and extra > 0

elif minimum == 4 and extra > 0:
    print(f"{number_dictionary[extra].capitalize()} will have a hotel, and {number_dictionary[properties - extra]} will have {number_dictionary[minimum]} houses")
else:


Finally, we consider the most complex case: some houses and no hotels. There are three possibilities here:

  1. Some properties build none, some build one (minimum = 0, extra > 0)
  2. All properties build equally (minimum >0, extra = 0)
  3. All properties build, some build extra (minimum > 0, extra >0)

This is where properly ordering our logical arguments is paying off. We know logically that properties building more than 4 houses will become hotels, but we don’t have to code in value limits because we’ve already separated all those cases out in the earlier chunks of code!

    print("You can build", number_dictionary[number_houses], "house" if number_houses == 1 else "houses as follows:")
    if minimum == 0: #minimum = 0
        print(f"{number_dictionary[extra].capitalize()} will have {number_dictionary[minimum + 1]}")
    elif extra == 0: #extra = 0
        print(f"{number_dictionary[properties].capitalize()} will have {number_dictionary[minimum]}")
    else: #minimum and extra >0
        print(f"{number_dictionary[extra].capitalize()} will have {number_dictionary[minimum + 1]}, and {number_dictionary[properties - extra]} will have {number_dictionary[minimum]}")

And we’re done!

Here’s how the final program looks when run:

Creating a dynamic recipe scaler in python using branching logic

This program was written in 2021 as part of an introductory programming course at Penn State – University Park


The purpose of this program is to create a dynamic recipe scaler that accepts an unlimited number of inputs from the user. The output is a dynamically formatted recipe utilizing mixed fractions that have been scaled according to user needs.

Learning Outcomes:

  1. Utilize lists, list functions, and list indexes
  2. Address a multi-branched logic problem and determine most concise operational routes for solutions to all branches
  3. Format output with mixed fractions, text, and multiple columns
print("This is a recipe scaler for serving large crowds!")
print("")

#setting up lists to hold keyboard inputs
quant_list = []
unit_list = []
item_list = []

#KEYBOARD INPUTS
raw_line = input("""Enter one ingredient per line, with a numeric value first.
Indicate the end of the input with an empty line:
""")
while raw_line != "":                              
#will end input after detecting empty line
    if raw_line.count(" ") == 1:                
     #handles unitless ingredients (such as eggs)
        quant, item = raw_line.split(' ')
        quant_list.append(quant)
        unit_list.append(" ")
        item_list.append(item)
    else:
        quant, unit, item = raw_line.split(' ',2) 
        #split the line input into 3 values
        #next add values to appropriate lists
        quant_list.append(quant)                    
        unit_list.append(unit)
        item_list.append(item)
    raw_line = input("") 
    #repeat input for next ingredient                               

#prints recipe with formatting
print("Here is the recipe that has been recorded:")
k=0
while k < len(quant_list):
    print(format(quant_list[k], "^7") + format(unit_list[k], "8") + format(item_list[k]))
    k = k + 1


#KEYBOARD INPUTS
servings = int(input("How many does this recipe serve? "))
people = int(input("How many people must be served? "))

#calculate how much to multiple recipe by
multiplier = people//servings
if people % servings != 0:               
    multiplier = multiplier +1
    #forces the program to always round up
print(f"""Multiplying the recipe by {multiplier}
""")

Because recipes frequently utilize fractions (like 1/2 tsp vanilla extract or 3/4 cup flour), scaling the recipe is more difficult than simply multiplying the inputted quantity numbers by the calculated multiplier.

There are a few ways the program can play out:

  1. INTEGERS: the easiest case, requires simply multiplying quantity by multiplier.
  2. FRACTIONS: requires multiplying only the numerator, then may or may not require simplifying
    1. FRACTIONS <1: these cannot be mixed and can be added directly to the output
    2. FRACTIONS =<1: these need to be simplified
      1. MIXED: will result in a combination of integers and fractions
      2. WHOLE: simplify to whole number integer
scaled_list = []
for i in quant_list:
#separate integers from fractions based on presence of "/"
    if "/" in i:                                          
        numer, denom = i.split('/')
        numer = int(numer)
        denom = int(denom)
        numer = numer * multiplier

        #separate out fractions that need to be simplified
        if numer // denom >= 1:              
            whole = numer // denom
            whole = str(whole)

            #identifies and simplifies mixed fractions
            if numer % denom != 0:           
                part_numer = numer % denom
                part_numer = str(part_numer)
                denom = str(denom)
                remainder = part_numer + "/" + denom
                mixed_fraction = whole + " " + remainder

            #takes remaining fractions and simplifies to whole numbers
            else:                                         
                mixed_fraction = whole
            scaled_list.append(mixed_fraction)

        #takes fractions <1 and assembles them for output
        else:                                             
            numer = str(numer)
            denom = str(denom)
            fraction = numer + "/" + denom
            scaled_list.append(fraction)

    #remaining numbers are integers
    #can be multiplied and passed to output
    else:                                                
        i = int(i)  
        i = i * multiplier
        scaled_list.append(i)

#OUTPUT
#print new recipe
n=0
while n < len(quant_list):
    print(format(scaled_list[n], "^7") + format(unit_list[n], "8") + format(item_list[n]))
    n = n + 1

#print number of servings for scaled recipe
print(f"""
Serves {servings * multiplier}""")

This is how the final program looks:

reCreating Mastermind in Python IDLE using built-in random module

Mastermind is a two-player game revolving around a secret combination of four colored pegs. One player makes a series of guesses at that combination and the correctness is evaluated by the second player utilizing black and white pegs. A black peg indicates a correctly-colored peg in the correct position, and white peg indicates a correctly-colored peg in the wrong position.

Combinations and guess in this program will be indicates by four-letter strings, where each letter is the initial of a color: Red, Yellow, Blue, Green, Purple, White


The first step in building the program requires some general housekeeping – defining variables, importing necessary modules, and developing systems to prevent errors or impossible games.

colors = ['R', 'Y', 'B', 'G', 'P', 'W']
import random

def is_valid(combination):
    '''identifies whether combination is valid:
       four letters from the list defined above
       Returns True or False accordingly'''
    if len(combination) != 4:
        return False
    else:
        valid = True
        for letter in combination:
            if letter not in colors:
                valid = False
        return valid

def input_combination():
    '''asks the user for a combination,
       and continues to ask if the input is invalid'''
    combination = input("What would you like to guess? ")
    while not is_valid(combination):
        print("Guess must be four letters from "+ ", ".join(colors))
        combination = input("Try again: ")
    return combination



Now it’s time to use the random module to quickly and easily create lists of valid combinations without having to type everything out by hand. Also allows for easy modification of code, such as changing the valid colors list.

def all_combinations():
    '''generates a list of all valid combinations'''
    combo_list = []
    for first in colors:
        for second in colors:
            for third in colors:
                for fourth in colors:
                    combo_list.append(first+second+third+fourth)
    return combo_list

def random_combination():
    '''Generates random combination of four colors,
       random.select() is NOT chosen, because it disallows duplicaties'''
    return (random.choice(colors) + random.choice(colors)
         +  random.choice(colors) + random.choice(colors))




Examine each position to see if colors match (black pegs), removing matching pegs from both lists when found so as not to score extra white pegs. Then, repeat for white pegs and return both.

def check_guess(guess, answer):
    '''compares a guessed combination with the right answer'''
  
#makes editable copies of each list  
    answer_copy = list(answer)     
    guess_copy = list(guess)
    black = 0
    white = 0

    for ind in range(len(guess_copy)):
        if guess_copy[ind] == answer_copy[ind]:
            #removes correct answers from lists so won't be double counted
              when checking white pegs
            guess_copy[ind] = "?"
            answer_copy[ind] = "*"
            black = black + 1

    for ind in range(len(guess_copy)):
        if guess_copy[ind] in answer_copy:
            answer_ind = answer_copy.index(guess_copy[ind])
            answer_copy[answer_ind] = "@"
            guess_copy[ind] = "#"
            white = white + 1

    return black, white



Now to create a function where the keyboard user tries to guess a combination chosen by the computer

def user_guesses():
    '''A game of mastermind where the keyboard user
       tries to guess a combination chosen by the computer.'''

    computer_pick = random_combination()
    user_pick = input_combination()
    user_tries = 0
    while user_pick != computer_pick:
        blackwhite = check_guess(user_pick,computer_pick)
        print(f"""Black pegs: {blackwhite[0]}
White pegs: {blackwhite[1]}""")
        user_tries = user_tries + 1
        user_pick = input_combination()   

    print("You got it!")
    return user_tries



Now to create a function where computer guesses at combination chosen by keyboard user

def computer_guesses():
    # Start with all possible guesses
    pool = all_combinations()
    computer_pick = random_combination()
    black_user = 0
    computer_tries = 0
    
    # If the answer is not yet known
    while len(pool) > 1:
        print("My guess is " + computer_pick)
        computer_tries = computer_tries + 1
        black_user = int(input("How many black pegs? "))
        white_user = int(input("How many white pegs? "))
        for n in reversed(range(len(pool))):
            blackwhite = check_guess(pool[n],computer_pick)
            if blackwhite[0] != black_user or blackwhite[0] + blackwhite[1] != black_user + white_user:
                pool.pop(n)
        if len(pool) > 1:    
            computer_pick = random.choice(pool)

    # Report the solution when found
    if len(pool) == 0:
        print("I could not find any answer that matches your responses.")
    else:
        print("I think I have it!  Your combination is ",pool[0])

    return computer_tries


One final block of code that will be used to check how the logic of the rest of the code is functioning, ie checking that the program will return the correct number of black and white pegs.

def test_check_guess(guess,answer,result,message):
    response = check_guess(guess,answer)
    if response == result:       # It worked!
        return True
    elif len(response) != 2:
        print('Did not get black and white pegs comparing',guess,'to',answer)
        return False
    else:
        print('Got response of',response,'comparing',guess,'to',answer)
        print('Correct result would be',result,'--',message)
        return False

def test_checker():
    test_check_guess('RBBB','RGGG',(1,0),'cannot find the black peg')
    test_check_guess('BBBR','GGGR',(1,0),'cannot find the black peg')
    test_check_guess('RRBB','RRGG',(2,0),'did you find both black pegs?')
    test_check_guess('RBBR','RGGR',(2,0),'two black pegs cannot also be white')
    test_check_guess('RRRR','RRRR',(4,0),'four black pegs cannot also be white')
    test_check_guess('RRBB','GGRR',(0,2),'only one white peg per guess peg')
    test_check_guess('RBBB','GGRR',(0,1),'only one white peg per guess peg')
    test_check_guess('RRBB','GGGR',(0,1),'only one white peg per answer peg')
    test_check_guess('RBYW','BYWG',(0,3),'must be able to find all the colors')
    test_check_guess('RBYW','WRBY',(0,4),'must be able to find all the colors')
    test_check_guess('RBYW','RYWB',(1,3),'do not count black peg as white also')
    test_check_guess('RRRR','RGGG',(1,0),'do not count black peg as white also')
    test_check_guess('RGGG','RRRR',(1,0),'do not count black peg as white also')
    test_check_guess('RRRR','GGGR',(1,0),'do not count black peg as white also')
    test_check_guess('GGGR','RRRR',(1,0),'do not count black peg as white also')

test_checker()


Finally, I put it all together. Opening the program will initiate a two-part game, where the keyboard user first tries to guess the computers combination and then has the computer guess at their combination. Whoever (user or computer) guesses the opponent’s combination is fewer tries wins!

print("Welcome to Mastermind!")
yesno = 'y'
while yesno in ['y','Y']:
    print("Try to guess my combination!")
    print("The color choices are: " + ", ".join(colors))
    user_tries = user_guesses()
    print("My turn, to guess at yours!")
    computer_tries = computer_guesses()
    if user_tries < computer_tries:
        print(f"You win! I got it in {computer_tries} and you took {user_tries}")
    elif user_tries > computer_tries:
        print(f"You lose! I got it in {computer_tries} and you took {user_tries}")
    else:
        print(f"We tied! We both took {computer_tries}")
    yesno = input("Play again? ")


Good luck! The computer can usually guess the correct combo in roughly 4 tries!