いものやま。

雑多な知識の寄せ集め

「BirdHead」のUIを作ってみた。(その12)

昨日は手札のタッチ処理を実装した。

今日はそれを使って人間プレイヤーを実装し、コントローラ(シーン)に修正を加えて、実際に遊べるようにする。

Human

さっそく、人間プレイヤーの実装。

//==============================
// BirdHead
//------------------------------
// Human.swift
//==============================

import Foundation

class Human: Player, PlayableHandNodeDelegate {
  // 続く

PlayerプロトコルとPlayableHandNodeDelegateプロトコルに準拠するようにしてある。

プロパティとイニシャライザ

まずはプロパティとイニシャライザ。

  // 続き

  private(set) var name: String
  private(set) var isCom: Bool
  
  private let handNode: PlayableHandNode
  
  private var currentView: GameInfo.PlayerView!
  
  private let selectQueue: NSOperationQueue
  private var selectedAction: Action!
  
  init(name: String, handNode: PlayableHandNode) {
    self.name = name
    self.isCom = false
    
    self.handNode = handNode
    
    self.currentView = nil
    
    self.selectQueue = NSOperationQueue()
    self.selectQueue.maxConcurrentOperationCount = 1
    self.selectedAction = nil
  }

  // 続く

特に難しいことはなく。

一つ、説明が必要なところとして、selectQueueというオペレーションキューを用意しているということ。
これは入力待ちを行うためのもので、変種オセロのUIを作ってみた。(その15) - いものやま。を参照。
(ただし、今回はNSOperationQueue#suspendプロパティを使って、実行待ちを行うようにしている)

アクションの選択

次に、アクションの選択。

  // 続き
  
  func select(view: GameInfo.PlayerView) throws -> Action {
    self.selectQueue.suspended = true
    
    let semaphore = NSBlockOperation { /* do nothing */ }
    self.selectQueue.addOperation(semaphore)
    
    self.currentView = view
    self.handNode.playableHandNodeDelegate = self
    self.handNode.setLegalActions(view.legalActions)
    
    semaphore.waitUntilFinished()
    
    self.handNode.removeLegalActions()
    self.handNode.playableHandNodeDelegate = nil
    self.currentView = nil
    
    if self.selectedAction == nil {
      throw PlayerError.SelectIsCanceled
    }
    
    let selectedAction = self.selectedAction
    self.selectedAction = nil
    
    return selectedAction
  }

  // 続く

最初にselectQueueのsuspendedプロパティをtrueにしておいて、入力待ち出来るようにしておく。
そして、セマフォの役割を果たすオペレーションをキューに追加。

そしたら、PlayableHandNodeの委譲先を自分に設定して、PlayableHandNodeに合法手をセットし、入力待ちへ。

入力があって待ち状態が解除されたら、後片付けをしたあとに入力の内容を受け取り、選択されたアクションを返している。

PlayableHandNodeDelegateプロパティへの準拠

最後に、PlayableHandNodeからの通知への対応。

  // 続き
  
  func playableHandNodeSelectCardNodes(cardNodes: [CardNode]) {
    var cards = [Int]()
    for cardNode in cardNodes {
      cards.append(cardNode.card)
    }
    let playAction = Action.play(cards)
    let discardAction = Action.discard(cards)
    
    let legalActions = self.currentView.legalActions
    if legalActions.contains(playAction) {
      self.selectedAction = playAction
    } else if legalActions.contains(discardAction) {
      self.selectedAction = discardAction
    }
    
    self.selectQueue.suspended = false
  }
}

まず、選択されたカードの情報から選択された合法手を得る。
そのあとは、selectQueueのsuspendedプロパティをfalseにすることで、入力待ちを解除する。

これで人間プレイヤーの実装は完了。

GameSceneの修正

人間プレイヤーの実装が出来たので、それに合わせてコントローラ(シーン)の修正を行う。

//==============================
// BirdHead
//------------------------------
// GameScene.swift
//==============================

import SpriteKit

class GameScene: SKScene, GameInfoObserver, ButtonNodeDelegate {
  // 省略
  
  init(size: CGSize, info: GameInfo) {
    self.info = info
    
    let handNode0 = PlayableHandNode(frameWidth: size.width)
    let human = Human(name: "You", handNode: handNode0)
    
    let sarsaParameterURL = NSBundle.mainBundle().URLForResource("SarsaComParameter", withExtension: "plist")!
    self.players = [
      human,
      SarsaCom.load(sarsaParameterURL),
      SarsaCom.load(sarsaParameterURL),
      SarsaCom.load(sarsaParameterURL),
    ]
    
    // 以下、省略

まず、イニシャライザで、SarsaAIの代わりに人間プレイヤーを作るようにする。
このとき、PlayableHandNodeが必要となるので、人間プレイヤーの手札だけは先に作っておくようにする。

基本的にはこれだけでOKなんだけど、手番が来たときに、全部のカードを明るくしてしまうんじゃなくて、プレイできるカードだけ明るくしたほうがいいので、次のような変更を入れておく。

-      self.handNodes[turnPlayerIndex].enableAll()
+      let playerView = try! self.info.playerViewFor(turnPlayerIndex)
+      if let playableHandNode = self.handNodes[turnPlayerIndex] as? PlayableHandNode {
+        playableHandNode.setLegalActions(playerView.legalActions)
+      } else {
+        self.handNodes[turnPlayerIndex].enableAll()
+      }

なお、同様の箇所が何個かあるけど、全部同じように変更する。

これでコントローラの修正もOK。

動作確認

さっそく動作確認。
ちゃんとプレイ出来てることが分かると思う。

今日はここまで!