はじめに (対象読者・この記事でわかること)
この記事は、Swiftでパズルゲームやボードゲームを作ろうとしているが「Fatal error: Index out of range」で進められなくなっている方を対象にしています。
記事を読むと、配列の範囲外アクセスがなぜ起きるのか、どう防ぐべきなのかが一目でわかり、即座に使える「安全な配列アクセス」の実装パターンを3つ身につけられます。さらに、パズル特有の「スワップ」「消去」「詰め込み」処理でよく落ちるポイントをサンプル付きで解説するので、同じエラーで詰まる時間がゼロになります。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- Swiftの基本的な文法(変数・配列・for文)
- オプショナル型の扱い(if let や guard let)
- パズルゲームのルール(落ちもの、スワップ、3マッチなど)のいずれかを知っているとイメージしやすい
なぜパズルゲームは「Index out of range」が多いのか
パズルゲームは2次元配列([[Tile]])でボードを表現するのが定番です。スワップや消滅、重力処理を行うとき、隣マスや上下のマスを参照しますが、端や端に近いマスでは存在しないインデックスにアクセスしてしまい、クラッシュします。
例えば (row, col) = (0, 0) のマスで左隣を見ようとすると col - 1 = -1 となり、即死です。
この「境界値を見逃す」ことがパズルゲームで最も多いクラッシュ原因です。
安全な配列アクセスを実装して即解決する
ステップ1:安全なサブスクリプトを用意する
まず、配列に「範囲外ならnilを返す」サブスクリプトを追加します。
Swiftextension Collection { /// 安全なアクセス。indexが範囲外ならnil subscript(safe index: Index) -> Element? { indices.contains(index) ? self[index] : nil } }
これだけで array[safe: index] という書き方が使えるようになります。
ステップ2:パズルボードで使う実装例
2次元配列でも同じように拡張できます。
Swiftextension Collection where Element: Collection { /// 2次元配列用安全アクセス subscript(safe row: Int, col: Int) -> Element.Element? { guard row >= 0, col >= 0 else { return nil } guard let rowArray = self[safe: row] else { return nil } return rowArray[safe: col] } } // 使用例 if let tile = board[safe: 0, -1] { print(tile) } else { print("範囲外なので無視") }
ステップ3:スワップ処理を安全に書き換える
従来の危険なコード:
Swift// ❌ クラッシュする例 let target = board[row][col + 1] // col+1が範囲外のとき死ぬ
安全なコード:
Swift// ✅ 安全な例 guard let target = board[safe: row, col + 1] else { return }
ハマった点:「nil合并演算子 ??」で誤魔化すと無限ループが潜む
最初は board[safe: row, col + 1] ?? Tile.empty のように空タイルを返してしまい、境界で空タイルをスワップし続けて無限ループする事例がありました。
「nil = 存在しない」という扱いを徹底し、早期リターンするのがコツです。
解決策:防御的プログラミング3選
- 先に範囲チェック関数を作る
Swiftfunc isInside(row: Int, col: Int) -> Bool { return 0 <= row && row < rows && 0 <= col && col < cols }
- for文の端まで考える
Swiftfor row in 0..<rows { for col in 0..<cols { // 上下左右を見る for (dr, dc) in [(-1,0),(1,0),(0,-1),(0,1)] { let nr = row + dr, nc = col + dc guard isInside(row: nr, col: nc) else { continue } // ここで安全にアクセス } } }
- UnitTestで境界値を網羅する
Swiftfunc testAccessAtCorner() { let board = Board(rows: 3, cols: 3) XCTAssertNil(board[safe: -1, 0]) XCTAssertNil(board[safe: 0, 3]) XCTAssertNotNil(board[safe: 2, 2]) }
まとめ
本記事では、Swiftでパズルゲームを作る際の「Index out of range」を防ぐための安全な配列アクセス手法を解説しました。
- 配列に
safe:サブスクリプトを追加するだけで範囲外アクセスが即排除できる - 2次元配列でも同様に拡張し、ボードアクセスを
board[safe: row, col]として統一する - 防御的プログラミング(範囲チェック、早期continue、UnitTest)を3セット導入することで、クラッシュゼロを維持しやすくなる
この記事を通して、パズルゲーム開発で最も時間を奪うクラッシュを事前に防ぎ、ルール実装に集中できる環境が手に入りました。
次回は、安全なアクセスを前提に「連鎖消滅」「重力落下」「連鎖評価」を効率的に実装する方法を取り上げます。
参考資料
- Swift公式ドキュメント:Collection
- Hacking with Swift:How to safely index arrays
- 書籍『Swift実践入門』(技術評論社)
