"To understand it, one must live it..." But do not underestimate the power of drilling, newly shaven one.
The "weight" of a function goes up by three for wrong answers, and by five if the user gives up and asks for the answer. Right answers decrease weight by 1. When weight reaches zero, the function is removed from the list of questions.
In a typical session, the user is presented with a series of definitions of Perl functions. He or she responds (hopefully) with the name of the function. If the answer is right, great! But if not, the user gets to try again. If they really can't get it, they can ask for the right answer by hitting "?".
The program saves a "gamefile" for the user so they will be able to pick up where they left off.
NOTE: some users (especially on Linux machines) have had problems with linefeeds in the code causing errors. To fix this, you may have to run this code to fix it after downloading the code:
perl -p -ibak -e 's/\r//g' yapat.pl
MAJOR UPDATE: Removed problems with newlines, will work on any machine now (I hope!)
#!/usr/bin/perl -w
=head1 NAME
yapat - Yet Another Perl Aptitude Test
A perlfunc driller/teacher which uses weighting to customize
the lesson
=head1 SYNOPSIS
yapat [-w weight] [-f gamefile]
=head1 ARGUMENTS
-w Initial weight. By default, this is 1.
-f Name of saved gamefile. By default, yapat.data.
=head1 DESCRIPTION
You're pretty good at Perl. But how's your perlfunc knowledge?
"To understand it, one must live it..." But do not underestimate the
power of drilling, newly shaven one.
yapat is a program which uses definitions taken from the
perlfunc page of PerlMonks.com to drill
the disciple, um, user in the names of the perl functions.
yapat uses a weighting system to determine how often the user
will see a definition... if the user misses a definition a few times,
they will see it a lot more often. If they get it right enough,
they'll never see it again!
The "weight" of a function goes up by three for wrong answers, and by
five if the user gives up and asks for the answer. Right answers
decrease weight by 1. When weight reaches zero, the function is
removed from the list of questions.
In a typical session, the user is presented with a series of
definitions of Perl functions. He or she responds (hopefully) with
the name of the function. If the answer is right, great! But if not,
the user gets to try again. If they realy can't get it, they can ask
for the right answer by hitting "?".
The program saves a "gamefile" for the user so they will be able
to pick up where they left off.
=head1 COMMANDS
In place of typing an answer, the user also may type:
score - see current score and functions which are "troubling" them
? - to give up and see the correct function
save - save the current gamefile without quitting
quit - to quit and save the current gamefile
=head1 AUTHOR
zeno
=head1 BUGS
I understand that these perlfuncs are not up to date with 5.6.
I want to fix this, but I haven't found a short version of the
5.6 perlfuncs (and I don't feel qualified to paraphrase the
long ones!)
=head1 UPDATES
28-feb-2001 Added a twenty function queue to prevent seeing the same q
+uestions
repeatedly.
Fixed some spelling errors and unclear definitions in the
+perlfuncs.
Added a help system
02-mar-2001 Fixed newline problem for linux machines
03-mar-2001 Really fixed newline problem (check out line 151)
=cut
use strict;
use Data::Dumper;
use Getopt::Std;
my %dict;
my %weight;
my ($max,$score, $tries, $correct)=(0,0,0,undef);
my @funcqueue = (1) x 20; # A queue of twenty last funcs
# use Getopt::Std to get command line args (p,516, Perl Cookbook)
my %option = ();
getopts("w:f:",\%option);
my $gamefile = $option{f} || "yapat.data";
my $default_weight = $option{w} || 1;
sub by_weight {
$weight{$a} <=> $weight{$b};
}
sub hmax {
# return the maximum value of a hash
my ($hash) = @_;
my $max = 0;
foreach (keys %$hash) {
if ($$hash{$_} > $max) {
$max = $$hash{$_};
}
}
return $max;
}
sub save {
open(DUMPFILE, ">$gamefile") or die "can't open $gamefile:$!";
print DUMPFILE Data::Dumper->Dump([\%weight], ['*weight']);
print DUMPFILE Data::Dumper->Dump([\@funcqueue], ['*funcqueue']);
print DUMPFILE "\$score = $score;\n";
print DUMPFILE "\$tries = $tries;\n";
close DUMPFILE;
print "Game saved to $gamefile\n";
}
sub help_message {
print << "helptext";
yapat gives you the definition of a perlfunc and asks you
to type in the function name. You can't mispell it. You
can't put it in the wrong case. You have to get it just
right! (Kind of like Perl). The more you get one wrong,
the more often you see it again.
You can also use these commands:
?\tWhen you give up and want to see the right answer
help\tTo see this menu
score\tTo see your score and a list of commands you're having trouble
+with
save\tTo save your game file
quit\tTo save your game file and quit
Good luck. Don't worry, it's not supposed to be easy.
But when you get through, you'll know more about Perl!
helptext
}
print "Welcome to Yet Another Perl Aptitude Test (yapat)!\n";
print "Do you want instructions (Y/n)? ";
$_ = <STDIN>;
if (!m/^n/i) {
help_message;
}
# read dictionary into a hash
foreach (<DATA>) {
if (m/^[\s\r]+$/) {next}
chomp;
my ($func,$defn) = m/([^:]+):(.*)/;
$dict{$func}=$defn;
# this to avoid \r problems in linux machines
chop($dict{$func});
$weight{$func}=$default_weight;
}
# if a previous dumpfile exists, eval it
# so the user is haunted by previous results!
$/="\n";
if (-e $gamefile) {
print "Continue previous game (Y/n)? ";
my $ui = <STDIN>;
if ($ui !~ m/^n/i) {
open (DUMPFILE,'<'.$gamefile) ;
undef $/;
eval <DUMPFILE>;
close DUMPFILE;
print "Game loaded\n";
}
}
$/="\n";
# This loop is where the actual quiz happens
while (1) {
# Determine which function to ask about,
# based on weight.
$correct = undef;
# pass that fat hash by reference
$max = hmax(\%weight);
# if the max is zero, the user has answered
# correctly for all functions, compensating
# for previous errors.
if ($max == 0) {
print "Astounding. You've answered everything correctly.\n";
# delete the gamefile
unlink $gamefile;
last;
}
foreach(reverse sort by_weight keys %weight) {
if (rand($max*10) < $weight{$_}) {
my $candidate=$_;
# make sure we haven't seen this in the last 20 turns
if (!map {(m/^$candidate$/)} (@funcqueue)) {
shift(@funcqueue);
push(@funcqueue,$candidate);
# shorten the queue if we are under twenty
if ((scalar keys %weight) < (@funcqueue+1)) {
shift(@funcqueue);
}
$correct = $candidate;
last;
}
}
}
# avoid error if we make it through the list
# without choosing a function.
if (!defined $correct) {next};
# Show the user the definition of the perl function
print "$dict{$correct} (weight: $weight{$correct})\n";
print "(type an answer, '?' to see the answer, 'help', 'score', 'sav
+e', or 'quit')\n";
# Get the user's input
$_=<STDIN>;
chomp;
# Wrong answer, or choices which require the same
# question again (save, score)
while ( !m/^$correct$/ and !m/^quit$/i and !m/\?/) {
if (m/^save$/) {
save();
} elsif (m/^score$/i) {
# User asks for score.
if (!$tries) {$tries++};
printf "Score: $score/$tries = %6.2f%%\n",100*($score/$tries);
print "There are ".(scalar keys %weight)." functions left on you
+r question list.\n";
# tell the user what functions seems to be eluding him or her
my $trouble = undef;
foreach(reverse sort by_weight keys %weight) {
if ($weight{$_} > 1) {
$trouble .= ((defined $trouble)?', ':'').$_;
}
}
if ($trouble) {
print "You seem to be having trouble with:\n $trouble\n\n";
} else {
print "So far, you seem to be doing great!\n\n";
}
} elsif (m/^help$/i) {
help_message;
} else {
$tries++;
printf "Sorry.\nScore: $score/$tries = %6.2f%%\n",100*($score/$t
+ries);
$weight{$correct} += 2;
}
print "$dict{$correct} (weight: $weight{$correct})\n";
print "(type an answer, '?' to see the answer, 'help', 'score', 's
+ave', or 'quit')\n";
$_=<STDIN>;
chomp;
}
if (m/$correct/) {
# player shoots, player SCORES!
$tries++;
$score++;
$weight{$correct}-- unless ($weight{$correct} < 1);
printf "Correct!\nScore: $score/$tries = %6.2f%%\n",100*($score/$t
+ries);
if ($weight{$correct} < 1) {
delete $weight{$correct};
delete $dict{$correct};
print "$correct removed from question list!\n";
}
print "\n";
} elsif (m/q/i) {
# player quits
printf "Final score: $score/$tries = %6.2f%%\n",100*($score/$tries
+)
if ($tries > 0);
save();
last;
} elsif (m/\?/) {
# player gives up and asks for the right answer
$tries++;
$weight{$correct} += 5;
print "$correct\t-> $dict{$correct} (weight: $weight{$correct})\n"
+;
printf "Score: $score/$tries = %6.2f%%\n\n",100*($score/$tries);
}
}
__DATA__
-X:run a file test
abs:absolute value function
accept:accept an incoming socket connect
alarm:schedule a SIGALRM
atan2:arctangent of Y/X
bind:binds an address to a socket
binmode:prepare binary files on old systems
bless:create an object
caller:get context of the current subroutine call
chdir:change your current working directory
chmod:changes the permissions on a list of files
chomp:remove a trailing record separator from a string
chop:remove the last character from a string
chown:change the owership on a list of files
chr:get character this number represents
chroot:make directory new root for path lookups
close:close file (or pipe or socket) handle
closedir:close directory handle
connect:connect to a remote socket
continue:optional trailing block in a while or foreach
cos:cosine function
crypt:one-way passwd-style encryption
dbmclose:breaks binding on a tied dbm file
dbmopen:create binding on a tied dbm file
defined:test whether a value, variable, or function is defined
delete:deletes a value from a hash
die:raise an exception or bail out
do:turn a BLOCK into a TERM
dump:create an immediate core dump
each:retrieve the next key/value pair from a hash
endgrent:be done using group file
endhostent:be done using hosts file
endnetent:be done using networks file
endprotoent:be done using protocols file
endpwent:be done using passwd file
endservent:be done using services file
eof:test a filehandle for its end
eval:catch exceptions or compile code
exec:abandon this program to run another
exists:test whether a hash key is present
exit:terminate this program
exp:raise e to a power
fcntl:file control system call
fileno:return file descriptor from filehandle
flock:lock an entire file with an advisory lock
fork:create a new process just like this one
format:declare a picture format with use by the write function
formline:internal function used for formats
getc:get the next character from the filehandle
getgrent:get next group record
getgrgid:get group record given group user ID
getgrnam:get group record given group name
gethostbyaddr:get host record given its address
gethostbyname:get host record given name
gethostent:get next hosts record
getlogin:return who logged in at this tty
getnetbyaddr:get network record given its address
getnetbyname:get networks record given name
getnetent:get next networks record
getpeername:find the other end of a socket connection
getpgrp:get process group
getppid:get parent process ID
getpriority:get current nice value
getprotobyname:get protocol record given name
getprotobynumber:get protocol record numeric protocol
getprotoent:get next protocols record
getpwent:get next passwd record
getpwnam:get passwd record given user login name
getpwuid:get passwd record given user ID
getservbyname:get services record given its name
getservbyport:get services record given numeric port
getservent:get next services record
getsockname:retrieve the sockaddr for a given socket
getsockopt:get socket options on a given socket
glob:expand filenames using wildcards
gmtime:convert UNIX time into record or string using Greenwich time
goto:create spaghetti code
grep:locate elements in a list which test true against a given criteri
+on
hex:convert a string to a hexadecimal number
import:patch a module's namespace into your own
int:get the integer portion of a number
ioctl:system-dependent device control system call
join:join a list into a string using a separator
keys:retrieve list of indices from a hash
kill:send a signal to a process or process group
last:exit a block prematurely
lc:return lower-case version of a string
lcfirst:return a string with just the first letter in lower case
length:return the number of bytes in a string
link:create a hard link in the filesytem
listen:register your socket as a server
local:create a temporary value for a global variable (dynamic scoping)
+
localtime:convert UNIX time into record or string using local time
log:retrieve the natural logarithm for a number
lstat:stat a symbolic link
m:match a string with a regular expression pattern
map:apply a change to a list to get back a new list with the changes
mkdir:create a directory
msgctl:SysV IPC message control operations
msgget:get SysV IPC message queue
msgrcv:receive a SysV IPC message from a message queue
msgsnd:send a SysV IPC message to a message queue
my:declare and assign a local variable (lexical scoping)
next:iterate a block prematurely
no:unimport some module symbols or semantics at compile time
oct:convert a string to an octal number
open:open a file, pipe, or descriptor
opendir:open a directory
ord:find a character's numeric representation
pack:convert a list into a binary representation
package:declare a separate global namespace
pipe:open a pair of connected filehandles
pop:remove the last element from an array and return it
pos:find or set the offset for the last/next m//g search
print:output a list to a filehandle
printf:output a formatted list to a filehandle
prototype:get the prototype (if any) of a subroutine
push:append one or more elements to an array
q:singly quote a string
qq:doubly quote a string
quotemeta:quote regular expression magic characters
qw:quote a list of words
qx:backquote quote a string
rand:retrieve the next pseudorandom number
read:fixed-length buffered input from a filehandle
readdir:get a directory from a directory handle
readlink:determine where a symbolic link is pointing
recv:receive a message over a Socket
redo:start this loop iteration over again
ref:find out the type of thing being referenced
rename:change a filename
require:1) demands some semantics, 2) demands that the current version
+ of Perl be at least as recent as that version, 3) demands that a lib
+rary file be included if it hasn't already been included.
reset:clear all variables of a given name
return:get out of a function early
reverse:flip a string or a list
rewinddir:reset directory handle
rindex:right-to-left substring search
rmdir:remove a directory
s:replace a pattern with a string
scalar:force a scalar context
seek:reposition file pointer for random-access I/O
seekdir:reposition directory pointer
select:reset default output or do I/O multiplexing
semctl:SysV semaphore control operations
semget:get set of SysV semaphores
semop:SysV semaphore operations
send:send a message over a socket
setgrent:prepare group file for use
sethostent:prepare hosts file for use
setnetent:prepare networks file for use
setpgrp:set the process group of a process
setpriority:set a process's nice value
setprotoent:prepare protocols file for use
setpwent:prepare passwd file for use
setservent:prepare services file for use
setsockopt:set some socket options
shift:remove the first element of an array, and return it
shmctl:SysV shared memory operations
shmget:get SysV shared memory segment identifier
shmread:read SysV shared memory
shmwrite:write SysV shared memory
shutdown:close down just half of a socket connection
sin:return the sine of a number
sleep:block for some number of seconds
socket:create a socket
socketpair:create a pair of sockets
sort:sort a list of values
splice:add or remove elements anywhere in an array
split:split up a string using a regexp delimiter
sprintf:formatted print into a string
sqrt:square root function
srand:seed the random number generator
stat:get a file's status information
study:optimize input data for repeated searches
sub:declare a subroutine, possibly anonymously
substr:get or alter a portion of a string
symlink:create a symbolic link to a file
syscall:execute an arbitrary system call
sysread:fixed-length unbuffered input from a filehandle
system:run a separate program
syswrite:fixed-length unbuffered output to a filehandle
tell:get current seekpointer on a filehandle
telldir:get current seekpointer on a directory handle
tie:bind a variable to an object class
time:return number of seconds since 1970
times:return elapsed time for self and child processes
tr:transliterate a string (non-AWK style)
truncate:shorten a file
uc:return upper-case version of a string
ucfirst:return a string with just the first letter in upper case
umask:set file creation mode mask
undef:remove a variable or function definition
unlink:remove one link to a file
unpack:convert binary structure into normal perl variables
unshift:prepend more elements to the beginning of a list
untie:break a tie binding to a variable
use:load in a module at compile time
utime:set a file's last access and modify times
values:return a list of the values in a hash
vec:test or set particular bits in a string
wait:wait for any child process to die
waitpid:wait for a particular child process to die
wantarray:get list vs array context of current subroutine call
warn:print debugging info without bailing
write:print a picture record
y:transliterate a string (AWK style)