31
\$\begingroup\$

Introduction

This is one is pretty straightforward. We will be drawing a snake in ascii. This was inspired by that old snake game where you have to collect the fruit and you continuously grow.

Definition

Given a positive integer N that represents the snake's length, draw a snake so that it has a body of n plus a head and a tail.

Parts:

  • head: <, >, ^, v
  • tail: @
  • vertical: |
  • horizonal: -

All corners should be satisfied with a \ or / respectively. Unless the head ends on a corner in which case the head <, >, ^, v takes priority in the direction the snake is curled. i.e. for the length 1 example, it is turned counter clockwise and so the head is turned that way. For a clockwise solution it would be to the right >.

The snake must start in the middle with its tail but it may go outwards in any direction you choose either clockwise or counter-clockwise. It must also wrap tightly around itself as it expands outwards in a circular fashion.

Example

/--\
|/\|
||@|
|\-/
\--->

Where the @ is the tail and starting position. As seen above the tail starts in the middle, goes up to the left in a counter-clockwise rotation outward.

Here the length is 19 plus a tail and a head.

As another example, here is length 1:

<\
 @

Winning

This is code-golf so the answer that is submitted with the smallest number of bytes wins, with time to be used as a tie breaker.

Have fun!

\$\endgroup\$
19
  • 2
    \$\begingroup\$ It's not very clear that I'm not just allowed to draw a straight snake like @---->. You probably intend more strict conditions about the snake shape. Also make clear how much whitespace is or isn't allowed \$\endgroup\$
    – Ton Hospel
    Oct 4, 2016 at 11:57
  • 1
    \$\begingroup\$ "The snake must start in the middle with it's tail but it may go outwards in any direction you choose and either clockwise or counter-clockwise" \$\endgroup\$ Oct 4, 2016 at 11:59
  • 1
    \$\begingroup\$ So I say @ is the middle (possible add some spaces to make it so), declare "to the right" to be the direction and make just the head point down and declare that clockwise. Your terms may seem clear to you, but they are actually ambiguous. I realize you probably mean an as tightly as possible coiled snake, but you should make that clear \$\endgroup\$
    – Ton Hospel
    Oct 4, 2016 at 12:10
  • 1
    \$\begingroup\$ Don't worry. That one is a lot harder due to the offsets in that challenge. \$\endgroup\$ Oct 4, 2016 at 14:07
  • 2
    \$\begingroup\$ Nice first challenge! Welcome to the site! \$\endgroup\$
    – Luis Mendo
    Oct 4, 2016 at 18:14

7 Answers 7

10
\$\begingroup\$

MATL, 85 83 bytes

And I thought that having a spiral builtin would make for short code...

Oli2+XH:UQXItH:+XJh(YsXKoQ3I(4J(5l(H:)0HX^XkU(6H(3M1YL3X!P)'|-\/@' '<v>^'KHq))h0hw)

Try it online!

Explanation

Let N denote the input. We will create a vector of length ceil(sqrt(N+2))^2, that is, the smallest perfect square that equals or exceeds N+2. This vector will be filled with numeric values, rolled into a spiral (that's why its length needs to be a perfect square), and then the numeric values will be replaced by characters.

Let n denote each step starting from 1 at the center of the spiral. The steps where the snake turns are given by n2+1 (that is: 2, 5, 10, ...) for \ symbols and n2+n+1 (that is: 3, 7, 13, ...) for /. The steps between a \ and a / should be -, and those between a / and a \ should be |.

The vector is created such that it contains 1 at the turn points (2,3,5,7,10,13...) and 0 at the rest. The parity of the cumulative sum tells if each entry should be a - or a |. Adding 1 to this result we get a vector containing 1 (for |) or 2 (for -). But this makes the turn points themselves become 1 or 2 too. So the turn points, whose positions we know, are overwritten: positions n2+1 are filled with 3 and positions n2+n+1 are filled with 4. The tail and head are also special cases: the first element of the vector (tail) is set to 5, and the element with index N+2 (head) is set to 6. Finally, elements with indices exceeding N+2 are set to 0.

Taking input N=19 as an example, we now have a vector with length 25:

5 3 4 1 3 2 4 1 1 3 2 2 4 1 1 1 3 2 2 2 6 0 0 0 0

We need to roll this vector into a spiral. For this we use a builtin function that generates a spiral matrix, followed by a reflection and a transposition to produce:

13 12 11 10 25
14  3  2  9 24
15  4  1  8 23
16  5  6  7 22
17 18 19 20 21 

Indexing the vector with the matrix gives

4 2 2 3 0
1 4 3 1 0
1 1 5 1 0
1 3 2 4 0
3 2 2 2 6

where 0 corresponds to space, 1 corresponds to |, 2to -, 3 to \, 4 to /, 5 to @, and 6 to the head.

To know which of the four characters ^, <, v, or > the head should have, we use the cumulative sum of turn points that we previously computed. Specifically, the second-last value of this cumulative sum (i.e. the N+1-th value) modulo 4 tells us which character should be used for the head. We take the second-last value of the cumulative sum, not the last, because of the requirement "if the head ends on a corner the head <, >, ^, v takes priority in the direction the snake is curled". For the N=19 example the head is >.

Now we can build a string containing all the snake characters, including the appropriate character for the head at the sixth position: '|-\/@> '. We then index this string with the above matrix (indexing is 1-based and modular, so space goes last), which gives

/--\ 
|/\| 
||@| 
|\-/ 
\--->
\$\endgroup\$
1
  • 1
    \$\begingroup\$ awesome job! thanks for participating! \$\endgroup\$ Oct 4, 2016 at 17:42
8
\$\begingroup\$

Python 2, 250 233 191 bytes

n=input()
l=[''],
a=x=0
b='@'
while a<=n:x+=1;l+=b,;l=zip(*l[::-1]);m=x%2;b='\/'[m]+x/2*'-|'[m];k=len(b);a+=k
l+=b[:n-a]+'>v'[m]+' '*(k-n+a-1),
if m:l=zip(*l[::-1])
for i in l:print''.join(i)
  • Saved 39 bytes thanks to @JonathanAllan

repl.it

Draw the snake by rotating the entire snake 90º clockwise and adding the bottom segment, this way the snake will always be anticlockwise.
The new segment will always start with \ and have - as body for even sides and / - for odd sides. The segments sizes (without corners) are 0,1,1,2,2,3... which is floor(side/2).
If the segment is the last, it remove the excess characters, add the head and complete with spaces.

desired_size=input()
snake=[['']]
snake_size=side=0
new_segment='@'
while snake_size<=desired_size:
    side+=1
    snake+=[new_segment]
    snake=zip(*snake[::-1])
    odd_side=side%2
    new_segment='\/'[odd_side]+side/2*'-|'[odd_side]
    snake_size+=len(new_segment)
diff=desired_size-snake_size
snake+=[new_segment[:diff]+'>v'[odd_side]+' '*(len(new_segment)-diff-1)]
if odd_side:
    snake=zip(*snake[::-1])

for line in snake:print ''.join(line)
\$\endgroup\$
9
  • \$\begingroup\$ Nice Job! you have the tiebreaker win. Lets see what else people come up with. \$\endgroup\$ Oct 4, 2016 at 17:06
  • 2
    \$\begingroup\$ Surely this is the ideal language to solve this challenge in. \$\endgroup\$
    – Neil
    Oct 5, 2016 at 12:32
  • \$\begingroup\$ +1. Only glitch is that when the head is on a corner it is meant to point straight, not around the corner. \$\endgroup\$ Oct 5, 2016 at 23:56
  • 1
    \$\begingroup\$ Save 16 bytes by indexing into strings like so: '\/'[m], '-|'[m] and '>v'[m] \$\endgroup\$ Oct 6, 2016 at 0:11
  • 1
    \$\begingroup\$ Save 1 more by removing the space between print and ''.join \$\endgroup\$ Oct 6, 2016 at 0:32
7
\$\begingroup\$

JavaScript (ES6), 193 201 203 215 220 224

Edit saved 4 bytes thx @Arnauld
Edit2 changed logic, not storing the current increments for x and y, just get them from the current direction
Edit3 having saved a few bytes, I decided to use them for a better management of the blank space
Edit4 8 bytes saved not following exactly the examples about the head direction - like other answers

The current version works with Chrome, Firefox and MS Edge

This answer gives some trailing and leading space (and blank lines).

n=>(t=>{for(x=y=-~Math.sqrt(++n)>>1,g=[i=t];(g[y]=g[y]||Array(x).fill` `)[x]='^<v>|-/\\@'[t?n?i-t?4+t%2:x-y?7:6:t%4:8],n--;i=i>1?i-2:++t)d=t&2,t&1?x+=d-1:y+=d-1})(0)||g.map(x=>x.join``).join`
`

Slightly less golfed

n=>
{
  g = [],
  // to save a few bytes, change line below (adds a lot of spaces)
  // w = ++n,
  w = -~Math.sqrt(++n)
  x = y = w>>1,
  s=c=>(g[y] = g[y] || Array(x).fill(' '))[x] = c, // function to set char in position
  s('@'); // place tail
  for (
     i = t = 0; // t increases at each turn, t%4 is the current direction
     n--;
     i = i > 0 ? i - 2 : t++ // side length increases every 2 turns
  )
     d = t & 2,
     t & 1 ? x += d-1: y += d-1
     s(!n ? '^<v>' [t % 4] // head
          : '|-/\\' [i > 0 ? t % 2 : x-y ? 3 : 2]) // body
  return g.map(x=>x.join``).join`\n`
}

f=
n=>(t=>{for(x=y=-~Math.sqrt(++n)>>1,g=[i=t];(g[y]=g[y]||Array(x).fill` `)[x]='^<v>|-/\\@'[t?n?i-t?4+t%2:x-y?7:6:t%4:8],n--;i=i>1?i-2:++t)d=t&2,t&1?x+=d-1:y+=d-1})(0)||g.map(x=>x.join``).join`
`

function update() {
  O.textContent=f(+I.value);
}

update()
<input type=number id=I value=19 oninput='update()' 
 onkeyup='update() /* stupid MS browser, no oninput for up/down keys */'>
<pre id=O>

\$\endgroup\$
8
  • \$\begingroup\$ You can save a few bytes by replacing (' ') with ` ` and ('@') with `@` \$\endgroup\$
    – Arnauld
    Oct 4, 2016 at 22:03
  • \$\begingroup\$ @Arnauld Array(2).fill` ` ==> [ Array[1], Array[1] ], while Array(2).fill(' ') ==> [' ',' '] \$\endgroup\$
    – rgajrawala
    Oct 4, 2016 at 22:05
  • \$\begingroup\$ @usandfriends - True. But that should not make any difference once join'ed. \$\endgroup\$
    – Arnauld
    Oct 4, 2016 at 22:13
  • \$\begingroup\$ @Arnauld at first I agreed with usandfriends, but it works indeed. Thanks \$\endgroup\$
    – edc65
    Oct 5, 2016 at 6:45
  • \$\begingroup\$ @TravisJ It doesn't work in Chrome, but Firefox seems to work. \$\endgroup\$
    – Adnan
    Oct 5, 2016 at 9:10
3
\$\begingroup\$

JavaScript (ES7), 200 bytes

(n,s=(n*4+1)**.5|0,i=+`1201`[s%4],d=i=>`-`.repeat(i))=>[...Array(s-2>>2)].reduce(s=>`/-${d(i)}\\
${s.replace(/^|$/gm,`|`)}
|\\${d(i,i+=2)}/`,[`/\\
|@`,`/-\\
|@/`,`@`,`/@`][s%4])+`
\\${d(n-(s*s>>2))}>`

ES6 version for ease of testing:

f=(n,s=Math.sqrt((n*4+1))|0,i=+`1201`[s%4],d=i=>`-`.repeat(i))=>[...Array(s-2>>2)].reduce(s=>`/-${d(i)}\\
${s.replace(/^|$/gm,`|`)}
|\\${d(i,i+=2)}/`,[`/\\
|@`,`/-\\
|@/`,`@`,`/@`][s%4])+`
\\${d(n-(s*s>>2))}>`;
<input type=number min=1 oninput=o.textContent=f(this.value)><pre id=o>

\$\endgroup\$
1
  • \$\begingroup\$ Interesting implementation. I had not thought of doing that. Thanks for contributing and nice job!! \$\endgroup\$ Oct 6, 2016 at 11:09
3
\$\begingroup\$

Perl, 111 110 bytes

Includes +1 for -p

Give size on STDIN

snake.pl:

#!/usr/bin/perl -p
s%> %->%+s%\^ %/>%||s/
/  
/g+s%.%!s/.$//mg<//&&join"",//g,$/%seg+s/ /^/+y%/\\|>-%\\/\-|%for($\="/
\@
")x$_}{
\$\endgroup\$
1
  • \$\begingroup\$ Awesome! Nice Job! Thanks for contributing! \$\endgroup\$ Oct 11, 2016 at 11:19
0
\$\begingroup\$

Batch, 563 bytes

@echo off
if %1==1 echo /@&echo v&exit/b
set w=1
:l
set/ah=w,a=w*w+w
if %a% gtr %1 goto g
set/aw+=1,a=w*w
if %a% leq %1 goto l
:g
call:d
set r=/%r%\
set/ae=h%%2,w=%1-h*w+2
for /l %%i in (1,1,%h%)do call:r
call:d
echo \%r%^>
exit/b
:d
set r=
for /l %%i in (3,1,%w%)do call set r=%%r%%-
exit/b
:r
echo %r:!=^|%
if %e%==0 set r=%r:@!=\/%
set r=%r:@/=\/%
set r=%r:!\=\-%
set r=%r:/@=\/%
set r=%r:/!=-/%
set r=%r:@!=\/%
set r=%r:/\=!@%
set r=%r:/-=!/%
if %e%==1 set r=%r:/\=@!%
set r=%r:/\=@/%
set r=%r:-\=\!%
if %e%==1 set r=%r:/\=/@%

Explanation: Special-cases 1 as the rest of the code requires a snake width of at least two. Next, calculates the largest quarter square (either an exact square or a rectangle 1 wider than it is high) whose area is less than the length of the snake. The snake will be coiled into this rectangle starting at the bottom left corner and ending with the tail in the middle, and the remaining length will run under the bottom of the rectangle. The rectangle is actually generated from simple string replacements; most of the time each line is generated from the previous line by moving the diagonals 1 step, but obviously the tail also has to be dealt with, and there are slight differences depending on whether the height of the rectangle is even or odd.

\$\endgroup\$
1
  • \$\begingroup\$ Awesome! Thanks for contributing! \$\endgroup\$ Oct 11, 2016 at 11:18
-1
\$\begingroup\$

Python 2.7, A WHOPPING 1230 bytes

I am new to python and code golf but I felt I had to answer my own question and sulk away in shame after the fact. A lot of fun working on it though!

def s(n):
x = []
l = 0
if n % 2 == 1:
    l = n
else:
    l = n + 1
if l < 3:
    l = 3
y = []
matrix = [[' ' for x in range(l)] for y in range(l)] 
slash = '\\'
newx = l/2
newy = l/2
matrix[l/2][l/2] = '@'
newx = newx-1
matrix[newx][newy] = slash
#newx = newx-1
dir = 'West'

for i in range(0, n-1):    
    newx = xloc(newx, dir)
    newy = yloc(newy, dir)
    sdir = dir
    dir = cd(matrix, newx, newy, dir)
    edir = dir

    if (sdir == 'West' or sdir == 'East') and sdir != edir:
        matrix[newx][newy] = '/'
    else:
        if (sdir == 'North' or sdir == 'South') and sdir != edir:
            matrix[newx][newy] = '\\'
        else:
            if dir == 'East' or dir == 'West':
                matrix[newx][newy] = '-'
            else:
                matrix[newx][newy] = '|'
newx = xloc(newx, dir)
newy = yloc(newy, dir)
sdir = dir
dir = cd(matrix, newx, newy, dir)
edir = dir
print 'eDir: ' + dir
if dir == 'North':
    matrix[newx][newy] = '^'
if dir == 'South':
     matrix[newx][newy] = 'v'
if dir == 'East':
     matrix[newx][newy] = '>'
if dir == 'West':
     matrix[newx][newy] = '<'    


p(matrix, l)

def cd(matrix, x, y, dir):    
if dir == 'North':
    if matrix[x][y-1] == ' ':
        return 'West'
if dir == 'West':
    if matrix[x+1][y] == ' ':
        return 'South'
if dir == 'South':
    if matrix[x][y+1] == ' ':    
        return 'East'
if dir == 'East':
    if matrix[x-1][y] == ' ':        
        return 'North'
return dir

def p(a, n):
for i in range(0, n):
    for k in range(0, n):
        print a[i][k],
    print ' '

def xloc(x, dir):
if dir == 'North':
    return x -1
if dir == 'West':
    return x
if dir == 'East':
    return x 
if dir == 'South':
    return x + 1
 def yloc(y, dir):
if dir == 'North':
    return y
if dir == 'West':
    return y - 1
if dir == 'East':
    return y + 1
if dir == 'South':
    return y

s(25)

https://repl.it/Dpoy

\$\endgroup\$
1
  • 6
    \$\begingroup\$ This can massively be reduced just by removing unnecessary spaces, newlines, comments, functions, etc.. \$\endgroup\$ Oct 8, 2016 at 0:12

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.