石取りゲーム
先日のRuby勉強会のお題だったのですが、時間がなくて演習中にできなかったので、今日改めてやってみました。というかid:nshttskさんの成果(id:nshttsk:20070428:1177787956)をリファクタリングしてみました(賢いコンピュータの実装はサボりました)。リファクタリングのポイントは以下です。
- テストし易そうなインターフェースにする(Mockオブジェクトを使ってでテストできる部分を増やす)。
- (上にもからめて)入出力部分をまとめる。MVCモデルぽくする。
うーむ、やっぱりかえって複雑になってしまったような気がします。それにしてもメソッド名のセンスが悪いなぁ。我ながら。
コメントありましたらお願いします。どしどしお願いします。
#!/usr/bin/env ruby module StoneTakingGame class Player def initialize(game, name) @game = game @name = name end attr_reader :name def how_many_stones raise "#{@name} must rewrite this method." end end class HumanPlayer < Player def initialize(game, name) super(game, name) end def how_many_stones @game.query_how_many_stones end end class RandomComputerPlayer < Player def initialize(game, name = 'Random Computer') super(game, name) end def how_many_stones rand(3) + 1 end end class Board def initialize(nstones = rand(10) + 10) @nstones = nstones end attr_reader :nstones def take(n) @nstones -= n end def empty? @nstones < 1 end end class View def query_game_config name = get_line('Input your name: ') name = 'Hoge' if name.empty? is_first = (get_line('Do you take stone at FIRST? [Y/n]: ') =~ /[Yy]/) [name, is_first] end def query_how_many_stones n = get_line('Input number of taking stone: ').to_i raise 'invalid number.' unless (1 .. 3).include?(n) n rescue puts "input between 1 and 3, OK?" retry end def disp_beggining_of_turn(player, board) puts "[#{player.name}'s turn]" s = "Number of stone: #{board.nstones}\n" board.nstones.times do |n| s << " " if n % 10 == 0 and n != 0 s << "\n" if n % 20 == 0 and n != 0 s << '*' end puts s end def disp_how_many_stones(player, n) puts "#{player.name} takes #{n} stone#{(n <= 1) ? '' : 's'}" puts end def disp_lost_player(player) puts "#{player.name} lost!" end private def get_line(prompt) $stdout.print prompt $stdout.flush gets.chomp end end class Game def initialize(view, board, players = [RandomComputerPlayer.new(self)]) @view = view @board = board @players = players end def start name, is_first = @view.query_game_config @players.push HumanPlayer.new(self, name) @players.reverse! if is_first each_players do |player| @view.disp_beggining_of_turn(player, @board) n = player.how_many_stones @board.take(n) @view.disp_how_many_stones(player, n) if @board.empty? @view.disp_lost_player(player) break end end end def query_how_many_stones @view.query_how_many_stones # delegate to @view. end private def each_players loop { @players.each {|p| yield(p) } } end end end if $0 == __FILE__ include StoneTakingGame Game.new(View.new, Board.new).start end