6
\$\begingroup\$

I have a program that gives the player a random U.S. state, asks for its capital, and keeps score. This keeps going until the player decides to stop playing. At the end, it prints out both the amount of correct answers and amount of incorrect answers.

I took some of the advice into account that I got on my other posts and put the dictionary within the main function (I had it in its own function before), but was wondering if there was a more efficient way to do the while loop that is the basis of the game, as well as if me converting the dict to a list is an okay way to solve this problem.

MIN = 0
MAX = 49

import random

def main():
    
    stateDict = {'Alabama': 'Montgomery',
              'Alaska': 'Juneau',
              'Arizona': 'Phoenix',
              'Arkansas': 'Little Rock',
              'California': 'Sacramento',
              'Colorado': 'Denver',
              'Connecticut': 'Hartford',
              'Delaware': 'Dover',
              'Florida': 'Tallahassee',
              'Georgia': 'Atlanta',
              'Hawaii': 'Honolulu',
              'Idaho': 'Boise',
              'Illinois': 'Springfield',
              'Indiana': 'Indianapolis',
              'Iowa': 'Des Moines',
              'Kansas': 'Topeka',
              'Kentucky': 'Frankfort',
              'Louisiana': 'Baton Rouge',
              'Maine': 'Augusta',
              'Maryland': 'Annapolis',
              'Massachusetts': 'Boston',
              'Michigan': 'Lansing',
              'Minnesota': 'Saint Paul',
              'Mississippi': 'Jackson',
              'Missouri': 'Jefferson City',
              'Montana': 'Helena',
              'Nebraska': 'Lincoln',
              'Nevada': 'Carson City',
              'New Hampshire': 'Concord',
              'New Jersey': 'Trenton',
              'New Mexico': 'Santa Fe',
              'New York': 'Albany',
              'North Carolina': 'Raleigh',
              'North Dakota': 'Bismarck',
              'Ohio': 'Columbus',
              'Oklahoma': 'Oklahoma City',
              'Oregon': 'Salem',
              'Pennsylvania': 'Harrisburg',
              'Rhode Island': 'Providence',
              'South Carolina': 'Columbia',
              'South Dakota': 'Pierre',
              'Tennessee': 'Nashville',
              'Texas': 'Austin',
              'Utah': 'Salt Lake City',
              'Vermont': 'Montpelier',
              'Virginia': 'Richmond',
              'Washington': 'Olympia',
              'West Virginia': 'Charleston',
              'Wisconsin': 'Madison',
              'Wyoming': 'Cheyenne'}
              
    statesList = list(stateDict)

    correct = 0
    incorrect = 0

    again = 'y'
    
    while (again == 'y'):
        num = random.randint(MIN,MAX)
        state = statesList[num]
        print('\n' + state)
        ans = input("\nEnter the capital of this state: ")
        if (ans == stateDict[state]):
            correct += 1
            print("Correct")
            again = input("Type y to play again, anything else to stop: ")
        else:
            incorrect += 1
            print("Incorrect")
            again = input("Type y to play again, anything else to stop: ")

    print("\nYou have",correct,"correct answers")
    print("\nYou have",incorrect,"incorrect answers")
                  

main()
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

You are on the right track in several respects -- for example, putting all behavior/actions inside of functions. That said, constants are fine at the top level, and one advantage of putting them there is allowing the reader to focus on the algorithm without the visual heaviness of a big data structure.

Don't force your user to worry about capitalization: lowercase the data and then use Python's built-in string methods to capitalize geographic names for presentation to the user.

Opinions vary on this, but I recommend using natural language as the primary (not sole) guide when picking variables: in most contexts, for example, capitals is a better name than stateDict; states better than statesList. Sometimes a data-type suffix can disambiguate names, but don't make that your first impulse.

The random library includes a choice() function, so you do not need MIN, MAX.

I recommend less chatty messages to users in interactive programs. Once the user understands the basics (computer prints state, user types capital), they do not require verbose instructions. Ruthless text cutting is a virtue in these situations.

Nest the main() call under the conditional shown below. This mechanism will make your scripts amenable to importing, easier debugging, and automated testing should you ever need it.

Whenever you find yourself creating multiple closely related variables (correct and incorrect), consider whether you can unify them under a data structure. Sometimes taking that approach will suggest further refactoring to simplify code (for example, by reducing if-else branching). In the illustration below, I started just by focusing on unifying the correct/incorrect tallies, but that inspired other simplifications in the reporting of overall results.

import random

# Your dict, but lowercase.
capitals = {
    'alabama': 'montgomery',
    'alaska': 'juneau',
    ...
}

def main():
    # Setup.
    states = tuple(capitals)
    tally = {True: 0, False: 0}
    results = {True: 'Correct', False: 'Incorrect'}
    result = ''

    # Quiz loop.
    print('State capitals quiz:')
    while True:

        # Get user reply.
        state = random.choice(states)
        ans = input(f'{result}{state.title()}? ').lower()
        if ans in ('', 'q', 'quit'):
            break

        # Tally result.
        res = ans == capitals[state]
        tally[res] += 1
        result = results[res] + '. '

    # Print overall results.
    fmt = 'Results:\n- {}: {}\n- {}: {}'
    args = (results[True], tally[True], results[False], tally[False])
    print(fmt.format(*args))

if __name__ == '__main__':
    main()

Finally, people who take quizzes generally want to see the right answers. You could print the correct result immediately in response to a wrong reply. Or you could keep track of all states with bad replies and print correct answers at the end.

\$\endgroup\$
5
  • \$\begingroup\$ Lots of very good points - thank you! \$\endgroup\$
    – am2021
    Apr 18, 2021 at 0:46
  • 3
    \$\begingroup\$ Good answer, +1. Two small suggestions: 1. Move trailing spaces from the values in results to the f-string argument to input when assigning ans. 2. Assign ans to input(...).lower() to make differently capitalized quit commands work. \$\endgroup\$ Apr 18, 2021 at 10:18
  • 1
    \$\begingroup\$ Mostly good, though your fmt should not be hard coding the correct and incorrect strings - you have those in results. \$\endgroup\$
    – Reinderien
    Apr 19, 2021 at 4:20
  • \$\begingroup\$ I would perhaps consider implementing the dict as a CaseInsensitiveDict. I think it ships with the requests module but can be implemented in 20 lines of code. \$\endgroup\$
    – Kate
    Apr 19, 2021 at 19:30
  • \$\begingroup\$ @Anonymous A dependency or implementing a custom container just to avoid calling str.title() and str.lower()? I'll let the OP consider that tradeoff. \$\endgroup\$
    – FMc
    Apr 19, 2021 at 23:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.