トラック
/
Crystal
Crystal
/
演習
/
チェスゲーム
チェスゲーム

チェスゲーム

学習演習

はじめに

範囲は、2つの値の間の区間を表します。 範囲をサポートする最も一般的な型は、Int、Char、Stringです。 コレクションを手早く作ったり、文字列を切り出したり、ある値が範囲に含まれているか調べたり、繰り返し処理をしたりと、さまざまなことに使えます。 範囲は、範囲演算子..または...を使って作ります(それぞれ終端を含むものと含まないものです)。

1..5  # A range containing 1, 2, 3, 4, 5
1...5 # A range containing 1, 2, 3, 4

範囲演算子が2つあるのは、終端の値を含む範囲と含まない範囲を作り分けるためです。これは、たとえば0から始まるインデックスを扱うときに便利です。

範囲は、Rangeの初期化でも作ることができます。

Range.new(1, 5) # A range containing 1, 2, 3, 4, 5
Note

Crystalで範囲演算子..や...を使って範囲を作り、その範囲に対してメソッドを呼び出したいときは、範囲を括弧(())で囲む必要があります。 そうしないと、メソッドが範囲演算子の2番目の引数に対して呼び出されてしまうからです。

(1..5).size # => 5
1..5.size # => Error: undefined method 'size' for Int32

部分文字列の取得

文字列を切り出したいときは、範囲演算子を使って部分文字列を取得できます。 つまり、部分文字列の開始インデックスと終了インデックスで範囲を作ります。

"Hello World"[0..4] # => "Hello"
"Hello World"[6..10] # => "World"

負のインデックスを使うと、文字列の末尾から部分文字列を取得することもできます。

"Hello World"[-5..-1] # => "World"
"Hello World"[6..-4] # => "Wo"

範囲のメソッド

範囲には、操作に使えるメソッドがいくつか用意されています。 たとえば、範囲内のすべての値の合計を求めたり、ある値が範囲に含まれているか調べたりするのに使えます。

メソッド 説明 例
sum 範囲内のすべての値の合計を返します (1..5).sum # => 15
size 範囲のサイズを返します (1..5).size # => 5
includes? 指定した値が範囲に含まれていればtrue、そうでなければfalseを返します (1..5).includes?(3) # => true

終端のない範囲と始端のない範囲

範囲は、終端のないものや始端のないものにできます。 終端や始端のない範囲では、開始値または終了値がnilになりますが、範囲を定義するときにnilは省略できます。

始端や終端のない範囲は、たとえば文字列を先頭から、または末尾まで切り出したいときに便利です。

"Hello World"[0..] # => "Hello World"
"Hello World"[4..] # => "o World"
"Hello World"[..5] # => "Hello"
Caution

コレクションに対して使うのでなければ、注意しないと、終端のない範囲が終わりのないシーケンスを生むことがあります。

Charの範囲

Charも範囲に使えて、2つの文字の間の文字の区間を取得できます。 たとえば、アルファベットを得たいときに便利です。

'a'..'z' # A range containing ['a', 'b', 'c', ..., 'z']

Stringの範囲

Stringも範囲に使えて、2つの文字列の間の文字列の区間を取得できます。 ただし、文字列の範囲で複数の文字を使うと、その動作はCharの場合とは少し異なります。 より複雑な文字列の範囲を扱うと動作が分かりにくくなることがあるので、注意してください。

("aa".."az") # A range containing ["aa", "ab", "ac", ..., "az"]

説明

チェスのファンなら、自分だけのチェスゲームを書いてみたいですよね。 すでにネット上にはたくさんのチェスの実装が公開されていますが、自分で作るものは世界に一つだけのものです!

まずは、駒を動かすための基本的な仕組みから実装していきます。

チェスは、8マス×8マスの盤の上で行います。 マスは、アルファベット1文字と数字1つで表します。

1. ランクとファイルの範囲を定義する

このゲームでは、盤面のランクを保持する必要があります。 ランクとは盤面の行のことで、1から8までの番号が付いています。

また、盤面のファイルも保持する必要があります。 ファイルとは盤面の列のことで、AからHまでのアルファベットで表します。

それぞれランクとファイルの範囲を保持するChess::RANKSとChess::FILESという定数を定義します。

Chess::RANKS
# => 1..8

Chess::FILES
# => 'A'..'H'

2. マスが有効かどうかを確認する

このゲームでは、マスが有効かどうかを確認する必要があります。 マスは、そのランクとファイルが、それぞれランクとファイルの範囲内にあれば有効です。

rankというランクを表す整数と、fileというファイルを表す文字を入力として受け取るChess.valid_square?メソッドを定義します。 このメソッドは、ランクとファイルがそれぞれの範囲内にあればtrueを、そうでなければfalseを返します。

Chess.valid_square?(1, 'A')
# => true

3. プレイヤーのニックネームを取得する

このゲームでは、プレイヤーのニックネームを取得する必要があります。 ニックネームは、プレイヤーの名の最初の2文字と、姓の最後の2文字を組み合わせたものです。 ニックネームは大文字にします。

プレイヤーの名を表す文字列first_nameと、姓を表す文字列last_nameを入力として受け取るChess.nicknameメソッドを定義します。 このメソッドは、プレイヤーのニックネームを大文字の文字列として返します。

Chess.nickname("John", "Doe")
# => "JOOE"

4. 移動のメッセージを作成する

このゲームでは、どのプレイヤーがどのマスに移動したかを伝えるメッセージを作成する必要があります。 メッセージには、プレイヤーのニックネームと、移動先のマスを使います。 また、マスのファイルとランクがファイルとランクの範囲内にあるかどうかを確認して、その移動が有効かどうかも判断する必要があります。

移動が有効な場合、メッセージは"{nickname} moved to {square}"とします。 移動が無効な場合、メッセージは"{nickname} attempted to move to {square}, but that is not a valid square"とします。

プレイヤーの名を表す文字列first_name、姓を表す文字列last_name、そして移動先のマスを表す文字列squareを入力として受け取るChess.move_messageメソッドを定義します。 このメソッドは、その移動のメッセージを文字列として返します。

Chess.move_message("John", "Doe", "A1")
# => "JOOE moved to A1"
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Crystal Exercism

チェスゲームを始める準備はできましたか?

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