Recursive ASCII Spirals

21

This competition is over. Thanks for the interesting non-esolang entries, and congrats to Jakuje for his winning JavaScript submission.

In the great tradition of ASCII Art Challenges on this site, here's another one. Given an input, draw a spiral.

&>----v
||>--v|
|||>v||
|||@|||
||^-<||
|^---<|
^-----<

Simple, yeah? Heh, heh, heh ... Yeah...

(Inspired by the ASCII Dragons Curve post, and Optimizer's ASCII Art of the Day posts)

Input

Input will be in the form of a series of parameters, taken from the usual STDIN/function argument/etc., whatever your language equivalent, comprised of four parts. These parts can be four separate arguments, a quadruple, an array of size 4, etc. For simplicity and consistency throughout the challenge, I will represent the input as a single word.

  • An integer 2 ≤ x ≤ 20 that specifies the size of the spiral in terms of "squares" with each printed character representing one "square" in size. Theoretically this could be enormous in scope, but given that we're drawing ASCII art, a safe upper limit on this will be 20 so that it fits somewhat decently on screen.
  • A single letter of d u r or l, indicating the initial movement from the starting "square" (down, up, right, left).
  • An optional c, indicating "counter-clockwise." If the c is omitted, assume clockwise rotation for the spiral.
  • A final integer 1 ≤ y ≤ 10 that specifies how many times to recurse the spiral drawing, using the finish "square" of the previous spiral as the starting "square" of the new one. I'm picking an upper limit of 10 because I want the drawing to finish at some point.
  • A few example inputs: 20lc5 13d2 2rc1

Of interest, do note that odd values for the size input will result in the @ always being the exact center of a spiral, but even values may have the starting "square" offset in any of the four diagonal directions, dependent upon the direction of initial travel. This can result in some ... interesting ... patterns. See the two even examples below.

Input that doesn't follow the input specification (e.g., 11q#s) is undefined and I fully expect the program to barf appropriately. :)

Output

Output is an ASCII printable output via language-equivalent STDOUT, with the following specifications:

  • The starting "square" (of each recursion) must be marked with an at-sign @.
  • The final "square" must be marked with an ampersand &. In the case of multiple recursions, only the very final "square" should be marked &.
  • Corners of the spiral path need to "point" in the direction of travel, using < > v ^.
  • Vertical travel need to be drawn by pipes |.
  • Horizontal travel needs to be drawn with dashes -.
  • "Squares" that are overwritten by later recursions should display the most-recent direction of travel. This will result in "newer" recursions seeming to be layered on top of the "older" recursions. See the 4rc3 example below.
  • A final trailing newline is OK, leading spaces could be a must and so are allowed, but trailing spaces are not allowed.
  • I won't dock you if you use escape sequences to draw the ASCII art going to STDOUT, but I will be silently disappointed in you. (You'll still be eligible for the bounty if you use them)

Examples

2d4 = diameter of 2, starts by going down, clockwise, 4 recursions

&@@@@
^<<<<

In this example, the drawing starts in the upper-right @, goes down one, left one, up one. At this point, we've finished the 2d portion, and so start the 2nd recursion, so we have another @, down one, left one, up one; then the 3rd recursion; then the 4th and finally our &.

4rc3 = diameter of 4, starts by going right, counter-clockwise, 3 recursions

&--<
v-<|
|@^|<
>--^|
 |@^|<
 >--^|
  |@^|
  >--^

In this example, the drawing starts in the bottom @, goes right one, up one, spirals around, until it reaches the middle @ and finishes the 4rc portion. This then repeats two more times to get the full 3 recursions requested. Note that 4rc1 would be just the upper-left 4x4 block of this example.

7u1 = diameter of 7, starts by going up, clockwise, 1 recursion (note the is the same as the intro)

&>----v
||>--v|
|||>v||
|||@|||
||^-<||
|^---<|
^-----<

Winning & Restrictions

This is Code Golf, so smallest answer in bytes wins. Submissions should be in the usual form of program/function/CJam Code Block/etc. Standard Loophole Restrictions Apply. Professional driver on closed course. If irritation persists, discontinue use and consult your doctor.

AdmBorkBork

Posted 2015-09-01T13:50:32.017

Reputation: 41 581

3

The specifics are quite different, but just for reference, here's an earlier spiral drawing challenge: http://codegolf.stackexchange.com/questions/52494/print-an-ascii-spiral-in-olog-n-memory.

– Reto Koradi – 2015-09-01T14:12:47.353

2Nice challenge. +1 for "Professional driver on closed course" – jrich – 2015-09-01T16:23:06.887

3It asks for a ><> answer. – The_Basset_Hound – 2015-09-01T21:35:18.490

With 150 rep at stake, I think you should be clearer about escape sequences. Are they allowed or not? There's no middle point in code golf; we use whatever we can to make out code shorter. – Dennis – 2015-09-04T14:00:31.637

@Dennis Clarified. I meant the "silently judging" in jest, but I can see how it could have been confusing. – AdmBorkBork – 2015-09-04T15:57:52.047

2"C'mon, guys ... are you going to let Common Lisp win out? ;-)" That is the most hilarious reason for a bounty I ever saw. Thanks – coredump – 2015-09-04T17:22:56.263

the requirement with "counter-clockwise" is complicated to parse. but it looks like late for changes. – Jakuje – 2015-09-06T19:19:05.137

@Jakuje Note that the question is quite flexible about the format of inputs: "These parts can be four separate arguments, a quadruple, an array of size 4, etc.". I took advantage of this in my answer. Nice try with Lua, btw. – coredump – 2015-09-09T07:05:38.690

@coredump After writing the comment I realized it is not so complicated with limitation of recursion to 9. Looks like I will have to fight more :) – Jakuje – 2015-09-09T09:09:36.767

1I'm sitting here chuckling that Common Lisp and Lua are the two languages fighting for top spot on a code-golf question. :) – AdmBorkBork – 2015-09-09T13:31:34.560

1@TimmyD A duel of giants. Others are too scared to participate :-) – coredump – 2015-09-09T14:10:04.753

The first example input 22lc5 has 22 which is greater than 20. – mbomb007 – 2015-09-10T00:15:01.353

@The_Basset_Hound Upon further inspection, I'm not going to try to pump out a ><> answer. The necessary lack of trailing spaces will make it much more difficult. It'd still be a fun challenge, but I'm not going to spend the time on it trying to earn the bounty. If only trailing spaces were allowed, it would be so much easier... – mbomb007 – 2015-09-10T00:30:37.387

@mbomb007 Hah, thanks. I fixed the typo. Also, I explicitly didn't allow trailing spaces, for the precise reason you hit upon. – AdmBorkBork – 2015-09-10T13:05:16.263

aj, trailing spaces. I missed this requirement so I guess I have things to fix in the evening. – Jakuje – 2015-09-10T13:38:15.583

Answers

6

Javascript, 578, 575, 553, 478, 377 Bytes

After the beaten Lua I switched to some more compact language and moved competition to Javascript:

s=function(w,d,c,r){d="dlur".indexOf(d)
j=i=G=H=I=J=w*r;o=[];for(x=0;x<J*2;x++){o[x]=[]
for(y=0;y<J*2;)o[x][y++]=' '}for(;r--;){a=d;m=l=z=1;o[i][j]="@"
for(k=w*w-1;k--;){G=G<i?G:i;H=H>i?H:i;I=I<j?I:j;J=J>j?J:j
o[i+=(1-a)%2][j+=a?a-2:0]=l++==m?(a+=c=="c"?3:1,m+=z=!z,l=1,"v<^>"[a%=4]):k?"|-"[a%2]:"&"}}for(i=G;i<=H;)console.log(o[i++].slice(I,J+1).join("").replace(/\s+$/g,''))}

Algorithm is the same, but written in more more compact language so I managed to beat the evil Lisp :)

Edit: Some structural changes were needed to reach under Lisp again and to eliminate trailing whitespaces. But we are here again.

Edit2: Some abstractions taken into account to get under 500. Hope it will be enough :)

Edit3: Thanks @Timwi, the code is another 100 chars slimmer. I didn't update the explanation yet.

Tests (online version, tested in Chrome):

----| 2d4 |---
s.js:9 &@@@@
s.js:9 ^<<<<
ss.html:7 ----| 4rc3 |---
s.js:9 &--<
s.js:9 v-<|
s.js:9 |@^|<
s.js:9 >--^|
s.js:9  |@^|<
s.js:9  >--^|
s.js:9   |@^|
s.js:9   >--^
ss.html:9 ----| 7u1 |---
s.js:9 &>----v
s.js:9 ||>--v|
s.js:9 |||>v||
s.js:9 |||@|||
s.js:9 ||^-<||
s.js:9 |^---<|
s.js:9 ^-----<
ss.html:11 ----| 8r3 |---
s.js:9       >------v
s.js:9       |>----v|
s.js:9       ||>--v||
s.js:9       |||@v|||
s.js:9    >------v|||
s.js:9    |>----v|<||
s.js:9    ||>--v||-<|
s.js:9    |||@v|||--<
s.js:9 >------v|||
s.js:9 |>----v|<||
s.js:9 ||>--v||-<|
s.js:9 |||@v|||--<
s.js:9 ||^-<|||
s.js:9 |^---<||
s.js:9 ^-----<|
s.js:9 &------<
ss.html:13 ----| 8rc3 |---
s.js:9 &------<
s.js:9 v-----<|
s.js:9 |v---<||
s.js:9 ||v-<|||
s.js:9 |||@^|||--<
s.js:9 ||>--^||-<|
s.js:9 |>----^|<||
s.js:9 >------^|||
s.js:9    |||@^|||--<
s.js:9    ||>--^||-<|
s.js:9    |>----^|<||
s.js:9    >------^|||
s.js:9       |||@^|||
s.js:9       ||>--^||
s.js:9       |>----^|
s.js:9       >------^

And to be fair, there is fair explanation:

s = function(w, d, c, r) {
    // width, direction, "c" as counter-clockwise and number of repetition
    // transfer direction to internal numerical representation
    d=d=="d"?0:d=="u"?2:d=="l"?1:3;
    // output strings
    x="v<^>"
    y="|-"
    // this is size of our canvas. Could be smaller, but this is shorter
    M = w * r * 2;
    // fill canvas with spaces to have something to build upon
    o = [];
    for (i = 0; i < M; i++) {
        o[i] = [];
        for (j = 0; j < M; j++)
            o[i][j] = ' '
    }
    // i,j is starting position
    // G,H,I,J are current boundaries (maximum and minimum values of i and j during the time)
    j = i = G = H = I = J = M / 2
    for (q = 0; q < r; q++) { // number of repeats
        a = d; // reset original direction
        // m is the expected length of side
        // l counts the of current side length
        m = l = 1;
        z = 0; // counts occurrences of the length
        o[i][j] = "@" // write initial character
        for (k = w * w; k > 1; k--) { // cycle over the whole spiral
            // update boundaries
            G = G < i ? G : i;
            H = H > i ? H : i;
            I = I < j ? I : j;
            J = J > j ? J : j;
            // move to the next position according to direction
            i+=a<3?1-a:0;
            j+=a>0?a-2:0
            if (k == 2) // we reached the end
                o[i][j] = "&"
            else if (l == m) {
                // we reached the corner so we need to count next direction
                a=(c=="c"?a+3:a+1)%4;
                // and write according sign
                o[i][j]=x[a]
                // first occurrence of this length
                if (z == 0)
                    z = 1; // wait for finish of the other
                else {
                    m++; // increase length of side
                    z = 0 // wait again for the first one
                }
                l = 1 // start the side counter over
            } else {
                l++ // another part of this side
                // according side character
                o[i][j] = y[a%2]
            }
        }
    }
    // blow it all out
    for (i = G; i <= H; i++)
        console.log(o[i].slice(I, J + 1).join("").replace(/\s+$/g, ''))
}

Jakuje

Posted 2015-09-01T13:50:32.017

Reputation: 468

Very nice. With accordance to the rules and following your example, I decided to remove the &optional keyword (and a space) in order to save 10 bytes, which gives 576... evil laughter (well, you said you could golf a little more, so this should not be difficult to beat; until someone write a 60-bytes answer in Pyth, of course). – coredump – 2015-09-09T21:32:24.760

@coredump Challenge accepted :) It is more difficult than I expected, but still possible :) I believe you can do it in pyth, but nobody will ever understand it so I believe the complexity is over the possibilities of such language. – Jakuje – 2015-09-09T21:57:32.013

3If you chain the assignments i=M/2;j=i;G=i;H=i;I=i;J=i; into i=j=G=H=I=J=M/2; and m=1;l=1; into m=l=1; you can save 12 bytes – SLuck49 – 2015-09-10T12:40:22.500

Thanks for comment. I will improve it. Lua didn't support it and I was too lazy and tired yesterday :) – Jakuje – 2015-09-10T12:49:09.773

That will be hard to beat. Well done :-) – coredump – 2015-09-10T21:26:41.013

2

This solution is pretty clever. However, I found several more places that can be golfed: 377 bytes

– Timwi – 2015-09-11T03:21:55.247

Thanks. I think I still have things to learn :) This was still my first code-golf in javascript. – Jakuje – 2015-09-11T06:44:14.537

@Jakuje: Definitely keep going, you’re doing very well for a beginner! :) – Timwi – 2015-09-11T12:21:01.917

1@Jakuje I believe it was Timwi's intention for you to take the 377 byte version and edit your answer to use it. ;) (Otherwise he would have just posted a separate answer.) – Martin Ender – 2015-09-11T12:51:37.933

7

Common Lisp, 649 617 605 586 576 565 554 527 518

(lambda(r v z c &aux(m 0)s(c(if c 1 -1))o p(x 0)i(y 0)j u)(#8=dotimes(_ z)(#7=setf p(aref"ruld"v)i(1-(abs(- p 2)))j(- 1(abs(1- p)))s'@)(#8#($(1- r))#5=(#7#m(min m x)o(cons`(,x,y,s)o)s(aref"-|-|"p)x(+ x i)y(+ y j))#2=(#7#s(*(- c)j)j(* i c)i s p(mod(+ p c)4)s(aref">^<v"p)u(#8#(_(1+ $))#5#))#2#))(#7#s'& u #5#o(#4=stable-sort(#4#o'< :key'car)'> :key'cadr)y(cadar o)x m)(dolist(k o)(do()((>=(cadr k)y))(#7#y(1- y)x m)(terpri))(do()((<=(car k)x))#9=(incf x)(princ" "))(and(=(cadr k)y)(=(car k)x)#9#(princ(caddr k)))))

All tests still pass. The ungolfed function was also updated to reflect the changes, as were comments. I finally got rid of remove-duplicates in order to shorten the code, but now I don't know where to find more bytes. Well done Jakuje.

Examples

(funcall *fun* 8 #\r 3 nil)

      >------v
      |>----v|
      ||>--v||
      |||@v|||
   >------v|||
   |>----v|<||
   ||>--v||-<|
   |||@v|||--<
>------v|||
|>----v|<||
||>--v||-<|
|||@v|||--<
||^-<|||
|^---<||
^-----<|
&------<

(funcall *fun* 8 #\r 3 t) ;; counter-clockwise

&------<
v-----<|
|v---<||
||v-<|||
|||@^|||--<
||>--^||-<|
|>----^|<||
>------^|||
   |||@^|||--<
   ||>--^||-<|
   |>----^|<||
   >------^|||
      |||@^|||
      ||>--^||
      |>----^|
      >------^

(funcall *fun* 7 #\u 1 nil)

&>----v
||>--v|
|||>v||
|||@|||
||^-<||
|^---<|
^-----<

(funcall *fun* 2 #\d 4 nil)

&@@@@
^<<<<

See also 20lc10 (pastebin).

Ungolfed

There is no recursion involved here, just a basic Turtle graphics approach with loops:

  1. Draw the spirals in memory by storing (x y char) triples in a stack.
  2. Stable-sort elements according to y and x
  3. Iterate over that list while avoiding duplicates (previous traces) and print from top-left to bottom-right.
(lambda
    (r v z c
     &aux
       (m 0)       ; minimal x
       s           ; symbol to print (a character)
       (c          ; 1 if clockwise, -1 otherwise
        (if c
            1
            -1))
       o           ; stack of (x y char) traces
       p           ; position of s in ">^<v"
       i           ; move direction of x
       j           ; move direction of y
       (x 0)       ; position in x
       (y 0)       ; position in y
       u           ; auxiliary variable
       )
  ;; repeat for each recursive step
  (dotimes (_ z)
    ;; initialize spiral parameters
    (setf s '@            ; start spiral with @
          p (position v"ruld") ; initialize p according to input V

          ;; Set initial direction in I and J.
          i (1-(abs(- p 2))) ; i(0..3) = ( 1, 0, -1, 0 )
          j (- 1(abs(1- p))) ; j(0..3) = ( 0, 1, 0, -1 )

    ;; Iterate with increasing diameter $. For each iteration, draw a
    ;; "L"-shape that extends previous shape. Here is roughly what
    ;; happens at each step:
    ;;
    ;;   3334
    ;;   3124
    ;;   3224
    ;;   4444
    ;;
    (dotimes($(1- r))

      ;;
      ;; Assign the form to the reader variable #1# in order to
      ;; copy-paste later. This is like declaring a local function,
      ;; but shorter: push trace into list O and move forward.
      ;;
      #1=(setf m (min m x)
               o (cons `(,x ,y ,s) o)
               s (aref "-|-|" p)
               x (+ x i)
               y (+ y j))

      ;;
      ;; Helper function #2#: rotate and draw a line of length $.
      ;;

      #2=(setf u (* (- c) j) ; rotation as a vectorial                   
               j (* i c)     ; product of (i j 0) with (0 0 c).
               u i           ; At first I used PSETF, but now I reuse
                             ; the existing SETF with an auxiliary 
                             ; variable U to shorten the code and get
                             ; rid of PROGN. That's also why I affect
                             ; the result of DOTIMES to U (no need
                             ; for two forms and a PROGN, just SETF).

               p (mod(+ p c)4)   ; ">^<v" is sorted counter-clockwise, which 
               s (aref ">^<v" p) ; means that adding P and C (modulo 4) gives 
                                 ; the next symbol after rotation.

               ;; trace a line by repeatedly invoking code snippet #1#
               u (dotimes(_(1+ $)) #1#))
      ;; 
      ;; Rotate and draw a second line, hence drawing a "L"-shape.
      ;;
      #2#))

  ;; Finally, draw the final symbol &
  (setf s '&)
  #1#

  (setf o

        ;; From inside-out:
        ;;
        ;; - stable sort O according to X
        ;;   (from lowest (left) to highest (right))
        ;;
        ;; - stable sort the result according to Y
        ;;   (from highest (top) to lowest (bottom))
        ;;
        (stable-sort (stable-sort o '< :key 'car) '> :key 'cadr)

        ;; initialize Y with the first Y in O, which is also the
        ;; minimum of all Y.
        y (cadar o)

        ;; initialize X with the minimum of all X
        x m) 

  ;; For each trace in O
  (dolist (k o)

    ;; Add as many lines as needed to advance Y to current trace's Y.
    (do ()
      ((<= y (cadr k)))
      (setf y (1- y)
            x m)
      (terpri))

    ;; Add as many spaces as needed to advance X to current trace's X.
    (do () ((>= x (car k))) (incf x) (princ " "))

    ;; Then, print current trace's character and increment X.
    ;; This happens only when we have a "new" trace, not another
    ;; trace at the same point (which was being overwritten).
    (and(=(car k)x)(=(cadr k)y)(incf x)(princ(caddr k)))

coredump

Posted 2015-09-01T13:50:32.017

Reputation: 6 292

4

Lua 5.2, 740 Bytes

s=io.read W=io.write Z=math.max A=math.min
D="d"U="u"L="l"R="r"function n()G=A(G,i)H=Z(H,i)I=A(I,j)J=Z(J,j)i=(a==D and i+1 or a==U and i-1 or i)j=(a==R and j+1 or a==L and j-1 or j)end
w=s"*n"d=s(1)c=s(1)r=(c=="c")and s"*n"or c
c=c=="c"M=w*(r+1)o={}for i=1,M do o[i]={}for j=1,M do o[i][j]=" "end end
i=M/2 j=i G=i H=i I=i J=i
for q=1,r do a=d m=1 l=1 z=0
o[i][j]="@"for k=3,w^2 do
n()if l==m then
a=c and(a==D and R or a==U and L or a==L and D or a==R and U)or(a==D and L or a==U and R or a==L and U or a==R and D)o[i][j]=(a==D and"v"or a==U and"^"or a==L and"<"or a==R and">")
if z==0 then z=1 else m=m+1;z=0 end
l=1
else
l=l+1
o[i][j]=(a==D or a==U)and"|"or"-"end end
n()o[i][j]="&"end
for i=G,H do for j=I,J do
W(o[i][j])end W("\n")end

I though that it would be fun to try to implement some algorithm to beat Lisp, but Lua is probably not the best option. I spend too much time on it, over-engineered some parts to end with this uggly one, but working solution. Probably I will try different language later to beat Lisp since there is some 90 characters that I can't take away from this algorithm.

Testing outputs:

jakuje@E6430:/tmp$ echo "2d4" | lua s.lua 
&@@@@
^<<<<
jakuje@E6430:/tmp$ echo "4rc3" | lua s.lua 
&--<  
v-<|  
|@^|< 
>--^| 
 |@^|<
 >--^|
  |@^|
  >--^
jakuje@E6430:/tmp$ echo "7u1" | lua s.lua 
&>----v
||>--v|
|||>v||
|||@|||
||^-<||
|^---<|
^-----<

Jakuje

Posted 2015-09-01T13:50:32.017

Reputation: 468

2

PHP, 524 bytes

I arrived late at this party. My PHP solution is neither the smallest, nor the smartest. It just works.

$a=$argv;
$b=[['|','^',0,-1],['-','>',1,0],['|',v,0,1],['-','<',-1,$x=$y=$o=$p=$q=$r=0]];
for($t=$a[4];$t;$t--){$d=strpos(urdl,$a[2]);$c=$b[$d];$m[$y][$x]='@';
for($s=0;++$s<$a[1];){for($k=3;--$k;){for($i=$s;--$i;)
$m[$y+=$c[3]][$x+=$c[2]]=$c[0];$x+=$c[2];$y+=$c[3];$c=$b[$d=($d+($a[3]==c?3:1))%4];
$m[$y][$x]=$c[1];}$o=min($x,$o);$p=max($p,$x);$q=min($y,$q);$r=max($r,$y);}
for($i=$s;--$i;)$m[$y+=$c[3]][$x+=$c[2]]=$c[0];$m[$y][$x]='&';}
for($y=$q;$y<=$r;$y++){$l='';for($x=$o;$x<=$p;$x++)$l.=$m[$y][$x]?:' ';
echo rtrim($l)."\n";}

How to run it:

$ php -d error_reporting=0 recursive-ascii-spirals.php 4 r c 3
&--<
v-<|
|@^|<
>--^|
 |@^|<
 >--^|
  |@^|
  >--^
$ php -d error_reporting=0 recursive-ascii-spirals.php 7 u '' 1
&>----v
||>--v|
|||>v||
|||@|||
||^-<||
|^---<|
^-----<

The detailed version with tests, explanation and other goodies can be found on github.

axiac

Posted 2015-09-01T13:50:32.017

Reputation: 749