http://www.perlmonks.org?node_id=67678
Category: Miscellaneous
Author/Contact Info Thomas Stanley
Description: This program will read data from a CSV file and let you select how many
questions you wish to have on your test. It will then select random
questions for you to answer. This program doesn't yet support multiple
length questions(can't have multiple choice and true/false together)
The format for a question in the CSV file is as follows:

ID|Question|Choice|Choice|Choice|Choice|Answer

Where ID is a number relating to the question

**NOTE** I will be upgrading this program to include the use of modules, most likely Text::CSV_XS or tilly's Text::xSV module. I also would like to figure out how to include true/false questions as well.

**NOTE 2**Version 1.02 is now official. It uses tilly's Text::xSV module to parse the question file, which is pipe delimited.

**NOTE 3**Version 1.06 Now can support both true/false and multiple choice questions in the same question file.

**NOTE 4**Version 1.08 Now has a text-based menu interface, which means you can chose from a number of tests. I also wrote some POD for it as well
#!/usr/bin/perl -w
###############################################
##    Name: QuizTaker                        ##
## Version: 1.08                             ##
##  Author: Thomas Stanley                   ##
###############################################
use strict;
use File::Slurp;
use Text::Wrap;

my $A=0;
my $Choice;

while($A == 0){
  system('clear');
  print"\n";
  print"\t\t     *******************************     \n";
  print"\t\t     ***       QuizTaker.pl      ***     \n";
  print"\t\t     ***       Version 1.08      ***     \n";
  print"\t\t     ***    by Thomas Stanley    ***     \n";
  print"\t\t     *******************************     \n";
  print"\n"x4;

  print"\t\t\t\t1) Take a test\n";
  print"\t\t\t\t2) Exit\n";
  print"\n\n";
  print"\t\t\t\tYour choice: ";
 
  $Choice = <STDIN>;
  chomp($Choice);
  my $B = 0;
 
  while($B == 0){
    if($Choice == 1){
      &Choose();
      $B=1;
    }elsif($Choice == 2){
      print"\n\n\t\t\t\tThanks for playing!\n";
      $A = 1;
    }
    $B = 1;
  }
}
##############################
sub Choose{
  system('clear');
  
  my %File_Lengths=();
  my %NumberOfTests=();
  my $Choice;
  my $MaxQuestions;
  my $Length;
  my $File;
  my $C;
  my $D = 1;
  my $H = 0;
  my $I = 0;

  my $refFile_lengths = GetFileLengths(\%File_Lengths);

  foreach $C(keys %File_Lengths){ 
    $C=~s/\.\w{3}//;
    $NumberOfTests{$D} = $C;
    $D++;
  }
  print"\n";
  foreach $C(keys %NumberOfTests){
    print"\t\t\t$C) $NumberOfTests{$C}\n";     
  }
  
  while($H == 0){
    print"\n\t\t\tEnter the test number: ";
    my $number = <STDIN>;
    chomp $number;
    $Choice = "$NumberOfTests{$number}.psv";

    if(exists $NumberOfTests{$number}){
      while($I == 0){
        print"\n\t\tHow many questions do you wish to answer (Max = $F
+ile_Lengths{$Choice}): ";
        my $MaxQuestions = <STDIN>;
        chomp $MaxQuestions;

        if(($MaxQuestions<1)||($MaxQuestions>$File_Lengths{$Choice})){
          print"\n\n\t\tPlease re-enter the number of questions.";
          sleep(2);
    }else{
          $Length = $File_Lengths{$Choice};
          &Randomize($Choice,$Length,$MaxQuestions);
          $I = 1;
        }
      }    
    }else{
      print"\n\t\tPlease choose again!!\n\n";
      sleep(2);
    }
    $H = 1;
  }
  return;
}
##################################
sub GetFileLengths{
  my $File_Lengths = shift;
  my $Directory = "./Questions";
  my $File;
  my @Files = read_dir($Directory);
  my $Check;
  my $path;

  foreach $File(@Files){
    if($File=~/\.psv/){
      $path = $Directory."/".$File;
      $Check = Check($path);
      if($Check<1){}else{
        $$File_Lengths{$File} = $Check;
      }
    }
  }            
  
  my $number = keys %$File_Lengths;

  if($number == 0){
   die"No files available\n";
  }
  return $File_Lengths;
} 
#############################
sub Check{
  my $file=shift;
  my $lines;
  if(-e $file){
   $lines =`wc -l < $file`;
  }else{
    die"Can't find $file!\n";
  }
  return $lines;
}
#############################
sub Randomize{
  my $File = shift;
  my $FileLength = shift;
  my $Max = shift;
  my $Directory = "./Questions";
  my %Randoms = ();
  my %Data=();
  my %TestQuestions=();
  my %TestAnswers=();
  srand();

  my $Path = $Directory."/".$File;
 
  my $ref=Loader(\%Data,$Path);
  
  for(1..$Max){
    my $question_number = int(rand($FileLength)+1);
    redo if exists ($Randoms{$question_number});
    $Randoms{$question_number} = 1;
  }

  my @Randoms = keys %Randoms;
  my $ref2=shuffle(\@Randoms);

  for(my $a=0;$a<$Max;$a++){
    $TestAnswers{$Randoms[$a]} = pop@{$Data{$Randoms[$a]}};
    $TestQuestions{$Randoms[$a]} = $Data{$Randoms[$a]};
  }

  my %QuestionLength = ();
  my $b;

  foreach my $key (keys %TestQuestions){
    $b = @{$TestQuestions{$key}};
    $QuestionLength{$key} = $b;
  }

  &Tester(\%TestQuestions,\%TestAnswers,\@Randoms,\%QuestionLength,$Ma
+x);
  return;
}
######################
sub Loader{
  my $Data=shift;
  my $file=shift;
  my $question_number;
  my $length;
  my $f;
  my @Sorter=();


  open(FH,"$file")||die"Can't open $file: $!\n";

  while(<FH>){
    @Sorter=split /\|/;
    $question_number=shift @Sorter;
    $length=@Sorter;

    for($f=0;$f<$length;$f++){
      $$Data{$question_number}[$f]=$Sorter[$f];
    }
  }

  close FH;
  return $Data;
}
##########################
sub Tester{
  my $Questions=shift;
  my $Answers=shift;
  my $Randoms=shift;
  my $question_length=shift;
  my $Max=shift;
  my $length;
  my $question_number=1;
  my $question_answer="";
  my $answer;
  my $number_correct=0;
  my $key;
  my $g;
  
  system('clear');
  print"\n";

  while($question_number<=$Max){
    $key=shift @$Randoms;
    $length=$$question_length{$key};

    print"Question Number $question_number\n";

    for($g=0;$g<$length;$g++){
      print wrap("","","$$Questions{$key}[$g]\n");
    }
    
    print"\nYour answer: ";
    $answer=<STDIN>;
    chomp $answer;
    $answer=uc $answer;
    $question_answer=$$Answers{$key};
    chomp $question_answer;
    $question_answer = uc $question_answer;

    if($answer eq $question_answer){
      print"That is correct!!\n\n";
      $question_number+=1;
      $number_correct+=1;
    }else{
      print"That is incorrect!\n";
      print"The correct answer is $question_answer.\n\n";
      $question_number+=1;
    }
  }
  &Final_Score($number_correct, $Max);

  return;
}
###########################
sub Final_Score{
  my $Correct=shift;
  my $Max=shift;
  my $Percentage=($Correct/$Max)*100;

  print"You answered $Correct out of $Max correctly.\n";
  printf"For a final score of %.2f%%\n",$Percentage;
  sleep(4);
  return;
}

#
# Fisher-Yates Shuffle
#

sub shuffle {
    my $array = shift;
    my $i;
    for ($i = @$array; --$i; ) {
        my $j = int rand ($i+1);
        next if $i == $j;
        @$array[$i,$j] = @$array[$j,$i];
    }
}
__END__;

=pod

=head1 QuizTaker.pl Version 1.08

This program will allow you to take simple tests,
based upon a file of questions and answers that you
create. A sample file of questions have been included
with this distribution, so you can see how its done.
The values are stored within the file, with the pipe
character "|" as a separator. Please ensure that you
have an extension of .psv on the end of your question/answer
files.

To run this program, type the following at the command prompt in
the directory where you have installed QuizTaker:


        ./QuizTaker.pl


This program also requires the File::Slurp module which can be
found on your nearest CPAN mirror.


=head2 Improvements
 
=over 4

=item 1

Now includes this section of POD

=item 2

Now uses the Text::Wrap module found in the core distribution
of Perl, to wrap the lines of long questions

=item 3

Now has a menu based interface, and allows you to choose
which test you would like to take.

=item 4

Cleaned up some syntax within the functions to make them
look better

=item 5

Fixed a bug where if the letter in the answer file
is in lowercase, it would break in the Test subroutine.

=item 6

Implemented the Fisher-Yates shuffling algorithm to provide for
a more random sequence in the array of questions

=back

=head2 To Do List

=over 4

=item 1

Make a graphical user interface using the Tk module

=back

=head2 Questions/Comments

If you have any questions or comments about this program
please email me at Thomas_J_Stanley@msn.com . I am always
striving to make this program better.

=cut
Replies are listed 'Best First'.
Re (tilly) 1: QuizTaker.pl
by tilly (Archbishop) on Mar 28, 2001 at 09:37 UTC
    Shameless plug time.

    If you are going to be CSV format, why not do it right and handle arbitrary text in fields with Text::xSV? :-)

      I actually was considering using modules to do this, but decided not to,
      in the interest of not having some one who isn't familiar with Perl using
      this program and having to install a module on their system to run it.

      TStanley
      In the end, there can be only one!