63643


swift初心者です。15マスのパズルアプリを作成しているのですが、以下のメッセージが出ていて対応に困っています。

[code]

    Fatal error: Index out of range
    
[/code]

「配列のインデックスが範囲から外れている。配列の個数以上の範囲を指定をするように。」というメッセージであっているのだと考えていますが、具体的にどう直せば良いのか、教えていただきたいと思います。

**参考にしたサイト:**  
[Swiftで行こう！--パズルゲーム(16パズル) - note](https://note.com/dngri/n/n54981e48e2e6)  
[Swiftで行こう！--パズルゲーム(16パズル)2 - note](https://note.com/dngri/n/n27fe677e51bb)

* * *

**現状のソースコード**

[code]

    import UIKit
    
    class ViewController: UIViewController {
    
        let START = 0 //スタート
        let SCREEN = UIScreen.main.bounds.size //画面サイズ
    
        //変数
        var gameView: UIView!       //ゲームビュー
        var titleLabel: UILabel!    //タイトルラベル
        var piece = [UIImageView]() //ピース画像
        var data = [Int](repeating: 0, count: 16) //ピース配置情報　←　ご指摘の通り修正致しました
        var shuffle: Int = 0        //シャッフル
        var startButton: UIButton!  //スタートボタン
    
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            //ゲーム画面のXY座標とスケールの指定
            let x: CGFloat = (SCREEN.width-360)/2
            let y: CGFloat = (SCREEN.height-640)/2
            let scale = SCREEN.width/360
            gameView = UIView()
            gameView.frame = CGRect(x: x, y: y, width: 360, height: 640)
            gameView.transform = CGAffineTransform(scaleX: scale, y: scale)
            view.addSubview(gameView)
    
            //背景の生成
            let background = makeImageView(frame: CGRect(x: 0, y: 0, width: 360, height: 640),
                                   image: UIImage(named: "1.jpg")!)
            gameView.addSubview(background)
    
            //絵の背景の生成
            let pictureBackground = makeImageView(frame: CGRect(x: 29, y: 179, width: 302, height: 302),
                                          image: UIImage(named: "3.png")!)
            gameView.addSubview(pictureBackground)
    
            //タイトルの生成
            titleLabel = makeLabel(frame: CGRect(x: 0, y: 90, width: 360, height: 70),
                                    text: "Dog Puzzle", font: UIFont.systemFont(ofSize: 48))
            gameView.addSubview(titleLabel)
    
    
            //スタートボタンの生成
            startButton = makeButton(frame: CGRect(x: 124, y: 500, width: 114, height: 114),
                                      image: UIImage(named: "start.png")!, tag: START)
            gameView.addSubview(startButton)
        }
    
        //ラベルの生成
        func makeLabel(frame: CGRect, text: String, font: UIFont) -> UILabel {
            let label = UILabel()
            label.frame = frame
            label.text = text
            label.font = font
            label.textColor = UIColor.white
            label.textAlignment = NSTextAlignment.center
            label.lineBreakMode = NSLineBreakMode.byWordWrapping
            label.numberOfLines = 0
            return label
        }
    
        //イメージビューの生成
        func makeImageView(frame: CGRect, image: UIImage) -> UIImageView {
            let imageView = UIImageView()
            imageView.frame = frame
            imageView.image = image
            return imageView
        }
    
        //ピースイメージビューの生成
        func makePieceImageView(frame: CGRect, index: Int,
                                picture: UIImage, piece: UIImage) -> UIImageView {
            UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
            picture.draw(
                in: CGRect(x: CGFloat(-75*(index%4)),
                           y: CGFloat(-75*Int(index/4)),
                           width: 300, height: 300))
            piece.draw(in: CGRect(x: 0, y: 0, width: 75, height: 75))
            let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
            UIGraphicsEndImageContext()
            return makeImageView(frame: frame, image: image)
        }
    
        //イメージボタンの生成
        func makeButton(frame: CGRect, image: UIImage, tag: Int) -> UIButton {
            let button = UIButton(type: UIButton.ButtonType.custom)
            button.frame = frame
            button.setImage(image, for: UIControl.State.normal)
            button.tag = tag
            button.addTarget(self, action: #selector(onClick(sender:)),
                             for: UIControl.Event.touchUpInside)
            return button
        }
    
        //====================
        //タッチイベント
        //====================
        //ボタンクリック時に呼ばれる
    
        @objc func onClick(sender: UIButton) {
            if sender.tag == START {
                //シャッフルの実行
                shuffle = 20
                while shuffle > 0 {
                    if movePiece(tx: rand(num: 4), ty: rand(num: 4)) {shuffle -= 1}
                }
                for i in 0..<16 {
                    let dx: CGFloat = 30+75*CGFloat(i%4)
                    let dy: CGFloat = 180+75*CGFloat(i/4)
                    piece[data[i]].frame =
                        CGRect(x: dx, y: dy, width: 75, height: 75)
                }
    
                //ゲーム開始
                titleLabel.text = "Dog Puzzle"
                piece[15].alpha = 0
                startButton.alpha = 0
            }
        }
    
        //タッチ開始時に呼ばれる
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            if startButton.alpha != 0 {
                return
            }
            //タッチ位置からピースの列番号と行番号を求める
            let pos = touches.first?.location(in: gameView)
            if 30 < pos!.x && pos!.x < 330 && 180 < pos!.y && pos!.y < 480 {
                let tx = Int((pos!.x-30)/75)
                let ty = Int((pos!.y-180)/75)
                movePiece(tx: tx, ty: ty)
            }
        }
    
        //ピースの移動
        func movePiece(tx: Int, ty: Int) -> Bool {
            //空きマスの行番号と列番号を求める(4)
            var fx = 0
            var fy = 0
            for i in 0..<16 {
                if data[i] == 15 {   //　←ここで出てくる配列のエラーを解決したいです。
                    fx = i%4
                    fy = Int(i/4)
                    break
                }
            }
            if (fx == tx && fy == ty) || (fx != tx && fy != ty) {
                return false
            }
    
            //ピースを上にスライド
            if fx == tx && fy < ty {
                for i in fy..<ty {
                    data[fx+i*4] = data[fx+i*4+4]
                }
                data[tx+ty*4] = 15
            }
                //ピースを下にスライド
            else if fx == tx && fy > ty {
                for i in stride(from:fy, to: ty, by: -1) {
                //エラー発生
                    data[fx+i*4] = data[fx+i*4-4]
                }
                data[tx+ty*4] = 15
            }
                //ピースを左にスライド
            else if fy == ty && fx < tx {
                for i in fx..<tx {
                    data[i+fy*4] = data[i+fy*4+1]
                }
                data[tx+ty*4] = 15
            }
                //ピースを右にスライド
            else if fy == ty && fx > tx {
                for i in stride(from:fy, to: tx, by: -1) {
                //エラー発生
                    data[i+fy*4] = data[i+fy*4-1]
                }
                data[tx+ty*4] = 15
            }
    
            //シャッフル時はピースの移動アニメとクリアチェックは行わない
            if shuffle > 0 {
                return true
            }
    
            //ピースの移動アニメとクリアチェック
            var clearCheck = 0
            for i in 0..<16 {
                let dx: CGFloat = 30+75*CGFloat(i%4)
                let dy: CGFloat = 180+75*CGFloat(i/4)
    
                //ピースの移動のアニメ
                if data[i] != 15 {
                    UIView.beginAnimations("anime0", context: nil)
                    UIView.setAnimationDuration(0.3)
                    piece[data[i]].frame = CGRect(x: dx, y: dy, width: 75, height: 75)
                    UIView.commitAnimations()
                } else {
                    piece[data[i]].frame = CGRect(x: dx, y: dy, width: 75, height: 75)
                }
    
                //クリアチェック
                if data[i] == i {clearCheck += 1}
            }
    
            //ゲームクリア判定
            if clearCheck == 16 {
                titleLabel.text = "Clear!"
                startButton.alpha = 100
    
                //ピースの出現アニメ
                UIView.beginAnimations("anime1", context: nil)
                UIView.setAnimationDuration(0.6)
                piece[15].alpha = 100
                UIView.commitAnimations()
            }
            return true
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
        func rand(num: UInt32) -> Int {
            return Int(arc4random()%num)
        }
    
    }
    
[/code]


CC BY-SA 4.0
2020-03-06T13:19:11.750
63632
2020-03-07T05:09:49.547
2020-03-07T05:09:49.547
3060
38107
question
swift array
Swiftパズルアプリ作成　Fatal error: Index out of range


ご提示のコード12行目：

[code]

    var data = [Int]()
    
[/code]

このArray型のイニシアライザは、要素の型を`Int`とし、 **要素数0** の配列を生成します。  
要素数0ですから、たとえインデックス0の要素を参照しようとしても、「out of
range」のエラーが起きます。また、ご提示のコードのどこを見ても、配列`data`の要素数を16に拡大するコードが見当たりません。

[Swift Standard library | Array | Initializer
init(repeating:count:)](https://developer.apple.com/documentation/swift/array/1641692-init)

配列`Array`の生成時に、あらかじめ要素数を指定できるイニシアライザを使えば、とりあえずエラーは出なくなります。ご提示のコード12行目を、

[code]

    var data = [Int](repeating: 0, count: 16)
    
[/code]

としてみてください。


CC BY-SA 4.0
2020-03-06T17:11:25.430
63635
2020-03-06T17:11:25.430
18540
63632
answer


コメントで得られた情報から、参考にされたのは以下の記事であると判断しました。

  1. [Swiftで行こう！--パズルゲーム(16パズル)](https://note.com/dngri/n/n54981e48e2e6)
  2. [Swiftで行こう！--パズルゲーム(16パズル)2](https://note.com/dngri/n/n27fe677e51bb)
  3. [Swiftで行こう！--パズルゲーム(16パズル)まとめ!](https://note.com/dngri/n/nd1dfd430b208)

関連記事も書かれているのかもしれませんが、リンクもありませんし、記事通りにコードを編集していけばアプリが完成するなんて構成でも無いですし、お世辞にも「初心者向けによくできた入門記事」とは言えませんね…。普通こんなコード書かないよ、なんて部分もありますし。

(「"iPhone/iPadプログラミングバイブル
Swift/iOS8/Xcode6対応"より」と明記してあるので、書籍をお持ちの方が並行してみるための解説記事として書かれたモノなのでしょう。「普通こんなコード書かないよ」が書籍側の問題なのかどうかはわかりませんが…。)

* * *

まず、実行時エラーが出た根本的な原因は、空っぽ(要素数が0)の配列をアクセスしようとしていることです。したがってとりあえず要素数が16になるように初期化すれば表題のクラッシュは防げるのですが、これではまったく意味の無い動作をするだけになります。

`data`(元記事では`_data`、Swiftではアンダーラインスタートの識別子なんて滅多に使いませんから、`_`を省いてしまったと言うのはいい判断だと思います)の初期化については、記事中には書かれていないようです。

これは「ピース配置情報」と言うことですから、どの位置(0〜15で表される)にどのピース(やはり0〜15)が存在するか、を表している情報のようです。最初は0〜15のピースを順番に割り振ればいいでしょうから、初期化はこんな風にかけます。

[code]

        var data: [Int] = Array(0..<16) //ピース配置情報
    
[/code]

このように初期化することで、`data[0] = 0`, `data[1] = 1`, ... , `data[15] =
15`となる要素数16の配列で初期化されます。

* * *

次に`piece`(元記事では`_piece`)の方ですが、これは元記事中に配列の中身を設定する処理が記載されているのに、あなたのコードには取り込まれていないようです。

[code]

            let picture = UIImage(named: "itimai.png")!
            let bunkatu = UIImage(named: "bunkatu.png")!
    
            for i in 0..<16 {
                 piece.append(makePieceImageView(frame: CGRect(
                     x: CGFloat(30+(i%4)*75),
                     y: CGFloat(180+Int(i/4)*75),
                     width: 75, height: 75),
                     index: i, picture: picture, piece: bunkatu))
                 gameView.addSubview(piece[i])
            }
    
[/code]

(変数名はあなたのコードに合わせて書き換えてあります。ちなみに`bunkatu.png`には半透明の画像を用意しないといけないようですね。)

`data`の初期化とはやり方が違いますが、16回`append`が呼ばれることで、`piece`の方も要素数が16の配列になります。

上記のコードは、`viewDidLoad()`の最後にでも付け足してみてください。

* * *

ざっと動かしてみた感じでは、他にもまだまだ直さないといけないところはあるようですが、これでFatal error: Index out of
rangeが出てアプリがクラッシュすることはなくなるはずです。

わからないところや関係なさそうなところを飛ばしながら、コードを追加しては試すと言うことをしておられるだろうと思いますが、こんな風に「配列の中身を設定する」部分をすっ飛ばしてしまう(まぁ、記事側には書かれていないので仕方ないかもしれませんが)と意味不明のエラーに悩まされてしまうことも出てきます。

似たような趣旨のチュートリアルはよく探せば見つかりそうに思います。できれば「書籍だけで完結」「サイトだけで完結」した最新のチュートリアルを見つけられた方が、学習が捗るのでは無いかと思います。


CC BY-SA 4.0
2020-03-07T05:02:50.917
63643
2020-03-07T05:02:50.917
13972
63632
answer