変種オセロ「良い子悪い子普通の子」については、以下を参照。
これをSwiftで書き直してみた。
なお、Swiftのバージョンは1.2なので、2.0とはちょっと違うところもある。
ボードの実装
まずは、ボードの実装から。
次のように、ボードをクラスとして定義した。
/* Board.swift */ public class Board { // ここにボードの定義。 // 詳細は後述。 }
構造体かクラスか
Swiftでは、データ構造の種類として、以下のようなものがある。
- 基本的なデータ型(整数型など) 値型
- 列挙型 値型
- タプル 値型
- 構造体 値型
- クラス 参照型
- クロージャ 参照型
ここで重要なのが、「値型」なのか「参照型」なのかということ。
「値型」の場合、オブジェクトは常に1つの変数/定数からのみ参照される。
なので、変数/定数に代入を行った場合、(基本的には)オブジェクトのコピーが作られ、新しく作られたオブジェクトを参照するようになる。
一方、「参照型」の場合、オブジェクトは複数の変数/定数から参照される場合がある。
なので、変数/定数に代入を行ったとしても、オブジェクトのコピーは作られず、元のオブジェクトをそのまま参照する。
これは、関数やメソッドの引数に渡した場合も同様。
値型の場合は(基本的には)コピーが作成されるので、関数やメソッド内でオブジェクトに変更を加えても、元のオブジェクトには影響が出ない。(※inout引数を除く)
一方、参照型の場合はコピーが作成されないので、関数やメソッド内でオブジェクトに変更を加えると、元のオブジェクトに影響が出る。
なので、Swiftにおいて、構造体とクラスはかなり近い機能を持っているのだけど、「値型」か「参照型」かという部分が違うので、どちらを使うべきかを考える必要があったりする。
実際のところ、今回のボードのような、データの集まりで、状態がなく、振る舞いに変化が生じないような場合には、本来は構造体を使うべきだったりする。
ただ、データを内部でキャッシュするということを行っているので、外側から見た状態には変化がなくても、内側では実は状態が変化しているので、構造体にしてしまうとちょっと都合が悪い。
(具体的には、定数に代入したときに、内部の状態を変更することが出来ない)
なので、今回はクラスとして実装することにしている。
色の定義
次に、ボードのマスの状態を表すための、色の定義。
/* Board.swift */ public class Board { public enum Color: Int { case WALL = -1 case EMPTY case BLACK case GRAY case WHITE public var opponent: Color { assert( (self == .BLACK) || (self == .WHITE), "invalid color. [color: \(self)]") return Color(rawValue: (self.rawValue + 2) % 4)! } private mutating func changeWithColor(color: Color) { assert( (self == .BLACK) || (self == .GRAY) || (self == .WHITE), "invalid color. [color: \(self)]") assert( (color == .BLACK) || (color == .WHITE), "invalid color. [color: \(color)]") let diff: Int if color == .BLACK { diff = -1 } else { diff = 1 } self = Color(rawValue: (self.rawValue + diff))! } } // 続く
Rubyの場合、列挙型がなかったので定数として定義していたけど、Swiftには列挙型があるので、列挙型を使って定義。
さらに、Swiftの場合は列挙型にメソッドや計算型プロパティの定義も出来るので、一緒に定義した。
一つ気をつけたいところとして、メソッドBoard.Color#changeWithColor(_:Color)
の定義の頭にmutating
というキーワードがついていること。
構造体や列挙型は値型のデータ構造で、自分自身や自分のデータを書き換えるメソッドの場合、このキーワードをつける必要がある。
そして、オブジェクトが定数に代入されている場合、このキーワードのついたメソッドを呼び出すことは出来なくなる。
アサーション
ここでちょっと困ったのが、メソッドの定義。
例えば、自身が黒なら白、白なら黒を返すBoard.Color#opponent
という計算型プロパティは、実際には自身が壁や空白で呼び出されるとダメなわけで、その場合には失敗を返さないといけない。
RubyやJavaのように例外があるクラスなら、失敗後の処理を呼び出し元に委譲させるために例外を投げればいいだけなんだけど、Swiftの場合、例外を投げることは出来ない。
その場合、オプショナル型を使ってnilを返すべきとなるのだけど、そうするとどこもかしこもオプショナル型だらけで、処理のフローがキレイに書けない。
なので、もういっそのこと、不正な呼び出しがあったらアプリを落とす感じで、アサーションを入れた。
Appleが主張するには「例外が起きるのはプログラムに間違いがあるからだ」なので、これでOK。
落ちたらプログラマ(つまり自分)の書いたコードが悪い、と。
# まぁ、実際にはそんなことないんだけど。
# 「例外」っていうのは、外部的な要因でも起こるものだから。
アサーションを入れるには、次のようにする。
assert(満たすべき条件, エラーメッセージ)
そうすると、条件が満たされなかった場合にはアプリが終了し、エラーメッセージが表示される。
今日はここまで!