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

Recently one of my friends wrote a guess-the-word game in c, which prompted me to write a shorter one in perl, with more features. The coding was fine until I decided to support an option to use a random word from /usr/dict/words as the answer as opposed to one entered to <>.

Essentially the problem was that I passed the dict as $ARGV[0] and subsequently attempted to use <>, only to find that the behavior changed due to the presence of @ARGV as per perlop <EXPR>. While I was able to "fix" the code by clobbering @ARGV, equating it to the empty list, this brings me to the crux of my question:

When using both @ARGV and <STDIN>, what's the perlish implementation without clobbering @ARGV?

I would presume explicit use of <STDIN> or even readline(*STDIN) is better coding practice, but I'm at a loss as to a perlish impelementation.

UPDATE: By request, the guess-the-word game code:

#!/usr/bin/perl -w use strict; my ($answer, $guess, $guesses) = ("", "", 0); if (@ARGV) { open FILE, $ARGV[0] or die "No such dictionary..."; rand($.) < 1 && ($answer = (split(/[\r\n]+/,$_))[0]) while <FI +LE>; close FILE; @ARGV = (); } else { $answer = (split(/[\r\n]+/,<>))[0]; } system 'clear'; do { $guess = (split(/[\r\n]+/,<>))[0]; unless ($guess eq $answer) { if (length $guess != length $answer) { print "The answer is ", length $guess > length + $answer ? "shorter" : "longer", "\n"; } else { print "The $$_[0] letter ($$_[1]) is ", $$_[2] +? "" : "in", "correct\n" foreach map { [$_, (split //, $guess)[$_], ( +split //, $guess)[$_] eq (split //, $answer)[$_] ? 1 : 0 ] } (0..(len +gth $guess) - 1); } } $guesses++; } while ($answer ne $guess); print "You figured it out in a mere $guesses guesses\n";

Lastly, I should note that this was a question of style and practice, rather than a request for help with a particular code. After reading the reply by EvanCarroll, which concurs with my presumption that using <STDIN> is better programming practice, I re-read perlop and discovered several loops of the form:

while (<>) { ... }

When I read such passages while learning perl, I misunderstood <> for <STDIN>. While this misunderstanding has taken awhile to surface, I now know the distinction between <> and <STDIN>.

Thanks to everyone who helped out -- and feel free to try the game :-)

Replies are listed 'Best First'.
Re: Using <> in the presence of @ARGV
by pg (Canon) on Oct 04, 2005 at 02:33 UTC

    <STDIN> is fine, what becomes a problem is <>.

    use strict; use warnings; while (<>) { print; }

    If you run this with perl -w blah.pl something, <> is going to read from something, and fails if something is not a file etc. This is probably a feature you don't want.

    In general, the best practice is to avoid the feature of reading from @ARGV, and in your code use <STDIN>. If you want to have a choice of reading either a file or user input, use redirect.

    use strict; use warnings; while (<STDIN>) { print; }

    If you want the input come from a file instead of user, just do something like perl -w blah.pl <somefile.

Re: Using <> in the presence of @ARGV
by EvanCarroll (Chaplain) on Oct 04, 2005 at 01:47 UTC
    @ARGV has nothing at all to do with <STDIN>. One is an array of command line arguements. One is an input stream.

    If your old program was reading from <>, and you were sending it information through STDIN, than you were employing bad practice, as you should have been reading from <STDIN>, and not <> (the diamdond opperator.)

    Now if you want the arguement to be a random line in dict, than one easy way to go about this is to record the ammount of lines, and read from the file and skip until $. = the random number between 1..LAST_LINE. Because code size is an issue that solution is probably the best for you.


    Evan Carroll
    www.EvanCarroll.com
      Now if you want the arguement to be a random line in dict, than one easy way to go about this is to record the ammount of lines, and read from the file and skip until $. = the random number between 1..LAST_LINE. Because code size is an issue that solution is probably the best for you.
      Actually, a slightly more efficient solution is the following gem, slightly adapted from perlfaq5:
      open my $fh, "somefile" or die $!; my $line; rand($.) < 1 && ($line = $_) while <$fh>;
      This sets $line to a uniform, random line from "somefile" and it has the advantage of only reading through the file once. For that reason (and the fact that the important stuff can be reduced to a one-liner), it's probably more concise than the naïve solution!

      blokhead

        If you need to pick more than one word during a run of this script, then a solution based on Tie::File would probably be better.

        use Tie::File; tie my @word, 'Tie::File', '/usr/dict/words' or die $!; print "$word[rand @word]\n" for 1..10;

        -sauoq
        "My two cents aren't worth a dime.";
        
        If memmory isn't an issue, my dict file is only 880kb:
        my @file = <$fh>; print $file[int rand $#file];


        Evan Carroll
        www.EvanCarroll.com
Re: Using <> in the presence of @ARGV
by GrandFather (Saint) on Oct 04, 2005 at 02:38 UTC

    A cleaner way to do it is use shift to grab the first parameter off the command line:

    f (@ARGV) { open FILE, shift or die "No such dictionary..."; rand($.) < 1 && ($answer = (split(/[\r\n]+/,$_))[0]) while <FI +LE>; close FILE;

    Perl is Huffman encoded by design.
Re: Using <> in the presence of @ARGV
by GrandFather (Saint) on Oct 04, 2005 at 01:41 UTC

    We would like to see the pertinent code. I imagine it looks something like:

    use strict; use warnings; # Create a test dictionary. PM sample code only open outFile, "< dict.txt"; print outFile "apple\norange\nonion\n"; close outFile; # Read the dictionary ... # Code that causes grief # Play the game my $more; do { ... # more code that causes grief print "Guess another? "; $more = <>; } while ($more =~ /^y(es)?\b/i);

    Perl is Huffman encoded by design.