Perl - Moving the Secret Word List into a Separate File

Well, to start with I decided that I needed a file and a way to access it.  Now, we've seen at least one "input" file in our program beginning in lesson three - <STDIN>.  Actually, Perl automatically provides three filehandles called STDIN, STDOUT, and STDERR, corresponding to the three standard I/O channels in most programming environments - keyboard, screen, and error log.  So, how do we get another "handle" attached to a file of our own choice?

Here's a small chunk of code to do that:

sub init_words {
	open (WORDSLIST, "wordslist");
	while ($name = <WORDSLIST>) {
		chomp ($name);
		$word = <WORDSLIST>;
		chomp ($word);
		$words{$name} = $word;
	}
	close (WORDSLIST);
}

Again, we're putting this code into a subroutine so that we can keep the main part of the program uncluttered, as well as for flexibility (later in our stroll you'll see what I'm referring to here).

The arbitrarily chosen format of the word list is one item per line, with names and words, alternating.  So, for our current "database," we'd have something like this:

	jason
	camel
	michelle
	llama
	tracey
	alpaca
	karee
	panda
	patrick
	ram
	larry
	wolf

The open function initializes a filehandle named WORDSLIST by associating it with a file named wordslist in the current directory.  Note that the filehandle doesn't have a funny character in front of it as the three variable types do.  Also, filehandles are generally uppercase - although they aren't required to be - for reasons detailed later.  We'll also put some additional open "checks" in later in this lesson.

The while loop reads lines from the wordslist file (via the WORDSLIST filehandle) one line at a time.  Each line is stored into the $name variable.  At the end of the file, the value returned by the <WORDSLIST> operation is an empty string (technically it's undef, but close enough for right now), which looks false to the while loop, and terminates it.  That's how we get out at the end.

So, we've read a line into $name.  First, off comes the newline using the chomp function.  Then, we have to read the next line to get the secret word, holding that in the $word variable.  It, too, gets the newline hacked off.

The final line of the while loop puts $word into %words with a key of $name, so that the rest of the program can access it later.

Once the file has been read, the filehandle can be recycled with the close function.  (Filehandles are automatically closed anyway when the program exits, but we're trying to be tidy.  We'll add more "tidiness" to the close function later in this lesson.)

The subroutne definition can go after or before the other one.  And we invoke the subroutine instead of setting %words in the beginning of the program, so one way to wrap up all of this might look like:

#!/usr/bin/perl -w
&init_words();
print "What is your name? ";
$name = <STDIN>;
chomp ($name);
if ($name =~ /^Larry\b/i) {				# back to the other way (;-)
	print "Hello, Larry!  How good of you to be here!\n";
}
else {
	print "Hello, $name!\n"; 			# ordinary greeting
	print "What is the secret word? ";
	$guess = <STDIN>;
	chomp ($guess);
	while (! &good_word($name,$guess)) {
		print "Wrong, try again.  What is the secret word? ";
		$guess = <STDIN>;
		chomp ($guess);
	}						# end of while not correct
	print "Good guess!  Have a great day!!";
}

# subroutines from here down

sub init_words {
	open (WORDSLIST, "wordslist") || die "can't open wordslist: $!";
	while (defined ($name = <WORDSLIST>)) {
		chomp ($name);
		$word = <WORDSLIST>;
		chomp ($word);
		$words{$name} = $word;
	}
	close (WORDSLIST) || die "couldn't close wordslist: $!";
}

sub good_word {
	my($somename,$someguess) = @_;			# name the parameters
	$somename =~ s/\W.*//;				# zap everything after the first word
	$somename =~ tr/A-Z/a-z/;			# lowercase everything
	if ($somename eq "larry") {			# should not need to guess
		return 1;				# return value is true
	}
	elsif ((($words{$somename}) || "mfd") eq $someguess) {	
		return 1;				# return value is true
	}
	else {
		return 0:				# return value is false
	}
}

Now it's starting to look like a full grown program.  Notice the first executable line is an invocation of init_words().  The return value is not used in a further calculation, which is good because we didn't return anything remarkable.  In this case, it's guaranteed to be a true value (the value 1, in particular), because if the close had failed, the die would have printed a message to STDERR and exited the program.  We'll look in detail at the die function in a later lesson, but because it's essential to check the return values of anything that might fail, we'll get into the habit of using it right from the start.  The $! variable, contains the system error message explaining why the system call failed (we'll also look in more detail at that in a later lesson).

The open function is also used to open files for output, or open programs as files (we'll see this in a couple of lessons and in general for the open function in more detail in a later lesson too).

The defined function is how you test for undef when it matters (more on this in a later lesson).

Let's build in a little security.  The Chief Director of Secret Word Lists comes to you and shouts, "That secret word list has got to change at least once a week!"  We can't force the list to be different, but we could issue a warning if the secret word list has not been modified in more than a week!  What programming elements do you think might be required for this task?  See you next lesson!