はじめてのswiftでテトリスを作ってみよう！

# 初めてswiftを触った人でも簡単にテトリスゲームを作成

まずは完成イメージを頭に入れてやる気を出します。

![Screen Shot 2017-07-15 at 8.06.42.png](https://qiita-image-store.s3.amazonaws.com/0/167850/f431c876-40e5-ec46-50d2-aad6c5a843fd.png)



#目次



#動作環境
MacOS 10.12.5
Xcode 7.1

#まずはxcode7.1をインストールしよう。

とりあえず、Appstoreからxcodeをダウンロードすると最新版になってしまいます。
ダウンロードした際のバージョン↓8.3。これだと推奨環境の7.1でないのでエラーが出る。実はこれでだいぶはまった。

![Screen Shot 2017-07-15 at 8.25.37.png](https://qiita-image-store.s3.amazonaws.com/0/167850/0dd8b627-d92b-815d-3caa-a17fc087160d.png)

Developer用のダウンロードサイトに飛んで、IDとPWを入力する。
![Screen Shot 2017-07-15 at 8.18.00.png](https://qiita-image-store.s3.amazonaws.com/0/167850/0de96680-06c7-c736-4a1c-e3f805c0c9af.png)

すると、異なるバージョンがダウンロードできるので、xcode7.1を選択してダウンロードする。

#無料のチュートリアルをゲットしよう

まずは参考サイトに飛んでチュートリアルをゲットします。
[Swiftris](https://www.bloc.io/swiftris-build-your-first-ios-game-with-swift)

メールアドレスを入力して、Learn Moreを押すとチュートリアルが見れるようになります。
![Screen Shot 2017-07-15 at 8.47.25.png](https://qiita-image-store.s3.amazonaws.com/0/167850/0b081856-e81b-1e0a-e2cc-971f92ef0d74.png)

こんな画面。
![Screen Shot 2017-07-15 at 8.52.54.png](https://qiita-image-store.s3.amazonaws.com/0/167850/81491271-1a49-f7d5-6307-65a2ad44c755.png)

これで準備は完了です。

#新規プロジェクトを作成しよう！
###いらないファイルを削除してアセットを追加
Create a new Xcode　Projectを選択する。
![Screen Shot 2017-07-15 at 9.14.18.png](https://qiita-image-store.s3.amazonaws.com/0/167850/6c32aa4b-9b7a-46e0-a490-69dcedc4f0ed.png)

次の画面でGameを選択してnextを押す。
<img width="1392" alt="Screen Shot 2017-07-15 at 9.15.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/1e7ee09a-f2fb-1a7f-2458-2629e9b3324e.png">

バージョンが古い影響で画面が少しずれるけど、進めていくには問題ないので適当に記入していく。
<img width="1392" alt="Screen Shot 2017-07-15 at 10.10.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/5fb4b669-3729-b78c-0231-bc0954e1b127.png">

自分の好みの場所を選んで、その場所にファイルを作成する。
![Screen Shot 2017-07-15 at 10.12.17.png](https://qiita-image-store.s3.amazonaws.com/0/167850/ea94d578-451d-86f4-acaf-0b433bb199e8.png)

Portraitを選ぶ。
<img width="1392" alt="Screen Shot 2017-07-15 at 10.15.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/8ee57bb3-d951-5a6f-4690-3707cde26d04.png">

#不要なファイルを削除
GameScene.sksを削除、Assets.xcassets配下のSpaceshipも削除する。

![Screen Shot 2017-07-15 at 10.32.09.png](https://qiita-image-store.s3.amazonaws.com/0/167850/89777da1-74a9-b2ed-ae0b-08eda053cdba.png)

###コードの中のいらない部分を削除
GameScene.swiftを見てみると下記の状態になっているかと思います。

```
import SpriteKit

class GameScene: SKScene {
    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        let myLabel = SKLabelNode(fontNamed:"Chalkduster")
        myLabel.text = "Hello, World!";
        myLabel.fontSize = 45;
        myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
        
        self.addChild(myLabel)
    }
    
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
       /* Called when a touch begins */
        
        for touch in touches {
            let location = touch.locationInNode(self)
            
            let sprite = SKSpriteNode(imageNamed:"Spaceship")
            
            sprite.xScale = 0.5
            sprite.yScale = 0.5
            sprite.position = location
            
            let action = SKAction.rotateByAngle(CGFloat(M_PI), duration:1)
            
            sprite.runAction(SKAction.repeatActionForever(action))
            
            self.addChild(sprite)
        }
    }
   
    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */
    }
}
```

このコードのいらない部分をトリミングして下記の状態にします。

```
import SpriteKit

class GameScene: SKScene {

    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */
    }
}
```

次はGameViewController.swiftです。

```
import UIKit
import SpriteKit

class GameViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        if let scene = GameScene(fileNamed:"GameScene") {
            // Configure the view.
            let skView = self.view as! SKView
            skView.showsFPS = true
            skView.showsNodeCount = true
            
            /* Sprite Kit applies additional optimizations to improve rendering performance */
            skView.ignoresSiblingOrder = true
            
            /* Set the scale mode to scale to fit the window */
            scene.scaleMode = .AspectFill
            
            skView.presentScene(scene)
        }
    }

    override func shouldAutorotate() -> Bool {
        return true
    }

    override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
            return .AllButUpsideDown
        } else {
            return .All
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Release any cached data, images, etc that aren't in use.
    }

    override func prefersStatusBarHidden() -> Bool {
        return true
    }
}
```

いらない部分を削除後のコードです。

```
import UIKit
import SpriteKit

class GameViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func prefersStatusBarHidden() -> Bool {
        return true
    }
}
```

###アセットの追加
ダウンロードする場所が少しわかりづらいのですが、 赤字の `Download the necessary assets` をクリックするとファイルがダウンロードされます。
![Screen Shot 2017-07-15 at 10.59.36.png](https://qiita-image-store.s3.amazonaws.com/0/167850/f28549c5-e009-e908-8302-291e6d31c862.png)

ファイルを解凍すると中身が6つあります。

- Images
- Sounds
- Sprites.atlas
- icon-29pt.png
- icon-40pt.png
- icon-60pt.png

![Screen Shot 2017-07-15 at 11.03.40.png](https://qiita-image-store.s3.amazonaws.com/0/167850/117e9e8a-490d-ca6b-2036-6a30d3eece1f.png)

test（＊自分で作成したフォルダ名）フォルダーにSoundsフォルダーをおきます。

Soundsフォルダーを持ってくると下記の画面が出てくるので、Copy items if neededにチェックされていることを確認して、Finishボタンを押す。
<img width="1072" alt="Screen Shot 2017-07-15 at 11.14.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/e995c2aa-8fbc-3ef4-279a-6c30d469cfbe.png">

下記のような画面になっていればOK。
<img width="1072" alt="Screen Shot 2017-07-15 at 11.15.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/e1860714-a2f3-0816-d5a6-1da56c86df6f.png">

Sprites.atlasフォルダー、Imagesフォルダーも同じように追加する。
<img width="1072" alt="Screen Shot 2017-07-15 at 11.23.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/b1587543-8f4b-1bb9-2209-83a32dc97ff2.png">

次はAssets.xcassetsを開いてアイコンを追加していく。ドラッグ＆ドロップで枠にあてはめていく。

追加前の画像
<img width="1072" alt="Screen Shot 2017-07-15 at 11.25.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/0b15be12-ce0b-6e2d-5418-36be2a576b60.png">

追加後の画像
<img width="1044" alt="Screen Shot 2017-07-15 at 11.28.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/4cc2e1f6-f62e-b3e6-cad1-396510472fde.png">



GameSceneとGameViewControllerにいよいよコードを追加していく。

GameScene.swift

```
import SpriteKit

class GameScene: SKScene {
    required init(coder aDecoder: NSCoder) {
        fatalError("NSCoder not supported")
    }
    
    override init(size: CGSize) {
        super.init(size: size)
        
        anchorPoint = CGPoint(x: 0, y: 1.0)
        
        let background = SKSpriteNode(imageNamed: "background")
        background.position = CGPoint(x: 0, y: 0)
        background.anchorPoint = CGPoint(x: 0, y: 1.0)
        addChild(background)
    }
    
    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */
    }
}
```

GameViewController.swift

```
import UIKit
import SpriteKit

class GameViewController: UIViewController {
    var scene: GameScene!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Configure the view.
        let skView = view as! SKView
        skView.multipleTouchEnabled = false
        
        // Create and configure the scene.
        scene = GameScene(size: skView.bounds.size)
        scene.scaleMode = .AspectFill
        
        // Present the scene.
        skView.presentScene(scene)
    }

    override func prefersStatusBarHidden() -> Bool {
        return true
    }
}
```

#配列を作る
testディレクト下にArray2D.swiftファイルを作成する。

ファイルを作成をして、SwiftFileを選択する。
<img width="1072" alt="Screen Shot 2017-07-15 at 11.47.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/dc00d50e-109f-5aa4-a6d8-728dc2b4fbe6.png">

ファイルの中にArray2Dクラスを作成する。

```
class Array2D<T> {
    let columns: Int
    let rows: Int
    // #2
    var array: Array<T?>
    
    init(columns: Int, rows: Int) {
        self.columns = columns
        self.rows = rows
        // #3
        array = Array<T?>(count:rows * columns, repeatedValue: nil)
    }
    
    // #4
    subscript(column: Int, row: Int) -> T? {
        get {
            return array[(row * columns) + column]
        }
        set(newValue) {
            array[(row * columns) + column] = newValue
        }
    }
}
```

#時間制御

GameScene.swiftにコードを追加して、テトリスが落ちていく時間の制御を行う。

```
import SpriteKit

let TickLengthLevelOne = NSTimeInterval(600)

class GameScene: SKScene {
    var tick:(() -> ())?
    var tickLengthMillis = TickLengthLevelOne
    var lastTick:NSDate?
    
    required init(coder aDecoder: NSCoder) {
        fatalError("NSCoder not supported")
    }
    
    override init(size: CGSize) {
        super.init(size: size)
        
        anchorPoint = CGPoint(x: 0, y: 1.0)
        
        let background = SKSpriteNode(imageNamed: "background")
        background.position = CGPoint(x: 0, y: 0)
        background.anchorPoint = CGPoint(x: 0, y: 1.0)
        addChild(background)
    }
    
    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */
        guard let lastTick = lastTick else {
            return
        }
        let timePassed = lastTick.timeIntervalSinceNow * -1000.0
        if timePassed > tickLengthMillis {
            self.lastTick = NSDate()
            tick?()
        }
    }
    
    func startTicking() {
        lastTick = NSDate()
    }
    
    func stopTicking() {
        lastTick = nil
    }
}
```

#テトリスのブロックに関するクラスを作成
testディレクト下にArray2D.swiftファイルを作成した時と同じようにBlockfileを作成して、Blockクラスを書いていく。最初の部分ではブロックの色に言及している。ブルー、オレンジ、パープル、レッド、ティール（青緑的な）、黄色の６種類があります。

Block.swift

```
import SpriteKit

let NumberOfColors: UInt32 = 6

enum BlockColor: Int, CustomStringConvertible {
    
    case Blue = 0, Orange, Purple, Red, Teal, Yellow
    
    var spriteName: String {
        switch self {
        case .Blue:
            return "blue"
        case .Orange:
            return "orange"
        case .Purple:
            return "purple"
        case .Red:
            return "red"
        case .Teal:
            return "teal"
        case .Yellow:
            return "yellow"
        }
    }
    
    var description: String {
        return self.spriteName
    }

    static func random() -> BlockColor {
        return BlockColor(rawValue:Int(arc4random_uniform(NumberOfColors)))!
    }
}
```

次にBlockクラスの中身について書いていく。（...で中身を省略。）

Block.swift

```
import SpriteKit
...
    static func random() -> BlockColor {
        return BlockColor(rawValue:Int(arc4random_uniform(NumberOfColors)))!
    }
}

class Block: Hashable, CustomStringConvertible {
    // Constants
    let color: BlockColor

    // Properties
    var column: Int
    var row: Int
    var sprite: SKSpriteNode?
    
    var spriteName: String {
        return color.spriteName
    }
    
    var hashValue: Int {
        return self.column ^ self.row
    }
    
    var description: String {
        return "\(color): [\(column), \(row)]"
    }
    
    init(column:Int, row:Int, color:BlockColor) {
        self.column = column
        self.row = row
        self.color = color
    }
}

func ==(lhs: Block, rhs: Block) -> Bool {
    return lhs.column == rhs.column && lhs.row == rhs.row && lhs.color.rawValue == rhs.color.rawValue
}
```

#様々なブロックの形を用意していこう

L字型のブロックや、横長のブロックや、凸型のブロックを用意していきます。

![Screen Shot 2017-07-16 at 12.02.55.png](https://qiita-image-store.s3.amazonaws.com/0/167850/85ded056-f2c1-51a0-c45e-1255ed5a596a.png)

もう慣れたと思いますが、新規ファイルShape.swiftを作り、中身のコードを書いていきます。

Shape.swift

```
import SpriteKit

let NumOrientations: UInt32 = 4

enum Orientation: Int, CustomStringConvertible {
    case Zero = 0, Ninety, OneEighty, TwoSeventy
    
    var description: String {
        switch self {
        case .Zero:
            return "0"
        case .Ninety:
            return "90"
        case .OneEighty:
            return "180"
        case .TwoSeventy:
            return "270"
        }
    }
    
    static func random() -> Orientation {
        return Orientation(rawValue:Int(arc4random_uniform(NumOrientations)))!
    }
    
    static func rotate(orientation:Orientation, clockwise: Bool) -> Orientation {
        var rotated = orientation.rawValue + (clockwise ? 1 : -1)
        if rotated > Orientation.TwoSeventy.rawValue {
            rotated = Orientation.Zero.rawValue
        } else if rotated < 0 {
            rotated = Orientation.TwoSeventy.rawValue
        }
        return Orientation(rawValue:rotated)!
    }
}
```

次にShapeクラスの中身を書いていきます。（まだ記述していないメソッドがあるので、エラー表示がでると思いますが、慌てないで！）

Shape.swift

```
// The number of total shape varieties
let NumShapeTypes: UInt32 = 7

// Shape indexes
let FirstBlockIdx: Int = 0
let SecondBlockIdx: Int = 1
let ThirdBlockIdx: Int = 2
let FourthBlockIdx: Int = 3

class Shape: Hashable, CustomStringConvertible {
    // The color of the shape
    let color:BlockColor
    
    // The blocks comprising the shape
    var blocks = Array<Block>()
    // The current orientation of the shape
    var orientation: Orientation
    // The column and row representing the shape's anchor point
    var column, row:Int
    
    // Required Overrides
    // Subclasses must override this property
    var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [:]
    }
    // Subclasses must override this property
    var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [:]
    }

    var bottomBlocks:Array<Block> {
        guard let bottomBlocks = bottomBlocksForOrientations[orientation] else {
            return []
        }
        return bottomBlocks
    }
    
    // Hashable
    var hashValue:Int {
        return blocks.reduce(0) { $0.hashValue ^ $1.hashValue }
    }
    
    // CustomStringConvertible
    var description:String {
        return "\(color) block facing \(orientation): \(blocks[FirstBlockIdx]), \(blocks[SecondBlockIdx]), \(blocks[ThirdBlockIdx]), \(blocks[FourthBlockIdx])"
    }
    
    init(column:Int, row:Int, color: BlockColor, orientation:Orientation) {
        self.color = color
        self.column = column
        self.row = row
        self.orientation = orientation
        initializeBlocks()
    }
    
    // #6
    convenience init(column:Int, row:Int) {
        self.init(column:column, row:row, color:BlockColor.random(), orientation:Orientation.random())
    }
}

func ==(lhs: Shape, rhs: Shape) -> Bool {
    return lhs.row == rhs.row && lhs.column == rhs.column
}
```

画面のようにそんなもんはないと怒られますが、あとで作るので気にしないでください。

<img width="1280" alt="Screen Shot 2017-07-16 at 12.17.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/da849029-bd9c-06ed-324c-98730b4a2d10.png">

まだ定義していないメソッドを呼び出しているので、エラーが出ています。それを解決します。

Shape.swift

```
...
convenience init(column:Int, row:Int) {
        self.init(column:column, row:row, color:BlockColor.random(), orientation:Orientation.random())
    }
    
    final func initializeBlocks() {
        guard let blockRowColumnTranslations = blockRowColumnPositions[orientation] else {
            return
        }

        blocks = blockRowColumnTranslations.map { (diff) -> Block in
            return Block(column: column + diff.columnDiff, row: row + diff.rowDiff, color: color)
        }
    }
    
}

func ==(lhs: Shape, rhs: Shape) -> Bool {
    return lhs.row == rhs.row && lhs.column == rhs.column
}
```

これで大枠のブロックの形は準備できました。次にそれぞれのブロックの形について書いていきましょう。

###四角のブロック
SquareShape.swift

```
class SquareShape:Shape {
    /*
    | 0•| 1 |
    | 2 | 3 |
    
    • marks the row/column indicator for the shape
    
    */
    
    // The square shape will not rotate
    
    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero: [(0, 0), (1, 0), (0, 1), (1, 1)],
            Orientation.OneEighty: [(0, 0), (1, 0), (0, 1), (1, 1)],
            Orientation.Ninety: [(0, 0), (1, 0), (0, 1), (1, 1)],
            Orientation.TwoSeventy: [(0, 0), (1, 0), (0, 1), (1, 1)]
        ]
    }
    
    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.OneEighty:  [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.Ninety:     [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]]
        ]
    }
}
```

###T字のブロック
TShape.swift

```
class TShape:Shape {
    /*
    Orientation 0

      • | 0 |
    | 1 | 2 | 3 |

    Orientation 90

      • | 1 |
        | 2 | 0 |
        | 3 |

    Orientation 180

      •
    | 1 | 2 | 3 |
        | 0 |

    Orientation 270

      • | 1 |
    | 0 | 2 |
        | 3 |

    • marks the row/column indicator for the shape

    */

    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero:       [(1, 0), (0, 1), (1, 1), (2, 1)],
            Orientation.Ninety:     [(2, 1), (1, 0), (1, 1), (1, 2)],
            Orientation.OneEighty:  [(1, 2), (0, 1), (1, 1), (2, 1)],
            Orientation.TwoSeventy: [(0, 1), (1, 0), (1, 1), (1, 2)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[SecondBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.Ninety:     [blocks[FirstBlockIdx], blocks[FourthBlockIdx]],
            Orientation.OneEighty:  [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[FourthBlockIdx]]
        ]
    }
}
```

###一本棒のブロック
LineShape.swift

```
class LineShape:Shape {
    /*
        Orientations 0 and 180:

            | 0•|
            | 1 |
            | 2 |
            | 3 |

        Orientations 90 and 270:

        | 0 | 1•| 2 | 3 |

    • marks the row/column indicator for the shape

    */

    // Hinges about the second block

    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero:       [(0, 0), (0, 1), (0, 2), (0, 3)],
            Orientation.Ninety:     [(-1,0), (0, 0), (1, 0), (2, 0)],
            Orientation.OneEighty:  [(0, 0), (0, 1), (0, 2), (0, 3)],
            Orientation.TwoSeventy: [(-1,0), (0, 0), (1, 0), (2, 0)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[FourthBlockIdx]],
            Orientation.Ninety:     blocks,
            Orientation.OneEighty:  [blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: blocks
        ]
    }
}
```

###L字のブロック
LShape.swift

```
class LShape:Shape {
    /*

    Orientation 0

        | 0•|
        | 1 |
        | 2 | 3 |

    Orientation 90

          •
    | 2 | 1 | 0 |
    | 3 |

    Orientation 180

    | 3 | 2•|
        | 1 |
        | 0 |

    Orientation 270

          • | 3 |
    | 0 | 1 | 2 |

    • marks the row/column indicator for the shape

    Pivots about `1`

    */

    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero:       [ (0, 0), (0, 1),  (0, 2),  (1, 2)],
            Orientation.Ninety:     [ (1, 1), (0, 1),  (-1,1), (-1, 2)],
            Orientation.OneEighty:  [ (0, 2), (0, 1),  (0, 0),  (-1,0)],
            Orientation.TwoSeventy: [ (-1,1), (0, 1),  (1, 1),   (1,0)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.Ninety:     [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[FourthBlockIdx]],
            Orientation.OneEighty:  [blocks[FirstBlockIdx], blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[ThirdBlockIdx]]
        ]
    }
}
```

###J字のブロック
JShape.swift

```
class JShape:Shape {
    /*

    Orientation 0

      • | 0 |
        | 1 |
    | 3 | 2 |

    Orientation 90

    | 3•|
    | 2 | 1 | 0 |

    Orientation 180

    | 2•| 3 |
    | 1 |
    | 0 |

    Orientation 270

    | 0•| 1 | 2 |
            | 3 |

    • marks the row/column indicator for the shape

    Pivots about `1`

    */

    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero:       [(1, 0), (1, 1),  (1, 2),  (0, 2)],
            Orientation.Ninety:     [(2, 1), (1, 1),  (0, 1),  (0, 0)],
            Orientation.OneEighty:  [(0, 2), (0, 1),  (0, 0),  (1, 0)],
            Orientation.TwoSeventy: [(0, 0), (1, 0),  (2, 0),  (2, 1)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.Ninety:     [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[ThirdBlockIdx]],
            Orientation.OneEighty:  [blocks[FirstBlockIdx], blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[FourthBlockIdx]]
        ]
    }
}
```

###S字のブロック
SShape.swift

```
class SShape:Shape {
    /*

    Orientation 0

    | 0•|
    | 1 | 2 |
        | 3 |

    Orientation 90

      • | 1 | 0 |
    | 3 | 2 |

    Orientation 180

    | 0•|
    | 1 | 2 |
        | 3 |

    Orientation 270

      • | 1 | 0 |
    | 3 | 2 |

    • marks the row/column indicator for the shape

    */

    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero:       [(0, 0), (0, 1), (1, 1), (1, 2)],
            Orientation.Ninety:     [(2, 0), (1, 0), (1, 1), (0, 1)],
            Orientation.OneEighty:  [(0, 0), (0, 1), (1, 1), (1, 2)],
            Orientation.TwoSeventy: [(2, 0), (1, 0), (1, 1), (0, 1)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[SecondBlockIdx], blocks[FourthBlockIdx]],
            Orientation.Ninety:     [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.OneEighty:  [blocks[SecondBlockIdx], blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]]
        ]
    }
}
```

###Z字のブロック
ZShape.swift

```
class ZShape:Shape {
    /*

    Orientation 0

      • | 0 |
    | 2 | 1 |
    | 3 |

    Orientation 90

    | 0 | 1•|
        | 2 | 3 |

    Orientation 180

      • | 0 |
    | 2 | 1 |
    | 3 |

    Orientation 270

    | 0 | 1•|
        | 2 | 3 |


    • marks the row/column indicator for the shape

    */

    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero:       [(1, 0), (1, 1), (0, 1), (0, 2)],
            Orientation.Ninety:     [(-1,0), (0, 0), (0, 1), (1, 1)],
            Orientation.OneEighty:  [(1, 0), (1, 1), (0, 1), (0, 2)],
            Orientation.TwoSeventy: [(-1,0), (0, 0), (0, 1), (1, 1)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[SecondBlockIdx], blocks[FourthBlockIdx]],
            Orientation.Ninety:     [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.OneEighty:  [blocks[SecondBlockIdx], blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]]
        ]
    }
}
```

#作成したブロックを落としてみよう！

Shape.swift

```
     final func initializeBlocks() {
        guard let blockRowColumnTranslations = blockRowColumnPositions[orientation] else {
            return
        }
        blocks = blockRowColumnTranslations.map { (diff) -> Block in
            return Block(column: column + diff.columnDiff, row: row + diff.rowDiff, color: color)
        }
    }

     final func rotateBlocks(orientation: Orientation) {
         guard let blockRowColumnTranslation:Array<(columnDiff: Int, rowDiff: Int)> = blockRowColumnPositions[orientation] else {
             return
         }
// #1
         for (idx, diff) in blockRowColumnTranslation.enumerate() {
             blocks[idx].column = column + diff.columnDiff
             blocks[idx].row = row + diff.rowDiff
         }
     }

     final func lowerShapeByOneRow() {
         shiftBy(0, rows:1)
     }

// #2
     final func shiftBy(columns: Int, rows: Int) {
         self.column += columns
         self.row += rows
         for block in blocks {
             block.column += columns
             block.row += rows
         }
     }

// #3
     final func moveTo(column: Int, row:Int) {
         self.column = column
         self.row = row
         rotateBlocks(orientation)
     }

     final class func random(startingColumn:Int, startingRow:Int) -> Shape {
         switch Int(arc4random_uniform(NumShapeTypes)) {
// #4
         case 0:
             return SquareShape(column:startingColumn, row:startingRow)
         case 1:
             return LineShape(column:startingColumn, row:startingRow)
         case 2:
             return TShape(column:startingColumn, row:startingRow)
         case 3:
             return LShape(column:startingColumn, row:startingRow)
         case 4:
             return JShape(column:startingColumn, row:startingRow)
         case 5:
             return SShape(column:startingColumn, row:startingRow)
         default:
             return ZShape(column:startingColumn, row:startingRow)
         }
     }
```

次にSwiftris.swiftを作成していきます。

Swiftris.swift

```
let NumColumns = 10
let NumRows = 20

let StartingColumn = 4
let StartingRow = 0

let PreviewColumn = 12
let PreviewRow = 1

class Swiftris {
    var blockArray:Array2D<Block>
    var nextShape:Shape?
    var fallingShape:Shape?
    
    init() {
        fallingShape = nil
        nextShape = nil
        blockArray = Array2D<Block>(columns: NumColumns, rows: NumRows)
    }
    
    func beginGame() {
        if (nextShape == nil) {
            nextShape = Shape.random(PreviewColumn, startingRow: PreviewRow)
        }
    }
    
    // #6
    func newShape() -> (fallingShape:Shape?, nextShape:Shape?) {
        fallingShape = nextShape
        nextShape = Shape.random(PreviewColumn, startingRow: PreviewRow)
        fallingShape?.moveTo(StartingColumn, row: StartingRow)
        return (fallingShape, nextShape)
    }
}
```

GameScene.swiftを編集していきます。

GameScene.swift

```
import SpriteKit

// #7
 let BlockSize:CGFloat = 20.0

 let TickLengthLevelOne = NSTimeInterval(600)

class GameScene: SKScene {
// #8
    let gameLayer = SKNode()
    let shapeLayer = SKNode()
    let LayerPosition = CGPoint(x: 6, y: -6)

    var tick:(() -> ())?
    var tickLengthMillis = TickLengthLevelOne
    var lastTick:NSDate?

    var textureCache = Dictionary<String, SKTexture>()

    required init(coder aDecoder: NSCoder) {
        fatalError("NSCoder not supported")
    }

    override init(size: CGSize) {
        super.init(size: size)

        anchorPoint = CGPoint(x: 0, y: 1.0)

        let background = SKSpriteNode(imageNamed: "background")
        background.position = CGPoint(x: 0, y: 0)
        background.anchorPoint = CGPoint(x: 0, y: 1.0)
        addChild(background)

        addChild(gameLayer)

        let gameBoardTexture = SKTexture(imageNamed: "gameboard")
        let gameBoard = SKSpriteNode(texture: gameBoardTexture, size: CGSizeMake(BlockSize * CGFloat(NumColumns), BlockSize * CGFloat(NumRows)))
        gameBoard.anchorPoint = CGPoint(x:0, y:1.0)
        gameBoard.position = LayerPosition

        shapeLayer.position = LayerPosition
        shapeLayer.addChild(gameBoard)
        gameLayer.addChild(shapeLayer)
    }

    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */
        guard let lastTick = lastTick else {
            return
        }
        let timePassed = lastTick.timeIntervalSinceNow * -1000.0
        if timePassed > tickLengthMillis {
            self.lastTick = NSDate()
            tick?()
        }
    }

    func startTicking() {
        lastTick = NSDate()
    }

    func stopTicking() {
        lastTick = nil
    }

// #9
     func pointForColumn(column: Int, row: Int) -> CGPoint {
         let x = LayerPosition.x + (CGFloat(column) * BlockSize) + (BlockSize / 2)
         let y = LayerPosition.y - ((CGFloat(row) * BlockSize) + (BlockSize / 2))
         return CGPointMake(x, y)
     }

     func addPreviewShapeToScene(shape:Shape, completion:() -> ()) {
         for block in shape.blocks {
// #10
             var texture = textureCache[block.spriteName]
             if texture == nil {
                 texture = SKTexture(imageNamed: block.spriteName)
                 textureCache[block.spriteName] = texture
             }
             let sprite = SKSpriteNode(texture: texture)
// #11
             sprite.position = pointForColumn(block.column, row:block.row - 2)
             shapeLayer.addChild(sprite)
             block.sprite = sprite

             // Animation
             sprite.alpha = 0
// #12
             let moveAction = SKAction.moveTo(pointForColumn(block.column, row: block.row), duration: NSTimeInterval(0.2))
             moveAction.timingMode = .EaseOut
             let fadeInAction = SKAction.fadeAlphaTo(0.7, duration: 0.4)
             fadeInAction.timingMode = .EaseOut
             sprite.runAction(SKAction.group([moveAction, fadeInAction]))
        }
         runAction(SKAction.waitForDuration(0.4), completion: completion)
     }

     func movePreviewShape(shape:Shape, completion:() -> ()) {
         for block in shape.blocks {
             let sprite = block.sprite!
             let moveTo = pointForColumn(block.column, row:block.row)
             let moveToAction:SKAction = SKAction.moveTo(moveTo, duration: 0.2)
             moveToAction.timingMode = .EaseOut
             sprite.runAction(
                 SKAction.group([moveToAction, SKAction.fadeAlphaTo(1.0, duration: 0.2)]), completion: {})
         }
         runAction(SKAction.waitForDuration(0.2), completion: completion)
     }

     func redrawShape(shape:Shape, completion:() -> ()) {
         for block in shape.blocks {
             let sprite = block.sprite!
             let moveTo = pointForColumn(block.column, row:block.row)
             let moveToAction:SKAction = SKAction.moveTo(moveTo, duration: 0.05)
             moveToAction.timingMode = .EaseOut
             if block == shape.blocks.last {
                 sprite.runAction(moveToAction, completion: completion)
             } else {
                 sprite.runAction(moveToAction)
             }
         }
     }
 }
```

GameViewControllerを修正していきます。

GameViewController.swift

```
import UIKit
import SpriteKit

class GameViewController: UIViewController {

    var scene: GameScene!
    var swiftris:Swiftris!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Configure the view.
        let skView = view as! SKView
        skView.multipleTouchEnabled = false

        // Create and configure the scene.
        scene = GameScene(size: skView.bounds.size)
        scene.scaleMode = .AspectFill
// #13
        scene.tick = didTick

        swiftris = Swiftris()
        swiftris.beginGame()

        // Present the scene.
        skView.presentScene(scene)

// #14
         scene.addPreviewShapeToScene(swiftris.nextShape!) {
             self.swiftris.nextShape?.moveTo(StartingColumn, row: StartingRow)
             self.scene.movePreviewShape(self.swiftris.nextShape!) {
                 let nextShapes = self.swiftris.newShape()
                 self.scene.startTicking()
                 self.scene.addPreviewShapeToScene(nextShapes.nextShape!) {}
             }
         }
    }

    override func prefersStatusBarHidden() -> Bool {
        return true
    }

// #15
     func didTick() {
         swiftris.fallingShape?.lowerShapeByOneRow()
         scene.redrawShape(swiftris.fallingShape!, completion: {})
     }
}
```

まだ地面は突き抜けてしまいますが、ブロックが上から落ちてきているはずです。

#ルールの設定をしよう
Swiftris.swiftにコードを加えていきます。

Swiftris.swift

```
...
let PreviewRow = 1

 protocol SwiftrisDelegate {
     // Invoked when the current round of Swiftris ends
     func gameDidEnd(swiftris: Swiftris)

     // Invoked after a new game has begun
     func gameDidBegin(swiftris: Swiftris)

     // Invoked when the falling shape has become part of the game board
     func gameShapeDidLand(swiftris: Swiftris)

     // Invoked when the falling shape has changed its location
     func gameShapeDidMove(swiftris: Swiftris)

     // Invoked when the falling shape has changed its location after being dropped
     func gameShapeDidDrop(swiftris: Swiftris)

     // Invoked when the game has reached a new level
     func gameDidLevelUp(swiftris: Swiftris)
 }

class Swiftris {
    var blockArray:Array2D<Block>
    var nextShape:Shape?
    var fallingShape:Shape?
    var delegate:SwiftrisDelegate?
...
```

さらに修正を加えていきます。この時点では`endGame()`が定義されていないのでエラーがでますが、気にしないで進んでください。

Swiftris.swift

```
...
func beginGame() {
        if (nextShape == nil) {
            nextShape = Shape.random(PreviewColumn, startingRow: PreviewRow)
        }
        delegate?.gameDidBegin(self)
    }

    func newShape() -> (fallingShape:Shape?, nextShape:Shape?) {
        fallingShape = nextShape
        nextShape = Shape.random(PreviewColumn, startingRow: PreviewRow)
        fallingShape?.moveTo(StartingColumn, row: StartingRow)
// #1
         guard detectIllegalPlacement() == false else {
             nextShape = fallingShape
             nextShape!.moveTo(PreviewColumn, row: PreviewRow)
             endGame()
             return (nil, nil)
         }
         return (fallingShape, nextShape)
     }

// #2
     func detectIllegalPlacement() -> Bool {
         guard let shape = fallingShape else {
             return false
         }
         for block in shape.blocks {
             if block.column < 0 || block.column >= NumColumns
                 || block.row < 0 || block.row >= NumRows {
                 return true
             } else if blockArray[block.column, block.row] != nil {
                 return true
             }
         }
     return false
 }
...
```

 endGame()を定義する前に、Shape.swiftにコードを加えます。

Shape.swift

```
...
final func rotateBlocks(orientation: Orientation) {
        guard let blockRowColumnTranslation:Array<(columnDiff: Int, rowDiff: Int)> = blockRowColumnPositions[orientation] else {
            return
        }
        for (idx, diff) in blockRowColumnTranslation.enumerate() {
            blocks[idx].column = column + diff.columnDiff
            blocks[idx].row = row + diff.rowDiff
        }
    }

// #3
     final func rotateClockwise() {
         let newOrientation = Orientation.rotate(orientation, clockwise: true)
         rotateBlocks(newOrientation)
         orientation = newOrientation
     }

     final func rotateCounterClockwise() {
         let newOrientation = Orientation.rotate(orientation, clockwise: false)
         rotateBlocks(newOrientation)
         orientation = newOrientation
     }

    final func lowerShapeByOneRow() {
        shiftBy(0, rows:1)
    }

     final func raiseShapeByOneRow() {
         shiftBy(0, rows:-1)
     }

     final func shiftRightByOneColumn() {
         shiftBy(1, rows:0)
     }

     final func shiftLeftByOneColumn() {
         shiftBy(-1, rows:0)
     }

    final func shiftBy(columns: Int, rows: Int) {
        self.column += columns
        self.row += rows
        for block in blocks {
            block.column += columns
            block.row += rows
        }
    }
...
```

再度Swiftris.swiftを修正していきます。ブロックが回転したり、左右に動いたりする機能を追加していきます。いくつかの関数がまだ定義していないので、エラーがでると思いますが、気にせず次に進んでください。

Swiftris.swift

```
...
func detectIllegalPlacement() -> Bool {
        guard let shape = fallingShape else {
            return false
        }
        for block in shape.blocks {
            if block.column < 0 || block.column >= NumColumns
                || block.row < 0 || block.row >= NumRows {
                return true
            } else if blockArray[block.column, block.row] != nil {
                return true
            }
        }
        return false
    }

// #4
     func dropShape() {
         guard let shape = fallingShape else {
             return
         }
         while detectIllegalPlacement() == false {
             shape.lowerShapeByOneRow()
         }
         shape.raiseShapeByOneRow()
         delegate?.gameShapeDidDrop(self)
     }

// #5
     func letShapeFall() {
         guard let shape = fallingShape else {
             return
         }
         shape.lowerShapeByOneRow()
         if detectIllegalPlacement() {
             shape.raiseShapeByOneRow()
             if detectIllegalPlacement() {
                 endGame()
             } else {
                 settleShape()
             }
         } else {
             delegate?.gameShapeDidMove(self)
             if detectTouch() {
                 settleShape()
             }
         }
     }

// #6
     func rotateShape() {
         guard let shape = fallingShape else {
             return
         }
         shape.rotateClockwise()
         guard detectIllegalPlacement() == false else {
             shape.rotateCounterClockwise()
             return
         }
         delegate?.gameShapeDidMove(self)
     }

// #7
     func moveShapeLeft() {
         guard let shape = fallingShape else {
             return
         }
         shape.shiftLeftByOneColumn()
         guard detectIllegalPlacement() == false else {
             shape.shiftRightByOneColumn()
             return
         }
         delegate?.gameShapeDidMove(self)
     }

     func moveShapeRight() {
         guard let shape = fallingShape else {
             return
         }
         shape.shiftRightByOneColumn()
         guard detectIllegalPlacement() == false else {
             shape.shiftLeftByOneColumn()
             return
         }
         delegate?.gameShapeDidMove(self)
     }
...
```

最後にエラーが出ている部分を解消していきます。

```
...
func settleShape() {
         guard let shape = fallingShape else {
             return
         }
         for block in shape.blocks {
             blockArray[block.column, block.row] = block
         }
         fallingShape = nil
         delegate?.gameShapeDidLand(self)
     }

// #9
     func detectTouch() -> Bool {
         guard let shape = fallingShape else {
             return false
         }
         for bottomBlock in shape.bottomBlocks {
             if bottomBlock.row == NumRows - 1
                 || blockArray[bottomBlock.column, bottomBlock.row + 1] != nil {
                     return true
             }
         }
         return false
     }

     func endGame() {
         delegate?.gameDidEnd(self)
     }

    func dropShape() {
        guard let shape = fallingShape else {
            return
        }
        while detectIllegalPlacement() == false {
            shape.lowerShapeByOneRow()
        }
        shape.raiseShapeByOneRow()
        delegate?.gameShapeDidDrop(self)
    }
...
```

エラーは消えているでしょうか？消えていない場合は、どこかで追加する場所を間違えたりしているかもしれません。

###得点について
ただ上から落ちてくるテトリスではつまらないので、ブロックを消したときの得点についてのコードを追加していきます。

Swiftris.swift

```
...
 let PreviewColumn = 12
 let PreviewRow = 1

 let PointsPerLine = 10
 let LevelThreshold = 500

 protocol SwiftrisDelegate {
...
```

スコアとレベルの初期値を設定。

```
...
var blockArray:Array2D<Block>
    var nextShape:Shape?
    var fallingShape:Shape?
    var delegate:SwiftrisDelegate?
    
    var score = 0
    var level = 1
    
    init() {
        fallingShape = nil
        nextShape = nil
        blockArray = Array2D<Block>(columns: NumColumns, rows: NumRows)
    }
...
```

```
...
func endGame() {
         score = 0
         level = 1
        delegate?.gameDidEnd(self)
    }
...
```

```
...
func endGame() {
         score = 0
         level = 1
        delegate?.gameDidEnd(self)
    }
...
```

次にテトリスで横一列を揃えたときに関するコードを書いていきます。

```
...
func endGame() {
        score = 0
        level = 1
        delegate?.gameDidEnd(self)
     }

// #10
     func removeCompletedLines() -> (linesRemoved: Array<Array<Block>>, fallenBlocks: Array<Array<Block>>) {
         var removedLines = Array<Array<Block>>()
         for row in (1..<NumRows).reverse() {
             var rowOfBlocks = Array<Block>()
// #11
             for column in 0..<NumColumns {
                 guard let block = blockArray[column, row] else {
                     continue
                 }
                 rowOfBlocks.append(block)
             }
             if rowOfBlocks.count == NumColumns {
                 removedLines.append(rowOfBlocks)
                 for block in rowOfBlocks {
                     blockArray[block.column, block.row] = nil
                 }
             }
         }

// #12
         if removedLines.count == 0 {
             return ([], [])
         }
// #13
         let pointsEarned = removedLines.count * PointsPerLine * level
         score += pointsEarned
         if score >= level * LevelThreshold {
             level += 1
             delegate?.gameDidLevelUp(self)
         }

         var fallenBlocks = Array<Array<Block>>()
         for column in 0..<NumColumns {
             var fallenBlocksArray = Array<Block>()
// #14
             for row in (1..<removedLines[0][0].row).reverse() {
                 guard let block = blockArray[column, row] else {
                     continue
                 }
                 var newRow = row
                 while (newRow < NumRows - 1 && blockArray[column, newRow + 1] == nil) {
                     newRow += 1
                 }
                 block.row = newRow
                 blockArray[column, row] = nil
                 blockArray[column, newRow] = block
                 fallenBlocksArray.append(block)
             }
             if fallenBlocksArray.count > 0 {
                 fallenBlocks.append(fallenBlocksArray)
             }
         }
         return (removedLines, fallenBlocks)
     }
...
```

ブロックを消す動作をする機能を書いていきます。

Swiftris.swift

```
func removeAllBlocks() -> Array<Array<Block>> {
         var allBlocks = Array<Array<Block>>()
         for row in 0..<NumRows {
             var rowOfBlocks = Array<Block>()
             for column in 0..<NumColumns {
                 guard let block = blockArray[column, row] else {
                     continue
                 }
                 rowOfBlocks.append(block)
                 blockArray[column, row] = nil
             }
             allBlocks.append(rowOfBlocks)
         }
         return allBlocks
     }
```

GameViewController.swiftに修正を加えていきます。

GameViewController.swift

```
class GameViewController: UIViewController, SwiftrisDelegate {
```

```
swiftris = Swiftris()
swiftris.delegate = self
swiftris.beginGame()
```

```
削除する部分！！！

scene.addPreviewShapeToScene(swiftris.nextShape!) {
             self.swiftris.nextShape?.moveTo(StartingColumn, row: StartingRow)
             self.scene.movePreviewShape(self.swiftris.nextShape!) {
                 let nextShapes = self.swiftris.newShape()
                 self.scene.startTicking()
                 self.scene.addPreviewShapeToScene(nextShapes.nextShape!) {}
             }
         }
```


```
func didTick() {
// #15
         swiftris.letShapeFall()
     }

     func nextShape() {
         let newShapes = swiftris.newShape()
         guard let fallingShape = newShapes.fallingShape else {
             return
         }
         self.scene.addPreviewShapeToScene(newShapes.nextShape!) {}
         self.scene.movePreviewShape(fallingShape) {
// #16
             self.view.userInteractionEnabled = true
             self.scene.startTicking()
         }
     }

     func gameDidBegin(swiftris: Swiftris) {
         // The following is false when restarting a new game
         if swiftris.nextShape != nil && swiftris.nextShape!.blocks[0].sprite == nil {
             scene.addPreviewShapeToScene(swiftris.nextShape!) {
                 self.nextShape()
             }
         } else {
             nextShape()
         }
     }

     func gameDidEnd(swiftris: Swiftris) {
         view.userInteractionEnabled = false
         scene.stopTicking()
     }

     func gameDidLevelUp(swiftris: Swiftris) {
 
     }

     func gameShapeDidDrop(swiftris: Swiftris) {
 
     }

     func gameShapeDidLand(swiftris: Swiftris) {
         scene.stopTicking()
         nextShape()
     }

// #17
     func gameShapeDidMove(swiftris: Swiftris) {
         scene.redrawShape(swiftris.fallingShape!) {}
     }
```

ここまでくるとブロックが地面へ突き抜けていかないはずです。

#クリックして回転させたり、動かしてみよう

まずは、Main.storyboardを開きます。

<img width="1280" alt="Screen Shot 2017-07-17 at 20.51.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/c7bb7030-66a5-c6c6-acd7-24775715a640.png">

右下でTapと検索して、Tap Gesture Recognizerを探します。それを黒い画面にドラッグアンドドロップします。

<img width="1280" alt="Screen Shot 2017-07-17 at 20.51.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/21b07aeb-bc9e-1655-938b-70ecf24b8bc1.png">

画面右上の部分をクリックして、Assistant Editorで開きます。

<img width="1280" alt="Screen Shot 2017-07-17 at 20.55.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/3abbafca-b5ba-769c-7ab2-eb1389f5158a.png">

下記画面のようにTap Gesture RecognizerをControlを押しながらGameViewControllerにドラッグアンドドロップします。

- Action
- didTap
- UITapGestureRecognizer

<img width="1280" alt="Screen Shot 2017-07-17 at 21.07.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/9b2e09a0-c9a7-cbb5-5150-4d4a28bbb5fb.png">

うまくいけば、GameViewControllerに下記のコードを加わっているはずです。

```
@IBAction func didTap(sender: UITapGestureRecognizer) {
}
```

Tap Gesture Recognizerを右クリックして、下記の画面のようにせっていします。

<img width="1280" alt="Screen Shot 2017-07-17 at 21.12.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/167850/3d1ae0cf-f7e6-745f-3785-7db73625e9e4.png">

GameViewController.swiftに少し修正を加えます。

GameViewController.swift

```
class GameViewController: UIViewController, SwiftrisDelegate, UIGestureRecognizerDelegate {
```

```
@IBAction func didTap(sender: UITapGestureRecognizer) {
     swiftris.rotateShape()
 }
```

これでブロックが回転しているはずです。