ナンプレ bug対策完全ガイド:原因と修正ポイントを初心者でも分かるように徹底解説しよう

はじめに
数独(ナンプレ)ゲームを作るとき、数百行にわたるコードの中でわずかなミスや設計の不備が、思わぬ動作不具合(bug)を引き起こします。
初心者が「何が問題なのかわからない」「何を修正すればよいのかわからない」という壁に直面しがちです。そこで今回は、数独プレイヤーの基本的な構造から、発生しやすいバグの原因、そしてそれらをどのように発見・修正できるかを、コード例とともに分かりやすく解説します。


数独プログラムの基本構造

数独を実装するときは、主に3つの要素に分けて考えると管理しやすいです。

要素 主な役割 典型的なコード例
1. データ構造 9×9 の盤面、固定セルかどうか、候補リストなどを保持 int[][] board = new int[9][9]; boolean[][] fixed = new boolean[9][9];
2. ルールチェック 入力がルールに従っているか(同じ行・列・3×3ブロックに重複がないか)を検証 boolean isValid(int r, int c, int val)
3. UI/UX ユーザーインタフェース、入力・出力ロジック Scanner sc = new Scanner(System.in);

この3要素が相互に依存し合うため、どこかに不整合が生じると全体が崩れます。


よくあるバグとその原因

1. 値の範囲チェックミス

  • 原因:0〜9 の範囲(または1〜9)を意識していない。
  • 典型的事例

    if(value < 1 || value > 9) { /* error */ }
    // ここに value == 0 のケースが入ってきてしまう
    
  • 対策

    • すべての入力操作で範囲チェックを必ず実装する。
    • 可能なら、配列のインデックスを直接使用せず、専用メソッド(isValidValue)でチェックする。

2. 3×3 ブロックのインデックス計算ミス

  • 原因(row / 3) * 3 + col / 3 と書くのを忘れたり、余計に余計な計算をしたり。
  • 典型的事例

    int block = (row / 3) * 3 + (col / 3); // 正しい
    int wrongBlock = (row / 3) + (col / 3); // 1×1 つのブロックに見えてしまう
    
  • 対策

    • テストケース:各ブロックのコーナーとなるセルを入力して確認。
    • 計算ロジックを ユーティリティ へ分離し、複数の場所から呼び出せるようにする。

3. 固定セルの変更を許可してしまう

  • 原因fixed 配列の更新漏れ、もしくは入力時のチェック不足。
  • 典型的事例

    if(infix[r][c]) return false; // けど infix ではなく fixed と誤記
    
  • 対策

    • 入力前に必ず isFixed(r, c) を呼び、固定セルなら 再入力不可 と返す。
    • 変更可否を関数でカプセル化し、単一責任化。

4. ループの境界条件ミス

  • 原因for(int i=0;i<9;i++) と正しく書くのに、i<=9 としてしまう。
  • 典型的事例

    for(int row=0; row<9; row++) {
        for(int col=0; col<9; col++) {
            // 正常な処理
        }
    }
    

    修正前col<=9 だと 10 列目にアクセスして ArrayIndexOutOfBoundsException

  • 対策

    • IDE の自動補完 を利用して境界条件を必ず 0 <= idx < SIZE に統一。
    • Javadoc@paramにサイズ制限を明記し、ドキュメント化する。

5. 盤面のコピーミス(シャローコピー)

  • 原因int[][] のコピー時に参照コピーをしてしまい、元盤面に影響が出る。
  • 典型的事例

    int[][] clone = board; // 参照コピー
    
  • 対策

    • Arrays.copyOfclone を使うときは、2 次元配列なので各行もコピー。
    int[][] copy = board.clone();
    for (int i = 0; i < copy.length; i++) {
        copy[i] = board[i].clone();
    }
    

バグを見つけるための実践的手順

ステップ やること ツール / コード例
1. ユニットテスト それぞれの機能(isValidValue, isFixed, isBlockValid など)を単体でテスト JUnit 5, assertTrue, assertFalse
2. ログ出力 主要処理箇所で変数の状態を出力 System.out.println("row="+row+",col="+col+",val="+val);
3. デバッグ IDE のブレークポイントを活用 Debugger のステップ実行
4. 入力妥当性確認 すべての入力パスで必ず null や invalid チェック `if (value == null
5. テストケースの網羅性 9×9 の盤面をランダムに生成し、ルール違反を検知 Random board generator, for loops

例:JUnit での簡単なテスト

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

public class SudokuTest {

    @Test
    void testValidRow() {
        Sudoku su = new Sudoku();
        su.setCell(0, 0, 5);
        su.setCell(0, 1, 3);
        su.setCell(0, 2, 5); // 重複

        assertFalse(su.isRowValid(0),
            "Row 0 should be invalid due to duplicate 5");
    }

    @Test
    void testFixedCell() {
        Sudoku su = new Sudoku();
        su.setFixed(4, 4, 7);
        su.setCell(4, 4, 9); // 変更しようとする

        assertEquals(7, su.getCell(4, 4),
            "Fixed cell should remain unchanged");
    }
}

よくある修正ポイント(箇条書き)

問題 修正内容 具体例
固定セルを変更できてしまう 入力前に isFixed() で判定 if(isFixed(r,c)) { System.out.println("変更不可"); }
3×3 ブロックチェックの誤算 getBlock(row, col) を作成 private int getBlock(int r, int c){ return (r/3)*3 + c/3; }
盤面のコピーでシャローコピー 深いコピー を実装 先ほどのコードスニペット
ルート計算で >=9 の条件ミス ループ境界を統一 for(int i=0; i<9; i++)
値の範囲チェックで 0 を忘れる 1〜9 のチェックを必ず `if(val < 1

デバッグ時に役立つヒント

  1. 小さくテストケースを作成

    • 1 行だけ入力し、正しく判定されるか確認。
    • 特定のブロックで重複をテスト。
  2. エラーメッセージに詳細情報を含める

    • 何が原因かを即座に把握できる。
    • 例: Error: row 3, col 5 already has value 2.
  3. ステップ実行で状態を確認

    • board[r][c] の値、fixed[r][c] の状態を確認。
    • ブレークポイントで変数をフォーカス。
  4. コードレビューを行う

    • 他者の目でバグが発見されやすい。
    • 特にインデックス計算や境界チェックは見逃しやすい。
  5. ログレベルを変更

    • 本番では INFO、デバッグ時は DEBUG
    • コンソールやファイルに出力される内容を制御。

まとめ:成功へのチェックリスト

  1. 入力検証

    • 範囲:1〜9
    • 固定セルチェック
  2. ルールチェック(行・列・ブロック)

    • isRowValid, isColumnValid, isBlockValid を統一的に呼び出す。
  3. 盤面のコピー

    • 深コピーを実装し、状態を保持。
  4. テスト

    • JUnit で単体テストを作成。
    • ランダムケースでプレッシャーテスト。
  5. デバッグ

    • ブレークポイント+ロギングで状態を追跡。

初心者は「コードを書きすぎている」「すべてを一度に扱おうとした」ことがバグの主因です。
「小さく、一度に一つのことだけを正しく動かす」 という設計思想を守れば、バグを見つけやすく、修正もしやすくなります。

数独ゲームを作る過程で出てくるバグは、プログラミングのロジックとデバッグ力を鍛える絶好の機会です。
本ガイドを参考に、まずは簡単なルールチェックから実装をスタートし、徐々に機能を増やしていくことで、安定した数独アプリを完成させてください。

コメント

タイトルとURLをコピーしました