父親のパズル好きが遺伝したのか、ちょっとした暇つぶしにパズルを楽しむことは少なくありません。ただ本格的にハマっていると言う程ではないので、何ヶ月も前に買ったパズル雑誌で手を付けていない問題を見付けてはチョコチョコいじくる程度です。お絵かきロジックは、特にサイズの大きな問題が面倒で、Excelのセルをマス目に見立てて計算で解答を得られないか、と思い付くのは自然の成行きでした。実際に開発を始めてみと、思った程に単純ではないことを知り、少々手こずりましたが、何とかカタチになったので公開してみました。このページでは自身の備忘録という意味も含め、どのようにVBAに考えさせているか、という部分をまとめていきます。
オリジナルの“お絵かきロジックアナライザ”には公表を控えたいコード部分もあるので、全く同じできあがりに向うわけではありませんが、解析方法については総てこのサイト内で明らかにしていきます。
VBAで事務処理プログラムを作るというのは、その殆どが「手順の自動化」であり、割と殺風景(と言う表現が正しいのかどうか....)なプログラムとなります。プログラマとしては「自分が作ったアルゴリズムや数学モデルがPC上で思った通りに走る」という部分に達成感を得るところがあり、手順を記述するだけのプログラムに物足りなさを覚えるのは当然のコトだと思います。そういった意味ではお絵かきロジックをExcelに解かせるというテーマは、プログラマ脳のトレーニングには結構良い題材だと思います。
私にとっては備忘録的なTips集ですが、ここで紹介するコードを元に独自のアレンジを加えていけば、自分なりの“お絵かきロジックアナライザ”が完成するでしょう。
Visual Basic Editor(以下VBE)の[ツール]→[オプション]で「変数の宣言を強制する」にチェックマークが付いているものとします。変数管理をより厳密に行うため、このチェックマークは常にONにしておくことをおススメします。このチェックマークが付いていると、各モジュールのコード先頭に‘Option Explicit’が付加されます。
先ずはお絵かきロジックのシートをExcelワークシート上に再現する事からハナシを進めます。
Excelの最大シートサイズは256列×65536行です(Excel2007からは16384列×1048576行)。これは何も入力されていない新規シート上で、Ctrl+→やCtrl+↓を押すことで確認できます。[オプション]ダイアログの[全般]タグで「R1C1参照形式を使用する」にチェックを入れると列も番号表示になり、一層解り易くなります。
イメージフィールド(白と黒で塗分けてイラストが浮かび上がる部分)の最大サイズを縦横同数とすると、少ない方の256列を基準にすることになり、ヒントエリア(ヒントになる数値が入る部分)とイメージフィールドとを足して256列以内にしなければなりません。もちろん古いバージョンのExcelは切捨てるということならもっと大きなフィールドが可能ですが、実際にそこまで大きなサイズのロジックは世の中に多くは存在しないでしょう。またお絵かきロジックのフィールド部は5マスごとに太い線で区切られており、太線で始まり太線で終わるように、つまりフィールド幅は5の倍数になっています。もちろん半端なサイズの問題があっても構わないでしょうが、23マスとか41マスとかのロジックは見たことがないので、フィールド最大サイズは5の倍数でキリの良い数字(20とか50とか100とか)ということにします。
それではヒント部分とフィールド部分をどのように割振ったらいいでしょうか。これはお絵かきロジックの基本ルールを理解していれば簡単に求められます。例としてフィールド幅5の場合、ヒント値が5なら1つのヒントで埋まりますが、ヒント値が1なら3個のヒントが配置できます。フィールド幅10なら、‘1’が5個まで並べられます。つまり、フィールド幅が偶数の場合の最大ヒント幅はフィールド幅の半分、奇数なら半分の切上げとなります。例えば20×20のお絵かきロジックで必要となる最大のヒント幅は10マスとなります。ただしこれはあくまで「考えられる最大のサイズ」であり、実際に20×20でヒント数が10個もあるような問題にはなかなかお目に掛かりません(この辺りの対応は後述します)。ともかく、フィールド幅の半分程度をヒント数として準備しておけばコトが足りるワケです。
256÷1.5=170.666...なので、列数256のシート上ではフィールド幅170までであれば、どんな問題でも間違いなく配置可能です。この場合ヒント幅は85で、使用領域は255列分になります。170ではちょっとキリが悪い気もするので、最大フィールド幅は150としておきましょうか。この場合、ヒント幅が最大75で、225列を使用することになります。ちなみにお絵かきロジックアナライザでは200×200のフィールドサイズを最大としています。当然ヒント幅が不足するので、その旨を警告した上で作業を進めるようにしています。
配置可能なシートサイズが決まったので、実際に指定サイズのシートを作成する準備に取りかかりましょう。先ずは新規ワークシートを作成し、ファイル名を“Nonogram.xls”としておきます。入力項目は横サイズと縦サイズの2つなので、InputBox関数を2回使っても良いのですが、せっかくなのでユーザーフォームを使ってそれっぽい入力ダイアログを作ってみます。メニューバーで[ツール]→[マクロ]→[Visual Basic Editor]をクリックしてVBEを開き、メニューバーから[挿入]→[ユーザーフォーム]をクリックしてプロジェクトにユーザーフォームを挿入します。
デザイン例を右に示します(公開しているお絵かきロジックアナライザのデザインそのままです)。フォームのオブジェクト名はSetFieldSizeに変更しておきます。各コントロールのオブジェクト名は横サイズ入力欄をFieldWidth、縦サイズ入力欄をFieldHeightとし、OKボタンはOkClick、キャンセルボタンはCancelClickとします。タブオーダーもキチンと設定しておきましょう。サイズ入力欄のControlTipTextプロパティを「150以下」とでも設定しておけばマウスオーバー時にヒント表示されます。また数値入力に限るので全角文字は必要ないため、IMEModeプロパティはfmIMEModeOffで良いでしょう。
それではマクロからこのユーザーフォームを呼出してみます。VBEメニューバーから[挿入]→[標準モジュール]をクリックして標準モジュールを作成したら、モジュール名をModule1からNonoModuleに変更しておきます。NonoModule内に以下のプロシージャを記述してみましょう。
Sub FieldSizeDialog()
' フィールドサイズ設定ダイアログ
SetFieldSize.Show '新規作成フォーム表示
End Sub
[ツール]→[マクロ]→[マクロ]でFieldSizeDialogを実行すると、作成したフォームが表示されます。現状ではOKなどをクリックしても何も起こらないので、×ボタンでフォームを閉じておきましょう。
サイズ指定ができるようになったので、これに従いお絵かきロジックの‘解答用紙’を作成していきます。具体的に言えばSetFieldSizeフォームでOkClickボタンのClickイベントに、FieldWidthとFieldHeightのValueプロパティを取得してシートを作成するような記述をすれば完成です。CancelClickボタンのClickイベントではフォームを閉じるだけです。SetFieldSizeフォームのコード部分に以下のプログラムを記述します。
Private Sub OkClick_Click()
' OKクリックイベント
Dim FieldX As Byte 'フィールドサイズ
Dim FieldY As Byte
Dim HintX As Byte 'ヒントサイズ
Dim HintY As Byte
FieldX = Me.FieldWidth.Value 'フィールドサイズ取得
FieldY = Me.FieldHeight.Value
HintX = (FieldX + 1) \ 2 'ヒントサイズ算出
HintY = (FieldY + 1) \ 2
Unload Me 'フォーム消去
Cells.Delete '編集領域クリア
With Cells
.RowHeight = 12 '行高/列幅調整
.ColumnWidth = 1.4
End With
Range(Cells(HintY + 1, 1), Cells(HintY + FieldY, HintX)) _
.Borders.LineStyle = xlContinuous '水平ヒントエリア
Range(Cells(1, HintX + 1), Cells(HintY, HintX + FieldX)) _
.Borders.LineStyle = xlContinuous '垂直ヒントエリア
Range(Cells(HintY + 1, HintX + 1), Cells(HintY + FieldY, HintX + FieldX)) _
.Borders.LineStyle = xlContinuous 'フィールドエリア
Range(Cells(HintY + 1, HintX + 1), Cells(HintY + FieldY, HintX + FieldX)) _
.BorderAround , xlMedium
End Sub
Private Sub CancelClick_Click()
' キャンセルクリックイベント
Unload Me 'フォーム消去
End Sub
まずは簡単なCancelClick_Clickイベントから解説します。キャンセルクリック時は何もしないでフォームを閉じさえすればいいので、Unloadステートメントでフォームをメモリ上から削除しています。オブジェクト名をMeとしていますが、これはコード記述されているモジュールのオブジェクト(この場合はSetFieldSizeフォーム)を示しており、Unload SetFieldSizeと記述しても同様の結果となります。
ところで、FieldSizeDialogプロシージャにおいてSetFieldSize.ShowとShowメソッドによってフォームを表示させているのだから、フォームを消すにはHideメソッドでSetFieldSize.Hideとすればいいのでは?と思われるかもしれませんが、Hideメソッドはその名の通りオブジェクトを隠すだけで、メモリ上には残したままになっています。再びShowメソッドでオブジェクトが表示させる場合、メモリに残っているフォームオブジェクトを再利用するのですが、Showメソッドはメモリ上にオブジェクトがなければLoadステートメントに当たる処理を自動的に行い、フォームをロードしてくれます。この後の処理で少しでもメモリを開放するために、SetFieldSizeフォームオブジェクトをUnloadステートメントでメモリ上から開放しているワケで、まぁ一種の‘こだわり’ですね。
OkClick_Clickイベントではヒントサイズ、フィールドサイズを収める変数を宣言し、フィールドサイズをフォームのテキストボックスから取得した上で、ヒントサイズを算出しています。フィールドサイズは150まで、ヒントサイズは75までなので、変数はバイト型で宣言していますが、数値取得や演算をする中で、負数になったりするとオーバーフローが発生しますので、そのあたりのチェックは今後厳重に行っていく必要があります。試しにFieldSizeDialogをマクロ実行してサイズ入力欄に「-1」などを入力してみるとオーバーフローでマクロが中断します。
必要な数値を取得したらフォームオブジェクトを開放し、シート上の総てのセルを削除した後に高さと幅を調整し、必要な部分に罫線を描いてプロシージャを終了します。ここでセル内容削除(Cells.Deleteの部分)と、その後の行高、列幅調整部分とは、同じCellsに対する操作で、Withの中にまとめられそうですが、DeleteメソッドをWith内にまとめるとエラーが発生します。つまりなぜ次のように記述しないのかということです。
With Cells
.Delete
.RowHeight = 12
.ColumnWidth = 1.4
End With
これはDeleteメソッドによりレンジ選択されていたはずのCellsオブジェクトが無くなってしまうため、オブジェクト指定がない状態で次のRowHeightプロパティが参照されようとするためです。Deleteメソッド実行後に改めてCellsオブジェクトを指定しているワケです。
それでは試しに新規のロジックシートを作ってみましょう。[ツール]→[マクロ]→[マクロ]でFieldSizeDialogを実行し、SetFieldSizeフォームのFieldWidthとFieldHeightに縦横サイズを指定して