GTK+ でぽちぽち遊び(Ruby)

キャンバス上でマウスクリックして下さい。ぽちぽち円が描かれます。


右クリックで終了ボタンが出ます。


Ruby コード。

require 'oekaki'

L = 500; R = 25

Oekaki.app width: L, height: L do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, L, L)
  end
  
  quit_window = proc do
    make_window do |w|
      w.title = ""
      b = button do
        set_size_request(120, 40)
        add(Gtk::Label.new.set_markup('<span size="x-large">Quit!</span>'))
        signal_connect("clicked") {Gtk.main_quit}
      end
      add(b)
    end
  end

  mouse_button do |w, e|
    quit_window.call if e.button != 1
    color(rand(65536), rand(65536), rand(65536))
    arc(true, e.x - R, e.y - R, R * 2, R * 2, 0, 64 * 360) 
  end
end

 
Gem 'oekaki'(かつての 'mygtk')については
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
メソッド Event#mouse_button, Event#make_window, Gtk::Window#button を加えました。
oekaki | RubyGems.org | your community gem host

複素フィボナッチ数列と GTK+ お絵かき

GTK+でお絵かきしてみた(Ruby) - Camera Obscura
これまで mygtk.rb と言っていた GTK+ お絵かきモジュールを、Gem 'oekaki' として RubyGems.org に登録いたしました。よろしかったら使ってやって下さい。
oekaki | RubyGems.org | your community gem host
その簡単な記録が
最小限度の Ruby Gem 作成と公開 - Marginalia
であります。


それとちょっと関係してといいますか、いつも拝読しているブログ「完全無欠で荒唐無稽な夢」の記事に、複素フィボナッチ数列というおもしろそうな題材がありましたので、お絵かきしてみました。数列を複素平面にプロットしただけであります。ただし、ものすごい勢いで原点から遠ざかったりもするので、原点からの距離は対数をとってあります。
複素フィボナッチ数列の件 - 完全無欠で荒唐無稽な夢

複素フィボナッチ数列とは,
  
というものですね。

まず、
  
の場合。

わかりにくいけれど、螺旋ですね。

上ブログ記事にあった、
  
の場合。

あんまり変わらないですかね。


Ruby のコード。

require 'oekaki'

L = 400
a = Complex(1, -1)
b = Complex(1,  1)

Oekaki.app width: L, height: L do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, L, L)
  end
  
  plot = lambda do |a|
    x1 = a.real
    y1 = a.imaginary
    r = Math.sqrt(x1 ** 2 + y1 ** 2)
    l = Math.log(r) * 2.5
    x = L / 2 + 1 + x1 * l / r
    y = L / 2 + 1 - y1 * l / r
    color(0, 65535, 0)
    arc(false, x, y, 3, 3, 0, 64 * 360) rescue return
  end  
  
  timer(20) do
    plot.call(a)
    a, b = b, b + Complex::I * a
  end
  
  key_in do |w, e|
    Gtk.main_quit if e.keyval == Gdk::Keyval::GDK_Return
  end
end

リターンキーで画面が閉じます。

Ruby Gem できた

野良 Gem の作り方(Ruby) - Camera Obscura
以前「野良 Gem」(「野良」なのは、僕が RubyGems.org に登録していないからです)を作ったのですが、いいかげんなやり方だったので、書き直してみます。

gem 'mygtk' を作ってみます。git が必要なので、なければインストールして下さい。
GitHub のアカウントに mygtk というリポジトリを作っておきます。
また、以下 Bundler を使うので、インストールしておいて下さい。


好きなディレクトリで

$ bundle gem mygtk

を実行します。すると mygtk フォルダが出来、色いろ他のファイルも作成されます。

$ cd mygtk

でフォルダの中に入ります。この中にある、mygtk.gemspec というファイルの中身を変更します。具体的には上のリンク先を見て下さい。spec.summary と spec.description は必ず中身を記述しなくてはなりません。また、依存する Gem があれば spec.add_dependency も記述します(標準添付ライブラリについては必要ありません)。

lib というディレクトリに入って、中の mygtk.rb を書きます(先頭に require "mygtk/version" が必要です)。これが require される Gem の本体になります。

git のインデックス(ステージ領域)に追加し、コミットします。

$ git add -A
$ git commit -m 'first commit'

Gem を作ります。

$ rake install

これで Gem が出来ているので、irb で require 'mygtk' などしてみて確認します。

リモートリポジトリ(この場合は GitHub)にファイルを上げます。

$ git remote add origin git@github.com:obelisk68/mygtk.git
$ git push -u origin master

リリースします。RubyGems.org に登録していれば、これで Gem が公開されます。登録していない場合はここでフリーズするので、しばらくして [Ctrl]+[C] で中断します。

$ rake release

これで GitHub に(野良)Gem が置かれました。終了です。


これで RubyGem に上げていれば、あとはふつうに Gem として使ってください。
「野良」の場合は、Bundler でインストールできます。Gemfile に

gem 'mygtk', github: 'obelisk68/mygtk'

と記述して、

$ bundle install

でインストールできます。使い方は以下。
GTK+でお絵かきしてみた(Ruby) - Camera Obscura


※参考
bundle gemは何をしてくれるのか? - ザリガニが見ていた...。
サルでもわかるGit入門 〜バージョン管理を使いこなそう〜 | どこでもプロジェクト管理バックログ

Ruby 実行のエラー時に n 回リトライするメソッド

n.times_retry でブロック内をエラーがあれば最大 n 回再実行し、それを超えれば停止します。エラーがないかリトライに成功すれば、そのまま次へ処理が渡されます。

引数の massage は true ならば情報を出力します(デフォルト true)。wait: n は retry の前に n秒スリーブします(デフォルトは 0)。ブロック引数の i は i 回目の実行ということです。

5.times_retry(message: true, wait: 1) do |i|
  puts i
  if i <= 3
    raise "sample error"
  end
end
puts "success"

__END__
#出力
1
Error: retry 1
times_retry.rb:23:in `block in <main>'
times_retry.rb:5:in `times_retry'
times_retry.rb:21:in `<main>'
2
Error: retry 2
times_retry.rb:23:in `block in <main>'
times_retry.rb:5:in `times_retry'
times_retry.rb:21:in `<main>'
3
Error: retry 3
times_retry.rb:23:in `block in <main>'
times_retry.rb:5:in `times_retry'
times_retry.rb:21:in `<main>'
4
success

 
メソッドのコード。

class Integer
  def times_retry(message: true, wait: 0)
    n = 1
    begin
      yield(n)
    rescue => e
      if n <= self
        puts "Error: retry #{n}" if message
        puts e.backtrace if message
        n += 1
        sleep(wait)
        retry
      end
      puts "Error: stop" if message
      raise e
    end
  end
end

GTK+で落書き 3(Ruby)


周転円みたいな。


ライブラリ「MyGtk」についてはこちら

require_relative 'mygtk'
include Math

L = 400
R1 = 150; R2 = (L - R1 * 2) / 2 - 5
STP1 = (PI / 180) * 0.5; STP2 = (PI / 180) * 10

MyGtk.app width: L, height: L do
  draw do
    color(5000, 0, 10000)
    rectangle(true, 0, 0, L, L)
  end

  i = 0
  
  id = timer(20) do
    x = L / 2 + (R1 * cos(STP1 * i) + R2 * cos(STP2 * i))
    y = L / 2 - (R1 * sin(STP1 * i) + R2 * sin(STP2 * i))
    color(0, 65535, 0)
    arc(false, x, y, 3, 3, 0, 64 * 360)
    i += 1
    Gtk.timeout_remove(id) if STP1 * i > PI * 2
    true
  end
end

コールバック関数は true を返さないといけません。

GTK+で落書き 2(Ruby)

MF / 今日の落書き Ruby/Tk で フィボナッチ配列(螺旋)
ここのパクリです。



ライブラリ「MyGtk」についてはここを参照。

require_relative 'mygtk'
include Math

ANGLE = 2 * PI * (1 + sqrt(5)) / 2

MyGtk.app width:300, height: 300, title: :Fibonacci do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, 300, 300)
    
    2000.times do |t|
      angle = t * ANGLE
      r = t * 0.1
      x = 147 + r * cos(angle)
      y = 147 + r * sin(angle)
      color(0, 65535, 0)
      arc(false, x, y, 3, 3, 0, 64 * 360)
    end
  end
end

コンウェイのライフゲームを Ruby で実装してみた

有名なコンウェイのライフゲーム」を素朴に実装してみました。一種の生態系シミュレーションですね。ルールは簡単です。「セル」が集まって長方形領域を形成しているとき、それぞれの「セル」はまわりの 8つの「セル」の状態によって「生きる」か「死ぬ」かします。

  • 「セル」がないとき
    • まわりにちょうど 3つの「セル」があれば「誕生」します。
  • 「セル」があるとき
    • まわりに 0 または 1個の「セル」があれば過疎で「死に」ます。
    • まわりに 2 または 3個の「セル」があればそのまま「生き」ます。
    • まわりに 4個以上の「セル」があれば、過密で「死に」ます。

このパターンを繰り返します。



パターンはランダムに与えられ、毎回自動的に lifemap.txt に(上書き)記録されます。-r オプションをつけて実行すると、開始パターンを lifemap.txt からロードして実行します。描画には GTK+ を使っています。

ソースは以下です。Gist

require 'gtk2'

Width  = 30
Height = 30

class Component
  def initialize
    f = Field.new(Width, Height)
    f.generate(150)     #ランダムに150個セットする
    if ARGV[0] == "-r"
      File.open("lifemap.txt") {|io| f = Marshal.load(io)}
    else
      File.open("lifemap.txt", "w") {|io| Marshal.dump(f, io)}
    end
    @window = Gui.new(f)
    @disp = FieldDisplay.new(f)
  end
  
  def main
    @disp.show
  end
end

class Field
  def initialize(width, height)
     @width  = width
     @height = height
     @field = Array.new(@width + 2) {Array.new(@height + 2, 0)}
  end
  attr_reader :width, :height
  
  def set(x, y)
    @field[x][y] = 1
  end
  
  def reset(x, y)
    @field[x][y] = 0
  end
  
  def get(x, y)
    @field[x][y]
  end
  
  def generate(n)
    n.times {set(rand(@width) + 1, rand(@height) + 1)}
  end
  
  #次の世代を得る
  def next
    nxf = Array.new(@width + 2) {Array.new(@height + 2, 0)}
    @width.times do |x|
      @height.times do |y|
        x1, y1 = x + 1, y + 1
        n = alive_cells(x1, y1)
        if get(x1, y1).zero?
          nxf[x1][y1] = 1 if n == 3
        else
          nxf[x1][y1] = 1 if n == 2 or n == 3
        end
      end
    end
    @field = nxf
  end
  
  def alive_cells(x, y)
    get(x - 1, y - 1) + get(x, y - 1) + get(x + 1, y - 1) +
      get(x - 1, y) + get(x + 1, y) +
      get(x - 1, y + 1) + get(x, y + 1) + get(x + 1, y + 1)
  end
end

class Gui
  CellWidth = 10
  Space = 3
  Margin = 8
  
  def initialize(f)
    @f = f
    
    @w = Gtk::Window.new
    @width  = CellWidth * @f.width  + Space * (@f.width  - 1) + 2 * Margin
    @height = CellWidth * @f.height + Space * (@f.height - 1) + 2 * Margin
    @w.set_size_request(@width, @height)
    @w.set_app_paintable(true)
    @w.realize
    @drawable = @w.window
    @gc = Gdk::GC.new(@drawable)
    colormap = Gdk::Colormap.system
    
    @white = Gdk::Color.new(65535, 65535, 65535)
    @black = Gdk::Color.new(0, 0, 0)
    colormap.alloc_color(@white, false, true)
    colormap.alloc_color(@black, false, true)
  end
end

class FieldDisplay < Gui
  def initialize(f)
    super(f)
  end
  
  def show
    @w.signal_connect("expose_event") do
      @gc.set_foreground(@black)
      @drawable.draw_rectangle(@gc, true, 0, 0, @width, @height)
    end
    
    Gtk.timeout_add(160) do
      @f.width.times do |x|
        @f.height.times do |y|
          if @f.get(x + 1, y + 1).zero?
            reset(x + 1, y + 1)
          else
            set(x + 1, y + 1)
          end
        end
      end
      @f.next
    end
    @w.signal_connect("destroy") {Gtk.main_quit}
    @w.show
    Gtk.main
  end
  
  def set(x, y)
    @gc.set_foreground(@white)
    draw_cell(x, y)
  end
  
  def reset(x, y)
    @gc.set_foreground(@black)
    draw_cell(x, y)
  end
  
  def draw_cell(x, y)
    x1 = Margin + (x - 1) * CellWidth + (x - 1) * Space
    y1 = Margin + (y - 1) * CellWidth + (y - 1) * Space
    @drawable.draw_rectangle(@gc, true, x1, y1, CellWidth, CellWidth)
  end
end

Component.new.main


※追記(2018/1/18)
コンソール版も作ってみました。

 

※追記(2018/4/12)
フィールド・エディタ付きのを作ってみました。