ASCII L-System Renderer

16

Hintergrund

Ein L-System (oder Lindenmayer-System) ist ein paralleles Umschreibungssystem, mit dem unter anderem Fraktale einfach modelliert werden können. Diese Frage betrifft deterministische, kontextfreie L-Systeme . Diese bestehen aus einem Symbolalphabet, einer anfänglichen Axiomzeichenfolge und einem Satz von Umschreiberegeln, die jedes Alphabetsymbol einer neuen Zeichenfolge zuordnen. Die Regeln werden parallel auf das Axiom angewendet und erzeugen einen neuen String. Dieser Vorgang wird dann wiederholt.

Zum Beispiel erzeugt das System mit dem Axiom "A" und den Regeln A = ABA; B = BBB die Folge von Zeichenfolgen "ABA", "ABABBBABA", "ABABBBABABBBBBBBBABABBBABA" usw. Aus Gründen der Übersichtlichkeit erwähnen wir das nicht ausdrücklich Alphabet bei der Definition des L-Systems. Weiterhin wird angenommen, dass jedes Symbol ohne explizite Umschreiberegel unverändert ist (dh die Standardregel für ein Symbol A ist A = A).

L-Systeme können mit einer Form von Schildkrötengrafiken visualisiert werden. Konventionell beginnt die Schildkröte nach rechts zu blicken. Eine Zeichenkette wird dann durch Iteration über ihre Symbole gezogen: Ein F bedeutet "eine Einheit nach vorne ziehen", ein G bedeutet "eine Einheit nach vorne ziehen", ein + bedeutet "um eine Winkeleinheit nach links drehen" und ein - bedeutet "um einen Winkel nach rechts drehen" Einheit". Alle anderen Symbole in der Zeichenfolge werden ignoriert. Für die Zwecke dieser Frage wird angenommen, dass die Winkeleinheiten immer 90 ° sind.

Aufgabe

Wenn eine Spezifikation eines L-Systems und eine Anzahl von Iterationen gegeben ist, sollte Ihr Programm eine ASCII-Wiedergabe der resultierenden Zeichenfolge (wie oben beschrieben) unter Verwendung von Box-Zeichen ausgeben.

  • Die Parameter werden als durch Leerzeichen getrennte Zeichenfolge übergeben, die das Axiom, die Umschreibungsregeln (als; -getrennte Liste von Gleichungen) und die Anzahl der Umschreibungsiterationen enthält. Beispielsweise erzeugt die Eingabe "FF = FGF; G = GGG 2" die Zeichenfolge "FGFGGGFGF" und zeichnet daher vier Linien mit entsprechenden Lücken.
  • Die vom L-System verwendeten Symbole können beliebige ASCII-Zeichen außer Leerzeichen und Semikolon sein. Es ist höchstens eine explizite Regel pro Symbol angegeben (wobei die Standard-Umschreiberegel die Identitätszuordnung ist, wie oben beschrieben).
  • Sie können davon ausgehen, dass die Ausgabe immer mindestens ein F enthält.
  • Die Ausgabe sollte die folgenden UNICODE-Box-Zeichen zur Darstellung der Visualisierung verwenden: ─ (U + 2500), │ (U + 2502), ┌ (U + 250C), ┐ (U + 2510), └ (U + 2514) , U (U + 2518), ├ (U + 251C), ┤ (U + 2524), ┬ (U + 252C), ┴ (U + 2534), ┼ (U + 253C), ╴ (U + 2574), ╵ (U + 2575), ╶ (U + 2576) und ╷ (U + 2577). Unten finden Sie Beispiele.
  • Die Ausgabe sollte keine Leerzeilen über oder unter dem obersten Kästchen enthalten. Es sollte auch keine Leerzeichen links vom linken oder rechts vom rechten Kästchen enthalten. Zeilen mit nachgestellten Leerzeichen, die nicht über das Feld ganz rechts hinausragen, sind zulässig.

Sie können ein Programm oder eine Funktion schreiben und Eingaben über STDIN (oder die nächstgelegene Alternative), ein Befehlszeilenargument oder ein Funktionsargument vornehmen. Die Ergebnisse sollten auf STDOUT (oder der nächstgelegenen Alternative) gedruckt, in einer Datei gespeichert oder als Zeichenfolge zurückgegeben werden.

Beispiele

# 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"
    ┌┐
   ┌┘└┐
  ┌┘  └┐
 ┌┼┐  ┌┼┐
╶┘└┘  └┘└╴

Weitere Beispiele zum Testen Ihres Programms sind:

# 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"

Die ersten beiden sehen wie folgt aus (erstellt mit der Antwort von @ edc65):

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben

Sie können jedes der Systeme auf dieser Seite testen .

Wertung

Kürzester Code (in Bytes) gewinnt. Es gelten Standardregeln.

Verschiedenes

Diese Herausforderung wurde von Draw a Random Walk with Slashes inspiriert . Tatsächlich ist es möglich, einen Zufallsrundgang als L-System darzustellen, wenn wir das System so erweitern, dass mehrere Regeln pro Symbol zulässig sind , wobei die Erweiterung beim Umschreiben nicht deterministisch gewählt wird . Eine Formulierung ist:

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

Eine andere verbreitete Erweiterung, die häufig beim Modellieren von Pflanzen verwendet wird, besteht darin, die Zeichen [und] so zu interpretieren, dass die aktuelle Position und der aktuelle Winkel verschoben und verschoben werden. Die meisten Pflanzen verwenden Winkel kleiner als 90 °, aber hier ist ein Beispiel, das dies nicht tut:

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

Keines dieser Beispiele muss bei dieser Herausforderung unterstützt werden.

Diese Herausforderung ähnelt auch "Tut mir leid, junger Mann, aber es ist Turtles ganz unten!" . Bei dieser Herausforderung wurde jedoch Linienrendering anstelle von ASCII verwendet und eine flexiblere Syntax ermöglicht.

Uri Granta
quelle

Antworten:

7

JavaScript (ES6), 440 Byte (410 Zeichen)

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')

Weniger golfen

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 Code Snippet zum Testen (in Firefox)

edc65
quelle
Tolle (und schnelle!) Antwort. Ich habe Screenshots der Drachen- und Hilbert-Kurvenausgaben zu der Frage hinzugefügt.
Uri Granta
6

Haskell, 568 Bytes

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

Testlauf:

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

Wie es funktioniert:

  • Umschreiben (Funktion g): Ich parse die Regeln in eine Assoziationsliste (Buchstabe -> Ersatzzeichenfolge) und ordne sie wiederholt dem Axiom zu.
  • Erstellen des Pfad (Funktion ufür einen einzelnen Schritt): I speichert den Pfad nicht in einer Matrix , aber in einem anderen Assoziationsliste mit (x, y) Positionen wie die Schlüssel und Bitmustern der 4 Grundblöcke ( , , und ) als Wert . Unterwegs verfolge ich die aktuelle Position und Richtung.
  • Zeichnen des Pfads (Funktion f): Zuerst berechne ich die max / min-Dimensionen aus der Pfadliste und iteriere dann über [max y -> min y] und [min x -> max x] und suche die zu zeichnenden Blöcke.
nimi
quelle
0

ES7, 394 Zeichen, 424 Byte

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
quelle