While loop counter - python

Using VS2015 Python 3.4
Having some issues with this while counter. It is slowly driving me insane as I'm sure it 'should' work but isn't updating the counter. I've ran stepping debug and can see the counter resetting to 3 before the while condition line. It is annoying me to say the least.
import random
import getpass
print ('Welcome to Rock, Paper or Sissors\nEnter an option.')
user_obj = getpass.getpass('Rock, Paper or Sissors: ').lower()
ai_obj = input('Rock, Paper or Sissors: ').lower()
rps = ('rock', 'paper', 'sissors')
#ai_rps = ['rock', 'paper', 'sissors']
#ai_obj = random.choice(ai_rps)
counter = 3
def rps_game(user_obj, ai_obj):
print('Player selected %s ' % user_obj)
print('Computer selected %s ' % ai_obj)
condition = user_obj in rps and ai_obj in rps
while condition == True and counter >= 0:
if user_obj == ai_obj:
print('Its a draw!')
elif user_obj == 'rock':
if ai_obj == 'paper':
print('You lose!')
break
else:
print('You win!')
elif user_obj == 'paper':
if ai_obj == 'sissors':
print('You lose!')
break
else:
print('You win!')
elif user_obj == 'sissors':
if ai_obj == 'rock':
print('You lose!')
else:
print('You win!')
break
else:
counter += 1
print('Invalid input, please select Rock, Paper or Sissors')
rps_game(user_obj, ai_obj)
rps_game(user_obj, ai_obj)

counter resets to its original value because it is a global variable. Global variables in Python behave differently than in other languages. Try this snippet :
counter = 3
def f():
counter = 2 # Shadows global 'counter' by a newly defined one
f()
print (counter) # Prints 3 !
You could expect the printed value to be 2, but is 3 instead. Global variables are immutable by default, and attempt to modify it in a local scope without global keyword will shadow by local definition instead. See this discussion to use global variable although it would be preferable to rework your program to use local variables.
EDIT:
There is also a mistake, counter is initialized to 3 and only incremented, so the condition >= 0 will always be satisfied (thus creating an infinite loop).
You can try:
[...]
def rps_game(user_obj, ai_obj, counter):
if counter <= 0:
return
print('Player selected %s ' % user_obj)
print('Computer selected %s ' % ai_obj)
while user_obj in rps and ai_obj in rps :
[...]
else:
print('Invalid input, please select Rock, Paper or Sissors')
rps_game(user_obj, ai_obj, counter-1)
rps_game(user_obj, ai_obj, 3)

The else containing the counter update is indented so that it hangs off of the while. That means that it executes only when the while loop terminates because the condition is false (not when you break out). In this case, the condition is false when either the user or ai input is not in the list of valid inputs or the counter is negative. The counter can never be negative because it starts at 3 and is only ever incremented. If the user input is invalid, the error message is printed, the counter is incremented, and then you use recursion to call the function with the same arguments. Since the arguments haven't changed, the function should do the same thing it just did, unless it depends on global data (counter). However, if counter is local (initialized inside the function), each invocation of the function gets a fresh copy. If counter is global (initialized outside the function, but declared global inside the function), it still won't work because you need to decrement it. And even then, because none of the other inputs have changed, it will just print the error message over and over.
Your loop bound appears to expect counter to count down from 3 since it keeps going as long as counter is positive. Incidentally, using >= as the conditional will make the loop execute 4 times (3, 2, 1, and 0). You might want to change the conditional to strictly greater than, and change the counter increment to a counter decrement.
Next, you are using recursion (the function calls itself), but this should be unnecessary since the loop gets you back to where you need to be.
Next, the lose cases break out of the loop, but the win cases do not. If you win, the code will loop, not modifying the counter or any of the other inputs, so it will not terminate.
Finally, the user input is read from outside the function, so it never changes inside the loop. If the user enters an illegal value, the loop will just keep testing the same value. You probably want to read the user input inside the loop.

Related

My program that uses functions in a while loop does not keep updated values

I am creating a game of pick up sticks for a class.
Our goal is to create a function that loops 4 other functions to run the game.
main():
while numb_sticks > 1:
func1()
func2()
func3()
func4()
else:
print("Player {} wins!")
My problem is keeping the values I changed from one function onto the next.
I have numb_sticks declared outside my functions, as well as the starting player.
numb_sticks = 20
player = 1
I have print() throughout my functions to keep track of what values are changing.
I have determined that as a function is completed, the numb_stick and player values revert to their original value outside the function.
This essentially creates an endless loop where the number of sticks always reverts to 20 and the turn is always for player 1.
I am required to use these two variables as parameters for my functions.
I do not know how to fix this.
Below is the entirety of my code.
I put comments along the entire program to explain what I am doing.
pick_up_sticks.py
import random
def not_quite_right(numb_sticks): #This function creates a chance to add sticks to the game.
global sticks_added #Declaring variables global to be changed across program.
global sticks_remaining
print("Initial numb_sticks nqr:", numb_sticks) #Testing change.
n = random.randint(1,10) #This and the next line set up the %30 chance for more sticks.
if n > 7:
sticks_added = random.randint(1,4) #This sets up a random amount of sticks between 1-4 to be added.
if numb_sticks + sticks_added <= 20: #Checks if cap is exceeded.
numb_sticks += sticks_added #Adding sticks to pile.
print(numb_sticks) #Testing change, will remove on final.
return
else:
sticks_added = 20 - numb_sticks #This is a small work around if the sticks added would have been more than 20.
numb_sticks = 20
return
else: #The following complets the 70% chance of no sticks being added.
sticks_added = 0
return
"""
---
"""
def take_sticks(player, numb_sticks):
global sticks_taken #Setting variables as global for use across program.
global sticks_remaining
print("Initial numb_sticks ts:", numb_sticks) #Test before change.
if numb_sticks > 1: #Checks if game is still going.
sticks_taken = input("Player {}, choose how many sticks you would like to take: ".format(player)) #Lets player choose stick amount.
try: #Next 4 lines check if the input is a number and restarts turn if it isn't.
int(sticks_taken)
except ValueError:
print("Invalid input! Please type a number.")
take_sticks(player, numb_sticks)
sticks_taken = int(sticks_taken) #Sets input as integer.
if sticks_taken <= 0 or sticks_taken >= 4: #Next 3 lines check if inputted number is legal move and restarts turn if it isn't.
print("You can only take 1, 2, or 3 sticks.")
take_sticks(player, numb_sticks)
elif sticks_taken < 4 and sticks_taken > 0: #Determines legal move.
numb_sticks -= sticks_taken #Sets sticks remaining by removing sticks taken from original 20 sticks.
print("Numb_sticks:", numb_sticks) #Test change.
if player == 1: #Next 5 lines changes players turn.
player = 2
print("Next turn is player", player,"'s turn.'") #Test player change.
return
else:
player = 1
return
return
else:
return
else:
return
def display_board(numb_sticks): #This function states how many sticks there are at the start of the turn.
print("There are", numb_sticks, " sticks on the board.")
def display_summary(player, sticks_taken, sticks_added, numb_sticks): #The following function simply displays the remaining sticks and the next players turn.
print("Player", player,"took", sticks_taken, "sticks, pictsie added", sticks_added, "sticks back, leaving", numb_sticks, "sticks.")
def main(): #Creating a loop for the game.
numb_sticks = 20 #Setting the number of sticks.
player = 1 #Sets player 1 to start.
while numb_sticks > 1:
display_board(numb_sticks)
take_sticks(player, numb_sticks)
not_quite_right(numb_sticks)
display_summary(player, sticks_taken, sticks_added, sticks_remaining)
else:
print("Player {} has won the game!".format(player)) #Declares a player the winner!
main() #Begins the game!
The reason for your infinite loop is that wherever numb_sticks is being decremented or incremented, numb_sticks is a temporary local variable (well, it's a parameter, which is also a temporary variable, just as a local variable is). Once the function reaches the end of that particular invocation, the temporary variable numb_sticks (along with its decremented or incremented value) is gone.
There are many ways of fixing this.
I got rid of the infinite loop with the following changes:
Modified the functions not_quite_right() and take_sticks() so that every
return is changed to return numb_sticks. This will return the
local variable's (incremented or decremented value) to the calling
point where the function was invoked.
Next, I modified the body of the while loop in your main() function as below:
display_board(numb_sticks)
numb_sticks = take_sticks(player, numb_sticks)
numb_sticks = not_quite_right(numb_sticks)
display_summary(player, sticks_taken, sticks_added, sticks_remaining)
The game ended in about 5 moves.
So, did you try passing your global values, declared in main() as parameters in the functions? This way your global values are always tied into the functions.
def main():
numb_sticks = 20
player = 1
if numb_sticks > 1:
func1(numb_sticks, player)
func2(numb_sticks, player)
else:
print("Player {} wins!")
def func1(numb_sticks, player):
def func2(numb_sticks, player):

My function is too long and I'm told to optimize

So I'm doing a project for an online class and when i got it scored it came back as not passed because my "play game" function was more than 18 lines long and I'm supposed to optimize it. The problem is they didn't make any suggestions as to what i should do and I keep looking at it and feel as if I need everything in it to make the fill in the blank quiz I'm building work. Any suggestions would be great as I am still very new to coding, and maybe what they are asking me to do is easy and I'm over complicating it in my head. This is the part of the code they want optimized...
def play():
'''
This is the main function which allows to play the game/quiz.
It calls the previous functions we have written.
'''
quiz = difficulty_level(user_level) #gives the difficulty
paragraph that the user asks for.
print quiz
print "\nYou will get maximum 3 guesses for each blank. Good luck.\n"
answers_list = relate_answer(user_level) #makes sure that the right
list is called up
blanks_index = 0
answers_index = 0
number_of_guesses = 3
while blanks_index < len(blanks): #This loop keeps going if all the
blanks are not replaced.
user_answer = raw_input("type in your answer for " + blanks[blanks_index] + ": ")
if check_answer(user_answer,answers_list,answers_index) == "Correct":
print "Awesome job! You gave the right answer!\n"
quiz = quiz. replace(blanks[blanks_index],user_answer)
blanks_index += 1
answers_index += 1
number_of_guesses = 3
print quiz
else:
number_of_guesses -= 1
if number_of_guesses == 0:
print "Game over! But try again!"
break
elif number_of_guesses < 0:
print "invalid"
break
else:
print "please try again."
print "You have " + str(number_of_guesses) + " guesses left."
print "Congratulations! You really know your stuff!."
play()
A quick summation of changes:
It looks like answers_index and blanks_index share the same state, no matter what, so we can generalize that to one variable idx. That cuts down on the number of variables you have to keep track of.
As others have suggested, initializing variables as a tuple like var1, var2 = 1, 2 decreases length and makes code more readable (to a point). If you have lots of variables to initialize, you can break the initialization into groups of related variables.
Setting a default value to user_level increases readability, and also gives an easy place to start debugging your function. This occurs in the def statement, so if you don't pass the argument, user_level will be 1, otherwise it will be whatever you give it in the function call. Example play(user_level=40) will overwrite that default value.
Putting the game loss condition at the start of the while loop increases visibility of the checks the code goes through before executing anything unnecessary. This should help you avoid the case where guess would be less than 0, because that code won't execute when guess==0 is True.
Setting a lose boolean with the check after exiting the while loop will also prevent the wrong print statement at the end from executing. That way, if the player loses, you will print the proper message.
Breaking the update of quiz into another function adds a more compact breakpoint into the code. In general, smaller functions that do less are preferable to large functions that do a lot. The smallest number of changes necessary is a good standard for what should be wrapped into a function, though this could be considered a stylistic convention more than a hard and fast rule.
The correct_answer method allows you to update quiz and guess in one line, and if something is wrong with the update itself, you don't have to run the entire script over and over. Instead you can take the quiz that might be causing the problem and plug it into that function until you find what's causing the problem.
I've made some minimal updates to the function you've provided which keep most of your conventions so far. Hopefully this is helpful, and happy coding!
def correct_answer(quiz, blank, answer):
"""
Function to update the quiz and return
both the quiz and reset guess to 3
"""
print("Awesome job! You gave the right answer!\n")
quiz = quiz.replace(blank, answer)
return quiz, 3
def play(user_level=1):
'''
This is the main function which allows to play the game/quiz.
It calls the previous functions we have written.
'''
idx, guess, lose = 0, 3, False
quiz = difficulty_level(user_level) #gives the difficulty paragraph that the user asks for.
print(quiz)
print("\nYou will get maximum 3 guesses for each blank. Good luck.\n")
answers_list = relate_answer(user_level) #makes sure that the right list is called up
while idx < len(blanks): #This loop keeps going if all the blanks are not replaced.
if guess <= 0:
lose = True
break
user_answer = raw_input("type in your answer for %s:" %str(blanks[idx]))
if check_answer(user_answer,answers_list,idx) == "Correct":
quiz, guess = correct_answer(quiz, blanks[idx], user_answer)
print(quiz)
else:
guess -= 1
print("please try again.\nYou have %s guesses left.\n"%str(guess))
idx += 1
if lose:
print("Game over, try again")
else:
print("Congratulations! You really know your stuff!.")
play(user_level = 3)

How do I clear a variable after an if statement?

I'm implementing a game which allows two players to pick alternately from a pile of 27 sticks. Each player may take 1, 2, or 3 sticks on each turn; the player forced to take the final stick loses.
I've done most of the code, but I must include validation. Besides taking 1-3 sticks, a player is not allowed to take the final stick. I've tried using a continue statement, but when player two exceeds the limit, the program returns to player 1's turn.
Here's what I have so far:
count = 27
T = False
F = False
while count > 1:
Playerone = int(input("Player 1's Turn, Enter from 1-3"))
if Playerone < 1 or Playerone > 3:
print("Error")
continue
count -= Playerone
print(count)
if count == 1:
print("P1 wins")
break
if count < 1:
print("You can't pick up those many sticks")
continue
Playertwo = int(input("Player 2's Turn, Enter from 1-3"))
if Playertwo < 1 or Playertwo > 3:
print("Error")
continue
count -= Playertwo
print(count)
if count == 1:
print("P2 wins")
break
if count < 1:
print("You can't pick up those many sticks")
continue
The last if statement is the issue
Help would be much appreciated,
You have a basic flaw in your loop flow: regardless of the problem encountered with either player's input, you use continue to return to the top of the loop, which gets you back to Player 1. You need to fix this: loop on a given player's input until it's valid in all ways. Something like this should do:
valid = False
while not valid:
Playertwo = int(input("Player 2's Turn, Enter from 1-3"))
if Playertwo < 1 or Playertwo > 3:
print("Error")
elif count - Playertwo < 1:
print("You can't pick up those many sticks")
else:
valid = True
Apply this to each player's input. Once you get out of this loop, you have valid input. From there, you can decrease the count and determine whether someone has won.
One way to ensure a valid user input is to use a loop.
Here's a quick example of a function you might use:
def prompt_integer(msg, minval, maxval, err_invalid, err_oob):
while True:
resp = input(msg) # Python3, use raw_input in Python2
try:
resp = int(resp)
if minval <= resp <= maxval:
return resp
else:
print(err_oob)
except ValueError:
print(err_invalid)
x = prompt_integer("Enter an integer: ", 1, 3, "Invalid Integer.", "Integer Out of Bounds")
Here, the function will not return until the user enters a valid integer between 1 and 3 (inclusive).
If they enter, say, 'abc', then the program will display "Invalid Integer." and ask them again.
If they enter, say, 5, when you've specified that the bounds are 1 and 3 then the program will display "Integer Out Of Bounds", and then ask them again.
When this function returns you know you've got an acceptable value.
You might use this function in your code, and modify maxval argument each time you call it according to how many sticks they're able to pick up.
This is what I would do, you could go crazy with Classes, but that is a little much for you right now(but something to look into). You should also look into creating methods, but check out my code below with the comments I left.
def player_input(player_number: int): # This is a method, that requires an integer to be pass to it
p_input = int(input("Player {}'s Turn, Enter from 1-3: ".format(player_number)))
while p_input < 1 or p_input > 3: # This will cause a loop continuously asking the player to input a number until that number is between 1 or 3.
print('Please choose a number between 1 and 3')
p_input = int(input("Player {}'s Turn, Enter from 1-3: ".format(player_number))) # Format will replace the '{}' with the value of the variable you give it
return p_input # This 'return' line will return the what the results of what the player typed in
def found_winner(stick_number: int): # stick_number is a required variable and ': int' requires that that variable be an integer
winner = False
if stick_number == 1:
winner = True
return winner # This method will return if a winner is found or not
def next_player(player_number: int): # This method will swap the players turn
if player_number == 1:
player_number = 2
elif player_number == 2:
player_number = 1
return player_number
def start_game(stick_count: int = 27): # This method will start the game, the '= 27' says that you can give me any stick count you want(that is an integer), but if you don't provide one I will use '27' by default
player_number = 1
while stick_count > 1: # This will loop through until the stick count is 1 or less
sticks_to_remove = player_input(player_number) # I store the plays result just in case the stick_count goes below 1, and then I remove the sticks if the the count doesn't go below 1
if stick_count - sticks_to_remove < 1:
print('You cant pick up that many sticks')
continue
else:
stick_count -= sticks_to_remove # Remove the sticks
if found_winner(stick_count): # Look for the winner
print('Player {} wins!'.format(player_number))
else:
player_number = next_player(player_number) # If no winner go to the next player
if __name__ == '__main__': # This says only execute the 'start_game()' method automatically if this python script is called, this is useful later when you start creating more complicated Python scripts that span multiple files.
start_game()

Python - UnboundLocalError: local variable 'health' referenced before assignment

Despite importing the variable 'health' from a different module, the function below provides the error shown in the title. 'Health' is also globalised and I have removed both the globalisation and the importation of the variable and I still receive the same error.
Below is the function that is causing the issue.
def combat():
enemy_health = (random.choice(random_enemy_Health))
enemy_attack = (random.choice(random_enemy_Attack))
print("\nYou are fighting a" ,random.choice(enemies), "with an attack amount of" ,enemy_attack, "and a health amount of" ,enemy_health,".")
while health > 0 and enemy_health > 0:
if turn == 1:
while loop == False:
response=input()
try:
move = response("Do you want to attack or flee? Type '1' to attack and '2' to flee.")
move = int(move)
if move == 1:
enemy_health = enemy_health - attack
print("You attacked!")
loop = True
elif move == 2:
hub_travel()
print("You fled the battle, come back once you are stronger!")
loop = True
else:
print("Invalid number, try again")
continue
except:
print("Invalid number, try again")
continue
turn = 2
if turn == 2:
AImove = randint(1,2)
if AImove == 1:
print ("Enemy attacked!")
health = health - enemy_attack
turn = 1
continue
print ("game over!")
if enemy_health == 0:
print("The enemy has been defeated!")
gold += random.choice(gold_dropped)
The error occurs on this line in particular:
while health > 0 and enemy_health > 0:
If I were you, instead of relying on globals, I would use parameters. This advice may help you to track some errors.
Globals variables is a possibility in programs that have a few lines of code. But, when you application grows, it is a bit hard to track the current value of some variable, because it can be used in several functions or methods (probably, you need a mental mapping to find out the current value). So, this is one of the reasons why you must prefer to use local variables or parameters instead of globals.
This change would allow your function to work the way you want:
def combat(health):
...
Of course, you'd have to find the places where you call the function and pass in the value for health. I don't know if the code at that point has access to that information.
This is probably the simplest fix that could possibly address this issue. It is certainly not the best fix, but this is not a good place for an architecture tutorial.

elif statement returning the "lost" result each time incorrectly. (homework)

I have updated my code with the changes made. I am still getting incorrect results...
# Import statements
import random
# Define main function that will ask for input, generate computer choice,
# determine winner and show output when finished.
def main():
# Initialize Accumulators
tie = 0
win = 0
lose = 0
score = 0
# initialize variables
user = 0
computer = 0
# Initialize loop control variable
again = 'y'
while again == 'y':
userInput()
computerInput()
if score == win:
print('You won this round, good job!')
win += 1
elif score == tie:
print('You tied this round, please try again!')
tie += 1
else:
print('You lost this round, please try again!')
lose += 1
again = input('Would you like to play another round (y/n)? ')
#determine winning average
average = (win / (win + lose + tie))
print('You won ', win, 'games against the computer!')
print('You lost ', lose, 'games against the computer.')
print('You tied with the computer for', tie)
print('Your winning average is', average)
print('Thanks for playing!!')
# get user input for calculation
def userInput():
print('Welcome to Rock, Paper, Scissor!')
print('Please make your selection and and Good Luck!')
print('1) Rock')
print('2) Paper')
print('3) Scissor')
user = int(input('Please enter your selection here: '))
print('You selected', user)
# get compter input for calculation
def computerInput():
computer = random.randint(1, 3)
print('The computer chose', computer)
def getScore():
if user == 1 and computer == 3:
score = win
return score
elif user == 2 and computer == 1:
score = win
return score
elif user == 3 and computer == 2:
score = win
return score
elif user == computer:
score = tie
return score
else:
score = lose
return score
# Call Main
main()
In Python:
>>> print("3" == 3)
False
Strings and integers are values of different data types, and will not compare equal. Try changing your input to:
userInput = int(input('Please enter your selection here: '))
This will convert the string typed by the user to a number for later comparison. (Note that I have assumed you are using Python 3.x, because input() behaves slightly differently in Python 2.x.)
Note that this will throw an error if you type anything other than a number.
Update: As pointed out by #FelipeFG in the comments below, you are also overwriting the function userInput with the value typed by the user. You'll need to change the name of one or the other, for example:
def getUserInput():
...
Also don't forget to change the place where you call the function. Do the same for computerInput (change to getComputerInput).
Later on, you can change those to actual functions that return values.
userInput() calls the function "userInput", but you discard the result. The same remark applies to computerInput().
userInput == 1 asks whether the function userInput itself is equal to 1. It isn't. The same remark applies to computerInput == 3 and the others.
In the function "userInput", userInput = ... binds the name "userInput" to the result of the expression. This makes "userInput" a local variable of the function. The function doesn't explcitly return anything, therefore it returns None.
If you're using Python 3, input returns a string, and you should convert its result to an int. If you're using Python 2, input evaluates whatever is entered, which isn't safe; you should use raw_input instead and convert its result to an int.
You need to compare against the return value of your function, not the function itself.
Also:
again = input('Would you like to play another round (y/n)? ')
This will throw an exception if you enter y or n, because there is no defined identifier of that name! What you want to use instead is raw_input()
Edit: As pointed out by Greg, this only applies to Python 2.x. You seem to be using Python3 though.

Categories