Rendu ASCII L-system

16

Contexte

Un système L (ou système Lindenmayer) est un système de réécriture parallèle qui, entre autres, peut être facilement utilisé pour modéliser des fractales. Cette question concerne les systèmes L déterministes et sans contexte . Il s'agit d'un alphabet de symboles, d'une chaîne d'axiomes initiale et d'un ensemble de règles de réécriture mappant chaque symbole d'alphabet sur une nouvelle chaîne. Les règles sont appliquées à l'axiome en parallèle, générant une nouvelle chaîne. Ce processus est ensuite répété.

Par exemple, le système avec l'axiome "A" et les règles A = ABA; B = BBB génère la séquence de chaînes "ABA", "ABABBBABA", "ABABBBABABBBBBBBBBABABBBABA", etc. Par souci de concision, nous ne mentionnons pas explicitement le alphabet lors de la définition du système L. De plus, tout symbole sans règle de réécriture explicite est supposé être inchangé (c'est-à-dire que la règle par défaut pour un symbole A est A = A).

Les L-systèmes peuvent être visualisés à l'aide d'une forme de graphique de tortue. Par convention, la tortue commence à faire face à droite. Une chaîne est ensuite dessinée en itérant sur ses symboles: un F signifie "avancer d'une unité", un G signifie "avancer d'une unité", un + signifie "tourner à gauche d'un angle" et un - - "tourner à droite d'un angle" unité". Tous les autres symboles de la chaîne sont ignorés. Aux fins de cette question, les unités d'angle sont supposées être toujours de 90 °.

Tâche

Étant donné une spécification de tout système L et un certain nombre d'itérations, votre programme doit produire un rendu ASCII de la chaîne résultante (comme décrit ci-dessus) en utilisant des caractères de dessin de boîte.

  • Les paramètres sont transmis sous la forme d'une chaîne séparée par des espaces comprenant l'axiome, les règles de réécriture (sous forme d'une liste d'équations séparées par) et le nombre d'itérations de réécriture. Par exemple, l'entrée "FF = FGF; G = GGG 2" génère la chaîne "FGFGGGFGF" et trace ainsi quatre lignes avec les espaces appropriés.
  • Les symboles utilisés par le système L peuvent être n'importe quel caractère ASCII à l'exception de l'espace et du point-virgule. Il existe au plus une règle explicite spécifiée par symbole (la règle de réécriture par défaut étant le mappage d'identité comme décrit ci-dessus).
  • Vous pouvez supposer que la sortie contiendra toujours au moins un F.
  • La sortie doit utiliser les caractères de dessin de boîte UNICODE suivants pour représenter la visualisation: ─ (U + 2500), │ (U + 2502), ┌ (U + 250C), ┐ (U + 2510), └ (U + 2514) , ┘ (U + 2518), ├ (U + 251C), ┤ (U + 2524), ┬ (U + 252C), ┴ (U + 2534), ┼ (U + 253C), ╴ (U + 2574), ╵ (U + 2575), ╶ (U + 2576) et ╷ (U + 2577). Voir ci-dessous pour des exemples.
  • La sortie ne doit pas contenir de lignes vides au-dessus du caractère de la case supérieure ou en dessous de la dernière. Il ne doit pas non plus contenir d'espace à gauche du caractère de la boîte la plus à gauche ou à droite de la plus à droite. Les lignes avec des espaces de fin qui ne s'étendent pas au-delà du caractère de boîte le plus à droite sont autorisées.

Vous pouvez écrire un programme ou une fonction, en prenant des entrées via STDIN (ou l'alternative la plus proche), un argument de ligne de commande ou un argument de fonction. Les résultats doivent être imprimés dans STDOUT (ou l'alternative la plus proche), enregistrés dans un fichier ou retournés sous forme de chaîne.

Exemples

# Cantor dust
>> "F F=FGF;G=GGG 0"
╶╴
>> "F F=FGF;G=GGG 1"
╶╴╶╴
>> "F F=FGF;G=GGG 2"
╶╴╶╴  ╶╴╶╴
>> "F F=FGF;G=GGG 3"
╶╴╶╴  ╶╴╶╴        ╶╴╶╴  ╶╴╶╴

# Koch curve
>> "F F=F+F−F−F+F 1"
 ┌┐
╶┘└╴
>> "F F=F+F-F-F+F 2"
    ┌┐
   ┌┘└┐
  ┌┘  └┐
 ┌┼┐  ┌┼┐
╶┘└┘  └┘└╴

Voici d'autres exemples pour tester votre programme:

# Dragon curve
>> "FX X=X+YF+;Y=-FX-Y n"

# Hilbert curve
>> "A A=-BF+AFA+FB-;B=+AF-BFB-FA+ n"

# Sierpinski carpet
>> "F F=F+F-F-F-G+F+F+F-F;G=GGG n"

Les deux premiers se présentent comme suit (produits à l'aide de la réponse de @ edc65):

entrez la description de l'image ici entrez la description de l'image ici

Vous pouvez tester n'importe quel système sur cette page .

Notation

Le code le plus court (en octets) gagne. Des règles standard s'appliquent.

Miscellania

Ce défi a été inspiré par Draw a Random Walk with Slashes . En fait, il est possible de représenter une marche aléatoire comme un L-système si nous étendons le système pour autoriser plusieurs règles par symbole, avec l'expansion choisie de manière non déterministe lors de la réécriture. Une formulation est:

"F F=FF;F=F+F;F=F++F;F=F+++F"

Une autre extension courante, souvent utilisée lors de la modélisation de plantes, consiste à interpréter les caractères [et] comme poussant et sautant la position et l'angle actuels. La plupart des plantes utilisent des angles inférieurs à 90 °, mais voici un exemple qui ne fonctionne pas:

"FAX X=[-FAX][FAX][+FAX];A=AFB;B=A"

Aucun de ces exemples ne doit être soutenu dans ce défi.

Ce défi est également similaire à "Désolé, jeune homme, mais c'est des tortues tout le long!" . Cependant, ce défi utilisait le rendu de ligne plutôt que ASCII et permettait une syntaxe plus flexible.

Uri Granta
la source

Réponses:

7

JavaScript (ES6), 440 octets (410 caractères)

F=p=>([a,r,n]=p.split(' '),t=>{r.split(';').map(x=>r[x[0]]=x.slice(2),r={});for(;n--;)a=[...a].map(c=>r[c]||c).join('');u=x=y=0,g=[];for(c of a)c=='+'?[t,u]=[u,-t]:c=='-'?[u,t]=[t,-u]:c<'F'|c>'G'?0:((y+=u)<0?(g=[[],...g],++y):g[y]=g[y]||[],(x+=t)<0?(g=g.map(r=>[,...r]),++x):0,c>'F'?0:g[g[f=t?0.5:2,y][x]|=(3+t-u)*f,y-u][x-t]|=(3+u-t)*f)})(1)||g.map(r=>[for(c of r)' ╶╴─╵└┘┴╷┌┐┬│├┤┼'[~~c]].join('')).join('\n')

Moins golfé

F=p=>{
  [a,r,n]=p.split(' '),
  r.split(';').map(x=>r[x[0]]=x.slice(2),r={}); // set rules
  for(;n--;)a=[...a].map(c=>r[c]||c).join(''); // build string
  t=1,u=x=y=0, // start pos 0,0 start direction 1,0
  g=[[]]; // rendering in bitmap g
  for(c of a)
    c=='+'?[t,u]=[u,-t] // left turn
    :c=='-'?[u,t]=[t,-u] // right turn
    :c=='F'|c=='G'?(     // move or draw
      (y+=u)<0?(g=[[],...g],++y):g[y]=g[y]||[], // move vertical, enlarge grid if needed
      (x+=t)<0?(g=g.map(r=>[,...r]),++x):0, // move horizontal, enlarge grid if needed
      c=='F'&&( // draw: set bits
        f=t?0.5:2,
        g[y][x]|=(3+t-u)*f,
        g[y-u][x-t]|=(3+u-t)*f
      )
    ):0;
  // render bits as box characters
  return g.map(r=>[' ╶╴─╵└┘┴╷┌┐┬│├┤┼'[~~c]for(c of r)].join('')).join('\n')
}

Test d' extrait de code à tester (dans Firefox)

edc65
la source
Excellente (et rapide!) Réponse. J'ai ajouté des captures d'écran des sorties des courbes dragon et hilbert à la question.
Uri Granta
6

Haskell, 568 octets

import Data.List.Split
p=splitOn
l=lookup
m=maximum
n=minimum
o[h,x]=(h,x)
Just x#_=x
_#x=x
g[s,r,i]=iterate((\c->lookup[c](map(o.p"=")(p";"r))#[c])=<<)s!!read i
u v@(a,x,y,d,e)c|c=='+'=(a,x,y,-e,d)|c=='-'=(a,x,y,e,-d)|c=='G'=(a,x+d,y+e,d,e)|c=='F'=(s(x,y)(d%e)a:s(x+d,y+e)(d?e)a:a,x+d,y+e,d,e)|1<2=v
s p n a=(p,n+(l p a)#0)
1%0=2;0%1=8;-1%0=1;0%(-1)=4
1?0=1;0?1=4;-1?0=2;0?(-1)=8
f z=unlines[[" ╴╶─╷┐┌┬╵┘└┴│┤├┼"!!(l(x,y)q#0)|x<-[n a..m a]]|y<-[m b,m b-1..n b]]where a=map(fst.fst)q;b=map(snd.fst)q;(q,_,_,_,_)=foldl u([],0,0,1,0)$g$p" "z

Essai:

*Main> putStr $ f "F F=F-F+F+F-F 3"
╶┐┌┐  ┌┐┌┐        ┌┐┌┐  ┌┐┌╴
 └┼┘  └┼┼┘        └┼┼┘  └┼┘
  └┐  ┌┼┼┐        ┌┼┼┐  ┌┘
   └┐┌┼┘└┘        └┘└┼┐┌┘
    └┼┘              └┼┘   
     └┐              ┌┘
      └┐┌┐        ┌┐┌┘
       └┼┘        └┼┘
        └┐        ┌┘
         └┐┌┐  ┌┐┌┘
          └┼┘  └┼┘
           └┐  ┌┘
            └┐┌┘
             └┘

Comment ça fonctionne:

  • réécriture (fonction g): j'analyse les règles dans une liste d'association (lettre -> chaîne de remplacement) et la mappe à plusieurs reprises sur l'axiome.
  • la création de la voie (fonction upour une seule étape): Je ne stocke pas le chemin d' accès dans une matrice mais dans une autre liste d'association avec (x, y) des positions que les clés et les configurations de bits des 4 blocs de base ( , , et ) en tant que valeurs . En cours de route, je garde une trace de la position et de la direction actuelles.
  • dessin du chemin (fonction f): je calcule d'abord les dimensions max / min à partir de la liste des chemins, puis j'itère sur [max y -> min y] et [min x -> max x] et recherche les blocs à dessiner.
nimi
la source
0

ES7, 394 caractères, 424 octets

F=p=>([a,r,n]=p.split` `,t=>{r.split`;`.map(x=>r[x[0]]=x.slice(2),r={});for(;n--;)a=[...a].map(c=>r[c]||c).join``;u=x=y=0,g=[];for(c of a)c=='+'||c=='-'?[t,u]=[u,-t]:c<'F'|c>'G'?0:((y+=u)<0?(g=[[],...g],++y):g[y]=g[y]||[],(x+=t)<0?(g=g.map(r=>[,...r]),++x):0,c>'F'?0:g[g[f=t?0.5:2,y][x]|=(3+t-u)*f,y-u][x-t]|=(3+u-t)*f)})(1)||g.map(r=>[for(c of r)'╶╴─╵└┘┴╷┌┐┬│├┤┼'[~~c]].join``).join`
`
user74131
la source