【说在前面】:算法
以前,我在微信朋友圈看到一个同事发了一个状态,说的是她在家辅导孩子作做业,一个数独的题目,好像没有作出来。我看了下,我也作不出来,后来仔细想了下,花了两个多小时时间,用Python编了个程序,把那个数独题目解出来了。随后我就发了一个公众号的推送,这个推送被我老婆看见了,说:“人工解数独两分钟,你写个程序花两个多小时?!何须呢?”(学霸就是学霸,说话都这么霸气!)我说:“这个程序能够解答任何9×9的数独问题。”她说:“若是换一个数独题目,又要从新改代码,不太方便!并且我也不懂什么编程,不会用你的程序。”我想一想也是,这也是我当初准备考虑用VBA的缘由,就是由于VBA依托于电子表格,出题、解答、展现结果都比较直观,程序使用起来也比较方便。后来之因此用Python来编程,是由于Python处理这种大量数据计算比较方便快捷。编程
当初用Python编写程序,利用到了面向对象的编程方法,若是你不了解面向对象编程,可能对我以前写的程序理解起来有难度。因此我想着用VBA编程,采用面向过程的方式编写一个程序来解答数独问题。数组
这几个周末的闲暇时间,我一直在考虑用VBA来写一个解数独问题的程序,采用面向过程的方式来编写,这样便于理解。若是面向过程都搞定了,未来在转换到面向对象,就很容易了。我在网上查阅了不少资料,也看了不少网友写的程序,都测试了一下,基本上都知足不了个人需求。不是解题太慢,就是解不出来,解答一些骨灰级难度的数独,还致使死机,系统电脑直接卡死、崩溃。有的还须要使用“猜想法”来解数独,感受超级不爽。微信
好吧!既然动了用VBA解数独的心思,那就必须得搞定。因而,想了几天,反复测试,编写代码,终于搞定了。通常数独能够秒解。app
我在网上查了不少资料和其余网友写的程序,都没有我这个厉害,找一个骨灰级难度的数独,解出来也就两分多钟。网上一些朋友提供的数独程序,骨灰级别难度的数独,根本解决不了。编程语言
以上就是一个骨灰级别难度的数独,用的程序解出来了。尝试了661773次,耗时207.829秒。是否是很牛X。我在网上尚未发现有谁用VBA写出过能这么快解数独的程序。ide
看视频演示,全网最强VBA解答数独问题!函数
注意:全网最强!强!!强!!!测试
不服来战~!网站
【若是是手机观看,建议最大化视频,手机横屏观看!】
视频地址:https://www.bilibili.com/video/BV1PZ4y1G7Zv
怎么样?展现视频看完,是否是以为这个程序很强大?视频里面演示的各类难度的数独,都是我在一个在线的数独网站上找的,你们有兴趣也能够本身试试,测试一下。
免费在线数独网址:
http://www.cn.sudokupuzzle.org/
【一个小插曲】
以前,我把这个数独程序写完了,作了展现视频,写了这个技术文档的草稿,给老婆看。她是一个程序小白,若是她都大概能看懂,有兴趣把展现视频看完,那说明这个技术文章写得仍是比较详细的,由于个人题目是《详解VBA解决数独问题》,是“详解”。做为彻底不懂编程的她看后,也提了不少问题和建议,大多都是很弱的问题。我想她若是有这个问题,可能你们在看的时候,也会有一样的问题,因此我就作了一些修改和进一步的讲解。也就有了下面的:
〇、程序界面设计 这个章节,这是我新增长的一个,因此用 〇 来编号。而后再给老婆看了下,她基本上满意了,因此我就准备发布了。
跟老婆的对话:(理工男和萌妹子的平常对话)
她:你此次改的还不错,写的很详细,可是我不想看,太长了,看了头疼。
我:那我作的展现视频怎么样?看看吧!
她:视频配乐很不错。感受你这个程序仍是很厉害的!
我:这都是在你的指导下,作的修改啊!
她:你比较像白居易。
我:什么意思啊?
她:白居易在写诗的时候,都会先读给老婆婆听,若是老婆婆听得懂,以为好,他才会发。因此他的诗都通俗易懂,并且又不失高端大气。
我:白居易?不知道,我只知道白云边。
她:·······,(直接不理我了)
------ 语法不兼容,系统已死机 ------
成功的把天聊死了 呵呵
---------- 以上都是废话,下面进入正题 ----------
不少时候,咱们以为计算机很聪明,很厉害!其实,计算机很笨,他惟一的优势就是“快”!他能很快的处理一些复杂的,须要反复处理的事情。
因此,对于计算机来讲,只有咱们想不到的事情,没有他作不到的事情。若是他作不到,那就是咱们尚未把问题想清楚、分析透彻,没法翻译成代码,让计算机执行。
所以,只有了解计算机的运行原理,懂得计算机的思惟方式,咱们才能把现实中的问题,按计算机可以理解的思路,想清楚,分析透,并翻译成代码,让计算机帮咱们作。
下面就向你们介绍一下VBA解数独的思路和代码。
刚才说了,计算机有一个很大的特色就是“快”!如何发挥它“快”的优点?那就要利用循环,快速的反复运算处理。那么怎么造成循环呢?那就找规律,要分析解决问题的核心规律,抽象成代码和变量,利用循环,快速运算处理。
如今咱们就来分析数独问题:
什么是数独?
数独是一种数学游戏。数独盘面是个九宫,每一宫又分为九个小格。玩家须要根据9×9盘面上的已知数字,推理出全部剩余空格的数字,并知足每一行、每一列、每个粗线宫(3*3)内的数字均含1-9,不重复。因此又称“九宫格”。
〇、程序界面设计
为了用户可以很好的体验这个程序,同时也为了后续的开发方便,咱们须要对程序界面作一个很好的设计。
程序主界面:
我是在WPS的电子表格里设计的,能够看到最左边和最上边的数字编号,这是表格的行、列编号,是绝对定位编号,我设计的数独九宫格,是在上面空了两行,左边空了一列开始绘制表格的,因此我在上面标注了 起始行号:3,起始列号:2,这就是针对我绘制的数独九宫格第一个格子的位置,相对于电子表格编号的定位,这是相对定位,便于之后调整表格位置,不用改代码,只须要修改起始行号和起始列号的数字就能够了。做答区也是这个思路设计的。
第13行,我作了一个 显示解题过程 的开关,只须要用鼠标点击那个小黑点,就能够自由的启用和关闭 显示解题过程 了,方便操做。
这些都是交互功能,不在此次详解的范围内,不细说,有兴趣能够看个人源代码。
1、数独区域划分和命名:
一、行、列编号
根据数独格子的样式,咱们给他命名并编号:
行编号:Row = 1 表示第一行,以此类推,第九行 Row = 9。
列编号:Col = 1 表示第一列,以此类推,第九列 Col = 9。
区编号:Box = 1 表示第一个小九宫格,以此类推,第九区 Box = 9。
二、点位编号:
这是每一个单元格的编号,其目的是方便找到所在点位对应的行号、列号和区号。
2、创建数独模型
咱们仍是以以前我那个同事发的数独题目为例:
能够看到,81个格子里面,已经有27个格子有数字了,54个格子是空的。这就是数独题目。如今咱们就要考虑,把这个题目读取出来,在程序代码里面造成数独模型。以前我考虑用数组来处理,可是VBA的数组操做很麻烦,因而果断放弃,这里采用字符串模式来处理。
咱们用一个嵌套循环,逐行读取数独题目中的数字,若是格子是空的,咱们就用0来填充占位,确保字符串长度为81。
代码:
解释:
函数名为 GetShuDuList(Row As Integer, Col As Integer) As String (核心函数)
用一个嵌套的For循环来读取数独表格的数据,第九行代码,这里用了一个 IIF语句,意思就是,在指定的行号和列号定位的单元格内是空值,就填充0,不然就读取单元格内的数字。
读取完后,用 GetShuDuList = List 返回。
最后获得:
300500000000000346002003000003000200010840000006320980035100069091050000000900007
这样的一个字符串。
之后咱们全部的操做和运算,都基于这个字符串来进行。
举例:
以添加了灰背景色的数字 3 为例。对应单元格点位编号,能够对照知道,这个3对应的点位为30点位。
GetShuDuList = 30050000000000034600200300000300020……
在数独模型GetShuDuList字符串中,也是对应的第30点位,即标红的那个3。
那么,如今咱们就要考虑一个重要的问题,如何经过这个点位30,算出这个点位对应在数独表格中的行号、列号和区号。
这里须要用到一点点数学知识,就是除法的取整和取余的问题。
很明显,这个30点位,对应的行号是4,对应的列号是3,对应的区号是4。那么如何算呢?
先算行号和列号:
这个数独是9行9列的表格,那么咱们就用点位号30除以9:
30÷9=3 余 3
很容易理解了,除法结果整数部分 +1 就是行号,余数部分就是列号。即30点位行号是4,列号是3。
咱们再试一下点位14看看。
14÷9=1 余 5
按上面计算方法,行号是2,列号是5。没问题,是正确的。
咱们再试试点位18看看。
18÷9=2 余 0
按上面计算方法,行号是3,错误。列号是0,错误。
咱们发现,当出现9的倍数的点位号时,不正确了。
稍微分析一下就能够知道,当出现9的倍数的点位号,也就是点位号除以9余数为0时,行号就是两个数除法运算的结果,列号就是9。
因而,咱们就把这个规律找出来了。那么如何翻译成VBA语言,让计算机理解执行呢?
在VBA中的语法,
除法的运算符是 / 例如 18 / 9 = 2
除法取整的运算符是 \ 例如 14 \ 9 = 1
除法取余数的运算符是 Mod 例如 14 Mod 9 = 5
好,咱们来写代码:
计算行号的函数:GetRowIndex(ShuDuIndex As Integer) As Integer
计算列号的函数:GetColIndex(ShuDuIndex As Integer) As Integer
这里咱们依然用到了IIF函数。
算行号:先取模(取余数),余数为零,行号就是点位号除以数独尺寸9,就获得了行号;余数不为零,就直接取整数再 +1,就是他的行号。
算列号:先取模(取余数),余数为零,列号就是数独尺寸9,余数不为零,就取余数(取模),获得的就是列号。
容易理解吧~!?
下面咱们就来处理计算区号的问题:
这个问题我没有找到很好的算法,就用笨办法来处理吧!
计算区号的函数:GetBoxIndex(ShuDuIndex As Integer) As Integer
以第四行代码为例讲解:
这里用的Select Case 语法,当点位号等于1,2,3,10,11,12,19,20,21中的一个数字时,表示在第一区,返回区号1。其余以此类推。
也就是把全部区内的点位号都罗列出来,逐个判断这个点位在哪一个区。
也很好理解吧?~!
好,如今咱们已经能够经过点位号来获得这个点位所在的行号、列号和区号了,而后咱们就要统计这个行、这个列、这个区内存在的数字了。
这里咱们写三个函数来处理:
一、统计点位所在行中数字的函数(去掉0):
GetRowList(ShuDuIndex As Integer, ShuDuList As String) As String
第5行,第6行代码就是利用咱们以前写的函数,经过点位号计算出这个点位所在的行号和列号。这里虽然用不到列号,仍是写着吧!
第8行,用两个函数的嵌套来统计数字。
Mid函数,用来截取整行字符串。
Mid(ShuDuList, (R - 1) * ShuDuSize + 1, ShuDuSize)
翻译一下:若是R = 3,表示第三行,那么
红色部分:
(3 - 1) * 9 + 1 = 19,表示第三行起始点位号是19。
绿色部分:
ShuDuSize = 9,这在以前的公共变量里面已经定义了。
整句话的意思是:在ShuDuList字符串中,从第19个点位开始截取字符串,截取9个,这样就恰好把第三行的数字截取出来了。
GetShuDuList = 30050000000000034600200300000300020……
上面的红色部分。即:002003000
Replace函数,套在Mid函数的外面,用来将截取出来的字符串中的0所有删除。即获得:23 两个数字字符串。
这是一个很基础的小招数,这里不深刻探讨,你们有兴趣深刻研究能够在网上查阅相关资料或查看VBA参考手册。
Mid函数介绍:
Replace函数介绍:
二、统计点位所在列中数字的函数(去掉0):
GetColList(ShuDuIndex As Integer, ShuDuList As String) As String
这里没法用Mid函数一次性来截取,由于他跳行了,不是连续的,因此,咱们只能用For循环来截取,For循环的步长Setp = ShuDuSize 即步长为9,经过循环截取,这样以来,咱们就能够获得整列的数字,而后执行第10行代码,去掉里面的0。
三、统计点位所在区中数字的函数(去掉0):
GetBoxList(ShuDuIndex As Integer, ShuDuList As String) As String
方法跟上面相似,只是咱们这里专门为获取区块内的数字写了一个函数:JoinBoxList(BoxIndex As Integer, ShuDuList As String) As String
一样是经过Mid函数来截取,最后用Replace函数来去掉里面的0。
最后的结果:
以标绿的单元格3行,4列,2区为例,在VBE本地窗口能够看到,
23:就是红框框第三行里的数字,删掉了0。
58319:就是橙色框框第四列里的数字,删掉了0。
53:就是蓝色框框第二区里的数字,删掉了0。
程序计算没有问题。
好,如今咱们能够经过
GetShuDuList(Row As Integer, Col As Integer) As String 获得数独模型列表
GetRowList(ShuDuIndex As Integer, ShuDuList As String) As String 获得点位所在行中的数字列表。
GetColList(ShuDuIndex As Integer, ShuDuList As String) As String 获得点位所在列中的数字列表。
GetBoxList(ShuDuIndex As Integer, ShuDuList As String) As String 获得点位所在区中的数字列表。
下面我就就要统计空表格所对应的行号、列号、区号和可能填入的数字列表。
咱们写一个函数来处理:
CanPointInfo(ShuDuList As String) As Variant (核心函数)
解释:
第3行:ReDim CanPointList(1 To 1) As Variant
定义一个一维可变数组 CanPointList,用来存放每一个空点位的相关信息。
第5行、第6行。定义一个ArrId = 1,用于表示数组下标,最后进行递增,以达到扩充数组的目的。
第7行,定义一个一维数组Point,用来存放空点位的相关信息。
第10行,作一个For循环,从1循环到81,遍历整个数组模型列表。
第11行,循环过程当中,利用Mid函数,逐个取出数组模型列表中的数字,若是等于0,则表示数独中这个点位是空的。
第12-15行,经过以前写的函数获得行号,列号,区号。
Point(1) 存放行号信息
Point(2) 存放列号信息
Point(3) 存放区号信息
Point(4) 存放行可能填入的数字信息,这里先让他为空,后续再处理。
第17-23行,定义三个字符串变量,经过以前写的函数,获得点位所在行、所在列、所在区的数字信息。
第25-32行,经过一个循环语句,从1循环到9,而后比对行中的数字,列中的数字,区中的数字,若是都没有出现,表示这个数字是可能填入的数字,而后将可能填入的数字压入Point(4)里面。
第34行,把整理好的Point存入扩充后的CanPointList里。
例如:
标灰的单元格,点位号是29,经过计算能够获得他的行号、列号、区号。
即:
Point(1) = 4,在第4行。
Point(2) = 2,在第2列。
Point(3) = 4,在第4区。
而后统计行、列、区的数字:
RowNumList = “32”
ColNumList = “139”
BoxNumList = “316”
而后运行25-32行代码:
数字1:在ColNumList、BoxNumList 中都存在,舍去;
数字2:在RowNumList 中存在,舍去;
数字3:在RowNumList、ColNumList、BoxNumList 中都存在,舍去;
数字4:都不存在,能够保留。执行第30行代码;
数字5:都不存在,能够保留。执行第30行代码;
数字6:在BoxNumList 中存在,舍去;
数字7:都不存在,能够保留。执行第30行代码;
数字8:都不存在,能够保留。执行第30行代码;
数字9:在ColNumList中存在,舍去。
循环结束后,Point(4) = “4578”
最后整理一下:
Point(1) = 4
Point(2) = 2
Point(3) = 4
Point(4) = “4578”
即:4行3列这个单元格对应的区位号是4,这个单元格可能填入的数字是 4578中的任何一个。
而后执行第33-35行代码:
ReDim Preserve CanPointList(1 To ArrId) As Variant
保留原有数组内的信息,扩充数组,并存入刚才获得的Point信息,而后CanPointList数组下标自增,以便于后续扩充,便于填入新的数据。
当第10行到第37行代码循环执行完后,那么这个数独的全部点位都遍历完了,而且把全部空单元格的信息都已经统计出来,并存入了CanPointList数组中了,这时,CanPointList数组就变成了一个二维数组。
好了,如今咱们经过
GetShuDuList 函数获得了整个数组模型列表
CanPointInfo 函数获得了每一个空点位的信息和可能填入的数组信息。
下面咱们咱们就要考虑,若是咱们从CanPointInfo拿出一个点位信息,填入到GetShuDuList 列表中,而后,咱们就要检查咱们填入的这个数字,在这个点位上的行中、列中,区中是否存在,若是不存在,咱们就能够填入,若是存在,就不符合数独规则,就要退出来,换另一个可能填入的数字,并恢复以前尝试的数字,而后进行下一轮,继续尝试。
这个检查的过程,咱们依然须要写函数来实现。
这里,咱们写一个Check函数,用来检查填入数字是否合法。
解释:
第3-6行,判断尝试填入的数字是否为0,若是是0,则表示还未赋值给尝试的点位,直接返回False,表示数据非法。
第9行,这里是经过点位信息里的行号和列号计算出数独模型列表的索引位置,为了程序的简洁,我这里写了一个函数来处理。
这个算法很容易理解啊,很少说。
第11-17行,经过以前写的函数,获取点位所在行、所在列、所在区中的数字列表。
第19-25行,跟以前的算法同样,经过InStr函数来判断尝试的数字是否存在,若是不存在,就都等于0,则返回True,表示这个数字能够填入,不然,返回False,表示这个数字非法。
好,到这里,咱们就要考虑如何从CanPointInfo 数组中取一个点位信息,存入GetShuDuList 列表中,并用Check函数来检查了。
从CanPointInfo 数组中取点位信息,有不少方法,从前面取也能够,从后面取也能够,从中间取,也能够。这里为了知足未来递归算法的要求,咱们从最后一个来取,这样取也不会打乱前面数组结构信息。由于从前面取或从中间取点位信息,CanPointInfo 数组结构会发生变化。
那么如何从最后一个数组元素取信息呢?在别的编程语言里面有专门的函数,像Python中,他自带一个pop()方法,能够取出数组最后一个元素,取出后,并将原数组中最后一个元素删除。
而VBA则没有这个方法,因此,咱们又得本身写函数来实现了。
首先,咱们写一个获得数组最后一个元素的函数。
GetArrLast(Arr As Variant) As Variant
很简单的一个函数,一句话搞定。
用UBound方法获得数组的最大下标,而后取出,并返回。
再写一个删除数组最后一个元素的函数。
DelArrLast(Arr As Variant) As Variant
解释:
第11行,经过LBound获得数组的最小下标,经过UBound获得数组最大下标,而后ReDim Preserve 从新定义动态数组,并保留数组原始信息,让最大下标减1。就等于把最后一个元素删除了,而后再返回。
这里须要注意,当数组只剩一个元素的时候,最小下标和最大下标都是1,若是最大下标再减1,这个数组就会在从新定义时报错。我当时在调试程序的时候,这个地方报错,我查了好久才查出来是这里的问题,后来我就用了一个条件判断语句来处理这个问题。
第6-10行,若是最大下标等于1,咱们就给这个数组的头两个元素赋值为0。
就至关于
Point(1) = 0
Point(2) = 0
也就是说,这个点位信息的行号为0,列号为0。正常状况下,点位信息的行号和列号不可能为0,这里咱们强行的赋值为0,之后在递归的时候,就能够以此为判断依据,若是行号等于0,那么这个递归就要结束了,能够做为递归终止的判断,防止发生死循环,致使系统崩溃。
如今咱们完成了从数组最后一个元素取值的函数,也完成了删除数组最后一个元素的函数。下面咱们就要考虑,若是取出来后,尝试不行,咱们又要恢复数组原始状态,咱们还要考虑在数组最后增长元素信息的方法。Python中,他自带一个append()方法,能够很容易的实现。而VBA没有,因此,咱们仍是得写函数来完成。
函数名称:AddArrLast(ArrSub As Variant, Arr As Variant) As Variant
一样用到了ReDim Preserve,保留数组原始信息,扩充数组的方法。以前已经讲过了,这里再也不重复。
接下来,咱们考虑如何把咱们准备填入的数字放到数独模型列表中的问题。
若是是数组,就很容易处理,可是以前咱们考虑过,用数组虽然很好处理这个问题,可是处理其余问题就比较麻烦,因此,最后咱们这里的数独模型列表并无用数组,而是用的字符串。如今咱们就要考虑如何把咱们须要填入的数字,替换到数独模型列表中的数字。
一说替换,你们可能立刻会想到用Replace方法。可是,这是不行的,由于咱们须要填入的数字在数独模型列表中都是以0来填充的,若是用Replace来替换,你究竟是要替换哪一个0呢?你不说清楚,计算机是不知道的,他会把全部的0都替换掉。固然,你也能够定位,可是定位后用Replace替换,他会把定位前的字符都删除掉,不知道这个函数他们是怎么设计的,为何要这么操做,也不是很清楚。因此,这也是不行的。
因而,咱们这里考虑用Left()和Right()函数来处理。
咱们写一个函数。
ReplaceMid(Str As String, RepStr As Variant, ShuDuIndex As Integer) As String
若是咱们须要替换掉第10个数字,那么咱们用Left取出左边的9个数字,Right取出第10个以后的全部的数字,而后左边的数字拼接上替换的数字,再拼接上右边的数字,就至关于把指定位置的数字替换掉了。很好理解吧?并且这样也比较简单高效。
详解:
好了,到此,咱们就要考虑在空单元格填入可能的数字并检查合法性的问题了。
这个问题很复杂,因此,必须得写个函数来处理。
TryInPoint(Point As Variant, ShuDuList As String, CanPointList As Variant) (核心函数)
解释:
第4行:取出点位信息中可能填入数字的列表。
第8行:循环取出可能填入的数字,每次取一个。
第9行:将取出来准备填入的数字放入Point(5)中。
第10行:利用咱们刚才写的Check函数判断合法性。
第11行:若是是合法的,就替换掉数独模型列表中对应的数字。
第12行:判断CanPointList数组是否已经到了最后一个,若是到了最后一个,即行号、列号等于 0 的时候,就调用 ShowOkShuDu函数,显示正确结果。
ShowOkShuDu这个函数咱们还没写。后面再写!
第16-18行:再从CanPointList中取出最后一个数组元素。
第20行:开始再次调用TryInPoint函数,递归尝试。
第22-25行:若是尝试出现非法数据,就恢复上一轮的操做。
到这里为止。咱们就把数独问题的核心算法写完了。剩下就是考虑如何把正确的数独结果显示出来了。
写一个显示数独结果的函数。
ShowOkShuDu(ShuDuList As String) (核心函数)
就是利用循环来展现,展现完后,执行第14行代码,弹出对话框,提示数独问题解答完成!
而后执行第15行代码,终止整个程序运行。
很容易理解,很少讲。
如今,数独问题,已经能够解决了。考虑到程序的强壮性和智能化。咱们还须要考虑一些其余的问题。
好比,在出题过程当中,是否已经存在同行、同列或一个区中存在重复的数字,即数独题目有误。这个咱们在做答前必需要检查一个题目是否有问题。
写一个检查数独题目是否正确的函数。
CheckShuDuOk(ShuDuList As String)
解释:
第8行:应为数独一共有9行、9列、9区,因此,咱们只须要循环九次,就能够检查全部的行、列、区了。
第9行:获得一行中的全部数字,并去掉0。
第12行:从1-9这九个数字,依次和行中的数字进行比较。若是等于0,则表示没有这个数字,尝试下一个;若是不等于0,说明里面存在,可是这样是否是就能够判断这个数字合法呢?不必定,由于你不知道里面究竟是存在一个仍是存在多个?存在一个,合法,存在多个,不合法,这是一个核心问题!!!
这个问题怎么弄?
用InStr和InStrRev这两个函数就能够搞定。
InStr 函数介绍:
InStrRev函数介绍:
好比:我这里有一个字符串:”351658”
当咱们检查到3时,发现不等于0,则表示里面有3。而后咱们在看是否只有一个。
InStr(”351658”, ”3”) = 1
从左往右找,3出如今第一个位置。
InStrRev(”351658”, ”3”) = 1
从右往左找,3也出如今第一个位置。
此时InStr和InStrRev的值相等,说明里面就只有一个3,合法。
当咱们检查到5时,发现不等于0,则表示里面有5。而后咱们在看是否只有一个。
InStr(”351658”, ”5”) = 2
从左往右找,5出如今第二个位置。
InStrRev(”351658”, ”5”) = 5
从右往左找,5出如今第五个位置。
此时InStr和InStrRev的值不相等,说明里面至少有两个5,非法。
好理解吧?!
如下检查列中的数字和检查区中的数字,方法相似,不在重复讲解。
还有一种错误:就是咱们在出题的时候,输入错误,输入了一个字母,或者输入了大于9或小于1的数字,这都是不符合数独游戏规则的,咱们也要将他判断出来。
因而,咱们还得写一个判断是否是数字的函数。
很简单,一句话搞定,经过IsNumeric函数来处理。而后用再判断是否大于9或小于1。
还有一种状况就是,数独出题区是满的,没有空单元格了,这也是一种错误。因此,咱们对GetShuDuList函数作些许修改。
解释:
增长第11行至18行代码,用来判断输入非法的字符和大于9小于1的数字。
增长第24行至27行,用来判断是否是满格数独。
好,如今咱们把这个数独问题的程序基本上写完了,而后咱们须要一个主程序来让他运行起来。
主程序-程序入口
ShuDuKu() (核心函数)
解释:
第2-3行:定义一个变量,赋值“此题无解”。当没有获得正确结果时,就会执行第24行代码,弹出“此题无解”的对话框。
第4行:公共变量赋值,表示数独尺寸为9X9的数独。
第5-8行:获得出题区的起始行、列号和做答区的起始行、列号。
第11行:调用GetShuDuList函数,获得数独模型列表。
第12行:调用CheckOkShuDu函数,检查出题是否正确。
第15行:调用CanPointInfo函数,获得空单元格的点位信息和可能存入的数字信息。
第18行:从CanPointList数组中取出最后一个数组元素,复制给点位信息数组。
第20行:调用DelArrLast函数,删除CanPointList数组的最后一个元素。
第22行:调用TryInPoint函数,开始尝试填入数字并检查合法性。
若是尝试完成,获得正确结果,在TryInPoint函数里面调用了ShowOkShuDu函数,最后有一个End语句,终止了整个程序,因此第24行就不会执行。
若是尝试完成,没有获得正确结果,则在TryInPoint函数中不会调用ShowOkShuDu函数,也就不会执行ShowOkShuDu函数中的End语句,程序会这行主程序的第24行语句,弹出此题无解信息。
好啦,如今整个程序就写完了。
固然,为了程序有更好的交互性,还能够在里面加入更多的人机交互的内容,好比如何绘制表格,如何给表格添加灰色背景,如何将出题区的内容复制到做答区等等,这不是这篇文章的重点,这里不详细介绍。
后面附上数独源文件,你们能够看源代码了解。
连接:https://pan.baidu.com/s/1KwKXaU0ipKCryriImP9zLQ
提取码:zd4x