Ruby2D で穴掘り迷路


見てのとおり、穴掘り迷路です。Gem 'ruby2d' を使いました。Ruby2D についてはこちら

コード。
dig_maze.rb

require "ruby2d"

L = 20
W = L * 2 + 3

Block_w = 10
set width: W * Block_w, height: W * Block_w, fps_cap: 10

blocks = W.times.map {|y|
  W.times.map {|x|
    Square.new x: x * Block_w, y: y * Block_w,
               size: Block_w, color: "green"
  }
}

field = Array.new(W) {Array.new(W, 1)}
field[0] = field[-1] = Array.new(W, -1)
(1..W - 2).each {|y| field[y][0] = field[y][-1] = -1}

field.define_singleton_method(:show) do
  each_index do |y|
    self[y].each_index do |x|
      self[y][x].zero? ? blocks[y][x].remove : blocks[y][x].add
    end
  end
end

start = [2, 2]
stack = [start]
show_stack = [start]

dig = ->(now) {
  movable = []
  [[1, 0], [0, -1], [-1, 0], [0, 1]].each do |dx, dy|
    x = now[0] + dx * 2
    y = now[1] + dy * 2
    movable << [x, y] if field[y][x] == 1
  end
  if movable.empty?
    return if stack.empty?
    jump = stack.delete_at(rand(stack.size))
    dig.(jump)
  else
    nxt = movable.sample
    show_stack << [(now[0] + nxt[0]) / 2, (now[1] + nxt[1]) / 2]
    show_stack << nxt
    stack << nxt
  end
}


update do
  now = show_stack.shift
  next unless now
  field[now[1]][now[0]] = 0
  field.show
  dig.(now) if show_stack.empty?
end

show

Gem 'Ruby 2D' で遊ぶ(2)

過去記事のプログラムを少し改変したものです。
 
コード。
ruby2d_sample2a.rb

require 'ruby2d'
require 'matrix'
include Math

Width = 500
C = 15    #円の数
R = 20    #円の半径

L = 70    #三角形の外接円の半径

set width: Width, height: Width

circles = C.times.map {
  Circle.new x: rand(R..Width - R), y: rand(R..Width - R),
     radius: R, color: Color.new([rand, rand, rand, 0.8]), z: 0
}
cvs = circles.map {[rand(-3.0..3.0), rand(-3.0..3.0)]}    #円の移動ベクトル
circles_move = circles.zip(cvs)


rot = ->(θ) {
   θ1 = θ / 180.0 * PI
   Matrix[[cos(θ1), sin(θ1)], [-sin(θ1), cos(θ1)]]
}

points = ->(θ) {
  o = Vector[Width / 2.0, Width / 2.0]
  v0 = Vector[0, -L]
  p1 = o + rot.(θ +   0) * v0
  p2 = o + rot.(θ + 120) * v0
  p3 = o + rot.(θ + 240) * v0
  [p1, p2, p3]
}

triangle = Triangle.new z: 10, opacity: 0.8

triangle.define_singleton_method(:rotate) do |θ|
  p1, p2, p3 = points.(θ)
  self.x1 = p1[0]
  self.y1 = p1[1]
  self.x2 = p2[0]
  self.y2 = p2[1]
  self.x3 = p3[0]
  self.y3 = p3[1]
end

triangle_color = [rand, rand, rand]
triangle_color_step =
   (Vector[rand(-1.0..1.0), rand(-1.0..1.0), rand(-1.0..1.0)]
     .normalize / 100).to_a

triangle_color.define_singleton_method(:update) do
  triangle.color = self + [0.8]
  replace(zip(triangle_color_step).map {|a, b| a + b})
  triangle_color.each_index do |i|
    if triangle_color[i] < 0 || triangle_color[i] > 1
      triangle_color_step[i] = -triangle_color_step[i]
    end
  end
end


θ = 0

update do
  #円の移動
  circles_move.each do |c, vec|
    c.x += vec[0]
    c.y += vec[1]
    c.x = Width + R if c.x < -R
    c.y = Width + R if c.y < -R
    c.x = -R if c.x > Width + R 
    c.y = -R if c.y > Width + R 
  end
  
  #三角形の回転
  triangle.rotate(θ)
  triangle_color.update if (θ % 3).zero?
  θ = (θ + 1) % 360
end

show

inject が Enumerator を返さない(Ruby)

例えば true/false の配列をビット列に変換したいとして、こうしたかった。

cond = [true, false, false, true, false, true]

cond.inject(0).with_index {|(acc, c), i| c ? acc | 1 << i : acc}
#=>TypeError (0 is not a symbol nor a string)

injectはブロックを取らない場合、Enumerator を返さないのですね。


仕方がないのでenum_forを使う。

cond.enum_for(:inject, 0).with_index {|(acc, c), i| c ? acc | 1 << i : acc}
#=>0b101001

これでinjectも Enumerator を返します。


しかし、enum_forってどうなっているのだろうね。どうも、ブロックを取るメソッドはすべて使えるらしい。変な例。

class String
  def upcase🍓
    yield(upcase)
  end
end

enum = "aaa".enum_for(:upcase🍓)
enum.map {|e| e + "0"}    #=>["AAA0"]

謎である。

Ruby で同値(必要十分)関係

Ruby で「p と q が同値」あるいは「必要十分」、つまり
 
の関係を表すにはどうしたらよいでしょうか。

これはp == qのことでも、p.equal?(q) のことでもありません。ではなくて、

p が T ならば q も T、p が F ならば q も F

ということです。T とか F は何だということになりますが、Rubytruefalseはちょっと曖昧で、Ruby の if ではnilfalseが「偽」と判定されますね。この「偽」になるオブジェクトを F、それ以外のオブジェクトを T とする、という意味です。真偽表だと

p q p⇔q
T T T
T F F
F T F
F F T

となります。

じつは同値 EQ は、排他的論理和 XOR の否定です。なので簡単そうですが、じつは Ruby には XOR を表す演算子がありません。
ここにあるように、Ruby で XOR を表すには、!!p ^ !!qあるいは!p ^ !qとすればよいようです。なので同値を判定するメソッドは

def eq(p, q)
  !(!p ^ !q)
end

とすればよいことになります。

AOJ 0141 Spiral Pattern (Ruby)

問題。
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0141&lang=ja

「ぐるぐる模様」を出力します。簡単そうでかなりむずかしかったので、印象に残っています。

6番目の「ぐるぐる模様」

######
#    #
# ## #
# #  #
# #  #
# ####

 
コード。

result = []

gets.to_i.times do
  n = gets.to_i
  
  field = Array.new(n) {" " * n}
  x, y = 0, n - 1
  
  go = ->(dx, dy, l) {
    l.times do
      field[y][x] = "#"
      x += dx
      y += dy
    end
  }
  square = ->(l) {
    if l != 1
      go.(0, -1, l - 1)
      go.(1,  0, l - 1)
      go.(0,  1, l - 1)
      if l != 3
        go.(-1, 0, l - 3)
        if l != 4
          if l > 4
            go.(0, -1, 2)
            square.(l - 4)
          end
          return
        end
      end
    end
    field[y][x] = "#"
  }
  
  square.(n)
  result << field.map {|l| l + "\n"}.join
end
puts result.join("\n")

 

解説

  • まず、キャンバスfield[][]の左下に、開始位置 (x, y) をセットします。
  • go.(dx, dy, l) は、(dx, dy) 方向に l ステップだけ描画して、(x, y) を進めます。
    • まず (x, y) に # を置いてから、ステップを進めるという順です。
  • square.(l)は l 番目の「ぐるぐる模様」のいちばん外側一周を描き、(x, y) を進めます。
    • l > 4 の場合は、外周一周を描いたら、2ステップ上に進んで、square.(l - 4) を再帰的に呼びます。
    • l <= 4 の場合はややこしくて、場合分けがたくさんあります。

square.(l)は、冗長でも case ~ when 式で書けばもっとすっきりしましたね。
こんな感じか。

  square = ->(l) {
    case l
    when 1
      field[y][x] = "#"
    when 2
      go.(0, -1, 1)
      go.(1,  0, 1)
      field[y][x] = "#"
    when 3
      go.(0, -1, 2)
      go.(1,  0, 2)
      go.(0,  1, 2)
      field[y][x] = "#"
    when 4
      go.(0, -1, 3)
      go.(1,  0, 3)
      go.(0,  1, 3)
      go.(-1, 0, 2)
    else
      go.(0, -1, l - 1)
      go.(1,  0, l - 1)
      go.(0,  1, l - 1)
      go.(-1, 0, l - 3)
      go.(0, -1, 2)
      square.(l - 4)
    end
  }

これだと平凡ですね。