24
\$\begingroup\$

Challenge Description

You have to show a simulation of rain in terminal.

In the example given below its adding 100 raindrops at random (use the default random function which your language offers) coordinates, waiting for 0.2 seconds and then redrawing again until the given time expires. Any character can be used for representing the raindrop.

Parameters

  • Wait time between redrawing in seconds.
  • Time for which the rain will be visible. This is just an integer representing the number of iterations. [So, the net time for which the rain will be visible is this integer multiplied by the wait time]
  • Message to be displayed when the rain ends. (This has to be centered)
  • Number of raindrops to be displayed on the screen.

Rules

  • A single byte should be used for representing a rain drop, and it can be anything, even cats and dogs.
  • It doesn't have to be responsive to terminal size which means you don't have to handle the bug for varied terminal sizes. You can specify the terminal width and height on your own.
  • Standard rules of golfing apply.

Code Sample and Output

This is an ungolfed version written in python 2.7 using ncurses.

import curses
import random
import time

myscreen = curses.initscr()
curses.curs_set(0) # no cursor please 
HEIGHT, WIDTH = myscreen.getmaxyx() 
RAIN = '/' # this is what my rain drop looks like 
TIME = 10 

def make_it_rain(window, tot_time, msg, wait_time, num_drops):
    """
    window    :: curses window 
    time      :: Total time for which it rains
    msg       :: Message displayed when it stops raining
    wait_time :: Time between redrawing scene 
    num_drops :: Number of rain drops in the scene 
    """
    for _ in range(tot_time):
        for i in range(num_drops):
            x,y=random.randint(1, HEIGHT-2),random.randint(1,WIDTH-2)       
            window.addstr(x,y,RAIN)
        window.refresh()
        time.sleep(wait_time)
        window.erase()

    window.refresh()
    window.addstr(HEIGHT/2, int(WIDTH/2.7), msg)


if __name__ == '__main__':
    make_it_rain(myscreen, TIME, 'IT HAS STOPPED RAINING!', 0.2, 100)
    myscreen.getch()
    curses.endwin()

Output -

enter image description here

\$\endgroup\$
  • 3
    \$\begingroup\$ In the future instead of posting again please edit the original. If people think the specs are clear they will nominate it for reopening. \$\endgroup\$ – Sriotchilism O'Zaic Jan 20 '17 at 20:03
  • 6
    \$\begingroup\$ Does the text have to be centered? Is it randomly raining, does initial position of droplets matter? How are you measuring time? In milliseconds, seconds, minutes? What is "Extra Points"? \$\endgroup\$ – Magic Octopus Urn Jan 20 '17 at 20:03
  • 1
    \$\begingroup\$ If you, A. Specify units. B. Specify terminal size or take it as input. and C. Remove the part about extra points; this challenge would be better defined. \$\endgroup\$ – Magic Octopus Urn Jan 20 '17 at 20:07
  • \$\begingroup\$ When you say "random", can we assume that means "uniformly random"? \$\endgroup\$ – Digital Trauma Jan 20 '17 at 20:07
  • 3
    \$\begingroup\$ 1 day in the sandbox is often not enough. Remember that people are here recreationally and from diverse timezones - immediate feedback should not be expected. \$\endgroup\$ – Digital Trauma Jan 20 '17 at 20:09

11 Answers 11

12
\$\begingroup\$

MATL, 52 bytes

xxx:"1GY.Xx2e3Z@25eHG>~47*cD]Xx12:~c!3G80yn-H/kZ"why

Inputs are, in this order: pause between updates, number of drops, message, number of repetitions. The monitor has size 80×25 characters (hard-coded).

GIF or it didn't happen! (Example with inputs 0.2, 100, 'THE END', 30)

enter image description here

Or try it at MATL Online.

Explanation

xxx      % Take first three inputs implicitly and delete them (but they get
         % copied into clipboard G)
:"       % Take fourth input implicitly. Repeat that many times
  1G     %   Push first input (pause time)
  Y.     %   Pause that many seconds
  Xx     %   Clear screen
  2e3    %   Push 2000 (number of chars in the monitor, 80*25)
  Z@     %   Push random permutation of the integers from 1 to 2000
  25e    %   Reshape as a 25×80 matrix, which contains the numbers from 1 to 2000
         %   in random positions
  HG     %   Push second input (number of drops)
  >~     %   Set numbers that are <= second input to 1, and the rest to 0
  47*c   %   Multiply by 47 (ASCII for '/') and convert to char. Char 0 will
         %   be displayed as a space
  D      %   Display
]        % End
Xx       % Clear screen
12:~     % Push row vector of twelve zeros
c!       % Convert to char and transpose. This will produce 12 lines containing
         % a space, to vertically center the message in the 25-row monitor
3G       % Push third input (message string)
80       % Push 80
yn       % Duplicate message string and push its length
-        % Subtract
H/k      % Divide by 2 and round down
Z"       % Push string of that many spaces, to horizontally center the message 
         % in the 80-column monitor
w        % Swap
h        % Concatenate horizontally
y        % Duplicate the column vector of 12 spaces to fill the monitor
         % Implicitly display
\$\endgroup\$
  • 1
    \$\begingroup\$ I like how it ends in why :) \$\endgroup\$ – tkellehe Jan 21 '17 at 18:56
  • 1
    \$\begingroup\$ @tkellehe I like the description on your profile :-) \$\endgroup\$ – Luis Mendo Jan 21 '17 at 19:03
  • 1
    \$\begingroup\$ thank you. Your language is so much fun to read. (Yes, I have been stalking your MATL answers lol) \$\endgroup\$ – tkellehe Jan 22 '17 at 0:22
10
\$\begingroup\$

JavaScript (ES6), 268 261 bytes

t=
(o,f,d,r,m,g=(r,_,x=Math.random()*78|0,y=Math.random()*9|0)=>r?g(r-(d[y][x]<`/`),d[y][x]=`/`):d.map(a=>a.join``).join`
`)=>i=setInterval(_=>o.data=f--?g(r,d=[...Array(9)].map(_=>[...` `.repeat(78)])):`



`+` `.repeat(40-m.length/2,clearInterval(i))+m,d*1e3)
<input id=f size=10 placeholder="# of frames"><input id=d placeholder="Interval between frames"><input id=r size=10 placeholder="# of raindrops"><input id=m placeholder="End message"><input type=button value=Go onclick=t(o.firstChild,+f.value,+d.value,+r.value,m.value)><pre id=o>&nbsp;

At least on my browser, the output is designed to fit into the Stack Snippet area without having to go "Full page", so if you ask for more than 702 raindrops it will crash.

Edit: Saved 7 bytes by using a text node as my output area.

\$\endgroup\$
  • \$\begingroup\$ You can save a few bytes by passing the function as a string to setInterval. Also, why do you use textContent instead of innerHTML? \$\endgroup\$ – Luke Jan 21 '17 at 10:38
  • \$\begingroup\$ @L.Serné Using an inner function allows me to refer to the variables in the outer function. \$\endgroup\$ – Neil Jan 21 '17 at 10:42
  • \$\begingroup\$ Oops, my bad, didn't notice that. \$\endgroup\$ – Luke Jan 21 '17 at 10:44
8
\$\begingroup\$

R, 196 192 185 bytes

Just a mock version I wrote based on the description. Hopefully it's somewhat what OP was looking for.

Saved some bytes thanks to @plannapus.

f=function(w,t,m,n){for(i in 1:t){x=matrix(" ",100,23);x[sample(2300,n)]="/";cat("\f",rbind(x,"\n"),sep="");Sys.sleep(w)};cat("\f",g<-rep("\n",11),rep(" ",(100-nchar(m))/2),m,g,sep="")}

The arguments:

  • w: Wait time between frames
  • t: Total number of frames
  • m: Custom message
  • n: Number of rain drops

Example

Why does it look like it's raining upwards?

Edit: I should mention that this is my customized 23x100 character R-studio console. The dimensions are hardcoded into the function but one could in principle use getOption("width") to make it flexible to console size.

enter image description here

Ungolfed and explained

f=function(w,t,m,n){
    for(i in 1:t){
        x=matrix(" ",100,23);             # Initialize matrix of console size
        x[sample(2300,n)]="/";            # Insert t randomly sampled "/"
        cat("\f",rbind(x,"\n"),sep="");   # Add newlines and print one frame
        Sys.sleep(w)                      # Sleep 
    };
    cat("\f",g<-rep("\n",11),rep(" ",(100-nchar(m))/2),m,g,sep="")  # Print centered msg
}
\$\endgroup\$
  • \$\begingroup\$ This looks perfectly fine! +1 and I don't think its going upwards it just your perception lol \$\endgroup\$ – hashcode55 Jan 20 '17 at 21:52
  • \$\begingroup\$ @plannapus. Realized rep() automatically floors the times argument so no need for that either. Saved another 7 bytes! \$\endgroup\$ – Billywob Jan 21 '17 at 10:25
  • \$\begingroup\$ Very nice solution. You can save one byte by pushing the console size to function arguments (if that's allowed); you can save another byte by using runif rather than sample to randomly populate the matrix. Like so: f=function(w,t,m,n,x,y){for(i in 1:t){r=matrix(" ",x,y);r[runif(n)*x*y]="/";cat("\f",rbind(r,"\n"),sep="");Sys.sleep(w)};cat("\f",g<-rep("\n",y/2),rep(" ",(x-nchar(m))/2),m,g,sep="")} \$\endgroup\$ – rturnbull Jan 21 '17 at 10:55
5
\$\begingroup\$

C 160 bytes

f(v,d,w,char *s){i,j;char c='/';for(i=0;i<v;i++){for(j=0;j<d;j++)printf("%*c",(rand()%100),c);fflush(stdout);sleep(w);}system("clear");printf("%*s\n",1000,s);}

v-Time the raindrops are visible in seconds.
d-Number of drops per iteration
w-Wait time in seconds, before the next iteration
s-String to be passed on once its done

Ungolfed version:

void f(int v, int d, int w,char *s)
{ 

   char c='/';
   for(int i=0;i<v;i++)
   { 
      for(int j=0;j<d;j++)
         printf("%*c",(rand()%100),c);

      fflush(stdout);
      sleep(w); 
   }   
   system("clear");
   printf("%*s\n", 1000,s);
}

Testcase on my terminal

v - 5 seconds
d - 100 drops
w - 1 second wait time
s - "Raining Blood" :)
\$\endgroup\$
4
\$\begingroup\$

R, 163 chars

f=function(w,t,n,m){for(i in 1:t){cat("\f",sample(rep(c("/"," "),c(n,1920-n))),sep="");Sys.sleep(w)};cat("\f",g<-rep("\n",12),rep(" ",(80-nchar(m))/2),m,g,sep="")}

With indents and newlines:

f=function(w,t,n,m){
    for(i in 1:t){
        cat("\f",sample(rep(c("/"," "),c(n,1920-n))),sep="")
        Sys.sleep(w)
    }
    cat("\f",g<-rep("\n",12),rep(" ",(80-nchar(m))/2),m,g,sep="")
}

It's adapted to a terminal size of 24 lines by 80 columns. w is the waiting time, t the number of frames, n the number of raindrops and m the final message.

It differs from @billywob's answer in the different use of sample: if the output size is omitted, sample gives a permutation of the input vector (here a vector containing the needed number of raindrops and the corresponding number of spaces, thanks to the fact that argument times of function rep is vectorized). As the size of the vector corresponds exactly to the size of the screen, there is no need to add newlines, or to force-shape it into a matrix.

Gif of output

\$\endgroup\$
3
\$\begingroup\$

NodeJS: 691 158 148 Bytes

Edit

As requested, additional features removed and golf'd.

s=[];setInterval(()=>{s=s.slice(L='',9);for(;!L[30];)L+=' |'[Math.random()*10&1];s.unshift(L);console.log("\u001b[2J\u001b[0;0H"+s.join('\n'))},99)

The rules specify disregard for size, but this version includes a glitch for the first few frames. It is 129 bytes.

s='';setInterval(()=>{for(s='\n'+s.slice(0,290);!s[300];)s=' |'[Math.random()*10&1]+s;console.log("\u001b[2J\u001b[0;0H"+s)},99)

Previous answer

Perhaps not the best golfing, but I got a bit carried away. It has optional wind direction and rain factor.

node rain.js 0 0.3

var H=process.stdout.rows-2, W=process.stdout.columns,wnd=(arg=process.argv)[2]||0, rf=arg[3]||0.3, s=[];
let clr=()=>{ console.log("\u001b[2J\u001b[0;0H") }
let nc=()=>{ return ~~(Math.random()*1+rf) }
let nl=()=>{ L=[];for(i=0;i<W;i++)L.push(nc()); return L}
let itrl=(l)=>{ for(w=0;w<wnd;w++){l.pop();l.unshift(nc())}for(w=0;w>wnd;w--){l.shift();l.push(nc())};return l }
let itrs=()=>{ if(s.length>H)s.pop();s.unshift(nl());s.map(v=>itrl(v)) }
let d=(c,i)=>{if(!c)return ' ';if(i==H)return '*';if(wnd<0)return '/';if(wnd>0)return '\\';return '|'}
let drw=(s)=>{ console.log(s.map((v,i)=>{ return v.map(  c=>d(c,i)  ).join('') }).join('\r\n')) }
setInterval(()=>{itrs();clr();drw(s)},100)

See webm of it working here

\$\endgroup\$
2
\$\begingroup\$

Noodel, noncompeting 44 bytes

I had centering text on my list of things to do since I made the language... But, I was lazy and did not add until after this challenge. So, here I am not competing, but had fun with the challenge:)

ØGQÆ×Øæ3/×Æ3I_ȥ⁻¤×⁺Æ1Ḷḋŀ÷25¶İÇæḍ€Æ1uụC¶×12⁺ß

The console is size is hard coded to 25x50 which does not look good in the online editor, but does for the snippet.

Try it:)


How It Works

Ø                                            # Pushes all of the inputs from the stack directly back into the stdin since it is the first token.

 GQÆ×Ø                                       # Turns the seconds into milliseconds since can only delay by milliseconds.
 GQ                                          # Pushes on the string "GQ" onto the top of the stack.
   Æ                                         # Consumes from stdin the value in the front and pushes it onto the stack which is the number of seconds to delay.
    ×                                        # Multiplies the two items on top of the stack.
                                             # Since the top of the stack is a number, the second parameter will be converted into a number. But, this will fail for the string "GQ" therein treated as a base 98 number producing 1000.
     Ø                                       # Pops off the new value, and pushes it into the front of stdin.

      æ3/×Æ3I_ȥ⁻¤×⁺                          # Creates the correct amount of rain drops and spaces to be displayed in a 50x25 console.
      æ3                                     # Copies the forth parameter (the number of rain drops).
        /                                    # Pushes the character "/" as the rain drop character.
         ×                                   # Repeats the rain drop character the specified number of times provided.
          Æ3                                 # Consumes the number of rain drops from stdin.
            I_                               # Pushes the string "I_" onto the stack.
              ȥ                              # Converts the string into a number as if it were a base 98 number producing 25 * 50 = 1250.
               ⁻                             # Subtract 1250 - [number rain drops] to get the number of spaces.
                ¤                            # Pushes on the string "¤" which for Noodel is a space.
                 ×                           # Replicate the "¤" that number of times.
                  ⁺                          # Concatenate the spaces with the rain drops.

                   Æ1Ḷḋŀ÷25¬İÇæḍ€            # Handles the animation of the rain drops.
                   Æ1                        # Consumes and pushes on the number of times to loop the animation.
                     Ḷ                       # Pops off the number of times to loop and loops the following code that many times.
                      ḋ                      # Duplicate the string with the rain drops and spaces.
                       ŀ                     # Shuffle the string using Fisher-Yates algorithm.
                        ÷25                  # Divide it into 25 equal parts and push on an array containing those parts.
                           ¶                 # Pushes on the string "¶" which is a new line.
                            İ                # Join the array by the given character.
                             Ç               # Clear the screen and display the rain.
                              æ              # Copy what is on the front of stdin onto the stack which is the number of milliseconds to delay.
                               ḍ             # Delay for the specified number of milliseconds.
                                €            # End of the loop.

                                 Æ1uụC¶×12⁺ß # Creates the centered text that is displayed at the end.
                                 Æ1          # Pushes on the final output string.
                                   u         # Pushes on the string "u" onto the top.
                                    ụC       # Convert the string on the top of the stack to an integer (which will fail and default to base 98 which is 50) then center the input string based off of that width.
                                      ¶      # Push on a the string "¶" which is a new line.
                                       ×12   # Repeat it 12 times.
                                          ⁺  # Append the input string that has been centered to the new lines.
                                           ß # Clear the screen.
                                             # Implicitly push on what is on the top of the stack which is the final output.

<div id="noodel" code="ØGQÆ×Øæ3/×Æ3I_ȥ⁻¤×⁺Æ1Ḷḋŀ÷25¶İÇæḍ€Æ1uụC¶×12⁺ß" input='0.2, 50, "Game Over", 30' cols="50" rows="25"></div>

<script src="https://tkellehe.github.io/noodel/noodel-latest.js"></script>
<script src="https://tkellehe.github.io/noodel/ppcg.min.js"></script>

\$\endgroup\$
  • 1
    \$\begingroup\$ Ah thats a cool language to have in my toolbox haha! Anyways, glad you liked the challenge :) +1 \$\endgroup\$ – hashcode55 Jan 24 '17 at 19:10
2
\$\begingroup\$

Ruby + GNU Core Utils, 169 bytes

Parameters to the function are wait time, number of iterations, message, and number of raindrops, in that order. Newlines for readability.

Core Utils were needed for tput and clear.

->w,t,m,n{x=`tput cols`.to_i;z=x*h=`tput lines`.to_i
t.times{s=' '*z;[*0...z].sample(n).map{|i|s[i]=?/};puts`clear`+s;sleep w}
puts`clear`+$/*(h/2),' '*(x/2-m.size/2)+m}
\$\endgroup\$
1
\$\begingroup\$

Python 2.7, 254 251 bytes

This is my own try without using ncurses.

from time import*;from random import*;u=range;y=randint
def m(t,m,w,n):
    for _ in u(t):
        r=[[' 'for _ in u(40)]for _ in u(40)]
        for i in u(n):r[y(0,39)][y(0,39)]='/'
        print'\n'.join(map(lambda k:' '.join(k),r));sleep(w);print '<esc>[2J'
    print' '*33+m

Thank to @ErikTheOutgolfer for correcting and saving me bytes.

\$\endgroup\$
  • \$\begingroup\$ You cannot put a for loop in one line (like you have in 40)];for i in u(). You also need an ESC char in '[2J' I think. Also, there was an extra space in u(n): r[y. I don't know how you counted 249 though. All issues I found were fixed here. \$\endgroup\$ – Erik the Outgolfer Jan 21 '17 at 12:23
  • \$\begingroup\$ The code I posted is working for me. And yeah I actually counted it wrong, I didn't count the white indented space, I didn't know about it. Thanks for the link! I'll edit it. \$\endgroup\$ – hashcode55 Jan 21 '17 at 13:35
  • \$\begingroup\$ @EriktheOutgolfer Oh and yeah regarding that ESC char, no escape sequence is needed. It just prints 'ESC[2J' effectively, which is ansi escape sequence for clearing the screen. It doesn't reset the cursor position though. \$\endgroup\$ – hashcode55 Jan 21 '17 at 13:59
  • \$\begingroup\$ You can golf it even more :) But you need to add a note below your code specifying that <esc> denotes a literal 0x1B ESC byte. Byte count is 242, not 246. \$\endgroup\$ – Erik the Outgolfer Jan 21 '17 at 14:23
  • \$\begingroup\$ @EriktheOutgolfer Oh thanks! \$\endgroup\$ – hashcode55 Jan 21 '17 at 14:34
1
\$\begingroup\$

SmileBASIC, 114 bytes

INPUT W,T,M$,N
FOR I=1TO T
VSYNC W*60CLS
FOR J=1TO N
LOCATE RND(50),RND(30)?7;
NEXT
NEXT
LOCATE 25-LEN(M$)/2,15?M$

Console size is always 50*30.

\$\endgroup\$
1
\$\begingroup\$

Perl 5, 156 bytes

154 bytes code + 2 for -pl.

$M=$_;$W=<>;$I=<>;$R=<>;$_=$"x8e3;{eval'$-=rand 8e3;s!.{$-}\K !/!;'x$R;print;select$a,$a,$a,$W;y!/! !;--$I&&redo}$-=3920-($l=length$M)/2;s;.{$-}\K.{$l};$M

Uses a fixed size of 160x50.

See it online!


Perl 5, 203 bytes

201 bytes code + 2 for -pl.

$M=$_;$W=<>;$I=<>;$R=<>;$_=$"x($z=($x=`tput cols`)*($y=`tput lines`));{eval'$-=rand$z;s!.{$-}\K !/!;'x$R;print;select$a,$a,$a,$W;y!/! !;--$I&&redo}$-=$x*($y+!($y%2))/2-($l=length$M)/2;s;.{$-}\K.{$l};$M

Uses tput to determine the size of the terminal.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.