|ハイブリッドOS|File System|ARM|Android|Java|制御システム|オープンシステム

 

技術者コラム

 
フォーム
 
参考画像1〜3
2014-09-27
 
参考画像4〜5
2014-09-27
 
第28回目:Swiftでオセロを作る(Part5)
2014-09-07
筆者:村田
 
こんにちは。
 
Xcode6のbeta7が出ていたので試してみました。前回と同じく暗黙Optional型の使用箇所について修正されているようです。OthelloViewの以下のメソッドでエラーがでました。
 
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) { ... }
 
NSSet!型からNSSet型へ、UIEvent!型からUIEvent型へ変更したようです。リリースノートを確認すると大きな変更はありませんでした。それではオセロの実装を進めていきます。
 
前回は一方向分の石をひっくり返すアルゴリズムを再帰ループを使って書きました。実はこのflipLine関数が当オセロプログラムのハイライトでして、残りの部分は簡単な紹介程度でサクサク進めていきたいと思います。まずは縦横斜めの8方向分のひっくり返しを行うflip関数です。
 
func flip(board:[[Int]], x:Int, y:Int, stone:Int) -> [(Int, Int)]? {
    if board[y][x] != EMPTY { return nil }
    var result:[(Int, Int)] = []
    result += flipLine(board, x, y, stone, 1, 0)
    result += flipLine(board, x, y, stone,-1, 0)
    result += flipLine(board, x, y, stone, 0, 1)
    result += flipLine(board, x, y, stone, 0,-1)
    result += flipLine(board, x, y, stone, 1, 1)
    result += flipLine(board, x, y, stone,-1,-1)
    result += flipLine(board, x, y, stone, 1,-1)
    result += flipLine(board, x, y, stone,-1, 1)
    if result.count > 0 {
        result += [(x, y)]
        return result
    } else {
        return nil
    }
}
 
戻り値の型はOptionalにしました。ひっくり返すxy座標の配列かnilを返します。4行目からはflipLine関数を使って8方向分のひっくり返しの位置をresultに加えています。方向はflipLine関数のdx,dy引数で指定します。最後の部分ですが、一つ以上ひっくり返せる石がある場合にのみ、最初に置こうとした石の位置もresultに加えています。
 
次は盤面上に黒石または白石を置くことができるか調べる関数です。64マスのどこに石を置くことができるかを配列にして返します。
 
func canPlaced(board:[[Int]], stone: Int) -> [(Int, Int)]? {
    var result:[(Int, Int)] = []
    for y in 1...8 {
        for x in 1...8 {
            if let tmp = flip(board, x, y, stone) {
                result += [(x, y)]
            }
        }
    }
    if result.isEmpty {
        return nil
    } else {
        return result
    }
}
 
64マスに対しflip関数を実行してチェックしています。flip関数の中ではflipLine関数を8回呼び出しているので一回のチェックで512回flipLine関数が呼び出されます。前回少し触れましたがflipLine関数のパフォーマンスが悪いとゲームとして遊べなくなります。
 
次はCPUの指し手を返す関数です。canPlaced関数が返した配列からランダムに一箇所選び、flip関数を呼び出してひっくり返す位置を返します。なお、arc4random関数は0〜UIntMaxをランダムに返す関数です。
 
func cpuFlip(board:[[Int]], stone: Int) -> [(Int, Int)]? {
    if let places = canPlaced(board, stone) {
        let (x, y) = places[ Int(arc4random()) % places.count ]
        return flip(board, x, y, stone)
    }
    return nil
}
 
次は盤面上の石の数を数える関数です。ゲームの勝敗を判断する際に使用します。
 
func calcStones(board:[[Int]]) -> (free:Int, black:Int, white:Int) {
    var free = 0, white = 0, black = 0
    for y in 1...8 {
        for x in 1...8 {
            switch board[y][x] {
            case BLACK_STONE: black++
            case WHITE_STONE: white++
            default: free++
            }
        }
    }
    return (free, black, white)
}
 
Swiftではタプルを戻り値に使うことができるので、関数から複数の値を返すことが簡単にできます。戻り値の型宣言を見ると分かるように、タプルを返す場合はタプルのメンバーに名前を付けることができます。以下のように戻り値を変数一つで束縛した場合、名前でタプルの中身にアクセスできます。
 
let stones = calcStones(board)
println(stones.black)
 
タプルの戻り値を分配束縛することもできます。以下の例のように不要な部分は_(アンダースコア)でマスクできます。
let (foo, bar, _) = calcStones(board)
println(foo)
 
ここまで副作用を持たない参照透過な関数としてflipLine,flip,canPlaced,cpuFlip,calcStonesを作ってきました。以降はOthelloViewクラス内に戻り、これら関数を組み合わせて副作用を持つメソッドを作っていきたいと思います。まずはひっくり返す石の配列をもらって実際にboard上の石をひっくり返すメソッドです。
 
func putStones(places:[(Int, Int)], stone: Int) {
    for (x, y) in places {
        board[y][x] = stone
    }
}
 
次はゲーム進行の実装です。今回のゲーム進行はユーザーが盤面をタッチしたタイミングで行われます。この瞬間にひっくり返す処理、CPUの次の指し手、ゲームの終了判定など全て行うことにします。一回のタッチでプレイヤー及びCPUのターンも処理します。別のゲーム進行方法も考えられます。一回のタッチではプレイヤーのターン処理のみ行い、次のタッチでCPUのターン処理を行うこともできます。その場合はプレイヤー/CPUのターン管理が必要になります。なおCPUの思考アルゴリズムを賢く複雑にする場合にはどちらのゲーム進行手法も不適切になります。思考アルゴリズムが結論を出すまでに時間を要する場合、タッチ後しばらく画面が固まってしまいますので、別途CPUの思考を処理するタイミングを設ける必要があります。今回はランダム選択というほぼ何も考えないAIですのでタッチのタイミングで処理可能です。
 
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
   // まずはプレイヤー(黒)が石を置けるか調べる
   if let canBlack = canPlaced(board, BLACK_STONE) {
        // 選択したマスのxy座標を取り出す
        if let (x, y) = getPos(touches) {
            // ひっくり返せるか調べる
            if let blackPlaces = flip(board, x, y, BLACK_STONE) {
                // 実際にひっくり返す
                putStones(blackPlaces, stone: BLACK_STONE)
                // CPU(白)のターンを進める
                if let whitePlaces = cpuFlip(board, WHITE_STONE) {
                    putStones(whitePlaces, stone: WHITE_STONE)
                }
            }
        }
    } else {
        // プレイヤーが石を置けないのでスキップしてCPUのターンを進める
        if let whitePlaces = cpuFlip(board, WHITE_STONE) {
            putStones(whitePlaces, stone: WHITE_STONE)
        }
    }
    updateGame() // ゲームの勝敗判定(次に説明します)
    setNeedsDisplay()
}
 
最後にゲームの勝敗判定メソッドを作りましょう。
 
func updateGame() {
    let (free, black, white) = calcStones(board)
    let canBlack = canPlaced(board, BLACK_STONE)
    let canWhite = canPlaced(board, WHITE_STONE)
    if free == 0 || (canBlack == nil && canWhite == nil) {
        // 空きマスが無い、または黒も白も置けない場合はゲーム終了
        println("Game Over (Black:\(black) White:\(white))")
    }
}
 
以上でほぼ完成です。早速完成したオセロゲームをiPhoneエミュレータで動かして対戦してみました。角を簡単に取らせてくれるのでプレイヤー(黒)の圧倒的勝利となりました。なお、ゲームオーバーがちゃんとユーザーに分かるように画面にラベルを置いたり、ゲームオーバーから再度ゲームができるようにリセットするなど仕上げ残っていますが、これについては以下のGitHubのコードを参照して下さい。見直してみるとxy座標を走査する2重for文があちこちにあるのが気になります。高階メソッドを作るなど何らかの抽象化を行うべきだったかもしれません。
 
https://github.com/muratamuu/SwiftOthello/blob/master/SwiftOthello/OthelloView.swift
 
[おわりに]
Swiftはいかがでしたか?特徴が無いのが特徴というか、最新言語なだけに他の言語のいい部分をうまく持ってきているというか、モダンというのでしょうね。読み書きし易いバランスの良い言語だと思いました。まだまだ言語機能の一部しか紹介できませんでしたが、今後正式リリースされたら是非Xcode6をダウンロードして遊んでみて下さい。それにしても、OSX/iOSの開発言語として活躍していたObjCは今後どうなるのでしょう。Swiftに置き換わっていくにつれてObjCはあまり見かけなくなってしまうのでしょうか。個人的にはObjCのシンタックスも好きなのでSwiftもObjCも楽しんでいきたいなと思います。それではPart5までお付き合い頂きまして有り難うございました。
 
以上。
 
参考画像:勝利の瞬間
2014-09-07
 
第27回目:Swiftでオセロを作る(Part4)
2014-08-31
筆者:村田
 
こんにちは。
 
今回も最初はXcodeのアップデートからです。beta6が出ました。アップデートしてコンパイルしたところ大きなエラーはでませんでした。一応リリースノートを眺めてみるとOptional型を使ったいくつかのAPIに変更があるようです。その内容について触れたいと思います。
 
「第25回の続き」という記事でOptional型を紹介しましたが、もう少し続きがあるのです。まずはInt?型の復習から。Int?型とはIntの数値かnilを値として持てる型です。Int?型からInt型を取り出すためには、nilじゃないことを確認してから「!」で取り出すのでした。
 
var optApples: Int? // OptionalなInt
 
optApples = 3
 
if optApples != nil {
    var apples = optApples! // 「!」で安全な取り出し
    ...
}
 
もう少しスッキリしたif文の使い方もありました。
 
if let apples = optApples {
    ... // optApplesがnilではない場合にifの中に入る
}
 
上記はInt?型から「明示的」にInt型へ変換する手順です。しかしプログラムの構造によってはOptional型変数に確実に値が入っていると見なせる場合もあります。こんな場合でも参照する度に「!」で型変換を行うのは少々面倒です。そこで登場するのがInt!型です。型名の後に「!」がついています。Int!型はOptional型ですが「暗黙的」にInt型へ変換されます。
 
var optApples: Int! // 暗黙的なOptinal
var apples: Int
 
optApples = 3
 
apples = optApples  // 暗黙的にInt!からIntへ変換されます
optApples = nil     // Optional型なのでnilを設定できます
apples = optApples  // 実行時エラー発生。nilからIntへは変換できません
 
普通のOptional型と同じようにnilチェックしてから明示的に「!」で取り出すこともできます。
 
if optApples != nil {
    var apples = optApples!
    ...
}
 
スッキリ版も同様に書けます。
 
if let apples = optApples {
    ...
}
 
というわけでOptional型には2通りあり、中身を取り出す際に「!」を付けなければならない明示的なInt?型と、「!」を付けなくても取り出せる暗黙的なInt!型があります。この暗黙のOptional型は実際にどんなところで使われているのでしょう。「一応nilが入ることがあるかもしれないけど、実際はただのInt」というようなニュアンスで使われているのだと思います。例えば画面にタッチした際のコールバックメソッドを見てみましょう。
 
func touchesBegan(touches: NSSet!, withEvent event: UIEvent!)
 
引数の型を見るとNSSet!、UIEvent!と暗黙のOptionalが使われています。しかし実際にnilが入ることはあり得ないでしょう。もしも明示的なNSSet?型だったらアクセスに一手間かかってしまいますが、暗黙のNSSet!型なので通常のNSSet型とみなしてアクセスできるわけです。(まあ本当にnilが入っていたらアウトですけど)
 
では話をbeta6のリリースノートに戻します。暗黙のOptional型が使われている一部の箇所について見直されました。その結果、「ほんとにもうっ、全然nilなんて入らないんだからねっ!」という場所は通常型に修正され、「いや〜結構nilが入るのでちゃんとnilチェックして欲しいな・・」という場所は明示的なOptional型に修正されました。前回までのオセロプログラムでもこのエラーが1箇所でました。
 
init(coder aDecoder: NSCoder!) { ... }
 
上のメソッドは前回話題にしたUIViewのイニシャライザですが、以下のように通常型の引数に修正されました。
 
init(coder aDecoder: NSCoder) { ... }
 
aDecoderにnilなんて入らないというわけです。ちょっと補足します。NSCoderはクラスですのでaDecoderは参照型変数です。JavaやC#であればnullを設定できる変数ですね。しかしSwiftではaDecoder変数が書き換え可能だったとしてもnilを設定できません。SwiftのnilはJavaやC#のnull、CやObjCのNULLとは異なります。nilというリテラル値はOptional型変数にしか設定できません。Swiftの参照型変数はnilにならないので値として扱いやすく、またOptional型のおかげでnilのあるところが分かりやすくていいですね。
 
えっと、今回もVerUpネタに時間をとってしましましたが、気を取り直してオセロの続きをやりたいと思います。オセロは自分の石で相手の石を挟むとひっくり返すことができます。この部分を作りたいと思います。石を置いた場所から上下左右+斜めの8方向に対してひっくり返せるかチェックする必要があります。まずは左方向にひっくり返せるかを例にして考えましょう。
 
●○○○
 
という状態で白石の隣に黒石を置く場合のアルゴリズムを考えます。
①すぐ左隣が黒石ではなく白石があることを確認したら、
②白石が途切れるまで、つまり黒石が見つかるまで繰り返し左隣のチェックを行います。黒石が見つからなければひっくり返せません。
③黒石が見つかったら今度は間にある白石を繰り返しひっくり返します。
②と③は繰り返し処理を含んでいますね。②のループで順番にチェックし、③のループで今度はひっくり返すという2回のループで書くことができそうです。ですが、もうちょっと動きを観察すると、②で左隣を順にチェックし、③はその逆を辿るようにひっくり返しながら戻ってくるという方法も考えられます。後者は再帰関数で書くとうまくいきそうです。関数名はひっくり返すループですのでflipLoop関数にします。
 
書き始める前にもう少し細かい点を説明します。なるべくメソッドにはせず関数で書いてみようと考えているので、グローバルスコープ関数として書きます。また盤面上の石をひっくり返す方法ですが、OthelloViewのメンバにいるboardをflipLoop関数から書き換えてしまうことは副作用になりますので避けようと思います。一つの方法として、関数内で新しいboardのコピーを用意して、その上でひっくり返した結果を返すという方法が考えられます。しかし、flipLoopは頻繁に呼ばれそうです。さらにboardをリストではなく配列として定義しているため、Immutableに扱うためには部分変更でも全体をコピーすることになりパフォーマンスが悪そうです。そこで別の方法として、ひっくり返す相手の石の位置(x,y)を配列にして返すようにして、実際の副作用(ひっくり返し)は後でやることにします。ではまずは関数のI/Fから。
 
func flipLoop(board:[[Int]], x:Int, y:Int, stone:Int) -> [(Int, Int)]? {
    return nil
}
 
現在のboardと石を置く位置xyと石の種類(黒/白)をもらって、ひっくり返す場所を配列にして返すという関数です。戻り値型はOptionalにしておいて、ひっくり返す場所が無い場合はnilを返すようにします。では中身を書いていきましょう。まずは再帰的にチェックしていった場合に、黒でも白でもなく空のマスに辿り着いてしまったら、ひっくり返せないのでnilを返して再帰ループを停止します。
 
func flipLoop(board:[[Int]], x:Int, y:Int, stone:Int) -> [(Int, Int)]? {
    if board[y][x] == EMPTY {
        return nil
    }
    return nil
}
 
次は再帰チェックで自分の石を見つけた場合です。ここまでひっくり返せるので再帰を停止します。再帰の巻き戻し時に相手の石の場所を格納するための空配列[]を返しましょう。
 
func flipLoop(board:[[Int]], x:Int, y:Int, stone:Int) -> [(Int, Int)]? {
    if board[y][x] == EMPTY {
        return nil
    } else if board[y][x] == stone {
        return []  // 再帰の果てに自分の石を発見(ひっくり返せる!)
    }
    return nil
}
 
最後は相手の石を見つけた場合です。さらに左隣をチェックするため再帰呼び出しをしましょう。
 
func flipLoop(board:[[Int]], x:Int, y:Int, stone:Int) -> [(Int, Int)]? {
    if board[y][x] == EMPTY {
        return nil
    } else if board[y][x] == stone {
        return []
    } else {
        var result = flipLoop(board, x-1, y, stone) // さらに左隣を調べる
    }
    return nil
}
 
再帰呼び出しの戻り値resultはどうしましょう。nilが入っていたら再帰の果てに自分の石が無かったのですから、そのままnilを返しましょう。配列が返ってきた場合、再帰の果てに自分の石があったということですので、今調べた相手の石をひっくり返すために配列に加えましょう。というわけで以下のようになりました。
 
func flipLoop(board:[[Int]], x:Int, y:Int, stone:Int) -> [(Int, Int)]? {
    if board[y][x] == EMPTY {
        return nil
    } else if board[y][x] == stone {
        return []
    } else if var result = flipLoop(board, x-1, y, stone) {
        result += [(x, y)] // nilじゃなければ相手の石を配列に追加して返す
        return result
    }
    return nil
}
 
再帰呼び出しの停止条件を確認しておきましょう。自分の石が見つかるか空っぽのマスが見つかれば停止します。boardの定義を思い出して下さい。8x8マスの周囲にぐるりと余分な領域を持たせていました。ここには石を置けませんので、必ず最後には空っぽのマスが見つかり無事停止します。
 
さて、まだまだいじります。今のところ左隣だけしか調べられませんので、走査する方向を引数でもらいましょう。x方向とy方向の増減分ということでdxとdyを引数に追加しました。
 
func flipLoop(board:[[Int]], x:Int, y:Int, stone:Int, dx:Int, dy:Int) -> [(Int, Int)]? {
    if board[y][x] == EMPTY {
        return nil
    } else if board[y][x] == stone {
        return []
    } else if var result = flipLoop(board, x+dx, y+dy, stone, dx, dy) { // dx,dy方向に進む
        result += [(x, y)]
        return result
    }
    return nil
}
 
このままでもいいのですがちょっと突っ込みます。再帰関数のルールとして、呼び出し時にパラメータが変化しなければなりません。flipLoop関数も第2、3パラメータが変化します。ところがそれ以外の4つのパラメータは変わっていません。これらは再帰に本質的には関わっておらず、再帰パラメータである必要がないと言えます。こういった変数はパラメータの外に出すことが可能です。というわけでクロージャを使って外側の関数スコープに移してみましょう。外側の関数の名前は、ええと、ある方向に対してひっくり返し処理を行うという意味でflipLine関数とします。flipLine関数のスコープに再帰に不要な変数を保持するとして、クロージャはどうやって作りましょう。SwiftはJavaScriptやC++のような関数内関数を定義することもできますが、今回は関数リテラル(無名関数)で書いてみます。(そう言えばC++11からC++もクロージャが使えると聞いたことがあるのですが試したことはないです。どんな風に書くのかな・・)
 
Swiftの関数リテラルは以下のように書きます。
 
{ (引数) -> 戻り値型 in 本体 }
 
中括弧で全体を囲み、inの前に関数の型、inの後に本体を書きます。上記は基本形ですが、Swiftの関数リテラルは条件が揃えば色々記述を省略することができ、{本体だけ}のように短く書くこともできます(Scalaっぽい書き方です)。またRubyのコードブロックのように高階メソッドの直後にくっつける記法もあります。興味のある方は言語マニュアルを参照してみて下さい。
 
ではflipLine関数の中にflipLoopを書いてみます。
 
func flipLine(board:[[Int]], x:Int, y:Int, stone:Int, dx:Int, dy:Int) -> [(Int, Int)]? {
 
    var flipLoop = { (x: Int, y: Int) -> [(Int, Int)]? in
        if board[y][x] == EMPTY {
            return nil
        } else if board[y][x] == stone {
            return []
        } else if var result = flipLoop(x+dx, y+dy) {
            result += [(x, y)]
            return result
        }
        return nil
    }
 
    return nil
}
 
どうでしょうか。flipLoopをクロージャにしたので引数が再帰パラメータだけになりました。それではクロージャを呼び出すコードも追加しておきましょう。
 
func flipLine(board:[[Int]], x:Int, y:Int, stone:Int, dx:Int, dy:Int) -> [(Int, Int)] {
    var flipLoop = { (x: Int, y: Int) -> [(Int, Int)]? in
        if board[y][x] == EMPTY {
            return nil
        } else if board[y][x] == stone {
            return []
        } else if var result = flipLoop(x+dx, y+dy) {
            result += [(x, y)]
            return result
        }
        return nil
    }
    if let result = flipLoop(x+dx, y+dy) { // クロージャの起動
        return result
    }
    return [] // flipLineの戻りはnilにする必要はない。ひっくり返す場所が無いなら空配列にします。
}
 
flipLine関数の戻り値型を見て下さい。Optional型をやめました。flipLoopの戻り値ではnilと[]の区別が必要なのでOptionalにしていますが、flipLineはひっくり返す位置が無い場合は空配列を返せば十分です。さて、以上で完成ならばいいのですが、なんとこれコンパイルが通りません:P 上のコードの7行目でflipLoopを再帰呼び出ししていますが、flipLoopが見つからないって怒られます。どうやら関数リテラルは再帰呼び出しできないみたいです。beta6のリリースノートを見ると「関数内関数で再帰呼び出し不可」という既知の問題がありました。こんな感じのです。
 
func foo() {
  func bar() { bar() } // 再帰呼び出しはコンパイルエラー
}
 
関数内関数と関数リテラルの問題が同根原因かどうか分かりませんが、現在はflipLoopのような再帰関数リテラルはコンパイルできません。そこでStackOverflowを検索してみると回避策が見つかりました。まずは同型のダミー関数オブジェクトを生成し、関数リテラルの中から前方参照することで回避できるようです。こんな感じでようやく最終版です。
 
func flipLine(board:[[Int]], x:Int, y:Int, stone:Int, dx:Int, dy:Int) -> [(Int, Int)] {
    var flipLoop: (Int, Int) -> [(Int, Int)]? = { _ in nil } // ダミー関数オブジェクト
    flipLoop = { (x: Int, y: Int) -> [(Int, Int)]? in
        if board[y][x] == EMPTY {
            return nil
        } else if board[y][x] == stone {
            return []
        } else if var result = flipLoop(x+dx, y+dy) {
            result += [(x, y)]
            return result
        }
        return nil
    }
    if let result = flipLoop(x+dx, y+dy) {
        return result
    }
    return []
}
 
再帰関数リテラルを書く前に変数を同じ型で宣言し、初期値として適当な関数オブジェクトを入れておきます。inの前のアンダースコアですが、プレースホルダとして機能する点はいいとして、なぜ1個でいいのか。言語マニュアルには書かれていませんが、Swiftでは引数が複数あっても実はタプルとして一つにまとめられているとのこと。詳しくはQiitaのdankogaiさんの記事で分析されています。興味のある方は検索してみて下さい。ともかく、これでようやく「一方向のひっくり返し関数」が完成しました。ふう。
 
今回はここまでにして、そろそろですね!iPhone6の発表イベント。9月9日(米時間)だそうです。関連情報を随分見かけるようになりました。大きなiPadやiWatchなんてのも期待されています。本体ハードウエアにも興味がありますが、発売の少し前になればiOS8のリリースがあるはずですね!iOS8はiOS7のようなUIデザインの大きな変更は無いでしょうが、APIがたくさん増えるようです。HomeKit(家電)、HealthKit(健康)のような新しいフレームワークを使ってどんなアプリやサービスが登場するのでしょうね。他にもWebKitやCloudKitなるものや、フォトライブラリやカメラ、CoreAudioにも手が入るとか。PromiseKitという非同期処理フレームワークは本コラムでのネタになりそうです。個人的に興味があるのはSceneKitというApple純正3D描画+物理演算フレームワークです。OSXだけでなくiOSでも動くようになるとのことで、この機会にトライしてみたいです。描画と言えばMetalというA7チップ用の高速グラフィックスAPIも発表され、これからどんなアプリが登場するのかとても楽しみです。
 
以上
 
 

連載記事のソースコード

連載記事のソースコード
 
・Haskellで問題を解く(Part1〜Part4) ソースコード
・Clojureで8クイーン問題にチャレンジ(Part1〜Part5) ソースコード
・OCamlでへびゲームを作る(Part1〜Part5) ソースコード
・Swiftでオセロを作る(Part1〜Part5) ソースコード
・Processingでシューティング(Part1〜Part4) ソースコード 
Haskellでテトリス(Part1〜Part9) ソースコード
・プチコン3号(BASIC)でさめがめ(Part1〜Part3) ソースコード
・Prologでさめがめを解く(Part1〜Part6) ソースコード