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

The code block following is a small part of one of J.L. Ford's scripts that I referred to in an earlier post. I've added comments where I felt they would be helpful in your trying to help me. There are two "challenges" shown here. The code works, but not exactly the way I would like it to. In the "if" loop beginning on line 17, pressing <Enter> after the 3 "elsif" choices returns the code to line 4. I added the "while" loop beginning at line 8 to make certain the input was numeric. (And I added that silly line 10 to get around the fact that I couldn't make this work the way I wanted it to.) I want the code to return to line 4 when the user presses <Enter> at line 9. What happens is that line 9 repeats with each <Enter>. So, my question is: how/where do I insert the code in lines 8 thru 11 (minus line 10, of course) so that hitting <Enter> sends the code back to line 4? In addition to a probable "placement" change, do I need to change the loop structure?

1 until ($isover) { 2 clear_the_screen(); 3 print "Find the secret number between 1 and 100: \n\n"; 4 #print "Enter your guess: \n\n > "; ">" in book's script; why? 5 chomp($userguess = <STDIN>); 6 7 #following "while" loop added by me in a "challenge" 8 while ($userguess !~ /^[+-]?\d+$/ ) { #testing to see whether us +er entry is a number 9 print "\nYour guess must be a number. Try again. \n\n"; 10 print "-->"; 11 chomp($userguess = <STDIN>); 12 13 } 14 15 $totalguesses++; 16 17 if ($userguess == $secretnumber) { 18 clear_the_screen(); 19 print "You guessed it! Press <Enter> to return to the Main Men +u.\n\n"; 20 chomp($reply = <STDIN>); 21 $isover = 1; #terminates current game and returns to main menu 22 23 #first "elsif" block added by me in a "challenge" 24 } elsif ($userguess > 100) { 25 print "\n\nYou chose a number higher than 100. Press <Enter> + to try again. \n\n"; 26 chomp($reply = <STDIN>); 27 28 } elsif ($userguess <= $secretnumber) { 29 clear_the_screen(); 30 print "$userguess is too low. Press <Enter> to guess again.\ +n\n"; 31 chomp($reply = <STDIN>); 32 33 } elsif ($userguess >= $secretnumber) { 34 clear_the_screen(); 35 print "$userguess is too high. Press <Enter> to guess again. +\n\n"; 36 chomp($reply = <STDIN>); 37 } 38 } 38 39 return $totalguesses;

Replies are listed 'Best First'.
Re: Modifying loop structure and placement
by merlyn (Sage) on Dec 24, 2009 at 02:05 UTC
    Are you serious? This is one of the exercises in JLFord's book? This is exactly the same exercise rootbeer created for our Learning Perl book.

    Of course, it's not that unobvious of a question, but this now seems suspicious. Do you have a copy of Learning Perl to compare some of the other exercises for me? I'm curious now.

    -- Randal L. Schwartz, Perl hacker

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

      Yes, the Ford script does the same thing, but it's much more involved -- about 175 lines of code. Longer doesn't mean better, of course. I found your script and created the PL file. Much simpler, does the same thing, but uses a regular expression -- which Ford hasn't covered at that point in his book. There sure is a lot to learn here, and comparing the two scripts for "economy" and "verbosity" is just one of those things.
      (Oops. Forgot the second part of your question.) I have both books. I'll look for other similarities. Altho your code in this example is so completely different from Ford's that I would not have noticed the similarity right away. I'm just getting started with your book. I'll keep my eyes peeled. (You would probably satisfy your curiosity more quickly by buying a copy of his book -- or getting it from a library. He constructs each chapter so that the script he uses at the end of the chapter sums up what he presented in that chapter.)
Re: Modifying loop structure and placement
by keszler (Priest) on Dec 24, 2009 at 01:53 UTC
    One possibility is the redo command. Add a LABEL at line 4 (or 1) and replace lines 10 and 11 with redo LABEL.
Re: Modifying loop structure and placement
by AnomalousMonk (Archbishop) on Dec 24, 2009 at 14:43 UTC

    Some further "challenges" you might consider WRT this and other such exercises:

    • Use warnings and strictures:  use warnings; use strict;
      (Although you may already have used them and just not included the statements in the posted example code, which looks like a piece of a function definition.)
    • Use lexical variables (although again, you may simply have not included the definitions of these variables in the example).
    • Define lexicals as close to the point of use and in as narrow a scope as is reasonable.
    • Factor common sub-statements. Any time you see things like repeated  clear_the_screen();  print ...;  chomp ...; code phrases, you know you've got a candidate for (re)factoring. Sometimes it's necessary to perpetrate code like that shown in the OP, but as this is a learning exercise, take some time for (possibly extensive) re-working.
    • Consider posting small, complete and executable code examples. Wrap more lengthy examples in  <readmore> ... </readmore> tags, or perhaps post the complete example on your public scratchpad and make references to sub-sections of code for discussion.

      I don't know what "factor common sub-statemnts" means.

      In several of these exercises I have, in fact, re-worked code, but I'm not ready for any extensive re-working.

      Your last suggestion about using "readmore" is certainly worth considering for some posts. I understand what you're saying. But I'm still at the early learning stage, and posting this longer, non-executable code block allowed me to present a specific problem. When I've learned more, I hope I will be able to follow your suggestion.

Re: Modifying loop structure and placement
by ambrus (Abbot) on Dec 24, 2009 at 14:47 UTC
    The code block following is a small part of one of J.L. Ford's scripts that I referred to in an earlier post.

    Earlier post is J.L. Ford's "Challenges".

Re: Modifying loop structure and placement
by biohisham (Priest) on Dec 24, 2009 at 16:12 UTC
    (#print "Enter your guess: \n\n > "; ">" in book's script; why?)

    Just to provide a prompt for entering a number, ">" can be "%" or any other character for that matter, like you don't want the user to think that the scrip is hanging or something...

    Now to return to line 4 like you want just use a label, a label is used in conjunction with goto, so the first segment of your code can be re-written as:

    until ($isover) { clear_the_screen(); print "Find the secret number between 1 and 100: \n\n"; LABEL: print "Enter your guess: \n\n % "; #">" #in book's scrip +t; why? chomp($userguess = <STDIN>); #following "while" loop added by me in a "challenge" while ($userguess !~ /^[+-]?\d+$/ ) { #testing to see whether user + entry is a number print "\nYour guess must be a number. Try again. \n\n"; goto LABEL; }
    Notice, I did not use "chomp($userguess = <STDIN>);" in line 11 since the flow would go from 4 four onwards once again...

    I'd stress on using the pragmas:

    use strict; use warnings;
    these can be very helpful at ensuring that you don't mistype a variable name and can give you helpful warnings and thus greatly reduce the debugging time..

    with $totalguesses, you can't return outside a subroutine, eval statement or do{}, hence using a print statement instead...

    ("if" loop beginning on line 17, pressing <Enter> after the 3 "elsif" choices returns the code to line 4)

    Not entirely correct, what returns the code to line 4 is that the $isover in until(){} is not defined yet and hence this observation...

    There's an extensive repetitive writing of certain statements in your code, that warrants taking a look at Perl subroutines which can enhance the logic-flow interpretation of the code..

    Update: I did not mention that I pre-assigned $secretnumber with the value of 100....
    Here's your code once again with places for you to notice being commented accordingly:
    use strict; use warnings; my $isover; #using strict requires variables declaration wit +h 'my'. my $totalguesses; my $reply; until ($isover) { clear_the_screen(); print "Find the secret number between 1 and 100: \n\n"; LABELED: print "Enter your guess: \n\n % "; #labeling a posi +tion my $userguess; chomp($userguess = <STDIN>); while ($userguess !~ /^[+-]?\d+$/ ) { print "\nYour guess must be a number. Try again. \n\n"; goto LABELED; } $totalguesses++; my $secretnumber=100; if ($userguess == $secretnumber) { clear_the_screen(); print "You guessed it! Press <Enter> to return to the Main Me +nu.\n\n"; chomp($reply = <STDIN>); $isover = 1; #Defines $isover hence the evaluation would retu +rn true #the until(){} block would exit then... }elsif ($userguess > 100) { print "\nYou chose a number higher than 100.<Enter> to try aga +in.\n"; chomp($reply = <STDIN>); }elsif ($userguess < $secretnumber) { clear_the_screen(); print "$userguess is too low. Press <Enter> to guess again.\n\ +n"; chomp($reply = <STDIN>); }elsif ($userguess > $secretnumber) { clear_the_screen(); print "$userguess is too high. Press <Enter> to guess again.\n +\n"; chomp($reply = <STDIN>); } } print "your guesses $totalguesses\n"; sub clear_the_screen{ }
    Finally, checking the Perl Documentation at the website or through your console is so rewarding so make it your new-year's new habit :).... Best of luck :)

    Excellence is an Endeavor of Persistence. Chance Favors a Prepared Mind.

      Um, do you really want to use <= and >=?

      The tests are for less and greater than.


      Be Appropriate && Follow Your Curiosity
        <= and >= are used in the script I copied from Ford's book. And now that you point this out, using that code doesn't make sense. That's just basic math structure, and even this Perl neophyte knows that. I will have to pay closer attention to the script I copy from his book. Thanks.

      Thanks for taking the time to post this extensive response. More for me to study. Note that the code came from JL Ford's book. I didn't create it, I copied it.

        Another "oops." The above reply is to the post above yours.
      First reply to the post below yours was meant as a reply to your post. (I didn't aim too well.)
Re: Modifying loop structure and placement
by Marshall (Canon) on Dec 24, 2009 at 18:21 UTC
    I don't know about these various "challenges". But it appears to me that some basic "command line loop" hints are in order.

    There are three things that we want to accomplish in the first statement of the user command loop:

    1. prompt the user
    2. get some user input
    3. decide whether we should stop the loop (like a quit, QuIt command or whatever).

    In my opinion the most common condition that "ends the loop" should be apparent right in the looping statement, not buried later down as some "last" in the body.

    Of course with all programming things there are judgments and I do use "last".

    This is one of the few places where the "comma" operator is the right idea! If you look at my code below, the while() statement contains all three of the above things! We prompt the user, then get the input and check it against the "quit" command in various cases. The truth or falseness of the while() is determined by the last statement in the while. The use of the comma operator here also eliminates the use of multiple print statements to re-prompt the user. There are cases where "retry" is appropriate, but I don't think that this is one of them.

    So now that we have prompted the user and gotten some input and decided that the program should continue, the user input data validation starts.

    The first thing is that a blank line should do "nothing" except re-prompt. That's just like your OS does when you hit "enter" or "carriage return"! This is expected behaviour.

    Now we proceed to perform various validation checks. Some of this stuff can become "order dependent" and I therefore prefer a simple if(condition){error msg;next} structure versus a more complex if,elseif,else type of deal. This allows the order of the conditions to be moved around easily and quite frankly the computation power that can be saved in a more complex "if" structure is absolutely meaningless.

    Now of course there are standard programs like getOpt and the long option version of that. I don't think that we are talking about that here.

    #!/usr/bin/perl -w use strict; #basic "command loop" while ( (print "Enter a command: "), (my $line = <STDIN>) !~ /^\s*q(uit)?\s*$/i ) { #### Validate input and if ok, call a sub at the #### of validation next unless $line =~ m/\S/; #chomp ($line); #actually optional here (\n counts as $) if ($line =~ m/^\s*help\s*$|^\s*h\s*$/i) { print "there is no help for the helpless!!!\n"; next; } if ($line =~ m/^\s*[+-]{0,1}\s*\d+\s*$/) { print "wow, a number! try again!\n"; next; } if ($line !~ m/^add$|^del$/i) { print "invalid_command!!\n"; next; } add() if $line =~ m/add/; del() if $line =~ m/del/; } sub add { print "some kind of add happend\n";} sub del { print "some kind of del happened\n";} __END__ Some example interaction: C:\TEMP>perl stdcommandloop.pl Enter a command: 34 wow, a number! try again! Enter a command: -45 wow, a number! try again! Enter a command: +-56 invalid_command!! Enter a command: Enter a command: Enter a command: help there is no help for the helpless!!! Enter a command: Enter a command: del some kind of del happened Enter a command: add some kind of add happend Enter a command: quit > (OS prompt is back, end of program)

      Thanks. You're right. "...some basic 'command line loop' hints are in order." That's exactly what I hoped to learn from the responses to my post. You folks are giving me the homework I was looking for.

Re: Modifying loop structure and placement
by Marshall (Canon) on Dec 24, 2009 at 21:33 UTC
    I am just guessing again.
    But I figure that this some guessing game based upon some random number between 0-100.

    Here is one version.
    The rand() function is not really that random but it is good enough for this game.

    This shows how to start the game, validate input to the game and also how to restart a new session of the game.
    Certainly some refinements are possible, but this a general framework.

    #!/usr/bin/perl -w use strict; my $secret_number; my $total_guesses; sub init_game { $secret_number = int(rand 101); $total_guesses =0; } init_game(); #initial start of game parameters print "General Rules:\n"; print " I have a secret number from 0-100\n"; print " Your job is to guess this number!\n"; print " You will be given hints for each guess\n"; print " Enter quit if you give up!\n"; while ( (print "Enter a guess: "), (my $number = <STDIN>) !~ /^\s*q(uit)?\s*$/i ) { next if $number !~ /\S/; #skip blank lines $number =~ s/^\s*//; #no leading spaces $number =~ s/\s*$//; #no trailing spaces (also chomps) if ($number =~ /[-.]+/) { print "only unsigned integers are allowed!\n"; next; } if ($number =~ /\D/) { print "Invalid!! Only integer numbers >=0 are allowed!\n"; next; } if ($number >100) { print "Hey, you didn't pay attention, max number is 100!\n"; next; } if (game_finished($number)) { print "would you like to play again?:(Y|y|yes)\n"; if ( (my $answer =<STDIN>) =~ /^\s*Y(es)?\s*$/i) { init_game(); print "New Game Starting!!!\n\n"; } else { exit(0); } } } sub game_finished { my $number = shift; $total_guesses++; if ($number == $secret_number) { print "Horray! you got the secret number, $secret_number!\n"; print "But it took you $total_guesses total guesses to do it!!\ +n"; return(1); } if ($number < $secret_number) { print "You guessed too low! Dummy!\n"; return(0); } if ($number > $secret_number) { print "You guessed too high! Duh!\n"; return(0); } } Example Session: General Rules: I have a secret number from 0-100 Your job is to guess this number! You will be given hints for each guess Enter quit if you give up! Enter a guess: 50 You guessed too high! Duh! Enter a guess: 25 You guessed too high! Duh! Enter a guess: 12 You guessed too high! Duh! Enter a guess: 6 You guessed too low! Dummy! Enter a guess: 8 You guessed too high! Duh! Enter a guess: 7 Horray! you got the secret number, 7! But it took you 6 total guesses to do it!! would you like to play again?:(Y|y|yes) y New Game Starting!!! Enter a guess: q
    Update: I guess I could have combined two statements:
    if ($number =~ /[-.]+/) if ($number =~ /\D/) into just one regex: if( $number =~/[-.\D]/) { print "Invalid!! Only unsigned integer numbers >=0 are allowed! +\n"; next; } probably could add if( $number =~/[-.+\D]/) also to disallow the + sig +n. But I don't this minor issue detracts from the main point.
      Thanks for taking the time to create this extensive response. A lot for me to digest.
      A bit shorter version of the "guessing game"
      #!/usr/bin/perl -w use strict; my $magic = int (rand 100 ); my $guesses = 1; my $line; print "Guess a number between 0-99\n"; while ( (print "Enter an integer: "), $line = <STDIN>, $line !~ /^\s*q(uit)?\s*$/i ) { next if $line =~ /^\s*$/; #re-pompt on blank line if ( $line !~ /^\s*(\d{1,2})\s*$/ ) { print "Illegal entry! Try again!\n\n"; next; } print "Too high!\n\n" if $1 > $magic; print "Too low!\n\n" if $1 < $magic; last if $1 == $magic; $guesses++; } print "You got it right in $guesses guesses! \n" if ($line !~ /^\s*q(uit)?\s*$/i);