一周学堂:学VB, 编“扫雷” 1

第1天 小按钮表面布阵,随机数底层埋雷 1

第2天 勇士斗胆赴雷宴,标签显数辨假真 4

第3天 空雷阵电脑较劲,定时卡表识真功 9

第4天 呆板小程遭嘲弄,编程还需自我批评 13

第5天 脑筋一转上帝笑,界面乾坤大挪移 15

第6天 善变是有趣的通行证,随机是程序员的座右铭 18

第7天 凤凰涅槃诞生神鸟,扫雷重识善待游戏 22

一周学堂:学VB, 编“扫雷”

几天前,和几位程序员朋友谈起一个问题:从什么程序开始学编程呢?你是从哪个程序开始学编程的呢?这是个很难回答的问题:)程序太多了,但要系统地做一个程序,我觉得扫雷就不错。

“扫雷”是一个非常酷的游戏,酷在三个方面。一方面,对于玩家来说,扫雷是一出集推理和幸运于一身的好戏,它非常残酷,也许在只剩下最后两颗雷的时候您却触雷了,没办法,只有玩完,残酷是游戏魅力之一;另一方面,对于编程爱好者来说,编出它来,玩自己的扫雷,那肯定是另一番感受;第三方面,就算您仅仅当一名扫雷的忠实玩家,读读下面的程序,对于您后面的扫雷工作肯定也会有相当大的帮助。

真地自己做出个扫雷游戏来却并不是很容易的事情,下面,我们就来做一个自己的扫雷游戏,用的语言是VB6.0。我们的准备工作很简单,请您先将自己电脑中的扫雷游戏调出来,将中级的扫雷版面挖完,最好是能挖出一盘高级来。

为什么要先玩呢?

我们的目的只有一个,请您尽快熟悉这个游戏,只有熟悉才可能下手。连一个游戏的游戏规则都不知却能编出这个游戏来,那是不可想象的。下面,我们的扫雷工作就开始了。

第1天 小按钮表面布阵,随机数底层埋雷

知识点:VB设计程序的特点

一个VB程序大体可分成两部分,窗体和代码。窗体和附着在它上面的控件组成了程序的界面,但只有这个表面还不行,还要有代码在后台对界面进行控制,让它们来完成我们想要的动做。通过VB编程可分为两大部分,界面设计和代码编辑,虽然是两部分,可二者是紧密相联的,可根据一部分的需要对另一部分进行调整;另外,我们工作时有两个窗口对应于两者的工作:对象窗口和代码窗口(通过菜单“视图”的相应选项)。

扫雷的过程中给您印象最深的是什么呢?给我印象最深的是格子,方方正正覆盖在桌面上的格子,那下面可能就可能是雷。当然在玩的过程中另外一种东东也很深,格子下面的数字,就是他们,可能就是这些东东,使得我们的挖雷工作有了头绪,也使得我们的工作一次次不得不从头重来。最好的办法是先将它们做出来!

一、做雷区格子和雷数目显示格子

格子用什么东西来表示呢?看上去,和命令按钮(CommandButton)最相似,就用它吧。命令按钮的下面呢?挖出的不是雷的地方要显示一个数字,好让我们继续,如果是雷,需显示出雷来,可以放上标签(Label),两个给人印象最深刻的东东解决了。

可是扫雷又不可能是一个按钮,那就太没意思的,先做一个8*8的吧。要用到64个按钮,为了便于整体处理,可以将放到一个组合框(Frame)中。所以步骤如下:

  1. 将窗体的caption属性改为:扫雷。
  2. 在窗体上放一个frame框frame1,caption为””(空)。
  3. 在窗体上的frame1中放入一个命令按钮,并将其命名为CmdMine,调整其长度和宽度都为250,其caption为空””。
  4. 在窗体上的frame1中放入一个标签,并将其命名为LblMine,调整其长度和宽度为250,250,其caption为””,还有appearance(外表)值设为0,BackColor(背景)设为和窗体背景相同的颜色,BoardStyle(边框样式)设为1,有边框。

将上面的属性调好,现在在空体上已经有一个命令按钮和一个文本框了,因为要做一个8*8的扫雷,所以一共需上述两类东东各64个,怎样做出另外的63个呢?可以通过复制来完成。

  1. 复制其它63个命令按钮

    选择cmdMine,然后选择“编辑”菜单中的复制,再选择Frame,然后选择“编辑”菜单中的粘贴。在粘贴完成后要调整好64个按钮的位置,其index的值0-7在一行上,8-15在一行,依次类推。

  2. 按照和命令按钮类似的办法再复制出其他的63个lblMine,其排列方式和命令按钮相似。

二、其他东东

因为挖雷是比较容易“不成功 便成仁”的工作,我们可以再设置一命令按钮CmdRestart,它的功能是重新开始游戏,为了更生动,将其表面设为图形的方式,一是要调整其Style(类型)属性为1,图形样式,再者还要调整picture(图片)为一个笑脸图标。设置上一文本框txtBnum用来放置雷的总数,一文本框txtTime用来放置挖雷所用时间。用两个标签来说明其作用,再用一个标签用来放鼓励的话语,其名称为lblView。

界面的工作就完成了(如图1示),可能您存在这样的疑问,将雷数目显示在下面,那挖起雷来还有什么意思?将来的界面和图上稍微不同的是,下面的雷数目显示框应该放在按钮的下面,不过这一步还是放在后面调节,这种放法有利于我们后面对程序进行调试,因为地雷究竟是一种什么情况,现在一目了然了,在扫雷过程中的玄秒之处也将一目了然。

图1 有了界面,我们运行一下程序,还像那么回事,可是只有界面显然是不行的,现在界面没有代码进行控制,所以死呆呆地什么也不能干。我们后面的工作就是使它们变得聪明起来。

有了上面8*8的雷区界面还是不能进行扫雷游戏的。要想扫雷,先要有雷可扫,什么是雷呢?怎样在雷区的下面放上地雷呢?这是我们下面要研究的问题。

三、布雷

要想布雷,就要和上面的程序界面相联系了,因为我们使用了控件的数组形式,cmdMine(0),cmdMine(1),…,cmdMine(63),最好我们的地雷也要和这些控件有一定的联系。设数组Flag(63)为地雷标志数组,有雷则其值是1,无雷则其值为0,正好和上面64个雷区命令按钮一一对应。

怎样布雷呢?因为有了上面的数组,布雷实质上就是给上面的数组中包含的64个量赋上一个值0或1。如果赋上的值是0,表示无雷。如果是1,则有雷。

如果我们以12颗雷做为基数,则布雷可通过下面的语句实现。

For i = 1 To 12 '随机布雷
Flag(Int(Rnd * 63)) = 1
Next I

这种布雷方法的缺点也是显而易见的,雷的总个数无法定下,不过好处是简单。

小贴士:随机数的意义

游戏的魅力在于其多样性,或者说是结果的猜不透,要实现这种猜不透,那最值得大书特书的是随机函数,它可能是计算机世界中唯一没有理性的东东了,在VB中,是RND。

四、做雷阵

说白了也就是雷数目的计算和显示。

既然有了雷,另一个问题是某一区域其周围雷的数目的显示,因为这是后面进行扫雷工作的依据,我们要根据这个数字判断,然后根据数字和数字间的关系来挖雷。如何显示的,要想显示比较简单,只要将lblMine的Caption属性设为那个雷数目就行了,所以首先要算出雷的数目,然后才能显示。雷的区域这么多,所以最好再有一个数组来保存某位置周围雷的数目,定义FlagNum()数组来盛放,也和64个雷区命令按钮一一对应。所以只要flag()中有值,那么FlagNum()中也一定有相应的值与之对应。

怎样建立这种对应关系呢?建立的过程也就是怎样计算某区域周围雷的数目的过程。雷的数目等于其周围8个格中的雷的总数,最少0,最多只有8。可在边角的区域又没有8个格,仔细分析一下,还得分类计算,有“四个角”、“四条边不包含角”和“非四角四边”组成9个区域,定义一函数 Function JudgeNum(ByVal X As Integer) As Integer来完成,其取值范围自然也就是0到8。

下面是求地雷数函数:

Private Function JudgeNum(ByVal X As Integer) As Integer
Select Case X
Case 0’左上角
 JudgeNum = Flag(X + 8) + Flag(X + 9) + Flag(X + 1)
Case 1 To 6 ‘上边不含角
 JudgeNum = Flag(X - 1) + Flag(X + 7) + Flag(X + 8) + Flag(X + 9) + Flag(X + 1)
Case 7 ‘右上角
 JudgeNum = Flag(X + 8) + Flag(X + 7) + Flag(X - 1)
Case 56 ‘左下角
 JudgeNum = Flag(X - 8) + Flag(X - 7) + Flag(X + 1)
Case 63’右下角
 JudgeNum = Flag(X - 1) + Flag(X - 9) + Flag(X - 8)
Case 8, 16, 24, 32, 40, 48 ‘左边不含角
 JudgeNum = Flag(X - 8) + Flag(X - 7) + Flag(X + 1) + Flag(X + 8) + Flag(X + 9)
Case 15, 23, 31, 39, 47, 55’右边不含角
 JudgeNum = Flag(X - 9) + Flag(X - 8) + Flag(X - 1) + Flag(X + 7) + Flag(X + 8)
Case Is < 55’非边非角部分
 JudgeNum = Flag(X - 9) + Flag(X - 8) + Flag(X - 7) + Flag(X - 1) + Flag(X + 1) + Flag(X + 7) + Flag(X + 8) + Flag(X + 9)
Case Else  'x < 63’最下边不含角
 JudgeNum = Flag(X - 9) + Flag(X - 8) + Flag(X - 7) + Flag(X - 1) + Flag(X + 1)
End Select
End Function

有了上面的函数,雷数就算出来了,显示只要将得到的值赋给标签lblMine()就行了。

下面,我们再运行一下程序,还是死呆呆的,什么也没有,其实现在程序中已经有雷了,只不过雷的数目还未正确地在各个标签上显示出来而已。

提示:不要忘记在使用前先对数组的进行定义

Dim Flag(63) As Integer '设置地雷标志数组

Dim FlagNum(63) As Integer '地雷数目记录数组

小贴士:

今天我们的工作不太多,两个方面,一个是做表面,第二个也是做扫雷的最基本工作,布雷,雷呢?怎么做雷阵呢?其实雷是个数字1,无雷的数字是0;还是数字,一个地雷分布图,根据前面的1、0,最终会布出可挖的雷区图。这些信息都存放在数组中,包括上面的两个,为了方便控制,我们雷区的格子—即命令按钮—也是数组形式的。

第2天 勇士斗胆赴雷宴,标签显数辨假真

知识点:如何写代码

写代码要和界面密切相连,因为代码就是控制界面上的内容的,首要问题是搞清问题执行的步骤,当然,还要选择恰当的数据结构,例如本例中我们用到了数组,这样有利于问题迅速得到解决。

再猛的勇士面对雷阵,最重要的还是智慧。有了上面的雷阵,我们再回头看一下扫雷的步骤是如何的呢?

要想扫雷,先要有雷可扫。所以工作有两步要做:第一步是布雷,第二步是扫雷。

有了计算雷数目的函数,还要看一下它究竟正确不正确,怎样看呢?实践是检验真理的唯一标准,将其在屏幕上显示出来,一切不就再清晰不过了吗?拿什么来显示呢?标签。显示在lblMine标签中,比较比较看一下一切就都明白了。

一、显示雷数目

怎样显示呢?可分两种情形:一是给窗体的载入事件的,即Form_Load(),这里代表第一局,还有给CmdRestart_Click()的,也就是第2到任意局。先看前者。

雷数的计算仅仅是针对无雷区域的,从扫雷的规则上我们可以看到,这里即使算出一个雷数来也没有意义。为了便于比较,我们将有雷的地方显示为9,无雷的地方则显示其周围实际的雷数目,这样我们可以手工分析上面的求某区域周围的地雷数函数是否计算正确,如果有问题,那究竟错在哪里。代码如下:

For i = 0 To 63
 If Flag(i) = 1 Then
   Bnum = Bnum + 1 '计算雷总数
   FlagNum(i) = 9  '雷显示为9
   LblMine(i).Caption = 9
  Else
   FlagNum(i) = JudgeNum(i) ‘计算周围区域雷数
   If FlagNum(i) <> 0 Then LblMine(i).Caption = FlagNum(i) ‘显示周围实际的雷数目
 End If
Next i

这种计算方法对于上面提到的Form_load 和 cmdRestart_click是同样的。

因为程序中还需对其他的控件进行调整,所以全部程序如下:

LblView.Caption = "努力哟!"
Bnum = 0 '雷数目
For i = 0 To 63
 LblMine(i).Caption = ""
 CmdMine(i).Caption = ""
  Flag(i) = 0
Next i
For i = 1 To 12 '随机布雷
Flag(Int(Rnd * 63)) = 1
Next i
For i = 0 To 63
 If Flag(i) = 1 Then
   Bnum = Bnum + 1 '计算雷数
   FlagNum(i) = 9  '雷显示为9
   LblMine(i).Caption = 9
  Else
   FlagNum(i) = JudgeNum(i)
   If FlagNum(i) <> 0 Then LblMine(i).Caption = FlagNum(i)
 End If
Next i
TxtBnum.Text = Str(Bnum)

其界面如图2所示,只要我们运行程序或单击命令按钮cmdRestart,雷区将不断变化,9代表是地雷的位置,而其他的数字则代表该区域实际的雷数。通过手工比较可以知晓我们上面求雷数的函数是否正确。

图2 通过人工判断,说明我们上面的计算雷数目函数没有问题,如果有问题,要将其调试到直到没问题为止,从图中我们也可以进一步看出,上面的界面安排事实上也为我们调试程序提供了方便。

二、合并界面,开始扫雷

地雷阵已经布好,并且每踩到一个区域的雷的数目也能显示出来了,下面就可以扫雷了。要“扫”其实也简单,就是使上面代表雷区的命令按钮也活起来,自然首要的是代码加给命令按钮CmdMine_Click,我们用鼠标单击的时候,就表示在挖雷了。扫雷有两种情况,碰到雷,则死亡,一局结束,如果不是雷,则显示提示的数字,直至碰到雷死亡或者挖完。

判断和标志自然还是上面我们布雷时使用的雷标志数组Flag(),显示时则是另外的一个数组FlagNum()。

Private Sub CmdMine_Click(Index As Integer)
If Flag(Index) = 1 Then  '不幸中弹身亡
  CmdMine(Index).Visible = False
  LblMine(Index).Caption = "B"
  LblView.Caption = "哈哈再来一回合!"
  For i = 0 To 63
     If Flag(i) = 1 Then
       CmdMine(i).Caption = "B"
     End If
  Next i

 Else '开始挖雷
  CmdMine(Index).Visible = False
  TxtBnum.Text = Str(Bnum)
  If FlagNum(Index) <> 0 Then '显示周围雷的数目
    LblMine(Index).Caption = FlagNum(Index)
   Else

  End If
End If
End Sub

这样,扫雷的雏形就有了,为了更加真实形象,还要合并界面,即将下面的lblMine显示周围区域雷数的标签,放到命令按钮的下面(如图3示)。为了更加方便调试,我们也可保持其原先的位置,这样看起来不很好看,但调试时比较好用。

这样,我们就可以玩扫雷了。

图3

三、边扫雷边改进

如果仅有上面的代码,这个游戏还是不够完善的,动手玩玩会发现它的不少缺陷,一是界面上的按钮在第二局时无法恢复,再者缺乏智能,在连续的无雷区域老是只能一次挖开一个格子的位置,这样对快速进行扫雷无意义。由于现在的扫雷程序还比较呆板,发现的问题还不是很多,下面就修改一下。

第一个问题很容易处理,只要给cmdRestart重新开局按钮加上64个按钮初始化的语句就行了。代码如下:

For i = 0 To 63
  CmdMine(i).Visible = True
 Flag(i) = 0
Next I

而挖雷的代码如下:

Private Sub CmdMine_Click(Index As Integer)’雷区按钮
If Flag(Index) = 1 Then  '不幸中弹身亡
  CmdMine(Index).Visible = False
  LblMine(Index).Caption = "B"
  LblView.Caption = "哈哈再来一回合!"
  For i = 0 To 63
     If Flag(i) = 1 Then
       CmdMine(i).Caption = "B"
     End If
  Next i

 Else '开始挖雷
  CmdMine(Index).Visible = False
  TxtBnum.Text = Str(Bnum)
  If FlagNum(Index) <> 0 Then '显示周围雷的数目
    LblMine(Index).Caption = FlagNum(Index)
   Else
  End If
End If
End Sub

由于扫雷是一个极易中途“不成功而成仁”的节目,开局有两类,除了程序的第一次执行,对应于Form_load的代码,还有死亡后的重新开局,即CmnRestart_Click()部分的代码,两处代码基本一致,都起到将整个扫雷的系统初始化。其代码如下:

Private Sub CmdRestart_Click() ‘重新开始按钮,即第二次往后执行程序
LblView.Caption = "努力哟!"
Bnum = 0 '雷数目
For i = 0 To 63
 LblMine(i).Caption = ""
 CmdMine(i).Caption = ""
 CmdMine(i).Visible = True
 Flag(i) = 0
Next i
For i = 1 To 12 '随机布雷
Flag(Int(Rnd * 63)) = 1
Next i
For i = 0 To 63
 If Flag(i) = 1 Then
   Bnum = Bnum + 1 '计算雷数
   FlagNum(i) = 9  '雷显示为9
   LblMine(i).Caption = 9
  Else
   FlagNum(i) = JudgeNum(i)
   If FlagNum(i) <> 0 Then LblMine(i).Caption = FlagNum(i)
 End If
Next i
TxtBnum.Text = Str(Bnum)
End Sub

Private Sub Form_Load() ‘第一次执行程序
 ‘代码同上面基本一致,在此省略
End Sub

将代码修改完成,就可以玩一下试试了,但试后会觉得还不是很好用,第二个问题也就出现了。后面我们重点先处理一下第二个问题,即如何挖开空白区域。

小贴士:

程序是修改出来的,将两部分归位,一个简单的扫雷就有了。修改用一个术语来说叫做调试,调试的最简洁的办法是看到结果的直接形式,有了这种形式,对可能出现的错误结果,就容易分析可能出错的原因了。

第3天 空雷阵电脑较劲,定时卡表识真功

知识点:子程序

子程序也是一种强有力的工具,程序中功能性较强的部分或者重复较多的部分我们可以将其定义成一个子程序,在程序的相应部分只要调用它就可完成相应的功能,子程序最后不返回值。可以和上面用到的函数对比一下。

一、无雷区域处理

扫雷过程中可能碰到大片的无雷区域,这些区域再一个按钮一个按钮地点就太费时费力了,与我们有趣的初衷不符,因为这样的区域根本不用动脑筋,所以这部分最好让电脑来处理。

无雷时证明该处周围8个区域均无雷,可以放心大胆地挖开,编了一个子程序来实现。其计算区域有:“四个角”、“四条边不包含角”和非四角四边组成9个区域,可通过函数JudgeNo(x)来实现,无雷地区处理子程序可分为9类情况。其程序特点和前面的JudgeNumber()函数非常相似。

'无雷地区处理子程序 9分类情况
Private Sub JudgeNo(ByVal X As Integer)
Select Case X
Case 0’左上角
CmdMine(X + 8).Visible = False
CmdMine(X + 9).Visible = False
CmdMine(X + 1).Visible = False
Case 1 To 6‘上边不含角
CmdMine(X - 1).Visible = False
CmdMine(X + 7).Visible = False
CmdMine(X + 8).Visible = False
CmdMine(X + 9).Visible = False
CmdMine(X + 1).Visible = False

Case 7 ‘右上角
CmdMine(X + 8).Visible = False
CmdMine(X + 7).Visible = False
CmdMine(X - 1).Visible = False
Case 56‘左下角
CmdMine(X - 8).Visible = False
CmdMine(X - 7).Visible = False
CmdMine(X + 1).Visible = False
Case 63’右下角
CmdMine(X - 8).Visible = False
CmdMine(X - 9).Visible = False
CmdMine(X - 1).Visible = False
Case 8, 16, 24, 32, 40, 48‘左边不含角

CmdMine(X - 8).Visible = False
CmdMine(X - 7).Visible = False
CmdMine(X + 1).Visible = False
CmdMine(X + 8).Visible = False
CmdMine(X + 9).Visible = False

Case 15, 23, 31, 39, 47, 55‘右边不含角
CmdMine(X - 8).Visible = False
CmdMine(X - 9).Visible = False
CmdMine(X - 1).Visible = False
CmdMine(X + 8).Visible = False
CmdMine(X + 7).Visible = False

Case Is < 55‘非边非角

CmdMine(X - 8).Visible = False
CmdMine(X - 9).Visible = False
CmdMine(X - 7).Visible = False
CmdMine(X - 1).Visible = False
CmdMine(X + 1).Visible = False
CmdMine(X + 7).Visible = False
CmdMine(X + 8).Visible = False
CmdMine(X + 9).Visible = False

Case Else  'x < 63‘下边不含角
CmdMine(X - 8).Visible = False
CmdMine(X - 9).Visible = False
CmdMine(X - 7).Visible = False
CmdMine(X + 1).Visible = False
CmdMine(X - 1).Visible = False

End Select

上面的子程序特点和某区域周围雷数计算的函数是完全相同的。

子程序放在哪里呢?自然是挖雷的已挖开的部分,也就是CmdMine_Click()代码的第一个分支的else部分。即:

Private Sub CmdMine_Click(Index As Integer)
If Flag(Index) = 1 Then  '不幸中弹身亡
  CmdMine(Index).Visible = False
  LblMine(Index).Caption = "B"
  LblView.Caption = "哈哈再来一回合!"
  For i = 0 To 63
     If Flag(i) = 1 Then
       CmdMine(i).Caption = "B"
     End If
  Next i

 Else '开始挖雷
  CmdMine(Index).Visible = False
  TxtBnum.Text = Str(Bnum)
  If FlagNum(Index) <> 0 Then '显示周围雷的数目
    LblMine(Index).Caption = FlagNum(Index)
   Else
    '将为无雷地周围挖开,并显示各数
   JudgeNo Index
  End If
  For i = 0 To 63
    If CmdMine(i).Visible = False And FlagNum(i) = 0 And Flag(i) <> Index Then

       JudgeNo i '处理已挖开雷区,在此处引用函数

    End If
   Next i
  End If
End Sub

有了这个程序,扫雷游戏就变得聪明了许多,为我们后一步的调试也省去了不少事。如果运气好,可以一挖挖开一大片。下图就是一下挖开的!

让扫雷更加生动有趣

其实我们已经把扫雷做好了,只是它的功能还欠缺一些,把上面的小游戏多玩几局,不难发现下面的缺陷。 游戏已结束却不知结束。“用时”框还是死的。鼠标右键还未利用上。

二、定时器解决时间问题

前面的两个问题,和时间直接相联系,在窗体上放一个时间控件timer1,将其interval(时间间隔)设为1000,也就代表1秒种,再在时间控件中加入如下代码:

Private Sub Timer1_Timer()
 TxtTime.Text = Str(clock)
 clock = clock + 1
  If Fbnum = 64 - Bnum Then
    LblView.Caption = "真厉害,您赢了!"
  Frame1.Enabled = False
  Timer1.Enabled = False‘正常扫完雷时结束时间记录
 End If
 Fbnum = 0
 For i = 0 To 63
  If CmdMine(i).Visible = False Then
    Fbnum = Fbnum + 1
  End If
  Next i
End Sub

Clock作为计时变量,需要在代码开始位置进行声明。将上面代码放上,还是不能很好地表示。需做一些调整。

在cmdRestart_click中需加入对frame1的属性控制,并将Clock值重新清0。即:

clock = 0 '计时清零
Frame1.Enabled = True

此外,还需对计时器timer1的作用时间进行控制,当点完第一个扫雷按钮后完成,当扫雷结束时停止。所以时间控件的可用属性Enable还要在各部分进行调整,刚开始载入时为False(不可用),当执行cmdMine_Click时设为True,在具体扫雷过程中如果踩了雷则又不可用。在时间控件的判断中应再加入其结束,即扫完全部雷的时间控件的停止计时由时间控件自己控制。而Restart重新开始每局时,开始timer1是不可用的。

三、鼠标右键利用

鼠标右键还闲着,功能不用也是种浪费,怎样调整呢?

设置一数组FlagRMouse(i),我们只定义一个简单的功能标志地雷,其值有两个,True或False,前者表示已标志为雷,后者表示未标志为雷。其代码如下:

Private Sub CmdMine_MouseDown(Index As Integer, Button As Integer, Shift As Integer, X As Single, Y As Single)
'定义右键功能,右键的BUTTON值为2
i = Button
If i = 2 And FlagRMouse(Index) = False Then
   CmdMine(Index).Caption = "?"
   FlagRMouse(Index) = True
 Else
   CmdMine(Index).Caption = ""
   FlagRMouse(Index) = False
End If
End Sub

在初始载入Form_Load和cmdRestart_click刚开始要对其进行初始化。

这样,我们的扫雷游戏就宣布完成了。下面我们就挖吧,对其中不尽如人意的地方可再进行一些小小的调整。

小贴士:

函数和子程序是我们在编程中经常用到的东西,它们可以将一些功能相对独立的部分做成一个整体,便于我们使用,也使主程序显得更加短小精悍。

第4天 呆板小程遭嘲弄,编程还需自我批评

知识点:功能简化

要做界面,在Windows中带的扫雷比较麻烦,一下子全部做出来有困难。怎么办呢?有两种办法,一是更加深入地了解要编程的对象,这里,我们需对扫雷的游戏规则有深入的了解,对扫雷的运行过程也要有个详细的了解;当然还有更重要的方法是:功能简化,如果问题复杂,那就化繁就简,先实现简单功能,再实现复杂的功能。

一、呆板的扫雷

8*8雷区的扫雷游戏完成了,将其命名为V1.0,总结一下,最大的优点是呆板,最大的缺点也是呆板。有许多不尽如人意的地方,我们可进行一些细微的调整。从上面3天的工作中,觉得扫雷无非如此,编出来也不是什么难事。

细心观察V1.0,数组扮演了重要的角色,先后用到了五个数组,两个控件数组CmdMine 和LblMine,分别用来实现扮演扫雷的表面和周围雷数目的显示。还有三个控制数组,一个用来布雷Flag(63) ,一个用来记录根据前面的雷标志数组计算出的周围的雷数目, FlagNum(63),这两个构成了本程序的核心,我们的工作其实就是围绕着它们两个做的,正是由于这四个数组有种一一对应的关系,牵一发而动全身,使我们实现起扫雷来变得简单,找起规律来变得得心应手。

对于计算雷数数组又是重点中的重点,因为情况不同,计算雷的方法也是不同的,总结一下,总计有九种,我们通过一个函数来实现了。并且这种分类方法对于我们后面电脑挖开空白雷区也有启示作用。

最后一个数组是FlagRMouse(63),记录右键使用状态标志,这是对功能的一种扩充,也是对上面的四个数组的对应关系的一种综合运用。

为了更好地帮助玩家来扫雷,我们还在上面添加了计时计时信息,及完成的判断,雷数目显示等功能。

V1.0就这样实现了,可是在进行小小的调整不知您想过没有?能不能对上面的扫雷进行一下大一点的调整呢? 我们不得不遗憾地说,不能!如果我们不转换一下思路,是不可能的。而对我们编出的扫雷,欣喜过后,下面要做的工作是在批判、检讨和继承。V1.0的扫雷游戏如果不是我自己写的话,那要笑掉大牙,怎样将令人发笑的部分找出呢?下面我们就开始批判吧。

除了8*8,m*n如何,显然,上面的程序是不能的,因为一切都是固定下来的,除了8*8,别的什么也干不了。

这就是我们V1.0最大的毛病。不过从另一个角度想,我们能将8*8的实现,那m*n的从理论上和它应该相似,我们如果从特殊推广到一般,那m*n的扫雷不也就出来了。

这正是后面要做的工作。在做工作之前,您需要做的是再重新审视一下上面的程序,将和8及8*8相关的数据找一下,有7,63,8等的再重新看一下,能不能找出个完整的替换方案呢?这就是我们后面要做的工作 。

二、批判中前进到V2.0

当我们返回头去瞧扫雷V1.0时,不难发现这个扫雷实在是太笨,64个按钮,虽然是通过复制的手段去做的,也仍然很笨,只要有了一个,通过程序将其他的做出不就行了吗,这就是V2.0的工作。对于编程的区域,为什么又仅仅局限在8X8的格子中呢?又一个太笨的理由。我们先看一下如何将8*8的格子做出来,所以程序的界面变得要简单许多:

原来的64个按钮(CommandButton)和64个标签(Label),变成了1个按钮和1个标签,但注意这两个控件都要用其数组的形式,其index值均为0,作用和V1.0无二,还是用来当雷区按钮和显示雷区信息。

界面中还包含有对窗体上其它各控件的位置调整,但按钮的调整是第一重要的,如何调整呢?

因为已经有了一个按钮cmdBoom(0),要做出其他的按钮来,一是要知晓按钮的个数,二是将其位置进行调整,以堆集成四四方方的扫雷区。

  1. 先加载load cmdBoom(i)
  2. 计算并调整其位置

代码如下:下面的代码加给FormLoad中前面部分就行了。

'界面调整
 CmdMine(0).Left = 50 
 CmdMine(0).Top = 150 
 LblMine(0).Left = 50 
 LblMine(0).Top = 150  
 Frame1.Width = 100 + 8 * 250 '组合框宽度调整
 Frame1.Height = 200 + 8 * 250
 TxtBnum.Top = Frame1.Height + Frame1.Top + 250 '文本框位置调整
 'TxtTime.Top = Famboom.Height + Famboom.Top + 250
 TxtTime.Top = TxtBnum.Top
 Label1.Top = TxtBnum.Top '标签
 Label2.Top = TxtBnum.Top
    For k = 1 To 63
      Load CmdMine(k)
      '布雷按钮
      '雷数显示框 
      Load LblMine(k)
      j = Int(k / 8)
     '雷数显示框
      LblMine(k).Left = LblMine(0).Left + (k Mod 8) * (LblMine(0).Width - 5)
      LblMine(k).Top = LblMine(0).Top + j * (LblMine(0).Width - 5)
      LblMine(k).Visible = True
     '布雷按钮
      CmdMine(k).Left = CmdMine(0).Left + (k Mod 8) * (CmdMine(0).Width)
      CmdMine(k).Top = CmdMine(0).Top + j * (CmdMine(0).Height)
      CmdMine(k).Visible = True
  Next k

现在我们再运行程序,和原先的感觉差不多,不过其实我们的程序已经在发生变化了,界面从原先的手工制作,变成了程序制作了。

小贴士:

简化并不是简陋,简化的目的是为了更清楚地看清复杂的问题,把握其实质,以进一步解决该问题。另外,当我们遇到一个问题重复三遍以上时,还要考虑一下能不能用程序给解决掉。

第5天 脑筋一转上帝笑,界面乾坤大挪移

知识点:通过变量来思考

变量是我们编程中经常用到的元素,它将可能的内容变化生动地表达了出来,以一种前所未有的姿态,以一种变化的姿态,来面对我们这个不断变化的程序世界。

  1. 以变量的变化应对现实情况变化

    用变量的可变来应对变化,即用变量来取代具体数值。定义两个量来代表雷区的行数和列数:LineNum和RowNum。只要将这个量代表不同的值,我们实现任意大小雷区的设想就不远了,先通过这两个量来实践一下,看看在8*8区间上可行与否,然后再进行下面的调整。

    有了雷区行数和列数,雷区的按钮数BoomNum自然就成了LineNum*RowNum,而需要定义的按钮是从1到BoomNum-1。而0到BoomNum-1个按钮的位置,和列数直接有关,在程序中直接相联系的是一个数值8,因为8还可能代表行数,所以在做下面的转变时要分清这一点。好处行数在题中没有用到。所以将第8天的代码做一下修改,扩展到m*n的代码也就有了。

    除了对和界面相关的代码进行改编外,还需对两个子程序进行改编:一个是计算地雷数,再一个是挖雷过程中无雷区的处理。

    地雷数的计算和挖开雷区域的计算相似,都可分成V1.0中所列的九种形式。但这九种形式对于8*8的雷区我们直接通过一个多分支语句就可完成,由于对于任意的行乘列的形式RowNum*LineNum,问题就变得不太好描述,可先通过普通分支语句将其分成三大类:除以列数余数为0的情况;除以列数余数为(列数减一)的情况;其他情况。这三种情况各自又具有三种情况。其中计算地雷数和详细情况分类的函数如下:

    Private Function JudgeNum(ByVal X As Integer) As Integer
     If X Mod RowNum = 0 Then
      '0
      'rownum*(linenum-1)
      '其他
      Select Case X
        Case 0
         JudgeNum = Flag(X + RowNum) + Flag(X + RowNum + 1) + Flag(X + 1)
        Case RowNum * (LineNum - 1)
         JudgeNum = Flag(X - RowNum) + Flag(X - RowNum + 1) + Flag(X + 1)
        Case Else
         JudgeNum = Flag(X - RowNum) + Flag(X - RowNum + 1) + Flag(X + 1) + Flag(X + RowNum) + Flag(X + RowNum + 1)
        End Select
     Else
       If X Mod RowNum = RowNum - 1 Then
        'rownum-1
        'rownum*linenum-1
        '其他
         Select Case X
         Case RowNum - 1
           JudgeNum = Flag(X + RowNum) + Flag(X + RowNum - 1) + Flag(X - 1)
         Case RowNum * LineNum - 1
           JudgeNum = Flag(X - 1) + Flag(X - RowNum - 1) + Flag(X - RowNum)
         Case Else
           JudgeNum = Flag(X - RowNum - 1) + Flag(X - RowNum) + Flag(X - 1) + Flag(X + RowNum - 1) + Flag(X + RowNum)
         End Select
        Else
         '1到rownum-2
         'rownum*(linenum-1)+1 到rownum*linenum-2
         '其他
         Select Case X
         Case 1 To RowNum - 2
            JudgeNum = Flag(X - 1) + Flag(X + RowNum - 1) + Flag(X + RowNum) + Flag(X + RowNum + 1) + Flag(X + 1)
         Case RowNum * (LineNum - 1) + 1 To RowNum * LineNum - 1
           JudgeNum = Flag(X - RowNum - 1) + Flag(X - RowNum) + Flag(X - RowNum + 1) + Flag(X - 1) + Flag(X + 1)
         Case Else
           JudgeNum = Flag(X - RowNum - 1) + Flag(X - RowNum) + Flag(X - RowNum + 1) + Flag(X - 1) + Flag(X + 1) + Flag(X + RowNum - 1) + Flag(X + RowNum) + Flag(X + RowNum + 1)
         End Select
       End If
    
    
    End If
    End Function
    

    另外的无雷区处理子程序和上面的函数类似。总计也是九种情况,这里就不再细说了。

    经过上面的替换完成,我们原先的V1.0骨子里已发生了变化。不过,距真正的质变还有段距离,还需要下面继续做一些工作。

    上面我们虽然已经调整了程序中的大部分代码,但距离我们发生m*n还有距离,我们无处下手去进行这方面的调整,怎么办呢?必须调整界面,将这方面的功能设置项加入,不然,再好的金子没有发光的时候,只能烂在地里了。

  2. 以变化的界面设置应对变化的代码

    怎样进行设置呢?可以通过菜单项,为了达到自定义行数、列数和雷数,最好还要加上个窗体。我以要使m*n发生,需建立菜单,另建立窗体。

    通过菜单编辑器建立菜单:

    一级菜单有两个mnuGame和mnuHelp,标题分别是“游戏”和“帮助”,游戏下面有三个选项:mnuStart初级,mnuMidLevel中级 和mnuSetup自定义。

    因为初级中级都是固定的值,容易设定,自定义还要和一个窗体相联系。

    通过工程菜单中的添加窗体,新建一个名为frmselfset的窗体,并在窗体上加入两个命令按钮,两个标签和两个文本框,如图4所示:

    图4

    通过它进行自定义的设置。

    因为是在两个不同的窗体间传递变量,所以应将自定义中的三个值设为公用型变量,放在一模块中。 在“工程”菜单中选择添加模块,在模块中做如下声明。

    Public RowNum, LineNum, Fbnum   As Integer '行个数,列个数,雷个数
    

    而各菜单项的代码如下:

    Private Sub mnuStart_Click() '初级菜单项
    Call CmdRestart_Click
    End Sub
    Private Sub mnuMidlevel_Click() '中级菜单项
    RowNum = 16
    LineNum = 16
    Fbnum = 40
    Call CmdRestart_Click
    End Sub
    Private Sub mnuSetup_Click() '自定义菜单项
     Frmselfset.Show 1 '自定义设置窗口
     Call CmdRestart_Click '重新调整雷数和雷区
    End Sub
    

    自定义窗口中的代码如下:

    Private Sub CmdConfirm_Click() '确定按钮
     Dim n1, n2, n3
     n1 = Val(TxtRowNum.Text)
     n2 = Val(TxtLineNum.Text)
     n3 = Val(TxtBoomNum.Text)
     '对行数列数和地雷数进行限定
     If n1 > 0 And n2 > 0 And n3 > 0 And n1 < 18 And n2 < 30 Then
        If n3 < n1 * n2 Then
            RowNum = n1
            LineNum = n2
            Fbnum = n3
    
    
    
        Me.Hide
    
    
    End If
    
    End If End Sub Private Sub Command1_Click() Unload Me End Sub

    有了上面的改动,现在再运行程序和进行设定时,就开始出现问题了,这些问题正是我们从8*8到m*n转变时所必须面对的。

小贴士:

变化的方案看似很难把握,但不是不可把握,程序亦然,把握好其中的变与不变,还愁找不到解决方案吗?

第6天 善变是有趣的通行证,随机是程序员的座右铭

一、数据结构跟随变化

前面将变量和界面都做了相应的调整,从原先的64个按钮变成了任意多个按钮,但不管怎样变,我们还是可用两个值来将其描述,雷区的行数和列数LineNum、RowNum,按钮的数量成了RowNum*LineNum,面对于各个控件的位置和这两个数字也是息息相关的。所以,我们的编程思路实质上是从8*8向RowNum*LineNum的转变。原先的一个特殊规律8*8要转变成一般的规律RowNum*LineNum。实际上在界面设计上已经体现出这一原则,和任意区域的扫雷相适应的是雷标志数组等三个数组应该适应这种转变,再就是对雷的数目的调整,也应适应这种转变。

  1. 题中用到的三个数组是首先要调整的对象

    设置地雷标志数组Flag()、地雷数目记录数组FlagNum()和记录右键使用状态标志FlagRMouse()是程序正常运行的基础,这三个数组也成了动态的了,其大小在程序运行过程中是可变的,所以再用固定数组的定义形式已经不能满足要求,而要改成动态数组,其值为0到RowNum*LineNum-1。

    动态数组首要的调整是在载入Form_load时的调整:

    ReDim Flag(BoomNum - 1)
    ReDim FlagNum(BoomNum - 1)
    ReDim FlagRMouse(BoomNum - 1)
    
  2. 随机布雷方面的调整

    雷的数目是可以调整的,也是确定的,而V1.0中雷的数目上下还有出入。这是一个需要调整的问题。本题中先将特定个数的雷fbnum在数组的前0到(fbnum-1)位置设好,然后将数组中的全部数据顺序打乱。

    下面是随机布雷子程序源代码。

    Private Sub Boomset()
    Dim k, tnum, t2num As Integer
    BoomNum = RowNum * LineNum
    For k = 0 To BoomNum - 1
      FlagRMouse(k) = False '初始化右键标志
      LblMine(k).Caption = ""
      CmdMine(k).Caption = ""
      CmdMine(k).Visible = True
      Flag(k) = 0
    Next k
    For k = 1 To Fbnum '布雷数目
     Flag(k) = 1
    Next k
    For k = 0 To BoomNum - 1 '随机布雷
      tnum = Int(BoomNum * Rnd)
      t2num = Flag(k)
      Flag(k) = Flag(tnum)
      Flag(tnum) = t2num
    Next k
    TxtBnum.Text = Str(Fbnum)
    
    
    For k = 0 To BoomNum - 1
     If Flag(k) = 1 Then Bnum = Bnum + 1 '计算雷数
    FlagNum(k) = JudgeNum(k)  '
    
    
     If FlagNum(k) <> 0 Then LblMine(k).Caption = FlagNum(k)
    Next k
    End Sub
    

    有了这个子程序,我们对CmdRestart_Click()和Form_load中相应的布雷部分都要进行相应的调整。

    调整完成后,我们再来玩扫雷,还是不行,只能玩8*8的,原因在哪里呢?因为在CmdRestart_Click中我们对数组还未进行重定义。

    这种重定义不仅在Form_Load中有,在CmdRestart_Click中仍然要有。再接下去执行,还是不行,界面数组又不适应了,怎样使界面再次适应呢?那只有在CmdRestart_click中加入两个界面重新加载界面数组lblMine()和lblMine。下面我们将继续探讨这个问题。

  3. 雷区扫钮分布与初始化子程序

    怎样解决上面的问题呢?将地雷标志数组Flag()、地雷数目记录数组FlagNum()和记录右键使用状态标志FlagRMouse()三个数组调整完成后,去执行任意区域雷区的扫雷还是不行,原因在哪里呢?正是另外两个控件数组CmdMine()和lblMin()在做怪,第一个是做地雷表面的格子的扫钮,第二个则显示某区域周围的地雷数目。

    我们直接再加载一次重定义一下不就可以了吗?它却没那么简单,原因在哪里呢?在重定义前,我们必须先卸载掉,然后才能重定义,其实我们在向任意调整的时候,有些东东还是要用的,所以没必要重复地进行加载和卸载,在从一个较小的区间到一个较大的区间时,只要加上那些不存在的,如果是从大的区域向小区域调,只要将多余的卸掉就行了。这就是我们的编程思路。

    因为这个程序在Form_load 和cmdRestart_click中都要用到,故我们最好还是将它抽象为一个子程序。 这种抽象过程我们要充分考虑控件的数组形式和普通变量数组形式的不同之处。

    为了更加全面,我们将下面子程序中包含了对界面的处理功能,这个子程序在程序中至少要用到两次。代码如下:

    Private Sub MineButton() '雷按钮及界面安排
     Dim k, j As Integer
      BoomNum = RowNum * LineNum '总雷数
     ReDim Flag(BoomNum - 1)
     ReDim FlagNum(BoomNum - 1)
     ReDim FlagRMouse(BoomNum - 1)
    '第一个雷按钮及雷数标签位置
     CmdMine(0).Left = 50 '720
     CmdMine(0).Top = 150 '360
     LblMine(0).Left = 50 '720
     LblMine(0).Top = 150  '360
     Frame1.Width = 100 + RowNum * 250 '组合框宽度调整
     Frame1.Height = 200 + LineNum * 250
     TxtBnum.Top = Frame1.Height + Frame1.Top + 250 '文本框位置调整
     'TxtTime.Top = frame1.Height + frame1.Top + 250
     TxtTime.Top = TxtBnum.Top
     Label1.Top = TxtBnum.Top '标签
     Label2.Top = TxtBnum.Top
     Form1.Width = Frame1.Width + 500 '窗体大小
     Form1.Height = Frame1.Height + 2200
        While maxmine <= BoomNum - 1
          maxmine = maxmine + 1
          k = maxmine
          Load CmdMine(k)
          '布雷按钮
          '雷数显示框
          Load LblMine(k)
    
    
       Wend
       While maxmine > BoomNum - 1 ''''''''
          k = maxmine
          Unload CmdMine(k)
          '布雷按钮
          '雷数显示框
          Unload LblMine(k)
          maxmine = maxmine - 1
       Wend
      For k = 1 To BoomNum - 1
         j = Int(k / RowNum)
         '雷数显示框
          LblMine(k).Left = LblMine(0).Left + (k Mod RowNum) * (LblMine(0).Width - 5)
          LblMine(k).Top = LblMine(0).Top + j * (LblMine(0).Width - 5)
          LblMine(k).Visible = True
         '布雷按钮
          CmdMine(k).Left = CmdMine(0).Left + (k Mod RowNum) * (CmdMine(0).Width)
          CmdMine(k).Top = CmdMine(0).Top + j * (CmdMine(0).Height)
          CmdMine(k).Visible = True
      Next k
        clock = 0
        TxtTime.Text = "0"
        LblView.Caption = "努力哟!"
        Bnum = 0
    End Sub
    

    制做界面要充分考虑load 特性和位置特性的不同,因为我们对于雷的位置设置是可以任意调整的,但是对于一个控件的装载在它未卸载之前却只能有一次。这就决定了我们在程序中对于二者要区别处理。加载load只能一次,而位置要根据实际情况变化。

    还有一个问题是多余控件的卸载unload,如果从一个比较大的区域往一个小的雷区域调整,多余的控件起不到好作用反而环了我们的大事,只有将它们卸载掉。

    这个子程序起到了安排扫雷界面的作用,不论从较大的区域向较小的区域转换还是反之,都是可以的,子程序被Form_load和cmdRestart_Click调用。为了传递当前的控件总的数量,设置一个变量maxmine来进行记录,那么,要想是上面的子程序正确运行,在Form_Load的开始处需设其值为0,在尾时其值自然就是RowNum*LineNum-1,而在cmdRestart_click()的代码的最后部分,也要将其值设为RowNum*LineNum-1,以为下一次扫雷服务。

    现在,我们的扫雷的基本工作就完成了,下面,我们还需对代码进行一些简单调整,以使其功能比较完善,减少错误的数量。

  4. 搜寻其他的缺陷与错误

    上面编出的扫雷V2.0中还有一些bug,需对其进行一下调试,我们已经有了程序的原型,那就在玩的过程中找出那些东东。

    需要看一下什么东东呢?左键的功能,右键的功能,菜单的功能。左键撞雷时的情形及撞雷后的情形,一局全部挖完可能出现的情形,如果很难挖,很难出现全部挖完的情形,可以将雷的数目设的少一些。总之,我们要尽量地将扫雷可能出现的各种情况都考虑到,我挖了挖,主要有如下的问题未解决。

    挖雷的过程中,虽然触雷身亡,但仍可继续前行。这不符合扫雷的情况,需纠正一下。

    这个问题并不难解决,只要调整组合框frame1的enable属性就可以了,在触雷对应的代码处加入:

    Frame1.Enabled = False
    

    而在cmdRestart_click()中则生新将其设置为可用就行了。

    Frame1.Enabled = True
    

    这样,V2.0就完工了。

小贴士:

程序代码与界面的变化是全方位的,其基础,还是我们前面做的8*8的呆板程序,要好好理顺其中的脉络,要不然,可能出现崩盘的危险!

第7天 凤凰涅槃诞生神鸟,扫雷重识善待游戏

知识点:编程素材选择

学习编程中非常重要的一点,要有好的编程素材。只有这样,才能做到有程可编。游戏是一个不错的选择,令你感兴趣的小游戏是非常好的编程素材。

一、从扫雷的两个版本看编程

两个版本的扫雷游戏就这样就都做出了,两个版本差别很大,从原先的麻烦(手式做64个按钮)和呆板(只有64)个按钮,到简洁(一个按钮)和灵活(任意多的按钮),编程其实就这么简单。其实在这中间还有一些细节。

第一步调整:将64个按钮手工制做到程序制做,从手工调位置到程序调位置。

这一步在本质上和V1.0没太大的区别,区别从程序执行上和我们玩上都看不到,但这一步中其实已包含了一种整体思考的思想,控件的位置是有联系的,内部也有一定的规律。行列关系是这一步的关键。

第二步调整:做一个任意长度任意宽度的雷区,但还是以8*8为蓝本。

只要对1的行列关系把握好了,只要将其中的和行列对应的位置放入我们的行列变量,这一步就完成了。此步是整体思考和发生质变的第一步。

第三步调整:初始载入的扫雷也就是程序运行时的第一次扫雷

将初始载入的部分,即Form_Load(),中和8*8相联系的部分调整。

第四步调整:第2到任意次的扫雷

在第三步和第四步找到其相同点和不同点,将共同点总结,合并成一个子程序,实际该步是对第三步的重调整和重新开始按钮的代码改写,改写的过程中自然是对和8*8相联系的数据用行列变量rowNum和LineNum来替换。该步调整完成,代码发生了质变,而界面还未发生。

第五步调整:将和8*8相联系的相应量进行调整和变换直到任意的量

数组要调整,界面也要调整,使程序运行过程中真正地可以直接利用行列值和地雷数完成一个新的游戏,这一步,界面发生变化,操作也发生变化,程序从外表到内部都了生了质变,也是调试任务最重的一个环节。

计算机干的工作是什么样的呢?麻烦和呆板的,而人们做的呢?动脑筋找规律,动手找办法,将麻烦的东东的麻烦部分用简单的计算机语言描述给电脑做,让呆板的部分通过系统的思考变得灵活起来。人的工作是命令性质的,是指挥的工作。

改动的项目总结如下:

  1. 命名,使程序有了比较好的风格。
  2. 数组使用方式,从固定数组到可变数组。
  3. 控件使用方式,控件数组的形式和普通变量数组不太一样,需加载,并且加载只有一次,不用的多余的,最好、卸载掉,不然它可能起不到好作用而成了在捣乱。
  4. 子程序或函数,将重复性的具有特定功能的工作,重新界定其功能,用统一的子程序或函数来实现,使程序结构更加清晰。
  5. 公共变量的作用。多窗体中,在模块中进行定义的公用变量是窗体与窗体间交流的通道。

其实通过两个版本的比较也不难看出,编程过程中刚开始的麻烦呆板也是正常的,暂时处理不了的东东,可以用低一级的要求处理完成。好多初学编程的朋友往往容易陷入这样的境地,自己的功夫不行,对自己的要求却特高,有了一个问题,想得万分复杂,想了几个月后还未下上手,到头来再慨叹程序太难编。这是眼高手低症。

有时还是要妥协一下的,化繁就简也有许多好处,既然太复杂的临时做不出,那就退一步,先做一个功能少的,变化也少的。这样,原先的很复杂的问题就变得简单和容易上手了。

二、期待V3.0的出现

有了V2.0,程序的95%以上的框架就有了,但这并不能说明它已是完美无缺的了。在玩的过程中我们不难发现它的这样那样的缺撼,怎样对其进行修改呢?下面我们再找一下看看。

  1. 不能左右键同时开工对已标雷区域进行处理;
  2. 挖雷过程中对空白区域的处理不彻底;
  3. 游戏排行榜还未设立。

下面,我们将以第1点为例,对2.0进行进一步改进,使这个游戏更加好玩。

怎样共用鼠标左右键呢?要想对其共用,必须了解编程的过程中二者的特点。在VB中左键的值是1,右键的值是2,那么二者同时按下时其值将成为3,但事实上这样是不可能的,要做到这两个键同时按下,除非是超人,否则我们很难做到。

所以实现左右键共用,也是有一个时间问题的,有一个键用的时间早一些,另一个晚一些,而我们就是通过计算二者的和值来实现左右键共用的。

既然明确了左右键共用的本质问题,下面就可以编程来解决了。究竟是针对哪一个事件来编程呢?对比click等事件,还是选择MouseDown和MouseUp两个事件来组合使用,这个事件中包含着对鼠标键的控制。再一个问题是针对哪一个控件来编程。在挖雷的过程中,应该是在已经控开一个区域的情况下,并且8个格中的雷的位置已判断好并做上了标志,所以执行左右键共用的控件不是命令按钮,而成了标签,也就是LabNum。

下面就是左右键共用的最主要代码:

Private Sub LabNum_MouseDown(Index As Integer, Button As Integer, Shift As Integer, X As Single, Y As Single)
 'v30左右键共用 挖雷
   If Button = 3 Then
       '因为两键同时按下非常人能为,所以 Print "SupperMan"

       Print "Supper Man"
   Else
       If OldButton + Button = 3 Then
           'Print "BingGo"
           ‘执行挖开其他区域
           If JudgeqNum(Index) = -FlagNum(Index) Then
             Call JudgeqNo(Index)
           End If

       End If
       OldButton = Button
   End If
End Sub
Private Sub LabNum_MouseUp(Index As Integer, Button As Integer, Shift As Integer, X As Single, Y As Single)
OldButton = 0 'v30左右键共用
End Sub

此功能在具体实现上,用到了一个函数和一个子程序,函数JudgeqNum()用来计算某已挖开雷区周围8格中已标志的地雷数目,只有这个数目同该区的地雷数相同时,才可将非雷区挖开,子程序JudgeqNo()则是用来挖开非雷区的。

不论上面的函数和子程序其结构和我们前面用到的某区域周围雷数目计算函数JudgeNum()和挖开大片无雷区的子程序JudgeNo()的结构上是相似的。

JudgeqNum()的实现比较简单,只要将右键记录数组Flagrmouse()和某区域周围的格子对应起来就行了,而对于JudgeqNo()的处理,需对原程序cmdboom_Click()子程序的最外层加上一个:

If FlagRMouse(Index) = False Then 
 ‘原程序
endif

通过这个分支对已标志为雷的按钮将不再挖开。相应的调整还发生在对右键记录数组的处理上:

Private Sub cmdboom_MouseDown(Index As Integer, Button As Integer, Shift As Integer, X As Single, Y As Single) '右键功能
'定义右键功能,右键的BUTTON值为2
i = Button
If i = 2 Then '右键 v3.0调整
 If FlagRMouse(Index) = False Then
   CmdBoom(Index).Caption = "?"
   FlagRMouse(Index) = True
  Else
   CmdBoom(Index).Caption = ""
   FlagRMouse(Index) = False
 End If
End If

这里的调整,可以保证flagrmouse()只有两种结果真(-1)和假(0)。

函数和子程序的代码如下:

  1. 函数JudgeqNum(),求?号数目函数,也即求已标出的地雷数目 v30用函数
  2. 子程序JudgeqNo(),已标志雷区处理处理子程序 ,9分类情况

通过上面的两个函数、子程序以及对主程序做的相应调整,这样,扫雷游戏的修改又进了一步,可以通过左右键共用挖雷了。

此功能的实现和右键状态记录数组FlagRMouse()密切相关,我们在程序中定义其为两种状态,True(-1),false(0),分别对应于有雷和无雷,正是因为该数组的存在,我们可以判断某格周围区域已标记(?)为雷的数目,如果此数目和该处通过放在flagnum()数组中对应的值相等,其它的未挖开区域可以直接通过程序来完成,而不用一个一个通过人将其挖开。

其他的细节问题在上面我们做了简单介绍,请通过源程序再仔细看一下。

三、热爱游戏,热爱编程

游戏是非常好的编程素材,为什么这么说呢?原因有四:

之一:玩乃人之天性,兴趣是人的最好的老师。

游戏恰恰将二者合而为一,真正体现了寓教于乐的思想,我编程是因为我感兴趣,更是因为我快乐。

君不见程序员们对待编程的热情所至,又不是一个热爱所能了得的。废寝忘食那是小Kiss,夜以继日那是寻常事。那里面也不仅仅是一个钱字能说明的,编程的目的其实在这种状态下,经济利益成了其次的事情。

编程是件苦差事,更是件乐差事,众多程序员的孜孜不倦的行动是最好的说明。

之二:有比写代码更重要的工作

编程,用来搞一下名词解释,无非是编写程序,又可理解为编写代码。其实在写代码之前还有非常重要的工作,那就是详细了解我们要对之编程的对象,用一个名词来说叫做系统分析。系统分析一般来说是一项枯燥繁琐可能出力不讨好的工作。对游戏编程来说可能就变了。

因为是游戏,所以都有“游戏规则”,这是游戏好玩的地方。踏踏实实地认识一下游戏规则,其实是在学习另一种玩法。何乐而不为呢。

我想,我们没有理由不选择它。

其三:游戏规则的把握与嬗变。

陈规陋习令人厌恶不堪,一个游戏放在了你的手底下,活儿随你的心意转变。也许一个新游戏一种新玩法又诞生了。

这叫做创造的过程。不论什么人,对创造都是有期望的,那种感觉和步人后尘是完完全全的两种感觉。那是一种成就感,虽小却使人觉得很舒坦。

其四:好的游戏,一个好的开始。

相信吧,选择一个好的游戏,你会有一个好的开始;请相信,在兴趣的强大支持下,苦与乐的结合下,你定会成为一名优秀的程序员。