メタプログラミング Ruby poker

目次

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

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

1 pokerのテスト駆動開発

1.1 目的

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

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

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

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

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

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

1.4 test-unit

2 開発概要

2.1 クラスの構造

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 カードに関する定数をまとめるモジュール

2.2 ディレクトリ構造

~/COMM/bin/lstree ../poker

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

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

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

4 Constant

4.1 constant

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

4.1.1 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

4.1.2 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

4.1.3 run

ruby -I./src test/test-constant.rb

5 Card

5.1 card module

5.1.1 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

5.1.2 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

5.1.3 test の実行

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

6 Deck

6.1 deck

6.1.1 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

6.1.2 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

7 Hand

7.1 hand

7.1.1 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

7.1.2 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

8 Player

8.1 test-player.rb

8.1.1 test構造

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

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

8.1.2 new

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

8.1.3 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, @p.hand)
end

end

8.2 player.rb

8.2.1 クラス構造

module Poker

  class Player

8.2.2 initialize

attr_reader :name, :hand

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

8.2.3 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

9 Rakefile

9.1 test 用の Rakefile

9.1.1 rake

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

Rakefile は,ruby で書ける Makefile

9.1.2 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

10 emacs/org-mode/ruby のこと

10.1 org-mode

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

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'

10.1.2 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: 2016-12-11 日 21:42

Emacs 25.1.1 (Org mode 8.2.10)

Validate