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

ライフゲームの CoffeeScript 版

前回「コンウェイライフゲーム」を Ruby でシンプルに実装しましたが、今回その CoffeeScript 版です。ここで実行できるのがいいでしょう? 「実行」を押すとセルがランダムに生成されます。

セルの数:   

コード。HTML。

<form>
セルの数:<input type="text" name="num" size="4" value="170">
<input type="button" value="実行" onclick="start(this.form)">  
<input type="button" value="クリア" onclick="clear();">
</form>
<canvas id="Canvas" style="background-color: black;"></canvas>

CoffeeScript。 

width = 40; height = 40
cellwidth = 10; space = 3; margin = 8
wd = cellwidth * width  + space * (width  - 1) + 2 * margin
ht = cellwidth * height + space * (height - 1) + 2 * margin
ctx = f = w = id = null


Field = (width, height)->
  @width  = width
  @height = height
  @field = []
  for i in [0...(@width + 2)]
    @field[i] = []
    for j in [0...(@height + 2)]
      @field[i][j] = 0
  
  @generate = (n)->
    for i in [1..n]
      x = Math.floor(Math.random() * (@width  - 10)) + 5
      y = Math.floor(Math.random() * (@height - 10)) + 5
      @field[x][y] = 1
  
  @show = ->
    for x in [1..@width]
      for y in [1..@height]
        if @field[x][y]
          w.set(x, y)
        else
          w.reset(x, y)
  
  @windowx = (x)->
    margin + (x - 1) * cellwidth + (x - 1) * space
  
  @windowy = (y)->
    margin + (y - 1) * cellwidth + (y - 1) * space
  
  @next = ->
    nxf = []
    for i in [0...(@width + 2)]
      nxf[i] = []
      for j in [0...(@height + 2)]
        nxf[i][j] = 0
    for x in [1..@width]
      for y in [1..@height]
        n = @alive_cells(x, y)
        if @field[x][y]
          nxf[x][y] = 1 if n == 2 or n == 3
        else
          nxf[x][y] = 1 if n == 3
    @field = nxf
  
  @alive_cells = (x, y)->
    @field[x - 1][y - 1] + @field[x][y - 1] + @field[x + 1][y - 1] +
       @field[x - 1][y] + @field[x + 1][y] +
       @field[x - 1][y + 1] + @field[x][y + 1] + @field[x + 1][y + 1]
  
  null


Window = ->
  @set = (x, y)->
    ctx.fillStyle = "rgb(255, 255, 255)"
    @draw_cell(x, y)
  
  @reset = (x, y)->
    ctx.fillStyle = "rgb(0, 0, 0)"
    @draw_cell(x, y)
  
  @draw_cell = (x, y)->
    ctx.fillRect(f.windowx(x), f.windowy(y), cellwidth, cellwidth)
  
  null


mainloop = ->
  f.show()
  f.next()
  return


window.start = (e)->    #「実行」ボタンでここへ飛ぶ
  window.clearInterval(id) if id
  ctx.clearRect(0, 0, wd, ht)
  f = new Field(width, height)
  f.generate(parseInt(e.num.value))    #入力した数だけセルを生成する
  w = new Window()
  id = window.setInterval(mainloop, 200)
  return


window.clear = ->    #「クリア」ボタンでここへ飛ぶ
  window.clearInterval(id) if id
  ctx.clearRect(0, 0, wd, ht)
  return


$(window).load ->    #実際のコードは少しちがう
  cvs = document.getElementById("Canvas"); ctx = cvs.getContext("2d")
  cvs.width = wd; cvs.height = ht
  return

CoffeeScript では window.onload = が使えないので、jQuery$(window).load -> を使っている。

コンウェイのライフゲームを 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)
フィールド・エディタ付きのを作ってみました。

GTK+で落書き(Ruby)


GTK+ でアニメーションしてみました。エンターキーの入力で終了します。簡易ライブラリ mygtk.rb についてはここを参照してください。
circle_fall.rb

require_relative 'mygtk'

L = 400

MyGtk.app width: L, height: L, title: "Circle Fall" do
  timer(10) do
    show = proc do |y|
      x = rand(L - 10)
      color(rand(65535), rand(65535), rand(65535))
      arc(true, x, y, 10, 10, 0, 64 * 360)
    end

    show.call(0) if rand < 0.15
    show.call(rand(L - 10)) if rand < 0.05
    
    img = get_pic(0, 0, L, L)
    show_pic(img, 0, 1)
    color(0, 0, 0)
    rectangle(true, 0, 0, L, 1)
  end
  
  key_in do |w, e|
    Gtk.main_quit if e.keyval == Gdk::Keyval::GDK_Return
  end
  
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, L, L)
  end
end

動画化には ffmpeg を使いました(参照)。

これ、どこかでメモリリークしているようです。

RubyGem 'Gosu'

Ruby の Gem で色いろ遊んでみたいと思うのだけれど、「gem おすすめ」などで検索しても Rails の Gem しか出てきません。誰かおもしろい Gem を教えてくれないですかね。だからというわけではないですが、ちょっと探してみたところ、ゲーム作成用の Gem で「Gosu」というのを見つけました。かなり有名な Gem みたいですね。ゲームに特に興味はないのですが、ちょっとインストールしてみました。Windows, Mac, Linux で動作するようです。Linux Mint 18 で確認しました。

Hello • Gosu
公式サイトはこれです。インストールは、僕は Bundler でインストールしました。ついでに、gem 'gosu-examples' もインストールしておくとよいでしょう。僕の環境では $ bundle exec gosu-examples --fullscreen でデモが立ち上がります。

サンプル・プログラムを見てみると、かなりすごいですね。こんなことが Ruby で出来るのか。


The Japanese translation of https://github.com/jlnr/gosu/wiki/Ruby-Tutorial (GosuのRubyチュートリアルの日本語訳) · GitHub
チュートリアルの日本語訳を作った方がおられるので、リンクしておきます。