たとえばトランプのカードをプログラミング言語で処理することを考えてみ よう。
カードの持つ性質をまとめてみよう。
もっとも特徴的なことは、枚数が決まっている点であろう。
同じ種類のものがたくさんあるとき、日常生活ではどうやって区別するだろ う。トランプのように規則的に繰り返しているものとしてアパートの部屋を思い 浮かべると、「1号室、2号室」のように1次元的に通し番号を付けて呼ぶ方法と、 「1階-A室、2階-A室」のように2次元的に呼ぶ方法がある。
トランプのカードを表現するときも両方が考えられる。このうち2次元的な表 現をする場合を考えると、Rubyでは「配列」と「ハッシュ」がこのような用途に 使えそうである。まとめると、トランプのカードの表現には
が考えられる。以下、それぞれの表現方法について考えてみよう。
単純に、カードに番号を付けて表す。
通し番号を振るのに、1からではなく0からにしたのには理由がある。 一般的に「N個ずつ」繰り返すものに通し番号を付けるときには、 0 〜 (N-1) を一括りにして、N進法で管理するとよい。なぜなら 通し番号をNで割った商の部分がグループ番号になり、余りの部分が グループ内番号になるからである。具体例を示そう。
上のカード番号では、27番はダイヤの2になるはずである。
27 ÷ 13 = 2あまり1 であるから、 27 / 13 = 2 (整数) → グループ番号は2 27 % 13 = 1 → カード番号は1
グループ番号とカード番号ともに0から数えることにすれば、 商と余りを利用した計算で全てうまくいく。
通し番号 | 13で割った商 | 13で割った余り | カード |
---|---|---|---|
0 | 0 | 0 | ハート A |
1 | 0 | 1 | ハート 2 |
2 | 0 | 2 | ハート 3 |
: | : | : | : |
11 | 0 | 11 | ハート Q |
12 | 0 | 12 | ハート K |
13 | 1 | 0 | スペード A |
14 | 1 | 1 | スペード 2 |
: | : | : | : |
24 | 1 | 11 | スペード Q |
25 | 1 | 12 | スペード K |
26 | 2 | 0 | ダイヤ A |
27 | 2 | 1 | ダイヤ 2 |
: | : | : | : |
37 | 2 | 11 | ダイヤ Q |
38 | 2 | 12 | ダイヤ K |
39 | 3 | 0 | クラブ A |
40 | 3 | 1 | クラブ 2 |
: | : | : | : |
50 | 3 | 11 | クラブ Q |
51 | 3 | 12 | クラブ K |
しかしながら、もし通し番号を1から始めてしまうと、
となってしまって同じハートなのに商が変わることになり、割り算だけで グループ分けを行なうことができなくなる。
したがって、ここではトランプの通し番号を0から数え始めることにする。 13で割った余りに1を足せば、カードの数字になる。
【鉄則】 計算機処理するものを数えるときは0番からにする と都合がよいことが多い。
カードが単純な整数(0〜52)で管理できるなら、必要になるたびにランダムで 0〜52の整数を生成するようにすればよいと感じるかもしれないが、それでは各カー ドを重複なく順番に取り出すことはできない。なぜなら0〜52の擬似乱数を53回生 成した場合、5が2回続けて出たり、52が全部で4回出たり、といったことは普通に 起こり得るからである。
このような場合は、「通し番号」のような単純な整数の場合でも、それを 配列に格納して順番に取り出すようにすればよい。
[0] | [1] | [2] | ………… | [50] | [51] | [52] |
0 | 1 | 2 | ………… | 50 | 51 | 52 |
この図で見ると、配列の添字とカードの通し番号が一致しているので 無駄に思えるが、実際にカードを配るときには各要素は添字とは無関係の位置に 移動(シャッフル)することで、実際のカードゲームらしくなる。シャッフル方法 については後述する。
このような配列を用意するRubyプログラムは以下のようになるだろう。
def createCardsArray() card=Array.new # 空の配列を作る。[]と同じ 0.upto(52) do |x| card << x end card # 最後に card配列 を呼び主に返す end
続いて、カードを表現するのに、通し番号ではなく配列を使う方法を考えよ う。カードにはスートと数字がある。この二つの情報が表せれば、個々のカード が特定できる。ということで、要素数2の配列を使って、
[スート, 数字 ]
と保存すればよい。したがってこの方式を使って配列にカードを 格納する場合は、以下のようなイメージになる。配列の要素として さらに配列があることに注意せよ。
[0] | [1] | [2] | ………… | [51] | [52] |
["ハート", "A"] | ["ハート", "2"] | ["ハート", "3"] | ………… | ["クラブ", "K"] | ["その他", "JOKER"] |
これを初期化するRubyプログラムは以下のようになる。
def createCardsArray2() card=Array.new # 空の配列を作る for suit in ["ハート", "スペード", "ダイヤ", "クラブ"] card << [suit, "A"] 2.upto(10) do |n| card << [suit, n.to_s] # to_sで文字列化 end for n in ["J", "Q", "K"] card << [suit, n] end end card << ["その他", "JOKER"] card # 最後に card配列 を呼び主に返す end
ハッシュを用いて個々のカードを表現して変数に格納する 方法を考えよう。ハッシュは大きな空間の中で小さな部屋に好きな名前を付け、 その中に好きなデータを入れられる。カード一枚一枚を表すのにハッシュを 利用しよう。たとえば、「ハートの8」は以下のようなハッシュで表すことにす る。
"suit" "n" "ハート" "8"
「ハートの8」のハッシュのRuby表現 {"suit" => "ハート", "n" => "8"}
これを53枚分作り配列にする。
0
1
……
51
52
"suit"
"n"
"ハート"
"A"
"suit"
"n"
"ハート"
"2"
……
"suit"
"n"
"クラブ"
"K"
"suit"
"n"
"その他"
"JOKER"
この構造(ハッシュを集めた配列)を生成するメソッドは以下のようになる。
def createCardsHashArray() card = Array.new # 空の配列を作る for suit in ["ハート", "スペード", "ダイヤ", "クラブ"] card << {"suit" => suit, "n" => "A"} # ここで代入しているのがハッシュ 2.upto(10) do |n| card << {"suit" => suit, "n" => n.to_s} end for n in ["J", "Q", "K"] card << {"suit" => suit, "n" => n} end end card << {"suit" => "その他", "n" => "JOKER"} card # 最後に card配列 を呼び主に返す end
このメソッドcreateCardsHashArray
を呼ぶと以下のような、
要素にハッシュを持つ配列が生成される。
x = createCardsHashArray
=> [{"n"=>"A", "suit"=>"ハート"},
{"n"=>"2", "suit"=>"ハート"}, {"n"=>"3", "suit"=>"ハート"},
{"n"=>"4", "suit"=>"ハート"}, {"n"=>"5", "suit"=>"ハート"},
{"n"=>"6", "suit"=>"ハート"}, {"n"=>"7", "suit"=>"ハート"}, ………]
配列なので0から順序よく並んでいることに着目せよ。
以上3つの方式を検討した。
プログラム作成に当たって上記3つの方式から、一番処理の書きやすいものを 選ばねばならない。 ただ結論からいうと、作りたいゲームの種類によってどれが適切かが変わってくる。 ただ、多くのカードゲームでは、ハートのエース("A")を、文字列"A"として 処理するよりも、数値の1と見なして考えることが多いので、
"A"
= 1
"2"
= 2
"3"
= 3 :
"10"
= 10
"J"
= 11
"Q"
= 12
"K"
= 13
という対応関係がすぐに計算できる方がプログラムを書きやすい。 よって、カードを通し番号で保持している方法はこの点で有利である。 逆に、カードそのものを画面に表示する場合には、通し番号と、 カードの絵柄が分かった方が作りやすい。この場合は 2, 3 いずれの データ格納方式も好都合である。
次回実際にゲームを作るときに、どれを利用するか考えよう。