いものやま。

雑多な知識の寄せ集め

変種オセロのUIを作ってみた。(その16)

前回はターンコントローラを実装して、AI相手にもプレイが出来るようにした。

次は各ボタンの機能を対応していく。

まずはPassボタンの対応から。

人間プレイヤーの修正

Passボタンを使うのは人なので、人間プレヤーの修正をしていく。

具体的には、

  • HumanクラスをButtonNodeObserverプロトコルに準拠させる。
  • Humanクラスの入力元として、ボードの他にPassボタンを追加する。

といった感じ。
それと、追加されたコードにあわせて、若干リファクタリングをしたりとか。

修正後のコードは以下。

//==============================
// YWF
//------------------------------
// Human.swift
//==============================

import Foundation

public class Human: Player, BoardNodeObserver, ButtonNodeObserver {
  private var boardNode: BoardNode
  private var passButton: ButtonNode
  
  private var selectedAction: Board.Action!
  
  private let queue: NSOperationQueue
  private var semaphore: NSOperation!
  
  public init(boardNode: BoardNode, passButton: ButtonNode) {
    self.boardNode = boardNode
    self.passButton = passButton
    self.selectedAction = nil
    self.queue = NSOperationQueue()
    self.semaphore = nil
  }
  
  public func select(board: Board) -> Board.Action? {
    if board.mustPass {
      self.passButton.addObserver(self)
      self.passButton.enable()
    } else {
      self.boardNode.addObserver(self)
    }
    
    self.waitForInput()
    
    if board.mustPass {
      self.passButton.disable()
      self.passButton.removeObserver(self)
    } else {
      self.boardNode.removeObserver(self)
    }
    
    return self.selectedAction
  }
  
  public func boardNodeActionIsFinished(node: BoardNode) {
    // do nothing
  }
  
  public func boardNodeSquareIsSelected(node: BoardNode, _ square: SquareNode) {
    let row = square.row
    let col = square.col
    if node.board.isPlayable(row, col) {
      self.selectedAction = Board.Action.Play(row, col)
    } else if node.board.isChangeable(row, col) {
      self.selectedAction = Board.Action.Change(row, col)
    }
    self.notifyInput()
  }
  
  public func buttonIsSelected(button: ButtonNode) {
    switch button.type {
    case .Pass:
      self.selectedAction = Board.Action.Pass
      self.notifyInput()
    default:
      break
    }
  }
  
  private func waitForInput() {
    self.semaphore = NSBlockOperation {
      // do nothing
    }
    self.semaphore.waitUntilFinished()
    self.semaphore = nil
  }
  
  private func notifyInput() {
    self.queue.cancelAllOperations()
    self.queue.addOperation(self.semaphore)
  }
}

入力待ちするための仕組みなどは、変種オセロのUIを作ってみた。(その15) - いものやま。を参照。

今回の主な修正は、Human#buttonIsSelected(_: ButtonNode)。
ここで、Passボタンが押されたという通知が来た場合、選択されたアクションとして.Passを指定し、入力があったことを入力待ちしているスレッドに通知している。

ターンコントローラの修正

人間プレイヤーの修正に伴って、ターンコントローラも修正を行う。

//==============================
// YWF
//------------------------------
// TurnController.swift
//==============================

import Foundation

public class TurnController: BoardNodeObserver {
  // 省略

  private func requestActionFor(node: BoardNode) {
    node.lightUpEnableSquares()
    
    let board = node.board
    let action: Board.Action
    switch board.turn {
    case .Bad:
      action = self.badPlayer.select(board)!
    case .Good:
      action = self.goodPlayer.select(board)!
    default:
      action = .Pass
    }
    
    node.lighDown()
    
    switch action {
    case let .Play(row, col):
      node.lightUpsquare(row, col)
      node.play(row, col)
    case let .Change(row, col):
      node.lightUpsquare(row, col)
      node.change(row, col)
    case .Pass:
      node.pass()
    }
  }
}

違いは、パスをしなければならないときに、以前はプレイヤーに手を選択させることなく(=Player#select(_: Board)を呼び出すことなく)無条件でアクションを.Passとしていたけど、それを止め、パスしなければならないときにもプレイヤーに手の選択を委譲しているところ。
これにより、Passボタンを押してパスを行うということが可能になっている。

なお、これに伴って、AIのコードもちょっと修正が必要。
具体的には、パスしな手がないときにはアクションとして.Passを返すようにする必要がある。
ただ、修正は簡単なので、ここでは省略。

動作確認

いつものように、コントローラを書き換えて動作確認を行う。

といっても、変わるのはHumanクラスのオブジェクトの生成の引数を変えるところだけ。
なので、省略。

今日はここまで!