先日、新聞の日曜欄に載っていた数独の問題を解いていて、ふと思いついたのがこれをプログラミングして解いたら面白いなとということでした。数独、ナンバー・プレース、ナンプレ とか呼ばれているようです。数独の解き方は皆さんご存知だと思いますが、一応最初におさらいしたいと思います。
数独とは
数独とは、縦横に並んだ9×9のマス目に歯抜けになった数字が入っているもので抜けているマス目の数字を入れていきます。ルールは、縦、横、および 3×3 のブロックに1~9の数字を入れて行きます。各エリアには、同じ数字を2度入れることは出来ません。
解き方 その1
解き方のその1 は、ある一つの数値に注目してその数値が入れない箇所を消して行きます。例えば以下の赤枠の6に注目します。
赤枠の縦の列の他の場所には、6は入れません。
同じように横の行にも6は入れません。
同じ3×3のブロックにも入れません。
他の6の場所についても同じように考えます。
このようにして、6が入れない箇所を潰して行くことにより、6が入れる箇所が浮かび上がってきます。空いている箇所について、縦、横 または 3×3ブロックに一か所だけになっていればそこには確実に6が入ります。
該当箇所に6を入れて、再び6が入る可能性のないマス目を塗りつぶしてしまいます。
この状態では、縦、横、ブロック内に一か所だけの部分はなくなりました。次は違う数字で見て行きます。5で見てみます。
5が入る可能性の箇所で、縦、およびブロックに一か所のところが赤い星印のところです。ここに5を入れると、横のピンクの丸印のところには5は入らないことになります。そうすると、青い星印のところは5の入る可能性の場所で、3x3ブロックで1か所のみになるので5が入ります。
縦、横、ブロックに一か所の空き場所はなくなりましたので、つぎは違う数字で続けます。
同じ作業を続けて最終的に、すべての数字で次に確実に入る可能性の場所が見つからなくなった時点で他の方法を試みます。
解き方 その2
解き方のその2は、空いている箇所について注目します。それぞれの空いている箇所について、入る可能性のある数値を挙げて行きます。下図を見て下さい。赤枠の空きの縦、横、ブロックを観察して、ここに入る可能性の数字は、1と4と分かります。同じように次の黄色枠の空きには、1,2,4が入る可能性があります。それでは、青色枠の空きについて見てみます。ここに入る可能性のある数値は4のみであることが分かります。
すべての空いている箇所について、同じように観察して入る可能性の数字を探して行きます。その結果、下図の様に入る可能性の数字が一つだけの箇所がみつかれば、そこに数字を入れます。
上図の数値を入れた後、再び左上の空きの箇所から同じ作業を繰り返します。
同じ作業を繰り返します。
同じ作業を繰り返します。
同じ作業を繰り返します。
同じ作業を繰り返します。
これで完成しました。
解き方 その3
ネイキッドペア
下図のように、赤枠の縦で見た時に1,6のペアがあれば、そこの二マスには、1か6が入ります。ということは、この縦列の他のマスには、1,6は入れません。よって他のマスの候補から1,6を削除することができます。
ネイキッドトリプル
上記と同じことが、三つについても考えられます。ネイキッドトリプルの場合は、組み合わさる数字は必ずしも三つとは言えません。下図のように、28, 238,238 でもトリプルの組み合わせになります。どれか一つの数字が欠けていても組み合わせになります。
下図は、赤丸の 176 でネイキッドトリプルの処理で、青〇の 59 のネイキッドペアが出来て、その処理をすると、青枠の2の入る箇所が分かった様子です。
この他にも、四つのネイキッド・クワッド、五つの組み合わせのネイキッド・カルテットも有ります。
解き方 その4
解き方 その1、その2 その3 でも行き詰まった場合は仮定法(背理法、仮置法)を試みます。
これまでの作業で埋まった状態をよく観察し残りが2か所の部分を探します。
この図では、左下のブロックの残りは2か所です。ここには、3または7が入ることが分かります。
ここでどちらかの数字を仮定で入れて前に進みます。成功する確率は50%です。一応、失敗した場合を考えてこの状態を記録しておきます。
上に7が入ると仮定して進めてみます。
ある程度進んだ後で、上図のように矛盾する結果となりました。
記録しておいた図まで戻って、上に3、下に7を入れて進めます。
その結果、正解が得られました。
Visual Studio 2022 Visual Basic でプログラミング
それでは、これらの数独の解き方を念頭に Visual Basic でプログラミングを進めて行きます。
Form のデザイン
数独の各数値は、Form1 にTextBox を配置し、その中に入れるものとします。以下のようにForm1に TextBox を 9×9 で合計81個配置します。
段階を追って解説します。
先ず、以下のようにGroupBox を配置します。
GroupBoxのtextはブランクにして、GroupBoxの上にTextBoxを3x3個配置します。
上図のGroupBoxをコピー&ペーストで9個のグループボックスを配置します。
各GroupBoxのBackColorを変更します。
TextBoxのプロパティで各TextBox の名前を以下の図のように TextBox1 ~ TextBox81 に変更します。左上から右下へ 1 ~ 81 の順に変更します。
TextBox の初期化
各TextBox への入力は、プログラムで行いますが、81個のTextBoxを一々コードを記述して代入するのは大変です。TextBox の名前を変数では指定できません。Formに配置されたコントロールを名前で探して、見つかったコントロールを変数に入れるということが出来ます。
以下のような Function を記述しておきます。
Public Shared Function FindControlByFieldName(
ByVal frm As Form, ByVal name As String) As Object
'まずプロパティ名を探し、見つからなければフィールド名を探す
Dim t As System.Type = frm.GetType()
Dim pi As System.Reflection.PropertyInfo =
t.GetProperty(name,
System.Reflection.BindingFlags.Public Or
System.Reflection.BindingFlags.NonPublic Or
System.Reflection.BindingFlags.Instance Or
System.Reflection.BindingFlags.DeclaredOnly)
' If Not pi Is Nothing Then
If pi IsNot Nothing Then
Return pi.GetValue(frm, Nothing)
End If
Dim fi As System.Reflection.FieldInfo =
t.GetField(name,
System.Reflection.BindingFlags.Public Or
System.Reflection.BindingFlags.NonPublic Or
System.Reflection.BindingFlags.Instance Or
System.Reflection.BindingFlags.DeclaredOnly)
If fi Is Nothing Then
Return Nothing
End If
Return fi.GetValue(frm)
End Function
つぎのような形でこのFunction を使います。
Dim TBName As TextBox =
CType(FindControlByFieldName(Me, "TextBox" + i.ToString), TextBox)
上記のコードで i を 1 ~ 81 と変化させると、 TBName には、 TextBox1 ~ TextBox81 のコントロールが代入されることになります。
以下のコードで、プログラム開始時に各TextBox を初期化します。全てのTextBox のBackColor は Whiteにして、入力できるMaxLength は 1 桁、および Null を入力しています。
Private Sub Numpre_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For i = 1 To 81
Dim TBName As TextBox =
CType(FindControlByFieldName(Me, "TextBox" + i.ToString), TextBox)
TBName.BackColor = Color.White
TBName.MaxLength = 1
TBName.Text = ""
TBName.Font = New Font("Yu Gothic UI Semibold", 11.25, FontStyle.Bold)
TBName.TextAlign = HorizontalAlignment.Center
Next
End Sub
問題の読み込み
問題は次の様な形式のテキストファイルを読み込みます。
各マスの数値をカンマで区切っています。
, ,4, , , , , ,3 5,8, , ,2,6, , , , ,1,5, , , ,9, ,4, , ,5, , , ,2 , , ,3, ,4, , , 3, , , ,9, , ,1, ,7, , , ,2,4, , , , ,1,7, , ,3,5 1, , , , , ,6, ,
カンマで区切っていなくても読み込みます。
2 51 5 6 34 82 9 2 6 18 97 7 1 5 6 1 3 8 4 7 3
FormにButtonコントロールを追加して、テキストを問題選択とします。
問題選択ボタンは Button2 です。Button2_Click に次のコードを記述します。
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
'問題選択
'OpenFileDialogクラスのインスタンスを作成
Dim openFileDialog1 = New OpenFileDialog() With {.InitialDirectory = CurDir(),
.Filter = "ナンプレ問題ファイル(Numpre*.txt;*.txt)|Numpre*.txt;*.txt|すべてのファイル(*.*)|*.*",
.Title = "開くファイルを選択してください",
.RestoreDirectory = True,
.CheckFileExists = True,
.CheckPathExists = True
}
If openFileDialog1.ShowDialog() = DialogResult.OK Then
'OKボタンがクリックされたとき、選択されたファイル名を表示する
MondaiFile = openFileDialog1.FileName
ReadMondai(MondaiFile)
Dim fn As String() = Split(MondaiFile, "\")
Label1.Text = fn(fn.Length - 1)
Label1.Visible = True
End If
End Sub
OpenFileDialog() で次のようなファイル・ダイアログを開きます。初期ディレクトリーはCurrent Dirです。 Numpre*.txt でフィルターしています。
変数 MondaiFile は、他でも使いますので、このサブルーティンの外に Public として宣言しています。
Public Class Form3
Public MondaiFile As String
Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For i = 1 To 81
この画面で問題ファイルを読み込みます。
読み込んだファイルの処理に次のコードを記述します。
Sub ReadMondai(MondaiFile As String)
Dim i As Integer
Dim j As Integer
Dim sr As New StreamReader(MondaiFile)
Dim k As Integer = 0
Do Until sr.EndOfStream
For j = 1 To 9
Dim line1 As String = sr.ReadLine()
Dim arr1() As String = line1.Split(",")
' Debug.WriteLine(j.ToString)
For i = 1 To 9
k += 1
Dim TBName As TextBox =
CType(FindControlByFieldName(Me, "TextBox" + k.ToString), TextBox)
If line1.Length > 9 Then '行が9桁以上の場合は , で区切られていると判断
TBName.Text = arr1(i - 1)
Else
TBName.Text = line1.Substring(i - 1, 1)
End If
If IsNumeric(TBName.Text) = True Then
TBName.ForeColor = Color.Blue
Else
TBName.Text = ""
End If
TBName.BackColor = Color.White
'Debug.WriteLine(k.ToString)
Next
'Debug.WriteLine(line1)
Next
Loop
sr.Close()
End Sub
実行すると、次の様に問題がセットされます。
ファイル読み込みとは別に問題を入力出来るようにもします。上図の問題入力のボタンです。問題入力ボタンをクリックするとForm2を表示するように 該当のButton_click ルーティンに次を記述します。
Private Sub Button5_Click(sender As Object, e As EventArgs) Handles Button5.Click
Form2.Show()
End Sub
問題入力用にTextBox を配置した Formを追加します。OK と Cancel のボタンも追加しておきます。
Form2 のコードに次を記述します。問題が正しく入力されているかどうかのチェックは、行のデータのスペースを取り除いた値が数値のみか、全体の長さが9桁かを判定しています。
Public Class Form2
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim k As Integer
Dim InLine() As String = Split(TextBox1.Text, vbCrLf)
For i = 0 To 8
If IsNumeric(InLine(i).Replace(" ", "")) = False Or InLine(i).Length <> 9 Then
MsgBox("問題が正しく入力されていません")
Else
For j = 0 To 8
k += 1
Dim TBName As TextBox =
CType(Form1.FindControlByFieldName(Form1, "TextBox" + k.ToString), TextBox)
TBName.Text = InLine(i).Substring(j, 1)
If IsNumeric(TBName.Text) = True Then
TBName.ForeColor = Color.Blue
Else
TBName.Text = ""
End If
TBName.BackColor = Color.White
Next
End If
Next
Me.Hide()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Me.Hide()
End Sub
End Class
問題入力 ボタンをクリックするとForm2が表示されるので、問題をtextboxに入力します。
問題を正しく入力した後、OK ボタンをクリックします。
入力した問題を保存する
正しく問題が入力出来れば、その問題をテキストファイルで保存出来る様にします。Buttonコントロールを追加し問題保存とします。
次のコードの23-25で、81桁が入力されたことで、問題で正しく入力されたことを確認しています。正しく入力されたことを確認後、問題保存のボタンを表示します。
Public Class Form2
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim k As Integer
Dim InLine() As String = Split(TextBox1.Text, vbCrLf)
For i = 0 To 8
If IsNumeric(InLine(i).Replace(" ", "")) = False Or InLine(i).Length <> 9 Then
MsgBox("問題が正しく入力されていません")
Else
For j = 0 To 8
k += 1
Dim TBName As TextBox =
CType(Form1.FindControlByFieldName(Form1, "TextBox" + k.ToString), TextBox)
TBName.Text = InLine(i).Substring(j, 1)
If IsNumeric(TBName.Text) = True Then
TBName.ForeColor = Color.Blue
Else
TBName.Text = ""
End If
TBName.BackColor = Color.White
Next
End If
Next
If k > 80 Then
Form1.Button7.Visible = True
End If
Me.Hide()
End Sub
Private Sub Button7_Click(sender As Object, e As EventArgs) Handles Button7.Click
Dim sfd As New SaveFileDialog()
sfd.FileName = "Numpre*.txt"
sfd.InitialDirectory = CurDir()
sfd.Filter = "Numpreファイル(Numpre*.txt)|*.txt|すべてのファイル(*.*)|*.*"
sfd.FilterIndex = 1
sfd.Title = "保存先のファイルを選択してください"
sfd.RestoreDirectory = True
sfd.OverwritePrompt = True
sfd.CheckPathExists = True
If sfd.ShowDialog() = DialogResult.OK Then
Dim stream As System.IO.Stream
stream = sfd.OpenFile()
If Not (stream Is Nothing) Then
Dim sw As New System.IO.StreamWriter(stream)
sw.Write(Form2.TextBox1.Text)
sw.Close()
stream.Close()
End If
End If
End Sub
各TextBox のイベント発生で処理をすすめる
問題のセットまで終わりました。次はそれぞれの解き方をプログラミングして行きます。
その前に、各処理を始めるきっかけはTextBox でマウスボタンをクリックしたら始めることとします。
すべてのTextBox1 ~ TextBox81 に次のコードを用意します。
Private Sub TextBox1_MouseDown(sender As Object, e As EventArgs) Handles TextBox1.MouseDown
ProcThis(1)
End Sub
Private Sub TextBox2_MouseDown(sender As Object, e As EventArgs) Handles TextBox2.MouseDown
ProcThis(2)
End Sub
Private Sub TextBox3_MouseDown(sender As Object, e As EventArgs) Handles TextBox3.MouseDown
ProcThis(3)
End Sub
:
:
Private Sub TextBox79_MouseDown(sender As Object, e As EventArgs) Handles TextBox79.MouseDown
ProcThis(79)
End Sub
Private Sub TextBox80_MouseDown(sender As Object, e As EventArgs) Handles TextBox80.MouseDown
ProcThis(80)
End Sub
Private Sub TextBox81_MouseDown(sender As Object, e As EventArgs) Handles TextBox81.MouseDown
ProcThis(81)
End Sub
解き方その1のプログラミング
解き方その1は、数値に注目してその数値が入れない箇所をすべて潰していって、その数値の入る可能性がある箇所が縦、横、ブロックで一つだけのところがあるかどうかをチェックします。
同じ数値のTextBoxの位置を得る
先ず、数値の入ったTextBoxをクリックすると、その数値と同じ数値が入っているTextBoxの位置を全体から把握します。
Sub ProcThis(BoxNumber As Integer)
Dim b As Integer = BoxNumber
Dim Arr() As String
ReDim Arr(9)
Dim jj As Integer
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + b.ToString), TextBox)
Dim InNum As String = TBName.Text.Trim
Dim Li As Integer
Dim Co As Integer
Dim Bl As Integer
'同じ数字が入っているtextBoxの名前を得る
ClearColor()
For ii = 1 To 8
Arr(ii) = ""
Next
jj = 0
For ii = 1 To 81 '全体から同じ数字の入っているTextBoxの位置を把握
TBName = CType(FindControlByFieldName(Me, "TextBox" + ii.ToString), TextBox)
If TBName.Text.Trim = InNum Then
jj += 1
On Error Resume Next
Arr(jj) = ii
Debug.WriteLine(ii.ToString)
End If
Next
'Arr() には、指定されたTextBoxに入っている数値と同じ数値が入っているTextBoxの番号が入っています。
For ii = 1 To 8
If IsNumeric(Arr(ii)) = True Then
GetLandC(Arr(ii), Li, Co, Bl)
ChangeBC(Li, Co, Bl)
End If
Next
'CheckNext(InNum)
On Error GoTo 0
End Sub
サブルーチン GetLandC で与えられたTextBoxの位置から、行、カラム、ブロックを得ます。
'与えられたTextBoxの位置情報から、行、カラム、ブロックの情報を返します。
Shared Sub GetLandC(b As Integer, ByRef LineN As Integer, ByRef Colum As Integer, ByRef Block As Integer)
Dim bx As Integer = b
'Dim LineN As Integer
'Dim Colum As Integer
Select Case bx / 9
Case <= 1
LineN = 1
Case <= 2
LineN = 2
Case <= 3
LineN = 3
Case <= 4
LineN = 4
Case <= 5
LineN = 5
Case <= 6
LineN = 6
Case <= 7
LineN = 7
Case <= 8
LineN = 8
Case <= 9
LineN = 9
End Select
Select Case bx Mod 9
Case = 0
Colum = 9
Case = 1
Colum = 1
Case = 2
Colum = 2
Case = 3
Colum = 3
Case = 4
Colum = 4
Case = 5
Colum = 5
Case = 6
Colum = 6
Case = 7
Colum = 7
Case = 8
Colum = 8
End Select
Select Case LineN
Case <= 3
Select Case Colum
Case <= 3
Block = 1
Case <= 6
Block = 2
Case <= 9
Block = 3
End Select
Case <= 6
Select Case Colum
Case <= 3
Block = 4
Case <= 6
Block = 5
Case <= 9
Block = 6
End Select
Case <= 9
Select Case Colum
Case <= 3
Block = 7
Case <= 6
Block = 8
Case <= 9
Block = 9
End Select
End Select
Debug.WriteLine("Block=" + Block.ToString)
End Sub
BackColor 初期化
塗りつぶす作業を始める前に、BackColor を初期化します。
数値の入っているマス目は、Color.YellowGreen に、ブランクのマス目は、Color.White にします。
Sub ClearColor()
For i = 1 To 81
'TBName = FindControl(Me, "TextBox" + i.ToString)
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + i.ToString), TextBox)
If IsNumeric(TBName.Text) = True Then
TBName.BackColor = Color.YellowGreen
Else
TBName.BackColor = Color.White
End If
Next
End Sub
横(行)を塗りつぶす
先ず、横方向(行)を塗りつぶします。
'与えられた行、カラム、ブロックの値を元にバックカラーをイエローグリーンに変更します。
'イエローグリーンに変更されたTextBoxには指定されたTextBoxに入っている数値と同じ数値は入ることは出来ません。
Sub ChangeBC(LineN As Integer, Colum As Integer, Block As Integer)
'yoko
For i = LineN * 9 - 8 To LineN * 9
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + i.ToString), TextBox)
TBName.BackColor = Color.YellowGreen
Next
End Sub
6の入ったTextBox をクリックすると 6の入っている横のラインを塗りつぶします。既に数値が入っているマス目は、ClearColor()で塗りつぶされています。
縦(カラム)を塗りつぶす
前述の ChangeBC サブルーチンに次のコードを追加します。
'tate
For j = Colum To Colum + 72 Step 9
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + j.ToString), TextBox)
TBName.BackColor = Color.YellowGreen
Next
6をクリックした図です。6の縦の列も塗りつぶしました。
ブロックを塗りつぶす
前述のChangeBCサブルーチンに更に次のコードを追加します。
'block
Dim Ki As Integer
Select Case Block
Case 1
ki = 0
Case 2
ki = 1
Case 3
ki = 2
Case 4
ki = 9
Case 5
ki = 10
Case 6
ki = 11
Case = 7
ki = 18
Case 8
ki = 19
Case 9
ki = 20
End Select
For k = ki * 3 + 1 To ki * 3 + 3
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + k.ToString), TextBox)
'Dim TBName As Control = FindControl(Me, "TextBox" + k.ToString)
TBName.BackColor = Color.YellowGreen
Next
For k = ki * 3 + 10 To ki * 3 + 12
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + k.ToString), TextBox)
' Dim TBName As Control = FindControl(Me, "TextBox" + k.ToString)
TBName.BackColor = Color.YellowGreen
Next
For k = ki * 3 + 19 To ki * 3 + 21
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + k.ToString), TextBox)
'Dim TBName As Control = FindControl(Me, "TextBox" + k.ToString)
TBName.BackColor = Color.YellowGreen
Next
6をクリックした図です。ブロックも塗りつぶしました。この図から、3行目の9カラムに6が入れる事が分かります。更に、9行目の6カラムにも6が入れます。
縦、横、ブロックで一か所だけかを判断する
前述のコードで、調査している数値が入れない箇所を塗りつぶしました。白抜きのマスは調査中の数値が入る可能性のあるところです。次は、白抜きのマス目が 縦、横、ブロックに一つだけかどうかを調べて行きます。縦、横、ブロックで一か所だけの場合は、そこに数値を入れ、BackColor を赤に換えます。
次のサブルーティンを記述して、ProcThis() の最後に CheckNext(InNum) を追記します。
Sub CheckNext(Suuji As String)
Dim CountC As Integer = 0
Dim WhitePos As Integer
Dim j As Integer
Dim i As Integer
'yoko check
For i = 1 To 73 Step 9
For j = i To i + 8
Dim TBNameN As TextBox = CType(FindControlByFieldName(Me, "TextBox" + j.ToString), TextBox)
If TBNameN.BackColor <> Color.YellowGreen Then
CountC += 1
WhitePos = j
If CountC > 1 Then
Exit For
End If
End If
Next
If CountC = 1 Then
Dim TBNameN As TextBox = CType(FindControlByFieldName(Me, "TextBox" + WhitePos.ToString), TextBox)
TBNameN.BackColor = Color.Red
TBNameN.Text = Suuji
System.Threading.Thread.Sleep(100)
End If
CountC = 0
Next
'tate check
For i = 1 To 9
For j = i To 81 Step 9
Dim TBNameN As TextBox = CType(FindControlByFieldName(Me, "TextBox" + j.ToString), TextBox)
If TBNameN.BackColor <> Color.YellowGreen Then
CountC += 1
WhitePos = j
If CountC > 1 Then
Exit For
End If
End If
Next
If CountC = 1 Then
Dim TBNameN As TextBox = CType(FindControlByFieldName(Me, "TextBox" + WhitePos.ToString), TextBox)
TBNameN.BackColor = Color.Red
TBNameN.Text = Suuji
End If
CountC = 0
Next
'block check
For i = 1 To 9 Step 3
For k = i To i + 54 Step 27
For j = k To k + 2
Dim TBNameN As TextBox = CType(FindControlByFieldName(Me, "TextBox" + j.ToString), TextBox)
If TBNameN.BackColor <> Color.YellowGreen Then
CountC += 1
WhitePos = j
If CountC > 1 Then
Exit For
End If
End If
Next
For j = k + 9 To k + 11
Dim TBNameN As TextBox = CType(FindControlByFieldName(Me, "TextBox" + j.ToString), TextBox)
If TBNameN.BackColor <> Color.YellowGreen Then
CountC += 1
WhitePos = j
If CountC > 1 Then
Exit For
End If
End If
Next
For j = k + 18 To k + 20
Dim TBNameN As TextBox = CType(FindControlByFieldName(Me, "TextBox" + j.ToString), TextBox)
If TBNameN.BackColor <> Color.YellowGreen Then
CountC += 1
WhitePos = j
If CountC > 1 Then
Exit For
End If
End If
Next
If CountC = 1 Then
Dim TBNameN As TextBox = CType(FindControlByFieldName(Me, "TextBox" + WhitePos.ToString), TextBox)
TBNameN.BackColor = Color.Red
TBNameN.Text = Suuji
End If
CountC = 0
Next
Next
End Sub
6をクリックした結果です。
解き方その2のプログラミング
解き方その2は、空いている箇所に入る可能性のある数値を調べます。
クリックしたTextBoxが空の場合の処理
空き場所のTextBoxをクリックすると、すべての空き場所について、そこに入る可能性のある数値を調べることとします。全ての空きポジションについて検証した結果、どこも見つからなかったら、クリックした箇所に入る可能性の数字を msgboxで表示します。
ProcThis()の始めに次のコードを挿入します。
'クリックしたTextBoxが空の場合、全ての空き部分について、入る可能性のある数字を検証します。
Dim suuji As String = ""
Dim Fikai As Integer = 0
If IsNumeric(InNum) = False Then
ClearColor()
For i = 1 To 81
TBName = CType(FindControlByFieldName(Me, "TextBox" + i.ToString), TextBox)
If IsNumeric(TBName.Text) = False Then
GetLandC(i, Li, Co, Bl)
CheckPossibleNumber(i, Bl, suuji, Fikai)
End If
Next
'全ての空きポジションについて検証した結果、どこも見つからなかったら、クリックした箇所に入る可能性の数字を
'msgbox で表示します。
If Fikai = 0 Then
GetLandC(b, Li, Co, Bl)
CheckPossibleNumber(b, Bl, suuji, Fikai)
MsgBox(suuji)
End If
Return
End If
次のCheckPossibleNumber()のサブルーチン コードを記述します。
与えられたTextBoxの位置情報から、縦、横、ブロックをチェックし、入る可能性の数字を見つけます。可能性のある数字が一つの場合は、その数字をTextBoxに挿入し、バックカラーを赤に変更します。
'与えられたTextBoxの位置情報から、縦、横、ブロックをチェックし、入る可能性の数字を見つけます。
'可能性のある数字が一つの場合は、その数字をTextBoxに挿入し、バックカラーを赤に変更します。
Sub CheckPossibleNumber(TBN As Integer, Blo As Integer, ByRef suuji As String, ByRef Fikai As Integer)
Dim NumAr() As String = {"1", "2", "3", "4", "5", "6", "7", "8", "9"}
suuji = ""
Dim Num As Integer = TBN Mod 9
Dim ki As Integer
If Num = 0 Then
Num = 9
End If
'yoko
For j = TBN - Num + 1 To TBN + (9 - Num)
Dim TBNameN As TextBox = CType(FindControlByFieldName(Me, "TextBox" + j.ToString), TextBox)
If IsNumeric(TBNameN.Text) = True Then
NumAr(TBNameN.Text - 1) = ""
End If
Next
'tate
For j = Num To Num + 72 Step 9
Dim TBNameN As TextBox = CType(FindControlByFieldName(Me, "TextBox" + j.ToString), TextBox)
If IsNumeric(TBNameN.Text) = True Then
NumAr(TBNameN.Text - 1) = ""
End If
Next
'block
Select Case Blo
Case 1
ki = 0
Case 2
ki = 1
Case 3
ki = 2
Case 4
ki = 9
Case 5
ki = 10
Case 6
ki = 11
Case = 7
ki = 18
Case 8
ki = 19
Case 9
ki = 20
End Select
For k = ki * 3 + 1 To ki * 3 + 3
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + k.ToString), TextBox)
If IsNumeric(TBName.Text) = True Then
NumAr(TBName.Text - 1) = ""
End If
Next
For k = ki * 3 + 10 To ki * 3 + 12
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + k.ToString), TextBox)
If IsNumeric(TBName.Text) = True Then
NumAr(TBName.Text - 1) = ""
End If
Next
For k = ki * 3 + 19 To ki * 3 + 21
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + k.ToString), TextBox)
If IsNumeric(TBName.Text) = True Then
NumAr(TBName.Text - 1) = ""
End If
Next
For i = 0 To 8
suuji += NumAr(i)
Next
If suuji.Length = 1 Then
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + TBN.ToString), TextBox)
TBName.Text = suuji
TBName.BackColor = Color.Red
System.Threading.Thread.Sleep(100)
Fikai += 1
End If
End Sub
つぎの図は、前述の解き方その1で行き詰まった後、空き場所をクリックした結果です。
解き方その1,その2で検証
比較的簡単な問題であれば、ここまでの解き方その1,その2を繰り返すだけで解けてしまいます。
簡単な問題から、難問まで ここで検証してみます。
難易度 1
問題
空き場所をクリックします。(解き方その2)
空き場所をクリックします。(解き方その2)
空き場所をクリックします。(解き方その2)
空き場所をクリックします。(解き方その2)
この問題は、解き方その2を4回繰り返すだけで解けました。
難易度 2
問題
この問題は、始まりでは解き方その2ではどこも数字を入れることは出来ません。1~9を順にクリックして解き方その1で行き詰まるまで の結果が以下の図です。
空き場所をクリックします。(解き方その2)
その後、解き方その2を数回繰り返して完成しました。
難易度 3
問題
解き方その1、その2を繰り返した結果、次の図で行き詰まりました。
行3のカラム1をクリックすると、ここには5と7 が入ることが分かります。
仮定法で、5が入るものとして進めてみました。
しかし、以下で行き詰まりました。
逆に 7 が入るものとして進めても以下で行き詰まりました。
行き詰まった状態の図では、行3 カラム1,2 の他に 行2カラム4,6も仮定法で試すことが出来ます。ここには、6か7が入る可能性がありますが、6と仮定して進めてみます。
その結果、以下で行き詰まりました。
逆に7が入ると仮定して進めました。
すると、以下のように2カラム目に7が重複する矛盾した結果となりました。これにより、行2カラム4には6、カラム6には7が入るのが正しいと判断出来ます。
行2カラム4、6に 6,7 と入れて、行3 カラム1,2を5,7と仮定して進めます。
すると、以下で行き詰まりました。
逆に行3 カラム1,2を7,5と仮定して進めます。
しかし、以下で行き詰まりました。
行3 カラム1,2での仮定法は諦めて、他の箇所を探すと、行1カラム8,9も試せます。
行1カラム8,9が 1,9と入るものとして進めてみます。
その結果、やっと完成しました。
解き方 その 4 仮定法が出来るようにプログラミング
前項の難問3で行った仮定法では、ある時点で仮定してその後、解き方1,2で行き詰まったり、矛盾する結果が出たら、仮定した時点まで戻って数値を入れ替える必要があります。それが出来るように仮定方を始める時点を記憶できるようにします。
メモ、リターン ボタン追加
メモ と リターン の二つのButtonコントロールを追加します。
次のコードを追加します。Memo() は、Public 宣言なのでサブルーチンの外に記述します。
Public Memo(2, 81) As String
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
'メモ
For i = 1 To 81
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + i.ToString), TextBox)
Memo(0, i - 1) = TBName.Text
Select Case TBName.BackColor
Case = Color.White
Memo(1, i - 1) = "W"
Case = Color.YellowGreen
Memo(1, i - 1) = "Y"
Case = Color.Red
Memo(1, i - 1) = "R"
End Select
Next
' Button3.Visible = False
Button4.Visible = True
End Sub
Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
'リターン
For i = 1 To 81
Dim TBName As TextBox = CType(FindControlByFieldName(Me, "TextBox" + i.ToString), TextBox)
TBName.Text = Memo(0, i - 1)
Select Case Memo(1, i - 1)
Case = "W"
TBName.BackColor = Color.White
Case = "Y"
TBName.BackColor = Color.YellowGreen
Case = "R"
TBName.BackColor = Color.Red
End Select
Next
'Button3.Visible = True
'Button4.Visible = False
End Sub
メモ、リターンの使い方
解き方その1とその2で解いてきて、行き詰まった時に、候補の数値が二つだけのマス目が無いかどうかを観察します。次に入る数値の候補が二つのところがあれば、仮定としてその候補のどちらかの数値を入れて次に進みます。この時、もし仮定して入れた数値が間違っていれば、いずれ矛盾となる結果となります。矛盾した結果が出たときは、仮定を始めた時点の盤面まで戻って、仮定した数値と逆の数値を入れれば、それは正解の数値となります。メモ ボタンは、仮定法を始めるときにその時の盤面をメモします。リターンは、メモした時点に戻すときに押します。
自動更新をオフするスイッチを設定
仮定法を実施する場合、仮定で数値を入れますが、数値を入れようとマウスボタンを目的のTextBoxでクリックするとマウスダウンのイベントの動作をします。これを避けるため、仮定法で数値を入れる前に、動作しないように自動動作をオン・オフするスイッチを設けます。
次のように AUTO OFF というButtonコントロールを追加します。
次のコードを追加します。
Private Sub Button6_Click(sender As Object, e As EventArgs) Handles Button6.Click
If Button6.Text = "AUTO ON" Then
Button6.Text = "AUTO OFF"
Else
Button6.Text = "AUTO ON"
End If
End Sub
ProcThis()の最初に次のコードを記述します。
Sub ProcThis(BoxNumber As Integer)
If Button6.Text = "AUTO OFF" Then
Exit Sub
End If
リセットの設置
仮定法で失敗したときに、もう何が何だか分からなくなって最初に戻りたい という時に使うリセットボタンを設置します。