Named captures, ternary operator, declaring variables on the same line, not initializing the $total (there is no need). Reduces the function length by 25% and is still legible IMO (and no line is > than 80 chars for the old school-ists):
sub roll {
my $input = shift;
return if $input !~ /(?<num>\d*)d(?<die>\d+)\s*(?<op>\D?)\s*(?<end>\d*)/;
my (@dice,$total);
push @dice, int(rand($+{die}))+1 for ( 1..$+{num} );
if ( $+{op} eq 'b' ) {
$+{end} = $+{num} if $+{end} > $+{num};
@dice = sort { $b <=> $a } @dice;
$#dice = $+{end}-1;
}
$total += $_ for @dice;
return $+{op} eq '+' ? $total + $+{end} : $+{op} eq '-' ? $total - $+{end}
: $+{op} eq '*' ? $total * $+{end} : $+{op} eq '/' ? $total / $+{end}
: $total;
}
But i wouldn't write it like that, i would write it like this as i like to be generous with both vertical and horizontal space. I also like to be very lisp like with parentheses to prevent ambiguity and obscure precedence bugs:
sub roll {
my ( $input ) = @_;
return if $input !~ /(?<num>\d*)d(?<die>\d+)\s*(?<op>\D?)\s*(?<end>\d*)/;
my ( @dice,$total );
push( @dice, int( rand( $+{die} ) ) + 1 ) for ( 1 .. $+{num} );
if ( $+{op} eq 'b' ) {
$+{end} = $+{num} if $+{end} > $+{num};
@dice = sort { $b <=> $a } @dice;
$#dice = $+{end} - 1;
}
$total += $_ for @dice;
return $+{op} eq '+' ? $total + $+{end}
: $+{op} eq '-' ? $total - $+{end}
: $+{op} eq '*' ? $total * $+{end}
: $+{op} eq '/' ? $total / $+{end}
: $total;
}
FWIW here's the (pretty poor) tests i wrote before refactoring this function:
use strict;
use warnings;
use Test::Most;
my $result = roll( "10d10+10" );
like( $result,qr/^\d+$/,'roll' );
ok( $result > 0,' .. > 0' );
ok( $result < 100,' .. < 100' );
ok( defined roll( "10d10-10" ),'-10' );
ok( defined roll( "10d10*10" ),'*10' );
ok( defined roll( "10d10/10" ),'/10' );
ok( defined roll( "10d10b2" ),'b2' );
note $result;
note roll( "10d10-10" );
note roll( "10d10*10" );
note roll( "10d10/10" );
note roll( "10d10b2" );
If you're going to refactor a function to clean it up then always write tests, because it's so easy to introduce bugs. I may well have done in my attempt above, since the tests i wrote are no where near comprehensive.