プログラミングのすすめ 第十四歩 数独の解法プログラム Visual Studio(VB)

プログラミング

先日、新聞の日曜欄に載っていた数独の問題を解いていて、ふと思いついたのがこれをプログラミングして解いたら面白いなとということでした。数独、ナンバー・プレース、ナンプレ とか呼ばれているようです。数独の解き方は皆さんご存知だと思いますが、一応最初におさらいしたいと思います。

数独とは

数独とは、縦横に並んだ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

リセットの設置

仮定法で失敗したときに、もう何が何だか分からなくなって最初に戻りたい という時に使うリセットボタンを設置します。

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