もりさんのプログラミング手帳

教えることは、二度学ぶこと

スポンサーリンク

【連載】VBAでナンプレ(数独)を解いてみよう①<全体処理>

VBAでナンプレを解いてみようシリーズ【第1回目】です。

f:id:excel-accounting:20180511135041p:plain

ナンプレとは

これはご存知の方も多いのでさらっとおさらいです。

<ルール>
・空いているマスに1~9のいずれかの数字を入れる。
・縦、横、3×3のブロック内に同じ数字が入ってはいけない。

※「数独」という呼称は「数字は独身に限る(すうじはどくしんにかぎる)」の略語で、日本のパズル制作会社ニコリよって商標登録されているそうです。(Wikipediaより)

この連載について

1.対象読者

  • VBA入門・初級者の人
  • VBAに興味をもっている人
  • VBAが好きな人
  • パズルゲームが好きな人
  • VBAがきらいな人


2.この連載企画で伝えたいこと

『VBAって興味あるけど難しそう』

『仕事のために仕方なく勉強している』

『VBAの未来が見えない』

という方々に、

VBAってこんなこともできるんだよ!

という可能性やおもしろさ

お伝えできればと思っています。


『そうはいっても、こういうのってプログラマの人が作るんでしょ(自分にはムリ…)』と思っていませんか?


筆者は文系出身ノンプログラマーです。

f:id:excel-accounting:20180424002011p:plain:w200


「VBAは誰でも簡単にできる」とまでは言えませんが、勉強次第でこんなおもしろいマクロも作れるんですよ。


ですので、


完成したマクロを単に披露するのではなく、連載を通して「人の思考をどうやってVBAで実装しているか」「ノンプログラマーの視点」で、初級者の方にもわかりやすいよう、図解盛りだくさんで解説していきますね。


3.おことわり
この連載企画で作成するマクロは「総当たりで数字をあてはめて解答を決定する」というコンピュータ的な解き方ではなく

人間(筆者)がナンプレを解く時の考え方をプログラムにしたものです。

解き方の概要

連載を通して、下記の3つの解き方をプログラムにします。

【解法1】数字探し

そのセルに入る「数字」をさがす

f:id:excel-accounting:20180527185157p:plain:w400

【例題】

A1セルに入る数字を探します。

  • 「3×3のエリア」「行」「列」には5以外の数字がすべて存在している

よって、A1セルには数字の5しか入らないことがわかります。

f:id:excel-accounting:20180618151802p:plain:w350

【解法2】セル探し(エリア編)

「3×3のエリア」を検索範囲として、数字のxが入る「セル」をさがす(x=1~9)

f:id:excel-accounting:20180527185211p:plain:w450

【例題】

  • 赤枠の「3×3のエリア」には空白セルが4個あり、2,5,7,8のいずれかが入る
  • A2セル・C2セル:同じ【行】に数字の5が存在する
  • B3セル:同じ【列】に数字の5が存在する

以上より、数字の5はA1セルにしか入らないことがわかります。

f:id:excel-accounting:20180618151642p:plain:w350

【解法3】セル探し(行列編)

「行」および「列」を検索範囲として、数字のxが入る「セル」をさがす(x=1~9)

【行検索の例題】

  • 1行目には空白セルが4個あり、1,4,6,7のいずれかが入る
  • D1セル・F1セル:同じエリアに数字の1が存在する
  • G1セル:同じ列に数字の1が存在する

以上より、数字の1はA1セルにしか入らないことがわかります。

f:id:excel-accounting:20180530205023p:plain:w350


【列検索の例題】

こちらも行検索と同じ考え方で、数字の1はA1セルにしか入らないことがわかります。

f:id:excel-accounting:20180614224406p:plain:w350


このような3つの解き方で進めていきます。

全体処理

まずは、マクロ全体に関わる処理を作成していきます。

[全体処理]とは、家を建てる場合の「全体の骨組み」のイメージです。
f:id:excel-accounting:20180531230909p:plain:w250

イメージづくり

全体像のイメージを作っていきましょう。

  • イメージを描いて、
  • それを言葉にして、
  • プログラムを書く。

この「イメージづくり」がプログラムを作るうえで大事になってきますよ。

エクセルシートの縦横9×9セルに問題を作成する

A1セル~I9セルをパズルエリアとして、問題を入力します。

f:id:excel-accounting:20180527184234p:plain:w350


A1セル~I9セルを順番にみていき、空白セルに入る数字を探す。

f:id:excel-accounting:20180527184256p:plain:w350

答えの数字が見つかったらセルに書き込みます。


空白セルがなくなるまで答え探しを繰り返す

A1セル~I9セルのチェックを「1周」として、空白セルがなくなる=パズルが解けるまで、答え探しを繰り返します。

1周終了した時点で9×9エリアに空白セルが残っているかをチェックし、

まだ空白セルがあれば、もう一度A1セルから「2周目」を繰り返します。
f:id:excel-accounting:20180519230600p:plain:w250
以降、3周目・4周目・・・と続く。

すべてのセルが埋まったら、パズル完成です。
f:id:excel-accounting:20180519230737p:plain:w200

ただし、パズルが難しくて解けない場合を想定した「お手上げ判定」の処理も作ります。

f:id:excel-accounting:20180519231326p:plain:w200

フローチャート(ざっくり版)

ここまでのイメージをフローチャートにします。
※細かい処理は省略しています

f:id:excel-accounting:20180520172942p:plain:w320


★ポイント★が「お手上げ判定」です。

下記の①と②を比較して個数が同じである場合、

  • ①ループ2開始前の空白セルの個数
  • ②ループ2終了後の空白セルの個数

つまり、解法1~3で1セルも埋められなかった場合に「お手上げ」と判定します。

モジュール関連図

全部で4つのモジュールを作成します。

構成は下記図のとおりです。

f:id:excel-accounting:20180531230927p:plain:w400

変数

マクロ全体を通じてよく使用する変数を先に定義しておきます。

今後たくさん登場するので、しっかり頭に入れておきましょう。

【イメージ】
f:id:excel-accounting:20180531162017p:plain

【定義】 ※r,c,numはパブリック変数とします。

変数名
データ型
意味
PuzzleArea
Rangeオブジェクト パズル全体の9×9のエリア
BlockArea
Rangeオブジェクト PuzzleAreaの中の3×3エリア
r
Long型 9×9エリアの【行番号】
c
Long型 9×9エリアの【列番号】
num
Long型 1~9の数字

プログラム作成

今回は、フローチャート赤枠の[全体処理]を作成します。

f:id:excel-accounting:20180520210101p:plain:w350


これらはすべて共通モジュールに記述します。
f:id:excel-accounting:20180531225913p:plain:w250

まずはチャートに沿って、処理の単位で細かくコードを書いていきます。


【ループ1】9×9エリアがすべて埋まるまで繰り返す

Dim PuzzleArea As Range
Set PuzzleArea = Range("A1:I9")

Do
    '~処理~
Loop Until WorksheetFunction.CountBlank(PuzzleArea) = 0

Excelシート上の縦横9×9のパズル範囲を変数PuzzleAreaにセットします。

ワークシート関数CountBlankで空白セルを数えて、0になるまで繰り返します。

  • 0の場合→解読終了!
  • 0でない場合→次の周回にいきます。

f:id:excel-accounting:20180519230600p:plain:w200


9×9エリアの空白セル(①)をカウント

Dim BlankCnt1 As Long 'ループ2開始前の空白セル数
BlankCnt1 = WorksheetFunction.CountBlank(PuzzleArea)

単純に空白セルがなくなるまでループする条件だと、パズルが解けなくなった場合でも無限ループします。

これを防ぐために「お手上げ判定」処理を作成します。

【ループ2】開始前の空白セルをカウントして変数に格納しておきます。


【ループ2】9×9エリアのセルを順番に処理する

For r = 1 To 9 '【行】
    For c = 1 To 9 '【列】
        '~解読処理~
    Next c
Next r

For~Next文を2つ入れ子にして、

  • r=1行目~9行目
  • c=1列目~9列目

で、A1セル~I9セルを順番にみていきます。


9×9エリアの空白セル(②)をカウント

Dim BlankCnt2 As Long 'ループ2終了後の空白セル数
BlankCnt2 = WorksheetFunction.CountBlank(PuzzleArea)

【ループ2】終了後の空白セルをカウントして変数に格納します。
次のお手上げ判定で使用します。


空白セル①と②の個数が等しいか?

If BlankCnt1 = BlankCnt2 Then
    MsgBox "お手上げ!終了します。"
    Exit Sub
End If

これが「お手上げ判定」処理です。

[ループ開始前の空白セル]と[ループ終了後の空白セル]の個数が等しい場合、ループ内で1セルも埋められなかったことを意味します。
これ以上処理を繰り返しても意味がないので、マクロを終了します。

スポンサーリンク


本日のまとめ

共通モジュールの[全体処理]のプログラムです。

' ------------------------
' * 共通モジュール
' ------------------------
Public r As Long, c As Long '現在位置の行列番号
Public num As Long '1~9の数字

Sub VBAでナンプレを解く() '全体処理

    Dim PuzzleArea As Range
    Set PuzzleArea = Range("A1:I9")
    
    Do '【ループ1】開始
    
        Dim BlankCnt1 As Long 'ループ2開始前の空白セル数
        BlankCnt1 = WorksheetFunction.CountBlank(PuzzleArea)

        For r = 1 To 9 '【ループ2】開始
            For c = 1 To 9

            '--------------------------------------
            'ここで解読処理(解法1~3)
            '--------------------------------------
            
            Next c
        Next r
        
        Dim BlankCnt2 As Long 'ループ2終了後の空白セル数
        BlankCnt2 = WorksheetFunction.CountBlank(PuzzleArea)
        
        If BlankCnt1 = BlankCnt2 Then
            MsgBox "お手上げ!終了します。"
            Exit Sub
        End If
    
    Loop Until WorksheetFunction.CountBlank(PuzzleArea) = 0 '【ループ1】
    
End Sub

次回予告

次回は、解法1~3で使用する「共通部品」を作成していきます。

f:id:excel-accounting:20180521133556p:plain:w400

最後に

引き続き連載を読んでくださるはてなブロガーの方はぜひ読者になってくださいね!

ツイッターでもブログ更新のツイートをしているので、こちらもフォローしてくれると嬉しいです。

第2回目へつづきます
VBAでナンプレを解いてみよう②

スポンサーリンク