いものやま。

雑多な知識の寄せ集め

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

Undoボタンの対応も出来た。

あとは、ユーザ情報の表示。

レイアウトマネージャの修正

ユーザ情報の表示を行うとき、そのエリアのサイズが必要となったので、レイアウトマネージャの修正を行った。
ついでに、すこしリファクタリングも実施。

//==============================
// YWF
//------------------------------
// LayoutManager.swift
//==============================

import SpriteKit

public class LayoutManager {
  public enum Component {
    case Scene
    case Background
    case Header
    case Logo
    case Board
    case UserInfo
    case OpponentInfo
    case ExitButton
    case UndoButton
    case PassButton
  }
  
  public class func getInstanceFor(size: CGSize) -> LayoutManager {
    switch size.height {
    case 920..<1000:
      return SmallestLayout(size: size)
    case 1000..<1060:
      return SmallLayout(size: size)
    case 1060..<1140:
      return MiddleLayout(size: size)
    case 1140..<1500:
      return LargeLayout(size: size)
    default:
      return LayoutManager(size: size)
    }
  }
  
  private let size: CGSize
  
  private var userInfoHeight: CGFloat {
    return 80.0
  }
  
  private var opponentInfoHeight: CGFloat {
    return 80.0
  }
  
  private var buttonMargin: CGFloat {
    return 20.0
  }
  
  private init(size: CGSize) {
    self.size = size
  }
  
  public func getPosition(component: Component, relativeTo base: Component) -> CGPoint! {
    assert(
      component != .Scene,
      ".Scene is specified for component.")

    let point = self.getUpperLeftPosition(component)
    let size = self.getSize(component)
    if point == nil || size == nil {
      return nil
    }
    
    if base == .Scene {
      let x = point.x + size.width/2.0
      let y = self.size.height - point.y - size.height/2.0
      return CGPoint(x: x, y: y)
    } else {
      let basePoint = self.getUpperLeftPosition(base)
      let baseSize = self.getSize(base)
      if basePoint == nil || baseSize == nil {
        return nil
      } else {
        let x = (point.x + size.width/2.0) - (basePoint.x + baseSize.width/2.0)
        let y = -((point.y + size.height/2.0) - (basePoint.y + baseSize.height/2.0))
        return CGPoint(x: x, y: y)
      }
    }
  }
  
  public func getSize(component: Component) -> CGSize! {
    switch component {
    case .Scene:
      return self.size
    case .Background:
      return CGSize(width: 828.0, height: 1472.0)
    case .Header:
      return CGSize(width: 828.0, height: 160.0)
    case .Logo:
      return CGSize(width: 190.0, height: 60.0)
    case .Board:
      return CGSize(width: 760.0, height: 760.0)
    case .UserInfo:
      return CGSize(width: self.size.width - self.buttonMargin, height: self.userInfoHeight)
    case .OpponentInfo:
      return CGSize(width: self.size.width - self.buttonMargin, height: self.opponentInfoHeight)
    case .ExitButton, .UndoButton, .PassButton:
      return CGSize(width: 120.0, height: 60.0)
    }
  }
  
  private func getUpperLeftPosition(component: Component) -> CGPoint! {
    return nil
  }
  
  // for iPhone 4S
  private class SmallestLayout: LayoutManager {
    private var userInfoMargin: CGFloat  // workaround to set using method. (actually constant)
    
    private override init(size: CGSize) {
      self.userInfoMargin = 0.0
      super.init(size: size)
      self.userInfoMargin = (self.size.height - self.getSize(.Board).height - self.userInfoHeight - self.opponentInfoHeight)/4.0
    }
    
    private override func getSize(component: Component) -> CGSize! {
      switch component {
      case .UserInfo:
        let width = self.size.width - self.getSize(.UndoButton).width - self.getSize(.PassButton).width - self.buttonMargin*3.0
        return CGSize(width: width, height: self.userInfoHeight)
      case .OpponentInfo:
        let width = self.size.width - self.getSize(.ExitButton).width - self.buttonMargin*2.0
        return CGSize(width: width, height: self.opponentInfoHeight)
      default:
        return super.getSize(component)
      }
    }
    
    private override func getUpperLeftPosition(component: Component) -> CGPoint! {
      switch component {
      case .Scene, .Background:
        return CGPoint(x: 0.0, y: 0.0)
      case .Header, .Logo:
        return nil
      case .Board:
        let x = (self.size.width - self.getSize(.Board).width)/2.0
        let y = self.opponentInfoHeight + self.userInfoMargin*2.0
        return CGPoint(x: x, y: y)
      case .UserInfo:
        let y = self.opponentInfoHeight + self.getSize(.Board).height + self.userInfoMargin*3.0
        return CGPoint(x: 0.0, y: y)
      case .OpponentInfo:
        let y = self.userInfoMargin
        return CGPoint(x: 0.0, y: y)
      case .ExitButton:
        let x = self.size.width - (self.buttonMargin + self.getSize(.ExitButton).width)
        let y = self.userInfoMargin + (self.opponentInfoHeight - self.getSize(.ExitButton).height)/2.0
        return CGPoint(x: x, y: y)
      case .UndoButton:
        let x = self.size.width - (self.buttonMargin*2.0 + self.getSize(.UndoButton).width + self.getSize(.PassButton).width)
        let y = self.opponentInfoHeight + self.userInfoMargin*3.0 + self.getSize(.Board).height + (self.userInfoHeight - self.getSize(.UndoButton).height)/2.0
        return CGPoint(x: x, y: y)
      case .PassButton:
        let x = self.size.width - (self.buttonMargin + self.getSize(.PassButton).width)
        let y = self.opponentInfoHeight + self.userInfoMargin*3.0 + self.getSize(.Board).height + (self.userInfoHeight - self.getSize(.PassButton).height)/2.0
        return CGPoint(x: x, y: y)
      }
    }
  }
  
  // for iPad
  private class SmallLayout: LayoutManager {
    private var userInfoMargin: CGFloat  // workaround to set using method. (actually constant)
    private let headerHeight: CGFloat
    private let headerShadowHeight: CGFloat
    
    private override init(size: CGSize) {
      self.userInfoMargin = 0.0
      self.headerHeight = 80.0
      self.headerShadowHeight = 10.0
      super.init(size: size)
      self.userInfoMargin = (self.size.height - self.headerHeight - self.getSize(.Board).height - self.userInfoHeight - self.opponentInfoHeight)/4.0
    }
    
    private override func getSize(component: Component) -> CGSize! {
      switch component {
      case .UserInfo:
        let width = self.size.width - self.getSize(.UndoButton).width - self.getSize(.PassButton).width - self.buttonMargin*3.0
        return CGSize(width: width, height: self.userInfoHeight)
      default:
        return super.getSize(component)
      }
    }
    
    private override func getUpperLeftPosition(component: Component) -> CGPoint! {
      switch component {
      case .Scene, .Background:
        return CGPoint(x: 0.0, y: 0.0)
      case .Header:
        let offset = self.getSize(.Header).height - self.headerHeight - self.headerShadowHeight
        return CGPoint(x: 0.0, y: -offset)
      case .Logo:
        let margin = (self.headerHeight - self.getSize(.Logo).height)/2.0
        return CGPoint(x: margin, y: margin)
      case .Board:
        let x = (self.size.width - self.getSize(.Board).width)/2.0
        let y = self.headerHeight + self.userInfoMargin*2.0 + self.opponentInfoHeight
        return CGPoint(x: x, y: y)
      case .UserInfo:
        let y = self.headerHeight + self.opponentInfoHeight + self.getSize(.Board).height + self.userInfoMargin*3.0
        return CGPoint(x: 0.0, y: y)
      case .OpponentInfo:
        let y = self.headerHeight + self.userInfoMargin
        return CGPoint(x: 0.0, y: y)
      case .ExitButton:
        let x = self.size.width - (self.buttonMargin + self.getSize(.ExitButton).width)
        let y = (self.headerHeight - self.getSize(.ExitButton).height)/2.0
        return CGPoint(x: x, y: y)
      case .UndoButton:
        let x = self.size.width - (self.buttonMargin*2.0 + self.getSize(.UndoButton).width + self.getSize(.PassButton).width)
        let y = self.headerHeight + self.opponentInfoHeight + self.getSize(.Board).height + self.userInfoMargin*3.0 + (self.userInfoHeight - self.getSize(.UndoButton).height)/2.0
        return CGPoint(x: x, y: y)
      case .PassButton:
        let x = self.size.width - (self.buttonMargin + self.getSize(.PassButton).width)
        let y = self.headerHeight + self.opponentInfoHeight + self.getSize(.Board).height + self.userInfoMargin*3.0 + (self.userInfoHeight - self.getSize(.PassButton).height)/2.0
        return CGPoint(x: x, y: y)
      }
    }
  }
  
  // for iPhone 5 / 5s / 5c
  private class MiddleLayout: LayoutManager {
    private override var userInfoHeight: CGFloat {
      return 140.0
    }
    
    private var userInfoMargin: CGFloat  // workaround to set using method. (actually constant)
    private let headerHeight: CGFloat
    private let headerShadowHeight: CGFloat
    
    private override init(size: CGSize) {
      self.userInfoMargin = 0.0
      self.headerHeight = 80.0
      self.headerShadowHeight = 10.0
      super.init(size: size)
      self.userInfoMargin = (self.size.height - self.headerHeight - self.getSize(.Board).height - self.userInfoHeight - self.opponentInfoHeight)/4.0
    }
    
    private override func getSize(component: Component) -> CGSize! {
      switch component {
      case .UserInfo:
        let width = self.size.width - self.getSize(.UndoButton).width - self.buttonMargin*2.0
        return CGSize(width: width, height: self.userInfoHeight)
      default:
        return super.getSize(component)
      }
    }
    
    private override func getUpperLeftPosition(component: Component) -> CGPoint! {
      switch component {
      case .Scene, .Background:
        return CGPoint(x: 0.0, y: 0.0)
      case .Header:
        let offset = self.getSize(.Header).height - self.headerHeight - self.headerShadowHeight
        return CGPoint(x: 0.0, y: -offset)
      case .Logo:
        let margin = (self.headerHeight - self.getSize(.Logo).height)/2.0
        return CGPoint(x: margin, y: margin)
      case .Board:
        let x = (self.size.width - self.getSize(.Board).width)/2.0
        let y = self.headerHeight + self.userInfoMargin*2.0 + self.opponentInfoHeight
        return CGPoint(x: x, y: y)
      case .UserInfo:
        let y = self.headerHeight + self.opponentInfoHeight + self.getSize(.Board).height + self.userInfoMargin*3.0
        return CGPoint(x: 0.0, y: y)
      case .OpponentInfo:
        let y = self.headerHeight + self.userInfoMargin
        return CGPoint(x: 0.0, y: y)
      case .ExitButton:
        let x = self.size.width - (self.buttonMargin + self.getSize(.ExitButton).width)
        let y = (self.headerHeight - self.getSize(.ExitButton).height)/2.0
        return CGPoint(x: x, y: y)
      case .UndoButton:
        let x = self.size.width - (self.buttonMargin + self.getSize(.UndoButton).width)
        let y = self.headerHeight + self.opponentInfoHeight + self.getSize(.Board).height + self.userInfoMargin*3.0
        return CGPoint(x: x, y: y)
      case .PassButton:
        let x = self.size.width - (self.buttonMargin + self.getSize(.PassButton).width)
        let y = self.headerHeight + self.opponentInfoHeight + self.getSize(.Board).height + self.userInfoMargin*3.0 + self.getSize(.UndoButton).height + self.buttonMargin
        return CGPoint(x: x, y: y)
      }
    }
  }
  
  // for iPhone 6 / 6 Plus
  private class LargeLayout: LayoutManager {
    private override var userInfoHeight: CGFloat {
      return 140.0
    }
    
    private override var opponentInfoHeight: CGFloat {
      return 140.0
    }
    
    private var userInfoMargin: CGFloat  // workaround to set using method. (actually constant)
    private let headerHeight: CGFloat
    private let headerShadowHeight: CGFloat
    
    private override init(size: CGSize) {
      self.userInfoMargin = 0.0
      self.headerHeight = 100.0
      self.headerShadowHeight = 10.0
      super.init(size: size)
      self.userInfoMargin = (self.size.height - self.headerHeight - self.opponentInfoHeight - self.getSize(.Board).height - self.userInfoHeight)/4.0
    }
    
    private override func getSize(component: Component) -> CGSize! {
      switch component {
      case .UserInfo:
        let width = self.size.width - self.getSize(.UndoButton).width - self.buttonMargin*2.0
        return CGSize(width: width, height: self.userInfoHeight)
      default:
        return super.getSize(component)
      }
    }
    
    private override func getUpperLeftPosition(component: Component) -> CGPoint! {
      switch component {
      case .Scene, .Background:
        return CGPoint(x: 0.0, y: 0.0)
      case .Header:
        let offset = self.getSize(.Header).height - self.headerHeight - self.headerShadowHeight
        return CGPoint(x: 0.0, y: -offset)
      case .Logo:
        let margin = (self.headerHeight - self.getSize(.Logo).height)/2.0
        return CGPoint(x: margin, y: margin)
      case .Board:
        let x = (self.size.width - self.getSize(.Board).width)/2.0
        let y = self.headerHeight + self.userInfoMargin*2.0 + self.opponentInfoHeight
        return CGPoint(x: x, y: y)
      case .UserInfo:
        let y = self.headerHeight + self.opponentInfoHeight + self.getSize(.Board).height + self.userInfoMargin*3.0
        return CGPoint(x: 0.0, y: y)
      case .OpponentInfo:
        let y = self.headerHeight + self.userInfoMargin
        return CGPoint(x: 0.0, y: y)
      case .ExitButton:
        let x = self.size.width - (self.buttonMargin + self.getSize(.ExitButton).width)
        let y = (self.headerHeight - self.getSize(.ExitButton).height)/2.0
        return CGPoint(x: x, y: y)
      case .UndoButton:
        let x = self.size.width - (self.buttonMargin + self.getSize(.UndoButton).width)
        let y = self.headerHeight + self.opponentInfoHeight + self.getSize(.Board).height + self.userInfoMargin*3.0
        return CGPoint(x: x, y: y)
      case .PassButton:
        let x = self.size.width - (self.buttonMargin + self.getSize(.PassButton).width)
        let y = self.headerHeight + self.opponentInfoHeight + self.getSize(.Board).height + self.userInfoMargin*3.0 + self.getSize(.UndoButton).height + self.buttonMargin
        return CGPoint(x: x, y: y)
      }
    }
  }
}

ポイントとなるのは、LayoutManager#getSize(_: LayoutManager.Component)をpublicメソッドにしたこと。

一つ心配だったことは、レイアウトマネージャの実体(LayoutManagerのサブクラス)は可視性がprivateで、ファイル外から直接使うことが出来ないようにしているのだけど、その場合、メソッドの可視性もすべてprivateにしないといけなかったということ。
このせいで、各実装でオーバーライドしているgetSize()の可視性もprivateにせざるを得なかったのだけど、そのとき、オーバーライドされたメソッドが呼び出されるのかどうかが怪しかった。
けど、実際には問題なくて、ちゃんとオーバーライドされたメソッドが呼び出されていた。

ユーザ情報の表示

ユーザ情報を表示できるようにするために、UserInfoNodeを実装していく。

//==============================
// YWF
//------------------------------
// UserInfoNode.swift
//==============================

import SpriteKit

public class UserInfoNode: SKNode, BoardNodeObserver {
  private enum SizeClass {
    case Large
    case Small
  }

  // 続く

なお、SizeClassという列挙体は、トークンやフォントのサイズを切り替えるためのもの。

フォントの定義、画像のロードなど

まずはクラス定数、クラス変数、クラスメソッドから。

  // 続き
  
  private static let fontName = "Times New Roman"
  private static let fontSize: [SizeClass: CGFloat] = [.Large: 60.0, .Small: 40.0]
  private static let userNameFontColor = SKColor(red: 0.2, green: 0.1, blue: 0.0, alpha: 1.0)
  
  private static var textures = [String: SKTexture]()
  
  private static let fadeDuration = NSTimeInterval(0.2)
  
  public class func loadAssets() {
    let atlas = SKTextureAtlas(named: "PlayerMarker")
    for status in [PieceNode.Status.Bad, PieceNode.Status.Good] {
      for sizeClass in [UserInfoNode.SizeClass.Large, UserInfoNode.SizeClass.Small] {
        let name = UserInfoNode.getTextureNameFor(status, sizeClass: sizeClass)
        UserInfoNode.textures[name] = atlas.textureNamed(name)
      }
    }
  }
  
  private class func getTextureNameFor(status: PieceNode.Status, sizeClass: SizeClass) -> String {
    var name: String
    
    switch status {
    case .Bad:
      name = "BadPlayerMarker"
    case .Good:
      name = "GoodPlayerMarker"
    default:
      fatalError("illegal status.")
    }
    
    switch sizeClass {
    case .Large:
      name.extend("Large")
    case .Small:
      name.extend("Small")
    }
    
    return name
  }
  
  private class func getTextureFor(status: PieceNode.Status, sizeClass: SizeClass) -> SKTexture {
    let name = UserInfoNode.getTextureNameFor(status, sizeClass: sizeClass)
    return UserInfoNode.textures[name]!
  }

  // 続く

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

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

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

  // 続き
  
  private var playerName: String
  
  private var status: PieceNode.Status
  private var size: CGSize
  private var sizeClass: SizeClass
  
  private var playerNameNode: SKLabelNode!
  private var counter: SKLabelNode!
  
  private var tokenMargin: CGFloat {
    return 20.0
  }
  
  public init(playerName: String, status: PieceNode.Status, size: CGSize, boardNode: BoardNode) {
    self.playerName = playerName
    self.status = status
    self.size = size
    if size.height <= 80.0 {
      self.sizeClass = .Small
    } else {
      self.sizeClass = .Large
    }
    self.playerNameNode = nil
    self.counter = nil
    
    super.init()
    
    let tokenNode = SKSpriteNode(texture: UserInfoNode.getTextureFor(self.status, sizeClass: self.sizeClass))
    let tokenSize = tokenNode.frame.size
    let tokenPosition = CGPoint(x: -self.size.width/2.0 + self.tokenMargin + tokenSize.width/2.0, y: 0.0)
    self.addChild(tokenNode)
    tokenNode.position = tokenPosition
    
    let placeHolderSize = CGSize(width: self.size.width - tokenSize.width - self.tokenMargin*2.0, height: self.size.height)
    let placeHolderNode = SKShapeNode(rectOfSize: placeHolderSize)
    let placeHolderPosition = CGPoint(x: self.size.width/2.0 - placeHolderSize.width/2.0, y: 0.0)
    self.addChild(placeHolderNode)
    placeHolderNode.position = placeHolderPosition
    placeHolderNode.lineWidth = 0.0
    
    self.playerNameNode = SKLabelNode(fontNamed: UserInfoNode.fontName)
    self.playerNameNode.fontSize = UserInfoNode.fontSize[self.sizeClass]!
    self.playerNameNode.fontColor = UserInfoNode.userNameFontColor
    self.playerNameNode.horizontalAlignmentMode = .Left
    self.playerNameNode.verticalAlignmentMode = .Center
    self.playerNameNode.position = CGPoint(x: -self.size.width/2.0 + self.tokenMargin*2.0 + tokenSize.width, y: -3.0)
    self.addChild(self.playerNameNode)
    self.setPlayerNameLabel(playerName)
    
    self.counter = SKLabelNode(fontNamed: UserInfoNode.fontName)
    self.counter.text = "\(boardNode.board.count((status == .Bad) ? .Bad : .Good))"
    self.counter.fontSize = UserInfoNode.fontSize[self.sizeClass]! * 0.8
    self.counter.fontColor = (self.status == .Bad) ? SKColor.whiteColor() : SKColor.blackColor()
    self.counter.horizontalAlignmentMode = .Center
    self.counter.verticalAlignmentMode = .Center
    self.counter.position = CGPoint(x: 0.0, y: 0.0)
    tokenNode.addChild(self.counter)
    
    boardNode.addObserver(self)
  }

  public required init?(coder aDecoder: NSCoder) {
      fatalError("init(coder:) has not been implemented")
  }

  // 続く

ちょっと説明が必要なのは、placeHolderNode。
これはなんなのかというと、UserInfoNodeのサイズを一定にするためのもの。

※注 ただ、実際には不要だった。
これに関してはSKNodeのpositionの仕様について理解が甘かったのが原因。
SKNodeのpositionは座標系の原点を決めるだけで、SKSpriteNodeのようにテクスチャの中心位置まで定めるわけではなかった。

それと、SKLabelNodeのhorizontalAlignmentMode。
これが.Leftになっていると、SKLabelNodeのpositionがテキストの左端に置かれるようになる。
(つまり、文字列がpositionから右に伸びることになる)

駒の数の表示の更新

BoardNodeObserverプロトコルに準拠して、駒の数の表示の更新を行う。

  // 続き
  
  public func boardNodeActionIsFinished(node: BoardNode) {
    let fadeOutAction = SKAction.fadeOutWithDuration(UserInfoNode.fadeDuration)
    self.counter.runAction(fadeOutAction) {
      self.counter.text = "\(node.board.count((self.status == .Bad) ? .Bad : .Good))"
      let fadeInAction = SKAction.fadeInWithDuration(UserInfoNode.fadeDuration)
      self.counter.runAction(fadeInAction)
    }
  }
  
  public func boardNodeSquareIsSelected(node: BoardNode, _ square: SquareNode) {
    // do nothing
  }

  // 続く

ここも特に書くことはない・・・

プレイヤー名の表示

最後、プレイヤー名を表示させる部分について。

  // 続き
  
  public func setPlayerNameLabel(playerName: String) {
    var trimmedName = playerName
    while (true) {
      self.playerNameNode.text = (trimmedName == playerName) ? trimmedName : (trimmedName + "...")
      let currentSize = self.calculateAccumulatedFrame().size
      if self.size.width < currentSize.width {
        let lastIndex = trimmedName.endIndex.predecessor()
        trimmedName.removeAtIndex(lastIndex)
      } else {
        self.playerName = trimmedName
        break
      }
    }
  }
}

基本的にはそのまま表示させればいいのだけど、考慮しておいた方がいいのが、プレイヤー名が仮に長くなった場合にどうするのか、ということ。
例えば、プレイヤー名を自由に変えられるようにしたり、GameCenterに対応したりする場合、プレイヤー名が長くなるということは十分に考えられる。
そのとき、プレイヤー名がボタンとかにかかってしまうとよろしくない。
そこで、必要に応じてプレイヤー名を短縮して表示する必要がある。

それを実現するための実装がこれで、プレイヤー名をセットしたときに幅がはみ出てしまった場合には、名前を末尾から刈り取って表示するようにしている。

動作確認

さて、いつものようにコントローラを修正して、動作確認をしてみる。

//==============================
// YWF
//------------------------------
// GameViewController.swift
//==============================

import UIKit
import SpriteKit

class GameViewController: UIViewController {
  @IBOutlet weak var skView: SKView!
  
  override func viewDidLoad() {
    BoardNode.loadAssets()
    PieceNode.loadAssetsAndCreateTemplates()
    UserInfoNode.loadAssets()
    ButtonNode.loadAssets()
  }
  
  override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    
    var size = self.view.bounds.size
    if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
      size.width *= 2.0
      size.height *= 2.0
    }
  
    let scene = GameScene(size: size)
    scene.scaleMode = .AspectFill
    
    let layoutManager = LayoutManager.getInstanceFor(size)
    
    let backgroundTexture = SKTexture(imageNamed: "Background")
    let background = SKSpriteNode(texture: backgroundTexture)
    background.position = layoutManager.getPosition(.Background, relativeTo: .Scene)
    scene.addChild(background)
    
    let headerPosition = layoutManager.getPosition(.Header, relativeTo: .Background)
    if headerPosition != nil {
      let headerTexture = SKTexture(imageNamed: "Header")
      let header = SKSpriteNode(texture: headerTexture)
      header.position = headerPosition
      background.addChild(header)
      
      let logoTexture = SKTexture(imageNamed: "Logo")
      let logo = SKSpriteNode(texture: logoTexture)
      logo.position = layoutManager.getPosition(.Logo, relativeTo: .Header)
      header.addChild(logo)
    }

    var board = Board()
    let boardNode = BoardNode(board: board)
    boardNode.position = layoutManager.getPosition(.Board, relativeTo: .Background)
    background.addChild(boardNode)
    
    let undoButton = ButtonNode(type: .Undo)
    let passButton = ButtonNode(type: .Pass, enabled: false)
    let exitButton = ButtonNode(type: .Exit)
    undoButton.position = layoutManager.getPosition(.UndoButton, relativeTo: .Background)
    passButton.position = layoutManager.getPosition(.PassButton, relativeTo: .Background)
    exitButton.position = layoutManager.getPosition(.ExitButton, relativeTo: .Background)
    background.addChild(undoButton)
    background.addChild(passButton)
    background.addChild(exitButton)
    
    let userInfoSize = layoutManager.getSize(.UserInfo)
    let opponentInfoSize = layoutManager.getSize(.OpponentInfo)
    let userInfoNode = UserInfoNode(playerName: "You", status: .Bad, size: userInfoSize, boardNode: boardNode)
    let opponentInfoNode = UserInfoNode(playerName: "Computer", status: .Good, size: opponentInfoSize, boardNode: boardNode)
    userInfoNode.position = layoutManager.getPosition(.UserInfo, relativeTo: .Background)
    opponentInfoNode.position = layoutManager.getPosition(.OpponentInfo, relativeTo: .Background)
    background.addChild(userInfoNode)
    background.addChild(opponentInfoNode)
    
    self.skView.presentScene(scene)
    
    let badPlayer = Human(boardNode: boardNode, passButton: passButton)
    let goodPlayer = AlphaBetaCom(status: .Good, depth: 3)
    let turnController = TurnController(boardNode: boardNode, undoButton: undoButton,
                                        badPlayer: badPlayer, goodPlayer: goodPlayer)
    turnController.start()
  }
  
  // hide status bar.
  override func prefersStatusBarHidden() -> Bool {
    return true
  }
}

これを実行してみると、以下のような感じ。

ユーザの情報が表示され、アクションが行われるたびに駒の数の表示が更新されていくのが分かると思う。

今日はここまで!