第26回 Ruby/Rails勉強会@関西に参加

日本Rubyの会 公式Wiki - 第26回 Ruby/Rails勉強会@関西
http://jp.rubyist.net/?KansaiWorkshop26


遅くなったが、Ruby初級者向けレッスン by okkezさんの回答だけでも貼っとこう。そういえば、前々回の分が、okkezさんに添削されていたのだった。今更ですがありがとうございます。気付かなくて申し訳なかったです。


勉強会の内容については、

  • Rack, nginx, thinが面白そうなので、調べたい。
  • ujihisaさんの発表で出たvimscriptだが、vimscriptからRubyを使うことが出来るのか。emacs(elisp)からRubyが使えたりもするのだろうか。elispなんか全く分からないが。ちょっと調べた感じでは情報が見つからない・・・。

という感じでした。

1. ナベアツ問題

Macだと簡単にしゃべらせることが出来るようだ。楽しそう。でも、windowsでがんばってしゃべらせる気にはならないなあ。

MIN = 1
MAX = 100

class Nabeatsu
  def perform(min, max)
    min.upto(max) do |i|
      speech = ""
      speech << "Aho" if should_be_idiot?(i)
      speech << "Meaow" if should_be_animal?(i)
      speech << i.to_s if speech == ""
      puts speech
    end
  end
  
  def should_be_idiot?(num)
    num % 3 == 0 || num.to_s =~ /3/
  end

  def should_be_animal?(num)
    num % 5 == 0
  end
end

Nabeatsu.new.perform(MIN, MAX)
2. Bottles of Beer on the Wall

残りボトルが無くなった時に歌詞が変わるのに最初気付かなかった。

class Song
  @@initial_bottle_num = 99
  @@lyrics = ["%s of beer on the wall",
              "%s of beer",
              "Take one down and pass it around",
              "%s of beer on the wall"]
  @@third_line_for_last = "There are no more to pass around"
  @remaining_bottle_num = 0
  
  def initialize
    @remaining_bottle_num = @@initial_bottle_num 
  end

  def sing_all
    sing
    sing_all if 0 <= @remaining_bottle_num
  end

  def sing
    num  = bottle_count(@remaining_bottle_num)
    remain = bottle_count(@remaining_bottle_num - 1)
    lyrics = @@lyrics
    lyrics[2] = @@third_line_for_last if @remaining_bottle_num <= 0
    printf(lyrics.join("\n") + "\n\n", num, num, remain)
    @remaining_bottle_num = @remaining_bottle_num - 1
  end

  private
  def bottle_count(num)
    case
    when num <= 0
      num = "No more bottles"
    when num == 1
      num = num.to_s + " Bottle" 
    else
      num = num.to_s + " Bottles" 
    end
  end
end

song = Song.new.sing_all
3. 石取りゲーム

長い!!考え過ぎだろう。ROBOT_SELFISH_RATEの率で、スタート時の石の数が先手が勝つ個数で、かつプレイヤーが先手を選んだ時に、ゴネて自分を先手にする。また、ROBOT_MISTAKE_RATEの率で、相手が勝つような残り個数の時に、ミスって負けに転じる数を取る。0にすると全くミスらない。

require "observer"

module GameLogic
  def take(stone_count)
    if win?(stone_count)
      #取る数毎に、勝つか負けるかを取得
      max_takable = [MAX_TAKABLE_STONE, stone_count].min
      wins_or_loses = 
        (1..max_takable).to_a.inject({:wins => [], :loses => []}) do |h, i|
          win?(stone_count - i) ? h[:wins] << i : h[:loses] << i
          h
        end
      if rand < ROBOT_MISTAKE_RATE
        #負ける方を選択
        wins_or_loses[:wins][rand(wins_or_loses[:wins].size)]
      else
        #勝つ方を選択
        wins_or_loses[:loses][rand(wins_or_loses[:loses].size)]
      end
    else
      take_random(stone_count)
    end
  end

  def win?(stone_count)
    stone_count % (MAX_TAKABLE_STONE + 1) != 1 
  end

  def robot_selfish?(stone_count)
    win?(stone_count) && rand <= ROBOT_SELFISH_RATE
  end

  def take_random(stone_count)
    rand([MAX_TAKABLE_STONE, stone_count].min) + 1
  end

  def takable?(num, stone_count)
    !num.nil? && 0 < num.to_i && num.to_i <= [MAX_TAKABLE_STONE, stone_count].min
  end

  def continue?(input)
    !input.nil? && (input == "y" || input == "Y")
  end
end

class State
  include GameLogic
  attr_accessor :result, :stone_count, 
    :players_turn, :player_last_took, :robot_last_took

  def initialize(state = nil)
    if !state.nil?
      @result =state.result
      @stone_count = state.stone_count
      @players_turn = state.players_turn
      @player_last_took = state.player_last_took
      @robot_last_took = state.robot_last_took
    end
  end
end

class InitialState < State
  def apply(input = nil)
    @stone_count = rand(MAX_STONE_COUNT - MIN_STONE_COUNT) + MIN_STONE_COUNT + 1
    @result = :greeting
    SelectTurnState.new(self)
  end
end

class SelectTurnState < State
  def apply(input = nil)
    @players_turn = input.to_i
    @players_turn = nil if @players_turn != 1 && @players_turn != 2
    if @players_turn.nil?
      @result = :select_turn
      self
    else
      selfish = robot_selfish?(@stone_count)
      if selfish && @players_turn == 1
        @players_turn = 2
        @result = :selfish_turn_determination
      else
        @result = :turn_determination
      end
      TurnDeterminationState.new(self)
    end
  end
end

class TurnDeterminationState < State
  def apply(input = nil)
    @player_last_took = nil
    @robot_last_took = nil
    if @players_turn == 2
      @robot_last_took = take(@stone_count)
      @stone_count = @stone_count - @robot_last_took
    end 
    @result = :take_stone
    PlayState.new(self)
  end
end

class PlayState < State
  def apply(input = nil)
    @player_last_took = nil
    @robot_last_took = nil
    if takable?(input, @stone_count)
      @player_last_took = input.to_i
      @stone_count = @stone_count - @player_last_took
      if @stone_count <= 0
        @result = :game_over_player_lost
        GameOverState.new(self)
      else
        @robot_last_took = take(@stone_count)
        @stone_count = @stone_count - @robot_last_took
        if @stone_count <= 0
          @result = :game_over_player_won        
          GameOverState.new(self)
        else
          @result = :take_stone
          self
        end
      end
    else
      @result = :take_stone_error
      self
    end
  end
end

class GameOverState < State
  def apply(input = nil)
    if continue?(input)
      @result = :restart
      InitialState.new(self)
    else
      @result = nil
      nil
    end
  end
end

class TextUi
  attr_accessor :game, :response

  def initialize(game)
    @game = game
    @game.add_observer(self)
    @response = TextResponse.new
  end
  
  def update(state)
    print @response.render(state)
    @game.update(gets.strip)
  end
end

class TextResponse
  def render(state)
    send(state.result.to_s, state) unless state.result.nil?
  end

  def greeting(state)
    "石の数は #{state.stone_count} 個です。\n" + 
      "先手/後手を選択してください。(1:先手/2:後手)>"
  end

  def select_turn(state)
    "先手/後手を選択してください。(1:先手/2:後手)>"
  end

  def turn_determination(state)
    sprintf "あなたは %s です。何か押すとスタートします。>", 
      state.players_turn == 1 ? "先手" : "後手"
  end

  def selfish_turn_determination(state)
    "ダメです。あなたは後手です。何か押すとスタートします。>"
  end

  def take_stone(state)
    stone_count(state) + takable_stones + ">"
  end

  def take_stone_error(state)
    remaining_stone_count(state) + takable_stones + ">"
  end

  def game_over_player_won(state)
    stone_count(state) + "\nあなたの勝ちです。もう一回やりますか?(y:もう一回/n:終了)>"
  end

  def game_over_player_lost(state)
    stone_count(state) + "\n私の勝ちです。もう一回やりますか?(y:もう一回/n:終了)>"
  end

  def restart(state)
    "何か押すと最初から始めます>" 
  end

  private
  def takable_stones
    sprintf "何個取りますか?(%s)", (1..MAX_TAKABLE_STONE).to_a.join("/")
  end

  def stone_count(state)
    res = player_took(state)
    robot_took = robot_took(state)
    res += res != "" && robot_took != "" ? " / " + robot_took : robot_took
    res += res != "" ? " 取りました。" : ""
    res += remaining_stone_count(state) 
  end

  def player_took(state)
    state.player_last_took.nil? ?
      "" :
      "あなた: #{state.player_last_took}" 
  end

  def robot_took(state)
    state.robot_last_took.nil? ?
      "" :
      "私: #{state.robot_last_took}"
  end

  def remaining_stone_count(state)
    "残り #{state.stone_count} 個です。"
  end
end

class Game
  include Observable
  attr_accessor :state

  def initialize
    @state = InitialState.new
  end

  def start
    update(nil)
  end

  def update(input)
    @state = @state.apply(input)
    if @state.nil?
      exit
    else
      changed
      notify_observers(@state)
    end
  end
end

MIN_STONE_COUNT = 10
MAX_STONE_COUNT = 100
MAX_TAKABLE_STONE = 3
ROBOT_SELFISH_RATE = 0.7
ROBOT_MISTAKE_RATE = 0.1

game = Game.new
ui = TextUi.new(game)
game.start

今他の人達のエントリ見たら、なんかみんな短いな。