13
\$\begingroup\$

Following my entry to the Obfuscated Hello World I thought it might be fun to share the underlying code. But why just show the code, lets make it a golf too!

Challenge

Write a script that scrolls a string across the terminal, from right to left, settling on the left side.

Input

Takes a string as an argument.

Result

Prints the scrolling marquee to STDOUT. Max width of ~50 chars. Starts with 0 or 1 char showing. Some space between letters while scrolling. Stops when settled (having no extra space between word chars). Slow scroll, but not too slow (< 1s per iteration).

Example

Running script with arg 'Hello World'

                                                   H

later

                H    e    l    l    o         W    o

later

H    e    l    l    o          W    o    r    l    d

later

Hell    o         W    o    r    l    d

finally

Hello World

For a running example, try my code from the "Hello World" challenge. Eventually I will post mine. It currently is 202 chars in Perl. Now that there are some competitors, I have posted mine in the answers.

Winner

I don't want the restrictions to be absolute, that's why I left them a little vague. The shortest script which follows the spirit of my original will win.

Notes

This game assumes xterm environment. Should another environment prove useful, only similar environments will be compared and a separate winner may be declared for each.

Addendum (April 25, 2012)

To address some budding issues, I'm making a ruling. Your character count must include code needed to:

  1. Flush STDOUT (Looking at you Ruby)
  2. Implement sleep with time delay of <1s (Looking at you Perl)

This may be done as command line switches to an interpreter, but those characters count in the total (sans surrounding whitespace).

\$\endgroup\$
  • \$\begingroup\$ I'm a little concerned about terminal behaviors for this kind of thing...xterm, vt102, ...? \$\endgroup\$ – dmckee Mar 26 '12 at 17:13
  • \$\begingroup\$ I'm assuming xterm, but I don't think it matters too much. Perhaps I don't understand your concern? \$\endgroup\$ – Joel Berger Mar 26 '12 at 17:20
  • \$\begingroup\$ These tricks are usually produced by relying on the way various terminals handle some of the non-printing characters, and terminals differed in what they could do and what sequences produced the effects. For reproducibility it might be good to have a specified terminal environment. \$\endgroup\$ – dmckee Mar 26 '12 at 17:27
  • 1
    \$\begingroup\$ It'll serve you right is someone posts a really short answer that depends on an obscure terminal that you don't have an emulator for, but OK. \$\endgroup\$ – dmckee Mar 26 '12 at 17:35
  • \$\begingroup\$ ok I think I have it now. Thanks for the thoughts :-) \$\endgroup\$ – Joel Berger Mar 26 '12 at 17:47

17 Answers 17

5
\$\begingroup\$

python 2 - 146 chars

edit: made it a function instead of input through stdin. first argument is the string, and the second argument is the length you want it to be. so invocation would be f('Hello World', 50). I also made it much smoother; when each character 'landed' there was an awkward pause

import os,time
def f(x,n):
 y=' '*n+'  '.join(x);z=0
 while y:w=y[0]==x[z];y=y[1+w:];z+=w;os.system('cls');print((x[:z]+y)[:n]);time.sleep(0.1)

old, 158 chars:

import os,time,sys
x=' '.join(sys.argv[1:])
y=' '*50+'  '.join(x)
z=0
while y:z+=y[0]==x[z];y=y[1:];os.system('cls');print((x[:z]+y)[:50]);time.sleep(0.1)
\$\endgroup\$
  • \$\begingroup\$ Using bash (at least in a recent installation of MacOSX and CentOS), the shell command used to clear the terminal screen should be 'clear' not 'cls'. \$\endgroup\$ – Paolo Mar 27 '12 at 8:04
  • \$\begingroup\$ 'cls' for windows, 'clear' for OSX/Linux builds, I guess \$\endgroup\$ – Blazer Mar 27 '12 at 8:23
  • \$\begingroup\$ Advice how to start the program for those not dealing with python on a daily basis, would be helpful. Start python. paste code, call f("Hello World, 40) worked for me. \$\endgroup\$ – user unknown Mar 31 '12 at 17:46
  • \$\begingroup\$ @user I um. I did put invocation up there? \$\endgroup\$ – Blazer Mar 31 '12 at 20:01
4
\$\begingroup\$

Ruby, 93 91 89 chars

u="\1";s=u*50+[*$*[0].chars]*(u*3);$><<s.tr(u," ")[0,50]+" \r"while s.sub!u,""*sleep(0.1)

The text to be displayed must be given as command line argument, e.g.

ruby marquee.rb "Hello World"

for the example shown above. Unfortunately I cannot show the animation here, so you have to try out the code yourself.

Previous version:

s=" "*67+[*$*[0].chars]*"   ";(s.size*3/4).times{|j|s[j/3]='';$><<s[16,50]+" \r";sleep 0.1}
\$\endgroup\$
  • \$\begingroup\$ Impressive size. Its not very smooth though, is that me (I'm on a pretty low-power machine at the moment) or is that how the code works? \$\endgroup\$ – Joel Berger Mar 26 '12 at 20:25
  • \$\begingroup\$ Figured it out, I had to set STDOUT.sync=true; so that it autoflushes. The Perl equiv is $|++. Thats an additional 17 chars, but still well below mine. Well I can't have Ruby beating Perl! Gonna hafta get working. Nice one. \$\endgroup\$ – Joel Berger Mar 26 '12 at 20:29
  • \$\begingroup\$ If I call ruby1.8 "Hello World" I get, not exactly to my surprise, an error saying: ruby1.8: No such file or directory -- Hello World (LoadError) \$\endgroup\$ – user unknown Mar 31 '12 at 17:51
  • \$\begingroup\$ @userunknown maybe you should put the path to the source file in there, too: ruby foo.rb args ;-) \$\endgroup\$ – Patrick Oscity Apr 26 '12 at 16:10
  • \$\begingroup\$ @padde: Yes, I should. Unfortunately Howard edited his post without informing me about his change. Have a look at the history to understand my question. \$\endgroup\$ – user unknown Apr 26 '12 at 16:14
3
\$\begingroup\$

C, 94 83 80 173 chars

EDIT: Added lots of code, implements all the requested functionality now. The constant 1e8 can be tweaked to control the speed. On my machine, it's quite fast as it is.
Some characters can surely be saved here. l can be static (saves initialization), c can become a pointer (replacing b+c).

char b[99],c=1;
main(a,t,w,i,l)char**t;{
    for(l=0;b[l++]=*t[1]++;b[l++]=32);
    for(w=80;i--||
        printf("\033[F\033[K%*.*s\n",w-=l<a,a++,b,i=1e8)>l+6||
        b[++c]&&memmove(b+c-1,b+c,l););
}

Old version (80 chars), with partial functionality:
Saved a couple of chars by replacing char**t with int*t. Works fine in 32-bit (int**t would support 64-bit).

main(i,t,w)
    int*t;
{
    for(w=80;i--||printf("\033[F\033[K%*s\n",w,t[1],i=1e8)*--w;);
}
\$\endgroup\$
2
\$\begingroup\$

K&R C -- 431 416 characters

Respects the standard to a high degree. Uses ncurses so it should be largely terminal independent. There is a slight stuttering when the text hits the side due to some trickery played to preserve the intended whitespace in the string.

The string to use should be passed as the first argument on the command line (and should be escaped if it contains spaces, more so if it contains a ! as my test string (Hello, World!) did).

#include <ncurses.h>
#include <unistd.h>
#define T usleep(1e5),S(l)
#define U mvprintw(23,0,"%s",l),refresh()
char l[63],*p,*q,r;
S(char*s){r=0;if(*s==32)q=s++;else{for(;*s-32||*(s+1)-32;s++); 
for(q=s;*s==32;s++);(s-q)&1?s--:usleep(1e5);}
for(r=0;*s;*q++=*s++){*s-32?r=1:0;}return r;}
main(int c,char**v){initscr();curs_set(0);for(c=0;c<62;l[c++]=32);
for(p=*++v;*p;){l[52]=*p++;U;T;U;T;U;T;}for(;T;U);getch();endwin();}

In a more readable and commented form:

#include <ncurses.h>
#include <unistd.h>

char l[63] /* take advantage of 0 initialization */,
  *p,*q, r;

/* Remove the first unwanted space. Unwanted means at the begining of
 * the line, all of even length blocks between non-spaces, and
 * all-bu-one of odd length blocks between non-spaces.
 *
 * Return true if the removed space occurs before a non-space character.
 */
S/*lide marquee*/(char*s){
  r=0; /* initialize the return value */
  if(*s==' '){
    q=s++;
  } else {
    /* Find the start of first block of contiguous spaces */
    for(;*s-' '||*(s+1)-' ';s++); 
    for(q=s;*s==' ';s++); /* q holds the start, s finds it's end */
    /* if this block is even length remove all, if odd, all but one */
    if( (s-q)%2 )s--; else usleep(1e5);
  }
  /* copy from s to q all the way to the end */
  for(r=0;*s;*q++=*s++){ 
    if(*s-' ')r=1; /* note if we pass a non-space */
  } 
  return r;
}

main(int c,char**v){
  initscr();curs_set(0); /* setup ncurses with invisible cursor */
  for(c=0;c<62;l[c++]=' '); /* initialize l */
  for(p=*++v;*p;){ /* load the message into the marque, skipping space */
    l[52]=*p++;
    mvprintw(23,0,"%s",l),
    refresh();
    usleep(1e5),
    S(l);
    usleep(1e5),
    S(l);
    usleep(1e5),
    S(l);
  }
  for(;usleep(1e5),S(l);mvprintw(23,0,"%s",l),refresh()); /* keeping sliding until we're done. */
  getch();
  endwin();
}
\$\endgroup\$
  • \$\begingroup\$ There's lots of potential for shortening, especially by replacing if with operators. For example - if((s-q)%2)s--;else usleep(1e5); -> s-q&1?s--:usleep(1e5); (or s-=s-q&1||usleep(1e5);) \$\endgroup\$ – ugoren Mar 28 '12 at 8:12
  • \$\begingroup\$ @ugoren: Yup, and I had forgotten to replace the ' 's with numeric equivalents. \$\endgroup\$ – dmckee Mar 30 '12 at 22:39
  • \$\begingroup\$ A few more tricks: Replace x==32 with x-32 (reverses meaning, so reverse if-else), or with x<33 (assuming 0..31 never used). Initialize with values you have (for(curs_set(c=0);...). *(s+1)->s[1]. Remove unneeded braces (replacing ; with , will help). \$\endgroup\$ – ugoren Apr 1 '12 at 8:21
2
\$\begingroup\$

Perl 5.13.2, 96

$_=join$;x4,$;x46,split//,pop;print substr(s/$;/ /gr,0,50)." \r"while$|=s/$;//+select'','','',.1

Stealing a lot from @Kevin Reid's answer, especially the /r trick available in newer Perls.

Perl, 115

Like @Joel Berger's answer, this would become much shorter if I could use sleep 1 and be slow, or pass -MTime::HiRes=sleep on the command line to enable sleep.1. Otherwise the only built-in way to get short sleeps is select'','','',.1 which is pretty long.

$|=@_=(($")x45,map{($")x4,$_}split//,pop);for(0..$#_){print@_," \r";splice@_,($_-=45)<0?0:$_/4,1;select'','','',.1}

Perl, 128

$_=$"x9 .pop;s/./    $&/g;$.=-46;$\=" \r";while($|=/./g){print substr($_,0,50);pos=++$.<0?0:$./4;s/\G.//;select'','','',.1}print

Perl, 133

$|=@_=split//,pop;for$i(reverse-$#_..50){for(@_){print$"x($j||$i),$_;($i+=$j=($i++>0)*4)>50&&last}print"    \r";$j=select'','','',.1}
\$\endgroup\$
  • \$\begingroup\$ yeah, I bit myself with my own rule on that one! I didn't realize that other langs would have a usleep built in. Oh well. \$\endgroup\$ – Joel Berger Mar 28 '12 at 15:42
  • \$\begingroup\$ some suggestions, you can remove the space after each x and the block form of map will save a few. \$\endgroup\$ – Joel Berger Mar 28 '12 at 16:02
1
\$\begingroup\$

JavaScript 180 218 chars

Production Version:

function f(){i--&&(i>50?h=h.substr(1):h=h.replace(" ",i==16?"&nbsp;":""),document.body.innerHTML="<pre>"+h.substr(0,50)+"</pre>",setTimeout(f,99))}h=(new Array(50)).join(" ")+"HelloWorld".split("").join("   "),i=80,f()

Ungolfed Version:

h=new Array(50).join(" ")+("HelloWorld".split("").join("   "));
i=80;

function f(){
        if(i--){
            if(i>50){
                h=h.substr(1);
            }else{
                h=h.replace(" ",(i==16)?"&nbsp;":"");
            }
            document.body.innerHTML="<pre>"+h.substr(0,50)+"</pre>";
            setTimeout(f,99);
        }
}
f();​

Here is a jsFiddle Demo

Note: if you try to reproduce it, make sure the code is below the body

\$\endgroup\$
  • \$\begingroup\$ I can't tell from the demo, does it "stack up" on the left side, or simply make it to the left and then show the final string? Howard's definitely works if you are unsure. \$\endgroup\$ – Joel Berger Mar 27 '12 at 4:21
  • \$\begingroup\$ @JoelBerger the hello world have 4 spaces between each letter, when the h is the first character, these spaces are removed. This demo is slower jsfiddle.net/fYvg7/1 \$\endgroup\$ – ajax333221 Mar 27 '12 at 4:29
  • \$\begingroup\$ That's close, but you should remove each space individually. \$\endgroup\$ – Joel Berger Mar 27 '12 at 4:31
  • \$\begingroup\$ @JoelBerger Fixed \$\endgroup\$ – ajax333221 Mar 27 '12 at 6:12
  • \$\begingroup\$ Well I hate to be a nag, but one other problem: yours starts with all the letters showing rather than entering one-by-one on the right. \$\endgroup\$ – Joel Berger Mar 27 '12 at 16:02
1
\$\begingroup\$

Perl 5.13.2, 115 characters

$_=$"x9 .pop=~y/ /\0/r;s/./    $&/g;print(y/\0/ /r=~/(.{50})/,"\r"),select$.,$.,$.,.02while$|=s/ (\S)/$1 /g;print$/
  • Warning-clean.
  • Can be squeezed a bit by reducing the space between characters or the initial whitespace.
  • Requires Perl 5.13.2 or newer due to use of /r.
  • The substitution to NUL to preserve spaces is unambiguous since POSIX argv is not NUL-clean. However, the loop substitution will turn any other whitespace into nothing (eventually).

Credits:

\$\endgroup\$
  • \$\begingroup\$ I love the r flag, best addition to the language since state \$\endgroup\$ – Joel Berger Apr 8 '12 at 19:55
1
\$\begingroup\$

bash 234

w=$1
p(){
i=$1
s=$2
p=$((50+s*3-i))
((p<s+1)) && p=$((s+1));
((p<50)) && echo -en "[20;"${p}H$3"  ";
}
clear
for i in {0..99}
do
for s in $(seq 0 ${#w})
do
p $i $s ${w:s:1} 
done
sleep .1
echo -en "[20;1H  "
done
echo -en "\b\b$w\n"

Usage:

./marquee.sh "Hello, fine marquee world"

ungolfed:

#!/bin/bash
w=$1
p(){
    #si String index
    it=$1
    #it=iteration
    si=$2
    pos=$((50+(si*3)-it))
    ((pos<si+1 )) && pos=$((si+1));
    ((pos<50)) && echo -en "[20;"${pos}H$3"  ";
}
clear
for it in {0..99}
do
    for si in $(seq 0 ${#w})
    do
        p $it $si ${w:si:1} 
    done
    sleep .1
    echo -en "[20;1H   "
done
echo -en "[22;1H"
\$\endgroup\$
1
\$\begingroup\$

R, 319 characters

Following the philosophy of @Blazer example (d is the delay in sec):

f=function(x,n=50,d=0.2){
    s=strsplit(x,"")[[1]];i=1;l=length
    while (i<(n+l(s)-1)){
        if(i<=l(s))cat(rep(" ", n-i),s[1:i])
        else if((i<=n)&&(i>l(s)))cat(rep(" ", n-i),s[1:l(s)])
        else cat(paste(s[1:(i-n+1)],collapse=""),s[(i-n+2):l(s)])
        Sys.sleep(d);system("clear");i=i+1
    }
    cat(paste(s[1:l(s)],collapse=""))
}

Usage:

f("Hello World",n=20,d=0.2)
\$\endgroup\$
1
\$\begingroup\$

Perl: 144 133

$|=@s=(($")x50,map{$_,($")x4}@i=split//,pop);{$n=0;$s[$n]ne$_?last:$n++for@i;splice@s,$n,1;print"\r",@s[0..50];sleep.1;$n!=@i&&redo}

In order to get the sleep of <1s though you need to run as:

perl -MTime::HiRes=sleep scriptname 'string to print'

Since I won't declare myself winner I won't argue myself over what counts there or not (but I really can't have Ruby win this ;-) )

\$\endgroup\$
  • \$\begingroup\$ 4 more and it fits on a tweet :D \$\endgroup\$ – ajax333221 Mar 29 '12 at 4:21
  • \$\begingroup\$ 4 characters right here: s/' '/$"/g and s/shift/pop/ \$\endgroup\$ – ephemient Mar 29 '12 at 17:53
  • \$\begingroup\$ yeah, I had included those, along with removing the push statement. I just hadn't posted it yet. \$\endgroup\$ – Joel Berger Mar 29 '12 at 20:35
0
\$\begingroup\$

Q,145

Doesn't exactly meet the requirements because the final line removes all the spaces that were in the original input string.

{c:2_'((!)(#)a)_'a:((l:3*(#)x)#" "),\(1_(,/)b,'x,'b:" ");{(-1 x;);system"sleep ",($)y}'[-1_c,(l-1)$d(!:)[d]except\(&)(^)d:((!)(#)q)!q:last c;y];}

It takes two arguments, input string and scroll speed

q){c:2_'((!)(#)a)_'a:((l:3*(#)x)#" "),\(1_(,/)b,'x,'b:" ");{(-1 x;);system"sleep ",($)y}'[-1_c,(l-1)$d(!:)[d]except\(&)(^)d:((!)(#)q)!q:last c;y];}["hello";0.05]
             h
            h
           h
          h  e
         h  e
        h  e
       h  e  l
      h  e  l
     h  e  l
    h  e  l  l
   h  e  l  l
  h  e  l  l
 h  e  l  l  o
h  e  l  l  o
h e  l  l  o
he  l  l  o
he l  l  o
hel  l  o
hel l  o
hell  o
hell o
hello
\$\endgroup\$
  • \$\begingroup\$ unfortunately thats an important point. I know that the Perl scripts could get very small without it. \$\endgroup\$ – Joel Berger Mar 29 '12 at 16:27
0
\$\begingroup\$

PowerShell, 135

Not very golfed and probably a horrible approach, but I'm sick and can't really think ...

for($x="`r"+' '*50;$y-ne$x){$y=$x
write-host($x=$x-replace' ([^ ])','$1 ')-n
if(!($t++%5)){$x=$x-replace'.$',"$args"[$i++]}sleep -m 99}
\$\endgroup\$
0
\$\begingroup\$

J (116)

s(echo@((50#LF)&,)@([[i.@]&2e7)@(50&{.)@;@:(([,~#&' '@])&.>))"1([-=&0@/:@\:@:~:&0)^:(i.>:+/k)k=.50,3#~<:#s=.>2{ARGV

Takes the input string on the command line, i.e. jconsole marquee.ijs 'Hello, world!'

If it doesn't need to clear the screen, i.e. output like this:

H  e  l  l  o
H e  l  l  o
He  l  l  o
He l  l  o
...

is allowed, it would be 12 characters shorter.

Explanation:

  • s.=>2{ARGV: get the string from the command line
  • k.=50,3#~<:#s: the starting amount of whitespace added before each character, 50 before the first one and 3 before all the others. (gives an array, '50 3 3 3...')
  • ([-=&0@/:@\:@~:&0): given an array, decrements the first nonzero item in the array
  • ^:(i.>:+/k): this function applied N times, where N is 0 upto the sum of the amount of added whitespace. (gives a matrix: 50 3 3 3; 49 3 3 3; 48 3 3 3; ... 0 0 0 1; 0 0 0 0).
  • "1: run following function on each row of the matrix
  • ;@:(([,~#&' '@])@.>): add the given amount of spaces before each character in the string
  • (50&{.): take the first 50 characters of the string
  • ([[i.@]&2e7): a function that generates the list from 0 to 2*10^7, and then throws it away. This takes about a third of a second on my machine, this causes the delay.
  • ((50#LF)&,): add 50 newlines before the string, to clear the screen
  • echo: output the string
  • s (...): give the string as the left argument to the function
\$\endgroup\$
0
\$\begingroup\$

APL (70)

{⎕SM∘←1,⍨1,⍨,/D{⍺,⍨⍵⍴⍕⍬}¨P←⍵-{⍵×~×⍺}\×⍵⊣⎕DL÷8⋄0∨.≠P:∇P}1↓⎕SD,1↓3⍴⍨⍴D←⍞

Takes input from the keyboard, the output is in the ⎕SM window (which would be the terminal if you had a text-based APL I guess). The window size is detected automatically, if you really want it to be 50 change the 1↓⎕SD to 50.

Explanation:

  • 1↓⎕SD,1↓3⍴⍨⍴D←⍞: read the string and store in D. Generate a vector describing how much whitespace to add before each character, which is the screen width before the first character (1↓⎕SD), and 3 before the others (1↓3⍴⍨⍴D).

  • ⎕DL÷8: wait 1/8th of a second

  • P←⍵-{⍵×~×⍺}\×⍵: in the vector in the right argument, subtract 1 from the leftmost nonzero item, and store the new vector in P.
  • ,/D{⍺,⍨⍵⍴⍕⍬}¨P: for each character in D, prefix the amount of whitespace given in P.
  • ⎕SM∘←1,⍨1,⍨: display on the screen, in the leftmost column of the top row
  • 0∨.≠P:∇P: if there is a nonzero element in P, repeat with P.
\$\endgroup\$
0
\$\begingroup\$

PowerShell, 129 bytes

for($x=' '*52+(($args|% t*y)-join' '*4);$x-match'  '){write-host "`r$(-join($x=$x-replace'(?<!  .*)  ')[0..50])  "-n
sleep -m 99}

Try it online!

This script does not remove spaces from the arguments in contrast to Joey's script.

TIO does not display the output correctly. With Powershell console, you get the scrolling marqueeline.

\$\endgroup\$
0
\$\begingroup\$

05AB1E, 42 bytes

ð¶:S3úJ46ú[D50£¶ð:D?IQ#ðõ.;“…¢('\r')“.eт.W

Try it online (without the sleep). NOTE: I don't have 05AB1E installed locally, so I'm not 100% sure if the \r trick works (in theory it should work, however). In TIO the \r are interpret as newlines instead. Also, the TIO uses the legacy version, because .e is disabled in the new TIO version (program is the same in both the legacy and new version of 05AB1E, though).

Explanation:

ð¶:            # Replace all spaces in the (implicit) input-string with newlines
   S           # Split the string to a list of characters
    3ú         # Pad each character with 3 leading spaces
      J        # Join the characters together again
       46ú     # And pad the entire string with an additional 46 leading spaces
[              # Now start an infinite loop:
 D             #  Duplicate the string
  50£          #  And leave only the first 50 characters of this copy as substring
     ¶ð:       #  Replace the newlines back to spaces
        D?     #  Duplicate the string, and print it without trailing newline
 IQ            #  If the current string is equal to the input:
   #           #   Stop the infinite loop
 ðõ.;          #  Replace the first space with an empty string to remove it
 “…¢('\r')“    #  Push dictionary string "print('\r')"
           .e  #  Evaluate it as Python code
 т.W           #  Sleep for 100 ms

See this 05AB1E tip of mine (section How to use the dictionary?) to understand why “…¢('\r')“ is "print('\r')".

\$\endgroup\$
0
\$\begingroup\$

Python, 139 bytes

import os;P='\n'
def f(x,w):
 v=k=P*w+P.join(x);o=str.replace
 while v!=x:os.system('sleep 1;clear');k=o(k,P,'',1);v=o(k,P,' ');print v[:w]

Has to call f('Hello World', 50) to start.

\$\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.