Gem 'oekaki' のリファレンスを作る
http://obelisk704.web.fc2.com/ruby/oekaki_document.html
いまさらながら Gem 'oekaki' のリファレンスを書いたので、よろしければ使って下さい。
Ruby/SDL でテトリス
いやこれ、僕が作ったのではないのですよ。Gem 'rubysdl' のサンプルとして付属していたものを、多少改変しただけですが、きれいなコードで勉強になったのでここにメモしておきます。
コードは Gist に上げておきました。
Ruby/SDL でテトリス · GitHub
あと画像ファイル "icon.bmp" が必要ですので、これを使って下さい。これだけで動きます。必要な Gem は上にも書いたとおり 'rubysdl' ですので、$ gem install rubysdl や Bundler などでインストールして下さい。
キー操作はカーソルキーです。やってみればすぐわかります。
Linux Mint 18.3, Ruby 2.5.1 で確認しました。Ruby/SDL の Linux へのインストールなどについてはここも参考にしてもらえればよいと思います。
コード・リーディングしていて思ったのですが、適切なクラス名、メソッド名、変数名を付けることは大事ですね。上のコードでは自分に読みやすいようにそのあたりを改変しました。改悪になっていないといいのですが。それから、多少のコメントを書き加えています。Bug fix は一箇所だけありました。これは Ruby 本体の仕様改変に伴うものだと思います。
そうそう、それから誰か、Windows への Ruby/SDL のインストールの仕方をアップデートしてくれないですかね。いまのままだとどうやっていいのかさっぱりわからない。残念ながら、自分でコードを調べてインストールする実力がないので。
ランダムかつ重複しないように文字列を生成する(Ruby)
あることのために必要だったので、複数の文字列をランダムかつ重複しないように生成するメソッドを書いてみました。
こんな感じです。
$ irb irb(main):001:0> require_relative "generate_random_strings" => true irb(main):002:0> Utils.generate_random_strings(40) => ["kk", "cm", "aq", "vf", "zf", "uh", "qv", "pv", "bb", "jp", "td", "ri", "mr", "hq", "gy", "pe", "ta", "ot", "ob", "km", "zu", "cz", "sf", "qo", "zt", "uq", "tc", "fd", "xq", "ki", "po", "w", "dj", "ks", "mw", "am", "zr", "az", "iy", "gv"]
重複しない 40個の文字列(アルファベット小文字)が生成されて Array で返ります。文字の長さは最小になるようになっているので、長さ 1 と 2 の文字列が入り混じっています。
文字列の長さを指定して呼ぶこともできます。
irb(main):003:0> Utils.generate_random_strings(40, 4) => ["bakz", "aipi", "prgo", "cwfw", "qqkv", "lgtt", "neid", "jjjz", "cjst", "tdfd", "sguf", "nkqk", "bvpl", "tldk", "qszi", "qfvj", "mnjy", "epsd", "abix", "ldap", "lijm", "jqzl", "gclu", "fxxe", "tcxc", "rayu", "rcsn", "aitp", "focj", "ngxd", "ouxc", "reze", "svxc", "ppaz", "roeb", "qgdt", "mhdw", "ewap", "fxjb", "mmrx"]
長さ 4 のランダムな文字列が 40個返りました。
コード。
generate_random_strings.rb
module Utils def repeated_permutation(a, b) a ** b end def generate_random_strings(num, string_length = nil) table = [*"a".."z"] limit = [0, 26, 702, 18278, 475254, 12356630] result = [] generate_string1 = ->(n, l) { st = "" l.times do a, n = n % 26, n / 26 st = table[a] + st end st } generate_string2 = ->(n) { idx = limit.find_index {|i| i > n} generate_string1.(n - limit[idx - 1], idx) } if string_length and 26 < string_length raise "Given length of strings too big." end num_table = Set.new if string_length n = Utils.repeated_permutation(26, string_length) raise "Given length of strings too small." if n < num while num_table.size < num num_table << rand(n) end num_table.each {|i| result << generate_string1.(i, string_length)} else idx = limit.find_index {|i| i >= num} raise "Result Array too big." unless idx while num_table.size < num num_table << rand(limit[idx]) end num_table.each {|i| result << generate_string2.(i)} end result end module_function :repeated_permutation, :generate_random_strings end
Gem 化
Gem 'kaki-utils' に同梱しました。$ gem install kaki-utils や Bundler でインストールできます。
$ bundle exec irb irb(main):001:0> require 'kaki/utils' => true irb(main):002:0> Utils.generate_random_strings(40) => ["qv", "kj", "wf", "ch", "ds", "hp", "ro", "oj", "xa", "dz", "vv", "zz", "fh", "rf", "tr", "gw", "cf", "yx", "ep", "pr", "tl", "sn", "ar", "ao", "ij", "pl", "my", "gy", "sk", "yk", "to", "hq", "wj", "vf", "jh", "pu", "cg", "gq", "wu", "dx"]
みたいな感じ。
Ruby/Rouge でコードをシンタックスハイライトした HTML と CSS を出力させる
Ruby コードを HTML化して、ついでにシンタックスハイライトもできるようにできないか、やってみました。
画像だとこんな感じです。全体はこういう風です。
RubyGem 'rouge' というものを使いました。インストールはふつうに $ gem install rouge とかBundler でどうぞ。
とりあえず HTML と CSS を吐かせるのにかなり苦労しました。上の HTML, CSS ファイルはこんなコードで出力しています。
rouge_sample.rb
require 'rouge' source = File.read('oekaki_sample16.rb') formatter = Rouge::Formatters::HTML.new lexer = Rouge::Lexers::Ruby.new html = formatter.format(lexer.lex(source)) css = Rouge::Themes::ThankfulEyes.render(scope: '.highlight') html1 = <<EOS <!DOCTYPE html> <html lang="ja"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Syntax-Highlight</title> <head> <link rel="stylesheet" type="text/css" href="rouge_sample1.css"> <style type="text/css"> pre.highlight { font-family: Liberation Mono, Consolas, monospace, sans-serif; font-size: 86%; padding: 10px; line-height: 1.1em; } </style> </head> <body> <pre class="highlight"> EOS html = html1 + html + "</pre>\n\n</body>\n</html>" open("rouge_sample1.html", "w") {|io| io.write(html)} open("rouge_sample1.css", "w") {|io| io.write(css)}
色の付け方は Rouge::Themes::ThankfulEyes
のところで指定しています。ここでは ThankfulEyes を選んでいますが、Colorful, Github など、いろいろ選べます。詳しくはこちらを見て下さい。
また、表示させるソースの種類はRouge::Lexers::Ruby.new
で指定しています。これなら Ruby ですね。他のものはこちらを見て下さい。たいていのものはあるのではないでしょうか。
円落下の JavaScript 版
obelisk.hatenablog.comここの JavaScript 版です。ボタンをクリックしてみて下さい。
コード。<script type="text/javascript"> function circleFall(width, height) { var w = window.open("", null, "width=" + width + ",height=" + height); w.focus(); w.document.open(); var st = ` <!DOCTYPE html> <html lang="ja"> <title>Canvas</title> <body style="margin: 0; overflow: hidden;"> <canvas id="circleFallCanvas" width="${width}" height="${height}"></canvas> </body> </html> `; w.document.write(st); w.document.close(); var canvas = w.document.getElementById("circleFallCanvas"); var wd = w.innerWidth; var ht = w.innerHeight; //Chrome は 0 を返す if (wd != 0) {width = wd;} if (ht != 0) {height = ht;} canvas.width = width; canvas.height = height; var context = canvas.getContext("2d"); var max_r, min_r, colorMax, maxNum; max_r = 40; min_r = 10; colorMax = 256; maxNum = 60; var Circle = function(f) { function rnd(num) {return Math.random() * num;} this.maxR = rnd(max_r - min_r) + min_r; this.x = rnd(width); this.color = "rgb(" + String(rnd(colorMax)) + "," + String(rnd(colorMax)) + "," + String(rnd(colorMax)) + ")"; this.fallStep = rnd(3) + 1; this.r = 1; this.r_step = rnd(0.8) + 0.2; if (f) { this.y = rnd(height); } else { this.y = -rnd(max_r); } this.paint = function() { context.beginPath(); context.fillStyle = this.color; context.arc(this.x, this.y, this.r, 0, 2 * Math.PI); context.fill(); this.y += this.fallStep; this.r += this.r_step; if (this.r > this.maxR || this.r < 1) {this.r_step *= -1} if (this.y > height + max_r) {return true} return false; } } var circles = []; for (var i = 0; i < maxNum; i++) {circles[i] = new Circle(true);} window.draw = function() { context.fillStyle = "black"; context.fillRect(0, 0, width, height); for (var i = 0; i < maxNum; i++) { if (circles[i].paint()) {circles[i] = new Circle(false);} } } var id = setInterval("draw()", 80); function bul() {clearInterval(id);} w.onbeforeunload = bul; } </script> <form><input type="button" value="アニメーション" onclick="circleFall(1000, 700)"> <input type="button" value="(小さい版)" onclick="circleFall(800, 600)"></form>
Ruby で関数型プログラミングっぽく(コピペ) + Haskell 版
parrot.hatenadiary.jpここのブログ記事を読んで感銘を受けました。だからこれを読んでもらえればよいのですが、せっかくなのでコピペしておきます。元記事に感謝です。
まずは問題。
ある数字にそれを逆に並べた数字を足すという計算を、
https://parrot.hatenadiary.jp/entry/20110302/1299051431
回文数(上から読んでも下から読んでも同じ数)になるまで繰り返すとき、
もっとも計算回数を要する二桁の数を答えなさい
例:ab+ba=123の場合、123+321=444で回文数なので、2回となる
Ruby コードです。多少自己流に書き直しました。
reverse_num = ->(num) {num.to_s.reverse.to_i} is_palindrome = ->(num) {num == reverse_num.(num)} execute = ->(count, num) { r = num + reverse_num.(num) is_palindrome.(r) ? count : execute.(count + 1, r) } result = (10..99).map{|n| [n, execute.(1, n)]} max_count = result.map(&:last).max result.select{|r| r.last == max_count}.each{|r| puts "num:#{r.first} count:#{r.last}"}
結果。89 と 98 ですね。24回繰り返しています。
$ time ruby palindrome_num.rb num:89 count:24 num:98 count:24 real 0m0.091s user 0m0.076s sys 0m0.012s
いや、すばらしい。
Haskell 版
頑張って Scala ならぬ Haskell で同様のことをやってみました。
palindrome_num.hs
reverseNum :: Int -> Int reverseNum num = read $ reverse $ show num isPalindrome :: Int -> Bool isPalindrome num = (num == reverseNum num) execute :: Int -> Int -> Int execute count num = if isPalindrome r then count else execute (count + 1) r where r = num + reverseNum num main :: IO () main = putStr $ unlines $ map toS $ [r | r <- result, last r == maxCount] where result = [[n, execute 1 n] | n <- [10..99]] maxCount = maximum $ map last result toS [a, b] = "num:" ++ show a ++ " count:" ++ show b
こんなのでいいのかな。
結果。
$ time ./palindrome_num num:89 count:24 num:98 count:24 real 0m0.003s user 0m0.000s sys 0m0.000s
当然のことながら瞬殺ですな。
しかし、Ruby でも結構関数型っぽく書けますね。ほとんどそのまま Haskell になるじゃん。自分は Ruby で慣れているので、Ruby の方が見やすいくらいだ。以下の記事もよろしければどうぞ。
obelisk.hatenablog.com
- 作者:Miran Lipovača
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
追記(2020/12/4)
Ruby 版後半があまり美しくないので、少し凝ってみる。
module Enumerable def max_select pool = [] max_num = -Float::INFINITY each do |i| n = yield(i) if n > max_num max_num = n pool = [i] elsif n == max_num pool << i end end [max_num, pool] end end
Enumerable#max_select
は、ブロックの返り値の最大値(max_num
)を求めて、そのような最大値になるようなものをレシーバーからコレクトし(pool
)、[max_num, pool]
を返すメソッド。例えばこんな風に使える。ローマ字化した県名のうち、もっとも文字数が長いもの。
url = "https://gist.githubusercontent.com/koseki/38926/raw/671d5279db1e5cb2c137465e22424c6ba27f4524/todouhuken.txt" prefectures = URI.open(url).each_line.map {|l| l.chomp.split.last} prefectures.max_select(&:size) #=>[9, ["fukushima", "yamanashi", "hiroshima", "yamaguchi", "tokushima", "kagoshima"]]
9文字が最大値だとわかる。
まあ、他に使いみちがあるかどうかわからないものだが(笑)、これを使って以下のように。
reverse_num = ->(num) {num.to_s.reverse.to_i} is_palindrome = ->(num) {num == reverse_num.(num)} execute = ->(num, count = 1) { r = num + reverse_num.(num) is_palindrome.(r) ? count : execute.(r, count + 1) } count, numbers = (10..99).max_select(&execute) puts "max count: #{count}" puts "number: #{numbers}"
ちょっと関数型プログラミングっぽいかも知れない笑。
Ruby/SDL を使ってみる
これまで Ruby で画面に線を引いたり円を描いたりするのに、自作の Gem 'oekaki' を使ってきましたが、その中では Gem 'gtk2' を使っていて、たかがお絵かきに GTK+ を使うのは大袈裟すぎるようにも思われたので、Ruby/SDL を使ってみることにしました。Linux MInt 18.3, Ruby 2.5.1 で確認しています。(SDL と SGE が入れば、Windows や Mac でも動く筈です。)
まずは SDL のインストールが必要です。Linux では apt-get で入ります。libsdl2-2.0 は既に入っているかもしれません。
sudo apt-get install libsdl2-2.0 libsdl-sge-dev
お絵かきをするには特に SGE(SDL Graphics Extension)が必要です。SDL 2.0 の日本語リファレンスはここにあります。
Ruby/SDL をインストール。gem install rubysdl でよいです。もちろん Bundler で入れてもかまいません。Ruby/SDL のリファレンス・マニュアルはここにあります。簡単なサンプルはこちらもどうぞ。
サンプルとして、Gem 'oekaki' で描いた(参照)のと同様のデモを載せておきます。複数の円が拡大縮小しながら落下していきます。
コード。簡単なライブラリのようなもの。
sdl_draw.rb
require 'sdl' def draw(width, height, &blk) SDL.init(SDL::INIT_VIDEO) screen = SDL::Screen.open(width, height, 16, SDL::SWSURFACE) SDL::WM::set_caption("SDL", "") class << screen def color(r, g, b) format.map_rgb(r, g, b) end end Thread.new {screen.instance_eval(&blk)} loop do while (event = SDL::Event.poll) case event when SDL::Event::Quit exit end end sleep 0.2 end end
サンプル。
sdl_sample3.rb
require_relative 'sdl_draw' Width, Height = (ARGV.size == 2) ? ARGV.map(&:to_i) : [1000, 700] Max_r, Min_r = 40, 10 ColorMax = 256 MaxNum = 60 class Circle def initialize(ob) @slot = ob renewal @y = rand(Height) end def renewal @max_r = rand(Min_r..Max_r) @x = rand(Width) @y = -rand(@max_r) @color = @slot.color(rand(ColorMax), rand(ColorMax), rand(ColorMax)) @fall_step = rand(1..4) @r = 1 @r_step = rand(0.2..1.0) end def paint @slot.draw_circle(@x, @y, @r, @color, true, true) @y += @fall_step @r += @r_step @r_step *= -1 if @r > @max_r or @r < 1 renewal if @y > Height + Max_r end end draw(Width, Height) do circles = Array.new(MaxNum) { Circle.new(self) } black = color(0, 0, 0) loop do fill_rect(0, 0, Width, Height, black) circles.each(&:paint) flip sleep(0.08) end end
Ruby/SDL、シンプルでよいですね。これからも使ってみたいです。
Gem 'oekaki' 版の動画です。こんな感じ。
追記(10/22)
実行時に
uninitialized constant SDL::Mixer (NameError)
のエラーが出る場合は、
$ sudo apt-get install libsdl-mixer1.2 libsdl-mixer1.2-dev
でライブラリを入れてから、Gem を再インストールしてみて下さい。