My son (11) has just learned SI units and their prefixes at school. They started just with metres and litres, only some of the prefixes (mili, centi, deci, hecto, kilo) and they don't know the floating point yet. Because of his broken arm, he missed several days at school and needed to practice. So, I've written a Tk application for him to practice. It takes one parameter, the number of formulas to generate.
Feel free to localize it using the constants at the beginning. Adding the floating point left as an exercise for the reader.
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
use List::Util qw{ shuffle };
use Function::Parameters;
use Tk;
use constant {
SEPARATOR => ',',
OK => 'OK',
TITLE => 'SI Units Conversions',
DONE => 'Done',
QUIT => 'Quit',
ERRORS => 'Errors',
};
push @ARGV, -font => 'sans 15';
fun prefixes (@prefixes) {
+{ map +($_ => undef), "", @prefixes }
}
fun do_format ($x) {
my $f = reverse $x;
$f =~ s/(...)(?=.)/$1${\SEPARATOR}/g;
return reverse $f
}
fun validate ($new_value, @) {
$new_value =~ /^[0-9${\SEPARATOR}]*$/
}
my %known = (l => prefixes(qw( m c d h )),
m => prefixes(qw( m c d k )));
my %times = (m => 0.001,
c => 0.01,
d => 0.1,
"" => 1,
h => 100,
k => 1000);
my @exercises;
for (1 .. shift) {
my $unit = (keys %known)[rand keys %known];
my @prefixes = sort { $times{$a} <=> $times{$b} }
(shuffle(keys %{ $known{$unit} }))[0, 1];
push @exercises, [ $unit, @prefixes, 1 + int rand 100 ];
$exercises[-1][-1] *= 10 for 1 .. rand 2;
if (int rand 2) {
my $times = $times{ $prefixes[1] } / $times{ $prefixes[0] };
$exercises[-1]
= [ $unit, @prefixes[1, 0], $exercises[-1][-1] * $times ];
}
}
my (@solutions, @entries);
my $errors = 0;
my $mw = 'MainWindow'->new(title => TITLE);
for my $i (0 .. $#exercises) {
my $exercise = $exercises[$i];
my $f = $mw->Frame->pack;
my $ratio = $times{ $exercise->[2] } / $times{ $exercise->[1] };
$f->Label(-text => do_format($exercise->[3] * $ratio)
. " $exercise->[1]$exercise->[0] = ")
->pack(-side => 'left');
push @entries, $f->Entry(-textvariable => \$solutions[$i],
-validate => 'all',
-validatecommand => \&validate,
)->pack(-side => 'left');
$f->Label(-text => " $exercise->[2]$exercise->[0]")
->pack(-side => 'left');
}
my $f = $mw->Frame->pack;
$f->Button(-text => DONE,
-command => fun () {
for my $i (0 .. $#solutions) {
next if $entries[$i]->cget('-state') eq 'readonly';
my $value = ($solutions[$i] // "") =~ s/${\SEPARATO
+R}//gr;
if (($value || 0) == $exercises[$i][3]) {
$entries[$i]->configure(-state => 'readonly'
+,
-validate => 'none');
$solutions[$i] = OK;
} else {
++$errors;
}
}
})->pack(-side => 'left');
$f->Button(-text => QUIT,
-command => [$mw => 'destroy'])->pack(-side => 'left');
MainLoop();
say ERRORS, ": $errors";
map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]