メタプログラミング 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 を指定して, 動かして,シェルバッファに移動して,