読者です 読者をやめる 読者になる 読者になる

迷路の中を歩く(Ruby)

ひとつ前の記事で迷路を生成したので、その中を歩いてみるプログラムを書きました。OpenGL を使っています。


 
 
赤い床のマスがゴールです。"v" で左回転、"b" で右回転、スペースで前進します。迷路は実行のたびに新たに生成されます。
ぐぐってみても意外とこんな単純なゲーム(?)の実装がないのですよねえ。

require './miniopengl'
require './maze'

module WalkMaze
  def self.start(width, height, yokokabe, tatekabe)
    MiniOpenGL.app width: 400, height: 400, depth_buffer: :on, title: "maze" do
      px = width - 1; pz = height - 1    #迷路のスタート位置
      dir = [0, -1]                      #最初の向きの設定
      
      draw do
        glEnable(GL_BLEND)         #混合処理を可能にします
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        self.color_alpha = 0.9 
        glEnable(GL_DEPTH_TEST)    #隠面消去
        clear
        
        color(0, 1, 1)
        (height + 1).times {|z| line3(0, 0, z, width, 0, z)}
        (width  + 1).times {|x| line3(x, 0, 0, x, 0, height)}
        
        color(0, 0.8, 0)
        [height + 1, width].nest_loop do |z, x|
          next if yokokabe[x + z * width].zero?
          vtx = [x, 0, z, x + 1, 0, z, x + 1, 1, z, x, 1, z]
          draw_vertex(GL_QUADS, 3, vtx)
        end
        [width + 1, height].nest_loop do |x, z|
          next if tatekabe[z + x * height].zero?
          vtx = [x, 0, z, x, 0, z + 1, x, 1, z + 1, x, 1, z]
          draw_vertex(GL_QUADS, 3, vtx)
        end
        
        color(1, 0, 0)
        draw_vertex(GL_QUADS, 3, [0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1])    #ゴールの床
        
        display
      end
      
      reshape do |w, h|
        viewport(0, 0, w, h)
        init_projection
        perspective(120, w / h.to_f, 0.2, 20)
        init_modelview
        look_at(x = px + 0.5, 0.5, z = pz + 0.5, x + dir[0], 0.5, z + dir[1], 0, 1, 0)
      end
      
      check = lambda do
        if dir[0].zero?
          z = (dir[1] < 0) ? pz : pz + 1
          f = yokokabe[z * width + px]
        else
          x = (dir[0] < 0) ? px : px + 1
          f = tatekabe[x * height + pz]
        end
        f.zero?
      end
      
      end_f = false
      
      key_in do |key|
        exit if end_f
        case key
        when "v"    #左
          dir = [dir[1], -dir[0]]
        when "b"    #右
          dir = [-dir[1], dir[0]]
        when " "    #前
          if check.call    #前へ進めるかどうか
            px += dir[0]
            pz += dir[1]
          end
        end
        init_modelview
        look_at(x = px + 0.5, 0.5, z = pz + 0.5, x + dir[0], 0.5, z + dir[1], 0, 1, 0)
        redisplay
        if px.zero? and pz.zero?
          puts "Goal!"
          end_f = true
        end
      end
    end
  end
end


Width = 10; Height = 10

yokokabe, tatekabe = Maze.new(Width, Height).generate

Left = 20; Top = 20
CellWidth = 20

#fork { show_maze(Width, Height, yokokabe, tatekabe) }

WalkMaze.start(Width, Height, yokokabe, tatekabe)

maze.rb はひとつ前の記事です。miniopengl.rb はこちらを参照。

なお、LinuxOpenGL についてはこちらも参照。

Ruby で迷路作成

http://apollon.issp.u-tokyo.ac.jp/~watanabe/tips/maze.html
ここのリンク先のアルゴリズムを使って、迷路のジェネレーターを Ruby で書いてみました。リンク先でも Ruby での実装がありますが、自分でやってみました。


20×20の迷路です。

 

コードは以下です。迷路の作成と描画は別にしてあります。描画は自作の Gem 'oekaki' を使っています。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
また、自作のライブラリ 'utils' も使っています(参照)。メソッド Array.make と nest_loop がそれです。
maze.rb

require 'oekaki'
require 'utils'

class Maze
  def initialize(width, height)
    @width = width
    @height = height
    
    @yokokabe = Array.new(@width  * (@height + 1), 1)
    @tatekabe = Array.new(@height * (@width  + 1), 1)
    
    @left_wall = (@width..((a = @yokokabe.size) - @width - 1)).to_a
    @left_wall += ((a + @height)..(a + @tatekabe.size - @height - 1)).to_a
    @yokokabe_num = @yokokabe.size
    
    @cells = Array.make([@width, @height])
    i = 0
    [@height, @width].nest_loop do |y, x|
      @cells[x][y] = i
      i += 1
    end
  end
  
  def generate
    break_wall until finish
    [@yokokabe, @tatekabe]
  end
  
  def wall_position(num)
    po = []
    po[0] = (num < @yokokabe_num)
    if po[0]
      a = @width
    else
      num -= @yokokabe_num
      a = @height
    end
    po[1] = num % a
    po[2] = num / a
    po
  end
  
  def replace(s, t)
    [@width, @height].nest_loop do |x, y|
      @cells[x][y] = t if @cells[x][y] == s
    end
  end
  
  def break_wall
    num = @left_wall[rand(@left_wall.size)]
    po = wall_position(num)
    x = po[1]; y = po[2] - 1
    @left_wall.delete(num)
    if po[0]
      #横壁
      return if (a = @cells[x][y]) == (b = @cells[x][y + 1])
      @yokokabe[num] = 0
    else
      #縦壁
      return if (a = @cells[y][x]) == (b = @cells[y + 1][x])
      @tatekabe[num - @yokokabe_num] = 0
    end
    a, b = b, a if a < b
    replace(a, b)
  end
  
  def finish
    a = @cells[0][0]
    @cells.flatten.each {|b| return false if b != a}
    true
  end
end


def show_maze(w, h, yokokabe, tatekabe)
  wi = Left * 2 + w * (CellWidth + 1)
  he = Top  * 2 + h * (CellWidth + 1)
  
  Oekaki.app width: wi, height: he, title: "maze" do
    draw do
      color(0, 0, 0)
      rectangle(true, 0, 0, wi, he)
      
      color(65535, 65535, 65535)
      [h + 1, w].nest_loop do |y, x|
        next if yokokabe[x + y * w].zero?
        x1 = Left + x * (CellWidth + 1)
        y1 = Top  + y * (CellWidth + 1)
        line(x1, y1, x1 + CellWidth + 1, y1)
      end
      [w + 1, h].nest_loop do |x, y|
        next if tatekabe[y + x * h].zero?
        x1 = Left + x * (CellWidth + 1)
        y1 = Top  + y * (CellWidth + 1)
        line(x1, y1, x1, y1 + CellWidth + 1)
      end
      
      #save_pic(get_pic(0, 0, wi, he), "maze.png")    #画像ファイルの作成
    end
  end
end


if __FILE__ == $0
  Width = 20; Height = 20
  
  yokokabe, tatekabe = Maze.new(Width, Height).generate
  
  Left = 20; Top = 20
  CellWidth = 20
  
  show_maze(Width, Height, yokokabe, tatekabe)
end

メソッド Maze#generate で迷路を生成します。返り値は yokokabe と tatekabe で、それぞれ横方向と縦方向の壁をあらわす配列です(1 なら壁が存在し、0 なら存在しない。初期値はすべて 1)。メソッド Maze#break_wall は、壁をランダムにひとつ選んで、壊してもよい壁なら壊します。メソッド show_maze で迷路を表示します。ここで自家製の Gem 'oekaki' を使っています。png 画像ファイルに落とすことも出来ます。

Utils

上のコードに必要な 'utils' の部分は以下だけなので、これで代用してもらって構いません。
utils.rb

class Array
  #任意の階層だけ繰り返しをネストする
  def nest_loop(arg = [], &bk)
    check = lambda do |obj|
      return 0...obj if (c = obj.class) == Bignum or c == Integer or c == Fixnum
      obj
    end
    
    ar = dup
    for i in check.call(ar.shift)
      if ar.empty?
        yield(arg + [i])
      else
        ar.nest_loop(arg.push(i), &bk)
        arg.pop
      end
    end
    self
  end

  #多重配列を簡単に生成する
  def self.make(ar, ob=nil)
    raise "Argument class Error" unless ar.class == Array
    a = ar.dup
    ar1 = []
    a.shift.times {ar1 << (a.empty? ? (ob.dup rescue ob) : Array.make(a, ob))}
    ar1
  end
end

Linux Mint(Ubuntu)でSDカードに書き込みできない

まず SDカードを PC に挿入しても認識しない。
これは何故か SDカードを LOCK してから挿入したら認識した。

もう一度 LOCK を解除して挿入。しかしフォルダをコピーしても「転送先は読み込み専用です」という表示が出る。アンマウントして再マウントしてもダメ。パーミッションを変更してもダメ(というか、Linuxファイルシステムでないので無意味)。ちなみにファイル・システムは msdos
よくわからずにダメ元で SDカードを挿入したままで再起動。そうしたら普通に認識された。何なんだ。

「等値」と「等価」は Ruby では?

これを見てどうも JavaC++ の話のように思ったのだが、最初の「『等値』と『等価』の違いを説明してください」というのがよくわからなかった。僕は Ruby しか知らない素人初心者プログラマなのだが、Ruby だとどういうことなのだろう。

ただ、Ruby でも「==」と「equal?」はちがうよね。

irb(main):001:0> a = "hoge"
=> "hoge"
irb(main):002:0> b = "hoge"
=> "hoge"
irb(main):003:0> a == b
=> true
irb(main):004:0> a.equal?(b)
=> false
irb(main):005:0> a.object_id
=> 47028457345780
irb(main):006:0> b.object_id
=> 47028458255160
irb(main):007:0> a = b = "hoge"
=> "hoge"
irb(main):008:0> a.equal?(b)
=> true

上を見ればわかると思うけれども、「==」はオブジェクトが同じ内容なら true、「equal?」は同じオブジェクトID でなければ true にならない。上の「等値」と「等価」というのは、これと関係があるのかな。

Ruby で関数型プログラミングもどき

上のリンク先で JavaScript を使って、オブジェクト指向プログラミングと関数型プログラミングの対比をやってあったので、Ruby に移植してみました。

 

課題:
唐揚げ弁当がいくつかあるとします。それぞれ唐揚げが複数入っています。
この中からx個の唐揚げをつまみ食いするプログラムを作りましょう。
つまみ食いはバレないようにするために、
その時点で最も唐揚げ数が多いお弁当から取るという仕様にします。

http://qiita.com/stkdev/items/5c021d4e5d54d56b927c

 

オブジェクト指向で。こんな感じですかね。

class Bento
  def initialize(dish, num)
    @dish = dish
    @num = num
  end
  attr_reader :num
  
  def eat
    @num -= 1
  end
  
  def show
    puts @dish + @num.to_s + ""
  end
end

order = [Bento.new("唐揚げ", 10), 
         Bento.new("唐揚げ",  8),
         Bento.new("唐揚げ",  6)]
         
5.times do
  max_bento = order.inject(order.first) {|r, bento| r = (bento.num > r.num) ? bento : r}
  max_bento.eat
  puts "-------"
  order.each {|x| x.show}
end

結果。

-------
唐揚げ9個
唐揚げ8個
唐揚げ6個
-------
唐揚げ8個
唐揚げ8個
唐揚げ6個
-------
唐揚げ7個
唐揚げ8個
唐揚げ6個
-------
唐揚げ7個
唐揚げ7個
唐揚げ6個
-------
唐揚げ6個
唐揚げ7個
唐揚げ6個

 
 
次は関数型プログラミングっぽく。元の JavaScript コードは上と同じ答えを返さないので、多少変更してあります。

order = [{dish: "唐揚げ", num: 10},
         {dish: "唐揚げ", num:  8},
         {dish: "唐揚げ", num:  6}]
         
show = ->(d) {puts d[:dish] + d[:num].to_s + ""}

show_all = ->(data) {data.each {|x| show[x]}}

select_eating_bento = ->(data) {
  data.inject(data.first) {|r, bento| r = (bento[:num] > r[:num]) ? bento : r}
}

eating_karaage = ->(data) {
  data.map do |bento|
    if bento.equal?(select_eating_bento[data])
      {dish: bento[:dish], num: (bento[:num] - 1)}
    else
      bento
    end
  end
}

eating = ->(num, data) {
  data = eating_karaage[data]
  puts "-------"
  show_all[data]
  eating[num - 1, data] if num > 1
}

eating[5, order]

「副作用」がないようにというのが目標ですね。
 
どちらがわかりやすいでしょうか。

元記事は色いろコメントで怒られていますね…。

「10分でコーディング」やってみた(Ruby)


たぶん10分以内にできたと思う。

問題は、num_player 人のプレーヤーに deck で与えられたカードを切るというもの。ただし、全員に同じ枚数だけ配らないといけない。

class Cards
  def deal(num_players, deck)
    ar = Array.new(num_players, "")
    (deck.length / num_players).times do |i|
      num_players.times {|j| ar[j] += deck[i * num_players + j]}
    end
    ar
  end
end

p Cards.new.deal(4, "123123123")    #=>["12", "23", "31", "12"]
p Cards.new.deal(6, "01234")        #=>["", "", "", "", "", ""]

 
 

もう一問やってみた(15分)

問題なし。簡単。

class ReportAccess
  def who_can_see(user_names, allowed_data, report_data)
    ar = []
    user_names.each_with_index do |uname, i|
      ad = allowed_data[i].split(" ")
      catch(:exit) do
        report_data.each {|d| throw(:exit) unless ad.include?(d)}
        ar << uname
      end
    end
    ar
  end
end

ra = ReportAccess.new
p ra.who_can_see(["joe", "nick", "ted"],
                 ["clients products", "products orders", "clients orders"],
                 ["clients", "products"])
#=>["joe"]

 
 

さらにもうひとつ(10分)

問題なし。簡単。

class CCipher
  def decode(ciphertest, shift)
    st = ""
    ciphertest.each_byte do |byte|
      nb = byte - shift
      nb += 26 if nb < 65
      st += nb.chr
    end
    st
  end
end

p CCipher.new.decode("VQREQFGT", 2)    #=>"TOPCODER"

 
リンク先のブログ主は Java でコーディングしておられるが、Rubyist からは Java は書くことが多すぎるように見える。

Python の for else って地味に便利と Rubyist が思う

ので、遊びで Ruby で実装してみました。each_with_proc(pc, *args) で、ふつうの each のループが終ったあとに proc オブジェクト pc が pc.call(*args) されます。ブロックから break で抜けた場合は proc は呼ばれません。
 
 

module Enumerable
  def each_with_proc(pc, *args)
    each {|i| yield(i)}
    pc.call(*args)
  end
end


ar = []
4.times {ar << rand(10)}
pc = proc {puts "Nyao, hit!"}
ar.each_with_proc(pc) do |i|
  puts i
  break if i < 3
end

結果例。

7
8
1

6
5
3
6
Nyao, hit!

proc を先に指定しないといけないのが使いにくいですかね。


実際は throw 〜 catch するか、lambda と return を使ってこんな風にするか。

ar = []
4.times {ar << rand(10)}
->{
  ar.each do |i|
    puts i
    return if i < 3
  end
  puts "Nyao, hit!"
}.call

あんまりスッキリしていないですね。わかりにくい。