いものやま。

雑多な知識の寄せ集め

変種オセロをSwiftに移植してみた。(その3)

まだまだ続くよ。

ボードの実装(続き)

初期化

プロパティを定義したので、次は初期化。

  // 続き

  public init() {
    self.board = [
      [.WALL, .WALL , .WALL , .WALL , .WALL , .WALL , .WALL , .WALL , .WALL , .WALL , .WALL],
      [.WALL, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .WALL],
      [.WALL, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .WALL],
      [.WALL, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .WALL],
      [.WALL, .EMPTY, .EMPTY, .EMPTY, .GRAY , .BLACK, .WHITE, .EMPTY, .EMPTY, .EMPTY, .WALL],
      [.WALL, .EMPTY, .EMPTY, .EMPTY, .WHITE, .GRAY , .BLACK, .EMPTY, .EMPTY, .EMPTY, .WALL],
      [.WALL, .EMPTY, .EMPTY, .EMPTY, .BLACK, .WHITE, .GRAY , .EMPTY, .EMPTY, .EMPTY, .WALL],
      [.WALL, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .WALL],
      [.WALL, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .WALL],
      [.WALL, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .EMPTY, .WALL],
      [.WALL, .WALL , .WALL , .WALL , .WALL , .WALL , .WALL , .WALL , .WALL , .WALL , .WALL],
    ]
    self.move = 1
    self.turn = .BLACK
    self.token = .GRAY

    self.previous = nil

    self.countCache = [Int?](count: 4, repeatedValue: nil)
    self.legalCheckCache = [Bool?](count: Board.ROW_MAX * Board.COL_MAX, repeatedValue: nil)
  }

  convenience init(_ other: Board) {
    self.init()
    self.board = other.board
    self.move = other.move
    self.turn = other.turn
    self.token = other.token
  }

  // 続く

Swiftでは、初期化をイニシャライザinit()で行うのだけど、これには2種類ある。

  • 自力(とスーパークラスの指定イニシャライザ)で初期化を行う、指定イニシャライザ
  • 自クラスの他のイニシャライザを使って初期化を行う、簡易イニシャライザ

C++でいうコピーコンストラクタにあたる、他のボードオブジェクトをコピーして新しいオブジェクトを生成するイニシャライザの方は、簡易イニシャライザとしてある。
なので、convenienceというキーワードがついている。

そして、Rubynの場合、コピーするメソッドをprotectedメソッドとして別に用意していたけれど、こちらではそのままコピーを行っている。
これは、RubySwiftのアクセス制御の違いの問題で、

  • Rubyのprivateは「他のオブジェクトから」アクセス出来ない
  • Swiftのprivateは「他のファイルから」アクセス出来ない

となっているから。
なので、Rubyの方は「視点を『コピー後のオブジェクト』に変えるために」メソッドを呼び出す必要があるけど(selfがコピー元のオブジェクトからコピー後のオブジェクトに移動するので、コピー後のオブジェクトのインスタンス変数にアクセス出来るようになる)、Swiftの方は同じファイル内なのでそのままコピーが出来る。
(まぁ、Rubyの方も、メソッドを使って無理やりインスタンス変数を変えることは出来るけど・・・)

ハッシュ値の計算

さて、ハッシュ値の計算。
ここで遅延格納型プロパティが出てくる。

  // 続き

  private lazy var boardHash: Int = {
    [unowned self] in
    var value = 0
    for row in Board.ROW_MIN...Board.ROW_MAX {
      for col in Board.COL_MIN...Board.COL_MAX {
        value += row * col * self.board[row][col].rawValue
      }
    }
    return value
  }()

  // 続く

lazyとついているのが遅延格納型プロパティで、初めて参照されたときに初期値が設定されるようになっている。
設定の方法は、メソッドを呼び出したり、クロージャを呼び出したり。
初期値が設定されたあとは普通のプロパティと同じなので、初期値を設定するためのメソッドクロージャは呼び出されず、値がそのまま返ってくる。
なので、キャッシュとして使うことが出来る。

ここではクロージャを使って初期値の設定を行っている。

一つ注目したいのが、先頭の[unowned self]という部分。
これはキャプチャリストで、selfを非所有とするということを宣言している。
これにより、オブジェクトからクロージャへの強い参照と、クロージャからオブジェクトへの強い参照による循環を防ぐようにしている。
(もしかしたら不要かも・・・クロージャをクラスオブジェクトが所有しているのであれば参照は循環しないはず。ただ、書いてあっても問題はないので、書いておいた方が安全だと思う)

同一盤面の判断

ハッシュ値の計算を実装したので、これを使う同一盤面を判断するメソッドの実装。

  // 続き
  
  public func isSameSituation(other: Board) -> Bool {
    if self.turn != other.turn {
      return false
    }
    if self.token != other.token {
      return false
    }
    if self.boardHash != other.boardHash {
      return false
    }

    for row in Board.ROW_MIN...Board.ROW_MAX {
      for col in Board.COL_MIN...Board.COL_MAX {
        if self.board[row][col] != other.board[row][col] {
          return false
        }
      }
    }

    return true
  }

  // 続く

ここは特に書くことないかな?

今日はここまで!