| Bytes | Lang | Time | Link |
|---|---|---|---|
| 195 | Python 3.8 prerelease | 230805T212937Z | Donat |
| 257 | AWK | 230817T142533Z | Marius_C |
| 276 | Java JDK | 230824T181610Z | Donat |
| 271 | BASH | 230828T095017Z | Ivan |
| 177 | JavaScript Node.js | 230821T204343Z | Donat |
| 289 | Haskell | 230818T174535Z | Donat |
| 190 | Perl 5 | 230817T070117Z | Donat |
| 247 | Python 3.8 | 230804T162400Z | CursorCo |
| 086 | 05AB1E | 230811T121922Z | Kevin Cr |
| nan | Python 3 | 230803T235855Z | anderium |
| 235 | Javascript | 230803T172940Z | ccprog |
| 244 | Perl 5 | 230805T150004Z | good old |
| 188 | JavaScript ES12 | 230804T175339Z | Arnauld |
| 213 | K ngn/k | 230803T095734Z | doug |
| 425 | Python3 | 230803T162027Z | Ajax1234 |
| 078 | Pyth | 230803T221935Z | CursorCo |
| 061 | Charcoal | 230803T153908Z | Neil |
Python 3.8 (pre-release), 239 209 205 204 199 197 196 195 bytes
def f(s,n):
i=n%-2;g="--.\n<-'>"[i:7-i]
while(i:=i+2)<n:m="--"*i;g=m+f"--.\n.-%s |\n'{m}-'"%g.replace("\n"," |\n| ");z=d=0;*a,=g
for c in s:d+=a[z]in".'";a[z]=c;z+=2*(1,n,-1,-n)[d%4]
return a
I was able to simplify again by a significant amount. Now the output is a simple array of characters (this is better than before).
The recursive function g makes an empty snake, function f fills in the given string s.
- minus 30 bytes by simplification of function
g - minus 4 bytes by inlining the function
g(now non-recursive) - minus 1 byte thanks to c--
- minus another 5 bytes thanks to c-- !
- minus 2 bytes by using f-string and formatting
- minus 1 byte thanks to c--
- minus 1 byte thanks to Jakque
Here are my older versions:
Python 3.8 (pre-release), 271 269 257 254 bytes
This new version (built from the 275 bytes solution) stretches the output rules to the maximum, or even beyond? The output array contains iterables as elements, either strings or arrays of strings. By "".join(e) we always get a string out of element e.
- Minus 2 bytes, thanks to an idea of noodle man!
- Minus 12 bytes by myself, using new algorithm for spiral
- Minus 3 bytes by myself - small improvement
g=lambda n:[(m:="-"*(2*n-3))+"-."]+[".|- "[i>0::2]+g(n-2)[i]+" |"for i in range(n-2)]+[f"'{m}'"]if n>2else["--.",">","<-'"][n<2::2]
def f(s,n):
z=d=0;a=g(n)
for c in s:l=list(a[z//n]);d+=l[z%n*2]in".'";l[z%n*2]=c;a[z//n]=l;z+=(1,n,-1,-n)[d%4]
return a
Python 3.8 (pre-release), 361 ... 275 bytes
- Minus 18 bytes, thanks to noodle man!
- Minus 18 bytes additionally by myself
- Minus 9 bytes by myself
- Minus 9 bytes by myself
- Minus 11 bytes by myself
- Minus 20 bytes by returning a 2 dimensional array, thanks to noodle man!
- Minus 1 byte by myself
g=lambda n:[(m:="-"*(2*n-3))+"-."]+[".|- "[i>0::2]+g(n-2)[i]+" |"for i in range(n-2)]+[f"'{m}'"]if n>2else["--.",">","<-'"][n<2::2]
def f(s,n):
x,y,k,a=0,0,7,[list(s)for s in g(n)]
for c in s:k=(7-5*(k>6),-5*(k>1),k)[".'".find(a[y][x])];a[y][x]=c;x+=k//3;y+=k%3-1
return a
Recursive, but not shortest, unfortunately.
Function g makes an empty snake, function f fills in the string.
AWK, 332 321 317 281 273 257 bytes
func R(C){r=++c<A*A?length($1)<c?i<A?C:S%2?"'":".":substr($1,c,1):S%2?"<":">";g[x,y]=S%2?G r:r G}func f(){for(x=c=S=i=0;i<A=$2;R(B="-",G=++i<A?B:""))y=i;while(S++<=A){for(i=S;i++<A;R("|",G=i<A?" ":B))x+=t=S%2*2-1;for(i=S;i++<A;R(B,G=i<A?B:""))y-=t}return G}
- -11 bytes, replaced
condition ? var = val1 : var = val2withvar = condition ? val1 : val2 - -4 bytes, replaced
functionwithfunc - -36 bytes, thanks to @ceilingcat. Chained
=, chained ternary operator and other optimization - -8 bytes, thanks to @ceilingcat
- -16 bytes, thanks to @ceilingcat. Passed ternary operator as second arg for
func R(C)and moved incrementation ofcin same function to allow more efficient one-liner loop.
Ungolfed
function result(string2)
{
rep = string2
if ( i == $2-1 ) step % 2 ? rep = "'" : rep = "."
if ( length($1) > cpt ) rep = substr($1, cpt+1, 1)
if ( cpt == ($2 * $2)-1 ) step % 2 ? rep = "<" : rep = ">"
return rep
}
function snakearound()
{
x = 0
y = 0
cpt = 0
step = 0
for(i = 0; i < $2; i++)
{
grid[x, y] = result("-")
if (i+1 < $2) grid[x, y] = grid[x, y] "-"
cpt++
y++
}
y--
while(step <= $2)
{
step++
step % 2 ? tmp = 1 : tmp = -1
for(i = step; i < $2; i++)
{
x += tmp
grid[x, y] = result("|")
i == $2-1 ? string = "-" : string = " "
step % 2 ? grid[x, y] = string grid[x, y] : grid[x, y] = grid[x, y] string
cpt++
}
for(i = step; i < $2; i++)
{
y -= tmp
grid[x, y] = result("-")
i == $2-1 ? string = "" : string = "-"
step % 2 ? grid[x, y] = string grid[x, y] : grid[x, y] = grid[x, y] string
cpt++
}
}
for(i = 0; i < $2; i++){
for(j = 0; j < $2; j++) printf("%s", grid[i, j])
printf("\n")
}
}
```
Java (JDK), 279 278 276 bytes
char[]f(String s,int n){var a=g(n).toCharArray();int z=0,d=0;for(var c:s.toCharArray()){d+=a[z]/2&1;a[z]=c;z+=(d%2<1?1:n)*(d%4<2?2:-2);}return a;}String g(int n){String m;return n>1?(m="--".repeat(n-2))+"--.\n"+(n>2?".-"+g(n-2).replace("\n"," |\n| ")+" |\n'"+m:"<")+"-'":">";}
I have derived this from my JavaScript answer. Java is not really good for code golf, but I think, the result is not too bad.
- minus 3 bytes, thanks to ceilingcat
BASH , 580 349 308 303 291 286 283 277 275 272 271 bytes
Found this very entertaining, thanks! First variant was 580 bytes, then 349, ..., now 271:
s=$1 n=$2 N=$[n+n-1];f(){ b=$1;for((j=1;j<n;j++));{((c<N))&&T+=%s;[[ $4 ]]&&h=$4 S[i+$2/2]=-;t=${s:c:1} S[i]=${t:-$b} b=$3;$[i+=$2,c++];S[i-($2/N)]=\ ;};};while((n>0));do f . 2 - \>;$[n-=a];f . $N \|;f \' -2 - \<;$[n--];f \' -$N \|;a=1;done;S[i]=$h;printf $T\\n "${S[@]}"
Squeezed to 271(with 2>/dev/null):
#!/bin/bash
s=$1 n=$2 N=$[n+n-1]
f(){
b=$1
for((j=1;j<n;j++)); {
((c<N))&&T+=%s
[[ $4 ]]&&h=$4 S[i+$2/2]=-
t=${s:c:1} S[i]=${t:-$b} b=$3
$[i+=$2,c++]
S[i-($2/N)]=\
}
}
while((n>0));do
f . 2 - \>; $[n-=a]
f . $N \|
f \' -2 - \<; $[n--]
f \' -$N \| ; a=1
done;S[i]=$h;printf $T\\n "${S[@]}"
First variant was:
#!/bin/bash
s=$1
n=$2
l=${#s}
N=$((n+n-1)) # items per row
# Check if n is in range
(( (n*n)>=(l+1) )) || { echo "n must be 'n^2≥l+1'"; exit 1; }
# Fill the 'area' with spaces
for ((i=0; i<n*N; i++)); { snake[$i]=" "; }
# Create printing template
printf -v row "%${N}s"
row=${row// /%s}
# Setcounters
i=0
c=0
a=0
forward(){
b='.'
for ((j=1; j<n; j++)); do
cut=${s:c:1}
snake[$i]=${cut:-$b}
snake[$((i+1))]="-"
b='-'
h='>'
((i+=2))
((c++))
done
((n-=a))
}
down(){
b='.'
for ((j=1; j<n; j++)); do
cut=${s:c:1}
snake[$i]=${cut:-$b}
b='|'
((i+=N))
((c++))
done
}
back(){
b="'"
for ((j=1; j<n; j++)); do
cut=${s:c:1}
snake[$i]=${cut:-$b}
snake[$((i-1))]="-"
h='<'
b='-'
((i-=2))
((c++))
done
((n--))
}
up(){
b="'"
for ((j=1; j<n; j++)); do
cut=${s:c:1}
snake[$i]=${cut:-$b}
b='|'
((i-=N))
((c++))
done
a=1
}
print(){
printf "$row\n" "${snake[@]}"
# echo $i,$c,$n
}
# Create snake
while ((n>0)); do
forward
down
back
up
done
snake[$i]=$h; printf "$row\n" "${snake[@]}"
JavaScript (Node.js), 193 192 190 188 187 182 177 bytes
s=>n=>s.map(c=>a[d*=/[.']/.test(a[z-=d])?d*d<5?n:-1/n:1,z]=c,d=z=-2,a=[...g(n)])&&a
g=(n,m)=>--n?(m="--".repeat(--n))+`--.
${n?`.-${g(n).split`
`.join` |
| `} |
'`+m:"<"}-'`:">"
Now even shorter than Arnauld's excellent answer!
- Minus 5 bytes thanks to noodle man!
- Minus 5 bytes by clever improvement thanks to Arnauld!
This answer has been derived from my Python solution.
Function g makes an empty snake and function f updates it with the chars from string s. The function has to be called by f(s,n) and returns a plain array of single char strings.
Haskell, 333 289 bytes
f s n=h s n 0 0$concatMap(++"\n").g$n
h(c:t)n d z m=take z l++c:drop(z+1)l where e=mod(d+sum[1|a<-".'",m!!z==a])4;l=h t n e(z+2*[1,n,-1,-n]!!e)m
h _ _ _ _ m=m
g 1=[">"]
g 2=["--.","<-'"]
g n=(m++"--."):(map(++" |").h)b++["'-"++m++"'"]where b=g(n-2);m=b>>"--";h(l:k)=[".-"++l]++map("| "++)k
Function g makes an empty snake as a list of strings. Function h fills in the given string s while converting the list of strings to a single string. Function f is the resulting target function.
In function h, d is the direction where to go for the next character of string s and z is the position of the character to replace.
Perl 5, 236 224 222 214 190 bytes
- minus 24 bytes, thanks to: good old time!
sub g{my$m="--"x($n=-2+pop);$m?"$m--.\n.-@{[g($n)=~s/\n/ |\n| /gr]} |\n'$m-'":$n?">":"--.\n<-'"}sub f{$a=g$p=pop;$z+=(1,$p,-1,-$p)[($#_+=(substr$a,2*$z,1,$_)=~/[.']/)%4]for$_[$z=0]=~/./g;$a}
I have derived this from my Python answer. Function g makes an empty snake, function f fills in the string.
Here is the same code with a little more spacing for better readability:
sub g{
my$m="--"x($n=-2+pop);
$m?"$m--.\n.-@{[ g($n)=~s/\n/ |\n| /gr ]} |\n'$m-'":$n?">":"--.\n<-'"
}
sub f{
$a=g$p=pop;
$z+=(1,$p,-1,-$p)[($#_+=(substr$a,2*$z,1,$_)=~/[.']/)%4] for $_[$z=0]=~/./g;
$a
}
Python 3.8, 254 247 bytes
lambda n,s:(r:=range,G:=["<>"[n%2]],[(K:=(s+''.join((2*[(n+~h//4)*"-",(n-2-h//4)*"|"],".''.")[h%2][h//2%4]for h in r(4*n))[len(s):])[~(f+1)**2:],G:=['-'.join(K[:f+1])]+[G[~g][::-1]+"- "[f-g>1]+K[f-~g]for g in r(f)])for f in r(1,n)])and'\n'.join(G)
Essentially a port of my Pyth answer.
I'll hand wave a bit of an explanation. We start by assigning G to the center of the spiral, n being even or odd determines if the arrow points left or right. Next we construct the string for the spiral as if s was an empty string, then replace the beginning of that string with s. We then build the rest of the square, each iteration we flip G 180 degrees and add to the top and the right side. After n-1 iterations of this, we return G joined on newlines.
05AB1E, 86 bytes
LR·<€D¬šIõ¹·nи«'-ýŽ9¦S.Λ¶¡ιн»¶«„ -‚Dð¶:«D'-'|::2äε„|-‚D'|„.'Nè::¹N+ÉiNi'''<.;ë¨'>«]J
Inputs in the order \$n,S\$, where \$S\$ is a list of characters.
Outputs with two trailing newlines. If this is not allowed, a trailing ¨ or ? could be added so it'll output a single trailing newline instead.
Try it online or verify all test cases.
Explanation:
Step 1: Prepare the input character-list by joining by - and adding additional trailing -. Then use the (modifiable) Canvas builtin to put this string itself and all spaces at the correct positions in the output rectangle:
LR·<€D¬š # Create the list of lengths for the Canvas builtin:
L # Push a list in the range [1, first (implicit) input-integer]
R # Reverse it to range [n,1]
· # Double each inner value
< # Decrease each by 1
€D # Duplicate each value within the list
¬š # And prepend an additional copy of the first/largest item
Iõ¹·nи«'-ý '# Create the string to draw for the Canvas builtin:
I # Push the second input-list of characters
¹·n # Push the first input, double it, take the square of that: 4n²
õ и # Create a list with that many empty strings ""
« # Append it to the input-list
'-ý '# Join this list with "-" delimiter
Ž9¦S # Create the list of directions for the Canvas builtin:
Ž9¦ # Push compressed integer 2460
S # Convert it to a list of digits: [2,4,6,0]
.Λ # Use the (modifiable) Canvas builtin with these three options
¶¡ιн» # Remove all (0-based) odd-indexed rows
¶¡ # Split it on newlines
ι # Uninterleave it into two parts
н # Pop and only leave the first part
» # Join it back together by newlines
For more information on how the Canvas builtin works, see this 05AB1E tip of mine.
See this 05AB1E tip of mine (section How to compress large integers?) to understand why Ž9¦ is 2460.
Step 2: Replace all - that are currently at edge or corner positions with |:
¶« # Append a trailing newline
„ - # Push string " -"
 # Bifurcate, short for Duplicate & Reverse copy: "- "
‚ # Pair them together: [" -","- "]
D # Duplicate it
ð¶: # Replace all spaces with newlines in the copy: ["\n-","-\n"]
« # Merge the two together: [" -","- ","\n-","-\n"]
D # Duplicate it
'-'|: # Replace all "-" with "|" in the copy: [" |","| ","\n|","|\n"]
: # Replace all [" -","- ","\n-","-\n"] respectively with
# [" |","| ","\n|","|\n"]
Try the first two steps online.
Step 3: Replace all | at corner positions with either . (for the top halve) or ' (for the bottom halve):
2ä # Split the string into two equal-sized parts
ε # Map over both parts:
„|- # Push string "|-"
‚ # Bifurcate and pair again: ["|-","-|"]
D # Duplicate it
'| : '# Replace all "|" with the following in the copy:
„.'Nè '# The map-index'th character of string ".'"
: # Replace all ["|-","-|"] respectively with
# [".-","-."] for the first iteration of the map
# or ["'-","-'"] for the second iteration of the map
Try the first three steps online.
Step 4: And finally, fix the > or <, and output the result:
i # If
¹N+ # the first input + the (0-based) map-index
É # is odd:
Ni # If it's the second iteration of the map:
'' '<.; '# Replace the first "'" with "<"
ë # Else (it's the first iteration of the map)
¨ # Remove the last character of the first part
'>« '# And append a ">" instead
] # Close both if-statements and the map
J # Join the two modified parts back together
# (after which the result is output implicitly with trailing newline)
Python 3, 371 274 255 and 253 bytes
def f(s,n):
for y in(a:=range(n)):
for x in a:r=min(x,y,n+~x,n+~y);i=4*r*(n-r)-2*r+x+y+2*(2*(n+~r)-x-y)*(y>x);H=max(n+~y,y)>x>min(y-2,n-y-2);b=x+1>n/2;print(s[i:]and s[i]or"|-..''><"[H+2*((n+2*~x)*b-~x==y)+4*((n+~x-x)*b+x==n+~y)],end=" -"[H])
print()
Minus 64 bytes thanks to @noodleman and another 11 bytes thanks to @Kevin Cruijssen! I then saved another 22 bytes. Most recent improvement of 6 bytes by @xnor and 13 bytes by me.
Instead of most other solutions (as far as I can tell), I tried calculating the index into the string. I dug up a post on StackOverflow that helped with this, though a lot was just throwing random equations and seeing what they do. Maybe the indexing equation can be simplified with a bit more thought, but it's surprisingly short already.
There are lots of places where -x-1 is replaced with +~x (or variants). Booleans are heavily used for indexing and cancelling parts of equations with multiplication.
Ungolfed:
def function(string: str, number: int):
for y in range(number):
for x in range(number):
rounds = min(x, y, number - x - 1, number - y - 1)
# total increments after `rounds` revolutions and fix for overcounting
index = 4 * rounds * (number - rounds) - 2 * rounds
index += x + y # east + south increment
index += 2 * (2 * (number - rounds - 1) - x - y) * (y > x) # west + north
# See below for explanation
is_horizontal = max(number - y - 1, y) > x > min(y - 2, number - y - 2)
is_right = x + 1 > number / 2
# Both inlined
is_up_corner = x + 1 + is_right * (number - 2 * (x + 1)) == y
is_down_corner = x + is_right * (number - 1 - 2 * x) == number - y - 1
# is_middle = x + 1 - number % 2 == y == number // 2
# is_middle = is_up_corner and is_down_corner
# is_down = y > number / 2
# is_left_corner = y + is_down * (number - 2 * y) == x + 1
# is_right_corner = y + is_down * (number - 1 - 2 * y) != number - x - 1
print(
# string[index] if index < len(string) else
string[index:] and string[index] or \
# See below for explanation
"|-..''><"[is_horizontal + 2 * is_up_corner + 4 * is_down_corner],
end=" -"[is_horizontal]
)
print()
Explanation of is_horizontal boolean:
There are effectively four quadrants in which we need to determine whether the coordinate is a horizontal part in the snake, we can do this for the upper right corner with number - y - 1 > x. On its own this is not enough, because the lower part of the snake is now messed up. Taking y > x works for the lower right quadrant, but messes up the snake above. Combining these two equations into max(number - y - 1, y) > x leaves only the left part of the snake to fix. Creating conditions for the other corners you can combine x > min(y - 2, number - y - 2) into one large expression.
------------. ------------. ------------.
.---------. | .---------. | .---------. |
--.-----. | | --.-----. | | | .-----. | |
----.-> | | | becomes ----.-> | | | becomes | | .-> | | |
----' | ' | | ----'---' | | | | '---' | |
--' | | | ' | --'-------' | | '-------' |
' | | | | | ' '-----------' '-----------'
Explanation of special character indexing into |-..''><:
There are six special characters that we need to index into, which can be separated into three categories: Straight, corner and tail. By virtue of the equations, we can determine we need a tail if something is both an up- and down-corner. Obviously, if it is not a corner or tail, it should be a straight piece. Using the equation is_up_corner + 2 * is_down_corner we can differentiate the four cases straight, up-corner, down-corner, and tail. To select the correct straight and tail pieces we still need a more granular distinction. Using is_horizontal obviously works for straight pieces and additionally it works for the tail, because the left-pointing arrow has an appendage whereas the right-pointing does not. This can be combined with the previous equation by doubling the corners.
253 bytes solution
This solution outputs not a string, but a nested array/matrix of characters. This might be allowed according to the meta post: Strings and arrays of characters are interchangeable.
It's effectively the same as the above, but also returns instead of prints. It unfortunately requires *''.join() in the inner loop, because you cannot populate an array with two items per iteration. (And by doubling the iterations I only managed to golf to 255.)
lambda s,n:[[*''.join((r:=min(x,y,n+~x,n+~y),i:=4*r*(n-r)-2*r+x+y+2*(2*(n+~r)-x-y)*(y>x),H:=max(n+~y,y)>x>min(y-2,n-y-2),b:=x+1>n/2,s[i:]and s[i]or"|-..''><"[H+2*((n+2*~x)*b-~x==y)+4*((n+~x-x)*b+x==n+~y)])[4]+" -"[H]for x in range(n))]for y in range(n)]
Javascript, 384 371 304 288 263 235 bytes
s=(S,n)=>(t=[...S],A=Array,a=A.from(A(n),r=>A(2*n).fill(e=' ')),x=y=0,p=(b,d,f)=>(a[y][x]=x&1&&t.shift()||b[d],f?.(d)),i=2*n-1,m=d=>(z=d&2,d&1?y+=1-z:x+=1-z,--i?p('-|-|',d,m):n<2?p('>><<',d):(i=d&1?2*--n:n-1,p("..''",++d%4,m))),m(0),a)
-13 bytes saved thanks to Samathingamajig
-67 bytes by folding functions u() and d() into v(±1) and l() and r() into h(±1)
-16 bytes with only one recursive function
-25 bytes because of this
-28 bytes with more golfing
Try out
s=(S,n)=>(t=[...S],A=Array,a=A.from(A(n),r=>A(2*n).fill(e=' ')),x=y=0,p=(b,d,f)=>(a[y][x]=x&1&&t.shift()||b[d],f?.(d)),i=2*n-1,m=d=>(z=d&2,d&1?y+=1-z:x+=1-z,--i?p('-|-|',d,m):n<2?p('>><<',d):(i=d&1?2*--n:n-1,p("..''",++d%4,m))),m(0),a)
;[
[ 'snake', 4 ],
[ 'snakearound', 5 ],
[ 'snakearoundandround', 6 ]
]
.forEach(([ S,n ]) => {
console.log(s(S,n).map(r=>r.join('')).join('\n'))
})
Ungolfed
s = (S,n) => {
t = [...S];
a = Array.from(Array(n), r => Array(2*n).fill(e=' '));
x = y = 0;
p = (b,d,f) => { // set letter in all odd positions until
// exhausted, otherwise use snake
a[y][x] = x&1 && t.shift() || b[d];
f?.(d); // recurse unless end
}
i = 2*n-1; // counter for going in the same direction
m = d => {
// the next position is set before p is called
z = d&2; // forward or back?
if (d&1) { // vertical or horizontal?
y+=1-z;
} else {
x+=1-z;
}
if (--i) { // go on
p('-|-|', d, m);
} else if (n<2) { // stop
p('>><<', d);
} else { // next direction
i = d&1 ? 2*--n : n-1; // set going length, decrement n if horizontal
p("..''", ++d%4, m);
}
}
m(0);
return a;
}
Perl 5, 255 244 bytes
sub r{$a='';$a.=$/while s/.$/$a.=$&;''/emg;$_=$a}sub f{($s=pop.'-'x--($n=2*pop)**2)=~s/.\K/-/g;$k=local$_=($"x$n.$/)x$n;r while s@ +$|>( )+@$_=substr$s,0,-1+length$&,'';s/^-/$k&2?"'":'.'/e;$k++&1&&y/-/|/;"$_>"@me;$n&2&&y/>/</||r&r;s/.\K.//g;r}
Here's with newlines for better readability:
sub r{$a='';$a.=$/while s/.$/$a.=$&;''/emg;$_=$a}
sub f{
($s=pop.'-'x--($n=2*pop)**2)=~s/.\K/-/g;
$k=local$_=($"x$n.$/)x$n;
r while s@ +$|>( )+@
$_=substr$s,0,-1+length$&,'';
s/^-/$k&2?"'":'.'/e;
$k++&1&&y/-/|/;
"$_>"
@me;
$n&2&&y/>/</||r&r;
s/.\K.//g;
r
}
I'm sure there are glaring opportunities to golf it further, but at least, for now, it's decently competitive with other answers, considering sigils. I was more interested to play with algorithm -- the square-ish string is actually rotated while being filled. Replace r while... with print && r while... to see intermediate steps.
P.S. It's funny to watch how snake's head moves and serves as anchor where to place the next fragment.
Edit: Just a little bit shorter. Rotation subroutine is also slightly less ugly and can now handle rectangular (not just square) shapes (but of course there ought to be Perl golfing pre-fab somewhere for the purpose, 20-odd years old, so I'm re-inventing the wheel), which fact was immediately used and lead to another couple bytes off.
In fact local can be ditched (-5) and TIO's footer written so that the $_ doesn't hold read-only value while testing. And further couple bytes less if string is temporarily padded with arbitrarily large number of hyphens (1E6, say) instead of value computed from n. But I'm not that much desperate, yet :)
JavaScript (ES12), 188 bytes
Expects (string)(n). Returns a matrix of characters.
s=>n=>eval(`for(m=[k=x=y=0];~n;)for(d=-2;i=~n&&d++<2;n-=!n|d&!!y)for(;i++<(d&1?w=n*2-1:n||2);x-=d%2,y-=~-d%2)(m[y]||=[..."".padEnd(w)])[x]=d*x+1&1&&s[k++]||"|-..''< >"[i>2?d&1:d-3*~!n];m`)
Alternate formula
I've found the following alternate formula for special characters where each entry appears only once in the lookup string. But in the end, it's just as long as the simpler one.
".<'>|-"[i > 2 ? d & 1 | 4 : !n ^ d * 3 & 2]
Commented
This is a version without eval() for readability.
// s = input string, n = size parameter
s => n => {
for(
// m[] is the output matrix
// k is a pointer into s
// (x, y) is the current position in the matrix
m = [k = x = y = 0];
// stop when n = -1
~n;
)
for(
// d is the direction
d = -2;
// stop when n = -1 or d = 2
// i is the counter for the next loop
i = ~n && d++ < 2;
// decrement n if n = 0, or d is odd and y != 0
n -= !n | d & !!y
)
for(
;
// the width is 2n-1 if d is odd, or n if d is even
// there's a special case for the last iteration
i++ < (d & 1 ? w = n * 2 - 1 : n || 2);
// (x, y) <- (x + dx, y + dy)
x -= d % 2,
y -= ~-d % 2
)
// if m[y] is undefined, initialize it to w spaces
(m[y] ||= [..."".padEnd(w)])[x] =
// if either d or x is even, attempt to get the next
// character from s
d * x + 1 & 1 && s[k++]
// if falsy or undefined, use a special character:
// - "|" or "-" for segments (i > 2)
// - "." or "'" for corners (i = 2, n ≠ 0)
// - "<" or ">" for the final character (i = 2, n = 0)
|| "|-..''< >"[i > 2 ? d & 1 : d - 3 * ~!n];
return m
}
K (ngn/k), 337 331 315 290 279 231 224 211 213 bytes
{(x,w)#@[(x*w)#" ";i,*|i;:;@["|-"{1|1=x|-x}@-':i:0,+\(,/-:\1,w:-1+2*x)4!2_&s;(-2_-2+\s;!#y);:;(".''."4!!-2+#s:,/+|1(2*1+)\|!x; y:,/((#l)#("-";"")){x/y}',''l:(0,((#y)>)#+\(0,(-1+#l)#-1 1)+l:1_|1+&x#2)_y)],"<>"2!x]}
-6 : Save a few bytes with shorter names.
-16 : Tidying up
-25 : Cruft
-46 : Golfing gets me back under limits
-7 : Cleaner
-13 : A single statement!
+2 : handle empty string
Python3, 425 bytes
R=range
def f(n,s):
r=[[' ']*2*n for x in R(n)];x=y=X=K=0;Y=C=1
while 1:
K+=1
if s:r[x][y]=['-',s[0]][C%2or X!=0];s=s[C%2or X!=0:];C+=1
else:r[x][y]='-|'[X!=0]
if(y+Y==2*n-1or(0<=y+2*Y<2*n-1and' '!=r[x][y+2*Y])or Y<-y)and Y:
if K==2:r[x][y]='<>'[Y>0];return r
if r[x][y]in'-|':r[x][y]="'."[Y>0]
X=Y;Y=0
elif(x+X==n or' '!=r[x+X][y])and X:
if r[x][y]in'-|':r[x][y]="'."[X<0]
Y=-X;X=C=K=0
x+=X;y+=Y
Pyth, 78 bytes
ju&K>*hhHH+z>s.i_Psm*Rd"-|"Qs@L_B".'"tQlz.e++b?%klGd\-@K+Hk+j\-<KH__MGStQ]@"<>
Charcoal, 61 bytes
F⮌S⊞υιNηF⊖η«F⊖⁻ηι⎇υ⁺⊟υ-²↷²⎇υ⊟υ§.'ιF⁻⁻ηι²⎇υ⊟υ¹↷²⎇υ⊟υ§'.ι¹»§<>η
Try it online! Link is to verbose version of code. Explanation:
F⮌S⊞υι
Get the reverse of the input into a stack, so that characters can be processed by popping from it.
Nη
Input n.
F⊖η«
Repeat n-1 times. The loop counter is taken as an ascending number for the purposes of calculating which corner character to print, but for the side lengths I show how the actual number of repetitions of the inner loops vary.
F⊖⁻ηι
Repeat from n-1 down to once...
⎇υ⁺⊟υ-²
... output the next character followed by a -, or two -s if none.
↷²
Start moving vertically.
⎇υ⊟υ§.'ι
Output the next character or . or ' appropriately if there are none left.
F⁻⁻ηι²
Repeat from n-2 down to zero...
⎇υ⊟υ¹
... output the next character or | if there are none left.
↷²
Start moving horizontally, but in the other direction.
⎇υ⊟υ§'.ι¹
Output the next character or ' or . appropriately if there are none left.
»§<>η
Output the head of the snake.