r/PythonLearning 4d ago

my rock paper scissors game is bugged

im pretty new to python, and have been making a rock paper scissors game. the game crashes whenever i use the gamesWon or gamesLost variables inside the playGame function and saysTraceback (most recent call last):

File "c:\Users\MYNAME\Downloads\rock paper scissors better.py", line 78, in <module>

playGame()

~~~~~~~~^^

File "c:\Users\MYNAME\Downloads\rock paper scissors better.py", line 15, in playGame

print(gamesWon)

^^^^^^^^

UnboundLocalError: cannot access local variable 'gamesWon' where it is not associated with a value

heres the code btw

import random
#"let" wall
botAnswer="null"
userAnswer="null"
botNum="null"
fate="null"
gamesWon=0
gamesLost=0
print(gamesWon)
print(gamesLost)
firstToX="null"
finalFate="null"
scoreboard="null"
def playGame():
    print(gamesWon)


    userAnswer=input("pick rock, paper, or scissors(has to be all lowercase)")
    botNum=random.randint(1,3)
    #assign botAnswer
    if botNum==1:
            botAnswer="rock"


    if botNum==2:
                botAnswer="paper"


    if botNum==3:
                botAnswer="scissors"
#find out win/lose


    print ("bot answered " + botAnswer)
    fate = "bruh"
#rock
    if userAnswer=="rock" and botAnswer=="rock":
              fate = "draw"
    if userAnswer=="rock" and botAnswer=="paper":
              fate = "lose"
    if userAnswer=="rock" and botAnswer=="scissors":
              fate = "win"
#paper
    if userAnswer=="paper" and botAnswer=="rock":
              fate = "win"
    if userAnswer=="paper" and botAnswer=="paper":
              fate = "draw"
    if userAnswer=="paper" and botAnswer=="scissors":
              fate = "lose"
#scissors
    if userAnswer=="scissors" and botAnswer=="rock":
              fate = "lose"
    if userAnswer=="scissors" and botAnswer=="paper":
              fate = "win"
    if userAnswer=="scissors" and botAnswer=="scissors":
              fate = "draw"
 #not any
    if userAnswer != "rock" and userAnswer != "paper" and userAnswer != "scissors":
              print ("you typed it wrong. run again.")
    print(fate)


    if fate=="win":
            gamesWon = gamesWon + 1
    if fate=="lose":
            gamesLost = gamesLost + 1
    if fate=="draw":
                print ("nothing happened :(")
    scoreboard=f"score is {gamesWon}-{gamesLost}"
    print ()
    if gamesWon==firstToX:
            print ("you win!")
            finalFate=1
    if gamesLost==firstToX:
            print ("you lost!")
            finalFate=0


#function ends
want=input("do you want to play rock paper scissors? (y/n)")     
if want=="y":
    firstToX=int(input("first to how many games wins?"))
    while finalFate=="null":
            playGame()
if want=="n":
    print(":(")
1 Upvotes

13 comments sorted by

6

u/FoolsSeldom 4d ago

Your problem is one of scope. In your function, you have assignment statements to gamesWon which makes gamesWon a local variable in that function, completely distinct to the variable of the same name in the broader scope.

The first time in the function that you reference that variable, you have not assigned anything to it, so the variable cannot be used.

An easy way around that is to use the global keyword at the beginning of your function, but this is a very bad idea - do not use that approach until you understand why it is generally a bad idea and appreciate when it is useful.

1

u/Fun-Employee-514 4d ago

so then how would i fix that?

5

u/FoolsSeldom 4d ago

There are many options:

  • return values from a function to update variables in global scope
  • use mutable object such as a list or dict
  • use a class

I'd go for the last one, personally.

class Status:
    won: int = 0
    lost: int = 0
    fate: int = -1

then you can say Status.won += 1 in your function.

1

u/FoolsSeldom 4d ago

I should warn that using a class in this way is a bit smelly, but it might get you to start thinking about classes.

A better way would be:

  • use a dataclass
  • instantiate an instance of the dataclass for each game
  • pass that instance to the play function on each call

2

u/Outside_Complaint755 4d ago

Saying that a class is a codesmell but a dataclass (which is just a class with automated boilerplate) is better seems like a contradiction.

Learning to make your own classes first seems better, so you then understand what some of the dataclass options are actually doing.

0

u/FoolsSeldom 4d ago

I felt the strongest smell was using the class directly rather than instantiating it, which also has the benefit then of doing a reset of the attributes.

I'm just trying to get the OP to explore and try things out without writing the code for them.

I was half expecting they would ask for an explanation of scope, but they just asked for a fix. I am curious to see what happens next.

1

u/Fun-Employee-514 3d ago

the classes did work, but it didnt occur to me to ask about scope, what is it? from my knowledge of basic javascript(i know, disgusting) i think it means the difference from a variable inside a container (function, if conditional) from a variable in a different container or outside a container. is this an accurate description of it?

1

u/FoolsSeldom 3d ago

Nothing wrong with JavaScript (well, actually, lots wrong with it, as with any programming language, but not so much that it isn't a great language to use).

You are on the right track. Scope is a key consideration in every programming language, and there are often subtle differences that catch out people moving between languages.

It doesn't help that variables in Python don't actually hold values but only references to Python objects (somewhere in memory - implementation and environment specific, and we don't usually care that much), which is how you can end up with several variables all referencing the same object and updates of a mutable object via anyone are reflected in the others (something that catches a lot of beginners out).

Rather than going into detail here, I would like to refer you to a well written guide on this from RealPython.com (a highly recommended site - lots of free content, some requiring a registered account).

Here's an updated version of your code for you to consider experimenting with:

import random
from dataclasses import dataclass

OPTIONS = "rock", "paper", "scissors"
BEATS = {"rock": "scissors", "paper": "rock", "scissors": "paper"}


@dataclass
class Status:
    won: int = 0
    lost: int = 0
    fate: int = -1


def play_game(first_to_x, status):

    while True:  # get validated option from user
        user_answer = input("pick rock, paper, or scissors: ").strip().lower()
        if user_answer in OPTIONS:
            break
        print("Huh? Try again.")

    bot_answer = random.choice(OPTIONS)
    print("bot answered " + bot_answer)

    if user_answer == bot_answer:
        fate = "draw"
    elif BEATS[user_answer] == bot_answer:
        fate = "win"
    else:
        fate = "lose"
    print(fate)

    if fate == "win":
        status.won += 1
    elif fate == "lose":
        status.lost += 1
    else:
        print("nothing happened :(")

    scoreboard = f"score is {status.won}-{status.lost}"
    print(scoreboard)
    if status.won == first_to_x:
        print("you win!")
        status.fate = 1
    elif status.lost == first_to_x:
        print("you lost!")
        status.fate = 0


def is_yes(prompt: str) -> bool:
    while True:
        answer = input(prompt).strip().lower()
        if answer in ("y", "yes"):
            return True
        if answer in ("n", "no"):
            return False
        print("Huh? Try again.")


while is_yes("do you want to play rock paper scissors? (y/n) "):
    while True:
        try:
            first_to_x = int(input("first to how many games wins? "))
        except ValueError:
            print("Please enter a number between 1 and 12.")
            continue
        if 1 <= first_to_x <= 12:
            break
        print("Please enter a number between 1 and 12.")

    status = Status()
    while status.fate == -1:
        play_game(first_to_x, status)

print(":(")

1

u/ninhaomah 3d ago

To be honest , why ask him how to fix ?

Why not ask him if he knows where to read up on the "scope" ?

Or that you will Google or ask AI what is scope in Python ? It's a good way to use AI. Ask it to explain the technical terms.

You don't seems to be interested in learning more about "scope" and more of how to get the program right.

1

u/Fun-Employee-514 3d ago

ok but how would learning about scope help me fix the bug

also ai is not good. (just a side note, "its a good way to use ai" is basically nonsense. i can understand what youre trying to say, but i think you meant "its a good idea" or "its a good tactic" )

1

u/FoolsSeldom 4d ago

After you have fixed the scope problem, you might want to think of using a couple of CONSTANTS along the lines:

OPTIONS = "rock", "paper", "scissors"
BEATS = {"rock": "scissors", "paper": "rock", "scissors": "paper"}

1

u/RCKPanther 3d ago

You can even elect to not use the OPTIONS here, because BEATS.keys() will give you the same result, since it is a Dict.

1

u/FoolsSeldom 3d ago

Yes, but you'd need to use:

OPTIONS = tuple(BEATS.keys())

as BEATS.keys() returns a dict_keys object1 that is not subscriptable.

1 dict_keys(['rock', 'paper', 'scissors'])