メタプログラミング Ruby poker

目次

メタプログラミングRuby / 毎回の講義 / OOへ至る道 / ruby入門 / 講義ドキュ メント / note / poker

~suzuki/lects/meta-ruby/lects/poker ~suzuki/lects/meta-ruby/lects/note

pokerのテスト駆動開発

目的

ソフトウェア構成論で作ったポーカーを, rubyで作り直し,ruby での開発を体験してみましょう。

テスト駆動開発っぽくやってみましょう。

emacs org-mode でやりましょう。

ポーカーゲーム開発ストーリー

モジュール分けやモジュールの機能については,Prog Wikky - 講義内容 を参考にしてください。

テスト駆動開発については

test-unit

開発概要

クラスの構造

module Poker

  module Constant

  end
  class Card
    
  end
  class Deck
    
  end
  class Hand
    
  end
  class Player
    
  end
  class Play
    
  end
end
  • 12/21 Poker::Constant カードに関する定数をまとめるモジュール

ディレクトリ構造

~/COMM/bin/lstree ../poker

文芸的プログラムによる開発

例えばcardモジュールは,poker/card.org に作成し,org-babel の :tangle でtest/の下にテストを,src/の下にクラスを,作りましょう。

~suzuki/lects/meta-ruby/lects/poker の下にこのドキュメントがあります。 参考にしてください。

Constant

constant

カードに関する定数を定義するモジュール

test-constant.rb

test の構造
require 'test/unit'
require 'constant'

class Test_Constant < Test::Unit::TestCase
  include Poker
end
suit に関するテスト
  def test_constant_suit
    assert_equal(Const::SuitSyms.index(:CLUB), Const::CLUB)
    assert_equal(Const::SuitSyms.index(:DIAMOND), Const::DIAMOND)
    assert_equal(Const::SuitSyms.index(:HEART), Const::HEART)
    assert_equal(Const::SuitSyms.index(:SPADE), Const::SPADE)
    assert_equal(Const::SuitOrder[0], Const::CLUB)
    assert_equal(Const::SuitOrder[1], Const::DIAMOND)
    assert_equal(Const::SuitOrder[2], Const::HEART)
    assert_equal(Const::SuitOrder[3], Const::SPADE)
  end
no に関するテスト
  def test_constant_no
    assert_equal(Const::NoOrder, [  
                   Const::TWO, Const::THREE, Const::FOUR, Const::FIVE,
                   Const::SIX, Const::SEVEN, Const::EIGHT, Const::NINE,
                   Const::TEN, Const::JACK, Const::QUEEN, Const::KING,
                   Const::ACE])
  end
カードの枚数に関するテスト
  def test_constant_card
    assert_equal(52, Const::No_of_Cards)
  end
テスト全体
require 'test/unit'
require 'constant'

class Test_Constant < Test::Unit::TestCase
  include Poker

  def test_constant_suit
    assert_equal(Const::SuitSyms.index(:CLUB), Const::CLUB)
    assert_equal(Const::SuitSyms.index(:DIAMOND), Const::DIAMOND)
    assert_equal(Const::SuitSyms.index(:HEART), Const::HEART)
    assert_equal(Const::SuitSyms.index(:SPADE), Const::SPADE)
    assert_equal(Const::SuitOrder[0], Const::CLUB)
    assert_equal(Const::SuitOrder[1], Const::DIAMOND)
    assert_equal(Const::SuitOrder[2], Const::HEART)
    assert_equal(Const::SuitOrder[3], Const::SPADE)
  end

  def test_constant_no
    assert_equal(Const::NoOrder, [  
                   Const::TWO, Const::THREE, Const::FOUR, Const::FIVE,
                   Const::SIX, Const::SEVEN, Const::EIGHT, Const::NINE,
                   Const::TEN, Const::JACK, Const::QUEEN, Const::KING,
                   Const::ACE])
  end

  def test_constant_card
    assert_equal(52, Const::No_of_Cards)
  end

end

constant.rb

モジュールの構造
module Poker
  
  module Const
  end
end
suit に関する定数
    SuitSyms = [:CLUB, :DIAMOND, :HEART, :SPADE] 
    SuitChars = ['C', 'D', 'H', 'S'] 
    SuitOrder = [] # suit の強さ

    # 定数の生成: CLUB, DIAMOND, HEART, SPADE 

    SuitSyms.each_index do |i| 
      SuitOrder << const_set(SuitSyms[i], i)
    end
no に関する定数
    NoSyms = [:TWO, :THREE, :FOUR, :FIVE, :SIX, :SEVEN, :EIGHT, :NINE, :TEN,
              :JACK, :QUEEN, :KING, :ACE]
    NoChars = ['2', '3', '4', '5', '6', '7', '8', '9', '0', 'J', 'Q', 'K', 'A']
    NoOrder = []
    NoSyms.each_index do |i| 
      NoOrder << const_set(NoSyms[i], i)
    end
card に関する定数
    No_of_Cards = SuitOrder.size*NoOrder.size
constant.rb
module Poker
  
  module Const

    SuitSyms = [:CLUB, :DIAMOND, :HEART, :SPADE] 
    SuitChars = ['C', 'D', 'H', 'S'] 
    SuitOrder = [] # suit の強さ

    # 定数の生成: CLUB, DIAMOND, HEART, SPADE 

    SuitSyms.each_index do |i| 
      SuitOrder << const_set(SuitSyms[i], i)
    end


    NoSyms = [:TWO, :THREE, :FOUR, :FIVE, :SIX, :SEVEN, :EIGHT, :NINE, :TEN,
              :JACK, :QUEEN, :KING, :ACE]
    NoChars = ['2', '3', '4', '5', '6', '7', '8', '9', '0', 'J', 'Q', 'K', 'A']
    NoOrder = []
    NoSyms.each_index do |i| 
      NoOrder << const_set(NoSyms[i], i)
    end

    No_of_Cards = SuitOrder.size*NoOrder.size

    
  end
end

Card

card module

test/test-card.rb

構造
require 'test/unit'
require 'constant'
require 'card'

class Test_Card < Test::Unit::TestCase
  include Poker
end
test/setup
  def setup
    @sa = Card.new(Const::SPADE, Const::ACE)
    @sk = Card.new(Const::SPADE, Const::KING)
  end
test_new
  def test_card_new
    assert_equal(Card, @sa.class)
    assert_equal(Const::SPADE, @sa.suit)
    assert_equal(Const::ACE, @sa.no)
  end
test_compare
  def test_card_compare
    assert_equal(1, @sa.compare(@sk))
    assert_equal(0, @sa.compare(@sa))
    assert_equal(-1, @sk.compare(@sa))
  end
test_to_s
  def test_card_to_s
    assert_equal('SK', @sk.to_s)
    assert_equal('SA', @sa.to_s)
  end
test/test-card.rb
require 'test/unit'
require 'constant'
require 'card'

class Test_Card < Test::Unit::TestCase
  include Poker
  
  def setup
    @sa = Card.new(Const::SPADE, Const::ACE)
    @sk = Card.new(Const::SPADE, Const::KING)
  end

  def test_card_new
    assert_equal(Card, @sa.class)
    assert_equal(Const::SPADE, @sa.suit)
    assert_equal(Const::ACE, @sa.no)
  end

  def test_card_compare
    assert_equal(1, @sa.compare(@sk))
    assert_equal(0, @sa.compare(@sa))
    assert_equal(-1, @sk.compare(@sa))
  end

  def test_card_to_s
    assert_equal('SK', @sk.to_s)
    assert_equal('SA', @sa.to_s)
  end

end

card.rb

構造
require 'constant'

module Poker

  class Card
  end
end
new, suit, no
    attr_reader :suit, :no

    def initialize(suit, no)
      no = 14 if no==1
      @suit = suit
      @no = no
    end
compare
    def <=> (another)
      return 1 if @no > another.no
      return -1 if @no < another.no
      return 1 if @suit > another.suit
      return -1 if @suit < another.suit
      return 0
    end

    alias :compare :<=>
to_s
    def to_s
      (Const::SuitChars[@suit])+(Const::NoChars[@no])
    end
card.rb
require 'constant'

module Poker

  class Card 

    attr_reader :suit, :no

    def initialize(suit, no)
      no = 14 if no==1
      @suit = suit
      @no = no
    end

    def <=> (another)
      return 1 if @no > another.no
      return -1 if @no < another.no
      return 1 if @suit > another.suit
      return -1 if @suit < another.suit
      return 0
    end

    alias :compare :<=>

    def to_s
      (Const::SuitChars[@suit])+(Const::NoChars[@no])
    end

  end
end

test の実行

# ~/.rbenv/shims/ruby -I./src/ test/test_card.rb
ruby -I./src/ test/test_card.rb
echo 'end'

Deck

deck

test-deck.rb

deck test プログラムの構造
require 'test/unit'
require 'constant'
require 'card'
require 'deck'

class TestDeck < Test::Unit::TestCase
  include Poker
end
newのテスト
  def test_deck_new
    @d = Deck.new
    assert_equal(Const::No_of_Cards, @d.size)
  end
drawのテスト
  def test_deck_draw
    @d = Deck.new
    size = @d.size
    c = @d.draw
    assert_equal(Card, c.class)
    assert_equal(1, size-@d.size)
  end
discardのテスト
  def test_deck_discard
    @d = Deck.new
    (2*Const::No_of_Cards).times do |i|
      c = @d.draw
      assert_equal(Card, c.class)
      @d.discard(c)
    end
    assert_equal(0, @d.size)
  end
test全体
require 'test/unit'
require 'constant'
require 'card'
require 'deck'

class TestDeck < Test::Unit::TestCase
  include Poker

  def test_deck_new
    @d = Deck.new
    assert_equal(Const::No_of_Cards, @d.size)
  end

  def test_deck_draw
    @d = Deck.new
    size = @d.size
    c = @d.draw
    assert_equal(Card, c.class)
    assert_equal(1, size-@d.size)
  end

  def test_deck_discard
    @d = Deck.new
    (2*Const::No_of_Cards).times do |i|
      c = @d.draw
      assert_equal(Card, c.class)
      @d.discard(c)
    end
    assert_equal(0, @d.size)
  end
end

deck.rb

deck class の構造
# deck.rb
module Poker
  class Deck
  end
end
initialize
    def initialize

      @stock = []
      @used = []

      Const::SuitOrder.each { |suit|
        Const::NoOrder.each { |no|
          @stock.push(Card.new(suit, no))
        }
      }
      @stock.shuffle!

    end
draw
    def draw
      if @stock.size == 0
        @stock = @used
        @used = []
        @stock.shuffle!
      end
      @stock.pop
    end
size
    def size
      @stock.size
    end
discard
    def discard(card)
      @used.push(card)
      self
    end
shuffle!
    def shuffle!
      @stock.shuffle!
    end
deck 全体
# deck.rb
module Poker
  class Deck

    def initialize

      @stock = []
      @used = []

      Const::SuitOrder.each { |suit|
        Const::NoOrder.each { |no|
          @stock.push(Card.new(suit, no))
        }
      }
      @stock.shuffle!

    end

    def draw
      if @stock.size == 0
        @stock = @used
        @used = []
        @stock.shuffle!
      end
      @stock.pop
    end


    def discard(card)
      @used.push(card)
      self
    end


    def size
      @stock.size
    end


    def shuffle!
      @stock.shuffle!
    end

  end
end

Hand

hand

test-hand.rb

require 'test/unit'
require 'card'
require 'hand'

include Poker

class Test_Hand < Test::Unit::TestCase
end
setup
  def setup
    @ha = Card.new(Const::HEART, Const::ACE)
    @sa = Card.new(Const::SPADE, Const::ACE)
    @ca = Card.new(Const::CLUB, Const::ACE)
    @sq = Card.new(Const::SPADE, Const::QUEEN)
    @hq = Card.new(Const::HEART, Const::QUEEN)
    @hand = Hand.new
    @hand.putin(@ha)
    @hand.putin(@sa)
    @hand.putin(@ca)
    @hand.putin(@sq)
    @hand.putin(@hq)
  end
test method
  def test_fullhouse
    assert_equal(:fullHouse, @hand.judge)
  end
require 'test/unit'
require 'card'
require 'hand'

include Poker

class Test_Hand < Test::Unit::TestCase
  def setup
    @ha = Card.new(Const::HEART, Const::ACE)
    @sa = Card.new(Const::SPADE, Const::ACE)
    @ca = Card.new(Const::CLUB, Const::ACE)
    @sq = Card.new(Const::SPADE, Const::QUEEN)
    @hq = Card.new(Const::HEART, Const::QUEEN)
    @hand = Hand.new
    @hand.putin(@ha)
    @hand.putin(@sa)
    @hand.putin(@ca)
    @hand.putin(@sq)
    @hand.putin(@hq)
  end

  def test_fullhouse
    assert_equal(:fullHouse, @hand.judge)
  end
end

hand.rb

hand class の構造
require 'card'

module Poker

  class Hand

    PokerHands = [
      :highCard, :onePair, :twoPairs, 
      :threeCards, :straight, :flush, :fullHouse, :fourCards, :straightFlush
    ]
  end
end
initialize
    attr_reader :hand

    def initialize
      @hand = []
    end
putin 手札にカードを入れる
    def putin(card)
      @hand.push(card).sort!
    end
rank 役の強さ
    def rank
      PokerHands.index(judge)
    end
judge 役の判定
    def judge
      s = straight?
      f = flush?
      return :straightFlush if s && f 
      return :straight if s 
      return :flush if f 
      return calc_pairs
    end
calc_pairs ペアの計算
    def calc_pairs
      p = 0
      @hand.each { |a|
        @hand.each { |b|
          p = p+1 if a.no == b.no
        }
      }
      case p 
      when 5 then :highCard 
      when 7 then :onePair 
      when 9 then :twoPairs
      when 11 then :threeCards
      when 13 then :fullHouse 
      when 17 then :fourCards 
      end
    end
straight?
    def straight?
      false
    end
flush?
    def flush?
      false
    end
hand.rb 全体
require 'card'

module Poker

  class Hand

    PokerHands = [
      :highCard, :onePair, :twoPairs, 
      :threeCards, :straight, :flush, :fullHouse, :fourCards, :straightFlush
    ]

    attr_reader :hand

    def initialize
      @hand = []
    end

    def putin(card)
      @hand.push(card).sort!
    end
    
    def rank
      PokerHands.index(judge)
    end
    
    def judge
      s = straight?
      f = flush?
      return :straightFlush if s && f 
      return :straight if s 
      return :flush if f 
      return calc_pairs
    end

    def calc_pairs
      p = 0
      @hand.each { |a|
        @hand.each { |b|
          p = p+1 if a.no == b.no
        }
      }
      case p 
      when 5 then :highCard 
      when 7 then :onePair 
      when 9 then :twoPairs
      when 11 then :threeCards
      when 13 then :fullHouse 
      when 17 then :fourCards 
      end
    end

    def straight?
      false
    end

    def flush?
      false
    end
    
  end
end

Player

test-player.rb

test構造

require 'test/unit'
require 'constant'
require 'card'
require 'hand'
require 'player' 

class TestDeck < Test::Unit::TestCase
  include Poker
end

new

  def test_player_new
    @p = Player.new("hoge")
    assert_equal("hoge",  @p.name)
    assert_equal(Hand.new, @h.hand)
  end

test全体

require 'test/unit'
require 'constant'
require 'card'
require 'hand'
require 'player' 

class TestDeck < Test::Unit::TestCase
  include Poker

  def test_player_new
    @p = Player.new("hoge")
    assert_equal("hoge",  @p.name)
    assert_equal(Hand.new, @h.hand)
  end

end

player.rb

クラス構造

module Poker

  class Player

initialize

    attr_reader :name, :hand

    def initialize(name)
      @name = name
      @hand = Hand.new()
    end

player.rb 全体

Poker
poker.rb
require 'card'
require 'deck'
require 'hand'
require 'player'

module Poker

  class Play

    def initialize (names = ["foo", "bar", "hoge", "nanasi", "gombei"])
      @players = names.map { |n| Player.new(n) }      
    end

    def play
      deck = Deck.new
      5.times do 
        @players.each do |p|
          p.hand.putin(deck.draw)
        end
      end

      @players.each do |p|
        print p.name, ": "
        print p.hand.hand
      end

      @players.each do |p|
        p p.hand.judge
      end
    end
  end
end

if __FILE__ ==$0
  (Poker::Play).new.play
end
run
ruby -I./src poker.rb

Rakefile

test 用の Rakefile

rake

library rake (Ruby 2.2.0) はビルドツール

Rakefile は,ruby で書ける Makefile

Rakefile

# coding:utf-8
tests = ["test-card.rb", 
         "test-deck.rb",
         "test-hand.rb",
         "test-player.rb",
        ]

task :default => :test

task :test do
  tests.each do |test_file|
    sh "ruby -I./src test/#{test_file}"
  end
end
rake test

emacs/org-mode/ruby のこと

org-mode

ソースコードをファイルへ書き出す

org-mode での書き方

#+BEGIN_SRC ruby :tangle example/test_card.rb :mkdirp yes
# example/test_card.rb
require 'rubygems'
require 'test/unit'
'end'
#+END_SRC

Emacs コマンド

C-C C-v C-t
# example/test_card.rb
require 'rubygems'
require 'test/unit'
end'

ob-shell

ob-sh.el ソースコード
sh の shell 指定
(defvar org-babel-sh-command "sh"
  "Command used to invoke a shell.
This will be passed to  `shell-command-on-region'")
sh-ブロックの ruby が ~/.rbenv/shims/ruby ではない時

~/.rbevn/shims/ruby が動かないときは, sh-ブロックに :session を指定して, 動かして,シェルバッファに移動して,

著者: suzuki@cis.iwate-u.ac.jp

Created: 2015-12-21 月 13:22

Emacs 24.5.1 (Org mode 8.2.10)

Validate