数字のループ(Ruby)

何となくやってみました。

$ pry
[1] pry(main)> 5.times {|i| puts i}
0
1
2
3
4
=> 5
[2] pry(main)> (1..5).each {|i| puts i}
1
2
3
4
5
=> 1..5
[3] pry(main)> 1.upto(5) {|i| puts i}
1
2
3
4
5
=> 1
[4] pry(main)> for i in 1..5
[4] pry(main)*   puts i
[4] pry(main)* end  
1
2
3
4
5
=> 1..5
[5] pry(main)> i = 1
=> 1
[6] pry(main)> while i <= 5
[6] pry(main)*   puts i
[6] pry(main)*   i += 1
[6] pry(main)* end  
1
2
3
4
5
=> nil
[7] pry(main)> def count(i)
[7] pry(main)*   return if i > 5
[7] pry(main)*   puts i
[7] pry(main)*   count(i + 1)
[7] pry(main)* end  
=> :count
[8] pry(main)> count(1)
1
2
3
4
5
=> nil
[9] pry(main)> co = Enumerator.new do |y|
[9] pry(main)*   i = 1
[9] pry(main)*   while i <= 5
[9] pry(main)*     y << i
[9] pry(main)*     i += 1
[9] pry(main)*   end  
[9] pry(main)* end  
=> #<Enumerator: ...>
[10] pry(main)> loop {puts co.next}
1
2
3
4
5
=> nil
[11] pry(main)> co = Enumerator.new do |y|
[11] pry(main)*   i = 1
[11] pry(main)*   loop do
[11] pry(main)*     y << i
[11] pry(main)*     i += 1
[11] pry(main)*   end  
[11] pry(main)* end  
=> #<Enumerator: ...>
[12] pry(main)> puts co.take(5)
1
2
3
4
5
=> nil
[13] pry(main)> puts 1.step.take(5)
1
2
3
4
5
=> nil

ふーんという感じでしょうか。

OpenGL で 3D迷路(Ruby)

以前、平面の迷路の中を歩いてみるようなプログラムを作りましたが(参照)、今回は迷路も立体にしてみました。検索するとよくある、平面の迷路を立体視した「3D迷路」ではなく、迷路そのものが立体なのです。3D迷路を歩くとでも申しましょうか。デフォルトで 3×4×5 の大きさの迷路ですが、任意の大きさにできます。
20180712045608 20180712101539
20180712101537 20180712045606
 
Ruby + OpenGL で実装しています。Ruby 2.3.3, Linux Mint 18.3 で確認しました。Windows では動かないと思います。Linux への OpenGL のインストールはこちらを参照して下さい。コードは以下にあります。
3D maze · GitHub
3d_maze_walk.rb を実行して下さい。Gem は OpenGL 関係のもの('opengl', 'glut', 'glu')が必要です。また、自作の OpenGL ライブラリ 'miniopengl.rb' をカレントディレクトリに置いておいて下さい。


キー操作は迷いましたが、カーソルキーの左右で xy平面内の回転をします。カーソルキーの上下で、視線方向はそのままで上下(z軸方向)に移動します。スペースキーで前進。
常に真上が z軸の正の方向で固定されています。
スタート地点は原点からもっとも遠い位置です。ゴールは原点で、上の画像のごとく白い球が置いてあるので、その位置まで来たら 'e' キーで終了します。また、位置把握があまりにもむずかしいので、移動するたびにコンソールに位置を表示しています。これが [0, 0, 0] になるように移動して下さい。

実行のたびに新たな迷路が生成されます。ソースの x, y, z = 3, 4, 5 を変更すればどのような大きさの立体迷路でも作れますが((20, 20, 20) くらいが限界でしょうか)、既にこの大きさでもゴールするのはかなりむずかしいと思います。迷路生成のアルゴリズムここで使ったのと同じで、これを3次元にしただけです。ルートはひと通りしかありません。また、すべての場所に(唯一のルートで)行くことができます。壁は少しだけ透けて見えるようになっています。


迷路生成のバグがなかなか取れなくて、普段は書かないテストを書くはめになってしまいました。テストなしではちょっとむずかしかったと思います。やることはわかっていたにもかかわらず、コーディングには丸々一日かかってしまいました。

キー操作に慣れて自由に動きまわることができるようになると、ゲームとしても結構おもしろいと思います。


obelisk.hatenablog.com

『Ruby でつくる Ruby』を読む

このところ Rails 本以外の Ruby 本があまり出ないので、Rubyist としてはつまらないところもあるのですが、出る本は『プロを目指す人のための Ruby 入門』(このブログでの感想)などのように、いい本があります。本書は Ruby コミッターでもある通称「まめさん」、遠藤侑介さんが ASCII.jp に連載した記事を書籍化したもので、ミニ Ruby インタプリタMinRuby)を Ruby で実装することで、Ruby やプログラミングを学ぼうという本です。

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門

 
一口にプログラミングといってもいろいろな考え方があると思いますが、とりあえず(手続き型言語の)基本は次のように整理できるかもしれません。

  • 「変数」の概念
  • 条件分岐
  • ループ(繰り返し)
  • 関数(あるいはメソッド、サブルーチンなど)

おおよそこれらがわかれば、プログラミングは一応できるということになるでしょうし、これらを自由に操ることはそう簡単ではないともいえるでしょう。ただし、「ループ」は「条件分岐」や「関数の再帰呼出し」で代替できるともいえます。本書は、「抽象構文木」の処理を基本にして、この「変数の処理」「条件分岐」「関数」を実装することで、ミニ Ruby インタプリタを実装し、「ブートストラップ」(ミニ・インタプリタ自身をミニ・インタプリタで実行する)までもっていくことを解説します。

むずかしそうですか? いや、それがじつにクリアなんです。初心者の実装がむずかしいパーサー(あるいはパーザー)は Gem として事前に用意されているので、インタプリタ実装のおもしろいところだけを堪能できるわけです。自分のような初心者でもすみずみまでわかる、きわめてわかりやすい本でした。まあ自分は初心者かつ素人といっても数年間の Ruby プログラミングの経験がありますが、半年くらいの経験者でも充分わかると思います。(読書会に最適な本ではないでしょうか。)例えば「抽象構文木」とか「関数の再帰呼出し」とか「変数のスコープ」とか、むずかしそうに思う人もいるでしょうが、これらが実例によって知らぬ間に(?)解説されていきます。楽しいイラストもたくさんあって、気楽に読めますし。で、ブートストラップできるインタプリタが、(Gem の恩恵があるとはいえ)最終的にたった118行で実装できてしまうのです。

いや、言語の実装っておもしろいじゃないですか。初心者の Rubyist なら読まなきゃソンなくらいですよ!


それにしても、遠藤さんの最終的なコードには Ruby 特有の便利メソッドが一切使われていませんが、きれいで惚れ惚れするシンプルさですね。こうした「骨格」だけになっても、RubyRuby らしさは一目瞭然な気がします。やたらとアクロバティックなコードだけが Ruby ではないと思っていますし、ここぞというところで「決める」のがよい Ruby コードかとも思っています。


追記(7/28)
時々読み返していますが、ホントにおもしろい本ですね。これってどうやって実装しているのかなと思って読み返すと、すごく簡単にやっているので驚いてしまいます。やっぱりプログラミングはデータ構造とアルゴリズムが決定的で、これらをうまく選ぶとじつにきれいに実装できるものだな。著者の遠藤さんが、言語実装のいちばんおもしろいところを伝えたいと仰っていましたが、まんまと(?)うまく乗せられたなあという感じ。まあ parse は最初から Gem で用意されているのだけれど、それにしたってすごいですよ。入門書の好著じゃないかな。
 これに乗せられて、schemeRuby で実装とかしたいなあと思ってしまった。まあ僕のレヴェルではなかなかむずかしいだろうけれど、たぶんネットに実装例がいろいろあるのじゃないかな。scheme 実装ってよく課題であるみたいですものね。

GTK+ で落書き 17(Ruby)

アニメーションです。ランダムに次々と長方形で塗りつぶしていきます。単純だけれど、なかなかポップだと思うのですが。
20181125004545
 
自作の RubyGem 'oekaki' を使っています。Ruby コードは以下。
oekaki_sample21.rb

require 'oekaki'

Width, Height = 500, 500

Oekaki.app width: Width, height: Height do
  draw {clear}
  
  rectangle = ->{
    x1, y1 = rand(Width), rand(Height)
    x2, y2 = rand(Width), rand(Height)
    [x1, y1, (x2 - x1).abs, (y2 - y1).abs]
  }
  
  timer(80) do
    color(rand(0x10000), rand(0x10000), rand(0x10000))
    rectangle(true, *rectangle.())
  end
end

無題

小飼弾さん、マジで Ruby がキライなのだな。でもまあ Rubyist としては、ちょっとにやけるような dis りかた。まあ好きに言ったらいいんじゃね、とド素人は思う。

しかし、いい言語だから不愉快、むかつく、というのは世界中でそういう人はいるな。ド素人から見ると、プログラマって何でこんなに不毛な神学論争で消耗し合うのかと思う。まあしかし、それがいいことだからそうなっているのでしょうね、たぶん。よくわからんけど。ド素人としては、好きな言語が不当に dis られるとやはりあんまりいい気持ちにはなれないのだが。

少なくとも Rubyist は、Ruby に至らないところがあれば素直に認めて、Ruby がもっといい言語になるように役立ちたいものである。とにかく自分は、Ruby でプログラミングをするのが好きなド素人なのだ。ただそれだけのどうでもいい人。

L-system で落書き(Ruby)

20180630233118
 
自作の Gem 'kaki-lsystem' を使っています。
再帰曲線を描く言語「L-system」を Ruby で実装した - Camera Obscura
 
Ruby コード。
lsystem_sample5.rb

require 'kaki/lsystem'

l = Lsystem.new(500, 500)
l.set("+") {right(50)}
l.set("-") {left(50)}
l.set("F") {forward(20)}
l.init("F")
l.rule("F", "F-F+F-F+F-F+F-F")
l.draw(5)

 

20180630234320
lsystem_sample6.rb

require 'kaki/lsystem'

l = Lsystem.new(500, 500)
l.move(-20, -20)
l.set("+") {right(50)}
l.set("-") {left(50)}
l.set("F") {forward(40)}
l.init("F")
l.rule("F", "F+F+F-")
l.draw(6)

GTK+ で落書き 16(Ruby)

引き続き Gem 'oekaki' のタートルグラフィックスの機能を使ってお絵かきです。これもよくある「木」です。
20180630212801
 
Ruby コード。
oekaki_sample20.rb

require 'oekaki'

Oekaki.app width: 400, height: 500 do
  draw do
    clear(color(0xf82c, 0xec45, 0xa7cd))
    
    t = Oekaki::Turtle.new
    t.color(0x2d7e, 0x693e, 0x25f9)
    t.move(0, -240)
    t.left(90)
    
    branch = ->(l) {
      return if l < 2
      t.forward(l)
      t.right(60)
      branch.(l * 0.5)
      t.left(60)
      t.forward(l / 2)
      t.left(60)
      branch.(l * 0.5)
      t.right(70)
      branch.(l * 2 / 3.0)
      t.left(10)
      t.back(l * 1.5)
    }
    branch.(100)
  end
end