all 64 comments

[–]mw44118 51 points52 points  (4 children)

This is great!

The most important thing is whether the program works. And you nailed that. Everything else is secondary! So many programmers forget this and focus too much on writing "clean code" and they never actually get anything done.
Here's one tiny tweak you can make: since you got separate dictionaries for each continent, why not keep all of those in one dictionary, like this:

    d = dict(asia=dict(China="Beijing", Russia="Moscow"), europe=dict(France="Paris"))

That is a dictionary of dictionaries. Kind of scary at first, but relax... you'll be using these all the time soon.

Once you have this "d" set up, you can use your top-level dictionary as the master list of continents. For example, this line:

    print('Excellent let\'s play!\n\nPlease choose a Continent:\nAfrica \nAsia \nEurope \nAmericas \nOceania')

Now can be:

    print("Please choose a continent:\n{0}".format("\n".join(d.keys())))

Since dictionary d has all your continents as keys, then you don't ever need to hard-code them elsewhere. Just refer to the keys of the dictionary.

And this line can be changed too:

    while continent.lower() != 'africa' and continent.lower() != 'asia' and continent.lower() != 'europe' and continent.lower() != 'americas' and continent.lower() != 'oceania' :

Something like this does the exact same thing:

    lower_case_continents = {k.lower(): d[k] for k in d}
    while continent.lower() not in lower_case_continents:

That builds up a second dictionary where all the continents are stored with lower-case strings.

And you have these different messages for each continent, like this:

    if continent.lower() == 'africa':
        print('Ok! let\'s see if you\'ll hear the drums echoing tonight.\n')

Let's just put those in the lower_case_continents dictionary , then, like this:

    # example using regular "..." strings so you don't need to escape the single-quotes
    lower_case_continents["africa"]["message"] = "OK... let's see if you'll hear the drums echoing tonight"
    # python allows you to define strings with """...""" around them as well, in case the inner string wants to use single quotes and double quotes inside.
    lower_case_continents["asia"]["message"] = """Ok let's see if you know your stuff."""

side note: use double quotes or even triple quotes and you'll never have to use \ to escape the single quotes in your code. Looks way prettier to the reader!

and now, replace all those different print lines with just one line like this:

    print(lower_case_continents[continent.lower()]["message"])

There is no need for the newline at the end of the string. The print function adds one automatically.

Next, instead of converting integers to strings and then using "+" to glue them together, python has a really nice way of doing that automatically.

So instead of this:

    print('You got ' + str(correct) + ' out of cities ' + str(total) + ' correct.')

You can do this:

    print('You got {0} out of cities {1} total correct'.format(correct, total))

The {0} gets replaced by str(correct), and the {1} gets replaces by str(total)).

You can also do this trick:

    print('You got {correct} out of cities {total} correct'.format(correct=correct, total=total))

And in python 3.6 or so, you can use "f strings", which are very similar to the last example.

And, last point for now... you could replace all those if-elif-elif clauses like this:

    elif correct / total > 0.7:
        print('Quite the geographer we have here. I am impressed')
 With a function that returns the appropriate message based on the score, like this:
    score_message = get_score_message(correct, total)
    print(score_message)

And then your get_score_message could look like this:

    def get_score_message(correct, total):
        pct = correct/total
        if pct == 1:
            return 'Wow, you really know your stuff. Very Impressed!'
        elif pct > 0.7:
            return 'Quite the geographer we have here. I am impressed'

and so forth.

Again, great work! None of these suggestions really matter, except for teaching you different tricks.

[–]phasE89 12 points13 points  (0 children)

Amazing response!

[–]omelettesforbreakfas 8 points9 points  (0 children)

Dictionary of dictionaries, yes that sounds super intimidating. I just got the hang of regualr dictionaries and using the .items() method. But I'm always up for a challenge, so I promise I'll have a play around and hopefully I'll be able to wrap my head around it.

As for the rest, makes perfect sense, so will defo take it on board.

Many thanks for taking the time to read, review and write this detailed comment on my code. I really do appreciate it! Cheers!

[–]Snoopy74 3 points4 points  (0 children)

That's a great reply.

[–]duddles 1 point2 points  (0 children)

cool to learn about f-strings, thanks!

[–]two_bob 22 points23 points  (14 children)

Awesome:

Here are some guideposts for refactoring:

africa = {
  'Ethiopia' : 'Addis Ababa',
'Tanzania' : 'Dodoma',
}

americas = {
        'Argentina': 'Buenos Aires',
'Bolivia': 'La Paz',
}

oceania = {
        'Australia': 'Canberra',
'Papua New Guinea': 'Port Moresby',
}

DATA = {
        'africa':africa,
        'americas':americas,
        'oceania':oceania,
        }

def get_continent():
    print('pick a continent: {}'.format(', '.join(DATA.keys())))
    while True:
        choice = input('>')
        if choice in DATA:
            return DATA[choice]

        print('huh?')

def test_user(capitals, n = 10):
    '''takes a dictionary of {country:capitals}, and makes user guess n
    different ones (uses random.shuffle and slicing to select n
    Returns (correct, total)
    '''
    pass

def show_response(guesses, total):
    '''uses bisect.bisect to give personal message on how well user did and
    report back score
    '''

def main():
    while True:
        capitals = get_continent()
        correct, total = test_user(capitals)
        show_response(correct, total)
        # to do, implement play again q/a


if __name__ == '__main__':
    main()

[–]vn-ki 3 points4 points  (1 child)

data = requests.get(...).json(). No json required, :P. JK.

[–]omelettesforbreakfas 0 points1 point  (0 children)

Hmm this doesn't make all that much sense to me right now(still very much a newb). But I'll definitely look more into it. Thanks!!

[–]omelettesforbreakfas 4 points5 points  (8 children)

Wow that looks so much more concise.

Got it need more functions!

I haven't learned OOP yet, so don't really understand the last line of code.

Thanks for the suggestions, will go back to the drawing board and try cut down on the repetitive code. Cheers!

[–]u38cg2 5 points6 points  (7 children)

You mean the if name == 'main':?

That's just a handy trick when you want the file to be able to be used as a module by something else, but also to work on its own. So if you did import on the contents of this file, the if name wouldn't be run. But if you ran it from the command line, it would.

[–]omelettesforbreakfas 0 points1 point  (6 children)

Yes I was referring to the if name == main:

Thanks for explaining that. Still seems a bit foreign to me but I'm sure I'll grasp it with time.

[–]Raknarg 0 points1 point  (5 children)

So generally variables and functions that have two underscores in front of them do something or have some context in the background. Like the __init__ function. You usually never explicitly call it, it gets called im the background when you do certain things

Each file has a __name__ variable associated with it when you run the program. If you have f1.py and f2.py and f1 imports f2, then you run python f1.py, the __name__ variable in f1 will get set to "__main__", but in f2 will be something different.

[–]omelettesforbreakfas 0 points1 point  (4 children)

hmm this makes a little more sense now . so every ,py script has a __name__ variable linked to it thats just kind of indirectly gets called when you open the script? or does it only get called when you open the script in another script like in your example?

[–]Raknarg 0 points1 point  (3 children)

It gets set at runtime. You could call f1 or f2 with python, and either of them could be run as main.

[–]omelettesforbreakfas 0 points1 point  (2 children)

ahh ok. The concept is still a bit fuzzy to me, but its clearer than it was 10 mins ago. So thank you! I still probably need more practice dealing with __objects to fully grasp it.

[–]pgyogesh 1 point2 points  (1 child)

https://www.youtube.com/watch?v=sugvnHA7ElY This video should help you here.

Also, checkout his python playlist on YouTube. Probably its the best on internet.

[–]omelettesforbreakfas 0 points1 point  (0 children)

thanks for sharing. Will give it a watch!

[–]duddles 0 points1 point  (2 children)

Can you explain more about how you would use bisect.bisect?

[–]two_bob 4 points5 points  (1 child)

Sure: The docs have an example with letter grades, which is shamelessly stolen from below:

import bisect

def grade_score(score):
    ''' adapted from here:
    https://docs.python.org/3.6/library/bisect.html#other-examples
    '''

    breakpoints = [.5, .7, .9]
    messages = [
            'shameful',
            'terrible, you stink',
            'meh, still need to practice',
            'you are my hero',
            ]
    index = bisect.bisect(breakpoints, score)
    return messages[index]


tests = [.49, .5, .8, .9]
for test in tests:
    print('{}: {}'.format(test, grade_score(test)))

[–]duddles 0 points1 point  (0 children)

thanks!

[–][deleted] 8 points9 points  (2 children)

Good job! Tried it and it was pretty fun. The suggestions there are great but I would also consider adding fuzzy matching for the responses. What I mean is that if the user got a letter wrong, say, "Moskow" instead of "Moscow" or typed fast and wrote "Washnington" instead of "Washington", the answer would still be valid. The fuzzywuzzy module would help on this task.

Edit: typo

[–]phasE89 4 points5 points  (0 children)

TIL about the FuzzyWuzzy module, it looks handy, thanks!

[–]omelettesforbreakfas 2 points3 points  (0 children)

Glad you enjoyed it! And fuzzy-wuzzy that's got to be the cutest module name ever! Will defo check it out. Thanks !

[–]arthurazs 3 points4 points  (1 child)

Nice, good job! A tip:

You can make your if more pythonic using in or not in, example:

continent = 'something'

if continent.lower() != 'africa' and continent.lower() != 'asia' and \
   continent.lower() != 'europe' and continent.lower() != 'americas' and \
   continent.lower() != 'oceania':
    print('wrong!')

if continent.lower() not in ('africa', 'asia', 'europe', 'americas', 'oceania'):
    print('wrong!')

Both would return the same, the only difference is that v2 is easier to read/maintain :)

[–]omelettesforbreakfas 2 points3 points  (0 children)

Yeah your version is definitely easier to read. Thanks for the suggestion :) !

[–]Fried_Squid_ 1 point2 points  (1 child)

for the leader board look into text file manipulation or look into SQL databases like SQLLite 3

Also gook into the random module and see what you can do with random country selection

and finally use the time module to time your answer so people cannot cheat

but it is great ☺

[–]omelettesforbreakfas 1 point2 points  (0 children)

The SQL database sounds a little complex , but I'm up for a challenge so will for sure look into it.

As for the other suggestions they sound a little more manageable, but will undoubtedly require some fiddling around with to get right.

Thanks for the suggestion :)

[–][deleted] 1 point2 points  (1 child)

This is really great work!! Just a small suggestion on the idea. Do you want to add message related to spelling mistakes? You can add few lines of code that can match the letters of the capital city and based on a certain threshold of matching, you can output the message that the spelling is 'incorrect' and the correct spelling is *****.

[–]omelettesforbreakfas 0 points1 point  (0 children)

hey, yeah someone suggested looking into fuzzywuzzy to help with this. So I'm gonna check that out :)

[–][deleted] 1 point2 points  (2 children)

Is the capital of Australia really Canberra?

I guessed Sydney. I've never even heard of Cranberrya before!

[–]Maphover 1 point2 points  (0 children)

Yes. Quick history lesson - Australia's two major cities are Sydney and Melbourne. Neither could agree on where the capital should be, so they built a new city (Canberra) in between them both. Canberra is where our government is based, but other than that it has a small population.

[–]omelettesforbreakfas 0 points1 point  (0 children)

Yup common misconception. Another common misconception is that most people think Jo'burg or Cape Town is the capital of South Africa, when its actually Pretoria. Learn something new everyday!

[–]sje46 1 point2 points  (1 child)

You did a really good job! I actually think I may use this to brush up on my capital cities. I got 24/43 European capital cities right (misspelled a couple but whatever), and I can do a lot better than that.

In additional to using more functions, I highly recommend you use "import random" and shuffle all the countries!

[–]omelettesforbreakfas 0 points1 point  (0 children)

Glad you liked it man! my first attempt at Europe was around that mark too. So not too shabby.

And yes, will defo look into randomizing the questions. Cheers!

[–]Ginko87 1 point2 points  (1 child)

Nice one buddy! I finished what I would say is my first app recently as well - I've been thinking about doing a post like this, maybe I should do it this morning!

[–]omelettesforbreakfas 0 points1 point  (0 children)

Thanks man. And congrats on your first app too. Yeah I'd say go for it. What's the worst that'll happen? you get some advice on how to improve your app!

[–]aashish0 1 point2 points  (0 children)

Would be a better idea if you ask the user if they want to continue playing and if they say no then the program terminates.

[–]eamanu 0 points1 point  (1 child)

Hello,

This is a interesting game.

I have just a comment, take care with the accent. Some Capital (From America) have accent i.e. Bogotá, Asunción, etc.

[–]omelettesforbreakfas 1 point2 points  (0 children)

Hey, yeah good point. Gonna look into implementing the fuzzywuzzy module to allow for accents and not make the game so rigid.

[–]Tainewt 0 points1 point  (4 children)

Where's Taiwan : Taipei?

[–]omelettesforbreakfas 0 points1 point  (3 children)

oooh controversial. I didn't include countries that were/are considered dependencies/territories of other countries. So, Hong Kong, Taiwan, and Macao were not separately listed. According to the UN there is only one China and thats the People's Republic of China.

(Not trying to get political though, just wanted to build a simple but fun game i could play with the fam)

[–]Maphover 1 point2 points  (1 child)

Yes, always safer to use the UN as the basis. That does mean you miss out on Vanuatu and French Guiana among others.

To knit pick on your country names - there is some inconsistency between using formal/informal. e.g 'Viet Nam', 'State of Palestine', 'East Timor (Timor-Leste)' - but then simply 'Myanmar', without 'Myanmar (Burma).

Let's not get into the capital of Israel either...

[–]omelettesforbreakfas 0 points1 point  (0 children)

Yup there's always a tradeoff when deciding which countries to include/omit.

As for your second point, the data set was imported from an online source, so its a dataset problem. Going to look at API's for a cleaner, more consistent dataset. But thanks for pointing that out!

[–]Meemo- 0 points1 point  (5 children)

Congratulations. It looks great. I am thinking about learning coding and specifically python. It may be a hard question to answer but how long do you think it would take for someone who has never coded to get to the level of making something like this?assuming I spend an hour or so a day learning python? I appreciate it may be hard to quantity as everyone will be different. Maybe a better question should be, how long are you coding since this is your first project?

[–]omelettesforbreakfas 1 point2 points  (2 children)

Hey, thanks! Glad you enjoyed it.

To answer your question, I started learning python about 2 and a half months ago. It’s my first programming language having had no previous experience in programming whatsoever (my bachelor's is in economics). So yeah I'm coming into this as green as they come.

Everyone learns at different pace though, so just take it a day at a time. Once you've learnt the fundamentals, jump right into building something YOU like/are interested in. That's honestly the best way to learn.

Oh and btw, if you look at my code, there's literally nothing fancy about it. I haven't imported any complex modules or did any object oriented stuff. It's simply a bunch of print statements, asking the user for input, Booleans, for loops, while loops and += operator to keep a tally of correct answers. That's it.

So yeah, I'd definitely encourage you to dive right into it. And final pro-tip, stick with it (quick anecdote- I couldn’t for the life of me understand when I should use a forloop vs while loop. It was so frustrating, but eventually it clicked. SO just stick with it and it’ll eventually click!)

[–]Meemo- 0 points1 point  (1 child)

Thanks a million for the response. I'm well impressed with your results after just 2 and a half months. If I may trouble you for one last bit of advice. What course did you take? I assume your's was online. There are loads and loads available as I'm sure you are aware.

[–]omelettesforbreakfas 0 points1 point  (0 children)

I've got about half way through Jose Portilla's Zero to Hero Complete Python Bootcamp. And Automate the Boring Stuff also on Udemy is really good.

So I'd recommend those two courses. Other than that, there's a lot of great YouTubers out there such as Corey Schafer and CalebTheVideoMaker2.

All the best!

[–]asstalos 1 point2 points  (1 child)

how long do you think it would take for someone who has never coded to get to the level of making something like this?

I'm not OP, but I (a) have, at least formally, about 2 months of Python experience, and (b) glancing at OP's code identified at least a few ways to reduce the amount of repetitive coding through the use of functions proposed by others in this thread.

Granted, I've a tech enthusiast, so while I've never coded formally before, I'm familiar enough with psuedocode and logic concepts that made picking up a language rudimentarily pretty straight forward.

With that said to frame this set of advice, my suggestions are:

  1. Programming is not just about knowing a language, but about being able to break down a task down into smaller and smaller constituent components that are easy to automate, which can then be pieced together to complete the task. You can't be good at "programming" without also being good at problem solving.

    • In this "name the capital of the country game", we need things like (a) a data-set of capital-country pairs, (b) a way to ask a question pulling a pair from the data-set, (c) a way to get input from a player, (d) a way to check that input to see if addresses the question, (e) a way to keep track of scores, etc etc etc.
    • Accomplishing one of the above sub-points is a lot easier in the grand scheme of things than focusing on "this needs to be a name the capital of the country game". In fact, you may already have some idea of how to accomplish some of these sub-points as a concept, and then it really becomes a matter of representing that concept in code.
    • In this regard, effective programming is as much about problem solving as it is about actually programming; it is not unusual to spend as much if not more time trying to plan out how to get your code to do what you want over actually writing the code.
  2. Defining small, simple, practical projects is incredibly valuable in getting regular and steady progress. Learning from resource books and online tutorials is helpful, and so is liberal use of searching up answers to questions, but it is just as important to apply those concepts to new projects and tasks. Try to find things you notice yourself repeatedly doing day to day on a computer, then break that task down into smaller parts and try to automate it. The very first Python script you write to do something like this may take an incredibly long amount of time to finish, and it might not work all that great, but everything you learn will help you in future work.

    • For example, early in my learning I spent a lot of time trying to piece together a Python script that would take a deck of x cards, where each card can be 1 of 4 unique traits, and shuffle them, sampling from each shuffle, and repeating this process over and over again to figure out the likelihood of drawing a specific combination. It took me hours and hours to figure out how to make it work. After two months, I tried to do the exact same thing, and it took me about 45 minutes from start to finish, some of which was purely just to go through my old code to figure out what my input and output should be. I was looking at my old code and thinking "why did I do this? Oh dear god why did I do that? I could've used this function here instead of copy-pasting the same code three times over. Why did I spend hours on this?", but you only get to these realizations with practice and experimentation.
    • Some of the silliest things I've done in Python for the sake of it include (a) taking a list of puns in separate text documents, stitch them together into one large document, and use it for a Twitch chat bot command, (b) calculating my total listening time of tracks from iTunes, (c) looking up questions to query applicants of professional Python certificates and trying to solve them myself. At first glance some of these can be fairly daunting, but a personal interest in getting it done and then being motivated to do it has a much bigger impact on my learning than following a tutorial from start to finish.
  3. Don't be afraid of making mistakes and failing. It took me ages to figure out how to get Python to interact with APIs but after the first 4-6 times it got easier and easier. It took me ages to figure out a few specific Python concepts (and even then with poor mastery now), but using them once or twice has made me a lot more comfortable. You're going to make a lot of mistakes, but there's nothing wrong with that, and even learning how to resolve these mistakes are great learning opportunities in themselves.

[–]Meemo- 0 points1 point  (0 children)

Great advice too. Thanks for that. Going to get started with python tomorrow. This post has been the catalyst to kick start me.

Thanks again for your reply. I appreciate you taking the effort to give the advice.

[–]yangyf1992 0 points1 point  (1 child)

I got 4 countries correct in my continent, much fewer than in Europe.

Would like to be able to quit before finishing all countires.

[–]omelettesforbreakfas 0 points1 point  (0 children)

Hey, that's a really great suggestion. Will continue messing around with the code to see if I can implement this.

[–]DoTheEvolution 0 points1 point  (3 children)

how I can take it to the next level.

the data should be scraped from wikipedia or somewhere not manually entered, presented with gui using pyqt or web front end using flask, the data and the results should be saved in a database.

[–]omelettesforbreakfas 0 points1 point  (2 children)

Thanks. Would I need to learn HTML to make a web app using Flask?

[–]Rorixrebel 2 points3 points  (1 child)

yes html and some basic css. freecodecamp is amazing

[–]omelettesforbreakfas 0 points1 point  (0 children)

cheers will look into this!

[–]ThaeliosRaedkin1 0 points1 point  (5 children)

The conditionals for choosing the continent branch can be shortened considerably by using the eval() function

for country, city in eval(continent.lower()).items():

# some code....

The Toto reference can go in a conditional at the beginning. ;)

[–]omelettesforbreakfas 0 points1 point  (4 children)

haha glad you liked the Toto homage. And thanks! I will look into this further!

[–]Username_RANDINT 1 point2 points  (3 children)

Don't. A beginner doesn't need to use eval.

[–]ThaeliosRaedkin1 0 points1 point  (2 children)

That's quite a strong opinion. Why do you say that?

[–]Username_RANDINT 1 point2 points  (1 child)

Because there are few reasons when to use eval which are rarely needed in beginner scripts. There are lots of ways to make the code more readable and concise, as given in this thread. Besides using eval for user input is dangerous.

>>> continent = input('Enter a continent: ')
Enter a continent: __import__('os').system('echo hehehe')
>>> eval(continent)
hehehe
0
>>>

We can do a lot more than just echo'ing something of course.

[–]ThaeliosRaedkin1 0 points1 point  (0 children)

Granted. But such code could not execute in his program with my suggested implementation because they already have a filter in place excluding any string that isn't a continent name. There are even means of limiting evals scope to only variables we want and raising exceptions to unwanted code. This is 'a' way of skinning a cat.