いものやま。

雑多な知識の寄せ集め

変種オセロの仕上げをしてみた。(その2)

昨日はアイコンの作成を行った。

今日は勝敗の表示を行っていく。

勝敗表示の方法

なお、勝敗を表示するには、いくつかの方法が考えられる。

一番単純なのは、ダイアログ(アラート)を表示するという方法。
もうちょい手の込んだことをするなら、結果を表示するダイアログを自前で用意して表示したり、結果表示用のシーンを作成して遷移させたり。

本当は、結果をツイッターなどに投稿できるように、自前のダイアログやシーンを作った方がいいんだけど、今回はとりあえずダイアログを出すだけにする。

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

ゲームが終わったかどうかの判定ができるタイミングを知っているのは、ターンコントローラ。
ただ、ターンコントローラでダイアログを出すのも変な感じなので、ターンコントローラからシーンに対して、勝敗表示させるようにメッセージを送ることにした。

修正は、以下のとおり。

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

import Foundation

public class TurnController: BoardNodeObserver, ButtonNodeObserver {
  private weak var scene: GameScene!

    // 省略
  
  public init(scene: GameScene,
              boardNode: BoardNode, undoButton: ButtonNode,
              badPlayer: Player, goodPlayer: Player) {
    self.scene = scene
    // 省略
  }
  
  public func boardNodeActionIsFinished(node: BoardNode) {
    // 省略
    
    if self.boardNode.board.isGameEnd {
      let winner: Player!
      if self.boardNode.board.win(.Bad) {
        winner = self.badPlayer
      } else if self.boardNode.board.win(.Good) {
        winner = self.goodPlayer
      } else {
        winner = nil
      }
      
      let message: String
      if winner != nil {
        if winner.isCom {
          message = NSLocalizedString("LoseMessage", comment: "lose message")
        } else {
          message = NSLocalizedString("WinMessage", comment: "win message")
        }
      } else {
        message = NSLocalizedString("DrawMessage", comment: "draw message")
      }
      self.scene.showEndMessage(message)
    } else {
      self.selectOperation = NSBlockOperation {
        self.requestAction()
      }
      self.queue.addOperation(self.selectOperation)
    }
  }
  
  // 省略
}

まず、シーンに対してメッセージを送れるようにするために、シーンに対する弱い参照を保持するように修正。
(※シーンはターンコントローラに対して強い参照を持っているので、弱い参照にしないと循環する)
これに合わせて、イニシャライザの引数にもシーンを追加した。

そして、ゲームが終了していたら、シーンに対して通知。
まぁ、かなり微妙なインタフェースなんだけど・・・これは必要になったら改善するということで。

なお、メッセージを言語設定に合わせた表示に出来るようにするために、NSLocalizedString()を使っている。
これについては後述。

シーンの修正

ターンコントローラの修正に合わせて、シーンの修正も行う。

//==============================
// YWF
//------------------------------
// GameScene.swift
//==============================

import SpriteKit

public class GameScene: SKScene, ButtonNodeObserver {
  // 省略
  
  public init(size: CGSize,
              userName: String, userStatus: PieceNode.Status,
              opponentName: String, opponentPlayer: Player) {
    // 省略

    self.turnController = TurnController(scene: self,
                                         boardNode: self.boardNode, undoButton: self.undoButton,
                                         badPlayer: badPlayer, goodPlayer: goodPlayer)
  }

  // 省略

  public func showEndMessage(message: String) {
    let dialog = UIAlertController(title: NSLocalizedString("EndTitle", comment: "end title"),
                                   message: message,
                                   preferredStyle: .Alert)
    dialog.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
    self.view?.window?.rootViewController?.presentViewController(dialog, animated: true, completion: nil)
  }
  
  // 省略
  
  public func buttonIsSelected(button: ButtonNode) {
    let dialog = UIAlertController(title: nil,
                                   message: NSLocalizedString("ConfirmExit", comment: "confirm exit message"),
                                   preferredStyle: .Alert)
    dialog.addAction(UIAlertAction(title: "Yes", style: .Default,
                                   handler: {
                                     (action:UIAlertAction!) -> Void in
                                     let scene = StartScene(size: self.size)
                                     let transition = SKTransition.fadeWithDuration(NSTimeInterval(1.0))
                                     self.view?.presentScene(scene, transition: transition)
                                   }))
    dialog.addAction(UIAlertAction(title: "No", style: .Cancel, handler: nil))
    self.view?.window?.rootViewController?.presentViewController(dialog, animated: true, completion: nil)
  }
  
  // 省略
}

ターンコントローラのイニシャライザのインタフェースを変えたので、それに合わせて修正。
それと、勝敗表示を行うメソッドを実装している。
(UIAlertControllerについては、変種オセロのスタート画面を作ってみた。(その7) - いものやま。を参照)

あと、せっかくなので、ゲーム画面からスタート画面に戻るときの確認ダイアログのメッセージについても、ローカライズを行っている。

ローカライズ対応

メッセージを英語だけでなく日本語でも表示できるようにするために、ローカライズ対応。

iOSで文字列のローカライズを行う場合、一番簡単なのは、NSLocalizedString()関数とLocalizable.stringsファイルを使う方法。

まず、コードの方で、直接文字列を書いていた部分を、NSLocalizedString()関数で次のように置き換える:

NSLocalizedString("キーとなる文字列", comment: "文字列の説明")

なお、NSLocalizedString()関数の宣言は、Xcodeのコード補完を見る限り、NSLocalizedString(_:String, tableName: String?, bundle: NSBundle, value: String, comment: String)となっているみたいなんだけど、tableName、bundle、valueについてはデフォルトの引数が用意されているようなので、指定しなくても問題ない。

そして、プロジェクトにLocalizable.stringsを追加して、それをローカライズしたい言語ごとに用意したあと、各ファイルで

"キーとなる文字列" = "ローカライズされた文字列";

とする。

今回だと、以下のとおり。

//==============================
// YWF
//------------------------------
// Localizable.strings (Base)
//==============================

"ConfirmExit" = "Do you really want to leave this game?";

"EndTitle" = "Game End";
"WinMessage" = "You win.";
"LoseMessage" = "You lose.";
"DrawMessage" = "Draw.";
//==============================
// YWF
//------------------------------
// Localizable.strings (ja)
//==============================

"ConfirmExit" = "本当にゲームをやめますか?";

"EndTitle" = "ゲーム終了";
"WinMessage" = "あなたの勝ちです。";
"LoseMessage" = "あなたの負けです。";
"DrawMessage" = "引き分けです。";

これで、デバイスの言語設定にしたがった文字列が得られるようになる。

なお、こうすると言語ごとに文字列の長さが変わってくるので、レイアウトには注意が必要。
Auto Layoutを使って調整してあげる必要があるかもしれない。

動作確認

さて、動作確認。

ゲームが終わったタイミングでダイアログが表示されて、ちゃんと勝敗が表示されている。
あと、ダイアログのメッセージも日本語化されているのが分かると思う。

今日はここまで!