gorogoro.rb を読んだ

gorogoro.rb とは

大江戸Ruby会議08でぺんさんが最後に再生していた頭のおかしい (褒め言葉) Quine。

コードはこちら

https://gist.github.com/tompng/45d93b3386b5986a94b9c3c8beecba69

実装を見ていく

アスキーアートプログラミング構文

まず見慣れた (毎年 Ruby 会議に参加しているうちになぜか見慣れてしまった) アスキーアートプログラミング構文が使われている。

eval((c=%{
  ...
}).split*'')

詳しくは あなたの知らない超絶技巧プログラミングの世界 の第 2 章を参照だけど、%{} の中がプログラムを好きな形に整形したもの。スペースや改行を取り除いた上で、セミコロンで改行してあげると人間が読める程度に復元できる。

Quine

c=(w*26+';eval((c=%{'+c+"}).split*'')").split$/

の辺りが Quine。

eval c= を文字列として再構成して、自身を埋め込んでいる。

メインループ

0.upto(90) {|tm|
  ...
  puts(...)
  sleep(0.05)
}

sleep を入れながら 91 回 puts している。つまりこのプログラム全体は 91 枚のパラパラ漫画。

tm (タイマーかな) を固定すると任意のコマを描画できる。

キャンバス

m = 96
...
cn = (1..m).map { [0] * m }
...
puts(
  (0..47).map {|i|
    m.times.map {|j|
      l, j = [cn[2*i+1][j], cn[2*i][j]]
      %['".:Y,L#])[3*l+j]
    } * ''
  } * $/
)

96 * 96 の 2 次元配列を変数 cn (多分キャンバス) に入れてあり、puts している。

(0..47).map {|i| ... } は最後に $/ つまり "\n"* (Array#join) しているので m.times.map {|j| ... } が空文字で join しているので

各画素は

%['".:Y,L#])[3*l+j]

で、3*l+j を出力してみると

000000000000000000000034100000011100000000000000000000000000000000000000000000000000000000000000
000000000000000000000041000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000440000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000004400000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000004100000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000044000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000044000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000033343000000000000000000000000000000000000000000000000000000000000000000000000000
000000000003010000000011330000000000000000000000000000000000000000000000000000000000000000000000
000000000000668888888866014300000000000000000000000000000000000000000000000000000000000000000000
000000001062200008888888660143000000000000000000000000000000000000000000000000000000000000000000
000000000066660008888888806014300000000000000000000000000000000000000000000000000000000000000000
000000400888888600288882008004400000000000000000000000000000000000000000000000000000000000000000
000000008888888800000000008004400000000000000000000000000000000000000000000000000000000000000000
000000302888888200688660688004400000000000000000000000000000000000000000000000000000000000000000
000000100888800008888888882034400000000000000000000000000000000000000000000000000000000000000000
000000010028860008888888820344100000000000000000000000000000000000000000000000000000000000000000
000000001300286602288822003441000000000000000000000000000000000000000000000000000000000000000000
000000000013300002200033444100000000000000000000000000000000000000000000000000000000000000000000
000000000000111444444441100000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000001430000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000044000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000004400000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000440000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000044000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000001430000003333333000000000000000000000000000000000000000000000000000000
000000000000000000000000000143034111111111443300000000000000000000000000000000000000000000000000
000000000000000000000000000034100666686666001443000000000000000000000000000000000000000000000000
000000000000000000000000000310062222288888860014300000000000000000000000000000000000000000000000
000000000000000000000000003106200000088888888000430000000000000000000000000000000000000000000000
000000000000000000000000004006888860028888880080040000000000000000000000000000000000000000000000
000000000000000000000000000088888888000222200080040000000000000000000000000000000000000000000000
000000000000000000000000000088888882000660000880040000000000000000000000334444444433000000000000
000000000000000000000000004028888220068888868880040000000000000000000344441100000011143000000000
000000000000000000000000001302888000688888888200400000000000000000004441006688888866001430000000
000000000000000000000000000030028600088888882034144330000000000000044400622222288888866013000003
000000000000000000000000000001300222066222003410000114433300000000444002060000088888880600334441
000000000000000000000000000000001033333333110000000000001114443333440068888860028888820060110000
000000000000000000000000000000000000000000000000000000000000000111440088888880000020000680000000
000000000000000000000000000000000000000000000000000000000000000000440088888880006666006880000000
000000000000000000000000000000000000000000000000000000000000000000143028882200688888888800100000
000000000000000000000000000000000000000000000000000000000000000000014302886000888888888000000000
000000000000000000000000000000000000000000000000000000000000000000000130028600088888820010000000
000000000000000000000000000000000000000000000000000000000000000000000001330022000020001000000000
000000000000000000000000000000000000000000000000000000000000000000000000001100000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

のようになっていて、例えば 0 に置換することで各画素を出力している。目を細めてみるとこれだけでも絵が見えますねw

':. を使って 1 画素を更に上下に分割していたり (そのために行の倍のサイズのキャンバスがある)、#: を使い分けることで色の濃さ(質感)を表現していたりする。

カメラ

x = 1.62 * t + 2
y = -1.44 * t - 2
z = 10 - 7 * t - 4.1 * [tm - 6, 0].max / 9

の辺りがカメラ。

x = 0, y = 0, z = 3 とかに固定すると、その場で画像がクルクル回る。

3D 描画

rn が数式を 3D 描画しているところ?で、ピタゴラスの定理的なものが見えるねフンフンナルホドとか言ってみるけどまだ理解していない。

rn = ->(x, y, z, th) {
  xx = n + m * x / z
  yy = n + m * y / z
  rr = 144 / z
  r1 = 2**2i
  r2 = E**th.i
  tr = ->(x, y, z) {
    x, y = ((x + y.i) * r1).rect
    x, z = ((x + z.i) * r2).rect
    [*((x + y.i) / r1).rect, z]
  }
  ([0, xx - rr].max.ceil..[m - 1, xx + rr].min).each {|ix|
    ([0, yy - rr].max.ceil..[m - 1, yy + rr].min).each {|iy|
      vx, vy, vz = tr[ix / n - 1, iy / n - 1, -2]
      cx, cy, cz = tr[-x, -y, z]
      vv = vx * vx + vy * vy + vz * vz * 3
      vc = vx * cx + vy * cy + vz * cz * 3
      h = 0.3
      d = vc * vc - vv * (cx**2 + cy**2 + cz**2 * 3 - 1 - h * h * 3)
      d < 0 && next

      x2 = cx - vx * s = (h / vz).abs + cz / vz
      y2 = cy - vy * s
      x2**2 + y2**2 > 1 && next

      ...
    }
  }
}

各オブジェクト

lambda が一つのオブジェクトになっていて、例えば雷は tn

tn = ->(i,j) {
  a = 1 - j/52
  b = j>40 ? 0 : 1
  j = n - j/2.0
  (j/2 - a*16) < i && i < (j - b*16)
}

これを描画するとこうなる。

                                                ,#######################
                                                #######################
                                               ,######################
                                               ######################
                                              ,#####################
                                              #####################
                                             ,####################
                                             ####################
                                            ,###################
                                            ###################
                                           ,##################
                                           ##################
                                          ,#################
                                          #################
                                         ,################
                                         ################
                                        ,###############
                                        ###############
                                       ,##############
                                       ##############
                                      ,#############,,,,,,,,,,,,,,,,
                                      #############################
                                     ,############################
                                     ############################
                                    ,###########################
                                    ###########################
                                                   ,##########
                                                   ##########
                                                  ,#########
                                                  #########
                                                 ,########
                                                 ########
                                                ,#######
                                                #######
                                               ,######
                                               ######
                                              ,#####
                                              #####
                                             ,####
                                             ####
                                            ,###
                                            ###
                                           ,##
                                           ##
                                          ,#
                                          #
                                         ,

gt が雷門。

gt = ->(x, y) {
  y -= 0.2
  i = n + x * 168
  j = 24 + y * 84

  # 屋根
  next(x * (-y)**0.5 * 16 % 1 < 0.5 ? 1 : 2) if y < -0.4 && y > -0.9 - x**6 && y > -0.4 / x**2
  # 梁
  next(x.abs * 2.6 % 1 > 0.6 || y > -0.35 || x.abs < 0.1 ? 2 : 0) if y >= -0.4 && y < -0.32 - x**4 / 10
  # 提灯の中の「雷門」って文字列
  next(2) if (0 <= j && j < n && 0 < i && (c[j][i] || w) != w)
  # 提灯
  next(1) if 2 * x**4 + y**4 < 0.01
  # 風神雷神
  x = (x.abs - 0.5).abs
  y < -0.4 || y > 0.55 || x > 0.2 ? 0 : x > 0.12 || y > 0.47 || (y - 0.2) % 0.5 < 0.1 ? y > 0.45 && x > 0.12 ? 2 : 1 : 2 * x * x + y * y < 0.01 + y % 0.05 / 5 ? 1 : 0
}

雷門は特に提灯の中の「雷門」文字部分の実装が意味分かんなくて、

next(2) if (0 <= j && j < n && 0 < i && (c[j][i] || w) != w)

なんと c つまりこのプログラム自身の文字列を参照している。

AA になっているので提灯の中に「雷門」が出現するのだよね。プログラムを左寄せにすると提灯の中も左に寄る。

     ::Y#L::##::##''""'''""''""''""'''""''""''""'''""''""''""''"""''""''""''"""::##::##::##Y
    L::##::##::Y##::##::##L::##::##::###::##::##:::##::##::##Y::##::##::L##::##::##Y:L##::##'
   #::##::Y##::##::Y#L::##::##L::##::##:::##::##:::##::##::L##::##::L##::##::##Y::##::##Y::##'
  "::##L::##::##L::##::###::##::Y##::##:::##::##:::##::##:::##::##Y::##::##Y::##::L##::##::L##'
 "::Y#L::##::Y##::##L::##::Y##::##L::##::###::##:::##::###::##::L##::##Y::##::L##::##Y::##::L#Y
"::Y##::##L::##::Y##::Y##::##L::##::Y##::###::##:::##::###::##Y::##:::##::###::##Y::##::L##::##Y
::Y##::Y##::##L::##L::##:::##::Y##::###::###::##:::##:::##::###::###::##Y::##:::##::L##::L##::##
:Y##::Y##::Y##::###::###::###::##L::##L::##:::##:::##:::##:::##:::##::L##::L##::L##::L##::##Y::#
Y##::Y##::Y##::Y##::Y##:::##:::##:::##:::##:::##:::##:::##Y::##Y::##Y::##Y::##Y::##Y::##Y::##Y::
##::Y##::Y##:::##L::##L::###::###::Y##:::##:::##:::##Y::###::###::L##::L##:::##Y::##Y::##Y::L##:
L::Y##::Y##L::##L::###::Y##:::##L::###::###:::##:::###::###:::##:::##Y::###::L##:::##Y::##Y::L##
::###::Y##L::###::Y##:::##L::###:::##L::###:::##:::###::L##:::###::L##:::##Y::###::L##Y::###::L#
:##L::Y##L::###::Y##L::###:::##L::###:::###:::##:::###:::##Y::###:::###::L##:::###::L##Y::L##:::
##L::Y##:::###::Y##L::Y##L::###:::###:::###::Y##:::###:::###:::##Y::L##Y::###:::###::L##Y::L##Y:
L::Y##L::Y##L::Y##L::Y##L::Y##L::Y##L::Y##:::###:::###:::###:::###:::###:::###:::###:::###:::##Y
::Y##L::Y##L::Y##L:::###:::###:::###:::###:::###:::###Y::L###:::###:::###:::###:::###:::L##Y::L#
Y##L:::###L::Y##L::Y###:::###:::Y###:::###:::###::::###:::###:::###Y::L###:::###:::L##Y::L##Y:::
########               ##########         #############        ###########               #######
,,######"""""""""""""""##########"""""""""#############""""""""###########"""""""""""""""######,
   :::::    ,,,,,,,,,,,LLLLLL,,,,,,,,,,,,,LLLLLLLLLLLLL,,,,,,,,,,,,,LLLLL,,,,,,,,,,,,    :::::
   ::::::::::::::::::::::::::      '':::YYYY::::::::::::::::''      ::::::::::::::::::::::::::
   :::::::::::::::::::::::::: ####:::::::::::::::::::::::::::::'    ::::::::::::::::::::::::::
   :::::::::::::::::::::::::: ##":::::::::::::::::::::::::::::::'   ::::::::::::::::::::::::::
   :::::...............:::::: ####YYYYYYYYYY:::::::::::::::::::::   :::::................:::::
   :::::               :::::: ####LL:::::::::::::::::::::::::::::   :::::                :::::
   :::::               :::::: ####YYYYYYYYYYYYYYYYYYYYYY::::::::::  :::::                :::::
   :::::   '':::::''   :::::: ###########LLLLL::::::::::::::::::::  :::::    '::::::''   :::::
   :::::   ':::::::'   :::::: ###LLLL:::::::::::::::::::::::::::::  :::::   '':::::::'   :::::
   :::::  .:::::::::.  :::::: ##LLLLL:::::::::::::::::::::::::::::  :::::  .::::::::::.  :::::
   ::::: ':::::::::::' :::::: #YYYYYYY::::::::::::::::::::::::::::  :::::  ':::::::::::  :::::
   :::::  '::::::::::  :::::: ####LLLLL:::::::::::::::::::::::::::  :::::   ::::::::::'  :::::
   :::::  ..::::::::.  :::::: ###Y::::::::::::::::::::::::::::::::  :::::  ..::::::::..  :::::
   :::::   :::::::::   :::::: ########LLLLL:::::::::::::::::::::::  :::::    ::::::::'   :::::
   :::::   .........   :::::: ########LLLLLLLLLLLLLLLLLLLLLLLLLLLL,,LLLLL,,,,,........   :::::
   :::::               :::::: ##LLLLLLL:::::::::::::::::::::::::::  :::::                :::::
   :::::               :::::: ####LLLLLLLLLLLL:::::::::::::::::::   :::::                :::::
   :::::''''''''''''''':::::: ####LLLLLLLLLLLLLLLLL::::::::::::::   :::::'''''''''''''''':::::
   :::::::::::::::::::::::::: #######:::::::::::::::::::::::::::.   ::::::::::::::::::::::::::
   :::::::::::::::::::::::::: ##########LLL::::::::::::::::::::.    ::::::::::::::::::::::::::
   :::::::::::::::::::::::::: ,,,, .::::::::::::::::::::::::..      ::::::::::::::::::::::::::
   :::::               ::::::           .................           :::::                :::::
   :::::               ::::::                                       :::::                :::::
   :::::               ::::::                                       :::::                :::::
   :::::               ::::::                                       :::::                :::::
   :::::               ::::::                                       :::::                :::::
   #####'''''''''''''''######                                       #####''''''''''''''''#####
   #####:::::::::::::::######                                       #####::::::::::::::::#####
   #####:::::::::::::::######                                       #####::::::::::::::::#####

他のオブジェクトも全部数式で表現されている辺りも十二分に凄いんだけど、その凄さが霞んでしまうぐらいビックリした。

読み終わった感想

いやー、意味分かんなかった。

頑張って読んだらそれなりに理解はできるんだけど、こんなオシャレにカメラを動かせるセンスはまったく無いなー。