#!/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, 12.2 ], [ q, 28.3 ], [ q, 32.16 ], [ q, 5.15 ], [ q, 12.5 ], [ q, 85.2 ], [ q, 37.6 ], [ q, 33.8 ], [ q, 39.6 ], [ q, 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; } 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, q, q, q, q<#$%&&%!$>, q, q, q, q, q, ]; 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, 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 =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, and/or the Gnu GPL at L. 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 :