Files
basic-computer-games/81_Splat/perl/splat.pl
Martin Thoma e64fb6795c MAINT: Apply pre-commit
Remove byte-order-marker pre-commit check as there would be
many adjustments necessary
2022-03-05 09:29:23 +01:00

337 lines
11 KiB
Perl
Executable File

#!/usr/bin/env perl
use 5.010; # To get 'state' and 'say'
use strict; # Require explicit declaration of variables
use warnings; # Enable optional compiler warnings
use English; # Use more friendly names for Perl's magic variables
use List::Util qw{ shuffle }; # Shuffle an array.
use Scalar::Util qw{ looks_like_number };
use Term::ReadLine; # Prompt and return user input
our $VERSION = '0.000_01';
use constant ROW_TPLT => ( '%4d' x 8 ) . "\n";
print <<'EOD';
SPLAT
Creative Computing Morristown, New Jersey
Welcome to 'Splat' -- the game that simulates a parachute
jump. Try to open your chute at the last possible
moment without going splat.
EOD
while ( 1 ) {
say '';
my $initial_altitude = int( 9001 * rand() + 1000 );
my $nominal_terminal_velocity;
if ( get_yes_no( 'Select your own terminal velocity' ) ) {
$nominal_terminal_velocity = get_input(
'What terminal velocity (mi/hr)? ',
sub { looks_like_number( $ARG ) && $ARG > 0 },
'Please enter a positive number',
);
# Convert miles per hour to feet per second
$nominal_terminal_velocity = $nominal_terminal_velocity * 5280 / 3600;
} else {
$nominal_terminal_velocity = int( 1000 * rand() );
say "OK. Terminal velocity = $nominal_terminal_velocity mi/hr"
}
my $terminal_velocity = dither( $nominal_terminal_velocity );
my $nominal_gravity; # Acceleration due to gravity
if ( get_yes_no( 'Want to select acceleration due to gravity' ) ) {
} else {
state $body = [
[ q<Fine. You're on Mercury. Acceleration = 12.2 ft/sec/sec.>,
12.2 ],
[ q<All right. You're on Venus. Acceleration = 28.3 ft/sec/sec.>,
28.3 ],
[ q<Then you're on Earth. Acceleration = 32.16 ft/sec/sec.>,
32.16 ],
[ q<Fine. You're on the Moon. Acceleration = 5.15 ft/sec/sec.>,
5.15 ],
[ q<All right. You're on Mars. Acceleration = 12.5 ft/sec/sec.>,
12.5 ],
[ q<Then you're on Jupiter. Acceleration = 85.2 ft/sec/sec.>,
85.2 ],
[ q<Fine. You're on Saturn. Acceleration = 37.6 ft/sec/sec.>,
37.6 ],
[ q<All right. You're on Uranus. Acceleration = 33.8 ft/sec/sec.>,
33.8 ],
[ q<Then you're on Neptune. Acceleration = 39.6 ft/sec/sec.>,
39.6 ],
[ q<Fine. You're on the Sun. Acceleration = 896 ft/sec/sec.>,
896 ],
];
my $pick = $body->[ rand scalar @{ $body } ];
say $pick->[0];
$nominal_gravity = $pick->[1];
}
my $gravity = dither( $nominal_gravity );
print <<"EOD";
Altitude = $initial_altitude ft
Term. velocity = $nominal_terminal_velocity ft/sec +/- 5%
Acceleration = $nominal_gravity ft/sec/sec +/- 5%
Set the timer for your freefall
EOD
my $drop_time = get_input(
'How many seconds? ',
sub { m/ \A [0-9]+ \z /smx },
"Please enter an unsigned integer\n",
);
print <<'EOD';
Here we go.
Time (sec) Dist to fall (ft)
========== =================
EOD
if ( defined( my $altitude = make_jump(
$initial_altitude,
$gravity,
$terminal_velocity,
$drop_time ) )
) {
# Successful jump
state $succesful = [];
state $ordinal = [ qw{ 1st 2nd 3rd } ];
if ( defined( my $ord = $ordinal->[ @{ $succesful } ] ) ) {
say "Amazing!!! Not nad for your $ord successful jump!!!";
} else {
my $jumps = @{ $succesful };
my $worse = grep { $_ > $altitude } @{ $succesful };
my $fractile = 1 - $worse / $jumps;
my $better = $jumps - $worse;
if ( $fractile <= 0.1 ) {
print <<"EOD";
Wow! That's some jumping. Of the $jumps successful jumps
before yours, only $better opened their chutes lower than
you did.
EOD
} elsif ( $fractile <= 0.25 ) {
print <<"EOD";
Pretty good! $jumps successful jumps preceded yours and only
$better of them got lower than you did before their chutes
opened.
EOD
} elsif ( $fractile <= 0.5 ) {
print <<"EOD";
Not bad. There have been $jumps successful jumps before yours.
You were beaten out by $better of them.
EOD
} elsif ( $fractile <= 0.75 ) {
print <<"EOD";
Conservative, aren't you? You ranked only $better in the
$jumps successful jumps before yours.
EOD
} elsif ( $fractile <= 0.9 ) {
print <<"EOD";
Humph! Don't you have any sporting blood? There were
$jumps successful jumps before yours and you came in $worse jumps
better than the worst. Shape up!!!
EOD
} else {
print <<"EOD";
Hey! You pulled the rip cord much too soon. $jumps successful
jumps before yours and you came in number $better! Get with it!
EOD
}
}
push @{ $succesful }, $altitude;
} else {
# Splat
say q<I'll give you another chance.>;
}
next if get_yes_no( 'Do you want to play again' );
next if get_yes_no( 'Please' );
print <<'EOD';
Ssssssssss.
EOD
last;
}
# Return the first argument modified by up to plus or minus some
# fraction specified by the second argument (default 0.05)
sub dither {
my ( $arg, $fract ) = @_;
$fract //= 1 / 20;
return $arg + ( $arg * rand() * $fract ) - ( $arg * rand() * $fract );
}
use constant FORMAT_FALL => "%10.1f %10d\n";
use constant FORMAT_SPLAT => "%10.1f %s\n";
sub make_jump {
my ( $initial_altitude, $gravity, $terminal_velocity, $drop_time ) = @_;
my $altitude;
foreach my $step ( 0 .. 8 ) {
my $time = $step * $drop_time / 8;
if ( $time > $terminal_velocity / $gravity ) {
# Terminal velocity reached
printf "Terminal velocity reached at T plus %.2f seconds.\n",
$terminal_velocity / $gravity;
for my $step ( $step .. 8 ) {
my $time = $step * $drop_time / 8;
$altitude = $initial_altitude - (
$terminal_velocity * $terminal_velocity /
( 2 * $gravity ) + $terminal_velocity * (
$time - $terminal_velocity / $gravity ) );
if ( $altitude > 0 ) {
printf FORMAT_FALL, $time, $altitude;
} else {
splat(
$terminal_velocity / $gravity + (
$initial_altitude -
$terminal_velocity * $terminal_velocity /
( 2 * $gravity ) ) / $terminal_velocity,
);
return;
}
}
last;
} else {
$altitude = $initial_altitude - $gravity / 2 * $time * $time;
if ( $altitude > 0 ) {
printf FORMAT_FALL, $time, $altitude;
} else {
splat( sqrt( 2 * $initial_altitude / $gravity ) );
return;
}
}
}
say 'Chute open.';
return $altitude;
}
sub splat {
my ( $time ) = @_;
printf FORMAT_SPLAT, $time, 'Splat!';
state $rip = [
q<Requiescat in pace.>,
q<May the angel of heaven lead you into paradise.>,
q<Rest in peace.>,
q<Son-of-a-gun.>,
q<#$%&&%!$>,
q<A kick in the pants is a boost if you're headed right.>,
q<Hmmm. Should have picked a shorter time.>,
q<Mutter. Mutter. Mutter.>,
q<Pushing up daisies.>,
q<Easy come, easy go.>,
];
say $rip->[ rand scalar @{ $rip } ];
return;
}
# Get input from the user. The arguments are:
# * The prompt
# * A reference to validation code. This code receives the response in
# $ARG and returns true for a valid response.
# * A warning to print if the response is not valid. This must end in a
# return.
# The first valid response is returned. An end-of-file terminates the
# script.
sub get_input {
my ( $prompt, $validate, $warning ) = @ARG;
# If no validator is passed, default to one that always returns
# true.
$validate ||= sub { 1 };
# Create the readline object. The 'state' causes the variable to be
# initialized only once, no matter how many times this subroutine is
# called. The do { ... } is a compound statement used because we
# need to tweak the created object before we store it.
state $term = do {
my $obj = Term::ReadLine->new( 'reverse' );
$obj->ornaments( 0 );
$obj;
};
while ( 1 ) { # Iterate indefinitely
# Read the input into the topic variable, localized to prevent
# Spooky Action at a Distance. We exit on undef, which signals
# end-of-file.
exit unless defined( local $ARG = $term->readline( $prompt ) );
# Return the input if it is valid.
return $ARG if $validate->();
# Issue the warning, and go around the merry-go-round again.
warn $warning;
}
}
# Get a yes-or-no answer. The argument is the prompt, which will have
# '? [y/n]: ' appended. The donkey work is done by get_input(), which is
# requested to validate the response as beginning with 'y' or 'n',
# case-insensitive. The return is a true value for 'y' and a false value
# for 'n'.
sub get_yes_no {
my ( $prompt ) = @ARG;
state $map_answer = {
n => 0,
y => 1,
};
my $resp = lc get_input(
"$prompt? [y/n]: ",
sub { m/ \A [yn] /smxi },
"Please respond 'y' or 'n'\n",
);
return $map_answer->{ substr $resp, 0, 1 };
}
__END__
=head1 TITLE
splat.pl - Play the game 'splat' from Basic Computer Games
=head1 SYNOPSIS
splat.pl
=head1 DETAILS
This Perl script is a port of C<splat>, which is the 73rd entry in
Basic Computer Games.
This is a very basic port. All I really did was untangle the spaghetti.
=head1 PORTED BY
Thomas R. Wyant, III F<wyant at cpan dot org>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2022 by Thomas R. Wyant, III
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl 5.10.0. For more details, see the Artistic
License 1.0 at
L<https://www.perlfoundation.org/artistic-license-10.html>, and/or the
Gnu GPL at L<http://www.gnu.org/licenses/old-licenses/gpl-1.0.txt>.
This program is distributed in the hope that it will be useful, but
without any warranty; without even the implied warranty of
merchantability or fitness for a particular purpose.
=cut
# ex: set expandtab tabstop=4 textwidth=72 :