Gem 'Ruby2D' でライフゲーム

20190320020235
いつもの得意技(?)のライフゲームです。Ruby 用のグラフィック・ライブラリ 'Ruby2D' を使っています。

コード。
lifegame_for_Ruby2D.rb

require 'ruby2d'
include Ruby2D::DSL

class LifeGame
  CellWidth = 10
  Margin = 20
  Space = 2
  SideWidth = 35
  W = CellWidth * SideWidth + Space * (SideWidth - 1) + Margin * 2
  
  def initialize(num)
    set width: W, height: W, title: "LifeGame", fps_cap: 3
    
    cells = SideWidth.times.map do |y|
      SideWidth.times.map do |x|
        Square.new x: Margin + (CellWidth + Space) * x,
                   y: Margin + (CellWidth + Space) * y,
                   size: CellWidth,
                   color: Color.new([rand, rand, rand, 1.0]),
                   z: 0
      end
    end
    each_cell {|x, y| cells[y][x].remove}
    
    f = [1] * num + [0] * (SideWidth ** 2 - num)
    f.shuffle!
    @field = f.each_slice(SideWidth).to_a
    
    clear
    
    update do
      each_cell do |x, y|
        @field[y][x].nonzero? ? cells[y][x].add : cells[y][x].remove
      end
      next_field
    end
  end
  
  def each_cell
    SideWidth.times {|y| SideWidth.times {|x| yield(x, y)} }
  end
  
  def next_field
    tmp = Array.new(SideWidth) {Array.new(SideWidth, 0)}
    each_cell do |x, y|
      num = neighbor(x, y)
      if @field[y][x].nonzero?
        tmp[y][x] = 1 if num == 2 or num == 3
      else
        tmp[y][x] = 1 if num == 3
      end
    end
    @field = tmp
  end
  
  def neighbor(x, y)
    dirs = [[1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1]]
    dirs.map do |dx, dy|
      x1 = x + dx
      y1 = y + dy
      next if x1 < 0 or x1 >= SideWidth
      next if y1 < 0 or y1 >= SideWidth
      @field[y1][x1]
    end.compact.inject(&:+)
  end
  
  def go
    show
  end
end

LifeGame.new(250).go

ちょっとチュートリアルだけではわからないところがあったので、ソースコードを見る必要がありました。まずクラスの中で Ruby2D のメソッドをどう使ったらよいかで、これは最初に include Ruby2D::DSL を宣言しておくことで解決(他の方法もあります)。それから、メソッド update のブロックの中で状態がその都度更新されるのですが、そのフレーム数の指定はどうすればよいか。これはメソッド set のキーワード引数で :fps_cap で指定してやればできることがわかりました。なお、ソースコードはとても読みやすいものでした。
 
'Ruby2D' については以下で紹介しています。
obelisk.hatenablog.com
 

上とは関係のないどうでもいいこと

Ruby は型の指定がないのでコードが読みにくいという根強い意見がありますが、たぶんこれではないかと。

  • そもそもそのコード自体が読みにくい、クソなものである → Ruby のコードは一般にシンプルで短めなので、さっと読むのは楽な方です
  • 全体のコードが長すぎて読みにくい → ソースファイルを分割する必要がある
  • Ruby にはジェネリクスがない → Ruby の書き方を知らないにもほどがある

なお、これらはじつは他の言語にも応用できることです。別に Ruby は完璧な言語ではありませんが、実用的な(といってもまあいろいろでしょうけれど)言語としては非常によくできたものです。オブジェクト指向言語としてはもっともよくできたもののひとつでしょうし、クロージャ、第一級関数もサポートしており、関数型プログラミングのエッセンスも詰まっています。初心者にもやさしいですし、高度なプログラミングのできるプログラマにはそれなりの優れた書き方ができます。あんまり知らないでバカにするのはやめましょう。

僕は Ruby 以外では Go が結構好きなので(ソースコードの見た目が好きです笑)、Ruby と Go が連携できるような仕組みを作って欲しいのですけれど…。C言語がうまく書けるようになるのはなかなか大変…。

それから、「デザインパターンはオワコン」という意見も見ましたが、オワコンであろうが知っておいた方がよいと思います。純粋にプログラミング技術としておもしろいものです。最近はプロのプログラマの絶対数が増えたので、プロであり自信満々でもじつはよくわかっていない人も多いですから、我々素人のプログラミング好きは気をつけたいものです。


しかし、PythonPerl で型の指定がないので読みにくいという意見は、ほとんど目にしたことがないのだが…。