あけましておめでとうございます 2015

あけましておめでとうございます。日本のプログラマには古来より「正月はフラクタル」という習わしがあります。正月はフラクタルに触れて心穏やかに過ごそうというものです。

今年は L-system で遊んでみました。以下でススキっぽいのが描けます。

$ ruby 2015-fractal.rb 7 20 X X:A-[[X]+X]+A[+AX]-X A:AA

出力には Sixel を使ってみました。mltermで動作確認。

コッホ曲線。

$ ruby 2015-fractal.rb 4 90 A A:A+A-A-A+A

ドラゴン曲線。

$ ruby 2015-fractal.rb 16 90 AX X:X+YA+ Y:-AX-Y

C 曲線。

$ ruby 2015-fractal.rb 13 45 A A:+A--A+

ヒルベルト曲線。

$ ruby 2015-fractal.rb 6 90 X X:-YA+XAX+AY- Y:+XA-YAY-AX+ A:A
$ ruby 2015-fractal.rb 4 90 X X:XAYAX+A+YAXAY-A-XAYAX Y:YAXAY-A-XAYAX+A+YAXAY A:A

シェルピンスキーのギャスケット。

$ ruby 2015-fractal.rb 9 60 X X:YA-XA-YA Y:XA+YA+XA A:

ペンローズ・タイル

$ ruby 2015-fractal.rb 4 36 [N]++[N]++[N]++[N]++[N] M:OA++PA----NA[-OA----MA]++ N:+OA--PA[---MA--NA]+ O:-MA++NA[+++OA++PA]- P:--OA++++MA[+PA++++NA]--NA A:

Sixel 初めて使ってみましたが、簡単でいいですね。何か作りたくなった。

# 2015-fractal.rb

# read L-system definition
if ARGV.size < 4
  puts "usage: ruby #$0 <iteration> <angle> <init> <rules...>"
  exit 1
end
Iteration = ARGV.shift.to_i
Angle     = ARGV.shift.to_i
Init      = ARGV.shift
Rules = {}
ARGV.each do |s|
  pre, post = *s.split(":",2)
  Rules[pre] = post
end

# generate a program by L-system
s = Init
Iteration.times { s = s.gsub(/\w/) {|c| Rules[c] } }
s = s.gsub(/[B-Z]/, "")

# run the generated program
mod = 180 / Angle
lines  = {}     # drawn lines
pos = [0] * mod # current position (represented by polynomial)
dir = 0         # current angle
stack = []
s.each_char do |c|
  case c
  when ?A
    npos = pos.dup
    npos[dir % mod] += dir < mod ? 1 : -1
    line = [pos, npos].sort
    lines[line] = true if !lines[line]
    pos = npos
  when ?+ then dir = (dir + 1) % (mod * 2)
  when ?- then dir = (dir - 1) % (mod * 2)
  when ?[ then stack << [pos, dir]
  when ?] then pos, dir = *stack.pop
  end
end

# translate polynomials to the actual point
lines = lines.keys.map do |line|
  line.map do |point|
    point.map.with_index do |mag, i|
      Complex.polar(mag, Math::PI * Angle * i / 180)
    end.inject(&:+)
  end
end

# render it to the buffer
S = 450
l, r = lines.flatten.map {|p| p.real }.minmax
u, d = lines.flatten.map {|p| p.imag }.minmax
scale = S / 1.1 / [r - l, d - u].max
translate = Complex(r + l, d + u) / 2
fine = [(scale / 3).round, 4].max
image = (0 ... S / 6).map { [0] * S }
lines.each do |p1, p2|
  fine.times do |i|
    p = ((p1 * i + p2 * (fine - i)) / fine - translate) * scale
    x = (S / 2 + p.real).round
    y = (S / 2 + p.imag).round
    image[y / 6][x] |= 1 << (y % 6)
  end
end

# output the buffer as Sixel
r = (100 * rand).round
g = (100 * rand).round
b = (100 * rand).round
print "\eP0;0;0;q#0;2;0;0;0#1;2;#{ r };#{ g };#{ b }#1"
image.each do |line|
  print line.map {|c| (c + 0x3f).chr }.join + "-"
end
print "\e\\"