いものやま。

雑多な知識の寄せ集め

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

あともうちょい。

ボードの実装(続き)

走査メソッド

まずは走査メソッドから。

  // 続き

  private func traverseFrom(row: Int, _ col: Int, to direction: (Int, Int),
                _ block: (Int, Int, Int, Color) -> Bool) {
    var traverseRow = row + direction.0
    var traverseCol = col + direction.1
    var traverseColor = self.board[traverseRow][traverseCol]
    var stepCount = 1
    while (traverseColor != .WALL) && (traverseColor != .EMPTY) {
      let success = block(stepCount, traverseRow, traverseCol, traverseColor)
      if !success {
        break
      }
      traverseRow += direction.0
      traverseCol += direction.1
      traverseColor = self.board[traverseRow][traverseCol]
      stepCount += 1
    }
  }

  // 続く

引数の最後にblock: (Int, Int, Int, Color) -> Boolというクロージャをとるようにしている。
このおかげで、Rubyのときと同じように使える。

ただし、Rubyの場合、ブロックの中で

  • returnすると、ブロックが書かれているメソッドから抜ける
  • breakすると、ブロックの呼び出しを行っているメソッドから抜ける
  • nextすると、ブロックの呼び出しから抜ける

と、絶妙なコントロールが効いたりするんだけど、Swiftだとそれは出来ないので、処理を継続するかどうかをブロックの戻り値として受け取り、フローをコントロールするようにしている。

なお、direction.0direction.1というのは、それぞれタプルの1つ目の要素、タプルの2つ目の要素ということ。

合法手を実行するためのメソッド

  // 続き

  private func putPiece(row: Int, _ col: Int) {
    self.board[row][col] = self.turn

    for direction in Board.Direction {
      if self.hasColorFrom(row, col, to: direction) {
        self.traverseFrom(row, col, to: direction) {
          stepCount, traverseRow, traverseCol, traverseColor in
          if traverseColor == self.turn {
            return false
          } else {
            self.board[traverseRow][traverseCol].changeWithColor(self.turn)
            return true
          }
        }
      }
    }
  }

  private func changeTurn() {
    self.turn = self.opponent
  }

  private func changeToken() {
    self.token.changeWithColor(self.opponent)
  }

  private func setPrevious(other: Board) {
    self.previous = other
  }

  private func addMove() {
    self.move += 1
  }

  // 続く

ここで先ほどの走査メソッドを使っている。

クロージャメソッドの最後の引数としてとる場合、そのクロージャの定義を引数リストの外側に書けるという機能(接尾クロージャ)があるので、この辺りはRubyとかなり近い書き方が出来る感じ。

一つ注目したいのが、Board.Color#changeWithColor(_: Color)の呼び出し。
これは列挙型Board.Colorを定義したときに一緒に定義したメソッドで、列挙型のオブジェクト自体を変化させるように定義してある。(定義は変種オセロをSwiftに移植してみた。(その1) - いものやま。を参照)
これにより、引数で黒を指定すれば、灰色は黒に、白は灰色に変化するし、逆に白を指定すれば、灰色は白に、黒は灰色に変化する。
トークンの色を変えるときにもこれは使えて、灰色の石を自分の色に変えた場合、トークンは相手の色側に寄るので、引数として相手の色を指定している。

合法手のチェックをするためのメソッド

  // 続き

  private func hasColorFrom(row: Int, _ col: Int) -> Bool {
    for direction in Board.Direction {
      if self.hasColorFrom(row, col, to: direction) {
        return true
      }
    }
    return false
  }

  private func hasColorFrom(row: Int, _ col: Int, to direction: (Int, Int)) -> Bool {
    var found = false
    self.traverseFrom(row, col, to: direction) {
      stepCount, traverseRow, traverseCol, traverseColor in
      if traverseColor == self.turn {
        if stepCount == 1 {
          found = false
        } else {
          found = true
        }
        return false
      } else {
        return true
      }
    }
    return found
  }
}

ここでも先ほどの走査メソッドを使っている。

Rubyのときとちょっと違うのは、メソッドの名前。
Rubyの場合、外部引数名というものはないので、Board#has_color_from_toというちょっと無理やりな名前のつけ方になったけど、Swiftの場合、Objective-Cと同様に外部引数名をつけることが出来るので、Board#hasColorFrom(_: Int, _: Int, to: (Int, Int)という、より自然な命名になってる。
これ自体はいい感じ。

ただ、ちょっと文句をつけさせてもらうなら、この外部引数名の仕様をどうして関数/イニシャライザ/メソッドでそれぞれ違うようにしたのか・・・
分かりにくくてしょうがない。
ここはObjective-Cの都合に引っ張られすぎで、言語を新しく設計したのなら、その言語に都合のいい設計にして欲しかった。

何はともあれ、これでボードの実装は完了。

今日はここまで!