データの格納と表現例

たとえばトランプのカードをプログラミング言語で処理することを考えてみ よう。

カードの性質

カードの持つ性質をまとめてみよう。

もっとも特徴的なことは、枚数が決まっている点であろう。

トランプのカードの表現方法

同じ種類のものがたくさんあるとき、日常生活ではどうやって区別するだろ う。トランプのように規則的に繰り返しているものとしてアパートの部屋を思い 浮かべると、「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で割った余り カード
000ハート A
101ハート 2
202ハート 3
::::
11011ハート Q
12012ハート K
1310スペード A
1411スペード 2
::::
24111スペード Q
25112スペード K
2620ダイヤ A
2721ダイヤ 2
::::
37211ダイヤ Q
38212ダイヤ K
3930クラブ A
4031クラブ 2
::::
50311クラブ Q
51312クラブ K

しかしながら、もし通し番号を1から始めてしまうと、

となってしまって同じハートなのに商が変わることになり、割り算だけで グループ分けを行なうことができなくなる。

したがって、ここではトランプの通し番号を0から数え始めることにする。 13で割った余りに1を足せば、カードの数字になる。

【鉄則】 計算機処理するものを数えるときは0番からにする と都合がよいことが多い。

配列への格納

カードが単純な整数(0〜52)で管理できるなら、必要になるたびにランダムで 0〜52の整数を生成するようにすればよいと感じるかもしれないが、それでは各カー ドを重複なく順番に取り出すことはできない。なぜなら0〜52の擬似乱数を53回生 成した場合、5が2回続けて出たり、52が全部で4回出たり、といったことは普通に 起こり得るからである。

このような場合は、「通し番号」のような単純な整数の場合でも、それを 配列に格納して順番に取り出すようにすればよい。

[0][1][2]………… [50][51][52]
012………… 505152

この図で見ると、配列の添字とカードの通し番号が一致しているので 無駄に思えるが、実際にカードを配るときには各要素は添字とは無関係の位置に 移動(シャッフル)することで、実際のカードゲームらしくなる。シャッフル方法 については後述する。

このような配列を用意する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枚分作り配列にする。

01 …… 5152
"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つの方式を検討した。

  1. 単純通し番号方式(配列のみ)
  2. スートと数の配列をさらに配列に格納
  3. "suit", "n" をキーとするハッシュ(53個)を要素に持つ配列

プログラム作成に当たって上記3つの方式から、一番処理の書きやすいものを 選ばねばならない。 ただ結論からいうと、作りたいゲームの種類によってどれが適切かが変わってくる。 ただ、多くのカードゲームでは、ハートのエース("A")を、文字列"A"として 処理するよりも、数値の1と見なして考えることが多いので、

という対応関係がすぐに計算できる方がプログラムを書きやすい。 よって、カードを通し番号で保持している方法はこの点で有利である。 逆に、カードそのものを画面に表示する場合には、通し番号と、 カードの絵柄が分かった方が作りやすい。この場合は 2, 3 いずれの データ格納方式も好都合である。

次回実際にゲームを作るときに、どれを利用するか考えよう。


本日の目次