Swiftでパズルゲーム

#はじめに
パズルゲーム作れたら楽しいだろうなと思い作ってみました
パズルの仕様としてはパズドラを思い浮かべてもらえれば良いと思います。

参考　[Swiftris](https://www.bloc.io/tutorials/swiftris-build-your-first-ios-game-with-swift#!/chapters/675)

#ソース
https://github.com/konpeto000/tilepuzzle

#イメージ
＊（タイルの配置はランダムですので起動後３色以上並んでいても消えません）

![tilepuzzle.gif](https://qiita-image-store.s3.amazonaws.com/0/57208/a7943e90-0324-9fb4-197d-708bc9ac7547.gif)

#反省
ノードの管理が未だによくわかっておりません。。
アクション実行中にタッチ操作を受け付けてしまうので
これをどうにかしたいのですがどなたかわかる方がいらっしゃったら教えてください。。
今回は強引にアクション実行中にタッチ操作を受け付けないようにしています

#解説

*タイルの初期化*

    func randomColor()->UIColor{
        //0:red,1:green,2:blue,3:yellow
        let rnd = arc4random()%4
        var color:UIColor!
        switch rnd{
        case 0:
            color = UIColor.redColor()
        case 1:
            color = UIColor.greenColor()
        case 2:
            color = UIColor.blueColor()
        case 3:
            color = UIColor.yellowColor()
        default:
            break
        }
        return color
    }
    
    func initMakeTile(){
        
        for(var i:Int = 0;i < NumColumns;i++){
            for(var j:Int = 0;j < NumRows;j++){
                let sprite = makeTileOne()

                sprite.position = CGPointMake(CGFloat(i)*TileSize,-CGFloat(j)*TileSize)
                tileArrayPos[i][j] = sprite.position
                
                board.addChild(sprite)
            }
        }
        
    }
    
    func makeTileOne()->SKSpriteNode{
        let sprite = SKSpriteNode()
        sprite.anchorPoint = CGPointMake(0, 1.0)
        sprite.alpha *= 0.8
        sprite.color = randomColor()
        sprite.size = CGSizeMake(TileSize-1, TileSize-1)
        
        return sprite
    }

この一連で描画するタイルを作成し
タイルの位置を`tileArrayPos[][]`に保存する
後々`nodeAtPoint`を使い位置からノードを参照できるようにするため
***
*タッチ操作*

    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        /* Called when a touch begins */
        
        for touch in touches {
            let location = touch.locationInNode(self)
            touchedNode = self.nodeAtPoint(location)
            for node in self.board.children{
                if(touchedNode == node as NSObject && !moveActionFlag){
                    touchedNode.alpha *= 0.5
                }
            }

        }
    }
    
    override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
        for touch in touches {
            let location = touch.locationInNode(self)
            let passagedNode = self.nodeAtPoint(location)
            for node in self.board.children{
                if(passagedNode == node as NSObject && touchedNode != passagedNode && !moveActionFlag){
                    moveTile(passagedNode)
                }
            }
        }
    }
    
    override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
        for touch in touches {
            let location = touch.locationInNode(self)
            let endedNode = self.nodeAtPoint(location)
            for node in self.board.children{
                if(endedNode == node as NSObject && !moveActionFlag){
                    moveActionFlag = true
                    deleteAndDropTile()
                    touchedNode.alpha *= 2
                }
            }
        }
    }

*began関数*
ここで選択したノードを`touchedNode`に保存する
そしてそのノードのアルファ値を半分にするそうすることによって
選択したノードをわかりやすくした

*moved関数*
ここで選択されるノードを`passageNode`に保存し
それぞれの条件に当てはめるそして`moveTile(passagedNode)`にとばす

*ended関数*
ここでタイルの消去と落ちる処理を行う`deleteAndDropTile()`にとばす
また`touchedNode`のアルファ値を元に戻す

どの関数にもアクション実行中は実行しないというmoveActionFlagを条件に持たせる
もっとうまいやり方があると思うのだが思いつかなかった。。。
***
*タイルの移動*

    func moveTile(node:SKNode){
        let touchedAction = SKAction.moveTo(node.position, duration: 0.08)
        let passeagedAction = SKAction.moveTo(touchedNode.position, duration: 0.08)
        
        moveActionFlag = true
        touchedNode.runAction(touchedAction, completion: {self.moveActionFlag = false})
        node.runAction(passeagedAction, completion: {self.moveActionFlag = false})
    }

タッチしたタイルが他のタイルと接触したときに呼ばれ
そのノードの位置をアクションによって交換する関数
アクション実行中は`moveActionFlag`を`true`にして
それぞれのtouch関数の処理を呼び出せないようにする
アクションが終了したら`false`とする
***
*タイルの消去と落ちる処理*

    func deleteAndDropTile(){

        var deleteTileArray = Array(arrayLiteral:SKNode())
        deleteTileArray.removeAll()
        deleteColumns(&deleteTileArray)
        deleteRows(&deleteTileArray)
        
        if(deleteTileArray.isEmpty){
            moveActionFlag = false
            return
        }
        
        board.removeChildrenInArray(deleteTileArray)
        
        dropTile(){
            self.makeTileInEmpty(){
                self.deleteAndDropTile()
            }
        }
  
    }

消すタイルをそれぞれ行と列で分け`deleteTileArray`に入れる
もし入っていなければ`moveActionFlag`を折り再帰を終了する
`dropTile()`に飛ばしアクション終了後`makeTileInEmpty()`と`deleteAndDropTile()`に飛ばす
***
*列行の削除*

    
    func deleteColumns(inout array:[SKNode]){
        
        var deleteColumnsArray = Array(arrayLiteral:SKNode())
        
        for(var i:Int = 0;i < NumColumns;i++){
            var color = searchNode(tileArrayPos[i][0]).color
            var count = 0
            
            for(var j:Int = 0;j < NumRows;j++){
                var node = searchNode(tileArrayPos[i][j])
                if(color == node.color){
                    deleteColumnsArray.append(node)
                    count++

                }else{
                    if(count >= 3){
                        insertArray(&array, arrayB: deleteColumnsArray)
                    }
                    deleteColumnsArray.removeAll()
                    deleteColumnsArray.append(node)
                    color = node.color
                    count = 1
                }
                
            }
            
            if(count >= 3){
                insertArray(&array, arrayB: deleteColumnsArray)
            }
            deleteColumnsArray.removeAll()
        }
    }

(行は列の処理とほぼかわらないので割愛)
選択したノードを原点とし探索を開始する
次のノードの色が原点と同じならばカウント（同じ色の数）を増加し`deleteColumnsArray`に入れる
またそうでなく同じ色の数が３以上ならばその配列を`array`に入れるinoutなので`deleteTileArray`に入れると同じこと
そして同じ色の数が３未満ならば`deleteColumnsArray`の要素をすべて捨てる
***
*ノードの探索*

    
    func searchNode(pos:CGPoint)->SKSpriteNode{
        let node = self.nodeAtPoint(CGPointMake(pos.x+TileSize,pos.y-4*TileSize))
        return node as SKSpriteNode
    }
    
指定した場所のノードを`SKSpriteNode`として返す
`nodeAtPoint`でやるのだがいまいちわかっておらず
xにタイル1つ分yにタイル4つ分ずらさないとうまく行かない

***
*タイルの落ちる処理*

    func dropTile(completion:()->()){
        
        for(var i:Int = 0;i < NumColumns;i++){
            for(var j:Int = 0;j < NumRows;j++){
                let k = searchEmpty(searchNode(tileArrayPos[i][j]))
                if(k != 0){
                    let sprite = searchNode(tileArrayPos[i][j])
                    let action = SKAction.moveTo(tileArrayPos[i][j+k], duration: 0.1)
                    sprite.runAction(action)
                }
            }
        }
        
        runAction(SKAction.waitForDuration(0.2), completion: completion)
    }

とあるノードの下にある空白の個数を`searchEmpty`で探し
その個数分だけそのノードをアクションで下げる
***
*空白の探索*

    func searchEmpty(node:SKSpriteNode)-> Int{
        
        var count = 0
        if(node.name != "board"){
            for(var k:Int = Int(-1*node.position.y/TileSize);k < NumRows;k++){
                let sprite = searchNode(tileArrayPos[Int(node.position.x/TileSize)][k])
                if(sprite.name == "board"){
                    count++
                }
            }
        }
        
        return count
    }

とあるノードの下の空白の個数を数えるのだが
`Int(-1*node.position.y/TileSize)`はそのノードのj番目という意味である
`Int(-1*node.position.x/TileSize)`はi番目
空白とはいわゆる`board`自身なのでそのノードの名前が`board`つまり空白のときカウントしその数を返す
***
*空白にタイルを入れる*

 func makeTileInEmpty(completion:()->()){
        
        var emptyList = Array(count:NumColumns,repeatedValue:0)
        for(var i:Int = 0;i < NumColumns;i++){
            var count = 0
            for(var j:Int = 0;j < NumRows;j++){
                if(searchNode(tileArrayPos[i][j]).name == "board"){
                    count++
                    scorePoint += 100
                }
            }
            emptyList[i] = count
        }
        var i = 0
        for k in emptyList{
            for(var j:Int = 0;j < k;j++){
                let sprite = makeTileOne()
                board.addChild(sprite)
                let action = SKAction.moveTo(tileArrayPos[i][j], duration: 0.1)
                sprite.runAction(action)
            }
            i++
        }
        runAction(SKAction.waitForDuration(0.2), completion: completion)
    }

タイルがすべて下に落ちているので
その列の空白を数え各列ごとに`emptyList`に保存し
要素分だけタイルを作成し`board`に追加していく