19
\$\begingroup\$

This challenge is inspired from this answer at the Ask Ubuntu Stack Exchange.

Intro

Remember the Windows ME screensaver with the pipes? Time to bring the nostalgia back!

enter image description here

Challenge

You should write a program or function which will output an ASCII representation of the screensaver. In the screensaver there should be a single pipe which will grow in semi-random directions.
The start of the pipe will be randomly placed at any of the borders of the screen and the pipe piece should be perpendicular to the border (corner first-pipes can either be horizontal or vertical). Each tick the pipe will grow in the direction it is facing (horizontal/vertical) at an 80% chance or take a corner at a 20% chance.

Pipe representation

To create the pipe, 6 unicode characters will be used

─    \u2500    horizontal pipe
│    \u2502    vertical pipe
┌    \u250C    upper left corner pipe
┐    \u2510    upper right corner pipe
└    \u2514    lower left corner pipe
┘    \u2518    lower right corner pipe

Input

The program / function will take 3 values of input, which can be gathered through function parameters or prompted to the user.

  • Amount of ticks
  • Screen width
  • Screen height

Amount of ticks

For every tick, a piece of pipe will be added to the screen. Pipes will overwrite old pipe pieces if they spawn at the same position.

For example, take a screen of size 3x3

ticks == 3
─┐ 
 ┘ 


ticks == 4
─┐ 
└┘ 


ticks == 5
│┐ 
└┘ 

Whenever a pipe exits the screen, as in the last example at 5 ticks, then a new pipe will spawn at a random border. For example:

ticks == 6
│┐ 
└┘ 
  ─

The new pipe should have a 50% chance of being horizontal or vertical.

Screen width/height

The screen width and height can be combined into a single value if that's preferrable in your language of choice. The screen width and height will always have a minimum value of 1 and a maximum value of 255. If your language of choice supports a console or output screen which is smaller than a 255x255 grid of characters then you may assume that the width and height will never exceed the boundaries of your console. (Example: Windows 80x25 cmd window)

Output

The output of your program/function should be printed to the screen, or returned from a function. For every run of your program, a different set of pipes should be generated.

Test cases

The following test cases are all random examples of valid outputs

f(4, 3, 3)
 │
─┘
  │

f(5, 3, 3)
 │
─┘┌
  │

f(6, 3, 3)
─│
─┘┌
  │

f(7, 3, 3)
──
─┘┌
  │

Obviously, the more ticks that have occured, the harder it becomes to prove the validity of your program. Hence, posting a gif of your output running will be preferred. If this is not possible, please post a version of your code which includes printing the output. Obviously, this will not count towards your score.

Rules

  • This is , shortest amount of bytes wins
  • Standard loopholes apply
  • If you use the unicode pipe characters in your source code, you may count them as a single byte

This is quite a hard challenge that can possibly be solved in many creative ways, you are encouraged to write an answer in a more verbose language even though there are already answers in short esolangs. This will create a catalog of shortest answers per language. Bonus upvotes for fancy coloured gifs ;)

Happy golfing!

Disclaimer: I am aware that Unicode characters aren't ASCII, but in lack of a better name I just call it ASCII art. Suggestions are welcome :)

\$\endgroup\$
17
  • 9
    \$\begingroup\$ The unicode characters that you want in the output are not ASCII. \$\endgroup\$
    – Wheat Wizard
    Commented Sep 16, 2016 at 13:42
  • 2
    \$\begingroup\$ I think this should be tagged ascii-art instead of graphical-output -- reference \$\endgroup\$ Commented Sep 16, 2016 at 14:00
  • 13
    \$\begingroup\$ Nostalgia and Windows ME don't fit well on the same line \$\endgroup\$
    – Luis Mendo
    Commented Sep 16, 2016 at 14:02
  • 1
    \$\begingroup\$ The 3D Pipes screensaver predated Windows ME. \$\endgroup\$
    – Neil
    Commented Sep 16, 2016 at 16:19
  • 1
    \$\begingroup\$ @Jordan I thought he meant tuples. \$\endgroup\$
    – KarlKastor
    Commented Sep 17, 2016 at 20:00

5 Answers 5

13
\$\begingroup\$

Nothing says nostalgia quite like...

QBasic, 332 bytes

INPUT t,w,h
RANDOMIZE
CLS
1b=INT(RND*4)
d=b
IF b MOD 2THEN c=(b-1)/2*(w-1)+1:r=1+INT(RND*h)ELSE c=1+INT(RND*w):r=b/2*(h-1)+1
WHILE t
LOCATE r,c
m=(b+d)MOD 4
IF b=d THEN x=8.5*m ELSE x=13*m+(1<((b MOD m*3)+m)MOD 5)
?CHR$(179+x);
r=r-(d-1)MOD 2
c=c-(d-2)MOD 2
b=d
d=(4+d+INT(RND*1.25-.125))MOD 4
t=t-1
IF(r<=h)*(c<=w)*r*c=0GOTO 1
WEND

QBasic is the right language for the task because:

  • Its encoding includes box drawing characters--no need for Unicode
  • LOCATE allows you to print to any location on the screen, overwriting what was there previously
  • Microsoft®

Specifics

This is golfed QBasic, written and tested on QB64 with autoformatting turned off. If you type/paste it into the actual QBasic IDE, it will add a bunch of spaces and expand ? into PRINT, but it should run exactly the same.

The program inputs three comma-separated values: ticks, width, and height. It then asks for a random-number seed. (If this behavior isn't acceptable, change the second line to RANDOMIZE TIMER for +6 bytes.) Finally, it draws the pipes to the screen.

The maximum dimensions that can be entered are 80 (width) by 25 (height). Giving a height of 25 will result in the bottom row getting cut off when QBasic says "Press any key to continue."

How?

TL;DR: A lot of math.

The current row and column are r and c; the current direction is d and the previous direction is b. Direction values 0-3 are down, right, up, left. Arithmetic translates those into the correct step values for r and c, as well as the correct edge coordinates to start on.

The box drawing characters │┐└─┘┌ are code points 179, 191, 192, 196, 217, and 218 in QBasic. Those appear pretty random, but it still used fewer characters to generate the numbers with some (pretty convoluted, I'm-not-sure-even-I-understand-it) math than to do a bunch of conditional statements.

The code for changing direction generates a random number between -0.125 and 1.125 and takes its floor. This gives -1 10% of the time, 0 80% of the time, and 1 10% of the time. We then add this to the current value of d, mod 4. Adding 0 keeps the current direction; adding +/-1 makes a turn.

As for control flow, the WHILE t ... WEND is the main loop; the section before it, starting with line number 1 (1b=INT(RND*4)), restarts the pipe at a random edge. Whenever r and c are outside the window, we GOTO 1.

Show me the GIF!

Here you go:

Pipes!

This was generated by a somewhat ungolfed version with animation, color, and an automatic random seed:

INPUT t, w, h
RANDOMIZE TIMER
CLS

restart:
' Calculate an edge to start from

b = INT(RND * 4)
'0: top edge (moving down)
'1: left edge (moving right)
'2: bottom edge (moving up)
'3: right edge (moving left)
d = b

' Calculate column and row for a random point on that edge
IF b MOD 2 THEN
    c = (b - 1) / 2 * (w - 1) + 1
    r = 1 + INT(RND * h)
ELSE
    c = 1 + INT(RND * w)
    r = b / 2 * (h - 1) + 1
END IF
COLOR INT(RND * 15) + 1

WHILE t
    ' Mathemagic to generate the correct box-drawing character
    m = (b + d) MOD 4
    IF b = d THEN
        x = 17 * m / 2
    ELSE
        x = 13 * m + (1 < ((b MOD m * 3) + m) MOD 5)
    END IF
    LOCATE r, c
    PRINT CHR$(179 + x);

    ' Update row and column
    r = r - (d - 1) MOD 2
    c = c - (d - 2) MOD 2
    ' Generate new direction (10% turn one way, 10% turn the other way,
    ' 80% go straight)
    b = d
    d = (4 + d + INT(RND * 1.25 - .125)) MOD 4

    ' Pause
    z = TIMER
    WHILE TIMER < z + 0.01
        IF z > TIMER THEN z = z - 86400
    WEND

    t = t - 1
    IF r > h OR c > w OR r = 0 OR c = 0 THEN GOTO restart
WEND
\$\endgroup\$
1
  • \$\begingroup\$ I've typed this into my MS-DOS v6.22 VM :-) \$\endgroup\$
    – Neil
    Commented Sep 24, 2016 at 19:16
9
\$\begingroup\$

JavaScript (ES6), 264 266 274 281

(t,w,h,r=n=>Math.random()*n|0,g=[...Array(h)].map(x=>Array(w).fill` `))=>((y=>{for(x=y;t--;d&1?y+=d-2:x+=d-1)x<w&y<h&&~x*~y?0:(d=r(4))&1?x=r(w,y=d&2?0:h-1):y=r(h,x=d?0:w-1),e=d,d=r(5)?d:2*r(2)-~d&3,g[y][x]="─└ ┌┐│┌  ┘─┐┘ └│"[e*4|d]})(w),g.map(x=>x.join``).join`
`)

Counting unicode drawing characters as 1 byte each. (As specified by OP)

Less golfed

(t,w,h)=>{
  r=n=>Math.random()*n|0; // integer range random function
  g=[...Array(h)].map(x=>Array(w).fill(' ')); // display grid
  for (x=y=w;t--;)
    x<w & y<h && ~x*~y||( // if passed boundary
      d = r(4), // select random direction
      d & 1? (x=r(w), y=d&2?0:h-1) : (y=r(h), x=d?0:w-1) // choose start position 
    ),
    e=d, d=r(5)?d:2*r(2)-~d&3, // change direction 20% of times
    g[y][x]="─└ ┌┐│┌  ┘─┐┘ └│"[e*4|d], // use char based on current+prev direction
    d&1 ? y+=d-2 : x+=d-1 // change x,y position based on direction
  return g.map(x=>x.join``).join`\n`
}

Animated test

Note: trying to keep the animation time under 30 sec,more thicks make animation pace faster

f=(t,w,h,r=n=>Math.random()*n|0,g=[...Array(h)].map(x=>Array(w).fill` `))=>
{
  z=[]
  for(x=y=w;t--;d&1?y+=d-2:x+=d-1)
    x<w&y<h&&~x*~y?0:(d=r(4))&1?x=r(w,y=d&2?0:h-1):y=r(h,x=d?0:w-1),
    e=d,d=r(5)?d:2*r(2)-~d&3,g[y][x]="─└ ┌┐│┌  ┘─┐┘ └│"[e*4|d],
    z.push(g.map(x=>x.join``).join`\n`)
  return z
}

function go() {
  B.disabled=true
  var [t,w,h]=I.value.match(/\d+/g)
  var r=f(+t,+w,+h)
  O.style.width = w+'ch';
  var step=0
  var animate =_=>{
    S.textContent = step
    var frame= r[step++]
    if (frame) O.textContent = frame,setTimeout(animate, 30000/t);
    else   B.disabled=false
  }
  
  animate()
}

go()
#O { border: 1px solid #000 }
Input - ticks,width,height
<input value='600,70,10' id=I><button id=B onclick='go()'>GO</button>
<span id=S></span>
<pre id=O></pre>

\$\endgroup\$
1
  • \$\begingroup\$ Just when I thought QBasic might actually win a golf challenge. ;) Have an upvote. \$\endgroup\$
    – DLosc
    Commented Sep 17, 2016 at 17:14
9
\$\begingroup\$

Python 2.7, 624 616 569 548 552 bytes

from random import*
from time import*
i=randint
z=lambda a,b:dict(zip(a,b))
c={'u':z('lur',u'┐│┌'),'d':z('ldr',u'┘│└'),'l':z('uld',u'└─┌'),'r':z('urd',u'┘─┐')}
m=z('udlr',[[0,-1],[0,1],[-1,0],[1,0]])
def f(e,t,w,h):
 seed(e);s=[w*[' ',]for _ in' '*h]
 while t>0:
  _=i(0,1);x,y=((i(0,w-1),i(0,1)*(h-1)),(i(0,1)*(w-1),i(0,h-1)))[_];o=('du'[y>0],'rl'[x>0])[_]
  while t>0:
   d=c[o].keys()[i(7,16)//8];s[y][x]=c[o][d];x+=m[d][0];y+=m[d][1];t-=1;sleep(.5);print'\n'.join([''.join(k)for k in s]);o=d
   if(x*y<0)+(x>=w)+(y>=h):break

The first parameter is a seed, same seeds will generate the same output, printing each step with a 500 ms delay.

  • -10 bytes thanks to @TuukkaX

repl it

Example run

f(5,6,3,3)

will output

   

 ─┐ 
   

──┐ 
   

┘─┐ 
   
┐  
┘─┐ 

verbose version

import random as r
from time import *
char={
'u':{'u':'│','l':'┐','r':'┌'},
'd':{'d':'│','l':'┘','r':'└'},
'l':{'u':'└','d':'┌','l':'─'},
'r':{'u':'┘','d':'┐','r':'─'}
}
move={'u':[0,-1],'d':[0,1],'l':[-1,0],'r':[1,0]}
def f(seed,steps,w,h):
 r.seed(seed)
 screen=[[' ',]*w for _ in ' '*h]
 while steps > 0:
  if r.randint(0,1):
   x,y=r.randint(0,w-1),r.randint(0,1)*(h-1)
   origin='du'[y>0]  
  else:
   x,y=r.randint(0,1)*(w-1),r.randint(0,h-1)
   origin = 'rl'[x>0]
  while steps > 0:
   direction = char[origin].keys()[r.randint(0,2)]
   screen[y][x]=char[origin][direction]
   x+=move[direction][0]
   y+=move[direction][1]
   steps-=1
   sleep(0.5)
   print '\n'.join([''.join(k) for k in screen]),''
   if x<0 or y<0 or x>=w or y>=h:
    break
   origin=direction
\$\endgroup\$
1
  • 1
    \$\begingroup\$ There's an useless whitespace at if x*y<0 or. 0.5 can be reduced to .5. import * could be import*. ''.join(k) for has an useless whitespace. You should also be able to keep dict in a variable and call it each time you use it. Haven't tested how much this saves, but by saving the dict(zip(a,b)) in a lambda that does the job for two strings (a, b), it should chop some. +1. \$\endgroup\$
    – Yytsi
    Commented Sep 16, 2016 at 19:53
7
\$\begingroup\$

C (GCC/linux), 402 353 352 302 300 298 296 288 bytes

#define R rand()%
x,y,w,h,r;main(c){srand(time(0));scanf(
"%d%d",&w,&h);for(printf("\e[2J");x%~w*
(y%~h)||(c=R 8,(r=R 4)&1?x=1+R w,y=r&2
?1:h:(y=1+R h,x=r&2?1:w));usleep('??'))
printf("\e[%dm\e[%d;%dH\342\224%c\e[H\n",
30+c,y,x,2*"@J_FHAF__L@HL_JA"[r*4|(r^=R 5
?0:1|R 4)]),x+=--r%2,y+=~-r++%2;}

Credit to edc65 for storing the direction in a single 4-bit number.

Reads a width/height on stdin before looping the screensaver forever. E.g.:

gcc -w golf.c && echo "25 25" | ./a.out

Or for a full-screen screensaver:

gcc -w golf.c && resize | sed 's/[^0-9]*//g' | ./a.out

For readability I added newlines. Requires a linux machine with a terminal respecting ANSI codes. Has colors! If you remove color support it costs 17 bytes less.

example

\$\endgroup\$
5
\$\begingroup\$

Ruby, 413 403 396 bytes

Ruby pipes

A function that takes a number of ticks and a width as input and returns the final screen as a string. Could no doubt be golfed more.

->t,w{k=[-1,0,1,0,-1]
b=(" "*w+$/)*w
f=->t,a=[[0,m=rand(w),2],[w-1,m,0],[m,0,1],[m,w-1,3]].sample{n,m,i=a
d=k[i,2]
q=->n,m,i{_,g,j=rand>0.2?[[1,0],[3,0],[0,1],[2,1]].assoc(i):"021322033132243140251350".chars.map(&:to_i).each_slice(3).select{|c,|c==i}.sample
v,u=k[j||=i,2]
y=n+v
x=m+u
[g,y,x,j]}
g,y,x,j=q[n,m,i]
b[n*w+n+m]="─│┌┐┘└"[g]
y>=0&&y<w&&x>=0&&x<w ?t>1?f[t-1,[y,x,j]]:b:f[t]}
f[t]}

See it on repl.it: https://repl.it/Db5h/4

In order to see it in action, insert the following after the line that begins b[n*w+n+m]=:

puts b; sleep 0.2

...then assign the lambda to a variable e.g. pipes=->... and call it like pipes[100,20] (for 100 ticks and a 20x20 screen).

Ungolfed & explanation

# Anonymous function
# t - Number of ticks
# w - Screen width
->t,w{
  # The cardinal directions ([y,x] vectors)
  # Up = k[0..1], Right = k[1..2] etc.
  k = [-1, 0, 1, 0, -1]

  # An empty screen as a string
  b = (" " * w + $/) * w

  # Main tick function (recursive)
  # t - The number of ticks remaining
  # a - The current position and vector index; if not given is generated randomly
  f = ->t,a=[[0,m=rand(w),2], [w-1,m,0], [m,0,1], [m,w-1,3]].sample{
    # Current row, column, and vector index
    n, m, i = a
    d = k[i,2] # Get vector by index

    # Function to get the next move based on the previous position (n,m) and direction (d)
    q = ->n,m,i{
      # Choose the next pipe (`g` for glyph) and get the subsequent vector index (j)
      _, g, j = (
        rand > 0.2 ?
          [[1,0], [3,0], [0,1], [2,1]].assoc(i) : # 80% of the time go straight
          "021322033132243140251350".chars.map(&:to_i).each_slice(3)
            .select{|c,|c==i}.sample
      )

      # Next vector (`v` for vertical, `u` for horizontal)
      # If straight, `j` will be nil so previous index `i` is used
      v, u = k[j||=i, 2]

      # Calculate next position
      y = n + v
      x = m + u

      # Return next glyph, position and vector index
      [g, y, x, j]
    }

    # Get next glyph, and subsequent position and vector index
    g, y, x, j = q[n, m, i]

    # Draw the glyph
    b[n * w + n + m] = "─│┌┐┘└"[g]

    # Check for out-of-bounds
    y >= 0 && y < w && x >=0 && x < w ?
      # In bounds; check number of ticks remaining
      t > 1 ?
        f[t-1, [y,x,j]] : # Ticks remain; start next iteration
        b : # No more ticks; return final screen

      # Out of bounds; repeat tick with new random start position
      f[t]
  }
  f[t]
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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