mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 07:10:42 -08:00
321 lines
11 KiB
Python
321 lines
11 KiB
Python
"""
|
|
SPLAT
|
|
|
|
Splat similates a parachute jump in which you try to open your parachute
|
|
at the last possible moment without going splat! You may select your own
|
|
terminal velocity or let the computer do it for you. You may also select
|
|
the acceleration due to gravity or, again, let the computer do it
|
|
in which case you might wind up on any one of the eight planets (out to
|
|
Neptune), the moon, or the sun.
|
|
|
|
The computer then tells you the height you're jumping from and asks for
|
|
the seconds of free fall. It then divides your free fall time into eight
|
|
intervals and gives you progress reports on the way down. The computer
|
|
also keeps track of all prior jumps and lets you know how you compared
|
|
with previous successful jumps. If you want to recall information from
|
|
previous runs, then you should store the array `successful_jumps` on
|
|
disk and read it before each run.
|
|
|
|
John Yegge created this program while at the Oak Ridge Associated
|
|
Universities.
|
|
|
|
Ported in 2021 by Jonas Nockert / @lemonad
|
|
|
|
"""
|
|
from math import sqrt
|
|
from random import choice, random, uniform
|
|
from typing import List, Tuple
|
|
|
|
PAGE_WIDTH = 72
|
|
|
|
|
|
def numeric_input(question, default=0) -> float:
|
|
"""Ask user for a numeric value."""
|
|
while True:
|
|
answer_str = input(f"{question} [{default}]: ").strip() or default
|
|
try:
|
|
return float(answer_str)
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
def yes_no_input(question: str, default="YES") -> bool:
|
|
"""Ask user a yes/no question and returns True if yes, otherwise False."""
|
|
answer = input(f"{question} (YES OR NO) [{default}]: ").strip() or default
|
|
while answer.lower() not in ["n", "no", "y", "yes"]:
|
|
answer = input(f"YES OR NO [{default}]: ").strip() or default
|
|
return answer.lower() in ["y", "yes"]
|
|
|
|
|
|
def get_terminal_velocity() -> float:
|
|
"""Terminal velocity by user or picked by computer."""
|
|
if yes_no_input("SELECT YOUR OWN TERMINAL VELOCITY", default="NO"):
|
|
v1 = numeric_input("WHAT TERMINAL VELOCITY (MI/HR)", default=100)
|
|
else:
|
|
# Computer picks 0-1000 terminal velocity.
|
|
v1 = int(1000 * random())
|
|
print(f"OK. TERMINAL VELOCITY = {v1} MI/HR")
|
|
|
|
# Convert miles/h to feet/s.
|
|
return v1 * (5280 / 3600)
|
|
|
|
|
|
def get_acceleration() -> float:
|
|
"""Acceleration due to gravity by user or picked by computer."""
|
|
if yes_no_input("WANT TO SELECT ACCELERATION DUE TO GRAVITY", default="NO"):
|
|
a2 = numeric_input("WHAT ACCELERATION (FT/SEC/SEC)", default=32.16)
|
|
else:
|
|
body, a2 = pick_random_celestial_body()
|
|
print(f"FINE. YOU'RE ON {body}. ACCELERATION={a2} FT/SEC/SEC.")
|
|
return a2
|
|
|
|
|
|
def get_freefall_time() -> float:
|
|
"""User-guessed freefall time.
|
|
|
|
The idea of the game is to pick a freefall time, given initial
|
|
altitude, terminal velocity and acceleration, so the parachute
|
|
as close to the ground as possible without going splat.
|
|
"""
|
|
t_freefall: float = 0
|
|
# A zero or negative freefall time is not handled by the motion
|
|
# equations during the jump.
|
|
while t_freefall <= 0:
|
|
t_freefall = numeric_input("HOW MANY SECONDS", default=10)
|
|
return t_freefall
|
|
|
|
|
|
def jump() -> float:
|
|
"""Simulate a jump and returns the altitude where the chute opened.
|
|
|
|
The idea is to open the chute as late as possible -- but not too late.
|
|
"""
|
|
v: float = 0 # Terminal velocity.
|
|
a: float = 0 # Acceleration.
|
|
initial_altitude = int(9001 * random() + 1000)
|
|
|
|
v1 = get_terminal_velocity()
|
|
# Actual terminal velocity is +/-5% of v1.
|
|
v = v1 * uniform(0.95, 1.05)
|
|
|
|
a2 = get_acceleration()
|
|
# Actual acceleration is +/-5% of a2.
|
|
a = a2 * uniform(0.95, 1.05)
|
|
|
|
print(
|
|
"\n"
|
|
f" ALTITUDE = {initial_altitude} FT\n"
|
|
f" TERM. VELOCITY = {v1:.2f} FT/SEC +/-5%\n"
|
|
f" ACCELERATION = {a2:.2f} FT/SEC/SEC +/-5%\n"
|
|
"SET THE TIMER FOR YOUR FREEFALL."
|
|
)
|
|
t_freefall = get_freefall_time()
|
|
print(
|
|
"HERE WE GO.\n\n"
|
|
"TIME (SEC)\tDIST TO FALL (FT)\n"
|
|
"==========\t================="
|
|
)
|
|
|
|
terminal_velocity_reached = False
|
|
is_splat = False
|
|
for i in range(9):
|
|
# Divide time for freefall into 8 intervals.
|
|
t = i * (t_freefall / 8)
|
|
# From the first equation of motion, v = v_0 + a * delta_t, with
|
|
# initial velocity v_0 = 0, we can get the time when terminal velocity
|
|
# is reached: delta_t = v / a.
|
|
if t > v / a:
|
|
if not terminal_velocity_reached:
|
|
print(f"TERMINAL VELOCITY REACHED AT T PLUS {v / a:.2f} SECONDS.")
|
|
terminal_velocity_reached = True
|
|
# After having reached terminal velocity, the displacement is
|
|
# composed of two parts:
|
|
# 1. Displacement up to reaching terminal velocity:
|
|
# From the third equation of motion, v^2 = v_0^2 + 2 * a * d,
|
|
# with v_0 = 0, we can get the displacement using
|
|
# d1 = v^2 / (2 * a).
|
|
# 2. Displacement beyond having reached terminal velocity:
|
|
# here, the displacement is just a function of the terminal
|
|
# velocity and the time passed after having reached terminal
|
|
# velocity: d2 = v * (t - t_reached_term_vel)
|
|
d1 = (v**2) / (2 * a)
|
|
d2 = v * (t - (v / a))
|
|
altitude = initial_altitude - (d1 + d2)
|
|
if altitude <= 0:
|
|
# Time taken for an object to fall to the ground given
|
|
# an initial altitude is composed of two parts after having
|
|
# reached terminal velocity:
|
|
# 1. time up to reaching terminal velocity: t1 = v / a
|
|
# 2. time beyond having reached terminal velocity:
|
|
# here, the altitude that remains after having reached
|
|
# terminal velocity can just be divided by the constant
|
|
# terminal velocity to get the time it takes to reach the
|
|
# ground: t2 = altitude_remaining / v
|
|
t1 = v / a
|
|
t2 = (initial_altitude - d1) / v
|
|
print_splat(t1 + t2)
|
|
is_splat = True
|
|
break
|
|
else:
|
|
# 1. Displacement before reaching terminal velocity:
|
|
# From the second equation of motion,
|
|
# d = v_0 * t + 0.5 * a * t^2, with v_0 = 0, we can get
|
|
# the displacement using d1 = a / 2 * t^2
|
|
d1 = (a / 2) * (t**2)
|
|
altitude = initial_altitude - d1
|
|
if altitude <= 0:
|
|
# Time taken for an object to fall to the ground given that
|
|
# it never reaches terminal velocity can be calculated by
|
|
# using the second equation of motion:
|
|
# d = v_0 * t + 0.5 * a * t^2, with v_0 = 0, which
|
|
# when solved for t becomes
|
|
# t1 = sqrt(2 * d / a).
|
|
t1 = sqrt(2 * initial_altitude / a)
|
|
print_splat(t1)
|
|
is_splat = True
|
|
break
|
|
print(f"{t:.2f}\t\t{altitude:.1f}")
|
|
|
|
if not is_splat:
|
|
print("CHUTE OPEN")
|
|
return altitude
|
|
|
|
|
|
def pick_random_celestial_body() -> Tuple[str, float]:
|
|
"""Pick a random planet, the moon, or the sun with associated gravity."""
|
|
return choice(
|
|
[
|
|
("MERCURY", 12.2),
|
|
("VENUS", 28.3),
|
|
("EARTH", 32.16),
|
|
("THE MOON", 5.15),
|
|
("MARS", 12.5),
|
|
("JUPITER", 85.2),
|
|
("SATURN", 37.6),
|
|
("URANUS", 33.8),
|
|
("NEPTUNE", 39.6),
|
|
("THE SUN", 896.0),
|
|
]
|
|
)
|
|
|
|
|
|
def jump_stats(previous_jumps, chute_altitude) -> Tuple[int, int]:
|
|
"""Compare altitude when chute opened with previous successful jumps.
|
|
|
|
Return the number of previous jumps and the number of times
|
|
the current jump is better.
|
|
"""
|
|
n_previous_jumps = len(previous_jumps)
|
|
n_better = sum(1 for pj in previous_jumps if chute_altitude < pj)
|
|
return n_previous_jumps, n_better
|
|
|
|
|
|
def print_splat(time_on_impact) -> None:
|
|
"""Parachute opened too late!"""
|
|
print(f"{time_on_impact:.2f}\t\tSPLAT")
|
|
print(
|
|
choice(
|
|
[
|
|
"REQUIESCAT IN PACE.",
|
|
"MAY THE ANGEL OF HEAVEN LEAD YOU INTO PARADISE.",
|
|
"REST IN PEACE.",
|
|
"SON-OF-A-GUN.",
|
|
"#$%&&%!$",
|
|
"A KICK IN THE PANTS IS A BOOST IF YOU'RE HEADED RIGHT.",
|
|
"HMMM. SHOULD HAVE PICKED A SHORTER TIME.",
|
|
"MUTTER. MUTTER. MUTTER.",
|
|
"PUSHING UP DAISIES.",
|
|
"EASY COME, EASY GO.",
|
|
]
|
|
)
|
|
)
|
|
|
|
|
|
def print_results(n_previous_jumps, n_better) -> None:
|
|
"""Compare current jump to previous successful jumps."""
|
|
k = n_previous_jumps
|
|
k1 = n_better
|
|
n_jumps = k + 1
|
|
if n_jumps <= 3:
|
|
order = ["1ST", "2ND", "3RD"]
|
|
nth = order[n_jumps - 1]
|
|
print(f"AMAZING!!! NOT BAD FOR YOUR {nth} SUCCESSFUL JUMP!!!")
|
|
elif k - k1 <= 0.1 * k:
|
|
print(
|
|
f"WOW! THAT'S SOME JUMPING. OF THE {k} SUCCESSFUL JUMPS\n"
|
|
f"BEFORE YOURS, ONLY {k - k1} OPENED THEIR CHUTES LOWER THAN\n"
|
|
"YOU DID."
|
|
)
|
|
elif k - k1 <= 0.25 * k:
|
|
print(
|
|
f"PRETTY GOOD! {k} SUCCESSFUL JUMPS PRECEDED YOURS AND ONLY\n"
|
|
f"{k - k1} OF THEM GOT LOWER THAN YOU DID BEFORE THEIR CHUTES\n"
|
|
"OPENED."
|
|
)
|
|
elif k - k1 <= 0.5 * k:
|
|
print(
|
|
f"NOT BAD. THERE HAVE BEEN {k} SUCCESSFUL JUMPS BEFORE YOURS.\n"
|
|
f"YOU WERE BEATEN OUT BY {k - k1} OF THEM."
|
|
)
|
|
elif k - k1 <= 0.75 * k:
|
|
print(
|
|
f"CONSERVATIVE, AREN'T YOU? YOU RANKED ONLY {k - k1} IN THE\n"
|
|
f"{k} SUCCESSFUL JUMPS BEFORE YOURS."
|
|
)
|
|
elif k - k1 <= 0.9 * k:
|
|
print(
|
|
"HUMPH! DON'T YOU HAVE ANY SPORTING BLOOD? THERE WERE\n"
|
|
f"{k} SUCCESSFUL JUMPS BEFORE YOURS AND YOU CAME IN {k1} JUMPS\n"
|
|
"BETTER THAN THE WORST. SHAPE UP!!!"
|
|
)
|
|
else:
|
|
print(
|
|
f"HEY! YOU PULLED THE RIP CORD MUCH TOO SOON. {k} SUCCESSFUL\n"
|
|
f"JUMPS BEFORE YOURS AND YOU CAME IN NUMBER {k - k1}!"
|
|
" GET WITH IT!"
|
|
)
|
|
|
|
|
|
def print_centered(msg: str) -> None:
|
|
"""Print centered text."""
|
|
spaces = " " * ((PAGE_WIDTH - len(msg)) // 2)
|
|
print(spaces + msg)
|
|
|
|
|
|
def print_header() -> None:
|
|
print_centered("SPLAT")
|
|
print_centered("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
|
|
print(
|
|
"\n\n\n"
|
|
"WELCOME TO 'SPLAT' -- THE GAME THAT SIMULATES A PARACHUTE\n"
|
|
"JUMP. TRY TO OPEN YOUR CHUTE AT THE LAST POSSIBLE\n"
|
|
"MOMENT WITHOUT GOING SPLAT.\n\n"
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
print_header()
|
|
|
|
successful_jumps: List[float] = []
|
|
while True:
|
|
chute_altitude = jump()
|
|
if chute_altitude > 0:
|
|
# We want the statistics on previous jumps (i.e. not including the
|
|
# current jump.)
|
|
n_previous_jumps, n_better = jump_stats(successful_jumps, chute_altitude)
|
|
successful_jumps.append(chute_altitude)
|
|
print_results(n_previous_jumps, n_better)
|
|
else:
|
|
# Splat!
|
|
print("I'LL GIVE YOU ANOTHER CHANCE.")
|
|
z = yes_no_input("DO YOU WANT TO PLAY AGAIN")
|
|
if not z:
|
|
z = yes_no_input("PLEASE")
|
|
if not z:
|
|
print("SSSSSSSSSS.")
|
|
break
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|