ナンプレ(数独)
ナンプレとは、ナンバープレース、数独とも呼ばれる数字パズルですね。
数独という呼び方は、「数字は独身に限る」の略なんだそうです。
ExcelVBAでナンプレをつくる
ナンプレの問題を作るには、縦・横・ブロックに重複しない数字を当てはめていく必要があります。
以下のロジックで考えていきます。
- 9×9配列を用意する
- ランダムな1~9の数字を取得する
- 乱数から整数を生成する
- 一度使った数字を避ける(重複)
- 列,行,3×3ブロックで重複した数字かチェックする
- 「絶対に当てはまらない数字」の場合
- 配列に格納する
- Excelに張り付ける
1.9×9配列を用意する
まずは盤面数字を格納する9×9(81マス)の配列を準備します。
Dim num(8, 8) As Integer
(8, 8) の配列を宣言すればOKですね。
(0~8, 0~8)の位置に数字を入れる動作を81回動かします。
num(i, ,j)として考えていきましょう。
2.ランダムな1~9の整数を取得する
2.1 乱数から整数を生成する
Rnd関数とInt関数の組み合わせでランダム数字を発生させることができます。
Rnd : 0~1未満の数字をランダムに発生させる
Int: 数字を丸めて整数を取得する
1 2 |
Dim rnd_num As Integer 'ランダムな整数を格納する変数 rnd_num = Int(Rnd * 9) |
「Rnd*9」は 最大でも「 0.999…9 * 9」=「8.999…1」 となるのでInt関数で小数点以下を切り捨てられると8以上の数字にはなりません。
上記だと0~8が格納されます。
重複を避ける方法として、次項では配列arr(0~8)から1~9の整数を選びます。
生成するランダムの整数は、この配列のindex番号として使います。
2.2 一度使った数字を避ける(重複)
1~9の数字を配列arr()に配置しておき、一度使った数字が出ないようにします。
配列を減らしながら処理するため、ランダムで生成されるindex番号の最大値も減らす必要があります。
次の手順で配列を減らします。
また、UBound(),LBound()関数でランダムindex番号の範囲を指定します。
①使用した配列indexに最後尾の整数を入れる
②最後尾の配列を削除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
Dim arr() As Integer Dim maxint() As Integer Dim rnd_index as integer maxint = 9 ’マスの最大値 ReDim arr(maxint - 1) As Integer ’配列arr(0~8)に整数1~9を格納 For n = 0 To maxint - 1 arr(n) = n + 1 Next 'ランダム整数取得 Randomize '乱数のリセット rnd_index = Int(Rnd * (UBound(arr) - LBound(arr) + 1) + LBound(arr)) rnd_num = arr(rnd_index) ’------3.列・行・ブロックのチェックの処理------ ’------4.配列num()への格納の処理------ arr(rnd_index) = arr(UBound(arr)) ’①使用した配列indexに最後尾の整数を入れる If UBound(arr) = 0 Then ’最後の1つは飛ばしましょう。 Else ReDim Preserve arr(UBound(arr) - 1) ’②最後尾の配列を削除 End If |
3.列・行・3×3ブロックで重複した数字をチェックする
ナンプレのルールに沿った処理です。
マスの縦・横・3×3ブロック内に同じ数字が存在してはいけません。
現在位置(i, j)と入力候補数字rnd_numから、重複チェックの結果をTrue/Falseで返すFunction関数を作っておきます。
Falseが返ってくる場合は、数字を再度生成する必要があります。
Gotoしましょう。
列のチェックと行のチェックはほぼ同じになりますね。
1 2 3 4 5 6 7 8 9 10 11 |
’行のチェック Function c_c(j As Integer, rnd_num As Integer) As Boolean For n = 0 To 8 If num(n, j) = rnd_num Then c_c = False Exit Function Else c_c = True End If Next End Function |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
’ブロックのチェック Function b_c(i As Integer, j As Integer, rnd_num As Integer) As Boolean For m = 0 To 2 For n = 0 To 2 If num((i \ 3) * 3 + n, (j \ 3) * 3 + m) = rnd_num Then b_c = False Exit Function Else b_c = True End If Next Next End Function |
現在位置を正しく捉えないと、異なる範囲をチェックしてしまいます。
位置(i, j)を3で割った商をブロック位置として見ていきます。
例えばi=7,j=4の時、変数の中身はこんな風になっています。
arr(0),arr(1)の値が次の候補値ですが、ランダムIndexが0で生成された時は
行のチェックで1が重複してFalseが発生するはずです。
3.1 「絶対に入らない数字」の場合
ここまでの処理では、入力可能な数字選べたら次に進むようになっていますが、
後半の処理(右下側)になるほどチェックが厳しくなります。
残りの数字が全て当てはまらない場合は、ループを繰り返すことになります。
数字は9種類しかないため、10回目の処理に入ったら今いる列の入力をリトライさせます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Dim try As Integer retry: '----ランダム数字生成処理---- try = try + 1 If try > 9 Then '10回目でダメなら列をリセット For n = 0 To maxint - 1 num(i, n) = 0 Next GoTo retry End If |
全容
説明する項目ごとにコードを記載しましたが、全容はこんな感じです。
あとは17個以上残して、空欄にすれば問題の出来上がりです。
消すところはまた作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
'①9*9配列を作る Dim num(8, 8) As Integer '9*9配列 Dim n As Integer, m As Integer, i As Integer, j As Integer 'loop変数 Sub make_number_place() Dim arr() As Integer Dim rnd_num As Integer Dim rnd_index As Integer Dim maxint As Integer Dim clm As Boolean, rw As Boolean, block As Boolean Dim try As Integer maxint = 9 'マスの最大値 For i = 0 To maxint - 1 retry: 'loop回避用(列のリトライ) try = 0 '②ランダムに数字入力 '配列arr(0~8)に整数1~9を格納 ReDim arr(maxint - 1) As Integer For n = 0 To maxint - 1 arr(n) = n + 1 Next For j = 0 To 8 remake: try = try + 1 If try > 9 Then '10回目でダメなら列をリセット For n = 0 To maxint - 1 num(i, n) = 0 Next GoTo retry End If 'ランダム整数取得 Randomize rnd_index = Int(Rnd * (UBound(arr) - LBound(arr) + 1) + LBound(arr)) rnd_num = arr(rnd_index) '③列、行、ブロックの重複チェック true/false clm = c_c(j, rnd_num) If clm = False Then GoTo remake End If rw = r_c(i, rnd_num) If rw = False Then GoTo remake End If block = b_c(i, j, rnd_num) If block = False Then GoTo remake End If '④配列に格納 num(i, j) = rnd_num arr(rnd_index) = arr(UBound(arr)) If UBound(arr) = 0 Then Else ReDim Preserve arr(UBound(arr) - 1) End If Next Next Range("A1:I9") = (num()) MsgBox ("数字入力完了") End Sub ’行のチェック Function c_c(j As Integer, rnd_num As Integer) As Boolean For n = 0 To 8 If num(n, j) = rnd_num Then c_c = False Exit Function Else c_c = True End If Next End Function ’列のチェック Function r_c(i As Integer, rnd_num As Integer) As Boolean For n = 0 To 8 If num(i, n) = rnd_num Then r_c = False Exit Function Else r_c = True End If Next End Function ’ブロックのチェック Function b_c(i As Integer, j As Integer, rnd_num As Integer) As Boolean For m = 0 To 2 For n = 0 To 2 If num((i \ 3) * 3 + n, (j \ 3) * 3 + m) = rnd_num Then b_c = False Exit Function Else b_c = True End If Next Next End Function |
次の展望は
・問題を完成させる
・問題を解く
・Pythonで同じ処理を作る
→比較