mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-12 15:50:20 -08:00
184
13_Bounce/ruby/bounce.rb
Normal file
184
13_Bounce/ruby/bounce.rb
Normal file
@@ -0,0 +1,184 @@
|
||||
## Global constants
|
||||
|
||||
# Gravity accelaration (F/S^2) ~= 32
|
||||
G = 32
|
||||
|
||||
# Used to indent the plotting of ball positions
|
||||
# so that the height digits don't affect
|
||||
# where we start plotting ball positions
|
||||
BALL_PLOT_INDENT = "\t"
|
||||
|
||||
# The deviation between current plotted height and the actual
|
||||
# height of the ball that we will accept to plot the ball in
|
||||
# that plotted height
|
||||
BALL_PLOT_DEVIATION = 0.25
|
||||
|
||||
# The step we will take as we move down vertically while
|
||||
# plotting ball positions
|
||||
BALL_PLOT_HEIGHT_STEP = 0.5
|
||||
|
||||
|
||||
## Helper functions
|
||||
|
||||
# Calculates the bounce speed (up) of the ball for a given
|
||||
# bounce number and coefficient
|
||||
def calc_velocity_for_bounce(v0, bounce, coefficient)
|
||||
v = v0 * coefficient**bounce
|
||||
end
|
||||
|
||||
# Check https://physics.stackexchange.com/a/333436 for nice explanation
|
||||
def calc_bounce_total_time(v0, bounce, coefficient)
|
||||
v = calc_velocity_for_bounce(v0, bounce, coefficient)
|
||||
t = 2 * v / G
|
||||
end
|
||||
|
||||
# Check https://physics.stackexchange.com/a/333436 for nice explanation
|
||||
def calc_ball_height(v0, bounce, coefficient, t)
|
||||
v = calc_velocity_for_bounce(v0, bounce, coefficient)
|
||||
h = v * t - 0.5 * G * t**2
|
||||
end
|
||||
|
||||
def heighest_position_in_next_bounce(time_in_bounce, v0, i, c)
|
||||
time_in_next_bounce = time_in_bounce[i+1]
|
||||
return -1 if time_in_next_bounce.nil?
|
||||
return calc_ball_height(v0, i, c, time_in_next_bounce / 2) unless time_in_next_bounce.nil?
|
||||
end
|
||||
|
||||
def intro
|
||||
puts <<~INSTRUCTIONS
|
||||
BOUNCE
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
|
||||
THIS SIMULATION LETS YOU SPECIFY THE INITIAL VELOCITY
|
||||
OF A BALL THROWN STRAIGHT UP, AND THE COEFFICIENT OF
|
||||
ELASTICITY OF THE BALL. PLEASE USE A DECIMAL FRACTION
|
||||
COEFFICIENCY (LESS THAN 1).
|
||||
|
||||
YOU ALSO SPECIFY THE TIME INCREMENT TO BE USED IN
|
||||
'STROBING' THE BALL'S FLIGHT (TRY .1 INITIALLY).
|
||||
INSTRUCTIONS
|
||||
end
|
||||
|
||||
|
||||
## Plottin functions
|
||||
|
||||
def plot_header
|
||||
puts
|
||||
puts "FEET"
|
||||
end
|
||||
|
||||
def plot_bouncing_ball(strobbing_time, v0, c)
|
||||
## Initializing helper values
|
||||
|
||||
# How many bounces we want to plot
|
||||
# original BASIC version is 70 / (V / (16 * S2))
|
||||
# 70 is assumed to be an arbitrary number higher than 2G and 16 is 1/2G
|
||||
bounces_to_plot = (G**2 / (v0 / strobbing_time)).to_i
|
||||
|
||||
# Holds the total time the ball spends in the air in every bounce
|
||||
time_in_bounce = bounces_to_plot.times.map { |i| calc_bounce_total_time v0, i, c }
|
||||
|
||||
plot_width = 0
|
||||
|
||||
# Calculate the highest position for the ball after the very first bounce
|
||||
plotted_height = (calc_ball_height(v0, 0, c, v0/G) + 0.5).to_i
|
||||
|
||||
## Plotting bouncing ball
|
||||
while plotted_height >= 0 do
|
||||
# We will print only whole-number heights
|
||||
print plotted_height.to_i if plotted_height.to_i === plotted_height
|
||||
|
||||
print BALL_PLOT_INDENT
|
||||
|
||||
bounces_to_plot.times { |i|
|
||||
(0..time_in_bounce[i]).step(strobbing_time) { |t|
|
||||
ball_pos = calc_ball_height v0, i, c, t
|
||||
|
||||
# If the ball is within the acceptable deviation
|
||||
# from the current height, we will plot it
|
||||
if (plotted_height - ball_pos).abs <= BALL_PLOT_DEVIATION then
|
||||
print "0"
|
||||
else
|
||||
print " "
|
||||
end
|
||||
|
||||
# Increment the plot width when we are plotting height = 0
|
||||
# which will definitely be the longest since it never gets
|
||||
# skipped by line 98
|
||||
plot_width += 1 if plotted_height == 0
|
||||
}
|
||||
|
||||
if heighest_position_in_next_bounce(time_in_bounce, v0, i, c) < plotted_height then
|
||||
# If we got no more ball positions at or above current height in the next bounce,
|
||||
# we can skip the rest of the bounces and move down to the next height to plot
|
||||
puts
|
||||
break
|
||||
end
|
||||
}
|
||||
|
||||
plotted_height -= BALL_PLOT_HEIGHT_STEP
|
||||
end
|
||||
|
||||
# Return plot_width to be used by the plot_footer
|
||||
plot_width
|
||||
end
|
||||
|
||||
def plot_footer (plot_width, strobbing_time)
|
||||
# Dotted separator line
|
||||
puts
|
||||
print BALL_PLOT_INDENT
|
||||
(plot_width).times { |_| print "." }
|
||||
puts
|
||||
|
||||
# Time values line
|
||||
print BALL_PLOT_INDENT
|
||||
points_in_sec = (1 / strobbing_time).to_i
|
||||
plot_width.times { |i|
|
||||
if i % points_in_sec == 0 then
|
||||
print (i / points_in_sec).to_i
|
||||
else
|
||||
print " "
|
||||
end
|
||||
}
|
||||
puts
|
||||
|
||||
# Time unit line
|
||||
print BALL_PLOT_INDENT
|
||||
(plot_width / 2 - 4).to_i.times { |_| print " " }
|
||||
puts "SECONDS"
|
||||
puts
|
||||
end
|
||||
|
||||
def game_loop
|
||||
# Read strobing, velocity and coefficient parameters from user input
|
||||
puts "TIME INCREMENT (SEC)"
|
||||
strobbing_time = gets.to_f
|
||||
|
||||
puts "VELOCITY (FPS)"
|
||||
v0 = gets.to_f
|
||||
|
||||
puts "COEFFICIENT"
|
||||
c = gets.to_f
|
||||
|
||||
# Plotting
|
||||
plot_header
|
||||
|
||||
plot_width = plot_bouncing_ball strobbing_time, v0, c
|
||||
|
||||
plot_footer plot_width, strobbing_time
|
||||
end
|
||||
|
||||
|
||||
## Entry point
|
||||
|
||||
begin
|
||||
intro
|
||||
loop do
|
||||
game_loop
|
||||
end
|
||||
rescue SystemExit, Interrupt
|
||||
exit
|
||||
rescue => exception
|
||||
p exception
|
||||
end
|
||||
Reference in New Issue
Block a user