メタプログラミング 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 テスト駆動開発については
ソフトウェア構成論 テスト駆動開発とは を参考にしてください
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 を指定して, 動かして,シェルバッファに移動して,