Last active
March 18, 2023 01:33
-
-
Save houhoulis/42434f9e59b66241f874f9e769974969 to your computer and use it in GitHub Desktop.
Snowflakes falling in terminal in Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Seen on https://connectified.com/@[email protected]/109993732600145441. | |
# Fixed data structure bug, added drift & other changes/improvements. | |
# Ruby version doing the same calculations as the elixir version in snow.exs | |
HEIGHT = `tput lines`.to_i | |
WIDTH = `tput cols`.to_i | |
SLEEP_DURATION = 0.5 | |
LIFETIME = HEIGHT + 30 | |
WIND_SPEED = [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0].sample * [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0].sample / 4.21 | |
FLAKES_PER_ROW = 5 | |
def clear_screen = puts "\e\[2J" | |
def string_to_move_cursor(x, y) = "\e\[#{y};#{x}H" | |
def print_cursor_top_left | |
print "#{string_to_move_cursor(0, 0)}#{Time.now.strftime('%Y-%m-%d %H:%M:%S.%6N')}\nwind: #{WIND_SPEED.round(4)}#{string_to_move_cursor(0, 0)}" | |
end | |
def print_position(x, y, string) | |
if x >= 0 and x < WIDTH and y >= 0 and y < HEIGHT | |
print "#{string_to_move_cursor(x, y)}#{string}" | |
print_cursor_top_left | |
end | |
end | |
def erase_position(x, y) | |
if x >= 0 and x < WIDTH and y >= 0 and y < HEIGHT | |
print_position(x, y, " ") | |
print_cursor_top_left | |
end | |
end | |
def twinkle_print_position(x, y) | |
if rand < 0.94 | |
print_position(x, y, colorless_flake) | |
else | |
print_position(x, y, colorful_flake) | |
end | |
end | |
def colorless_flake | |
# "\e[37m*\e[0m" is grey. Distinctive in some color configs and nigh-indistinguishable in others | |
["*", "*", "*", "*", "\e[37m*\e[0m"].sample | |
end | |
def colorful_flake | |
# red is too vibrant: "\e[31m*\e[0m" | |
["\e[32m*\e[0m", "\e[33m*\e[0m", "\e[34m*\e[0m", "\e[35m*\e[0m", "\e[36m*\e[0m"].sample | |
end | |
trap("SIGINT") { puts "Bye!" ; exit! } | |
def run | |
clear_screen | |
snowfall([]) | |
end | |
def snowfall(flakes) | |
loop do | |
FLAKES_PER_ROW.times { | |
# the calculated area extends off-screen to the left and right | |
flakes << {x: rand(3 * WIDTH) - WIDTH - 1, y: 0, lifetime: LIFETIME} | |
} | |
flakes.map! do |flake| | |
x, y, lifetime = flake[:x], flake[:y], flake[:lifetime] | |
# Erase flake. | |
erase_position(x, y) unless lifetime == LIFETIME # newly-born, hasn't been drawn yet. | |
lifetime -= 1 | |
# Only keep & process flakes that haven't gone too wide or reached the end of their lifetime. | |
if x >= -WIDTH && x < 2 * WIDTH && lifetime > 0 | |
# Calculate wind- and random-drifted new x position, and extra drop in y position, | |
# as long as the flake hasn't reached the ground yet. | |
if y < HEIGHT - 1 | |
random_horizontal_drift = rand | |
x = random_horizontal_drift < 0.1 ? x-1 : random_horizontal_drift > 0.9 ? x+1 : x | |
x += 1 if WIND_SPEED > 0 && rand < WIND_SPEED | |
x -= 1 if WIND_SPEED < 0 && rand < 0 - WIND_SPEED | |
# A slight chance to drift one extra unit lower. | |
y += 1 if rand < 0.1 | |
end | |
# Fall the usual one unit if the flake still hasn't reached the ground yet. | |
y += 1 if y < HEIGHT - 1 | |
twinkle_print_position(x, y) | |
{x: x, y: y, lifetime: lifetime} | |
end | |
end.compact! | |
sleep SLEEP_DURATION | |
end | |
end | |
run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment