設計のプログラム化

プログラム化する際には、対象とする事象をどんなデータ構造で表すかを きちんと設計できれば、残りは自然と決まってくる。今回は既に、トランプの カードをどのように表現するか3パターンで考察してあるので、それらのうちか らブラックジャックの作成に有利なものを採択すればい。

問題がさほど複雑ではないので、今回の場合3つのデータ構造にはっきりした 優劣は見当たらず、どれを選んでも差し支えない。そこで採択する規準として「ハッ シュの練習」を重視することにして、今回は3番目の「53個のハッシュを配列」に したデータ構造を採択してプログラム化を進めよう。

必要な変数とメソッド

簡略化して設計したゲームを実現するために必要な部品を考えていこう。

変数

まずは、どんなデータをどういう変数に格納する必要があるかを考えるとこ ろから開始する。データの格納形式によって自ずとそれを処理するのに必要なメ ソッドが決まってくる。

シャッフルされたカードを保持する配列が必要なのは 前回触れたとおりである。さらに、コンピュータに配られたカード と、プレイヤーに配られたカードを保持する配列も必要だろう。

あとは、コンピュータ、プレイヤ両者の得点を記録する変数も必要だが、こ れは単純な数値なので、とくに難しくないだろう。

メソッド

もう一度実行例を確認しよう。

% ./blackjack.rb
ゲーム開始
わたしの手:
  [ ??? ]
  ハートのK
あなたの手:
  ダイヤの3
  クラブのK
もう一枚引きますか?(yかnで) y
あなたの手:
  ダイヤの3
  クラブのK
  ハートの7
もう一枚引きますか?(yかnで) n
勝負!
わたしの手:
  ダイヤの8
  ハートのK
あなたの手:
  ダイヤの3
  クラブのK
  ハートの7

わたし 18 : 20 あなた
で、あなた の勝ちです!

上記の実行例を見ると、

が必要だと分かる。これらのメソッドを設計していこう。

カードの配布と表示

カードを配るためにはまず、持ちカードを保存する配列と、 その配列にカードを格納していく部分を作らなければならない。 コンピュータが持っているカードの配列を com変数、プレイヤーの 持っているカードの配列を player変数、 にそれぞれ格納することにしよう。

  1. カードを52枚作り、シャッフルする部分を作る。 これは前回作成した2つのメソッドのままでよいだろう。 ただし、ジョーカーは作らないものとする。

    # カード1枚の情報を持つハッシュを52個入れる配列を作るメソッド
    def createCardsHashArray()
      card = Array.new		# 空の配列を作る
      for suit in ["ハート", "スペード", "ダイヤ", "クラブ"]
        card << {"suit" => suit, "n" => "A"}
        # ここで代入しているのがハッシュ
        2.upto(10) do |n|		# nが2から10まで変動して繰り返す
          card << {"suit" => suit, "n" => n.to_s}
        end
        for n in ["J", "Q", "K"]	# nに"J","Q","K"が順に代入される
          card << {"suit" => suit, "n" => n}
        end
      end
      # 前回はここにジョーカー作成があった。それを削除。
      card				# 最後に card配列 を呼び主に返す
    end
    
    # 受け取った配列をシャッフルするメソッド
    # これは前回のものと全く同じ
    def shuffle(a)
      srand
      0.upto(a.length-1) do |i|
        j = rand(a.length)    # 交換相手をランダムに選ぶ
        w = a[i]
        a[i] = a[j]
        a[j] = w
      end
      a
    end
    
  2. カードを配る部分を作る。

    カードを1枚ずつ配るには、カードを格納した配列の先頭から順に要素 を取り出していく。そのためには、今何枚目かを覚えておかなければなら ないので、そのための変数を用意して、順に取り出す。

    com    = Array.new
    player = Array.new
    cardNo = -1		# 今何枚目かを保存する変数
    cards  = shuffle(createCardsHashArray())
    
    # コンピュータの配列comに2枚配る。
    com << cards[cardNo+=1]	# +=1 すると 1足した結果自体も返す
    com << cards[cardNo+=1]	# 2枚目を配る
    
    # プレイヤの配列playerに2枚配る。
    player << cards[cardNo+=1]	# +=1 すると 1足した結果自体も返す
    player << cards[cardNo+=1]	# 2枚目を配る
    
  3. 配られたカードを表示する部分を作る。

    まずは、カードを1枚だけ引数で受け取ってそれを表示するメソッドを 作ろう。

    def dispCard(card)
      〜〜〜定義〜〜〜
    end
    

    となるのだが、少し詳しく考察しよう。 cardには1枚分のハッシュが入る。たとえば、以下のとおり。

    {"suit" => "ハート", "n" => "4"}

    ここから、スートと数を取り出して表示するには添字に keyを指定すればよいので。

    def dispCard(card)
      printf("%s の %s\n", card["suit"], card["n"])
    end
    

    となる。これを、コンピュータの手、プレイヤーの手について表示す ればよい。ただし、コンピュータは1枚隠しておくので、2枚目だけ表示す る。

    puts "わたしの手:"
    # コンピュータの1枚目を表示
    puts "  [ ??? ]"
    # コンピュータの2枚目を表示
    dispCard(com[1])	# 2枚目は com[1] に入っている
    # プレイヤー側
    puts "あなたの手:"
    dispCard(player[0])
    dispCard(player[1])
    

持ちカードの合計点を計算するメソッド

コンピュータ、プレイヤー側、いずれも配列に全ての持ちカードが入ってい る。これらの数の部分を足して計算するメソッドが必要となる。計算のときの規 則は以下のようになる。

  1. "2"〜"9"なら整数化(.to_i)してそのまま足す。
  2. "10", "J", "Q", "K" なら10を足す。
  3. "A"なら11を足す。21を超過したら10を引く。

問題となるのは、"A"の処理である。21を超過した場合に 「"A"があった場合のみ10を引く」という処理が必要になる、しか も"A"が複数枚あった場合は、枚数分だけ「21を超過していたら10 引く」を繰り返す必要がある。ということで、合計点メソッドの要点は、

となる。これをメソッドにすると以下のようになる。

def point(arr)
  ace = 0		# "A"の枚数
  sum = 0		# 合計点
  for card in arr	# arr配列全ての要素に対して繰り返す
    n = card["n"]	# card["n"]で数を表すvalueが得られる
    case n		# nの値によって場合分け
    when "A"		# "A"ならば…
      ace += 1		# 枚数を1増やしておく
      sum += 11		# まずは11点として加算
    when "10", "J", "Q", "K"
      sum += 10		# 絵札と10は10点
    else		# それ以外(つまり2〜9)はそのものを整数にして足す
      sum += n.to_i
    end # when終わり
  end # for終わり
  while sum > 21 && ace > 0 # 21より大きく、かつ、"A"の枚数が1以上なら
    sum -= 10		# 10を引く
    ace -= 1		# "A"を1つ使ったので減らす
  end
  sum			# 最後に合計点を呼び主に返す
end

人間にもう一枚引くか確認する部分

カードを配り終わったあとで、プレイヤーにもう一枚引くか確認する。 これは、プレイヤーからの入力が y(es) である間繰り返せばよい。y なら プレイヤーの持ちカードに一枚追加する。

while true
  STDERR.print "もう一枚引きますか?(yかnで) "
  answer = gets
  if /^y/i =~ answer
    player << cards[cardNo+=1]
    puts "もう一枚引きます。引いたカードはこれです。"
    dispCard(player[-1])
  else
    break		# while true を抜ける
  end
end

コンピュータがもう一枚引くか決定する部分

プレイヤーが引き終わったあとで、コンピュータがさらに一枚引くか決める。 これはコンピュータの持ちカードの得点が16以下である間繰り返す。

while point(com) <= 16
  puts "わたしはもう一枚引きます"
  com << cards[cardNo+=1]
end

結果を表示する部分

プレイヤー、コンピュータ、ともに必要なだけカードを引き直したら、 最後に両者の手を全て表示して比べる。

puts "勝負!"
puts "わたしの手:"
for c in com		# コンピュータの手持ちカード全て繰り返す
  dispCard(c)
end
puts "あなたの手:"
player.each do |c|	# プレイヤーの手持ちカード全て繰り返す
  dispCard(c)
end

# 得点を比べる
printf("わたし %d : %d あなた", point(com), point(player))

以上でプログラムの骨格は揃った。これらを全て組み合わせて プログラムが完成する。

bj.rb

#!/usr/koeki/bin/ruby

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				# 最後に card配列 を呼び主に返す
end

def shuffle(a)
  srand
  0.upto(a.length-1) do |i|
    j = rand(a.length)    # 交換相手をランダムに選ぶ
    w = a[i]
    a[i] = a[j]
    a[j] = w
  end
  a
end

def dispCard(card)
  printf("%s の %s\n", card["suit"], card["n"])
end


def point(arr)
  ace = 0		# "A"の枚数
  sum = 0		# 合計点
  for card in arr	# arr配列全ての要素に対して繰り返す
    n = card["n"]	# card["n"]で数が入る
    case n		# nの値によって場合分け
    when "A"		# "A"ならば…
      ace += 1		# 枚数を1増やしておく
      sum += 11		# まずは11点として加算
    when "10", "J", "Q", "K"
      sum += 10		# 絵札と10は10点
    else		# それ以外(つまり2〜9)はそのものを整数にして足す
      sum += n.to_i
    end # when終わり
  end # for終わり
  while sum > 21 && ace > 0 # 21より大きく、かつ、"A"の枚数が1以上なら
    sum -= 10		# 10を引く
    ace -= 1		# "A"を1つ使ったので減らす
  end
  sum
end

# カードを作ってシャッフルしたものを cards配列に格納
cards=shuffle(createCardsHashArray)

# カードを数える変数。-1で初期化
cardNo=-1

# コンピュータの持ちカードを格納する配列
com=[]
com << cards[cardNo+=1]
com << cards[cardNo+=1]

# プレイヤーの持ちカードを格納する配列
player=[]
player << cards[cardNo+=1]
player << cards[cardNo+=1]

# 最初の手持ちを表示する
puts "わたしの手:"
# コンピュータの1枚目を表示
puts "  [ ??? ]"
# コンピュータの2枚目を表示
dispCard(com[1])	# 2枚目は com[1] に入っている
# プレイヤー側
puts "あなたの手:"
dispCard(player[0])
dispCard(player[1])

# プレイヤーが満足するまで引かせる
while true
  STDERR.print "もう一枚引きますか?(yかnで) "
  answer = gets
  if /^y/i =~ answer
    player << cards[cardNo+=1]
    puts "もう一枚引きます。引いたカードはこれです。"
    dispCard(player[-1])# [-1]は配列の最後の要素
  else
    break		# while true を抜ける
  end
end

# コンピュータが16以下なら引き続ける
while point(com) <= 16
  puts "わたしはもう一枚引きます"
  com << cards[cardNo+=1]
end

# いよいよ勝負
puts "勝負!"
puts "わたしの手:"
for c in com		# コンピュータの手持ちカード全て繰り返す
  dispCard(c)
end
puts "あなたの手:"
player.each do |c|	# プレイヤーの手持ちカード全て繰り返す
  dispCard(c)
end

# 得点を比べる
cp, pp = point(com), point(player)
printf("わたし %d : %d あなた\n", cp, pp)


if cp >= 22		# 22以上なら0点ということにしてしまう
  cp = 0		# (コンピュータ)
end

if pp >= 22		# 22以上なら0点ということにしてしまう
  pp = 0		# (プレイヤー)
end

if pp > cp		# コンピュータより点が高ければ
  puts "あなたの勝ちです。参りました!"
else			# そうでなければ(引き分け含む)
  puts "わたしの勝ちです。べろべろばあ〜"
end

本日の目次