トラック
/
Crystal
Crystal
/
演習
/
ブラックジャック
ブラックジャック

ブラックジャック

学習演習

はじめに

caseは(他の言語ではしばしばswitchと呼ばれます)、if-elseのような制御式の一種です。caseを使うと、複数のif-else-if文を連ねて書けるので、より読みやすくなり、強力な構文も使えます。

caseは、キーワードcaseと、それに続く省略可能な式で定義します。次に、それぞれのケースごとにキーワードwhenを使い、そのあとにcase式と比較される式を書きます。whenキーワードはcaseキーワードからインデントしないようにします。whenキーワードの後ろには、case式がwhen式と一致したときに実行されるコードを書きます。caseには省略可能なelse文があり、どのケースにも一致しなかったときに実行されます。

case式は評価されたあと、各when式と比較されます。比較にはcase包摂演算子(===)が使われます。

case 1
when 1
  puts "One"
when 2
  puts "Two"
else
  puts "Other"
end
# => One

# This is the same as:
tmp = 1
if 1 === tmp
  puts "One"
elsif 2 === tmp
  puts "Two"
else
  puts "Other"
end

case包摂演算子(===)

case包摂演算子(===)は、等価演算子(==)とは少し違います。この演算子は、右辺が左辺の表す集合の要素であるかどうかを調べます。つまり、どちらのオペランドをどちら側に置くかが重要になります。どのように判定されるかは左辺の型によって変わり、たとえばRangeなら右辺がその範囲に入っているかどうかを、Objectなら右辺がそのObjectのインスタンスかどうかを調べます。

(1..3) == 1  # => false
(1..3) === 1 # => true

String == "foo"  # => false
String === "foo" # => true

複数の式を指定するcase

caseでは、1つのケースに複数の式を指定して一致させることができます。取りうる値はそれぞれカンマで区切ります。どれか1つでも一致すれば、そのコードが実行されます。1つのケースに複数の取りうる値があるときに便利です。

case 1
when 1, 2
  puts "One or two"
else
  puts "Other"
end

範囲を使うcase

caseでは、値がある範囲に入っているかどうかも調べられます。そのためには、when式に範囲を指定します。

case var
when 1..3
  puts "One to three"
else
  puts "Other"
end

case式を省略したcase

case式が不要なときは、省略できます。こうすると、それぞれのwhen式が真とみなされるかどうかで評価されるようになります。つまり、if-else-if文と同じように動作します。

case
when 1 == 1
  puts "One is equal to one"
when 1 > 2
  puts "One is greater than two"
else
  puts "Other"
end

1行で書くwhen

Crystalでは、case文を1行で書けます。シンプルな1行の文を書くときに使えます。1行で書くときは、when <expression> then <statement>.のように書きます。else文で使う場合は、else <statement>のように書きます。

case var
when 1 then puts "One"
when 2 then puts "Two"
else puts "Other"
end

型で分岐するcase

caseでは、型によるマッチングができます。比較にはis_a?演算子を使って、変数の型を調べます。たとえば、ユニオン型のそれぞれの型ごとに分岐を1つずつ書けます。

case var
when Int32
  puts "Int32"
when String
  puts "String"
else
  puts "Other"
end

暗黙のオブジェクト

caseでは、暗黙のオブジェクトを使えます。これにより、たとえばwhen式をcase式に対するメソッド呼び出しにできます。メソッドが真とみなされる値を返すかどうかで、そのケースが一致するかどうかが決まります。

case 1
when .odd?
  puts "Odd"
when .even?
  puts "Even"
end

説明

この演習では、ブラックジャックのゲームの最初のターンをシミュレーションします。

1. 与えられたカードの値を計算する

2枚のカードが配られ、ディーラーの表向きのカードが1枚見えています。 カードはすべて、"ace"、"king"、"three"、"two"などの文字列で表されます。 各カードの値は次のとおりです。

カード 値 カード 値
ace 11 eight 8
two 2 nine 9
three 3 ten 10
four 4 jack 10
five 5 queen 10
six 6 king 10
seven 7 other 0
Note

通常、エースは1または11の値を取ることができますが、ここでは簡単にするため、11の値しか取らないものとします。

カードを文字列として引数に取るBlackjack.parse_cardメソッドを実装してください。 このメソッドは、カードの値を整数で返します。

Blackjack.parse_card("ace")
# => 11

2. 値の範囲に名前を付ける

プレイヤーのスコアは、値の範囲に分類する必要があります。 プレイヤーのスコアは、プレイヤーの2枚のカードの値を合計して計算します。 使用する範囲は次のとおりです。

範囲 値
low [4, 11]
mid [12, 16]
high [17, 20]
blackjack [21]

2枚のカードを文字列として引数に取るBlackjack.card_rangeメソッドを実装してください。 このメソッドは、2枚のカードが該当する値の範囲の名前を返します。

Blackjack.card_range("ten", "king")
# => "high"

3. 最初のターンの判断ロジックを実装する

手持ちの2枚のカードとディーラーのカードに応じて、ゲームの最初のターンには戦略があり、次の選択肢があります。

  • スタンド(S)
  • ヒット(H)
  • スプリット(P)
  • 自動勝利(W)

まだ最適ではありませんが、友人のAlexが開発中の戦略に従います。その内容は次のとおりです。

  • エースのペアを持っている場合は、必ずスプリットします。
  • ブラックジャック(2枚のカードの合計が21になる場合)で、ディーラーがエース、絵札、または10を持っていなければ、自動的に勝ちです。 ディーラーがこれらのいずれかを持っている場合は、スタンドして、もう1枚のカードが公開されるのを待ちます。
  • カードの合計が[17, 20]の範囲にある場合は、必ずスタンドします。
  • カードの合計が[12, 16]の範囲にある場合は、ディーラーが7以上でない限り必ずスタンドします。ディーラーが7以上の場合は、必ずヒットします。
  • カードの合計が11以下の場合は、必ずヒットします。

3枚のカードを文字列として引数に取るBlackjack.first_turnメソッドを実装してください。 このメソッドは、取るべき判断を文字列で返します。

Blackjack.first_turn("ace", "ace", "two")
# => "P"
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Crystal Exercism

ブラックジャックを始める準備はできましたか?

Exercismに登録すれば、26個のコンセプト142個の演習、そして本物の人間によるメンタリングとともに、Crystalを学んでマスターできます。すべて無料です。