4

Building a simple Python game of "Rock, Paper, Scissors" for learning purposes.

I've read some other posts here on exiting from Python without a traceback. I'm trying to implement it but still getting a traceback! Can some Python wiz point out what's wrong for this Python dummy? The idea is that clicking RETURN (or typing "yes" or "y" will make the program run the play() again, but hitting CTRL-C will close it without a traceback. I´m using Python 2.7.

    # modules
    import sys, traceback
    from random import choice

    #set up our lists
    ROCK, PAPER, SCISSORS = 1, 2, 3
    names = 'ROCK', 'PAPER', 'SCISSORS'

    #Define a function for who beats who?
    def beats(a, b):
        return (a,b) in ((PAPER, ROCK), (SCISSORS, PAPER), (ROCK, SCISSORS))

    def play():
        print "Please select: "
        print "1 Rock"
        print "2 Paper"
        print "3 Scissors"
        # player choose Rock, Paper or Scissors
        player_choice = int(input ("Choose from 1-3: "))
        # assigns the CPU variable a random CHOICE from a list.
        cpu_choice = choice((ROCK, PAPER, SCISSORS))

        if cpu_choice != player_choice:
            if beats(player_choice, cpu_choice):
                print "You chose %r, and the CPU chose %r." % (names[player_choice - 1], names[cpu_choice - 1])
                print "You win, yay!!"
            else:
                print "You chose %r, and the CPU chose %r." % (names[player_choice - 1], names[cpu_choice - 1])
                print "You lose. Yuck!"
        else:
            print "You chose %r, and the CPU chose %r." % (names[player_choice - 1], names[cpu_choice - 1])
            print "It's a tie!"

        print "Do you want to play again? Click RETURN to play again, or CTRL-C to exit!"

        next = raw_input("> ")

        # THIS IS WHAT I'M WORKING ON - NEED TO REMOVE TRACEBACK!
        if next == "yes" or "y":
            try:
                play()
            except KeyboardInterrupt:
                print "Goodbye!"
            except Exception:
                traceback.print_exc(file=sys.stdout)
            sys.exit(0)
        elif next == None:
            play()
        else:
            sys.exit(0)

# initiate play() !
play()
Jamison
  • 2,218
  • 4
  • 27
  • 31
  • 1
    Can you tell us your traceback? – Martijn Pieters Sep 17 '12 at 20:00
  • This type of exit code without traceback I first saw here: http://stackoverflow.com/questions/1187970/how-to-exit-from-python-without-traceback , but the difference is that now I´m trying to use it within an if-elif-else statement... – Jamison Sep 17 '12 at 20:00
  • 2
    Can you paste your output here? I'm unable to reproduce. Also, your code won't run as copied in your question. You need to define `play()` and `next` – AJ. Sep 17 '12 at 20:01
  • The traceback is something like this: > Traceback (most recent call last): File "ex36a.py", line 60, in play() File "ex36a.py", line 44, in play next = raw_input("> ") KeyboardInterrupt – Jamison Sep 17 '12 at 20:01
  • 1
    Also, `exit(0)` should probably by `sys.exit(0)`. – Martijn Pieters Sep 17 '12 at 20:02
  • 2
    Be careful with your if statement. `a = 'n'; bool(a == 'yes' or 'y')` returns `True` – mgilson Sep 17 '12 at 20:02
  • I updated the code in the question to include it all - play() function, etc. – Jamison Sep 17 '12 at 20:16
  • Your indentation is all over the place, I can no longer tell what's inside `play()` and what isn't. – Martijn Pieters Sep 17 '12 at 20:16
  • @MartijnPieters: yes, I'm using Notepad++ and it was really hard to get the code pasted into this question's codeblock while preserving the tabs. It should be standard 4-space indents, but it seems to get mixed up when pasted. – Jamison Sep 17 '12 at 20:32
  • @Jamison: Don't use tabs when coding in python; replace all tabs with 4-space indents in notepad, then paste and indent with the `{}` code button on the toolbar. – Martijn Pieters Sep 17 '12 at 20:33
  • @MartijnPieters: good advice. I've learned now that tab == 4 spaces in Notepad++, but NOT when you try to paste into anything else - it was a pain to post here due to my error. – Jamison Sep 17 '12 at 20:41
  • why catch the other Exceptions just to print the stacktrace and exit? just don't catch it, right? – Tshirtman Sep 18 '12 at 11:27

3 Answers3

5

Try restructuring your main loop; something more along the lines of:

try:
    while (play()):
        pass
except KeyboardInterrupt:
    sys.exit(0)

And play looks like:

def play():
    _do_play() # code for the actual game

    play_again = raw_input('play again? ')
    return play_again.strip().lower() in ("yes", "y")
ron rothman
  • 17,348
  • 7
  • 41
  • 43
3

You call play() twice, so you need to put both cases in a try/except block:

if next in ("yes", "y"):
    try:
        play()
    except KeyboardInterrupt:
        print "Goodbye!"
    except Exception:
        traceback.print_exc(file=sys.stdout)
    sys.exit(0)
elif next is None:
    try:
        play()
    except KeyboardInterrupt:
        print "Goodbye!"
    except Exception:
        traceback.print_exc(file=sys.stdout)
        sys.exit(0)
else:
    sys.exit(0)

I've corrected two other problems, it is better to test for None with is in python, and your first if test was not going to work, as next == "yes" or "y" is interpreted as next == "yes" separately from "y" with an or in between. The "y" is always seen as True so you never would come to the other branches in your code.

Note that I suspect the above code could be simplified much more, but you don't show us your play() function at all, so you leave us to guess what you are trying to do.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Three answers for the price of one! – Tim Pietzcker Sep 17 '12 at 20:08
  • @KarlKnechtel: Yeah, reworded a little. – Martijn Pieters Sep 17 '12 at 20:12
  • Is there any really good reason to not put `None` into the `('y','yes')` tuple and avoid the repeated code? I realize that will once again take us back to testing `nxt == None` instead of `nxt is None`, but I think it's worth it to save the repeated code here (as it is very unlikely that `nxt` will be some object with a custom `__eq__`) – mgilson Sep 17 '12 at 20:14
  • @Martijn Pieters: I feel blessed to be learning Python among a lot of true experts. Thanks also for pointing out the "y" in next== "yes" or "y" being superfluous - that was a rookie mistake on my part. – Jamison Sep 17 '12 at 20:15
  • @mgilson: I actually suspect `play` is meant to return the next value of `next`.. – Martijn Pieters Sep 17 '12 at 20:15
  • 1
    And, I suppose, while we're on the topic of hints/code suggestions, we might want to advise the OP to not shadow the builtin `next` as well ... – mgilson Sep 17 '12 at 20:16
  • @mgilson: thanks for that comment on shadowing the built-in next() - I'll change my 'next' to 'nxt' per your suggestion! – Jamison Sep 17 '12 at 20:44
2

One problem is that you need to enclose your raw_input statement in a try except KeyboardInterrupt clause as well as the actual play function. e.g.

try:
   nxt = raw_input('>')
   if nxt.lower().startswith('y') or (nxt.strip() == ''):
      play()
   else:
      sys.exit(0)
except KeyboardInterrupt:
   sys.exit(0)
except Exception:
   traceback.print_exc(file=sys.stdout)
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • @MartijnPieters -- I'm still having a hard time parsing that traceback. (It's amazing how much those line-breaks help)... Ultimately though, can we agree that some `raw_input` somewhere isn't enclosed in his try/except? – mgilson Sep 17 '12 at 20:12
  • The `raw_input` is *in* his `play()` function (*"line 44, in play"* precedes the `raw_input` part). But yes, the `raw_input` throws the exception, but it isn't caught. – Martijn Pieters Sep 17 '12 at 20:13
  • @MartijnPieters: yes, I updated the code in the question to reflect the whole program. I'm just in my first hour or so looking at this, so it's really rough. THANKS for the great advice and insight. – Jamison Sep 17 '12 at 20:22