Task: Penrose tiling




Go: 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 

const theta = math.Pi / 5 

func setupPrototiles(w, h int) []tile {
    var proto []tile
    
    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})
            }
        }
    }
    
    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") 
        } else {
            dc.SetHexColor("FFFF00") 
        }
        dc.FillPreserve()
        dc.SetHexColor("A9A9A9") 
        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")
}


Java: 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 {
    
    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; 
    static final double T = toRadians(36); 

    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<>();

        
        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));
                }
            }
        }
        
        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);
        });
    }
}