All right. So in the last lesson,
we did a lot of setup and we got our game to have the basic features.
It's got a pop-up that asks us for a state's name and when I type a name in
here, I can receive it inside my code using this answer state.
Now your job is to use that 50_states.csv to check the user's answer against all
of the states inside this table and see if it matches one of them.
Now the user might spell it with a capital to begin with, say "Ohio",
or they might just write "ohio" and it should work for both of these cases.
In addition, when the user types in a state that is inside that 50_states.csv,
then that state should be written onto the screen at the location where it
exists.
So remember the location corresponds to the X and Y values that we've already
logged inside this data file.
But if the user guesses a state that's wrong,
then nothing happens and our input box appears again asking for a new input.
Now notice how here in the title of the text input,
I'm also keeping track of how many states they have guessed correctly out of 50
and every time they add a new state then that number updates.
This is going to rely on the knowledge that you've learned about reading from
CSV data, getting hold of the specific parts of the CSV data,
finding for example a particular row or a particular column and working with that
data in order to check to see if the user got the state right and then using the
data inside that row to figure out where to write some text at which X and Y
location so that we can get the actual name of the state written at the location
where they appear on the map.
So this is quite a challenge and I expect that you'll have to think about
it and play around with the code for at least 10 to 20 minutes.
So give it the time that it needs and have a think about how you would break down
this problem and try and complete this challenge.
So pause the video now.
All right, so I'm going to walk through the solution with you,
but I want you to do a conscience check.
If you haven't spent at least 20 minutes working on this problem, fixing it,
getting it to do what you want it to do, then go back, try again.
You need to feel the struggle in order to learn.
I can tell you how to do anything, but if that answer didn't come from you,
then you're not actually going to make progress.
So I really,
really urge you to give it a go before you watch the solution.
All right, so back to our code.
The first thing I'm going to do is I'm going to import the pandas module.
So I'm going to import pandas.
Now I've already installed pandas on the starting project that you downloaded,
but if you're doing this completely from scratch, from your own starting file,
then you'll probably get a red squiggly line under this pandas and it'll ask you
to install the package. But if you don't have it,
then it means it's already installed and ready to use.
Now we're going to get our pandas to go ahead and read our CSV file.
And the CSV file is called 50_states.csv.
So once the prompt comes up,
then I'm going to hit enter to get PyCharm to just insert it automatically.
Now I'm going to save that data frame that comes from that reading that CSV to a
variable called data.
Now our data has 50 rows of data and the first row is the column heading for each of them.
So we've got the state, the X and the Y values.
Let's have a think about how we might break down this problem that we have.
So when the user types an answer,
we have to check to see if that answer_state
is one of the states in all the states of the 50_states.csv.
The first column is a list of all 50 states.
So if we could somehow check to see if this state that they've guessed is one of
the states in that column,
then we'd know whether if they got it right or wrong.
If they got it right,
then we need to figure out how we can create a turtle to write a list of all 50
states. So we need to create a turtle to write
the name of the state at the state's X and Y coordinate.
For example,
if the user guessed South Carolina and indeed South Carolina is one of the 50
states,
then we should be able to pull out this entire row and then create a turtle,
make it go to this X and Y location and to write the name of the state.
onto this blank state image in our turtle game.
Let's see if we can tackle this.
Now the first thing we need to figure out is how do we get hold of all the
states of the 50_states.csv?
So we've got our data frame and if I go ahead and say data,
then I can pull out one of the columns.
So the column name is state and I can either use that as a key with a set of
square brackets or I can use it as simply a attribute name.
This gets me the data series, which is that first column.
And if I want to be able to turn that into a list,
remember that we saw that method to_list() and that means we can work with it just
like any other list. So let's call this all_states.
And once we've got hold of this all_states,
well we can check to see if the answer_state is in the all_states.
This is something that you won't be able to do.
So checking for membership using the "in" keyword unless you have converted this
into a list.
So once we figured out that this answer the user provided is in fact one of the
states in our 50 states,
then the next thing we need to do is to create a turtle.
So let's create a new turtle,
which we'll call "t" and we're going to use the turtle.Turtle class to construct
it.
And then we're going to get the turtle to hide the actual turtle shape and we're
also going to get it to penup() so that it doesn't actually do any drawing.
But what we do need it to do is we need it to go to a particular X and Y
location.
The X and Y location is going to correspond to the state that the user has
correctly guessed and the row of data from our 50_states.csv.
So how can we get hold of the actual row of data that corresponds to this state?
Well,
we can take our data and then inside some square brackets we can check that the
data.state is double equal to the answer state.
This is going to pull out the row where the state is equal to the answer_state.
So I'm going to save that under something called state_data and now I can tell my
turtle to go to state_data.x and state_data.y.
So because this is a row of data,
then I can tap into its attributes using the names of the columns,
the X and the Y.
And once my turtle gets there,
then I want it to write the name of the state.
So it's going to be state_data.state.
Now when we hit run on this code and guess the name of a state that definitely
exists, when we click OK,
we're going to get an error and it's really important that you get used to
seeing errors because they're going to happen to you all the time.
Just as in life,
the only certainties are death and taxes. In programming,
the only certainty is you will get bugs and you will get errors.
The important thing is to figure out what's causing the error.
So if we go ahead and copy this error at the end,
bad screen distance, that seems to be telling us what is actually going on.
And then I go into Google and I search for this particular error.
Then I can see other people who've encountered this issue.
So if we read through all this,
we realize that the issue boils down to a mismatch in data types.
It looks like Victor was providing TKInter with strings when he should have
provided an integer value.
Something similar is probably happening in our code as well.
Let's check what we're providing. Well,
I typed in "Ohio" and I can see from the error message that my state_data.x
had the value "34 175
This doesn't look like a number.
It looks like a mismatch in data types,
but what is "34 175?
We wanted to give TKInter a simple integer. However,
state_data.x is actually a Panda series from our data frame.
You can think of a Panda series as a column from our database.
It may look like we've selected the X value of a single state,
but we've actually selected a column in our data frame called X.
This Panda series contains both an index value, Ohio is at index 34,
and the coordinate that we're interested in, which is 175.
Let's extract the integer value of the coordinate with state_data.x.item().
That way we're accessing the single item contained in our Panda series.
So now if we bring back our screen.exitonclick() and then run our code,
we can type the name of the state, click OK,
and you can see the state data show up on the location that corresponds to that
particular state.
Now the only problem is we don't want all of this junk to be written here,
right?
And this is showing up because we're getting this data from our data series.
So what's a much simpler way? Well,
we've already got hold of the answer that the user typed, right?
So why don't we just write the answer_state?
So now when I type in the state and it matches one of the states in the 50
states,
then it's going to go to the X and Y location of that state and print the name
of the state that the user has typed in.
And because we've made sure that it's a correct state with a correct spelling,
then it doesn't actually matter if we use the answer_state or if we use the
state_data.
Now if you really want to use state_data instead of answer_state,
because say you're writing a different program and you need this functionality,
then you can actually tap into state_data.state,
which will get us the state name from the row corresponding to the matching
state. And then we can use a method called item().
Now item() is a method on the Panda series.
So we know that this is a Panda data series and you can do a type check on that
if you want to confirm.
But what the item() does is it looks into the underlying data and it basically
just grabs the first element.
So now if I run the game as it is and I type in the name of a state,
you can see it fetches the actual value under state_data.state without any
of the index, the zero or any of the other parts of the data series.
So I just wanted to show you that you can actually get a row of data from a
data frame and then get a particular value from that row and get the actual raw
value from that. But personally in this line of code,
I prefer using the answer_state because we know it's already been title cased,
it's already formatted correctly and it just makes the code look a bit cleaner
and it's easier to understand. So I'm going to stick with this.
So now that it's working,
all we need to do is to get this to repeat itself every single time the user
makes a new guess.
That means we're probably going to have to put it inside some sort of while
loop. What should be our while condition? Well,
we know that the user is going to guess all of the states and once they've
guessed all of them, then they win, right?
So why don't we create a list of guessed states and set that to equal a empty
list and we can check while the length of the guessed_states is less than the
50, then we can continue rerunning all of this code,
getting them to put a new input, checking the input and writing the state name.
But every time the user guesses a state correctly,
then we're going to add this answer_state to the guessed state.
So we can say guessed_states.append and then we're going to append this new
answer_state. So now when I run this code again,
then you can see that every time I guess a new state,
this text input pops up again and I can basically just keep going until I've
named all the states.
Now the next thing I want to change is the title of this text input window.
Instead of saying guess the state,
I want it to keep track of the number of states I've guessed correctly.
So I basically want to say something like 0 or 10 or whatever out of 50 states
correct.
This 0 is going to be replaced by a F string and we're going to use the length
of our guessed_states in order to get that number.
So now when I run this code,
you can see that I start out with 0 states, 0 out of 50,
and every time I guess a new state,
then it adds to that count so I can keep track of how many I've guessed and how
many I've got left.
Now the next thing you'll realize if you test this a few times is even if you
get a state right,
but if you actually didn't spell it with a capital C or capital letter to begin
with, then it's not going to accept it.
We don't see California showing up and this is because in our state_data,
they all have title case where the first letter is capitalized.
So how can we make sure that our game works if the user types all lowercase or
all uppercase or title case? As long as they got the spelling right,
we should ignore the casing.
So one way of doing this is to take this answer_state which comes from that text
input that the user has entered.
And then at the very end we can change that to a title case.
So this is a method that's on all strings.
So that means if I start out with a string which is "Angela" and then I say
str.title(),
then this will give me the same string back but it'll make the first letter
capitalized.
This is what we're going to do to the user's answer and then we'll be able to
check it against all the states and now it doesn't matter if the user has typed
everything in all caps or if they have typed it with all lowercase or if they
done something really crazy like this, it will still be accepted.
So we've now got most of the functionality of our game.
In the next lesson we're going to figure out how we can get this game to become
more of an educational tool and for it to create a final CSV when we're ready to
exit the game,
that's going to tell us all the states that we missed so that we can learn them
so that we can come back to the game with more knowledge.
For all of that and more, I'll see you on the next lesson.