8
\$\begingroup\$

I've been working to create a game in Python similar to Plague Inc. and instead of just writing one big clump of code and then getting it reviewed, I wanted to do it stage-by-stage and make it a little easier. Also, reviews might possibly help me later on in the development process.

import datetime
import random 

countries = [
   dict(Name = "China", Population = "1,363,560,000"),
   dict(Name = "India", Population = "1,242,070,000"),
   dict(Name = "United States", Population = "317,768,000"),
   dict(Name = "Indonesia", Population = "249,866,000"),
   dict(Name = "Brazil", Population = "201,032,714"),
   dict(Name = "Pakistan", Population = "186,015,000"),
   dict(Name = "Nigeria", Population = "173,615,000"),
   dict(Name = "Bangladesh", Population = "152,518,015"),
   dict(Name = "Russia", Population = "143,700,000"),
   dict(Name = "Japan", Population = "127,120,000"),
   dict(Name = "Mexico", Population = "119,713,203"),
   dict(Name = "Philippines", Population = "99,329,000"),
   dict(Name = "Vietnam", Population = "89,708,900"),
   dict(Name = "Egypt", Population = "86,188,600"),
   dict(Name = "Germany", Population = "80,716,000"),
   dict(Name = "Iran", Population = "77,315,000"),
   dict(Name = "Turkey", Population = "76,667,864"),
   dict(Name = "Thailand", Population = "65,926,261"),
   dict(Name = "France", Population = "65,844,000"),
   dict(Name = "United Kingdom", Population = "63,705,000"),
   dict(Name = "Italy", Population = "59,996,777"),
   dict(Name = "South Africa", Population = "52,981,991"),
   dict(Name = "South Korea", Population = "50,219,669"),
   dict(Name = "Colombia", Population = "47,522,000"),
   dict(Name = "Spain", Population = "46,609,700"),
   dict(Name = "Ukraine", Population = "45,410,071"),
   dict(Name = "Kenya", Population = "44,354,000"),
   dict(Name = "Argentina", Population = "40,117,096"),
   dict(Name = "Poland", Population = "38,502,396"),
   dict(Name = "Sudan", Population = "37,964,000"),
   dict(Name = "Uganda", Population = "35,357,000"),
   dict(Name = "Canada", Population = "35,344,962"),
   dict(Name = "Iraq", Population = "34,035,000"),
   ]

current_date = datetime.datetime.strftime(datetime.date.today(), "%m-%d-%Y")   

print "Welcome to a Python version of Plague Inc."
user = raw_input('\nWhat is your name? ').title() # for later on in the game
print '\nCountries to choose from are\n'
print "Country".ljust(15),"Population\n"
for country in countries:
    print country["Name"].ljust(15), country["Population"]
startcountry = raw_input('\nWhat country would you like to start in? ').title() 

if startcountry == 'Random': # random.sample can be used for more than one country later on
    startcountry = random.choice(countries)

diseasename = raw_input('\nWhat would you like to name your disease? ').title()

print '\nNews Bulletin'.ljust(45), current_date
print '-' * 55
print 'An unknown disease named', diseasename, 'has struck', startcountry, 'by storm.'
print 'Doctors and scientists will need to collaborate to find out more information about this disease.'
\$\endgroup\$
1
  • \$\begingroup\$ I suggest you put static game information in a text file and import the information at runtime. \$\endgroup\$ Apr 29, 2014 at 20:56

4 Answers 4

20
\$\begingroup\$

Some thoughts:

  • As the game goes on, you’ll need to record more information about the countries. Whether they’re doing medical research, border closures, infection/mortality rates, etc. Rather than storing all this information in a list of dictionaries, I’d suggest creating objects for each country. Here’s a simple example:

    class Country(object):
        def __init__(self, name, population):
            self.name = name
            self.population = population
    
    china = Country("China", 1363560000)
    

    Also follow martijnn2008’s advice, and load the countries from an external file (probably a CSV). Keeping the data separate from the code is generally good practice. You’d probably be better off having a small dataset in the program to start with, as you build the basic logic, then start using the full data set later.

    As unholysampler suggests, use an int for the population; this will make your life much easier later when chunks of the population start dying. You can always define a function that does nice comma-printing on your population data later.

  • Have different functions for parts of the game. For example, a start_game() function which just includes the code for setting up a new game, then another function news_bulletin() which prints a random news bulletin.

    This will make it easier to reuse this code later – for example, if the user gets to the end of the game and you want to ask "Play again?", you can just redirect them to start_game() rather than jumping through hoops.

  • Rather than expecting the user to enter a name that’s formatted exactly as it’s stored in your data, you could:

    • Do something to clean up messy input. For example, right now, "china" is an invalid input, but any reasonable person can see what they meant. Perhaps do something like

      if start_country.lower() in list_of_countries:
           # more code here
      

      And weirdness happens if I choose a country name which isn't on your list. You could perhaps just ask again and again until you get an answer, or just pick one at random for me. I just want to get on with the game!

    • Just offer them a choice from a numbered list. Here’s a very crude mockup of how that might work:

      Which country would you like to choose?
      1 China
      2 India
      3 United States
      Type the corresponding number: # wait for input
      3
      You have chosen "United States"
      
  • Magic numbers as arguments to ljust() throughout your code. Icky and fragile. Pull them out and make them variables, or better try to compute the necessary values on the fly.

    ETA: The problem arises because you’re assuming that the window is 55 characters wide (for example, the print '-' * 55 line). You should pull this out into a window_width variable, so you don't have problems later on (and if necessary, can adapt it for wider or narrower windows).

    What if, later, you want to add the Independent Former Republic of New South Nebelsbad? Stuff like that might cause you problems if you’re rigid about the width.

  • In your import statements, I would write

    from datetime import datetime
    

    Cuts out datetime.datetime statements, which just look weird. Also note that your MM-DD-YY may confuse players for whom that isn’t their default date system. How about

    >>> current_date = datetime.datetime.strftime(datetime.date.today(), "%d %B, %Y")
    >>> print current_date
    29 April, 2014
    

    That looks much more natural, and is unambiguous.

\$\endgroup\$
5
  • \$\begingroup\$ Can you just explain a little more on magic numbers, as I'm not quite sure what you mean. I just looked up formatting in python and used what was found. \$\endgroup\$
    – tda
    Apr 30, 2014 at 2:05
  • \$\begingroup\$ Btw, changing the datetime import statement breaks the code, as I've tried this in early development. \$\endgroup\$
    – tda
    Apr 30, 2014 at 2:15
  • \$\begingroup\$ @TjF: Huh, okay. Usually I use from datetime import datetime and then datetime.strftime, but it’s only a minor point. I’ll edit to clarify the magic number statement. \$\endgroup\$
    – alexwlchan
    Apr 30, 2014 at 6:31
  • 1
    \$\begingroup\$ The only suggestion I would make it to save the external information in XML form rather than csv, if only for easier serialization and visualization tools. I don't know much about python, but googling python xml makes xml look promising. It's certainly not the most efficient method of saving information, but it can be used to initialize data(countries) as well as saving game states. Then, optimizing memory usage can be worried about later. \$\endgroup\$
    – Shaz
    Apr 30, 2014 at 18:36
  • \$\begingroup\$ Thanks guys. I'll work on updating this and adding more to the game and post an update in a few weeks. \$\endgroup\$
    – tda
    May 1, 2014 at 1:37
12
\$\begingroup\$

Store the population as a number. If you want to include comas when the numbers are presented to the user, the UI code can add those in. When people start dying, trying to decrement a number stored in a string, that contains comas, is going to be a pain.


I would use a named tuple instead of a dictionary to store the data. You get access to all of the data with just .name instead of ["name"]. Additionally, your code will go bang sooner if you misspell a value's name. For example, you used Name and Population as the keys, so my attempt to use your dictionary look up would return None instead of telling me the property is not defined.


You have some magic numbers in you UI code. If you add another country with a much larger name, you need to adjust your text padding in multiple places.


You don't tell the user that Random is a valid option when selecting a country. You also don't validate that the entered country name is valid.

\$\endgroup\$
2
  • \$\begingroup\$ Did you mean a list of namedtuples or a namedtuple with two lists? \$\endgroup\$
    – Adam
    Apr 29, 2014 at 22:32
  • 1
    \$\begingroup\$ @codesparkle: Make one namedtuple to represent a country. Then make as many instances of that as needed, one for each country. \$\endgroup\$ Apr 29, 2014 at 22:37
6
\$\begingroup\$

As a someone who learnt to code in BASIC, your Python code in this question and a couple of others looks a lot like you also have experience of BASIC. A lot of former BASIC users find they can write things in Python which are pretty close to what they are used to, especially if they are familiar with a structured form of BASIC like QBasic or early VB (more recent versions of VB are quite different).

There is nothing wrong with this - Python was influenced by BASIC, including in its desire to be an easy-to-learn language for programming beginners. You should also ignore anyone who looks down on BASIC or on you for having started in BASIC. However, to most Python programmers this code looks quite weird and hard to read. This is because you are structuring your code in a way which is fairly unusual for Python, but normal for BASIC. This means you aren't using many of the language features in Python which make Python powerful and useful and friendly.

To imagine why the 'feel' of your code seems weird to Python programmers, imagine translating a sentence into another language, by translating each word in a dictionary, and keeping them in the same order. For some very short sentences, this might work:

Donde  esta  la   biblioteca ?
Where  is    the  library    ?

For slightly longer sentences, the sentence will sound strange but will probably be understandable:

Die Proletarier  haben nichts  in ihr  zu verlieren als ihre  Ketten.
The proletarians have  nothing in them to lose      as  their chains.

Compare with the correct translation: "The proletarians have nothing to lose but their chains."

If the sentence is too complicated or long, it might become almost incomprehensible:

Le  bon  sens  est la  chose du     monde la  mieux partagée : car   chacun   pense  en être  si bien pourvu,   que  ceux  même qui sont les plus difficiles à  contenter  en toute autre chose, n'ont    point coutume d'en  désirer   plus qu'ils    en ont.
The good sense is  the thing of the world the best  shared   : coach each one thinks in to be if well supplied, that those same who are  the more difficult  at to content in all   other thing, not have point custom  of in to desire more that they in have.

Now, first of all, you don't need to worry all the time about 'feel' or 'style'. While you are learning, and especially if you are writing toy programs or experiments only for yourself, you can write how you like. Ultimately it is more important that your code does what you want it to do, than that it is elegant or easy to read. As you try and code new things, you will pick up some ways of writing things that work in Python and make your code neater and better structured.

However, you are on CR because you want to write better code. One part of this is structure and another part is style. (There are many other important parts.) These are kind of linked - structure is kind of the style of whole programs, and style is kind of the structure of single lines, or things like variable names and spacing. Together they form readability. When you improve this, you will make it easier for others to read and discuss your code, but you will also make it easier for yourself to edit and maintain your code. You will often see readability mentioned on CR and on StackOverflow and Programmers.SE, including lots of places where people put forward different code solutions just to improve readability, without affecting at all how the code works.

I think you have already picked up lots of style things since your first questions on CR. PEP8, the Python style guide which has been recommended to you is an important tool, as is a good editor which highlights syntax and does indenting for you. If you look at a few lines of your code, you should be able to see quickly what the different elements of the code are - where the lines start and end, what is a definition, what are arguments to functions, etc.

As for structure, the most important element you should be using is the function. Functions are fundamental to most programming languages. A function in Python can be used to take an argument and modify it or return a derived object, it could allow some piece of code to be repeated exactly the same each time (essentially a subroutine), it could take several arguments and vary what it does depending on them, or anything in between. You should use functions to get rid of pieces of code which are repeated in several places, whether with variations or not, but you can also move code which is only used once into a function.

It allows top-down programming, where you write a program like this:

setup()
print_intro()
user_info = get_user_choices()
play_game(user_info)

with all those functions left to be filled in later:

def setup():
   # todo

def print_intro():
   # todo

...

When you do fill them in, you will use further unwritten functions.

def setup():
   countries = load_country_data()
   initialize_timer()
   initialize_save_file()

and so on, until you get down to functions which do a single simple thing, which you just write.

Once you've mastered functions in your code, you can start looking at how to use class and other Python constructs. As you do this, you will go from thinking of a certain program structure in your head, and then translating it into Python properly, to thinking in Python automatically. You will see that Python users have a specific term for the ways they like the style, structure and appearance of Python code to be: 'Pythonic'. Eventually you will have an instinct for what is and isn't Pythonic, and will even start to disagree with others as to what truly Pythonic code should be like.

The most important resource by far is reading other people's code. Code on CR is an ok place to start, but lots of it might be written by other beginners. Questions on SO or Programmers.SE might give more examples of good code, and lots of discussion of how to write good code. There are lots of other places you can find examples of Python code, written to illustrate things or as short working programs, probably including many simple games like the ones you have been working on. github is definitely a place to start looking for code to read. You can even try and read things which you don't understand what they do - library code or pieces of big programs. Though you won't get all of it, you will see (if the code is decent and decently commented) how it is structured and be able to follow the flow of execution and data.

Finally some of the whole concept of different styles and structures between languages might seem unintuitive and frustrating to you, as if your code was being picked apart for no good reason. If you go on to learn further programming languages, you will see that each has a slightly different (sometimes very different) 'feel' and 'style'. This actually affects how people use the tools so much that people choose between languages not because of differences in what they do, but based on how things can be expressed, and whether this chimes with their personality and how they like to write. Coders who speak a few languages know how to pick up the 'idioms' in a new language quickly, and start writing in a new style which fits it well.

\$\endgroup\$
0
\$\begingroup\$

I would recommend to try to use a windowed programme for this by either using pygame or tkinter. That way you could include an interactive map and then capture mouse clicks on each country just like in the real game. This is easier in pygame that tkinter in my opinion but u need to install pygame.

\$\endgroup\$
1
  • \$\begingroup\$ I would, but I am using Pythonista for iPad, and they don't have these modules yet. \$\endgroup\$
    – tda
    May 1, 2014 at 20:15

Your Answer

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

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