トラック
/
Clojure
Clojure
/
演習
/
カードゲーム
カードゲーム

カードゲーム

学習演習

はじめに

Clojureでは、リストはコレクションです。他の言語のリストと同じようなものです。Lispファミリーの言語と同じように、Clojureはリストを表すのに括弧(())を使います。

Clojureのリストは、2つの方法のいずれかで作成できます。list関数でリストを作るか、リストリテラルをquoteするかです。

リストが特別なのは、Clojureがリストを_呼び出し_として扱うからです。呼び出しは_演算子_で始まると想定されており、ふつうはこれが関数になります。リストの残りの要素は_被演算子_とみなされ、関数の引数になります。

Clojureがリストを特別に扱うため、リストリテラルを直接作ることはできません。quoteまたはその省略形'を使ってリストを引用すると、そのリストを評価しないよう指示したことになります。

一部のモダンな言語とは異なり、Clojureのリストは_異種混合_です。つまり、内部に複数の型の要素を含めることができます。たとえば'(2 "a" "b" 3)のようになります。 他のLispとは異なり、Clojureでは空のリストは真とみなされ、nilやfalseと同じではありません。

説明

Elyseは、もうすぐ始まるラスベガス旅行でポーカー(やその他のカードゲーム)をプレイするのをとても楽しみにしています。大の「セルフトラッキング」好きである彼女は、記録作業に役立つ小さな関数をいくつか作りたいと考えており、その内容を一緒に考えてほしいと頼んできました。

1. ポーカーのラウンドを記録する

Elyseはとりわけポーカーが好きで、自分が何ラウンドプレイしたのか、そしてそれが_どのラウンド_なのかを記録したいと思っています。各ラウンドにはそれぞれ番号があり、どのテーブルにも現在プレイ中のラウンド番号が表示されています。Elyseはテーブルを1つ選んで席に着き、最初のラウンドをプレイします。彼女は3ラウンドプレイする予定です。

現在のラウンド番号を受け取り、そのラウンドと、このあと続く_次の2つ_のラウンドを含む1つのlistを返すroundsという関数を実装しましょう。

(rounds 27)
;;=> (27 28 29)

2. すべてのラウンドを1か所で管理する

Elyseは最初のテーブルで何ラウンドかプレイし、それから休憩を挟んで2つ目のテーブルでも何ラウンドかプレイしました。ところが、テーブルごとに別々のリストになってしまいました。彼女は2つのリストを1つにまとめて、すべてのポーカーのラウンドを同じ場所で記録したいと考えています。

2つのリストを受け取り、1つ目のlistにあるすべてのラウンドのあとに2つ目のlistのすべてのラウンドを続けた、1つのlistを返すconcat-roundsという関数を実装しましょう。

(concat-rounds '(27 28 29) '(35 36))
;;=> (27 28 29 35 36)

3. 以前のラウンドを見つける

以前のポーカーのラウンドのいくつかについて話していると、別のプレイヤーが、そのうち2つはとても似た展開だったと口にしました。Elyseは、そのラウンドを自分がプレイしたのかどうか、よく分かりません。

2つの引数、プレイしたラウンドのリストとラウンド番号を受け取るcontains-round?という関数を実装しましょう。この関数は、そのラウンドがプレイしたラウンドのリストに含まれていればtrueを、含まれていなければfalseを返します。

(contains-round? '(27 28 29 35 36) 29)
;;=> true

(contains-round? '(27 28 29 35 36) 30)
;;=> false

4. カードの値の平均を求める

Elyseは、Black Joeという新しいゲームを試してみたくなりました。これはBlack Jackによく似ています。Black Jackでは、手札のカードの合計を目標の値にすることが目的ですが、Black Joeでは、カードの値の_平均_を7にすることが目的です。平均は、すべてのカードの値を合計し、その合計を手札の枚数で割ることで求められます。

Black Joeの手札の平均値を返すcard-averageという関数を実装しましょう。

(card-average '(5 6 7))
;;=> 6.0

5. 別の平均の求め方

Black Joeではスピードが重要です。Elyseは、平均をより速く求める方法を探してみることにしました。

彼女は、_平均に近い_数を求める方法を2つ思いつきました。

  • 手札の_最初_と_最後_の数の平均を取る。
  • 手札の中央値(真ん中のカード)を使う。

手札のカードの値を並べたリストであるhandを引数に取る、approx-average?という関数を実装しましょう。

上に挙げた方法の_どちらか_or_両方_が、_実際の平均_と_同じ_数になった場合にtrueを返します。

注:すべての手札の枚数は奇数で、中央値を見つけやすくなっています。

(approx-average? '(1 2 3))
;;=> true

(approx-average? '(2 3 4 8 8))
;;=> true

(approx-average? '(1 2 3 5 9))
;;=> false

6. さらに別の平均の求め方

平均の実験の結果に興味を引かれたElyseは、_偶数_の位置にあるカードの平均と、_奇数_の位置にあるカードの平均が同じ結果になるかどうか気になっています。テスト用の関数をもう1つ作ってみましょう!

偶数インデックスのカードの平均が、奇数インデックスのカードの平均と同じかどうかを示す真偽値を返すaverage-even-odd?という関数を実装しましょう。

(average-even-odd? '(1 2 3))
;;=> true

(average-even-odd? '(1 2 3 4))
;;=> false

7. ボーナスラウンドのルール

Black Joeでは、11回目の手札はボーナスルール付きのボーナスハンドになります。最後に引いたカードがジャックなら、その値を2倍にします。

手札を受け取り、最後のカードがジャック(11)かどうかを確認するmaybe-double-lastという関数を実装しましょう。最後のカードがジャック(11)である場合は、手札を返す前にその値を2倍にします。

(maybe-double-last '(5 9 11))
;;=> '(5 9 22)

(maybe-double-last '(5 9 10))
;;=> '(5 9 10)
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Clojure Exercism

カードゲームを始める準備はできましたか?

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