It appears to me that your programme has 2 characteristics contributing
to your problem:
- you’re not passing parameters around between function - all your
functions except for compare
take no parameters
- you’re having some input-based functions call themselves to repeat the
input if it is bad; this isn’t really doing what you might hope
Your problems passing around parameters stem in a big way from the fact
that you’re expecting many variable to be “global variables”: that
setting a variable in one function makes that setting available in
another function.
A “pure function” does not behave that way. A “pure function” is one
with no side effects. The desirable feature of a pure function is that
is it easy to combine with other functions in large programmes, as
otherwise you need to keep track of all the side effects. For example,
you have a free hand to name variables as you like because they will not
collide with the same names elsewhere.
Python functions a generally “pure functions” unless you make special
arrangements, in that any variables you set vanish when you leave the
function - information only gets out via the return
statement. And
information normally only gets in as a parameter.
So, to your code. You have a couple of global variables (player_wins
and comp_wins
) and this on its own puts you in a “global variables”
state of mind: that you can use the same name in different places
without connecting them. It is a natural thing, but unhelpful here.
I recommend that you get rid of all the global variables.
Move your globls and the main loop into its own function, like this:
def main():
player_wins = 0
comp_wins = 0
initialize()
ready_up()
while True:
player_move()
comp_move()
compare(player_choice, comp_choice, player_wins, comp_wins)
then right at the bottom of the programme, run that function:
main()
(This needs to be at the bottom so that all the other functions are
defined before you start running main
.)
Why is this a win? Now you have no global variables and everything
must be passed around with parameters. This is less confusing.
With that change you must use parameters in your calls.
The first change is that you should collect the return values from
player_move
and comp_move
. Like this:
while True:
player_choice = player_move()
comp_choice = comp_move()
compare(player_choice, comp_choice, player_wins, comp_wins)
The error you had before:
NameError: name 'player_choice' in line 74 is not defined
was because player_choice
is a local variable in the player_move
function. That means that it is not known here, in the main
function.
With the assignment statements above, you now have a completely separate
local variable player_choice
in the main function which has the return
value from player_move
. We give it the same name because it has the
same purpose. But the connection between the two names is the
assignment, where we copy the value from the function to the local
variable.
Now that you have done this, all the parameters you are passing to
compare
are known.
Look at the compare
function: is has 4 parameters. They are local
variables. The only way they have values is because you passed these
values when you called it from the main
function. In particular, they
do not have the same values as those in main
because of their name.
They have the same values because that is what you supplied at the
calling end. We give them the same names only because they have the same
meaning, so that we know what we’re dealing with.
The other slightly odd thing is your input functions. Let’s look at
player_move
:
def player_move():
global options
options = ['r','p','s']
player_choice = input('Make your move (r/p/s): ').lower()
if player_choice in options:
return player_choice
else:
player_move()
You’re returning player_choice
if it is valid, which is fine. However,
if you are not given a valid choice you call player_move()
again and
do not return its value. Because you do not return its value, if the
player chose a valid choice on the second or later inputs it will be
ignored. The direct change is to change the if-statement like this:
if player_choice in options:
return player_choice
else:
return player_move()
which calls player_move
again, and returns the choice from that.
However, that means player_move
calls player_move
calls
player_move
until a valid choice is made. The call stack starts to
look like this:
player_move()
player_move()
player_move()
player_move()
...
until a valid choice is made, then the calls each unwind, with each
outer player_move
returning the value from the inner one. That works,
but is … cumbersome. In other circumstances this kind of recursive
approach does not scale. Imagine searching a list of thousands of
elements this way. Computers have finite limits and at some point the
call stack above would exceed that limit.
A better way to write this kind of function is with a loop:
white True:
player_choice = input('Make your move (r/p/s): ').lower()
if player_choice in options:
return player_choice
This never calls player_move
again. Instead, it stays in the original
call until it gets a valid answer, then returns that answer.
Finally, note again that the player_choice
above is a local
variable, unrelated to any other player_choice
variable elsewhere in
your programme. It gets out and gets used via the return player_choice
statement.
Hopefully this clarifies some of what’s going on.
Cheers,
Cameron Simpson cs@cskk.id.au