質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

Q&A

解決済

4回答

4491閲覧

Rubyでポーカーの役を判定する

yuz55

総合スコア11

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

0グッド

1クリップ

投稿2017/04/14 00:40

編集2017/04/14 04:12

###前提・実現したいこと
Rubyで、ポーカーの役を判定するアプリケーションを作っています。
五枚のカードの情報(例:S8 S7 H6 H5 S4)を入力すると、それが何の役になっているのか判定できるようにしたいのですが、パターンマッチの部分で行き詰っています。

###該当のソースコード

ruby

1 2 3 4 5#役の配列をつくる。インデックスの大小で役の強弱を表す。 6YAKU = ["High_Cards", "One_Pair", "Two_Pair", "Three_of_a_Kind", "Straight", "Flush", "Fullhouse", "Four_of_a_Kind", "Straight_Flush", "Royal_Straight_Flush"] 7 8 9#役の判断。大きい順に行う。 10 11 12def eval_hand 13 14 puts "文字列を入力してください(例:S8 S7 H6 H5 S4)" 15 #半角空白で文字列を分割し、配列を返す。 16 hand = gets.split 17 #それそれのカードから、スートの部分だけ、数字の部分だけを取り出した配列を別に作る。 18 suits = Array.new 19 numbers = Array.new 20 21 hand.each do |c| 22 suits.push c[0] 23 numbers.push c[1] 24 end 25 26 def same_number_counts 27 same_numbers = numbers.group_by{|r|r}.map(&:size).sort.reverse 28 end 29 30 def straight? 31 steps = numbers.sort.map{|r|r-numbers[0]} 32 steps == [0,1,2,3,4] || steps == [0,9,10,11,12] 33 end 34 35 def flush? 36 suits.uniq.size == 1 37 end 38 39 case same_number_counts 40 when [2,1,1,1] 41 yaku = YAKU[1] 42 when [2,2,1] 43 yaku = YAKU[2] 44 when [3,1,1] 45 yaku = YAKU[3] 46 when [3,2] 47 yaku = YAKU[6] 48 when [4,1] 49 yaku = YAKU[7] 50 else 51 case [straight?, flush?] 52 when [true, false] 53 yaku = YAKU[4] 54 when [false, true] 55 yaku = YAKU[5] 56 when [true, true] 57 if numbers == [1,10,11,12,13] 58 yaku = YAKU[8] 59 else 60 yaku = YAKU[9] 61 end 62 else 63 yaku = YAKU[0] 64 end 65 end 66end 67 68def end_program 69 exit 70end 71 72def exception 73 puts "入力された値は無効です" 74end 75 76#メニューの表示 77while true do 78 puts "[0]役を判定する" 79 puts "[1]アプリを終了する" 80 input = gets.to_i 81 82 if input == 0 then 83 eval_hand 84 elsif input == 1 then 85 end_program 86 else 87 exception 88 end 89end

上記のファイルpoker.rbをターミナル上で実行したところ、以下のようなエラーが発生してしまいました。

:in same_number_counts': undefined local variable or method numbers' for main:Object (NameError)
from poker.rb:38:in eval_hand' from poker.rb:82:in <main>'

numbersの定義の部分に問題があるようなのですが…どうすれば解決できるでしょうか。ご助言いただけると嬉しいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

それぞれのカードから「スートの部分だけ」「ナンバーの部分だけ」を取り出した配列を別に作って置けばよいと思います。

Ruby

1 hand = gets.split 2 suits = new Array 3 numbers = new Array 4 5 hand.each do |c| 6 suits.push c[0] 7 numbers.push c[1] 8 end

こうしておいて、さらにsuits と numbers をソートしておけば、

  • ロイヤルストレート:numbers が A,10,J,Q,K で構成されている
  • ストレート:numbers が先頭から重複も抜けもなく5つの値が連続していること
  • フラッシュ:suits が全て同じスートのみで構成されている
  • スリーカード: numbers の中の値毎の個数を数えたとき、個数が3個である値が存在する
  • ツーペア: numbers の中の値毎の個数を数えたとき、個数が2以上である値が二つ存在する

といったように個別にチェックできるでしょう。
※ストレートとフラッシュがそれぞれ成立していれば、それはストレートフラッシュです。また、スリーカードとツーペアが同時に成立するなら、それはフルハウスになります

ジョーカー(ワイルドカード)を許容するかどうかでまた変わってきますが、基本的にはこの考え方で行けるはずです。

投稿2017/04/14 01:05

tacsheaven

総合スコア13703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yuz55

2017/04/14 04:14

ありがとうございます。いただいた回答を参考に、上のようなコードを書いたのですが、実行するとエラーが発生してしまいました。numbersの定義に問題があるようなのですが…。 どうか、今一度お力を貸していただけるとありがたいです!
moke

2017/04/14 05:06 編集

tacsheaven様の式はRubyっぽく書くと arr=[['S',8] ['S',7] ['H',6],['H',5],['S',4]] tarr=arr.transpose suits=tarr[0] numbers=tarr[1] と書けると思います。
guest

0

tacsheaven様の回答に補足という感じですが。
rubyはポーカー判定に向いているかもと思いrubyっぽい記述で概略を描いてみました。
※強さ判定が必要になったら
強さを数値で置き換えて判定すればいいとして(1が強くスペードが強い)
[役,数字,マーク]にして<=>で辞書式順序で比較してください
まず簡単のため
[J,Q,K,A]=[11,12,13,14]
[S,H,D,C]=[4,3,2,1]
とおきます。数字の大きさが強さ順になります。
tacsheaven様の式はRubyっぽく書くと

ruby

1arr=[[4,8] [4,7] [3,6],[3,5],[4,4]] 2tarr=arr.transpose 3 suits=tarr[0] 4 numbers=tarr[1]

と書けます。

で、まずnumbersの重複を調べます。

重複なし→ストレートかフラッシュかブタ
重複あり→ワンペアかツーペアかスリーカードかフルハウスかフォーカード

ruby

1if numbers.uniq.size==5#重複なし 2'フラッシュ' if suits.uniq.size==1#マークが1種類 3'ストレート' if (numbers.map{|n|n+1} & numbers).size=4#自分自身に1を足した数列との共通部分の要素が4つになる 4'ロイヤル' if numbers.sort==[10,11,12,13,14] 5else#重複あり 6 case when.group_by(&:itself).map{|k,v|v.size}.sort#重複ありの場合group_byを使って、それぞれの要素の数を数える。 7when [1,1,1,2] 8'ワンペア' 9when [1,2,2] 10'ツーペア' 11when [1,1,3] 12'スリーカード' 13when [2,3] 14'フルハウス' 15when [1,4] 16'フォーカード' 17end 18end

動くかは試していませんが雰囲気はわかるかと思います。

投稿2017/04/14 04:57

編集2017/04/14 16:42
moke

総合スコア2241

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yuz55

2017/04/14 23:06

ありがとうごじます。具体的にコードまで教えていただきありがたいです。参考にさせて頂きました!
guest

0

エラーにならないように書き換えてみました。
(判定結果がただしいかは検証していません。)

ruby

1# 役の配列をつくる。インデックスの大小で役の強弱を表す。 2YAKU = %w[ 3 High_Cards One_Pair Two_Pair Three_of_a_Kind 4 Straight Flush Fullhouse Four_of_a_Kind 5 Straight_Flush Royal_Straight_Flush 6].freeze 7 8# 役の判断。大きい順に行う。 9def eval_hand 10 puts '文字列を入力してください(例:S8 S7 H6 H5 S4)' 11 # 半角空白で文字列を分割し、配列を返す。 12 hand = gets.split 13 # それそれのカードから、スートの部分だけ、数字の部分だけを取り出した配列を別に作る。 14 suits = [] 15 numbers = [] 16 17 hand.each do |c| 18 suits << c[0] 19 numbers << c[1..-1].to_i 20 end 21 22 def same_number_counts(numbers) 23 numbers.group_by { |r| r }.map(&:size).sort.reverse 24 end 25 26 def straight?(numbers) 27 sorted = numbers.sort 28 steps = sorted.map { |r| r - sorted[0] } 29 steps == [0, 1, 2, 3, 4] || steps == [0, 9, 10, 11, 12] 30 end 31 32 def flush?(suits) 33 suits.uniq.size == 1 34 end 35 36 case same_number_counts(numbers) 37 when [2, 1, 1, 1] 38 YAKU[1] 39 when [2, 2, 1] 40 YAKU[2] 41 when [3, 1, 1] 42 YAKU[3] 43 when [3, 2] 44 YAKU[6] 45 when [4, 1] 46 YAKU[7] 47 else 48 case [straight?(numbers), flush?(suits)] 49 when [true, false] 50 YAKU[4] 51 when [false, true] 52 YAKU[5] 53 when [true, true] 54 if numbers == [1, 10, 11, 12, 13] 55 YAKU[8] 56 else 57 YAKU[9] 58 end 59 else 60 YAKU[0] 61 end 62 end 63end 64 65def end_program 66 exit 67end 68 69def exception 70 puts '入力された値は無効です' 71end 72 73# メニューの表示 74loop do 75 puts '[0]役を判定する' 76 puts '[1]アプリを終了する' 77 input = gets.to_i 78 79 case input 80 when 0 81 puts eval_hand 82 when 1 83 end_program 84 else 85 exception 86 end 87end

実行例:

$ ruby poker.rb [0]役を判定する [1]アプリを終了する 0 文字列を入力してください(例:S8 S7 H6 H5 S4) S8 S7 H6 H5 S4 Straight [0]役を判定する [1]アプリを終了する 1

投稿2017/04/14 23:33

katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yuz55

2017/04/15 07:51

ご回答を参考に、類似のコードを自分で書いてみたところ、無事に完成させることができました。詳しく教えていただきありがとうございます!
guest

0

ベストアンサー

エラーしている単純な理由は same_number_counts メソッドの中で参照している numbers が、この場所では有効(参照可能)でないためです。

関数定義のすぐ外側で有効だから中でも有効かというとそうではありません。ようするにある変数がある場所で有効かどうかという「スコープ」の問題です。

(ちなみにJavaScriptでは同様なケースで有効なので、そこらへんと混同しているのかもですね。またRubyの場合でも普通の関数定義ではなく「クロージャ」という別のものを使う場合は参照できたりしますがここでは割愛します)

そのコードのようにしたいのであれば、一例としては suits と numbers を @suits と @numbers に置き換えます。「@」から始まる変数名はインスタンス変数となり、今回のようなメソッドをまたがる場合でも参照可能なスコープを持っているというわけです。

補足

なお今回のソースはclass定義をしてないので「インスタンス?」となるかもしれませんが、今回のエラーメッセージにも含まれているようにclassの外で定義したインスタンス変数やメソッド等は「main」という名のデフォルトインスタンスに含まれるようになっているので、 @suits 等はその main のインスタンス変数となります。

さらにおまけ

メソッド定義(def)の中でメソッド定義を行ってますが、Rubyの場合、普通はこのようなことはしませんので、外に出した方が自然なコードとして良いです。中でメソッド定義を行っても外で定義してるのとほとんど変わらないので、今回のような勘違いが起きるだけです。

投稿2017/04/14 16:36

ksaga

総合スコア62

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yuz55

2017/04/14 23:09

詳しく説明していただき大変助かります。自分が混乱していたこと、理解が足りなかったことがしっくり頭に入ってきました。ありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問