#!/usr/pkg/bin/perl

=begin metadata

Name: arithmetic
Description: improve your arithmetic skills
Author: Abigail, perlpowertools@abigail.be
License: perl

=end metadata

=cut


use strict;
use integer;

use File::Basename qw(basename);
use Getopt::Std qw(getopts);

use constant EX_SUCCESS => 0;
use constant EX_FAILURE => 1;

my $Program = basename($0);
my ($VERSION) = '1.3';

my %opt;
getopts('o:r:', \%opt) or usage();
my $operators = '+';
if (defined $opt{'o'}) {
    usage() if ($opt{'o'} =~ m{[^-x+/]});
    $operators = $opt{'o'};
}
my $range = 10;
if (defined $opt{'r'}) {
    usage() if ($opt{'r'} !~ m/\A[0-9]+\Z/);
    $range = $opt{'r'};
}
# Special case the range of 0. Since the right operator can only be 0,
# division should be disallowed. die() if division was the only possible
# operator. There will be nothing left.
if ($range == 0) {
    $operators =~ s{/+}{}g;
    unless ($operators) {
        warn "$Program: division by 0 is not allowed\n";
        exit EX_FAILURE;
    }
}
usage() if @ARGV;

# Play the game!
my $start     = time;
my $questions = 0;
my $correct   = 0;

$SIG {INT}    = \&report;

use constant QUESTIONS => 20;
use constant REMEMBER  => 20;  # One in five? *shrug*

$range ++;  # Because bounds are inclusive.
# Remember questions with wrong answers.
my %remember = map {$_ => []} split // => $operators;
while ($questions < QUESTIONS) {
    my $operator = substr $operators => rand length $operators, 1;

    my ($left, $right, $answer);

    if (@{$remember {$operator}} && rand 100 < REMEMBER) {
        # Remember something we did wrong.
        ($left, $right, $answer) =
              @{$remember {$operator} -> [rand @{$remember {$operator}}]};
    }
    else {
        # Right is always in the range; with the exception of a 0 divisor.
        do {$right = int rand $range} while $operator eq '/' && $right == 0;

        if ($operator eq '-' || $operator eq '/') {
            # Pick the answer.
            $answer = int rand $range;

            # Find the other operand.
            $left = $operator eq '-' ? $answer + $right : $answer * $right;
        }
        else {
            # Pick the other operand.
            $left = int rand $range;

            # Find the answer.
            $answer = $operator eq '+' ? $left + $right : $left * $right;
        }
    }

    my $guess;
    # Keep trying till we get something that's a number.
    LOOP: {
        print "$left $operator $right = ";
        $guess = <>;
        if (!defined($guess)) {
            report();
        }
        chomp $guess;
        $guess =~ s/\A\s+//;
        $guess =~ s/\s+\Z//;
        if (length($guess) == 0 || $guess =~ /\D/) {
            print "Please type a number.\n";
            redo;
        }
    }

    if ($guess == $answer) {
        # We got it right, forget it now.
        $remember {$operator} = [grep {$left  != $_ -> [0] ||
                                       $right != $_ -> [1]}
                                               @{$remember {$operator}}] if
                                               @{$remember {$operator}};
        print "Right!\n";
        $correct ++;
    }
    else {
        # Remember wrong answers.
        push @{$remember {$operator}} => [$left, $right, $answer];
        print "What?\n"
    }

    $questions ++;
}

report();

sub report {
    my $seconds = time - $start;
    my $hours   = $seconds / 3600; $seconds %= 3600;
    my $minutes = $seconds /   60; $seconds %=   60;
    my $time    = (join ", " =>
                     map {/^1 / ? $_ : $_ . "s"}
                       grep {!/^0/}
                         "$hours hour", "$minutes minute", "$seconds second") ||
                       "no time at all!";

    print "\nYou had $correct answers correct, out of $questions. ",
            "It took you $time.\n";

    exit EX_SUCCESS;
}

sub usage {
    warn "$Program (Perl games) $VERSION\n";
    warn "usage: $Program [-o +-x/] [-r range]\n";
    exit EX_FAILURE;
}

__END__

=pod

=head1 NAME

arithmetic - improve your arithmetic skills

=head1 SYNOPSIS

B<arithmetic> [B<-o> B<+-x/>] [B<-r> I<range>]

=head1 DESCRIPTION

B<arithmetic> prompts you with simple arithmetic exercises, and
verifies your answers. B<arithmetic> will reply with B<Right!>
if you answered correctly, and with B<What?> if you answered incorrectly.
If B<arithmetic> thinks your answer is not a number, it will
respond with B<Please type a number!> and repeat the exercise.

After 20 questions, B<arithmetic> will tell you how many exercises you
answered correctly, and how much time it took.
You can quit at any time by typing the interrupt or end-of-file character.

If you answer an exercise incorrectly, B<arithmetic> will remember the
numbers involved, and favour those over others.

=head2 OPTIONS

B<arithmetic> accepts the following options:

=over 4

=item B<-o> B<+-x/>

By default, B<arithmetic> only asks addition exercises. By giving the
B<-o> option, B<arithmetic> will randomly choose from the given I<operators>.
If an operator is given multiple times, that operator will be picked
more often. The more it is repeated, the more it will be picked.

=item B<-r> I<range>

For I<addition> and I<multiplication>, B<arithmetic> will pick both
I<operands> in the range from 0 to I<range>, inclusively. For
I<subtraction> and I<division>, both the answer, and the right I<operand>
will be in this range. The default range is B<10>.

=back

=head1 ENVIRONMENT

The working of B<arithmetic> is not influenced by any environment variables.

=head1 BUGS

None known.

=head1 REVISION HISTORY

    $Log: arithmetic,v $
    Revision 1.2  2004/08/05 14:17:43  cwest
    cleanup, new version number on website

    Revision 1.1  2004/07/23 20:09:59  cwest
    initial import

    Revision 1.1  1999/03/14 07:44:35  abigail
    Initial revision

=head1 AUTHOR

The Perl implementation of B<arithmetic> was written by Abigail,
I<perlpowertools@abigail.be>.

=head1 COPYRIGHT and LICENSE

This program is copyright by Abigail 1999.

This program is free and open software. You may use, copy, modify, distribute,
and sell this program (and any modified variants) in any way you wish,
provided you do not restrict others from doing the same.

=cut
