yacoubean has asked for the wisdom of the Perl Monks concerning the following question:

I have a simple game I'm writing to expand my perl skills (I'm still a noob). The script runs in a while loop, and calls a bunch of subs. I want to give the player the option to play again after a game is finished, but still keep some of my data stored in variables. However, the problem I'm having is resetting some variables back to 0 or "". The first run through, everything works fine. But after selecting play again, I start getting errors with my variables. Heres some code:
while ($word_len >= $tot_found) { if ($word_len == $tot_found) { print_word(); play_again(); } else { print_word(); if ($guessed_word == 0) { get_guess(); check_guess(); } else { play_again(); } } } sub get_guess { $guess = <stdin>; chomp($guess); } sub check_guess { my $occurrences = ""; my $guess_len = length($guess); print "\n$guess\n"; if ($guess !~ /[[:alpha:]]/) { print "\n***Invalid letter*** (hit any key)\n"; <>; } # other stuff... } sub play_again { print "Do you want to play again (y or n)? "; my $play_again = <STDIN>; chomp($play_again); if ($play_again eq "y") { reset_game(); } else { last; } } sub reset_game { ($good_guesses, $bad_guesses) = ""; ($tot_found, $found_one, $occurrences, $all_occurrences, $guessed_ +word) = 0; }
On the second run through, I'm getting this error on the if ($guess !~ /[[:alpha:]]/) { line:
Unmatched [ in regex; marked by <-- HERE in m/[ <-- HERE ]/ at...
The variable $guess gets reset everytime a guess is made, so I didn't think I needed to reset it in the reset_game sub. But I tried that anyway and I still get the error.

I'm not sure if I'm using the best approach here, so any ideas would be much appreciated. TIA.

Replies are listed 'Best First'.
Re: Resetting variables
by davido (Cardinal) on Dec 21, 2004 at 17:25 UTC

    Lexical scoping exists to make your life easier. You don't need to reset variables if you use lexical scoping to your advantage. Here's a very concise example:

    use strict; use warnings; my $i = 0; while( $i++ < 10 ) { no warnings qw/uninitialized/; my $scoped; print "\$scoped contains $scoped\n"; $scoped = 100; }

    This snippet uses strict and warnings, but for the purposes of this particular demonstration turns off the "uninitialized value" warning. Next, it loops ten times. Each time, it creates a lexically scoped (my) variable called $scope. It prints that variable's contents (which are essentially empty, or undefined). It then assigns the number 100 to $scoped, and loops. If you weren't using lexical scoping, on the second loop iteration, $scoped would still contain 100, and that would print. But as you see if you run this, on each iteration $scoped is still empty. Why? Because at the end of each while block iteration, the lexical variable named $scoped "falls out of scope" (it disappears, forever gone). And on the next iteration, a new one is created (via 'my').

    What you need to do is write your script in such a way that when the player decides to play again, all the variables that need to be reset simply fall out of scope. The ones whos values should be kept would need to have been defined at a broader scope.

    This sort of thing is described in perlintro, perlsub and perlsyn, for starters. Therein you'll also read about properly passing parameters to subroutines rather than absorbing values through global osmosis. Welcome to a new way of thinking.


    Dave

      I'll reply to all of you that told me to use strict and warnings. I am doing that, and my code runs without errors or warnings the first pass. I also have lots of lexical scoping throughout my code, but I couldn't think of a better way to keep the variables I needed so I could use them in all the various subs and such. I tried passing paramaters and other things like that, but I kept getting 'unitialized' errors, so I went back to the global way. I have read perlintro about 50 times, and lots and lots of other documentation, but I'm still learning and making a lot of dumb mistakes. BTW, this game is not finished, and I haven't added in most of the data that I want the game to keep track of between runs (ex. a word list and related logic).

      Obviously this is a case of not enough code, so here is my entire script:

        The particular bug that's causing the error message you're referring to is in the reset_game() sub.

        The problem is that you're assigning a single scalar value '0' to a list. That means that $all_occurrences becomes undefined. Use an undefined value within a character class in a regexp and you get an error message. Try the followng:

        perl -e "my $var; 'string'=~/[$var]/"

        And here's the error...

        Unmatched [ in regex; marked by <-- HERE in m/[ <-- HERE ]/ at -e line + 1.

        If you're getting warnings when running under warnings and strictures, the goal is to understand why. The warnings are telling you something. In this case, you mentioned getting uninitialized value warnings. Instead of fixing the problem, you subverted the warnings, and that allowed bugs to creep in. If you had dug into why you were getting those undefined value warnings, you would have tracked the problem down to your reset sub.


        Dave

Re: Resetting variables
by BrowserUk (Patriarch) on Dec 21, 2004 at 17:23 UTC

    It is very hard to see how a hard coded regex would suddenly become invalid on a second pass. Indeed, it can't. At least not without something very, very unusual happening.

    I think you need to post the complete script that demostrates the problem, because your description of the problem does not make great deal of sense, and without being able to run the same code you are running and thereby reproduce the same errors you are encountering, there is no real way to help you.


    Examine what is said, not who speaks.        The end of an era!
    "But you should never overestimate the ingenuity of the sceptics to come up with a counter-argument." -Myles Allen
    "Think for yourself!" - Abigail        "Time is a poor substitute for thought"--theorbtwo         "Efficiency is intelligent laziness." -David Dunham
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
Re: Resetting variables
by trammell (Priest) on Dec 21, 2004 at 17:26 UTC
    A couple of suggestions:
    • use strict; and use warnings;
    • Use globals only where necessary, to prevent scary action-at-a-distance and simplify debugging.
Re: Resetting variables
by Roy Johnson (Monsignor) on Dec 21, 2004 at 17:30 UTC
    The code you included is not enough to duplicate your problem. In fact, it doesn't run because the print_word sub isn't defined. After defining it, I got a lot of complaints about the global variables, because I included
    use strict; use warnings;
    at the top of the program -- you should do that. Then I declared and initialized where necessary ($word_len and $tot_found need initial values), and ran the program without a problem (although it doesn't do anything interesting).

    The error you describe strikes me as very quirky. You might get around it by tacking the "o" (compile once) option onto the regex:

    /[[:alpha:]]/o

    Caution: Contents may have been coded under pressure.
Re: Resetting variables
by Random_Walk (Prior) on Dec 21, 2004 at 17:50 UTC

    Sadly you have not posted enough of your code to compile. Perhaps you can try to reduce the problem to the minimum set that still runs and exhibits the problem.

    If you scope each session of the game and its variables so that when one cycle is complete they go out of scope you will not have to call a reset_game routine

    use warnings; use strict; # variable we want to keep between runs my $high_score=0; my $play_it_again=1; while ($play_it_again) { my $last_score = play_game; $high_score = $last_score if $last_score > $high_score; $play_it_again=ask_user; } sub play_game { my $game_score=0; # game logic return $game_score; } sub ask_user { print "Do you want to play again (y or n)? "; my $play_again = <STDIN>; chomp($play_again); $play_again eq "y" ? 1 : 0 }

    use strict; Don't know if you do as this is obviously a small frag of something bigger and you do appear to use my aplenty

    Cheers,
    R.

Re: Resetting variables
by Sandy (Curate) on Dec 21, 2004 at 21:28 UTC
    By jove, I think I got it...

    Firstly, although I am not certain, I believe the error message is erroneously pointing to the wrong line $guess !~ m/[[:alpha:]]/ rather the error occurs in the elsif portion, where you have

    $guess =~ /[$good_guesses]/

    During the 1st pass, $good_guesses is either undefined, or set to a single letter (or multiple letter? doesn't matter).

    On second pass, the $good_guesses is reset to a null string.

    $guess =~ /[$good_guesses]/
    gets translates to ...
    $guess =~ /[]/
    However, if I am not mistaken, character classes will accept a ']' as a valid element of the character class, if it is the first element of the class. So, the parser is taking the ']' as an element of the class, and then continues to look for the closing square bracket.

    For whatever reason, if $good_guesses is undefined, the parser figures it out the way you meant. (UPDATE: Nope, that's not the reason, see update below)

    To get your code to work, I re-initialized $good_guesses = undef; in your "reset_game" sub.

    PS: I wrote my response after I got your program to work, but I did not actually go and read up on all the particulars of character classes in regular expressions, so I may not be entirely accurate.

    Sandy

    PS (again) ... That was fun!

    UPDATE: Made mistake... the reason the code works when $good_guesses is undefined is that there is a condition in front of the condition...

    elsif ((defined($good_guesses))&&($guess =~ /[$good_guesses]/))
    that prevents the interpretation
    $guess =~ /[]/
    when $good_guesses is not defined
      Hmm, I believe I am checking to make sure $good_guesses is defined (unless I don't understand what defined() really does):
      } elsif ((defined($good_guesses))&&($guess =~ /[$good_guesses]/)) { print "\n***You already guessed that letter!*** (hit any key)\ +n"; <>; } else { if ($word =~ /[$guess]/) { if (defined($good_guesses)) { $good_guesses .= "$guess" unless ($guess =~ /[$good_gu +esses]/); } else { $good_guesses .= "$guess"; }
        Simply put...
        my $var; # variable is undefined if ($var) { ... } # $var is false because it is undefined if (defined $var) {...} # false $var = ""; if ($var) { ... } # false because $var is null string if (defined $var) {...} # true because $var has been # defined as having a null string $var = 0; if ($var) { ... } # false because $var is zero if (defined $var) {...} # true because $var has been # defined as zero
      Yes, you appear to be right Sandy! I comment out two chunks that are doing stuff with $good_guesses, and it works (well, not the way I want, but I don't get the error). Now I have to figure out how to fix that, because it is now doing it on every run, not just the second pass. I don't know why though, because I'm defining $good_guesses = "" at the top.

      A lot of folks have wagged fingers at me for using global variables, and I know what they mean. I just don't know how else to do it! If I want to have a variable that needs to be accessed/updated by the main loop and by multiple subs...how can you do that without storing the variable outside of all the subs? If I try to put it in the loop it will be zeroed out on every loop, and the same with the subs. Any ideas would be appreciated.

      This is what I mean:

        Oops, our posts have crossed in the mail (so to speak).

        Try either

        if ($goodguesses && ($whatever =~ /[$goodguesses]/) {...}
        or undefine $goodguesses
        $goodguesses = undef;

        see previous post (just before your last one, for the differences between 'false' and 'defined'

Re: Resetting variables
by yacoubean (Scribe) on Dec 21, 2004 at 19:02 UTC
    This is still giving me problems. I've made a couple of modifications following the above suggestions, and now I'm getting the before mentioned error on the first run.
    if ($guess !~ /[[:alpha:]]/) { print "\n***Invalid letter*** (hit any key)\n"; <>; }
    The weird thing is that if I guess a letter, I get the error. If I guess a number (the condition matches) I don't get an error. What gives? It should just skip the statement when the condition doesn't match... Here's all my updated code: