Sierpinski curve
- Task
Produce a graphical or ASCII-art representation of a Sierpinski curve of at least order 3.
Action!
Action! language does not support recursion. Therefore an iterative approach with a stack has been proposed. <lang Action!>DEFINE C_="10+" DEFINE N_="20+" DEFINE E_="30+" DEFINE S_="40+" DEFINE W_="50+" DEFINE SafePlot="BYTE inside inside=InsideScreen() IF inside THEN Plot(x,y) FI" DEFINE SafeDrawTo="IF inside=1 AND InsideScreen()=1 THEN DrawTo(x,y) FI" DEFINE Next="Push(state+1,level)" DEFINE DrawN="Push(21,level-1)" DEFINE DrawE="Push(31,level-1)" DEFINE DrawS="Push(41,level-1)" DEFINE DrawW="Push(51,level-1)"
INT x,y,stackSize
DEFINE MAX_COUNT="100" BYTE ARRAY stack(MAX_COUNT)
PROC InitStack()
stackSize=0
RETURN
BYTE FUNC IsEmpty()
IF stackSize=0 THEN RETURN (1) FI
RETURN (0)
PROC Push(BYTE state,level)
stack(stackSize)=state stackSize==+1 stack(stackSize)=level stackSize==+1
RETURN
PROC Pop(BYTE POINTER state,level)
stackSize==-1 level^=stack(stackSize) stackSize==-1 state^=stack(stackSize)
RETURN
BYTE FUNC InsideScreen()
IF x<0 OR y<0 OR x>319 OR y>191 THEN RETURN (0) FI
RETURN (1)
PROC LineN()
SafePlot y==-4 SafeDrawTo
RETURN
PROC LineNE()
SafePlot x==+2 y==-2 SafeDrawTo
RETURN
PROC LineE()
SafePlot x==+4 SafeDrawTo
RETURN
PROC LineSE()
SafePlot x==+2 y==+2 SafeDrawTo
RETURN
PROC LineS()
SafePlot y==+4 SafeDrawTo
RETURN
PROC LineSW()
SafePlot x==-2 y==+2 SafeDrawTo
RETURN
PROC LineW()
SafePlot x==-4 SafeDrawTo
RETURN
PROC LineNW()
SafePlot x==-2 y==-2 SafeDrawTo
RETURN
PROC SierpinskiCurve(BYTE level)
BYTE state InitStack() Push(C_ 1,level+1) WHILE IsEmpty()=0 DO Pop(@state,@level) IF state=C_ 1 THEN Next DrawN ELSEIF state=C_ 2 THEN LineNE() Next DrawE ELSEIF state=C_ 3 THEN LineSE() Next DrawS ELSEIF state=C_ 4 THEN LineSW() Next DrawW ELSEIF state=C_ 5 THEN LineNW() ELSEIF state=N_ 1 THEN IF level=1 THEN LineNE() LineN() LineNW() ELSE Next DrawN FI ELSEIF state=N_ 2 THEN LineNE() Next DrawE ELSEIF state=N_ 3 THEN LineN() Next DrawW ELSEIF state=N_ 4 THEN LineNW() DrawN ELSEIF state=E_ 1 THEN IF level=1 THEN LineSE() LineE() LineNE() ELSE Next DrawE FI ELSEIF state=E_ 2 THEN LineSE() Next DrawS ELSEIF state=E_ 3 THEN LineE() Next DrawN ELSEIF state=E_ 4 THEN LineNE() DrawE ELSEIF state=S_ 1 THEN IF level=1 THEN LineSW() LineS() LineSE() ELSE Next DrawS FI ELSEIF state=S_ 2 THEN LineSW() Next DrawW ELSEIF state=S_ 3 THEN LineS() Next DrawE ELSEIF state=S_ 4 THEN LineSE() DrawS ELSEIF state=W_ 1 THEN IF level=1 THEN LineNW() LineW() LineSW() ELSE Next DrawW FI ELSEIF state=W_ 2 THEN LineNW() Next DrawN ELSEIF state=W_ 3 THEN LineW() Next DrawS ELSEIF state=W_ 4 THEN LineSW() DrawW ELSE Break() FI OD
RETURN
PROC Main()
BYTE CH=$02FC,COLOR1=$02C5,COLOR2=$02C6
Graphics(8+16) Color=1 COLOR1=$0C COLOR2=$02
x=1 y=187 SierpinskiCurve(6)
DO UNTIL CH#$FF OD CH=$FF
RETURN</lang>
- Output:
Screenshot from Atari 8-bit computer
AutoHotkey
Requires Gdip Library <lang AutoHotkey>SierpinskiW := 500 SierpinskiH := 500 level := 5 cx := SierpinskiW/2 cy := SierpinskiH h := cx / (2**(level+1)) Arr := [] squareCurve(level) xmin := xmax := ymin := ymax := 0 for i, point in Arr { xmin := A_Index = 1 ? point.x : xmin < point.x ? xmin : point.x xmax := point.x > xmax ? point.x : xmax ymin := A_Index = 1 ? point.y : ymin < point.y ? ymin : point.y ymax := point.y > ymax ? point.y : ymax } SierpinskiX := A_ScreenWidth/2 - (xmax-xmin)/2 , SierpinskiY := A_ScreenHeight/2 - (ymax-ymin)/2 for i, point in Arr points .= point.x - xmin + SierpinskiX "," point.y - ymin + SierpinskiY "|" points := Trim(points, "|") gdip1() Gdip_DrawLines(G, pPen, Points) UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height) return
- ---------------------------------------------------------------
lineTo(newX, newY) { global Arr[Arr.count()+1, "x"] := newX-SierpinskiW/2+h Arr[Arr.count() , "y"] := SierpinskiH-newY+2*h cx := newX cy := newY }
- ---------------------------------------------------------------
sierN(level) { global if (level = 1) { lineTo(cx+h, cy-h) ; lineNE() lineTo(cx, cy-2*h) ; lineN() lineTo(cx-h, cy-h) ; lineNW() } else{ sierN(level - 1) lineTo(cx+h, cy-h) ; lineNE() sierE(level - 1) lineTo(cx, cy-2*h) ; lineN() sierW(level - 1) lineTo(cx-h, cy-h) ; lineNW() sierN(level - 1) } }
- ---------------------------------------------------------------
sierE(level) { global if (level = 1) { lineTo(cx+h, cy+h) ; lineSE() lineTo(cx+2*h, cy) ; lineE() lineTo(cx+h, cy-h) ; lineNE() } else { sierE(level - 1) lineTo(cx+h, cy+h) ; lineSE() sierS(level - 1) lineTo(cx+2*h, cy) ; lineE() sierN(level - 1) lineTo(cx+h, cy-h) ; lineNE() sierE(level - 1) } }
- ---------------------------------------------------------------
sierS(level) { global if (level = 1) { lineTo(cx-h, cy+h) ; lineSW() lineTo(cx, cy+2*h) ; lineS() lineTo(cx+h, cy+h) ; lineSE() } else { sierS(level - 1) lineTo(cx-h, cy+h) ; lineSW() sierW(level - 1) lineTo(cx, cy+2*h) ; lineS() sierE(level - 1) lineTo(cx+h, cy+h) ; lineSE() sierS(level - 1) } }
- ---------------------------------------------------------------
sierW(level) { global if (level = 1) { lineTo(cx-h, cy-h) ; lineNW() lineTo(cx-2*h, cy) ; lineW() lineTo(cx-h, cy+h) ; lineSW() } else { sierW(level - 1) lineTo(cx-h, cy-h) ; lineNW() sierN(level - 1) lineTo(cx-2*h, cy) ; lineW() sierS(level - 1) lineTo(cx-h, cy+h) ; lineSW() sierW(level - 1) } }
- ---------------------------------------------------------------
squareCurve(level) { global sierN(level) lineTo(cx+h, cy-h) ; lineNE() sierE(level) lineTo(cx+h, cy+h) ; lineSE() sierS(level) lineTo(cx-h, cy+h) ; lineSW() sierW(level) lineTo(cx-h, cy-h) ; lineNW() lineTo(cx+h, cy-h) ; lineNE() }
- ---------------------------------------------------------------
gdip1(){ global If !pToken := Gdip_Startup() { MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system ExitApp } OnExit, Exit Width := A_ScreenWidth, Height := A_ScreenHeight Gui, 1: -Caption +E0x80000 +LastFound +OwnDialogs +Owner +AlwaysOnTop Gui, 1: Show, NA hwnd1 := WinExist() hbm := CreateDIBSection(Width, Height) hdc := CreateCompatibleDC() obm := SelectObject(hdc, hbm) G := Gdip_GraphicsFromHDC(hdc) Gdip_SetSmoothingMode(G, 4) pPen := Gdip_CreatePen(0xFFFF0000, 2) }
- ---------------------------------------------------------------
gdip2(){ global Gdip_DeleteBrush(pBrush) Gdip_DeletePen(pPen) SelectObject(hdc, obm) DeleteObject(hbm) DeleteDC(hdc) Gdip_DeleteGraphics(G) }
- ---------------------------------------------------------------
Exit: gdip2() Gdip_Shutdown(pToken) ExitApp Return </lang>
C++
Output is a file in SVG format. The curve is generated using the Lindenmayer system method. <lang cpp>// See https://en.wikipedia.org/wiki/Sierpi%C5%84ski_curve#Representation_as_Lindenmayer_system
- include <cmath>
- include <fstream>
- include <iostream>
- include <string>
class sierpinski_curve { public:
void write(std::ostream& out, int size, int length, int order);
private:
static std::string rewrite(const std::string& s); void line(std::ostream& out); void execute(std::ostream& out, const std::string& s); double x_; double y_; int angle_; int length_;
};
void sierpinski_curve::write(std::ostream& out, int size, int length, int order) {
length_ = length; x_ = length/std::sqrt(2.0); y_ = 2 * x_; angle_ = 45; out << "<svg xmlns='http://www.w3.org/2000/svg' width='" << size << "' height='" << size << "'>\n"; out << "<rect width='100%' height='100%' fill='white'/>\n"; out << "<path stroke-width='1' stroke='black' fill='none' d='"; std::string s = "F--XF--F--XF"; for (int i = 0; i < order; ++i) s = rewrite(s); execute(out, s); out << "'/>\n</svg>\n";
}
std::string sierpinski_curve::rewrite(const std::string& s) {
std::string t; for (char c : s) { if (c == 'X') t += "XF+G+XF--F--XF+G+X"; else t += c; } return t;
}
void sierpinski_curve::line(std::ostream& out) {
double theta = (3.14159265359 * angle_)/180.0; x_ += length_ * std::cos(theta); y_ -= length_ * std::sin(theta); out << " L" << x_ << ',' << y_;
}
void sierpinski_curve::execute(std::ostream& out, const std::string& s) {
out << 'M' << x_ << ',' << y_; for (char c : s) { switch (c) { case 'F': case 'G': line(out); break; case '+': angle_ = (angle_ + 45) % 360; break; case '-': angle_ = (angle_ - 45) % 360; break; } }
}
int main() {
std::ofstream out("sierpinski_curve.svg"); if (!out) { std::cerr << "Cannot open output file\n"; return 1; } sierpinski_curve s; s.write(out, 545, 7, 5); return 0;
}</lang>
- Output:
See: sierpinski_curve.svg (offsite SVG image)
Factor
<lang factor>USING: accessors kernel L-system sequences ui ;
- curve ( L-system -- L-system )
L-parser-dialect { "G" [ dup length>> draw-forward ] } suffix >>commands [ 45 >>angle ] >>turtle-values "F--XF--F--XF" >>axiom { { "X" "XF+G+XF--F--XF+G+X" } } >>rules ;
[ <L-system> curve "Sierpinski curve" open-window ] with-ui</lang>
When using the L-system visualizer, the following controls apply:
Button | Command |
---|---|
a | zoom in |
z | zoom out |
left arrow | turn left |
right arrow | turn right |
up arrow | pitch down |
down arrow | pitch up |
q | roll left |
w | roll right |
Button | Command |
---|---|
x | iterate L-system |
Fōrmulæ
Fōrmulæ programs are not textual, visualization/edition of programs is done showing/manipulating structures but not text. Moreover, there can be multiple visual representations of the same program. Even though it is possible to have textual representation —i.e. XML, JSON— they are intended for storage and transfer purposes more than visualization and edition.
Programs in Fōrmulæ are created/edited online in its website, However they run on execution servers. By default remote servers are used, but they are limited in memory and processing power, since they are intended for demonstration and casual use. A local server can be downloaded and installed, it has no limitations (it runs in your own computer). Because of that, example programs can be fully visualized and edited, but some of them will not run if they require a moderate or heavy computation/memory resources, and no local server is being used.
In this page you can see the program(s) related to this task and their results.
Go
A partial translation anyway which produces a static image of a SC of level 5, yellow on blue, which can be viewed with a utility such as EOG. <lang go>package main
import (
"github.com/fogleman/gg" "math"
)
var (
width = 770.0 height = 770.0 dc = gg.NewContext(int(width), int(height))
)
var cx, cy, h float64
func lineTo(newX, newY float64) {
dc.LineTo(newX-width/2+h, height-newY+2*h) cx, cy = newX, newY
}
func lineN() { lineTo(cx, cy-2*h) } func lineS() { lineTo(cx, cy+2*h) } func lineE() { lineTo(cx+2*h, cy) } func lineW() { lineTo(cx-2*h, cy) }
func lineNW() { lineTo(cx-h, cy-h) } func lineNE() { lineTo(cx+h, cy-h) } func lineSE() { lineTo(cx+h, cy+h) } func lineSW() { lineTo(cx-h, cy+h) }
func sierN(level int) {
if level == 1 { lineNE() lineN() lineNW() } else { sierN(level - 1) lineNE() sierE(level - 1) lineN() sierW(level - 1) lineNW() sierN(level - 1) }
}
func sierE(level int) {
if level == 1 { lineSE() lineE() lineNE() } else { sierE(level - 1) lineSE() sierS(level - 1) lineE() sierN(level - 1) lineNE() sierE(level - 1) }
}
func sierS(level int) {
if level == 1 { lineSW() lineS() lineSE() } else { sierS(level - 1) lineSW() sierW(level - 1) lineS() sierE(level - 1) lineSE() sierS(level - 1) }
}
func sierW(level int) {
if level == 1 { lineNW() lineW() lineSW() } else { sierW(level - 1) lineNW() sierN(level - 1) lineW() sierS(level - 1) lineSW() sierW(level - 1) }
}
func squareCurve(level int) {
sierN(level) lineNE() sierE(level) lineSE() sierS(level) lineSW() sierW(level) lineNW() lineNE() // needed to close the square in the top left hand corner
}
func main() {
dc.SetRGB(0, 0, 1) // blue background dc.Clear() level := 5 cx, cy = width/2, height h = cx / math.Pow(2, float64(level+1)) squareCurve(level) dc.SetRGB255(255, 255, 0) // yellow curve dc.SetLineWidth(2) dc.Stroke() dc.SavePNG("sierpinski_curve.png")
}</lang>
Java
<lang java>import java.io.*;
public class SierpinskiCurve {
public static void main(final String[] args) { try (Writer writer = new BufferedWriter(new FileWriter("sierpinski_curve.svg"))) { SierpinskiCurve s = new SierpinskiCurve(writer); s.currentAngle = 45; s.currentX = 5; s.currentY = 10; s.lineLength = 7; s.begin(545); s.execute(rewrite(5)); s.end(); } catch (final Exception ex) { ex.printStackTrace(); } }
private SierpinskiCurve(final Writer writer) { this.writer = writer; }
private void begin(final int size) throws IOException { write("<svg xmlns='http://www.w3.org/2000/svg' width='%d' height='%d'>\n", size, size); write("<rect width='100%%' height='100%%' fill='white'/>\n"); write("<path stroke-width='1' stroke='black' fill='none' d='"); }
private void end() throws IOException { write("'/>\n</svg>\n"); }
private void execute(final String s) throws IOException { write("M%g,%g\n", currentX, currentY); for (int i = 0, n = s.length(); i < n; ++i) { switch (s.charAt(i)) { case 'F': case 'G': line(lineLength); break; case '+': turn(ANGLE); break; case '-': turn(-ANGLE); break; } } }
private void line(final double length) throws IOException { final double theta = (Math.PI * currentAngle) / 180.0; currentX += length * Math.cos(theta); currentY -= length * Math.sin(theta); write("L%g,%g\n", currentX, currentY); }
private void turn(final int angle) { currentAngle = (currentAngle + angle) % 360; }
private void write(final String format, final Object... args) throws IOException { writer.write(String.format(format, args)); }
private static String rewrite(final int order) { String s = AXIOM; for (int i = 0; i < order; ++i) { final StringBuilder sb = new StringBuilder(); for (int j = 0, n = s.length(); j < n; ++j) { final char ch = s.charAt(j); if (ch == 'X') sb.append(PRODUCTION); else sb.append(ch); } s = sb.toString(); } return s; }
private final Writer writer; private double lineLength; private double currentX; private double currentY; private int currentAngle;
private static final String AXIOM = "F--XF--F--XF"; private static final String PRODUCTION = "XF+G+XF--F--XF+G+X"; private static final int ANGLE = 45;
}</lang>
- Output:
See: sierpinski_curve.svg (offsite SVG image)
jq
Works with gojq, the Go implementation of jq
This entry uses an L-system and turtle graphics to generate an SVG file which can be viewed using a web browser, at least if the file type is `.svg`. The SVG viewBox is dynamically sized.
See Category_talk:Jq-turtle for the turtle.jq module used here. Please note that the `include` directive may need to be modified depending on the location of the included file, and the command-line options used. <lang jq>include "turtle" {search: "."};
- Compute the curve using a Lindenmayer system of rules
def rules:
{ X: "XF+G+XF--F--XF+G+X", "": "F--XF--F--XF" };
def sierpinski($count):
rules as $rules | def p($count): if $count == 0 then . else gsub("X"; $rules["X"]) | p($count-1) end; $rules[""] | p($count) ;
def interpret($x):
if $x == "+" then turtleRotate(45) elif $x == "-" then turtleRotate(-45) elif $x == "F" or $x == "G" then turtleForward(5) else . end;
def sierpinski_curve($n):
sierpinski($n) | split("") | reduce .[] as $action ( turtle([100,100]) | turtleDown; interpret($action) ) ;
- viewBox = <min-x> <min-y> <width> <height>
- Input: {svg, minx, miny, maxx, maxy}
def svg:
"<svg viewBox='\(.minx|floor) \(.miny - 2 |floor) \(.maxx - .minx|ceil) \(2 + .maxy - .miny|ceil)'", " preserveAspectRatio='xMinYmin meet'", " xmlns='http://www.w3.org/2000/svg' >", path("none"; "red"; 1), "</svg>";
sierpinski_curve(5) | svg </lang>
Julia
Turtle procedural (lineto) version
Modified from Craft of Coding blog, Processing version <lang Julia>using Luxor
function sierpinski_curve(x0, y0, h, level)
x1, y1 = x0, y0 lineto(x, y) = begin line(Point(x1, y1), Point(x, y), :stroke); x1, y1 = x, y end lineN() = lineto(x1,y1-2*h) lineS() = lineto(x1,y1+2*h) lineE() = lineto(x1+2*h,y1) lineW() = lineto(x1-2*h,y1) lineNW() = lineto(x1-h,y1-h) lineNE() = lineto(x1+h,y1-h) lineSE() = lineto(x1+h,y1+h) lineSW() = lineto(x1-h,y1+h) function drawN(i) if i == 1 lineNE(); lineN(); lineNW() else drawN(i-1); lineNE(); drawE(i-1); lineN(); drawW(i-1); lineNW(); drawN(i-1) end end function drawE(i) if i == 1 lineSE(); lineE(); lineNE() else drawE(i-1); lineSE(); drawS(i-1); lineE(); drawN(i-1); lineNE(); drawE(i-1) end end function drawS(i) if i == 1 lineSW(); lineS(); lineSE() else drawS(i-1); lineSW(); drawW(i-1); lineS(); drawE(i-1); lineSE(); drawS(i-1) end end function drawW(i) if i == 1 lineNW(); lineW(); lineSW() else drawW(i-1); lineNW(); drawN(i-1); lineW(); drawS(i-1); lineSW(); drawW(i-1) end end function draw_curve(levl) drawN(levl); lineNE(); drawE(levl); lineSE() drawS(levl); lineSW(); drawW(levl); lineNW() end draw_curve(level)
end
Drawing(800, 800) sierpinski_curve(10, 790, 3, 6) finish() preview() </lang>
LSystem version
<lang julia>using Lindenmayer # https://github.com/cormullion/Lindenmayer.jl
sierpcurve = LSystem(Dict("X" => "XF+G+XF--F--XF+G+X"), "F--XF--F--XF")
drawLSystem(sierpcurve,
forward = 10, turn = 45, startingpen= (0.2, 0.8, 0.8), startingx = -380, startingy = 380, startingorientation = π/4, iterations = 5, filename = "sierpinski_curve.png", showpreview = true
) </lang>
Lambdatalk
<lang Scheme> {def sierp
{def sierp.r {lambda {:order :length :angle} {if {= :order 0} then M:length // move :length else {sierp.r {- :order 1} // recurse {/ :length 2} {- :angle}} T:angle // turn :angle {sierp.r {- :order 1} // recurse {/ :length 2} {+ :angle}} T:angle // turn :angle {sierp.r {- :order 1} // recurse {/ :length 2} {- :angle}} }}} {lambda {:order :length} {if {= {% :order 2} 0} // if :order is even then {sierp.r :order :length 60} // recurse with 60° else T60 // else turn 60° {sierp.r :order :length -60} // recurse with -60°
}}}
Four curves drawn using the turtle promitive.
{svg {@ width="580" height="580" style="box-shadow:0 0 8px #000;"}
{polyline {@ points="{turtle 50 5 0 {sierp 1 570}}" stroke="#ccc" fill="transparent" stroke-width="7"}} {polyline {@ points="{turtle 50 5 0 {sierp 3 570}}" stroke="#8ff" fill="transparent" stroke-width="5"}} {polyline {@ points="{turtle 50 5 0 {sierp 5 570}}" stroke="#f88" fill="transparent" stroke-width="3"}} {polyline {@ points="{turtle 50 5 0 {sierp 7 570}}" stroke="#000" fill="transparent" stroke-width="1"}}
} </lang>
See the result in http://lambdaway.free.fr/lambdawalks/?view=sierpinsky
Mathematica / Wolfram Language
<lang Mathematica>Graphics[SierpinskiCurve[3]]</lang>
Nim
We produce a SVG file using same algorithm as the one of C++ version. <lang Nim>import math
type
SierpinskiCurve = object x, y: float angle: float length: int file: File
proc line(sc: var SierpinskiCurve) =
let theta = degToRad(sc.angle) sc.x += sc.length.toFloat * cos(theta) sc.y -= sc.length.toFloat * sin(theta) sc.file.write " L", sc.x, ',', sc.y
proc execute(sc: var SierpinskiCurve; s: string) =
sc.file.write 'M', sc.x, ',', sc.y for c in s: case c of 'F', 'G': sc.line() of '+': sc.angle = floorMod(sc.angle + 45, 360) of '-': sc.angle = floorMod(sc.angle - 45, 360) else: discard
func rewrite(s: string): string =
for c in s: if c == 'X': result.add "XF+G+XF--F--XF+G+X" else: result.add c
proc write(sc: var SierpinskiCurve; size, length, order: int) =
sc.length = length sc.x = length.toFloat / sqrt(2.0) sc.y = 2 * sc.x sc.angle = 45 sc.file.write "<svg xmlns='http://www.w3.org/2000/svg' width='", size, "' height='", size, "'>\n" sc.file.write "<rect width='100%' height='100%' fill='white'/>\n" sc.file.write "<path stroke-width='1' stroke='black' fill='none' d='" var s = "F--XF--F--XF" for _ in 1..order: s = s.rewrite() sc.execute(s) sc.file.write "'/>\n</svg>\n"
let outfile = open("sierpinski_curve.svg", fmWrite)
var sc = SierpinskiCurve(file: outfile)
sc.write(545, 7, 5)
outfile.close()</lang>
- Output:
Same as C++ output.
Perl
<lang perl>use strict; use warnings; use SVG; use List::Util qw(max min);
use constant pi => 2 * atan2(1, 0);
my $rule = 'XF+F+XF--F--XF+F+X'; my $S = 'F--F--XF--F--XF'; $S =~ s/X/$rule/g for 1..5;
my (@X, @Y); my ($x, $y) = (0, 0); my $theta = pi/4; my $r = 6;
for (split //, $S) {
if (/F/) { push @X, sprintf "%.0f", $x; push @Y, sprintf "%.0f", $y; $x += $r * cos($theta); $y += $r * sin($theta); } elsif (/\+/) { $theta += pi/4; } elsif (/\-/) { $theta -= pi/4; }
}
my ($xrng, $yrng) = ( max(@X) - min(@X), max(@Y) - min(@Y)); my ($xt, $yt) = (-min(@X) + 10, -min(@Y) + 10);
my $svg = SVG->new(width=>$xrng+20, height=>$yrng+20); my $points = $svg->get_path(x=>\@X, y=>\@Y, -type=>'polyline'); $svg->rect(width=>"100%", height=>"100%", style=>{'fill'=>'black'}); $svg->polyline(%$points, style=>{'stroke'=>'orange', 'stroke-width'=>1}, transform=>"translate($xt,$yt)");
open my $fh, '>', 'sierpinski-curve.svg'; print $fh $svg->xmlify(-namespace=>'svg'); close $fh;</lang> See: sierpinski-curve.svg (offsite SVG image)
Phix
You can run this online here.
-- -- demo\rosetta\Sierpinski_curve.exw -- ================================= -- -- Draws curves lo to hi (simultaneously), initially {1,1}, max {8,8} -- Press +/- to change hi, shift +/- to change lo, ctrl +/- for both. -- ("=_" are also mapped to "+-", for the non-numpad +/-) -- with javascript_semantics include pGUI.e Ihandle dlg, canvas cdCanvas cddbuffer, cdcanvas integer width, height, lm = 0, tm = 0, -- left and top margins lo = 1, hi = 1 atom cx, cy, h procedure lineTo(atom newX, newY) cdCanvasVertex(cddbuffer, newX-width/2+h+lm, height-newY+2*h+tm) cx = newX cy = newY end procedure procedure lineN() lineTo(cx,cy-2*h) end procedure procedure lineS() lineTo(cx,cy+2*h) end procedure procedure lineE() lineTo(cx+2*h,cy) end procedure procedure lineW() lineTo(cx-2*h,cy) end procedure procedure lineNW() lineTo(cx-h,cy-h) end procedure procedure lineNE() lineTo(cx+h,cy-h) end procedure procedure lineSE() lineTo(cx+h,cy+h) end procedure procedure lineSW() lineTo(cx-h,cy+h) end procedure forward procedure sierN(integer level) forward procedure sierE(integer level) forward procedure sierS(integer level) forward procedure sierW(integer level) procedure sierN(integer level) if level=1 then lineNE() lineN() lineNW() else sierN(level-1) lineNE() sierE(level-1) lineN() sierW(level-1) lineNW() sierN(level-1) end if end procedure procedure sierE(integer level) if level=1 then lineSE() lineE() lineNE() else sierE(level-1) lineSE() sierS(level-1) lineE() sierN(level-1) lineNE() sierE(level-1) end if end procedure procedure sierS(integer level) if level=1 then lineSW() lineS() lineSE() else sierS(level-1) lineSW() sierW(level-1) lineS() sierE(level-1) lineSE() sierS(level-1) end if end procedure procedure sierW(integer level) if level=1 then lineNW() lineW() lineSW() else sierW(level-1) lineNW() sierN(level-1) lineW() sierS(level-1) lineSW() sierW(level-1) end if end procedure procedure sierpinskiCurve(integer level) sierN(level) lineNE() sierE(level) lineSE() sierS(level) lineSW() sierW(level) lineNW() end procedure function redraw_cb(Ihandle /*ih*/) {width, height} = IupGetIntInt(canvas, "DRAWSIZE") if width>height then lm = floor((width-height)/2) tm = 0 width = height else lm = 0 tm = floor((height-width)/2) height = width end if cdCanvasActivate(cddbuffer) for level=lo to hi do cx = width/2 cy = height h = cx/power(2,level+1) cdCanvasBegin(cddbuffer, CD_CLOSED_LINES) sierpinskiCurve(level) cdCanvasEnd(cddbuffer) end for cdCanvasFlush(cddbuffer) return IUP_DEFAULT end function function map_cb(Ihandle ih) cdcanvas = cdCreateCanvas(CD_IUP, ih) cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas) cdCanvasSetBackground(cddbuffer, CD_WHITE) cdCanvasSetForeground(cddbuffer, CD_BLUE) return IUP_DEFAULT end function procedure set_dlg_title() IupSetStrAttribute(dlg, "TITLE", "Sierpinski curve (%d..%d)",{lo,hi}) end procedure function key_cb(Ihandle /*ih*/, atom c) if c=K_ESC then return IUP_CLOSE end if if c=K_F5 then return IUP_DEFAULT end if -- (let browser reload work) c = iup_XkeyBase(c) if find(c,"+=-_") then bool bCtrl = IupGetInt(NULL,"CONTROLKEY"), bShift = IupGetInt(NULL,"SHIFTKEY") if c='+' or c='=' then if bCtrl or not bShift then hi = min(8,hi+1) end if if bCtrl or bShift then lo = min(lo+1,hi) end if elsif c='-' or c='_' then if bCtrl or bShift then lo = max(1,lo-1) end if if bCtrl or not bShift then hi = max(lo,hi-1) end if end if set_dlg_title() cdCanvasClear(cddbuffer) IupUpdate(canvas) end if return IUP_CONTINUE end function procedure main() IupOpen() canvas = IupCanvas("RASTERSIZE=770x770") IupSetCallbacks(canvas, {"MAP_CB", Icallback("map_cb"), "ACTION", Icallback("redraw_cb")}) dlg = IupDialog(canvas) IupSetCallback(dlg, "KEY_CB", Icallback("key_cb")) set_dlg_title() IupShow(dlg) IupSetAttribute(canvas, "RASTERSIZE", NULL) if platform()!=JS then IupMainLoop() IupClose() end if end procedure main()
Processing
<lang Processing>
// https://rosettacode.org/wiki/Sierpinski_curve#C.2B.2B // translation of the GO code https://rosettacode.org/wiki/Sierpinski_curve#Go // output on github at: https://github.com/rupertrussell/sierpinski_curve // animated gif created using: https://ezgif.com/ int width = 770; int height = 770; int level = 6; float cx = width / 2; float cy = height;
float oldx = width/2; float oldy = height;
float h = cx / pow(2, (level+1)); int count = 0;
void setup() {
size(770, 770); noLoop(); // stop draw from looping repeatedly
}
void draw() {
background(0, 0, 255); // blue background stroke(255, 255, 0); // yellow curve squareCurve(level); print("count = " , count); save("Sierpinski_Curve_Level" + level + ".png");
} void squareCurve(int level) {
sierN(level); lineNE(); sierE(level); lineSE(); sierS(level); lineSW(); sierW(level); lineNW(); lineNE(); // needed to close the square in the top left hand corner
}
void lineTo(float newX, float newY) {
if (count == 0) { oldx = newX-width/2+h; oldy = height-newY+2*h; } line(oldx, oldy, newX-width/2+h, height-newY+2*h); // save("line-" + count + ".png"); // save each step in drawing the curve cx = newX; cy = newY;
oldx = newX-width/2+h; oldy = height-newY+2*h; count ++;
}
void lineN() {
lineTo(cx, cy-2*h);
} void lineS() {
lineTo(cx, cy+2*h);
} void lineE() {
lineTo(cx+2*h, cy);
} void lineW() {
lineTo(cx-2*h, cy);
} void lineNW() {
lineTo(cx-h, cy-h);
} void lineNE() {
lineTo(cx+h, cy-h);
} void lineSE() {
lineTo(cx+h, cy+h);
} void lineSW() {
lineTo(cx-h, cy+h);
}
void sierN(int level) {
if (level == 1) { lineNE(); lineN(); lineNW(); } else { sierN(level - 1); lineNE(); sierE(level - 1); lineN(); sierW(level - 1); lineNW(); sierN(level - 1); }
}
void sierE(int level) {
if (level == 1) { lineSE(); lineE(); lineNE(); } else { sierE(level - 1); lineSE(); sierS(level - 1); lineE(); sierN(level - 1); lineNE(); sierE(level - 1); }
}
void sierS(int level) {
if (level == 1) { lineSW(); lineS(); lineSE(); } else { sierS(level - 1); lineSW(); sierW(level - 1); lineS(); sierE(level - 1); lineSE(); sierS(level - 1); }
}
void sierW(int level) {
if ( level == 1) { lineNW(); lineW(); lineSW(); } else { sierW(level - 1); lineNW(); sierN(level - 1); lineW(); sierS(level - 1); lineSW(); sierW(level - 1); }
} </lang>
- Output:
Offsite image at Sierpinski_Curve_Level5.png Offsite image at Sierpinski_Curve_Level6.png Offsite image at level 5 animated gif
Python
<lang python>import numpy as np import matplotlib.pyplot as plt from matplotlib.colors import hsv_to_rgb as hsv
def curve(axiom, rules, angle, depth):
for _ in range(depth): axiom = .join(rules[c] if c in rules else c for c in axiom)
a, x, y = 0, [0], [0] for c in axiom: match c: case '+': a += 1 case '-': a -= 1 case 'F' | 'G': x.append(x[-1] + np.cos(a*angle*np.pi/180)) y.append(y[-1] + np.sin(a*angle*np.pi/180))
l = len(x) # this is very slow, but pretty colors for i in range(l - 1): plt.plot(x[i:i+2], y[i:i+2], color=hsv([i/l, 1, .7])) plt.gca().set_aspect(1) plt.show()
curve('F--XF--F--XF', {'X': 'XF+G+XF--F--XF+G+X'}, 45, 5)
- curve('F+XF+F+XF', {'X': 'XF-F+F-XF+F+XF-F+F-X'}, 90, 5)
- curve('F', {'F': 'G-F-G', 'G': 'F+G+F'}, 60, 7)
- curve('A', {'A': '+BF-AFA-FB+', 'B': '-AF+BFB+FA-'}, 90, 6)
- curve('FX+FX+', {'X': 'X+YF', 'Y': 'FX-Y'}, 90, 12)</lang>
Output in the plot window.
Quackery
<lang Quackery> [ $ "turtleduck.qky" loadfile ] now!
[ stack ] is switch.arg ( --> [ ) [ switch.arg put ] is switch ( x --> ) [ switch.arg release ] is otherwise ( --> ) [ switch.arg share != iff ]else[ done otherwise ]'[ do ]done[ ] is case ( x --> ) [ $ "" swap witheach [ nested quackery join ] ] is expand ( $ --> $ ) [ $ "L" ] is L ( $ --> $ ) [ $ "R" ] is R ( $ --> $ ) [ $ "F" ] is F ( $ --> $ )
[ $ "G" ] is G ( $ --> $ ) [ $ "AFLGLAFRRFRRAFLGLA" ] is A ( $ --> $ ) $ "FRRAFRRFRRAF" 5 times expand turtle 1 8 turn witheach [ switch [ char L case [ -1 8 turn ] char R case [ 1 8 turn ] char A case [ ( ignore ) ] otherwise [ 5 1 walk ] ] ] -1 8 turn</lang>
- Output:
Raku
(formerly Perl 6)
<lang perl6>use SVG;
role Lindenmayer {
has %.rules; method succ { self.comb.map( { %!rules{$^c} // $c } ).join but Lindenmayer(%!rules) }
}
my $sierpinski = 'F--XF--F--XF' but Lindenmayer( { X => 'XF+G+XF--F--XF+G+X' } );
$sierpinski++ xx 5;
my $dim = 640; my $scale = 8; my $dir = pi/4; my @points = (316, -108);
for $sierpinski.comb {
state ($x, $y) = @points[0,1]; state $d = 0; when 'F'|'G' { @points.append: ($x += $scale * $d.cos).round(1), ($y += $scale * $d.sin).round(1) } when '+' { $d -= $dir } when '-' { $d += $dir } default { }
}
my $out = './sierpinski-curve-perl6.svg'.IO;
$out.spurt: SVG.serialize(
svg => [ :width($dim), :height($dim), :rect[:width<100%>, :height<100%>, :fill<black>], :polyline[ :points(@points.join: ','), :fill<black>, :transform("rotate(45, 320, 320)"), :style<stroke:#F7DF1E>, ], ],
);</lang> See: Sierpinski-curve-perl6.svg (offsite SVG image)
Rust
Program output is a file in SVG format. <lang rust>// [dependencies] // svg = "0.8.0"
use svg::node::element::path::Data; use svg::node::element::Path;
struct SierpinskiCurve {
current_x: f64, current_y: f64, current_angle: i32, line_length: f64,
}
impl SierpinskiCurve {
fn new(x: f64, y: f64, length: f64, angle: i32) -> SierpinskiCurve { SierpinskiCurve { current_x: x, current_y: y, current_angle: angle, line_length: length, } } fn rewrite(order: usize) -> String { let mut str = String::from("F--XF--F--XF"); for _ in 0..order { let mut tmp = String::new(); for ch in str.chars() { match ch { 'X' => tmp.push_str("XF+G+XF--F--XF+G+X"), _ => tmp.push(ch), } } str = tmp; } str } fn execute(&mut self, order: usize) -> Path { let mut data = Data::new().move_to((self.current_x, self.current_y)); for ch in SierpinskiCurve::rewrite(order).chars() { match ch { 'F' => data = self.draw_line(data), 'G' => data = self.draw_line(data), '+' => self.turn(45), '-' => self.turn(-45), _ => {} } } Path::new() .set("fill", "none") .set("stroke", "black") .set("stroke-width", "1") .set("d", data) } fn draw_line(&mut self, data: Data) -> Data { let theta = (self.current_angle as f64).to_radians(); self.current_x += self.line_length * theta.cos(); self.current_y -= self.line_length * theta.sin(); data.line_to((self.current_x, self.current_y)) } fn turn(&mut self, angle: i32) { self.current_angle = (self.current_angle + angle) % 360; } fn save(file: &str, size: usize, order: usize) -> std::io::Result<()> { use svg::node::element::Rectangle; let x = 5.0; let y = 10.0; let rect = Rectangle::new() .set("width", "100%") .set("height", "100%") .set("fill", "white"); let mut s = SierpinskiCurve::new(x, y, 7.0, 45); let document = svg::Document::new() .set("width", size) .set("height", size) .add(rect) .add(s.execute(order)); svg::save(file, &document) }
}
fn main() {
SierpinskiCurve::save("sierpinski_curve.svg", 545, 5).unwrap();
}</lang>
- Output:
See: sierpinski_curve.svg (offsite SVG image)
Sidef
Uses the LSystem() class from Hilbert curve. <lang ruby>var rules = Hash(
x => 'xF+G+xF--F--xF+G+x',
)
var lsys = LSystem(
width: 550, height: 550,
xoff: -9, yoff: -271,
len: 5, angle: 45, color: 'dark green',
)
lsys.execute('F--xF--F--xF', 5, "sierpiński_curve.png", rules)</lang> Output image: Sierpiński curve
Wren
<lang ecmascript>import "graphics" for Canvas, Color import "dome" for Window
var PX = 0 var PY = 0 var CX = 0 var CY = 0 var H = 0
class SierpinskiCurve {
construct new(width, height, level, back, fore) { Window.title = "Sierpinski Curve" Window.resize(width, height) Canvas.resize(width, height) _w = width _h = height _l = level _bc = back _fc = fore }
init() { Canvas.cls(Color.blue) CX = _w /2 CY = _h H = CX / 2.pow(_l + 1) PX = CX - _w/2 + 2*H PY = _h - CY + 3*H squareCurve(_l) }
lineTo(newX, newY) { Canvas.line(PX, PY, PX = newX - _w/2 + H, PY = _h - newY + 2*H, _fc, 2) CX = newX CY = newY }
lineN() { lineTo(CX, CY - 2*H) } lineS() { lineTo(CX, CY + 2*H) } lineE() { lineTo(CX + 2*H, CY) } lineW() { lineTo(CX - 2*H, CY) }
lineNW() { lineTo(CX - H, CY - H) } lineNE() { lineTo(CX + H, CY - H) } lineSE() { lineTo(CX + H, CY + H) } lineSW() { lineTo(CX - H, CY + H) }
sierN(level) { if (level == 1) { lineNE() lineN() lineNW() } else { sierN(level - 1) lineNE() sierE(level - 1) lineN() sierW(level - 1) lineNW() sierN(level - 1) } } sierE(level) { if (level == 1) { lineSE() lineE() lineNE() } else { sierE(level - 1) lineSE() sierS(level - 1) lineE() sierN(level - 1) lineNE() sierE(level - 1) } } sierS(level) { if (level == 1) { lineSW() lineS() lineSE() } else { sierS(level - 1) lineSW() sierW(level - 1) lineS() sierE(level - 1) lineSE() sierS(level - 1) } } sierW(level) { if (level == 1) { lineNW() lineW() lineSW() } else { sierW(level - 1) lineNW() sierN(level - 1) lineW() sierS(level - 1) lineSW() sierW(level - 1) } }
squareCurve(level) { sierN(level) lineNE() sierE(level) lineSE() sierS(level) lineSW() sierW(level) lineNW() lineNE() // needed to close the square in the top left hand corner }
update() {}
draw(alpha) {}
}
var Game = SierpinskiCurve.new(770, 770, 5, Color.blue, Color.yellow)</lang>
Yabasic
<lang Yabasic>// Rosetta Code problem: http://rosettacode.org/wiki/Sierpinski_curve // Adapted from https://www.ocg.at/sites/ocg.at/files/EuroLogo2001/P74Batagelj.pdf to Yabasic by Galileo, 01/2022
import turtle
sub Sierp(n, a, h, k)
if n = 0 move(k) : return turn(a) : Sierp(n - 1, -a, h, k) : turn(-a) : move(h) turn(-a) : Sierp(n - 1, -a, h, k) : turn(a)
end sub
sub Sierpinski(n, d)
local i pen(false) goxy(10, 680) pen(true) color 255, 255, 0 for i = 1 to 4 Sierp(n, 45, d/sqrt(2), 5*d/6) turn(45) move(d/sqrt(2)) turn(45) next
end sub
startTurtle() Sierpinski(9, 12) </lang>
zkl
Uses Image Magick and the PPM class from http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#zkl <lang zkl>sierpinskiCurve(5) : turtle(_,45,45); // n=5 --> 11,606 characters
fcn sierpinskiCurve(order){
LSystem("F--XF--F--XF",Dictionary("X","XF+G+XF--F--XF+G+X"), order)
} fcn LSystem(axiom,rules,order){ // Lindenmayer system
buf1,buf2 := Data(Void,axiom).howza(3), Data().howza(3); // characters do(order){ buf1.pump(buf2.clear(),'wrap(c){ rules.find(c,c) }); // change if rule t:=buf1; buf1=buf2; buf2=t; // swap buffers } buf1
}
fcn turtle(curve,angle,startAngle){ // angles in degrees
const D=10.0; dir:=startAngle; img,color := PPM(800,800), 0x00ff00; // green on black x,y := 15, img.h - x; foreach c in (curve){ switch(c){
case("F","G"){ // draw forward a,b := D.toRectangular(dir.toFloat().toRad()); img.line(x,y, (x+=a.round()),(y+=b.round()), color) } case("+"){ dir=(dir + angle)%360; } // turn left angle case("-"){ dir=(dir - angle)%360; } // turn right angle
} } img.writeJPGFile("sierpinskiCurve.zkl.jpg");
}</lang>
- Output:
Offsite image at Sierpinski curve order 5