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