いものやま。

雑多な知識の寄せ集め

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

まだまだ続くよー。

ボードの実装(続き)

ボードの情報へのアクセス

ざくっと。

  // 続き
  
  public func color(row: Int, _ col: Int) -> Color {
    assert(
      (Board.ROW_MIN <= row) && (row <= Board.ROW_MAX),
      "invalid row. [row: \(row)]")
    assert(
      (Board.COL_MIN <= col) && (col <= Board.COL_MAX),
      "invalid col. [col: \(col)]")

    return self.board[row][col]
  }

  public func count(color: Color) -> Int {
    assert(
      color != .WALL,
      "invalid color. [color: \(color)]")

    let index = color.rawValue
    if let count = self.countCache[index] {
      return count
    } else {
      var count = 0
      for row in Board.ROW_MIN...Board.ROW_MAX {
        for col in Board.COL_MIN...Board.COL_MAX {
          if self.board[row][col] == color {
            count += 1
          }
        }
      }
      self.countCache[index] = count
      return count
    }
  }

  // 続く

Board#count(:Color)もキャッシュを使っているけど、こちらは引数を取るのでプロパティには出来ないので、普通にインスタンス変数を使ってキャッシュをとってる。
ただ、Swiftは普通の型だとnilという値をとることが出来ないので、オプショナル型を使ってる感じ。

合法手のチェックなど

  // 続き

  public func isPlayable(row: Int, _ col: Int) -> Bool {
    if self.color(row, col) != .EMPTY {
      return false
    }

    let index = (row - 1) * Board.COL_MAX + (col - 1)
    if let legal = self.legalCheckCache[index] {
      return legal
    } else {
      let legal = self.hasColorFrom(row, col)
      self.legalCheckCache[index] = legal
      return legal
    }
  }

  public func isChangeable(row: Int, _ col: Int) -> Bool {
    if self.color(row, col) != .GRAY {
      return false
    }
    if self.token == self.opponent {
      return false
    }

    let index = (row - 1) * Board.COL_MAX + (col - 1)
    if let legal = self.legalCheckCache[index] {
      return legal
    } else {
      if self.hasColorFrom(row, col) {
        let newBoard = self.change(row, col, check: false)
        if newBoard.hasSameSituationBefore {
          self.legalCheckCache[index] = false
          return false
        } else {
          self.legalCheckCache[index] = true
          return true
        }
      } else {
        self.legalCheckCache[index] = false
        return false
      }
    }
  }

  public var mustPass: Bool {
    return (self.playablePlaces.isEmpty) && (self.changeablePlaces.isEmpty)
  }

  public private(set) lazy var playablePlaces: [(Int, Int)] = {
    [unowned self] in
    var places = [(Int, Int)]()
    for row in Board.ROW_MIN...Board.ROW_MAX {
      for col in Board.COL_MIN...Board.COL_MAX {
        if self.isPlayable(row, col) {
          places.append((row, col))
        }
      }
    }
    return places
  }()

  public private(set) lazy var changeablePlaces: [(Int, Int)] = {
    [unowned self] in
    var places = [(Int, Int)]()
    if self.token != self.opponent {
      for row in Board.ROW_MIN...Board.ROW_MAX {
        for col in Board.COL_MIN...Board.COL_MAX {
          if self.isChangeable(row, col) {
            places.append((row, col))
          }
        }
      }
    }
    return places
  }()

  public private(set) lazy var legalActions: [Action] = {
    [unowned self] in
    var actions = [Action]()
    if self.mustPass {
      actions.append(.Pass)
    } else {
      for (row, col) in self.playablePlaces {
        actions.append(.Play(row, col))
      }
      for (row, col) in self.changeablePlaces {
        actions.append(.Change(row, col))
      }
    }
    return actions
  }()

  // 続く

Board#playablePlacesBoard#changeablePlacesBoard#legalActionsも、それぞれ遅延格納型プロパティを使って実装している。

ちょっと迷ったのが、Board#mustPassという計算型プロパティ。
ボードの状態に関する情報を返すものなので、計算型プロパティとしたけど、メソッドとして実装することも出来るわけで。
それに、ボードの状態に関する情報を返しているといったらBoard#isPlayable(_:Int, _:Int)Board#isChangeable(_: Int, _: Int)も同様なので、かなり微妙なところ。

Rubyなら、外部に公開されるのは全部メソッドなので、何も迷うことがない。
メソッドを定義すればいいだけ。

JavaC++にしても、計算型プロパティなんてものはないので、インスタンス変数を直接公開しないのなら、メソッドを作る以外にはない。
そして、基本的にはインスタンス変数を外部に公開すべきではないから、普通はメソッドを定義することになる。
ましてや、このようなケースの場合はそもそも対応するインスタンス変数が存在しないのだから、何も迷うことなくメソッドを定義するだけ。

けど、Swift(やObjective-C 2.0)の場合、こういったときに、計算型プロパティにすることもメソッドにすることも出来てしまう。
厳密に言うと、Swift(やObjective-C 2.0)では、インスタンス変数にはRubyと同様に外部からアクセスは出来ず、プロパティ(という一種のメソッド)やメソッドを使ってアクセスすることになるんだけど、格納型プロパティを定義するとそれに対応したインスタンス変数が裏側で用意される一方で、計算型プロパティを定義した場合にはそれに対応したインスタンス変数は用意されない。
なので、計算型プロパティとメソッドの違いはほとんどなく、違いがあるとすれば呼び出しの書き方がちょっと変わってくるということくらい。 ましてや、セッターがない場合は本当に違いがなくて、何のためにこの機能があるのかがよく分からない。
無駄に複雑にしているだけ。

前にも書いたけど、Swift(やObjective-C 2.0)のこういったところは、かなり雑な設計で、残念としかいいようがないんだよなぁ。。。

今日はここまで!