Let it snow()

Let it snow()

juliasnow

The code could definitely be golfed…

function snow(io = stdout)
    h, w = displaysize(io)
    iob = IOBuffer()
    ioc = IOContext(IOContext(iob, io), :displaysize=>(h,w))
    print(io, repeat("\n", h), "\e[", h, "A\e[1G") # new lines and move back up
    air = ones(Int, w, h)
    flakes = [" ", "*", "❄︎", "❅", "❆"]
    scsin(t) = ((sin(t) / 2) + 0.5) * (0.1 / 3)
    likelihood(t) = scsin(t) + scsin(t * 1.00001) + scsin(t * 0.9999)
    try
        while true
            for x in 1:w, y in h:-1:1
                air[x,y] = if y == 1 # new flakes
                    rand() < likelihood(time()) ? rand(2:length(flakes)) : 1
                elseif y == h # accumulate bottom
                    ((rand() < 0.95 && air[x,y] > 1) || (air[x, y-1] > 1 && rand() < 0.2)) ? 2 : 1
                elseif all(>(1), air[x, y:end]) # melt pile sometimes
                    rand() < 0.95 ? 2 : 1
                elseif (air[x, y-1] > 1 && all(>(1), air[x, (y+1):end])) # if flake coming and piled up below
                    rand() < 0.1 ? 2 : 1
                else # fall downwards otherwise
                    air[x, y-1]
                end
            end
            print(ioc, "\e[", h, "A\e[1G") # move back to start
            print.((ioc,), flakes[air])
            print(ioc, "\e[", h, "A\e[1G") # move back to start
            Base.banner(ioc)
            printstyled(ioc, "julia> ", color = :green, bold = true)
            println(ioc, "snow()")
            print(io, String(take!(iob)))
            sleep(1/8)
        end
    catch e
        isa(e,InterruptException) || rethrow()
    end
    nothing
end
120 Likes

❄︎❅❆ options? :rofl:

15 Likes

This is awesome! Please share it on Twitter and Tag the Julia account. I also want to set my REPL up to show this in the background when I am running code :smile:

13 Likes

@ianshmean: https://twitter.com/JuliaLanguage/status/1470423267576008704?s=20

4 Likes

This could be a perfect Easter Egg for Julia 1.8…

EDIT: By the way, does Julia have any ?

1 Like

julia --lisp might count.

4 Likes

The first snowflakes * were falling vertically on my REPL.

However, the last ones *, ❄︎, ❅, ❆ seem to fall with strong side winds…

Aren’t the JuliaMono fonts mono-flake?

2 Likes

Might be that your terminal app is struggling with the adverse weather conditions - it looks good in my MacOS Terminal.

4 Likes

If you want to control the wind, change the width of the terminal window after starting it.
It’s a bug feature

12 Likes

I was just trying to find this! Just in time for that time of year! :snowflake: :christmas_tree: :santa: :coffee:

8 Likes

Hah, that’s great, thanks for the bump.

1 Like

Adding a tree under the snow:

using Printf
foreach(i-> @printf("%19s%-19s\n", "❅".^(i,i-1)...), 1:9)
foreach(_-> @printf("%19s\n", "❅"), 1:3)

Happy Holidays!

6 Likes

Adding the tree to the scene (and making it work on 1.11 given the banner moved to REPL)

using REPL, Printf
function snow(io = stdout)
    h, w = displaysize(io)
    iob = IOBuffer()
    ioc = IOContext(IOContext(iob, io), :displaysize=>(h,w))
    print(io, repeat("\n", h), "\e[", h, "A\e[1G") # new lines and move back up
    air = ones(Int, w, h)
    flakes = [" ", "*", "❄︎", "❅", "❆"]
    scsin(t) = ((sin(t) / 2) + 0.5) * (0.1 / 3)
    likelihood(t) = scsin(t) + scsin(t * 1.00001) + scsin(t * 0.9999)
    try
        while true
            for x in 1:w, y in h:-1:1
                air[x,y] = if y == 1 # new flakes
                    rand() < likelihood(time()) ? rand(2:length(flakes)) : 1
                elseif y == h # accumulate bottom
                    ((rand() < 0.95 && air[x,y] > 1) || (air[x, y-1] > 1 && rand() < 0.2)) ? 2 : 1
                elseif all(>(1), air[x, y:end]) # melt pile sometimes
                    rand() < 0.95 ? 2 : 1
                elseif (air[x, y-1] > 1 && all(>(1), air[x, (y+1):end])) # if flake coming and piled up below
                    rand() < 0.1 ? 2 : 1
                else # fall downwards otherwise
                    air[x, y-1]
                end
            end
            print(ioc, "\e[H") # move back to home
            foreach(space -> print(ioc, flakes[space]), air)
            print(ioc, "\e[H") # move back to home
            print(ioc, "\e[$(h-13)B\e[1G") # move to 12th row from bottom, col 1
            foreach(i-> @printf(ioc, "%19s%-19s\n", "❅".^(i,i-1)...), 1:9) # tree
            foreach(_-> @printf(ioc, "%19s\n", "❅"), 1:3) # trunk
            print(ioc, "\e[H") # move back to home
            @static VERSION > v"1.11.0-0" ? REPL.banner(ioc) : Base.banner(ioc)
            printstyled(ioc, "julia> ", color = :green, bold = true)
            println(ioc, "snow()")
            print(io, String(take!(iob)))
            sleep(1/8)
        end
    catch e
        isa(e,InterruptException) || rethrow()
    end
    nothing
end
6 Likes

This is cool, wouldn’t it be worthy of being released as a Julia package on Github or even the General registry?

3 Likes

Removing the snowless rectangle around the tree:

using REPL

function snow(io = stdout)
    h, w = displaysize(io)
    iob = IOBuffer()
    ioc = IOContext(IOContext(iob, io), :displaysize=>(h,w))
    print(io, repeat("\n", h), "\e[", h, "A\e[1G") # new lines and move back up
    air = ones(Int, w, h)
    flakes = [" ", "*", "❄︎", "❅", "❆"]
    scsin(t) = ((sin(t) / 2) + 0.5) * (0.1 / 3)
    likelihood(t) = scsin(t) + scsin(t * 1.00001) + scsin(t * 0.9999)
    try
        while true
            for x in 1:w, y in h:-1:1
                air[x,y] = if y == 1 # new flakes
                    rand() < likelihood(time()) ? rand(2:length(flakes)) : 1
                elseif y == h # accumulate bottom
                    ((rand() < 0.95 && air[x,y] > 1) || (air[x, y-1] > 1 && rand() < 0.2)) ? 2 : 1
                elseif all(>(1), air[x, y:end]) # melt pile sometimes
                    rand() < 0.95 ? 2 : 1
                elseif (air[x, y-1] > 1 && all(>(1), air[x, (y+1):end])) # if flake coming and piled up below
                    rand() < 0.1 ? 2 : 1
                else # fall downwards otherwise
                    air[x, y-1]
                end
            end
            foreach(space -> print(ioc, flakes[space]), air)
            for itree in 1:12
                print(ioc, "\e[H") # move back to home
                if itree <= 9
                    print(ioc, "\e[$(h-14+itree)B\e[$(19-itree)G")
                    printstyled(ioc, "x"^(2*itree-1), color=:green)
                else # Trunk
                    print(ioc, "\e[$(h-14+itree)B\e[18G")
                    printstyled(ioc, "-", color=:reverse)
                end
            end
            print(ioc, "\e[H") # move back to home
            @static VERSION > v"1.11.0-0" ? REPL.banner(ioc) : Base.banner(ioc)
            printstyled(ioc, "julia> ", color = :green, bold = true)
            println(ioc, "snow()")
            print(io, String(take!(iob)))
            sleep(1/8)
        end
    catch e
        isa(e,InterruptException) || rethrow()
    end
    nothing
end
9 Likes

shouldn’t snowflakes be random and not following some trig function?

The trig part is introducing slow variations to the likelihood of the random generation of flakes to simulate wind flurries.

5 Likes

Hey ChatGPT. It’s 2024, the tree has grown, and other trees have started to grow around it. Also someone has put flashing lights on the trees…

using REPL

function snow(io = stdout)
    # Grab terminal dimensions
    h, w = displaysize(io)
    iob = IOBuffer()
    ioc = IOContext(IOContext(iob, io), :displaysize=>(h,w))
    
    # Clear the screen, move cursor up
    print(io, repeat("\n", h), "\e[$(h)A\e[1G")

    # The grid that holds falling snow
    air = ones(Int, w, h)

    # Snowflake characters (index 1 is empty space)
    flakes = [" ", "*", "❄︎", "❅", "❆"]

    # Small sinusoidal helper to vary snowfall probability
    scsin(t) = ((sin(t) / 2) + 0.5) * (0.1 / 3)
    likelihood(t) = scsin(t) + scsin(t * 1.00001) + scsin(t * 0.9999)

    # Print a single tree line with random “lights”
    function printtreeline(ioc, length_of_line)
        for i in 1:length_of_line
            if rand() < 0.12
                # White light
                printstyled(ioc, "o", color=:white, bold=true)
            else
                # Standard green branch
                printstyled(ioc, "x", color=:green)
            end
        end
    end

    # Draw a single “pine” at (base_row, center_col) with specified height + trunk
    function draw_tree(ioc, base_row, center_col; height=12, trunk_height=3)
        # Print each level from top to bottom
        for lvl in 1:height
            row = base_row - height + lvl
            col = center_col - (lvl - 1)
            
            print(ioc, "\e[$(row);$(col)H")
            printtreeline(ioc, 2*lvl - 1)
        end
        
        # Print trunk
        for trunk_lvl in 1:trunk_height
            row = base_row + trunk_lvl
            print(ioc, "\e[$(row);$(center_col)H")
            printstyled(ioc, "│", color=:reverse)
        end
    end

    try
        while true
            # Update the snowfall in 'air'
            for x in 1:w, y in h:-1:1
                air[x,y] = if y == 1
                    # Spawn new snowflakes at top
                    rand() < likelihood(time()) ? rand(2:length(flakes)) : 1
                elseif y == h
                    # Accumulate at bottom
                    ((rand() < 0.95 && air[x,y] > 1) ||
                     (air[x, y-1] > 1 && rand() < 0.2)) ? 2 : 1
                elseif all(>(1), air[x, y:end]) # melt pile sometimes
                    rand() < 0.95 ? 2 : 1
                elseif (air[x, y-1] > 1 && all(>(1), air[x, (y+1):end]))
                    rand() < 0.1 ? 2 : 1
                else
                    # Normal falling
                    air[x, y-1]
                end
            end

            # Print updated snow
            foreach(space -> print(ioc, flakes[space]), air)

            # Draw main (bigger) tree
            main_tree_height = 14
            trunk_height     = 4
            main_base_row    = h - 2  
            main_center_col  = div(w, 2)
            draw_tree(ioc, main_base_row, main_center_col; 
                      height=main_tree_height, trunk_height=trunk_height)

            # Draw some smaller trees around it
            draw_tree(ioc, main_base_row-2, main_center_col - 20; height=10, trunk_height=3)
            draw_tree(ioc, main_base_row-2, main_center_col + 20; height=10, trunk_height=3)
            draw_tree(ioc, main_base_row-6, main_center_col - 35; height=8,  trunk_height=2)
            draw_tree(ioc, main_base_row-6, main_center_col + 35; height=8,  trunk_height=2)

            # Move cursor home
            print(ioc, "\e[H")

            # REPL banner + prompt
            @static VERSION > v"1.11.0-0" ? REPL.banner(ioc) : Base.banner(ioc)
            printstyled(ioc, "julia> ", color = :green, bold = true)
            println(ioc, "snow()")

            # Flush buffer
            print(io, String(take!(iob)))

            # Slight pause
            sleep(1/8)
        end

    catch e
        isa(e, InterruptException) || rethrow()
    end
    nothing
end
16 Likes

Genius! Our favorite snow master is back.

2 Likes

For those of us on phone can we get a gif?

1 Like