Penrose tiling

From Rosetta Code
Penrose tiling is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

A Penrose tiling can cover an entire plane without creating a pattern that periodically repeats.

There are many tile sets that can create non-periodic tilings, but those can typically also be used to create a periodic tiling. What makes Penrose tiles special is that they can only be used to produce non-periodic tilings.

The two best-known Penrose tile sets are Kite and Dart (P2) and Thin Rhombus and Fat Rhombus (P3)

These so-called prototiles are usually depicted with smooth edges, but in reality Penrose tiles have interlocking tabs and cut-outs like the pieces of a jigsaw puzzle. For convenience these deformations are often replaced with matching rules, which ensure that the tiles are only connected in ways that guarantee a non-periodic tiling. (Otherwise, for instance, you could combine the kite and dart to form a rhombus, and easily create a periodic tiling from there.)

You can construct a Penrose tiling by setting up some prototiles, and adding tiles through trial and error, backtracking whenever you get stuck.

More commonly a method is used that takes advantage of the fact that Penrose tilings, like fractals, have a self-similarity on different levels. When zooming out it can be observed that groups of tiles are enclosed in areas that form exactly the same pattern as the tiles on the lower level. Departing from an inflated level, the prototiles can be subdivided into smaller tiles, always observing the matching rules. The subdivision may have to be repeated several times, before the desired level of detail is reached. This process is called deflation.

More information can be found through the links below.

The task: fill a rectangular area with a Penrose tiling.

See also



11l

Translation of: Python
F penrose(depth)
   print(‘<svg viewBox="-100 -100 200 200" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <path id="A0" d="M 80.90169943749474 58.778525229247315 L 0 0 100 0" stroke="black" fill="#8bc" />
    <path id="B0" d="M 0 0 80.90169943749474 58.778525229247315 161.80339887498948 0" stroke="black" fill="#97e" />’)

   L(d) 0 .< depth
      print(‘  <g id="A’(d + 1)‘" transform="translate(100, 0) scale(0.6180339887498949)">
        <use href="#A’d‘" transform="rotate(108)" />
        <use href="#B’d‘" transform="scale(-1, 1)" />
    </g>
    <g id="B’(d + 1)‘">
        <use href="#A’(d + 1)‘" />
        <use href="#B’d‘" transform="translate(100, 0) scale(0.6180339887498949) rotate(144) translate(-80.90169943749474,-58.778525229247315)"/>
    </g>’)

   print(‘  <g id="G">
        <use href="#A’depth‘"/>
        <use href="#A’depth‘" transform="scale(1, -1)" />
    </g>
  </defs>
  <g transform="scale(2, 2)">
    <use href="#G" transform="rotate(-144)" />
    <use href="#G" transform="rotate(-72)" />
    <use href="#G" transform="rotate(0)" />
    <use href="#G" transform="rotate(72)" />
    <use href="#G" transform="rotate(144)" />
  </g>
</svg>’)

penrose(6)

C++

Translation of: Nim
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>

int main() {
    std::ofstream out("penrose_tiling.svg");
    if (!out) {
        std::cerr << "Cannot open output file.\n";
        return EXIT_FAILURE;
    }
    std::string penrose("[N]++[N]++[N]++[N]++[N]");
    for (int i = 1; i <= 4; ++i) {
        std::string next;
        for (char ch : penrose) {
            switch (ch) {
            case 'A':
                break;
            case 'M':
                next += "OA++PA----NA[-OA----MA]++";
                break;
            case 'N':
                next += "+OA--PA[---MA--NA]+";
                break;
            case 'O':
                next += "-MA++NA[+++OA++PA]-";
                break;
            case 'P':
                next += "--OA++++MA[+PA++++NA]--NA";
                break;
            default:
                next += ch;
                break;
            }
        }
        penrose = std::move(next);
    }
    const double r = 30;
    const double pi5 = 0.628318530717959;
    double x = r * 8, y = r * 8, theta = pi5;
    std::set<std::string> svg;
    std::stack<std::tuple<double, double, double>> stack;
    for (char ch : penrose) {
        switch (ch) {
        case 'A': {
            double nx = x + r * std::cos(theta);
            double ny = y + r * std::sin(theta);
            std::ostringstream line;
            line << std::fixed << std::setprecision(3) << "<line x1='" << x
                 << "' y1='" << y << "' x2='" << nx << "' y2='" << ny << "'/>";
            svg.insert(line.str());
            x = nx;
            y = ny;
        } break;
        case '+':
            theta += pi5;
            break;
        case '-':
            theta -= pi5;
            break;
        case '[':
            stack.push({x, y, theta});
            break;
        case ']':
            std::tie(x, y, theta) = stack.top();
            stack.pop();
            break;
        }
    }
    out << "<svg xmlns='http://www.w3.org/2000/svg' height='" << r * 16
        << "' width='" << r * 16 << "'>\n"
        << "<rect height='100%' width='100%' fill='black'/>\n"
        << "<g stroke='rgb(255,165,0)'>\n";
    for (const auto& line : svg)
        out << line << '\n';
    out << "</g>\n</svg>\n";
    return EXIT_SUCCESS;
}
Output:

Media:Penrose tiling cpp.svg

EasyLang

Run it

proc lsysexp level . axiom$ rules$[] .
   for l to level
      an$ = ""
      for c$ in strchars axiom$
         for i = 1 step 2 to len rules$[]
            if rules$[i] = c$
               c$ = rules$[i + 1]
               break 1
            .
         .
         an$ &= c$
      .
      swap axiom$ an$
   .
.
stack[] = [ ]
proc lsysdraw axiom$ x y ang lng . .
   linewidth 0.3
   move x y
   for c$ in strchars axiom$
      if c$ = "E"
         x += cos dir * lng
         y += sin dir * lng
         line x y
      elif c$ = "-"
         dir -= ang
      elif c$ = "+"
         dir += ang
      elif c$ = "["
         stack[] &= x
         stack[] &= y
         stack[] &= dir
      elif c$ = "]"
         l = len stack[]
         x = stack[l - 2]
         y = stack[l - 1]
         dir = stack[l]
         len stack[] -3
         move x y
      .
   .
.
axiom$ = "[b]++[b]++[b]++[b]++[b]"
rules$[] = [ "a" "cE++dE----bE[-cE----aE]++" "b" "+cE--dE[---aE--bE]+" "c" "-aE++bE[+++cE++dE]-" "d" "--cE++++aE[+dE++++bE]--bE" "E" "" ]
lsysexp 6 axiom$ rules$[]
lsysdraw axiom$ 50 50 36 4

FreeBASIC

Translation of: Phython

Not much of a FreeBASIC program: majority of this code is string literals that dumps an SVG on output.

Sub penrose(depth As Integer)
    Dim As String svg
    svg = "<svg viewBox=""-100 -100 200 200"" xmlns=""http://www.w3.org/2000/svg"">" + !"\n"
    svg &= "  <defs>" + !"\n"
    svg &= "	<path id=""A0"" d=""M 80.90169943749474 58.778525229247315 L 0 0 100 0"" stroke=""black"" fill=""#8bc"" />" + !"\n"
    svg &= "	<path id=""B0"" d=""M 0 0 80.90169943749474 58.778525229247315 161.80339887498948 0"" stroke=""black"" fill=""#97e"" />" + !"\n"
    
    For d As Integer = 0 To 5
        svg &=  "	<g id=""A" : svg &= (d+1) : svg &= """ transform=""translate(100, 0) scale(0.6180339887498949)"">" + !"\n"
        svg +=  "		<use href=" & """" & "#A" & d & """ transform=""rotate(108)"" />" + !"\n"
        svg &=  "		<use href=" & """" & "#B" & d & """ transform=""scale(-1, 1)"" />" + !"\n"
        svg &=  "	</g>" + !"\n"
        svg &=  "	<g id=""B" & d+1 & """>" + !"\n"
        svg &=  "		<use href=" & """" & "#A" & d+1 & """ />" + !"\n"
        svg &=  "		<use href=" & """" & "#B" & d & """ transform=""translate(100, 0) scale(0.6180339887498949) rotate(144) translate(-80.90169943749474,-58.778525229247315)""/>" + !"\n"
        svg &=  "	</g>" + !"\n"
    Next    
    
    svg &=  "	<g id=""G"">" + !"\n"
    svg &=  "		<use href=""#A6""/>" + !"\n"
    svg &=  "		<use href=""#A6"" transform=""scale(1, -1)"" />" + !"\n"
    svg &=  "	</g>" + !"\n"
    svg &=  "  </defs>" + !"\n"
    
    svg &=  "  <g transform=""scale(2, 2)"">" + !"\n"
    svg &=  "	<use href=""#G"" transform=""rotate(-144)"" />" + !"\n"
    svg &=  "	<use href=""#G"" transform=""rotate(-72)"" />" + !"\n"
    svg &=  "	<use href=""#G"" transform=""rotate(0)"" />" + !"\n"
    svg &=  "	<use href=""#G"" transform=""rotate(72)"" />" + !"\n"
    svg &=  "	<use href=""#G"" transform=""rotate(144)"" />" + !"\n"
    svg &=  "  </g>" + !"\n"
    svg &=  "</svg>"
    Print #1, svg
End Sub

Open "Penrose_tiling.svg" For Output As #1
penrose(6)
Close #1

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.

In this page you can see and run the program(s) related to this task and their results. You can also change either the programs or the parameters they are called with, for experimentation, but remember that these programs were created with the main purpose of showing a clear solution of the task, and they generally lack any kind of validation.

Solution

It can be done using an L-system. There are generic functions written in Fōrmulæ to compute an L-system in the page L-system.

The program that creates a Penrose tiling is:

Go

Library: Go Graphics
Translation of: Java
package main

import (
    "github.com/fogleman/gg"
    "math"
)

type tiletype int

const (
    kite tiletype = iota
    dart
)

type tile struct {
    tt          tiletype
    x, y        float64
    angle, size float64
}

var gr = (1 + math.Sqrt(5)) / 2 // golden ratio

const theta = math.Pi / 5 // 36 degrees in radians

func setupPrototiles(w, h int) []tile {
    var proto []tile
    // sun
    for a := math.Pi/2 + theta; a < 3*math.Pi; a += 2 * theta {
        ww := float64(w / 2)
        hh := float64(h / 2)
        proto = append(proto, tile{kite, ww, hh, a, float64(w) / 2.5})
    }
    return proto
}

func distinctTiles(tls []tile) []tile {
    tileset := make(map[tile]bool)
    for _, tl := range tls {
        tileset[tl] = true
    }
    distinct := make([]tile, len(tileset))
    for tl, _ := range tileset {
        distinct = append(distinct, tl)
    }
    return distinct
}

func deflateTiles(tls []tile, gen int) []tile {
    if gen <= 0 {
        return tls
    }
    var next []tile
    for _, tl := range tls {
        x, y, a, size := tl.x, tl.y, tl.angle, tl.size/gr
        var nx, ny float64
        if tl.tt == dart {
            next = append(next, tile{kite, x, y, a + 5*theta, size})
            for i, sign := 0, 1.0; i < 2; i, sign = i+1, -sign {
                nx = x + math.Cos(a-4*theta*sign)*gr*tl.size
                ny = y - math.Sin(a-4*theta*sign)*gr*tl.size
                next = append(next, tile{dart, nx, ny, a - 4*theta*sign, size})
            }
        } else {
            for i, sign := 0, 1.0; i < 2; i, sign = i+1, -sign {
                next = append(next, tile{dart, x, y, a - 4*theta*sign, size})
                nx = x + math.Cos(a-theta*sign)*gr*tl.size
                ny = y - math.Sin(a-theta*sign)*gr*tl.size
                next = append(next, tile{kite, nx, ny, a + 3*theta*sign, size})
            }
        }
    }
    // remove duplicates
    tls = distinctTiles(next)
    return deflateTiles(tls, gen-1)
}

func drawTiles(dc *gg.Context, tls []tile) {
    dist := [2][3]float64{{gr, gr, gr}, {-gr, -1, -gr}}
    for _, tl := range tls {
        angle := tl.angle - theta
        dc.MoveTo(tl.x, tl.y)
        ord := tl.tt
        for i := 0; i < 3; i++ {
            x := tl.x + dist[ord][i]*tl.size*math.Cos(angle)
            y := tl.y - dist[ord][i]*tl.size*math.Sin(angle)
            dc.LineTo(x, y)
            angle += theta
        }
        dc.ClosePath()
        if ord == kite {
            dc.SetHexColor("FFA500") // orange
        } else {
            dc.SetHexColor("FFFF00") // yellow
        }
        dc.FillPreserve()
        dc.SetHexColor("A9A9A9") // dark gray
        dc.SetLineWidth(1)
        dc.Stroke()
    }
}

func main() {
    w, h := 700, 450
    dc := gg.NewContext(w, h)
    dc.SetRGB(1, 1, 1)
    dc.Clear()
    tiles := deflateTiles(setupPrototiles(w, h), 5)
    drawTiles(dc, tiles)
    dc.SavePNG("penrose_tiling.png")
}
Output:
Image same as Java entry.

J

Translation of: perl
require'format/printf'
penrosesvg=: {{
  penrose=. rplc&(".{{)n
    'A';'';
    'M';'OA++PA----NA(-OA----MA)++';
    'N';'+OA--PA(---MA--NA)+';
    'O';'-MA++NA(+++OA++PA)-';
    'P';'--OA++++MA(+PA++++NA)--NA'
}}-.LF)^:y '(N)++(N)++(N)++(N)++(N)'
  LINE=. 2 2$0
  A=. a=. o.%5
  R=. 20
  LINES=. STACK=. EMPTY
  for_ch. penrose do.
    select. ch
      case. 'A' do. LINES=. LINES,,LINE=. (R*0,:2 1 o. A)+"1 {:LINE
      case. '+' do. A=. A+a
      case. '-' do. A=. A-a
      case. '(' do. STACK=. STACK, A;LINE
      case. ')' do. STACK=. }: STACK [ 'A LINE'=. {: STACK
    end.
  end.
  OFF=. 25+>.>./,LINES=. ~.LINES
  assert 1<(F=.'penrose_tiling_%d.svg' sprintf y) fwrite~ {{)n
<svg xmlns="http://www.w3.org/2000/svg" height="%d" width="%d">
  <rect height="100%%" width="100%%" style="fill:black" />
%s
</svg>
}} sprintf (2#<2*OFF),<}:,{{)n
  <line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" style="stroke:rgb(255,165,0)"/>
}} sprintf"1 OFF+LINES 
  (jpathsep 1!:43''),'/',F
}}

Example images (linked): penrosesvg 1, penrosesvg 2, penrosesvg 3, penrosesvg 4, penrosesvg 5

Java

Works with: Java version 8
import java.awt.*;
import java.util.List;
import java.awt.geom.Path2D;
import java.util.*;
import javax.swing.*;
import static java.lang.Math.*;
import static java.util.stream.Collectors.toList;

public class PenroseTiling extends JPanel {
    // ignores missing hash code
    class Tile {
        double x, y, angle, size;
        Type type;

        Tile(Type t, double x, double y, double a, double s) {
            type = t;
            this.x = x;
            this.y = y;
            angle = a;
            size = s;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof Tile) {
                Tile t = (Tile) o;
                return type == t.type && x == t.x && y == t.y && angle == t.angle;
            }
            return false;
        }
    }

    enum Type {
        Kite, Dart
    }

    static final double G = (1 + sqrt(5)) / 2; // golden ratio
    static final double T = toRadians(36); // theta

    List<Tile> tiles = new ArrayList<>();

    public PenroseTiling() {
        int w = 700, h = 450;
        setPreferredSize(new Dimension(w, h));
        setBackground(Color.white);

        tiles = deflateTiles(setupPrototiles(w, h), 5);
    }

    List<Tile> setupPrototiles(int w, int h) {
        List<Tile> proto = new ArrayList<>();

        // sun
        for (double a = PI / 2 + T; a < 3 * PI; a += 2 * T)
            proto.add(new Tile(Type.Kite, w / 2, h / 2, a, w / 2.5));

        return proto;
    }

    List<Tile> deflateTiles(List<Tile> tls, int generation) {
        if (generation <= 0)
            return tls;

        List<Tile> next = new ArrayList<>();

        for (Tile tile : tls) {
            double x = tile.x, y = tile.y, a = tile.angle, nx, ny;
            double size = tile.size / G;

            if (tile.type == Type.Dart) {
                next.add(new Tile(Type.Kite, x, y, a + 5 * T, size));

                for (int i = 0, sign = 1; i < 2; i++, sign *= -1) {
                    nx = x + cos(a - 4 * T * sign) * G * tile.size;
                    ny = y - sin(a - 4 * T * sign) * G * tile.size;
                    next.add(new Tile(Type.Dart, nx, ny, a - 4 * T * sign, size));
                }

            } else {

                for (int i = 0, sign = 1; i < 2; i++, sign *= -1) {
                    next.add(new Tile(Type.Dart, x, y, a - 4 * T * sign, size));

                    nx = x + cos(a - T * sign) * G * tile.size;
                    ny = y - sin(a - T * sign) * G * tile.size;
                    next.add(new Tile(Type.Kite, nx, ny, a + 3 * T * sign, size));
                }
            }
        }
        // remove duplicates
        tls = next.stream().distinct().collect(toList());

        return deflateTiles(tls, generation - 1);
    }

    void drawTiles(Graphics2D g) {
        double[][] dist = {{G, G, G}, {-G, -1, -G}};
        for (Tile tile : tiles) {
            double angle = tile.angle - T;
            Path2D path = new Path2D.Double();
            path.moveTo(tile.x, tile.y);

            int ord = tile.type.ordinal();
            for (int i = 0; i < 3; i++) {
                double x = tile.x + dist[ord][i] * tile.size * cos(angle);
                double y = tile.y - dist[ord][i] * tile.size * sin(angle);
                path.lineTo(x, y);
                angle += T;
            }
            path.closePath();
            g.setColor(ord == 0 ? Color.orange : Color.yellow);
            g.fill(path);
            g.setColor(Color.darkGray);
            g.draw(path);
        }
    }

    @Override
    public void paintComponent(Graphics og) {
        super.paintComponent(og);
        Graphics2D g = (Graphics2D) og;
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        drawTiles(g);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame f = new JFrame();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setTitle("Penrose Tiling");
            f.setResizable(false);
            f.add(new PenroseTiling(), BorderLayout.CENTER);
            f.pack();
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        });
    }
}

jq

Adapted from Perl

Works with: jq

Works with gojq, the Go implementation of jq

The SVG viewBox parameters are computed dynamically.

def pi: 4 * (1|atan);

def rules:
  {A : "",
   M : "OA++PA----NA[-OA----MA]++",
   N : "+OA--PA[---MA--NA]+",
   O : "-MA++NA[+++OA++PA]-",
   P : "--OA++++MA[+PA++++NA]--NA",
   "": "[N]++[N]++[N]++[N]++[N]" } ;

# Apply the rules
def penrose($count):
  rules as $rules
  | def repeat($count):
      if $count <= 0 then .
      else gsub("M";"m") | gsub("N";"n") | gsub("O";"o")  | gsub("P";"p")
      | gsub("A"; $rules["A"])
      | gsub("m"; $rules["M"])
      | gsub("n"; $rules["N"])
      | gsub("o"; $rules["O"])
      | gsub("p"; $rules["P"])
      | repeat($count-1)
      end;
  $rules[""] | repeat($count) ;

# Update {svg, x, y, theta, stack, minx, maxx, miny, maxy}
def interpret($z):
  def rnd: 1000*.|round/1000;
  def minmax:
      .minx = ([.minx, .x]|min)
    | .miny = ([.miny, .y]|min)
    | .maxx = ([.maxx, .x]|max)
    | .maxy = ([.maxy, .y]|max) ;

  if   $z == "+" then .theta += pi/5
  elif $z == "-" then .theta -= pi/5
  elif $z == "[" then .stack += [ {x, y, theta} ]
  elif $z == "]" then .stack[-1] as {$x, $y, $theta}
  | .x = $x | .y = $y | .theta = $theta
  | .stack |= .[:-1]
  elif $z == "A"
  then minmax
  | .r as $r
  |.svg += "<line x1='\(.x|rnd)' y1='\(.y|rnd)' "
  | .x += $r * (.theta|cos)
  | .y += $r * (.theta|sin)
  | .svg += "x2='\(.x|rnd)' y2='\(.y|rnd)' " 
  | .svg += "style='stroke:rgb(255,165,0)'/>\n"
  | minmax
  else .
  end ;

def penrose_tiling($n):
  penrose($n)
  | split("") 
  | reduce .[] as $action (
      {x:160, y:160, theta: (pi/5), r: 20,
       minx: infinite, miny: infinite,
       maxx: -infinite, maxy: -infinite,
      svg: "", stack: []};
      interpret($action) ) ;

# viewBox = <min-x> <min-y> <width> <height>
# Input: {svg, minx, miny, maxx, maxy}
def svg:
  ([.minx, .miny] | min - 2 | floor) as $min
  | ([.maxx - .minx,  .maxy - .miny] | max + 2 | ceil) as $size
  | "<svg viewBox=\"\($min) \($min) \($size) \($size)\" xmlns=\"http://www.w3.org/2000/svg\">",
    .svg,
    "</svg>";

penrose_tiling(5)
| svg
Output:

See https://imgur.com/gallery/UflFbPw

Julia

Translation of: Perl
using Printf

function drawpenrose()
    lindenmayer_rules = Dict("A" => "",
        "M" => "OA++PA----NA[-OA----MA]++", "N" => "+OA--PA[---MA--NA]+",
        "O" => "-MA++NA[+++OA++PA]-", "P" => "--OA++++MA[+PA++++NA]--NA")

    rul(x) = lindenmayer_rules[x]

    penrose = replace(replace(replace(replace("[N]++[N]++[N]++[N]++[N]",
        r"[AMNOP]" => rul), r"[AMNOP]" => rul), r"[AMNOP]" => rul), r"[AMNOP]" => rul)

    x, y, theta, r, svglines, stack = 160, 160, π / 5, 20.0, String[], Vector{Real}[]

    for c in split(penrose, "")
        if c == "A"
            xx, yy = x + r * cos(theta), y + r * sin(theta)
            line = @sprintf("<line x1='%.1f' y1='%.1f' x2='%.1f' y2='%.1f' style='stroke:rgb(255,165,0)'/>\n", x, y, xx, yy)
            x, y = xx, yy
            push!(svglines, line)
        elseif c == "+"
            theta += π / 5
        elseif c == "-"
            theta -= π / 5
        elseif c == "["
            push!(stack, [x, y, theta])
        elseif c == "]"
            x, y, theta = pop!(stack)
        end
    end

    svg = join(unique(svglines), "\n")
    fp = open("penrose_tiling.svg", "w")
    write(fp, """<svg xmlns="http://www.w3.org/2000/svg" height="350" width="350"> <rect height="100%" """ *
              """width="100%" style="fill:black" />""" * "\n$svg</svg>")
    close(fp)
end

drawpenrose()

Kotlin

Translation of: Java
// version 1.1.2

import java.awt.*
import java.awt.geom.Path2D
import javax.swing.*

class PenroseTiling(w: Int, h: Int) : JPanel() {
    private enum class Type {
        KITE, DART
    }

    private class Tile(
        val type: Type, 
        val x: Double, 
        val y: Double, 
        val angle: Double, 
        val size: Double 
    ) {
        override fun equals(other: Any?): Boolean {
            if (other == null || other !is Tile) return false
            return type == other.type && x == other.x && y == other.y &&
                   angle == other.angle && size == other.size
        }
    }

    private companion object {
        val G = (1.0 + Math.sqrt(5.0)) / 2.0  // golden ratio 
        val T = Math.toRadians(36.0)          // theta
    }

    private val tiles: List<Tile>

    init {
        preferredSize = Dimension(w, h)
        background = Color.white 
        tiles = deflateTiles(setupPrototiles(w, h), 5)
    }

    private fun setupPrototiles(w: Int, h: Int): List<Tile> {
        val proto = mutableListOf<Tile>()
        var a = Math.PI / 2.0 + T
        while (a < 3.0 * Math.PI) {
            proto.add(Tile(Type.KITE, w / 2.0, h / 2.0, a, w / 2.5))
            a += 2.0 * T
        } 
        return proto
    }

    private fun deflateTiles(tls: List<Tile>, generation: Int): List<Tile> {
        if (generation <= 0) return tls
        val next = mutableListOf<Tile>()
 
        for (tile in tls) {
            val x = tile.x
            val y = tile.y
            val a = tile.angle
            var nx: Double 
            var ny: Double
            val size = tile.size / G
 
            if (tile.type == Type.DART) {
                next.add(Tile(Type.KITE, x, y, a + 5.0 * T, size))
                var sign = 1
                for (i in 0..1) {
                    nx = x + Math.cos(a - 4.0 * T * sign) * G * tile.size
                    ny = y - Math.sin(a - 4.0 * T * sign) * G * tile.size
                    next.add(Tile(Type.DART, nx, ny, a - 4.0 * T * sign, size))
                    sign *= -1
                }
            } 
            else { 
                var sign = 1
                for (i in 0..1) {
                    next.add(Tile(Type.DART, x, y, a - 4.0 * T * sign, size))
                    nx = x + Math.cos(a - T * sign) * G * tile.size
                    ny = y - Math.sin(a - T * sign) * G * tile.size
                    next.add(Tile(Type.KITE, nx, ny, a + 3.0 * T * sign, size))
                    sign *= -1
                }
            }
        }
        // remove duplicates and deflate 
        return deflateTiles(next.distinct(), generation - 1)
    }

    private fun drawTiles(g: Graphics2D) {
        val dist = arrayOf(
            doubleArrayOf(G, G, G),
            doubleArrayOf(-G, -1.0, -G)
        )
        for (tile in tiles) {
            var angle = tile.angle - T
            val path = Path2D.Double()
            path.moveTo(tile.x, tile.y) 
            val ord = tile.type.ordinal
            for (i in 0..2) {
                val x = tile.x + dist[ord][i] * tile.size * Math.cos(angle)
                val y = tile.y - dist[ord][i] * tile.size * Math.sin(angle)
                path.lineTo(x, y)
                angle += T
            }
            path.closePath()
            with(g) {
                color = if (ord == 0) Color.pink else Color.red
                fill(path)
                color = Color.darkGray
                draw(path)
            }
        }
    }

    override fun paintComponent(og: Graphics) {
        super.paintComponent(og)
        val g = og as Graphics2D 
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                           RenderingHints.VALUE_ANTIALIAS_ON)
        drawTiles(g)
    }
}

fun main(args: Array<String>) {
    SwingUtilities.invokeLater {
        val f = JFrame()
        with (f) {
            defaultCloseOperation = JFrame.EXIT_ON_CLOSE
            title = "Penrose Tiling"
            isResizable = false
            add(PenroseTiling(700, 450), BorderLayout.CENTER)
            pack()
            setLocationRelativeTo(null)
            isVisible = true
        }
    }
}

Nim

Translation of: Phix

This is a translation of the Lindenmayer Phix version translated itself from Perl.

import math, strformat, tables

const Lindenmayer = {'A': "",
                     'M': "OA++PA----NA[-OA----MA]++",
                     'N': "+OA--PA[---MA--NA]+",
                     'O': "-MA++NA[+++OA++PA]-",
                     'P': "--OA++++MA[+PA++++NA]--NA"}.toTable

var penrose = "[N]++[N]++[N]++[N]++[N]"

for _ in 1..4:
  var next = ""
  for ch in penrose:
    next.add Lindenmayer.getOrDefault(ch, $ch)
  penrose = move(next)

var
  x, y = 160.0
  theta = PI / 5
  r = 20.0

var svg = ""
var stack: seq[(float, float, float)]

for ch in penrose:
  case ch
  of 'A':
    let (nx, ny) = (x + r * cos(theta), y + r * sin(theta))
    svg.add &"<line x1='{x:.1f}' y1='{y:.1f}' x2='{nx:.1f}' y2='{ny:.1f}'"
    svg.add " style='stroke:rgb(255,165,0)'/>\n"
    (x, y) = (nx, ny)
  of '+':
    theta += PI / 5
  of '-':
    theta -= PI / 5
  of '[':
    stack.add (x, y, theta)
  of ']':
    (x, y, theta) = stack.pop()
  else:
    discard

let svgFile = "penrose_tiling.svg".open(fmWrite)
svgFile.write """
<svg xmlns="http://www.w3.org/2000/svg" height="350" width="350">
<rect height="100%%" width="100%%" style="fill:black" />
"""
svgFile.write svg, "</svg>"
svgFile.close()
Output:

Same output as Perl.

Perl

use constant pi => 2 * atan2(1, 0);

# Generated with a P3 tile set using a Lindenmayer system.
%rules = (
    A => '',
    M => 'OA++PA----NA[-OA----MA]++',
    N => '+OA--PA[---MA--NA]+',
    O => '-MA++NA[+++OA++PA]-',
    P => '--OA++++MA[+PA++++NA]--NA'
);
$penrose = '[N]++[N]++[N]++[N]++[N]';
$penrose =~ s/([AMNOP])/$rules{$1}/eg for 1..4;

# Draw the curve in SVG
($x, $y) = (160, 160);
$theta   = pi/5;
$r       = 20;

for (split //, $penrose) {
    if (/A/) {
        $line  = sprintf "<line x1='%.1f' y1='%.1f' ", $x, $y;
        $line .= sprintf "x2='%.1f' ", $x += $r * cos($theta);
        $line .= sprintf "y2='%.1f' ", $y += $r * sin($theta);
        $line .= "style='stroke:rgb(255,165,0)'/>\n";
        $SVG{$line} = 1;
    } elsif (/\+/) { $theta += pi/5
    } elsif (/\-/) { $theta -= pi/5
    } elsif (/\[/) { push @stack, [$x, $y, $theta]
    } elsif (/\]/) { ($x, $y, $theta) = @{pop @stack} }
}
$svg .= $_ for keys %SVG;
open  $fh, '>', 'penrose_tiling.svg';
print $fh  qq{<svg xmlns="http://www.w3.org/2000/svg" height="350" width="350"> <rect height="100%" width="100%" style="fill:black" />\n$svg</svg>};
close $fh;

Penrose tiling (offsite image)

Phix

Translation of the original Python code. Output can be toggled to look like the java or perl output.

Library: Phix/pGUI
Library: Phix/online

You can run this online here.

--
-- demo\rosetta\Penrose_tiling.exw
-- ===============================
--
--  Resizeable. Press space to iterate/subdivide, C to toggle colour scheme
--
bool yellow_orange = true   -- false = magenta on black, outlines only

with javascript_semantics
constant title = "Penrose tiling"
include pGUI.e

Ihandle dlg, canvas
cdCanvas cddbuffer, cdcanvas

include builtins\complex.e
 
constant golden_ratio = (1 + sqrt(5)) / 2

function subdivide(sequence triangles)
    sequence result = {}
    integer colour
    complex A, B, C, P, Q, R
    for i=1 to length(triangles) do
        {colour, A, B, C} = triangles[i]
        if colour == 0 then
            -- Subdivide orange triangle
            P = complex_add(A,complex_div(complex_sub(B,A),golden_ratio))
            result &= {{0, C, P, B}, {1, P, C, A}}
        else
            -- Subdivide yellow triangle
            Q = complex_add(B,complex_div(complex_sub(A,B),golden_ratio))
            R = complex_add(B,complex_div(complex_sub(C,B),golden_ratio))
            result &= {{1, R, C, A}, {1, Q, R, B}, {0, R, Q, A}}
        end if
    end for
    return result
end function

function initial_wheel()
-- Create an initial wheel of yellow triangles around the origin
    sequence triangles = {}
    complex B, C
    atom phi
    for i=0 to 9 do
        phi = (2*i-1)*PI/10
        B = {cos(phi),sin(phi)}
        phi = (2*i+1)*PI/10
        C = {cos(phi),sin(phi)}
        if mod(i,2)==0 then
            {B, C} = {C, B}  -- mirror every second triangle
        end if
        triangles &= {{0, {0,0}, B, C}}
    end for
    return subdivide(triangles) -- ... and iterate once
end function

sequence triangles = initial_wheel()

integer hw, hh, h

procedure draw_one(sequence triangle, integer colour, mode)
    if yellow_orange then
        cdCanvasSetForeground(cddbuffer, colour)
    end if
    cdCanvasBegin(cddbuffer, mode)
    for i=2 to 4 do
        atom {x,y} = triangle[i]
        cdCanvasVertex(cddbuffer, x*h+hw, y*h+hh) 
    end for
    cdCanvasEnd(cddbuffer)
end procedure

function redraw_cb(Ihandle /*ih*/, integer /*posx*/, /*posy*/)
    {hw, hh} = sq_floor_div(IupGetIntInt(canvas, "DRAWSIZE"),2)
    h = min(hw,hh)
    if yellow_orange then
        cdCanvasSetBackground(cddbuffer, CD_WHITE)
    else
        cdCanvasSetBackground(cddbuffer, CD_BLACK)
        cdCanvasSetForeground(cddbuffer, CD_MAGENTA)
    end if
    cdCanvasActivate(cddbuffer)
    cdCanvasClear(cddbuffer)
    for i=1 to length(triangles) do
        sequence triangle = triangles[i]
        if yellow_orange then
            integer colour = iff(triangle[1]?CD_ORANGE:CD_YELLOW)
            draw_one(triangle,colour,CD_FILL)
        end if
        draw_one(triangle,CD_DARK_GREY,CD_CLOSED_LINES)
    end for
    cdCanvasFlush(cddbuffer)
    return IUP_DEFAULT
end function

function map_cb(Ihandle ih)
    cdcanvas = cdCreateCanvas(CD_IUP, ih)
    cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas)
    return IUP_DEFAULT
end function

function key_cb(Ihandle /*ih*/, atom c)
    if c=K_ESC then return IUP_CLOSE end if
    if c=' ' then
        if length(triangles)<=6100 then -- sane limit
            triangles = subdivide(triangles)
            IupUpdate(canvas)
        else
            IupSetAttribute(dlg,"TITLE",title & " (sane limit reached)")
        end if
    elsif upper(c)='C' then
        yellow_orange = not yellow_orange
        IupUpdate(canvas)
    end if
    return IUP_CONTINUE
end function

procedure main()
    IupOpen()
    canvas = IupCanvas("RASTERSIZE=600x600")
    IupSetCallbacks(canvas, {"MAP_CB", Icallback("map_cb"),
                             "ACTION", Icallback("redraw_cb")})
    dlg = IupDialog(canvas, `TITLE="%s"`,{title})
    IupSetCallback(dlg, "KEY_CB", Icallback("key_cb"))
    IupShow(dlg)
    IupSetAttribute(canvas, "RASTERSIZE", NULL) -- release the minimum limitation
    if platform()!=JS then
        IupMainLoop()
        IupClose()
    end if
end procedure

main()

Lindenmayer/svg

Translation of: Perl

Same output, obviously the resulting file can be opened in a separate browser.

without js
constant Lindenmayer = new_dict({{'A',""},
                                 {'M',"OA++PA----NA[-OA----MA]++"},
                                 {'N',"+OA--PA[---MA--NA]+"},
                                 {'O',"-MA++NA[+++OA++PA]-"},
                                 {'P',"--OA++++MA[+PA++++NA]--NA"}})
string penrose = "[N]++[N]++[N]++[N]++[N]"
for n=1 to 4 do
    string next = ""
    for i=1 to length(penrose) do
        integer ch = penrose[i]
        object l = getd(ch,Lindenmayer)
        next &= iff(l=NULL?ch:l)
    end for
    penrose = next
end for

atom x=160, y=160, theta=PI/5, r = 20
string svg = ""
constant line = "<line x1='%.1f' y1='%.1f' x2='%.1f' y2='%.1f' style='stroke:rgb(255,165,0)'/>\n"
sequence stack = {}
for i=1 to length(penrose) do
    integer ch = penrose[i]
    switch ch do
        case 'A':   atom nx = x+r*cos(theta),
                         ny = y+r*sin(theta)
                    svg &= sprintf(line,{x,y,nx,ny})
                    {x,y} = {nx,ny}
        case '+':   theta += PI/5
        case '-':   theta -= PI/5
        case '[':   stack = append(stack,{x,y,theta})
        case ']':   {x,y,theta} = stack[$]
                    stack = stack[1..$-1]
    end switch
end for
constant svgfmt = """
<svg xmlns="http://www.w3.org/2000/svg" height="350" width="350">
 <rect height="100%%" width="100%%" style="fill:black" />
 %s
</svg>"""
integer fn = open("penrose_tiling.svg","w")
printf(fn,svgfmt,svg)
close(fn)

Processing

LSystem

class LSystem 
{
  int steps = 0;

  String axiom;
  String rule;
  String production;

  float startLength;
  float drawLength;
  float theta;

  int generations;

  LSystem() {
    axiom = "F";
    rule = "F+F-F";
    startLength = 190.0;
    theta = radians(120.0);
    reset();
  }

  void reset() {
    production = axiom;
    drawLength = startLength;
    generations = 0;
  }

  int getAge() {
    return generations;
  }

  void render() {
    translate(width/2, height/2);
    steps += 5;          
    if (steps > production.length()) {
      steps = production.length();
    }
    for (int i = 0; i < steps; i++) {
      char step = production.charAt(i);
      if (step == 'F') {
        rect(0, 0, -drawLength, -drawLength);
        noFill();
        translate(0, -drawLength);
      } 
      else if (step == '+') {
        rotate(theta);
      } 
      else if (step == '-') {
        rotate(-theta);
      } 
      else if (step == '[') {
        pushMatrix();
      } 
      else if (step == ']') {
        popMatrix();            
      }
    }
  }

  void simulate(int gen) {
    while (getAge() < gen) {
      production = iterate(production, rule);
    }
  }

  String iterate(String prod_, String rule_) {
    drawLength = drawLength * 0.6;
    generations++;
    String newProduction = prod_;          
    newProduction = newProduction.replaceAll("F", rule_);
    return newProduction;
  }
}

PenroseLSystem

class PenroseLSystem extends LSystem {

  int steps = 0;
  float somestep = 0.1;
  String ruleW;
  String ruleX;
  String ruleY;
  String ruleZ;

  PenroseLSystem() {
    axiom = "[X]++[X]++[X]++[X]++[X]";
    ruleW = "YF++ZF4-XF[-YF4-WF]++";
    ruleX = "+YF--ZF[3-WF--XF]+";
    ruleY = "-WF++XF[+++YF++ZF]-";
    ruleZ = "--YF++++WF[+ZF++++XF]--XF";
    startLength = 460.0;
    theta = radians(36);  
    reset();
  }

  void useRule(String r_) {
    rule = r_;
  }

  void useAxiom(String a_) {
    axiom = a_;
  }

  void useLength(float l_) {
    startLength = l_;
  }

  void useTheta(float t_) {
    theta = radians(t_);
  }

  void reset() {
    production = axiom;
    drawLength = startLength;
    generations = 0;
  }

  int getAge() {
    return generations;
  }

  void render() {
    translate(width/2, height/2);
    int pushes = 0;
    int repeats = 1;
    steps += 12;          
    if (steps > production.length()) {
      steps = production.length();
    }

    for (int i = 0; i < steps; i++) {
      char step = production.charAt(i);
      if (step == 'F') {
        stroke(255, 60);
        for (int j = 0; j < repeats; j++) {
          line(0, 0, 0, -drawLength);
          noFill();
          translate(0, -drawLength);
        }
        repeats = 1;
      } 
      else if (step == '+') {
        for (int j = 0; j < repeats; j++) {
          rotate(theta);
        }
        repeats = 1;
      } 
      else if (step == '-') {
        for (int j =0; j < repeats; j++) {
          rotate(-theta);
        }
        repeats = 1;
      } 
      else if (step == '[') {
        pushes++;            
        pushMatrix();
      } 
      else if (step == ']') {
        popMatrix();
        pushes--;
      } 
      else if ( (step >= 48) && (step <= 57) ) {
        repeats = (int)step - 48;
      }
    }

    // Unpush if we need too
    while (pushes > 0) {
      popMatrix();
      pushes--;
    }
  }

  String iterate(String prod_, String rule_) {
    String newProduction = "";
    for (int i = 0; i < prod_.length(); i++) {
      char step = production.charAt(i);
      if (step == 'W') {
        newProduction = newProduction + ruleW;
      } 
      else if (step == 'X') {
        newProduction = newProduction + ruleX;
      }
      else if (step == 'Y') {
        newProduction = newProduction + ruleY;
      }  
      else if (step == 'Z') {
        newProduction = newProduction + ruleZ;
      } 
      else {
        if (step != 'F') {
          newProduction = newProduction + step;
        }
      }
    }

    drawLength = drawLength * 0.5;
    generations++;
    return newProduction;
  }

}

PenroseTile

PenroseLSystem ds;

void setup() {
  size(1000, 1000);
  ds = new PenroseLSystem();
  ds.simulate(5);
}

void draw() {
  background(0);
  ds.render();
}

Python

Not much of a python program: majority of this code is string literals that dumps an SVG on output.

def penrose(depth):
    print('''<svg viewBox="-100 -100 200 200" xmlns="http://www.w3.org/2000/svg">
  <defs>
	<path id="A0" d="M 80.90169943749474 58.778525229247315 L 0 0 100 0" stroke="black" fill="#8bc" />
	<path id="B0" d="M 0 0 80.90169943749474 58.778525229247315 161.80339887498948 0" stroke="black" fill="#97e" />''')

    for d in range(depth):
        print(f'''	<g id="A{d+1}" transform="translate(100, 0) scale(0.6180339887498949)">
		<use href="#A{d}" transform="rotate(108)" />
		<use href="#B{d}" transform="scale(-1, 1)" />
	</g>
	<g id="B{d+1}">
		<use href="#A{d+1}" />
		<use href="#B{d}" transform="translate(100, 0) scale(0.6180339887498949) rotate(144) translate(-80.90169943749474,-58.778525229247315)"/>
	</g>''')

    print(f'''	<g id="G">
		<use href="#A{d+1}"/>
		<use href="#A{d+1}" transform="scale(1, -1)" />
	</g>
  </defs>
  <g transform="scale(2, 2)">
	<use href="#G" transform="rotate(-144)" />
	<use href="#G" transform="rotate(-72)" />
	<use href="#G" transform="rotate(0)" />
	<use href="#G" transform="rotate(72)" />
	<use href="#G" transform="rotate(144)" />
  </g>
</svg>''')

penrose(6)

Racket

Translation of: Perl
#lang racket
 
(require racket/draw)
 
(define rules '([M . (O A + + P A - - - - N A < - O A - - - - M A > + +)]
                [N . (+ O A - - P A < - - - M A - - N A > +)]
                [O . (- M A + + N A < + + + O A + + P A > -)]
                [P . (- - O A + + + + M A < + P A + + + + N A > - - N A)]
                [S . (< N > + + < N > + + < N > + + < N > + + < N >)]))
 
(define (get-cmds n cmd)
  (cond
    [(= 0 n) (list cmd)]
    [else (append-map (curry get-cmds (sub1 n))
                      (dict-ref rules cmd (list cmd)))]))
 
(define (make-curve DIM N R OFFSET COLOR BACKGROUND-COLOR)
  (define target (make-bitmap DIM DIM))
  (define dc (new bitmap-dc% [bitmap target]))
  (send dc set-background BACKGROUND-COLOR)
  (send dc set-pen COLOR 1 'solid)
  (send dc clear)
  (for/fold ([x 160] [y 160] [θ (/ pi 5)] [S '()])
            ([cmd (in-list (get-cmds N 'S))])
    (define (draw/values x* y* θ* S*)
      (send/apply dc draw-line (map (curry + OFFSET) (list x y x* y*)))
      (values x* y* θ* S*))
    (match cmd
      ['A (draw/values (+ x (* R (cos θ))) (+ y (* R (sin θ))) θ S)]
      ['+ (values x y (+ θ (/ pi 5)) S)]
      ['- (values x y (- θ (/ pi 5)) S)]
      ['<  (values x y θ (cons (list x y θ) S))]
      ['> (match-define (cons (list x y θ) S*) S)
          (values x y θ S*)]
      [_ (values x y θ S)]))
  target)
 
(make-curve 500 4 20 80 (make-color 255 255 0) (make-color 0 0 0))

Raku

(formerly Perl 6)

Works with: Rakudo version 2018.05

Generated with a P3 tile set using a Lindenmayer system.

use SVG;

role Lindenmayer {
    has %.rules;
    method succ {
	    self.comb.map( { %!rules{$^c} // $c } ).join but Lindenmayer(%!rules)
    }
}

my $penrose = '[N]++[N]++[N]++[N]++[N]' but Lindenmayer(
    {
        A => '',
        M => 'OA++PA----NA[-OA----MA]++',
        N => '+OA--PA[---MA--NA]+',
        O => '-MA++NA[+++OA++PA]-',
        P => '--OA++++MA[+PA++++NA]--NA'
    }
);

$penrose++ xx 4;

my @lines;
my @stack;

for $penrose.comb {
    state ($x, $y) = 300, 200;
    state $d = 55 + 0i;
    when 'A' { @lines.push: 'line' => [:x1($x.round(.01)), :y1($y.round(.01)), :x2(($x += $d.re).round(.01)), :y2(($y += $d.im).round(.01))] }
    when '[' { @stack.push: ($x.clone, $y.clone, $d.clone) }
    when ']' { ($x, $y, $d) = @stack.pop }
    when '+' { $d *= cis -π/5 }
    when '-' { $d *= cis  π/5 }
    default { }
}

say SVG.serialize(
    svg => [
        :600width, :400height, :style<stroke:rgb(250,12,210)>,
        :rect[:width<100%>, :height<100%>, :fill<black>],
        |@lines,
    ],
);

See: Penrose tiling image

Scala

Java Swing Interoperability

Works with: Scala version 2.13
import java.awt.{BorderLayout, Color, Dimension, Graphics, Graphics2D, RenderingHints}
import java.awt.geom.Path2D

import javax.swing.{JFrame, JPanel}

import scala.math._

object PenroseTiling extends App {
  private val (φ, ϑ) = ((1 + sqrt(5)) / 2, toRadians(36)) // golden ratio and 36 degrees
  private val dist: Array[Array[Double]] = Array(Array(φ, φ, φ), Array(-φ, -1, -φ))

  class PenroseTiling extends JPanel {
    private val (w, h) = (700, 450)
    private val tiles: Set[Tile] = deflateTiles(setupPrototiles(w, h), 5)

    override def paintComponent(og: Graphics): Unit = {
      def drawTiles(g: Graphics2D): Unit =
        for (tile <- tiles) {
          val path: Path2D = new Path2D.Double()
          val distL = dist(tile.tileType.id)

          path.moveTo(tile.x, tile.y)
          for {i <- 0 until 3
               ω = tile.α + (i - 1) * ϑ}
            path.lineTo(
              tile.x + distL(i) * tile.size * cos(ω),
              tile.y - distL(i) * tile.size * sin(ω))

          path.closePath()
          g.setColor(if (tile.tileType == Type.Kite) Color.orange else Color.yellow)
          g.fill(path)
          g.setColor(Color.darkGray)
          g.draw(path)
        }

      super.paintComponent(og)
      val g: Graphics2D = og.asInstanceOf[Graphics2D]
      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
      drawTiles(g)
    }

    private def setupPrototiles(w: Int, h: Int): Set[Tile] = (0 to 5).map(n =>
      Tile(Type.Kite, (w / 2).toDouble, (h / 2).toDouble, Pi / 2 + ϑ + n * 2 * ϑ, w / 2.5)).toSet

    @scala.annotation.tailrec
    private def deflateTiles(tls: Set[Tile], generation: Int): Set[Tile] =
      if (generation > 0) {
        val next = for {
          tile <- tls
          size = tile.size / φ
        } yield {

          def nx(factor: Int) = tile.x + cos(tile.α - factor * ϑ) * φ * tile.size
          def ny(factor: Int) = tile.y - sin(tile.α - factor * ϑ) * φ * tile.size

          tile.tileType match {
            case Type.Dart =>
              Seq(Tile(Type.Kite, tile.x, tile.y, tile.α + 5 * ϑ, size)) ++
                (for (sign <- -1 to 1 by 2)
                  yield Tile(Type.Dart, nx(sign * 4), ny(sign * 4), tile.α - 4 * ϑ * sign, size))

            case Type.Kite => (for (sign <- 1 to -1 by -2) yield {
              Seq(Tile(Type.Dart, tile.x, tile.y, tile.α - 4 * ϑ * sign, size),
                Tile(Type.Kite, nx(sign), ny(sign), tile.α + 3 * ϑ * sign, size))
            }).flatten
          }
        }
        deflateTiles(next.flatten, generation - 1)
      } else tls

    private case class Tile(tileType: Type.Type, x: Double, y: Double, α: Double, size: Double)

    private object Type extends Enumeration {
      type Type = Value
      val Kite, Dart = Value
    }

    setPreferredSize(new Dimension(w, h))
    setBackground(Color.white)
  }

  new JFrame("Penrose Tiling") {
    add(new PenroseTiling(), BorderLayout.CENTER)
    pack()
    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE)
    setLocationRelativeTo(null)
    setResizable(false)
    setVisible(true)
  }

}

Sidef

Using the LSystem class defined at Hilbert curve.

var rules = Hash(
    a => 'cE++dE----bE[-cE----aE]++',
    b => '+cE--dE[---aE--bE]+',
    c => '-aE++bE[+++cE++dE]-',
    d => '--cE++++aE[+dE++++bE]--bE',
    E => '',
)

var lsys = LSystem(
    width:  1000,
    height: 1000,

    scale: 1,
    xoff: -500,
    yoff: -500,

    len:   40,
    angle: 36,
    color: 'dark blue',
)

lsys.execute('[b]++[b]++[b]++[b]++[b]', 5, "penrose_tiling.png", rules)

Output image: Penrose tiling

Wren

Translation of: Kotlin
Library: DOME
Library: Wren-dynamic
Library: Wren-set
Library: Wren-polygon
import "graphics" for Canvas, Color
import "dome" for Window
import "math" for Math
import "./dynamic" for Enum, Tuple
import "./set" for Set
import "./polygon" for Polygon

var Type = Enum.create("Type", ["KITE", "DART"])

var Tile = Tuple.create("Tile", ["type", "x", "y", "angle", "size"])

var DistinctTiles = Fn.new { |tiles|
    var tileStr = tiles.map { |t| t.toString }.toList
    var tileSet = Set.new(tileStr)
    var tileDst = []
    for (tile in tiles) {
        var str = tile.toString
        if (tileSet.contains(str)) {
            tileDst.add(tile)
            tileSet.remove(str)
        }
    }
    return tileDst
}

var Radians = Fn.new { |d| d * Num.pi / 180 }

var G = (1 + 5.sqrt) / 2   // golden ratio
var T = Radians.call(36)   // theta

class PenroseTiling {
    construct new(width, height) {
        Window.title = "Penrose Tiling"
        Window.resize(width, height)
        Canvas.resize(width, height)
        _w = width
        _h = height
    }

    init() {
        var tiles = deflateTiles_(setupPrototiles_(_w, _h), 5)
        drawTiles(tiles)
    }

    setupPrototiles_(w, h) {
        var proto = []
        var a = Num.pi / 2 + T
        while (a < 3 * Num.pi) {
            proto.add(Tile.new(Type.KITE, w / 2, h / 2, a, w / 2.5))
            a = a + 2 * T
        }
        return proto
    }

    deflateTiles_(tiles, generation) {
        if (generation <= 0) return tiles
        var next = []
        for (tile in tiles) {
            var x = tile.x
            var y = tile.y
            var a = tile.angle
            var nx
            var ny
            var size = tile.size / G
            if (tile.type == Type.DART) {
                next.add(Tile.new(Type.KITE, x, y, a + 5 * T, size))
                var sign = 1
                for (i in 0..1) {
                    nx = x + Math.cos(a - 4 * T * sign) * G * tile.size
                    ny = y - Math.sin(a - 4 * T * sign) * G * tile.size
                    next.add(Tile.new(Type.DART, nx, ny, a - 4 * T * sign, size))
                    sign = -sign
                }
            } else {
                var sign = 1
                for (i in 0..1) {
                    next.add(Tile.new(Type.DART, x, y, a - 4 * T * sign, size))
                    nx = x + Math.cos(a - T * sign) * G * tile.size
                    ny = y - Math.sin(a - T * sign) * G * tile.size
                    next.add(Tile.new(Type.KITE, nx, ny, a + 3 * T * sign, size))
                    sign = -sign
                }
            }
        }
        // remove duplicates and deflate
        return deflateTiles_(DistinctTiles.call(next), generation - 1)
    }

    drawTiles(tiles) {
        var dist = [ [G, G, G], [-G, -1, -G] ]
        for (tile in tiles) {
            var angle = tile.angle - T
            var x0 = tile.x
            var y0 = tile.y
            var ord = tile.type
            var vertices = [[x0, y0]]
            for (i in 0..2) {
                var x1 = tile.x + dist[ord][i] * tile.size * Math.cos(angle)
                var y1 = tile.y - dist[ord][i] * tile.size * Math.sin(angle)
                vertices.add([x1, y1])
                angle = angle + T
                x0 = x1
                y0 = y1
            }
            var poly = Polygon.quick(vertices)
            poly.drawfill((ord == 0) ? Color.orange : Color.yellow)
            poly.draw(Color.darkgray)
        }
    }

    update() {}

    draw(alpha) {}
}

var Game = PenroseTiling.new(700, 450)