使用VB.NET 2013的Windows完整数独游戏(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/complete-sudoku-game-for-windows-using-vb-net-2013-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 34 分钟阅读 - 16660 个词 阅读量 0使用VB.NET 2013的Windows完整数独游戏(译文)
原文地址:https://www.codeproject.com/Articles/832156/Complete-Sudoku-Game-for-Windows-using-VB-NET-2013
原文作者:Hung, Han
译文由本站 robot-v1.0 翻译
前言
This article is a tutorial on how to code your own Sudoku game using VB.NET
本文是有关如何使用VB.NET编写自己的Sudoku游戏的教程.
介绍(Introduction)
当数独在2000年代初流行时,我开始玩.在网络,报纸和iOS设备上玩了许多版本的游戏后,我决定看看是否可以编写自己的游戏.在4天的过程中,这就是结果.(I started playing Sudoku when it became popular in the early 2000s. After playing many versions of the game on the Web, newspapers, and on iOS devices, I decided to see if I could write my own. Over the course of 4 days, this is the result.) 该项目演示了几种编程概念,例如单例,共享代码,委托和事件,多线程以及MVC编程模式.(This project demonstrates several programming concepts like Singletons, Shared code, Delegates and Events, multi-threading, as well as the MVC programming pattern.)
背景(Background)
基本的数独游戏在9 x 9网格上进行游戏,该网格又细分为3 x 3迷你网格.游戏开始时会填充一些从1到9的数字的单元格.游戏的目的是填充空白单元格,以便每一行,每一列和3 x 3的迷你网格仅包含每个数字一次.这是一个示例难题.(The basic Sudoku game is played on a 9 x 9 grid that is subdivided into 3 x 3 mini grids. The game starts out with some cells filled in with the numbers from 1 through 9. The object of the game is to fill in the blank cells so that each row, column and 3 x 3 mini grid contains each number only once. Here is a sample puzzle.)
本文主要描述了游戏的组合方式以及我在此过程中做出的设计和代码决策的一些背景.(This article basically describes how the game was put together as well as some background on the design and code decisions I made along the way.) 编写游戏时,我使用MVC(即模型-视图-控制器)编程模式作为指南.它用于将程序的不同部分分成其逻辑组件.模型包含数据.视图包含所有与UI相关的代码.控制器包含业务逻辑,并将模型和视图联系在一起.视图对所显示的数据一无所知.同样,模型也不知道如何使用或显示数据.通过分离代码的不同逻辑部分,它使代码的更新和维护变得更加容易.同样,以相同的方式组织代码可以使长期维护变得更加容易.(I used the MVC, or Model-View-Controller, programming pattern as a guide when I wrote the game. It is used to separate the different parts of the program into its logical components. The Model contains the data. The View contains all the UI related code. And the Controller contains the business logic as well as ties the Model and the View together. The View does not know anything about the data that is being displayed. Likewise the Model does not know how the data is being used or displayed. By separating the different logical parts of the code, it makes updating and maintaining the code much easier. Likewise, organizing the code in the same way makes it easier to maintain in the long run.)
下图显示了MVC模式的不同部分如何相互影响.(The following is a diagram showing how the different parts of the MVC pattern interact with each other.)
在线上有几篇优秀的文章更详细地描述了这种编程模式.(There are several excellent articles online that describe this programming pattern in more detail.)
使用代码(Using the Code)
该项目是使用VS 2013编写的.它是完整的,可以加载,编译和运行.(The project was written using VS 2013. It is complete and can be loaded, compiled, and run.)
设计UI(Designing the UI)
我通过设计游戏的用户界面或UI或View来启动该项目.我本可以用一个(I started the project by designing the user interface, or UI, or the View, for the game. I could have used a) Panel
控制并在上面绘制9 x 9网格和游戏.但这将需要大量幕后代码来检查鼠标单击和类似操作.我本可以用一个(control and just drew the 9 x 9 grid and game on it. But that would have required a lot of behind the scene code to check for mouse clicks and stuff like that. I could have used a) DataGridView
像其他人一样进行控制.或创建了自己的自定义用户控件.但是最后,我决定使用(control like other people have done. Or created my own custom User Control. But in the end, I decided to use a) TableLayoutPanel
作为基础网格.(as the base grid.)
我把(I divided the) TableLayoutPanel
分为3行和3列以表示外部网格.在每个单元格中,我添加了另一个(into 3 rows and 3 columns to represent the outer grid. Into each cell, I added another) TableLayoutPanel
分为3行和3列,代表较小的3 x 3网格.在每个单元格中,我添加了一个(which was divided into 3 rows and 3 columns to represent the smaller 3 x 3 grid. In each cell, I added a) Label
控制我设置的(control which I set the) Dock
财产(property to) Fill
.我这样做是因为(. I did this because each cell in the) TableLayoutPanel
只能接受一个控件.通过使用(can only accept a single control. By using the) TableLayoutPanel
并改变(and changing the) CellBorderStyle
和(and the) Background
颜色,它给了板适当的强调,以显示整个9 x 9网格以及3 x 3子网格.然后,根据我希望游戏的外观和操作,添加了复选框,按钮等来完成UI.以下是在设计者视图中看到的游戏UI的图片.(color, it gave the board just the right emphasis to show the overall 9 x 9 grid as well as the 3 x 3 sub grids. I then added check boxes, buttons, etc. to complete the UI based on how I wanted the game to look and operate. The following is a picture of the UI of the game as seen in the designer view.)
在Sudoku中,用户需要在空白单元格中输入值.某些游戏在主网格的顶部,底部或侧面使用一系列数字按钮.其他人则使用弹出窗口.我选择在游戏中使用后一种方法.这样,用户不必将鼠标移到很远的地方即可将值输入到网格中.(In Sudoku, the user needs to enter values into the empty cells. Some games use a series of number buttons at the top, bottom or side of the main grid. Others used a pop-up window. I chose to use the latter method for my game. That way, the user does not have to move the mouse far to enter values into the grid.)
我新增了(I added a new) Form
到项目中,然后添加带有数字的按钮(to the project and then added buttons with the numbers) 1
通过(through) 9
.然后我通过设置关闭边框(. I then turned off the border by setting) FormBorderStyle
=无.我这样做是因为将边框放在主拼图上时会显得笨重.因此,我在右上角添加了一个带有"(= none. I did this because the border would look bulky when placed on top of the main puzzle. Because of this, I added another button to the upper right corner with an “) X
“,以允许用户关闭该窗口而不输入值.这是设计器视图中的外观.(” on it to allow the user to close this window without entering a value. Here is how it looks in the designer view.)
因为我想将此数字键盘放置在屏幕上,使其覆盖刚刚单击的单元格,所以设置(Because I want to position this number pad on the screen so that it overlays the cell that was just clicked, it is important to set the) StartPosition
财产(property to) Manual
.否则,Windows将在您首次打开该窗体时忽略任何尝试放置该窗体的尝试.(. Otherwise, Windows will ignore any attempts to position this form when you first open it.)
接下来是UI逻辑.基本上,每个按钮,复选框,标签等都有某种动作.在研究UI逻辑时,我在Controller中添加了存根代码和一些注释,以便以后知道要做什么.最初的代码基本上是这样的.(Next came the UI logic. Basically, each button, checkbox, label, etc. has some kind of action to it. As I went along and worked on the UI logic, I added stub code in the Controller and some comments so that I know what to do later on. The code basically looks like this at the beginning.)
在表格后面的代码中,我有这个:(In the code behind the form, I have this:)
Private Sub btnNew_Click(sender As Object, e As EventArgs) Handles btnNew.Click
_clsGameController.NewButtonClicked()
End Sub
然后在(Then in the) Controller
上课,我有以下内容:(class, I have the following:)
Friend Sub NewButtonClicked()
' Add code to process new game button command
End Sub
为了完成View,我向项目添加了另外两个表单.一个是"关于/帮助"框,另一个是拼图完成后弹出的框.(To complete the View, I added two more forms to the project. One is an About/Help box and the other pops up when the puzzle is completed.) 一旦对UI背后的基本代码进行了编程,下一步就是弄清楚如何创建新的难题.(Once the basic code behind the UI has been programmed, the next step is to figure out how to create new puzzles.)
生成新的数独难题(Generating New Sudoku Puzzles)
在玩了许多不同的游戏之后,我意识到一些商业数独游戏不会产生新的困惑.相反,它们装有预先构建的拼图.玩完所有这些拼图后,谜题就会开始重复.一段时间后,游戏就会变旧.因此,为此,我希望这款游戏能够创造出新的谜题.我很快就发现,知道游戏的玩法和实际创建一个新的难题是两件事.(After playing many different games, I came to the realization that some commercial Sudoku games do not generate new puzzles. Instead, they are loaded with pre-built puzzles. And once you have played them all, the puzzles start repeating. After a while, the game then becomes stale. So, to that end, I want this game to create new puzzles. Knowing how the game is played and actually creating a new puzzle are two different things as I soon found out.) 第一步是弄清楚如何生成有效的数独难题.事实证明,创建有效的Sudoku拼图实际上非常简单.许多人写了一些示例来演示如何完成此操作.在查看了不同的示例之后,我为我的项目改编了这个示例:(The first step was to figure out how to generate a valid Sudoku puzzle. As it turns out, creating a valid Sudoku puzzle is actually pretty simple. Many people have written examples that demonstrate how this is done. After looking at different examples, I adapted this one for my project:)
- http://www.codeproject.com/Articles/23206/Sudoku-Algorithm-Generates-a-Valid-Sudoku-in(http://www.codeproject.com/Articles/23206/Sudoku-Algorithm-Generates-a-Valid-Sudoku-in)
拼图创建的下一部分是根据游戏的难度开始删除单元格.从网络上的各种资源中可以找到下表,该表评估了游戏的难度等级.(The next part of the puzzle creation is to start removing cells based on the difficulty of the game. From various sources available on the Web, here is a table that rates the difficulty level of the game.)
难度级别(Difficulty Level) 赠品数量(Number of Givens) 非常简单(Very Easy) 50至60次(50 - 60 givens) Easy 36至49名(36 to 49 givens) Medium 32至35名(32 to 35 givens) Hard 28至31(28 to 31 givens) Expert 22至27名(22 to 27 givens)
有人会认为难度等级还应该包括什么概念或(Some would argue that difficulty ratings should also include what concepts or) 技术(techniques) 需要解决这个难题.而且所需的技术越多,评级就越困难.因此,具有相同给定数量的两个拼图可能具有不同的评分.但这超出了该项目的范围.(are required to solve the puzzle. And that the more techniques required, the more difficult the rating. Thus two puzzles with the same number of givens could have different ratings. But that is a little beyond the scope of this project.) 在更简单的级别上,我可以随意删除单元格,难题仍然可以解决.但是,正如我在玩了许多困难或专家级的谜题后发现的那样,有一点很重要,那就是移除了太多的单元格,必须开始猜测才能解决谜题.尽管有些人会认为这是游戏的一部分,但我认为猜测有损于游戏的核心概念,核心概念是仅使用纯逻辑来解决难题,而猜测中就没有逻辑.(At the easier levels, I could just randomly remove cells and the puzzle would still be solvable. But as I have found out after playing many difficult or expert level puzzles, there comes a point when too many cells are removed and one has to start guessing in order to solve the puzzle. While some people would argue that that is part of the game, I feel that guessing detracts from the core concept of the game which is to solve the puzzle using pure logic alone and there is no logic in guessing.) 因此,在删除单元格时,我必须合并代码以解决难题.首先,我想到了以下代码逻辑:(So, I had to incorporate code to solve the puzzle as I removed cells. At first, I thought of the following code logic:)
Create puzzle
Do
Remove x number of cells
Loop until puzzle is solvable
或以流程图形式:(Or in flowchart form:)
但是我很快意识到,以下是删除单元格的更好方法:(But I soon realized that the following is a better way to go about removing cells:)
Create puzzle
Do
Remove one cell
Solve puzzle
Loop while puzzle is solvable and there are more cells to remove
并以流程图形式:(And in flowchart form:)
这就是我每次删除一个单元格时解决的难题.这样,如果我刚刚删除的单元格使难题无法解决,那么我可以回溯并选择另一个单元格.(And that is to solve the puzzle as I remove one cell at a time. That way, if the cell I just removed renders the puzzle unsolvable, I can backtrack and choose another cell.) 下一部分是弄清楚如何解决游戏.同样,有很多方法可以做到这一点.一些人编写了蛮力算法,另一些人使用了(The next part was to figure out how to solve the game. Again, there are many ways to do it. Some wrote brute force algorithms, others used) 约束规划(constraint programming) ,还有其他人用过(, and yet others used) Knuth的跳舞链接算法(Knuth’s Dancing Links algorithm) .当我研究不同的技术时,我决定实现Knuth的Dancing Links算法.有几个优秀的(. As I looked at the different techniques, I decided to implement Knuth’s Dancing Links algorithm. There are several excellent) 文章(articles) 在网上描述了该技术以及使用不同编程语言的实际实现.我将此版本合并到我的项目中:(on the web that describe the technique as well as actual implementation using different programming languages. I incorporated this version into my project:)
- http://www.codeproject.com/Articles/19630/Dancing-Links-Library(http://www.codeproject.com/Articles/19630/Dancing-Links-Library)
当我打开(When I turned on)
Option Strict
在我的项目中,此代码生成了一些警告和错误.修复它们很容易.(in my project, this code generated several warnings and errors. It was easy enough to fix them all.) 一旦我弄清楚如何创建一个新的数独拼图,下一个任务就是如何管理实际的拼图生成.(Once I figured out how to create a new Sudoku puzzle, the next task was how to manage the actual puzzle generation.)
拼图管理(Puzzle Management)
简单级别的难题不需要很长时间即可生成.实际上,它一点也不明显.难度较大的难题需要一些时间才能生成.为了获得良好的游戏体验,我不希望用户在程序生成新游戏时等待.为了解决此问题,游戏将产生多个后台任务,以为每个级别生成新游戏.当用户点击”(Easy level puzzles do not take long to generate. In fact, it is not noticeable at all. The harder level puzzles take some time to generate. For a positive gaming experience, I did not want the user to wait while the program generates a new game. To solve this issue, the game will spawn several background tasks to generate new games for each level. Each level will have 5 games waiting to be loaded when the user clicks on “)新游戏(New Game)为了进一步改善用户体验,将在程序关闭时保存生成的游戏,以便用户下次运行该程序时,将加载预先生成的游戏,并且游戏可以立即进行.(”. And to further improve the user experience, the generated games will be saved when the program is closed so that the next time the user runs the program, the pre-generated games are loaded and the game is ready to go instantly.)
不用说,当第一次加载该项目时,将需要一些时间来为每个级别创建新游戏,因为该项目没有保存任何预建的游戏.因此,任何后续的加载/运行都会更快,因为它有机会在后台建立一些难题并将其保存.(It goes without saying that when this project is first loaded, it will take some time to create new games for each level since no pre-built games are saved with the project. So any subsequent loading/running will be much faster since it had a chance to build up some puzzles in the background and save them.)
实际上,我们实际上并不需要每个级别生成5个游戏,因为即使在专家级别,生成新游戏所需的时间也比用户解决它所需的时间少.但以防万一用户决定点击"(Actually, we do not really need to generate 5 games per level since even at the Expert level, it will take less time to generate a new game than it will take for the user to solve it. But just in case the user decides to click “)新游戏(New Game)在最终玩游戏之前,我们会多次维护每个关卡最多5个新游戏.(” several times before finally playing the game, we will maintain up to 5 new games per level.)
在.NET中,启动后台线程非常容易.这是一个例子.我们要做的第一件事是添加(Starting a background thread is very easy to do in .NET. Here is an example. First thing we need to do is add the) System.Threading
我们代码的名称空间.(namespace to our code.)
Imports System.Threading
实际启动线程的代码如下所示:(The code to actually start the thread looks like this:)
Friend Sub CreateNewGame()
Dim tThread As New Thread(AddressOf GenerateNewGame) ' Define a new thread
tThread.IsBackground = True ' Set it as a background thread
tThread.Start() ' Start it
End Sub
后台线程将执行的代码是这样的:(The code that the background thread will execute is this:)
Private Sub GenerateNewGame()
Dim uCells(,) As CellStateClass = GenerateNewBoard()
Dim e As GameGeneratorEventArgs = New GameGeneratorEventArgs(uCells)
RaiseEvent GameGeneratorEvent(Me, e)
End Sub
该后台线程是自终止的.意思是,一旦产生了难题并引发了事件,线程便会终止,因为没有其他事情可做.(This background thread is self-terminating. Meaning, once the puzzle is generated and the event raised, the thread terminates because there is nothing else to do.) 在其他情况下,我们需要在程序打开时保持后台线程运行.在这种情况下,代码将类似于以下内容:(There are other times when we need to keep a background thread running while the program is open. In this case, the code would look something like the following:)
Private _bStop as Boolean
Private Sub GameMaker()
_bStop = False
Do
' Do something
Loop Until _bStop
End Sub
的(The) _bStop
变量允许控制器在用户关闭游戏时终止此后台任务.否则,代码将处于无限循环中.从技术上讲,我们不需要使用(variable allows the Controller to terminate this background task when the user closes the game. Otherwise, the code will just sit in an infinite loop. Technically, we do not need to use the) _bStop
变量.程序退出时,所有后台任务都将中止.但这不是退出的干净方法,因此我们添加了(variable. When the program exits, all background tasks are aborted. But that is not a clean way to exit so we add the) _bStop
变量.当应用程序关闭时,我们将(variable. And when the application closes, we set the) _bStop
可变为(variable to) True
这样循环才能正常退出.我们可以添加更多代码来检查后台任务在关闭应用程序之前是否已正确退出,但这只是一个简单的游戏.但是在更复杂的程序中,必须确保后台线程正确关闭,无论如何,请在关闭应用程序之前执行这些检查.一个示例是写入数据库的后台任务.当应用程序关闭时,我们要确保在关闭连接之前完成对数据库的所有挂起的写操作.(so that the loop can exit gracefully. We can add more code to check if the background tasks have exited properly before closing the application, but this is just a simple game. But in more complex programs where it is necessary to make sure that the background threads closed properly, by all means, perform those checks before closing the application. An example would be a background task that writes to a database. When the application closes, we want to be sure that all pending writes to the database are completed before closing the connection.)
使用后台线程的另一个原因是,在生成新的拼图时,UI不会陷入困境.(Another reason for using background threads is so that the UI is not bogged down while new puzzles are generated.)
我使用此代码来管理拼图生成任务以及管理内置拼图.当用户请求一个新的拼图时,它将从队列中删除,并产生一个新的游戏创建线程.(I use this code to manage the task of puzzle generation as well as managing the built puzzles. When the user requests a new puzzle, it is removed from the queue and a new game creation thread is spawned.)
我们可以让这个循环在用户玩游戏时继续进行,但是为什么在没有任何线程可做的情况下减少不必要的CPU周期呢?通常,这可能会降低计算机的速度,并影响整个游戏体验.(We could let this loop just keep going while the user is playing the game, but why chew up unnecessary CPU cycles when there is nothing for this thread to do? This could potentially slow down the computer in general and affect the whole gaming experience.)
基本上,我们要做的是一旦所有5个谜题生成完毕,就将循环置于某种暂停状态,直到下次需要生成新的谜题时为止.为此,我们将使用(Basically, what we want to do here is once all 5 puzzles have been generated, to put the loop into some kind of suspended state until the next time when a new puzzle needs to be generated. To do this, we will use the) AutoResetEvent
类.这是类级别的声明:(Class. Here is the class level declaration:)
Private _MakeMoreGames As New AutoResetEvent(False)
的(The) AutoResetEvent
允许两个或多个线程互相发信号.在上面的代码中,当我需要挂起后台线程时,该线程进行以下调用:(allows two or more threads to signal each other. In the code above, when I need to suspend the background thread, the thread makes the following call:)
_MakeMoreGames.WaitOne()
当我需要唤醒线程时,我从另一个线程进行了此调用.(And when I need to wake up the thread, I make this call from another thread.)
_MakeMoreGames.Set()
这就是线程可以相互发出信号的方式.当难题从难题队列中删除后,我称(This is how threads can signal each other. When a puzzle is removed from the queue of puzzles, I call) Set
在(on the) AutoResetEvent
.这表明等待基本唤醒并创建另一个难题的后台线程.(. This signals the background thread that is waiting to essentially wake up and create another puzzle.)
的完整代码(The complete code for the) GameMaker
子例程如下所示:(subroutine looks like this:)
Private Sub GameMaker()
Do
Try
SyncLock _objQLock
If _qGames Is Nothing Then
_qGames = New Queue(Of CellStateClass(,))
End If
If _qGames.Count < _cDepth Then
_clsGameGenerator.CreateNewGame()
End If
End SyncLock
Catch ex As Exception
' Process error
End Try
_MakeMoreGames.WaitOne()
Loop Until _bStop
End Sub
在循环开始时,代码检查队列中是否有足够的游戏.如果没有,它将生成一个线程来创建一个新游戏:(At the beginning of the loop, the code checks to see if there are enough games in the queue. If not, it will spawn a thread to create a new game:)
_clsGameGenerator.CreateNewGame()
产生线程后,它将进入睡眠状态:(Once the thread is spawned, it will then go to sleep:)
_MakeMoreGames.WaitOne()
创建新拼图后,事件代码如下:(When the new puzzle is created, the event code looks like this:)
Private Sub GameGeneratorEvent(sender As Object, e As GameGeneratorEventArgs) _
Handles _clsGameGenerator.GameGeneratorEvent
SyncLock _objQLock
If (_qGames Is Nothing) Then
_qGames = New Queue(Of CellStateClass(,))
End If
_qGames.Enqueue(e.Cells)
End SyncLock
_MakeMoreGames.Set()
End Sub
新的拼图排队后,它将向主循环发送信号以唤醒:(Once the new puzzle is queued up, it will send a signal to the main loop to wake up:)
_MakeMoreGames.Set()
然后,游戏管理线程将唤醒并检查是否需要创建更多游戏.通过使用(Then the game management thread will wake up and check to see if more games need to be created. By using the) AutoResetEvent
类中,主循环不需要一直保持运行.它在需要运行时运行.(class, the main loop does not need to keep running all the time. It runs when it needs to run.)
因为我正在创建一个多线程应用程序,所以在访问保存已生成游戏的变量时维护线程安全非常重要.为了保持线程安全,我使用了(Because I am creating a multi-threaded application, it is important to maintain thread safety when accessing the variables that hold the generated games. To maintain thread safety, I used the) SyncLock
声明.简而言之,(statement. In a nutshell, the) SyncLock
语句可确保多个线程不会同时执行受保护的语句块.(statement ensures that multiple threads do not execute the protected statement block at the same time.) SyncLock
防止每个线程进入该块,直到没有其他线程执行该块为止.(prevents each thread from entering the block until no other thread is executing it.)
为了(In order for the) SyncLock
声明起作用,我们需要声明一个类级别(statement to work, we need to declare a class level) Object
变量:(variable:)
Private _objQLock as New Object
声明对象后,我们可以使用(Once we have declared the object, we can use the) SyncLock
像这样的声明:(statement like this:)
SyncLock _objQLock
_qGames = New Queue(Of CellStateClass(,))
End SyncLock
的(The) SyncLock .. End SyncLock
无论代码如何退出该块,该块都可以保证释放锁定.即使在未处理的错误情况下.(block guarantees the release of the lock no matter how the code exits the block. Even in the case of an unhandled error condition.)
显然,我们需要使用相同的(Obviously, we need to use the same) Object
每当我们锁定(variable whenever we are locking the) Game
队列.换句话说,对于我们需要保护的每个数据对象,它都应该有一个匹配项(queue. In other words, for every data object we need to protect, it should have a matching) Object
与(variable for use with the) SyncLock
声明.(statement.)
同步访问代码块的另一种方法是使用(Another way to synchronize access to a code block is by using a) 互斥体(Mutex) .这是另一个使用(. Here is another example using a) Mutex
.我们宣布一个班级(. We declare a class level) Mutex
变量:(variable:)
Private _mQueueMutex As New Mutex
这是它的用法:(And here is how it is used:)
_mQueueMutex.WaitOne()
_qGames = New Queue(Of CellStateClass(,))
_mQueueMutex.ReleaseMutex()
此技术的问题在于,如果第二行出现错误,则第三行可能不会执行,并且(The problem with this technique is that if an error occurs in the second line, then the third line might not execute and the) Mutex
将不会被释放.为了解决这个问题,该代码应以(will not be released. In order to fix this, the code should be surrounded with a) Try ... Catch
声明.因此,它将类似于以下内容:(statement. So it will look like the following:)
Try
_mQueueMutex.WaitOne()
_qGames = New Queue(Of CellStateClass(,))
Catch ex As Exception
Throw ex
Finally
_mQueueMutex.ReleaseMutex()
End Try
或者,如果可以安全地忽略该错误:(Or if the error can be safely ignored:)
Try
_mQueueMutex.WaitOne()
_qGames = New Queue(Of CellStateClass(,))
Finally
_mQueueMutex.ReleaseMutex()
End Try
通过把(By putting the) ReleaseMutex
在 - 的里面(inside the) Finally
块,(block, the) Mutex
将保证被释放.两种技术都可以,但是使用(will be guaranteed to be released. Both techniques work, but using) SyncLock
只有三行代码,看起来更干净.(is only three lines of code and looks cleaner.)
当我继续对游戏生成器的其余部分进行编码时,我遇到了有关随机数生成器的另一个问题.一般来说,随机数生成器并不是真正的随机数.它看起来是随机的,但不是.它使用一个数学公式来生成一个数字序列,从统计上来说,其输出是"随机的".在控制台项目中尝试以下代码:(As I went along and coded the rest of the game generator, I ran across another issue with respect to the Random Number generator. Generally speaking, the Random Number generator is not really random. It looks random, but it is not. It uses a mathematical formula to generate a sequence of numbers where the output is, statistically speaking, “random.” Try the following code in a console project:)
Dim rnd1 As New Random
Console.Write("First sequence :")
For I As Int32 = 1 To 10
Console.Write("{0, 5}", rnd1.Next(100))
Next
Console.WriteLine()
Dim rnd2 As New Random
Console.Write("Second sequence:")
For I As Int32 = 1 To 10
Console.Write("{0, 5}", rnd2.Next(100))
Next
它将输出以下内容:(It will output the following:)
First sequence : 37 65 63 35 30 4 76 89 53 1
Second sequence: 37 65 63 35 30 4 76 89 53 1
如您所见,即使我们创建了两个不同的实例(As you can see, even though we created two different instances of the) Random
类,它生成相同的数字序列.多次运行它将生成与上面显示的数字不同的数字序列,但是两个序列仍将匹配.如果我们将此代码放入我们的游戏生成器中,它将基本上一次又一次地生成相同的游戏,这会很无聊.(class, it generated the same sequence of numbers. Running it multiple times will generate a different sequence of numbers than shown above, but both sequences will still match. If we put this code in our game generator, it will basically generate the same game over and over again and that will be boring.)
为了解决这个问题,(To get around this problem, the) Random
类允许我们使用种子值初始化随机数生成器.最常用的种子值是当前日期/时间或自午夜以来的秒数.为了进一步"随机化"种子值,我将(class allows us to initialize the Random number generator with a seed value. The most common seed value used is the current date/time or number of seconds since midnight. To further “randomize” the seed value, I will) mod
的(the) Ticks
这样就增加了10,000.(by 10,000 like so.)
Dim tsp As New TimeSpan(DateTime.Now.Ticks) ' Create a seed value
' using the current time.
Dim iSeed As Int32 = _
CInt((CLng(tsp.TotalMilliseconds * 10000) Mod Int32.MaxValue) Mod 10000)
这样,它将生成一个介于0到9,999之间的种子值,而不是一个真正的大数字,其中只有较低的数字会有所不同.(This way, it will generate a seed value between 0 and 9,999 instead of a really big number where only the lower digits will differ.) 下一个问题是如何以确保在整个游戏中仅使用其一个实例的方式实现此随机代码.答案是使用Singleton模式.的(The next issue is how to implement this Random code in such a way as to ensure that only one instance of it is used throughout the game. The answer is the use of the Singleton pattern. The) 单例模式(Singleton pattern) 是一种将类的实例限制为单个对象的方法.有几种方法可以实现该模式,并且在Web上搜索将产生许多示例.(is a way to restrict the instantiation of a class to a single object. There are several ways to implement the pattern and searching the Web will yield many examples.) 这是两种实现Singleton模式的方法.第一种方法称为后期实例化.之所以这样称呼,是因为该类是在首次调用它来执行某事时实例化的.(Here are two ways to implement the Singleton pattern. The first method is called late instantiation. It is called that because the class is instantiated when it is first called upon to do something.)
Friend Class SingletonClass
Private Shared _instance As SingletonClass
Private Shared _objInstanceLock As New Object
Private Sub New()
' Declared private so that no one can instantiate this class.
End Sub
Friend Shared Sub DoSomething()
If _instance Is Nothing Then
SyncLock _objInstanceLock
If _instance Is Nothing Then
_instance = New SingletonClass
_instance.InitInstance()
End If
End SyncLock
End If
_instance.DoSomethingInstance()
End Sub
Private Sub DoSomethingInstance()
' Do some action
End Sub
Private Sub InitInstance()
' Initialize instance level variables
End Sub
End Class
这是实现Singleton模式的另一种方法.这被称为延迟实例化,因为该类是在(Here is another way to implement the Singleton pattern. This is referred to as lazy instantiation because the class is instantiated when the) GetInstance
函数首先被调用.的(function is first called. The) GetInstance
必须先调用property属性,然后才能使用该类的任何其他方法或属性.(property needs to be called first before any of the other methods or properties of the class can be used.)
Friend Class SingletonClass
Private Shared _instance As SingletonClass
Private Shared _objInstanceLock As New Object
Private Sub New()
' Declared private so that no one can instantiate this class.
End Sub
Friend Shared ReadOnly Property GetInstance As SingletonClass
Get
If _instance Is Nothing Then
SyncLock _objInstanceLock
If _instance Is Nothing Then
_instance = New SingletonClass
_instance.InitInstance()
End If
End SyncLock
End If
Return _instance
End Get
End Property
Friend Sub DoSomething()
' Do some action in the instance
End Function
Private Sub InitInstance()
' Initialize instance level stuff.
End Sub
End Class
在两个示例中,(In both examples, the) New
构造函数用(constructor is declared with the) Private
修饰符.这有助于强制使用Singleton模式,因为它可以防止其他人创建自己的实例.在Web上搜索有关两者之间的区别以及何时使用它们的详细说明.(modifier. This helps enforce the Singleton pattern because it prevents other people from creating their own instance. Search the Web for a detailed explanation for the differences between the two and when to use them.)
要使用第一个Singleton模式,我们可以简单地这样称呼它:(To use the first Singleton pattern, we just simply call it this way:)
SingletonClass.DoSomething
要使用第二种模式,我们必须创建一个变量来保存实例.(To use the second pattern, we have to create a variable to hold the instance.)
Dim instance as SingletonClass
然后,我们需要获取一个实例,然后才能使用(Then we need to get an instance before we can use the functions within the) Singleton
类.(class.)
instance = SingletonClass.GetInstance
instance.DoSomething
第一种方法看起来更容易,因为不必记住在使用实例之前先获取实例.但是这两种方法都各有利弊,网络对此进行了详尽的讨论.(The first method looks to be easier since one does not have to remember to first get an instance before using it. But there are pros and cons to both methods and the Web talks about them in great detail.)
如果我们仔细看第二个例子,有几个变量和方法具有(If we look closely at the second example, there are several variables and methods that have the) Shared
修饰符.的(modifier. The) Shared
修饰符基本上表示函数或变量始终存在.如果没有该修饰符,则该修饰符仅在创建实例时存在.一种(modifier basically indicates that the function or variable exists at all times. Without the modifier, it will only exist when an instance is created. A) Shared
函数不能访问实例变量,但是实例函数可以访问(function cannot access an instance variable, but an instance function can access a) Shared
函数或变量.(function or variable.)
因此,在以下代码中:(So, in the following code:)
instance = SingletonClass.GetInstance
instance.DoSomething
第一行通过调用(The first line loads an instance of the class by calling the) Shared
功能(function) GetInstance
.然后在第二行中,我们可以调用实例方法(. Then in the second line, we can call the instance method) DoSomething
.(.)
有时,程序在各处都有需要的代码.一个示例是检查index变量,以确保它位于(There are times when the program has code that is needed all over the place. An example would be checking the index variable to make sure that it is between) 1
和(and) 9
.(.)
Friend Class Common
Friend Shared Function IsValidIndex(iIndex As Int32) As Boolean
Return ((1 <= iIndex) AndAlso (iIndex <= 9))
End Function
Friend Shared Function IsValidIndex(uIndex As CellIndex) As Boolean
Return (IsValidIndex(uIndex.Col, uIndex.Row))
End Function
Friend Shared Function IsValidIndex(iCol As Int32, iRow As Int32) As Boolean
Return (IsValidIndex(iCol) AndAlso IsValidIndex(iRow))
End Function
Friend Shared Function IsValidStateEnum(iState As Int32) As Boolean
Return ((0 <= iState) AndAlso (iState <= 4))
End Function
End Class
可以说这太过分了,因为我们编写了这款游戏,所有内容都应介于(One can say that is overkill because we wrote this game and everything should be between) 1
和(and) 9
.的确是这样,但是如果您正在与一群人一起从事大型项目,那么在有人没有得到有关索引限制的备忘的情况下,这将有助于执行此规则.如果索引超出有效范围,则可以扩展此函数以引发错误.这是一个例子:(. That is true, but if you are working on a large project with a group of people, it would help to enforce this rule just in case someone did not get the memo about the index limits. One can then extend this function to throw an error if the index is outside of the valid range. Here is an example:)
Friend Shared Function IsValidIndex(iIndex As Int32) As Boolean
If ((1 <= iIndex) AndAlso (iIndex <= 9)) Then
Return True
Else
Throw New Exception("Index is outside of the valid range of 1 through 9.")
End If
End Function
如果我们看一下(If we look at the) Common
在上面的类中,前三个函数具有相同的名称,(class above, the first three functions have the same name,) IsValidIndex
.这就是所谓的(. This is called) overloading
一种方法,它是面向对象编程范例的一部分.我们使用相同的名称是因为它执行相同的操作:它检查以确保索引在(a method which is part of the Object Oriented Programming paradigm. We use the same name because it does the same thing: it checks to make sure the index is between) 1
和(and) 9
, 包括的.这三个功能之间的区别在于参数列表.这称为方法的签名.基于签名,编译器然后知道要使用哪个函数.(, inclusive. The difference between the three functions is the parameter list. This is called the signature of the method. Based on the signature, the compiler then knows which function to use.)
最后,当生成游戏时,我们如何继续进行操作并通知某人新游戏已经创建,并将其与其他游戏排在一起?有几种方法可以做到这一点.我本可以以这样的方式编写代码的:一旦创建了游戏,就可以让同一个线程排队.但是,这样做的乐趣是什么.此外,它可能变得凌乱,因为我们随后需要将数据变量公开给其他类,并将类实例传递给^杂乱的编码,以确保进行维护和进行噩梦.(Finally, when a game is generated, how do we go about and notify somebody that a new game was just created and to queue it up with the rest of the games? There are several ways to do this. I could have coded it in such way that once the game is created, to have that same thread queue it up. But what is the fun in that. Besides, it can get messy because we would then need to expose the data variables to other classes and pass the class instance around … messy coding for sure and a nightmare to maintain.)
由于我使用后台线程来生成游戏,因此我认为最好使用事件和委托来进行此类通知.第一部分是声明一个委托和匹配事件,如下所示:(Since I am using background threads to generate the games, I thought it best to use Events and Delegates to do this kind of notification. The first part is to declare a delegate and the matching event like so:)
Friend Delegate Sub GameGeneratorEventHandler(sender As Object, e As GameGeneratorEventArgs)
Friend Event GameGeneratorEvent As GameGeneratorEventHandler
我也可以这样声明:(I could have declared it this way as well:)
Friend Delegate Sub GameGeneratorEventHandler(uCells(,) as CellStateClass)
Friend Event GameGeneratorEvent As GameGeneratorEventHandler
区别在于Delegate声明的参数.但是为了保持Windows窗体控件所使用的样式,我使用了第一种方法.这需要我创建自己的自定义(The difference is in the parameters of the Delegate declaration. But to maintain the same style used by Windows Forms Controls, I used the first method. This requires that I create my own custom) EventArgs
类:(class:)
Friend Class GameGeneratorEventArgs
Inherits EventArgs
Private _uCells(,) As CellStateClass
Friend ReadOnly Property Cells As CellStateClass(,)
Get
Return _uCells
End Get
End Property
Friend Sub New(uCells As CellStateClass(,))
_uCells = uCells
End Sub
End Class
注意使用(Note the use of) Inherits EventArgs
在类声明中.因此,一旦生成了新的拼图,我就会创建一个(in the class declaration. So, once the new puzzle is generated, I create an) EventArgs
对象并将其传递给(object and pass it to the) RaiseEvent
代表.(delegate.)
Dim e As GameGeneratorEventArgs = New GameGeneratorEventArgs(uCells)
RaiseEvent GameGeneratorEvent(Me, e)
另一方面,游戏管理器类包含以下声明和事件侦听器:(On the other side, the Game Manager class contains the following declarations and event listener:)
Private WithEvents _clsGameGenerator As GameGenerator
Private Sub GameGeneratorEvent(sender As Object, _
e As GameGeneratorEventArgs) Handles _clsGameGenerator.GameGeneratorEvent
' Do something with the incoming event arguments.
End Sub
后来,我在游戏中添加了一个计时器,以便用户知道他/她解决难题所需的时间.计时器还使用"事件"和"委托"来告诉程序时间已到.(Later on, I added a timer to the game so that the user knows how long it takes for him/her to solve the puzzle. The timer also uses Events and Delegates to tell the program when time has expired.)
计时器到期时,它将更新UI上的标签以指示已到期了多少时间.由于计时器在后台任务中运行,因此无法直接更新标签.相反,它必须使用(When the timer expires, it will update a label on the UI to indicate how much time has expired. Because the timer runs in a background task, it cannot update the label directly. Instead it must use) Invoke
将调用传递到创建控件的适当线程.让我告诉你它是如何完成的.首先,我们需要声明一个回调例程.(to pass the call to the proper thread where the control was created. Let me show you how it is done. First, we need to declare a callback routine.)
Private Delegate Sub SetStatusCallback(sMsg As String)
然后,在设置(Then, in the function that sets the) Label
文本,代码如下所示:(text, here is how the code looks:)
Private Sub SetStatusText(sMsg As String)
If lblStatus.InvokeRequired Then
Dim callback As New SetStatusCallback(AddressOf SetStatusText)
Me.Invoke(callback, New Object() {sMsg})
Else
lblStatus.Text = sMsg
End If
End Sub
首先,我们检查(First, we check if the) Label
需要通过调用来调用(needs to be invoked by calling) InvokeRequired
在(on the) Label
.如果(. If) true
,然后创建回调例程和(, then create the callback routine and) Invoke
它.否则,只需更新(it. Otherwise, just update the) Label
文本..(text..)
至此,我们已经创建了基本的UI以及游戏生成和管理代码.接下来是模型.(Up to this point, we have created the basic UI as well as the game generation and management code. Next comes the Model.)
建立模型(Building the Model)
模型基本上是存储游戏数据的地方.因此,当用户单击"新游戏"时,控制器将从游戏管理器中向模型加载新游戏.控制器负责根据视图中的用户输入来更改模型的数据.从理论上讲,该模型不应包含任何用于处理数据的业务逻辑或代码.它只包含数据.但是,我做出了一个小让步.(The Model is basically a place where the data for the game is stored. So when the user clicks on “New Game,” the Controller will load the Model with a new game from the Game Manager. The Controller is in charge of making changes to the Model’s data based on user input from the View. Theoretically, the Model is not supposed to contain any business logic or code to manipulate the data. It just contains data. However, I made one small concession.) 玩数独游戏时,解决游戏问题的一个方面是在空的单元格中输入笔记或铅笔标记.注释基本上是特定单元格可能包含的数字.例如:(When playing Sudoku, one aspect to solving the game is entering notes or pencil marks into empty cells. Notes are basically numbers that a particular cell can possibly contain. For example:)
在上方突出显示的单元格中,数字(In the highlighted cell above, the numbers) 1
,(,) 4
和(, and) 9
是可能的答案.这是因为其他数字已经在此单元格所在的列或3 x 3网格中表示.为了帮助用户,该程序将生成注释.由于注释是数据的一部分,因此我将代码与(are possible answers. This is because the other numbers are already represented in the column or 3 x 3 grid where this cell is located. To help the user, the program will generate notes. Since the notes are part of the data, I put the code to generate the notes in with the) Model
类.所以,当(class. So, when the) Model
装有新游戏,它要做的第一件事就是生成笔记.(is loaded with a new game, one of the first things it does is generate notes.)
当为用户生成注释时,该程序将使用Sudoku的基本规则.那就是数字(The program will use the basic rule of Sudoku when generating notes for the user. And that is, the numbers) 1
通过(through) 9
只能在每行,每列或3 x 3网格中出现一次.如果数字在行,列或3 x 3网格中,则将从注释中删除.(can appear in each row, column, or 3 x 3 grid only once. If a number is in the row, column, or 3 x 3 grid, it will be eliminated from the notes.)
控制者(Controller)
构建模型后,最后一部分就是构建业务或游戏逻辑.所有进入(Once the Model is built, the last part is to build the business or game logic. All that goes into the) Controller
类.到目前为止,UI进行的所有调用都仅指向(class. Up to now, all the calls made by the UI are just pointing to stub code in the) Controller
类.游戏逻辑实际上是项目中最容易编写代码的部分.(class. The game logic was actually the easiest part of the project to code.)
我只是将代码写入了构建UI时先前生成的所有存根代码.最具挑战性的部分是突出显示刚刚选择的单元格.为了直接在控件上绘制,我需要将控件的图形对象传递给(I just wrote the code to all the stub code that was generated earlier when I built the UI. The challenging part was highlighting the cell that was just selected. In order to draw directly on the control, I needed to pass the graphics object for the control to the) Controller
.因此,我不得不修改存根代码的参数.(. So I had to modify the parameters of the stub code.)
这是在所选单元格周围绘制高亮边框的代码:(Here’s the code that draws the highlight border around the selected cell:)
Private Sub DrawBorder(uSelectedCell As CellIndex, _
e As PaintEventArgs, bHighlight As Boolean)
With _lLabels(uSelectedCell.Col, uSelectedCell.Row)
Dim highlightPen As Pen
If bHighlight Then
highlightPen = New Pen(Brushes.CadetBlue, 4)
Else
highlightPen = New Pen(.BackColor, 4)
End If
e.Graphics.DrawRectangle(highlightPen, 0, 0, .Width, .Height)
highlightPen.Dispose()
End With
End Sub
确定笔的颜色(突出显示或取消突出显示)后,然后在"标签"的边框周围绘制一个矩形.的(Once I determine the pen color, either highlight or unhighlight, I then draw a rectangle around the border of the Label. The) Pen
对象的宽度为4像素,因此很容易看到.(object has a width of 4 pixels so it is easily visible.)
e.Graphics.DrawRectangle(highlightPen, 0, 0, .Width, .Height)
注意我通过了(Notice I passed the) PaintEventArgs
进入这个功能,因为那是(into this function since that is where the) Graphics
对象位于.这来自(object is located. This comes from the) Paint
Windows窗体控件的事件.(event of the Windows Forms control.)
Private Sub LabelA1_Paint(sender As Object, e As PaintEventArgs) Handles LabelA1.Paint
_clsGameController.PaintCellEvent(1, 1, e)
End Sub
当然,这一功能与(Of course, there are several other functions between this one and the) DrawBorder
上面的功能.但关键是,我通过了(function above. But the point is, I pass the) PaintEventArgs
以防万一需要直接在控制器上绘制(to the Controller just in case it needs to draw directly on the) Label
控制.(control.)
另外,在显示空白单元格的注释时,我直接将其绘制在控件上,而不是设置(Also, when displaying the notes for empty cells, I drew them directly on the control rather than setting the text of the) Label
控制.(control.)
Private Sub DrawNotes(iCol As Int32, iRow As Int32, e As PaintEventArgs)
With _Model.Cell(iCol, iRow)
If .HasNotes Then
Dim drawFont As New Font("Arial", 8)
For I As Int32 = 1 To 3
For J As Int32 = 1 To 3
Dim noteIndex As Int32 = J + ((I - 1) * 3)
If .Notes(noteIndex) Then
With _lLabels(iCol, iRow)
Dim X As Int32 = CInt((.Width / 3) * (J - 1))
Dim Y As Int32 = CInt((.Height / 3) * (I - 1))
e.Graphics.DrawString(noteIndex.ToString, _
drawFont, Brushes.Black, X, Y)
End With
End If
Next
Next
drawFont.Dispose()
End If
End With
End Sub
确定需要在单元格中绘制笔记后,我会得到一个(Once I determine I need to draw notes in the cell, I get a) Font
与对象(object with the) Arial
字体大小(font in size) 8
:(:)
Dim drawFont As New Font("Arial", 8)
然后我计算(I then calculate the) X
和(and) Y
用以下内容写数字的坐标:(coordinates of where to write the number with the following:)
Dim X As Int32 = CInt((.Width / 3) * (J - 1))
Dim Y As Int32 = CInt((.Height / 3) * (I - 1))
然后,我最后用下面的代码画出数字:(Then, I finally draw the number out with the following line:)
e.Graphics.DrawString(noteIndex.ToString, drawFont, Brushes.Black, X, Y)
所有这些代码都可以在(All this code is found in the) Controller
类.有人可能会说,实际的绘图代码应该放在View中,因为那是View的功能.和(class. One could make the argument that the actual drawing code should be placed in the View since that is the function of View. And the) Controller
应该只包含游戏逻辑,因为这是(should only contain the gaming logic because that is the function of the) Controller
.在这种情况下,(. In this case, the) Controller
的工作是进行所有计算,然后告诉"视图"绘制内容以及绘制位置.也许在将来的版本中,我会这样做.(’s job is to do all the calculations and then tell the View what to draw and where. Maybe in a future version, I will do just that.)
保存用户设置(Saving User Settings)
在VS 2013中,保存设置很容易.只需拉起(Saving settings is easy to do in VS 2013. Just pull up the) Project
属性,点击(properties, click on the)设定值(Settings)使用左侧的标签,创建所需的所有设置.(Tab on the left side and create all the settings that you need.)
然后可以使用以下语法以代码访问设置:(The settings can then be accessed in code by using the following syntax:)
My.Settings.[setting name]
这是项目中的一个示例:(Here is an example from the project:)
cbDifficultyLevel.SelectedIndex = My.Settings.Level
在这里,我将组合框控件设置为最后一个(Here, I am setting the combo box control to the last) Level
保存的设置.(setting that was saved.)
因为设置的范围设置为(Because the Scope of the settings are set to) User
,设置将保存到以下位置:(, the settings will be saved to the following location:)
[user directory]\AppData\Local\SudokuPuzzle\
SudokuPuzzle.vshost.exe_Url_jnscgesaimzkgsbjhoygfwifnfxk1g51\1.0.0.0\user.config
由于Windows生成的随机字符串,不同计算机上的实际目录名称将有所不同.但这是设置的一般位置.(The actual directory name will be different on different machines due to the random string generated by Windows. But that is the general location of the settings.)
[user directory]\AppData\Local\SudokuPuzzle\...
命名空间和包装(Namespace and Wrapping Up)
该代码的最后一个补充是使用(One final addition to the code is the use of) Namespace
.通过使用(. By using) Namespace
,我可以将代码拆分为MVC模式的不同部分.因此,属于(, I can split up the code into the different parts of the MVC pattern. So the code that belongs to the) Model
将被以下内容包围:(will be surrounded by the following:)
Namespace Model
Friend Class GameModel
...
End Class
End Namespace
要访问(To access the) GameModel
上例中的class,全限定名是(class in the example above, the fully qualified name is) SudokuPuzzle.Model.GameModel
.使用(. Using) Namespace
帮助实施MVC编程模式.请注意,Windows窗体不喜欢包含在(helps enforce the MVC programming pattern. One caveat, Windows Forms do not like being enclosed in a) Namespace
.的(. The) SudokuPuzzle
根(root) Namespace
在应用程序属性中分配.(is assigned in the application properties.)
此外,该程序的主要入口是(Also, the main entry point for the program is the) Main
形成.通常,在MVC模型中,(form. Normally, in the MVC model, the) Controller
是主要的切入点.但是对于这个项目,我只是将主要入口点留在了(is the main entry point. But for this project, I just left the main entry point as the) Main
该项目是作为Windows窗体应用程序创建的.(form since the project was created as a Windows Forms Application.)
这是游戏运行时的外观:(Here is how the game looks when it is running:)
兴趣点(Points of Interest)
当我开始计划解决难题的代码时,我想我会像人类一样实现它.那就是通过使用几种技术来得出每个空单元格的答案.但是随着我的前进,我确定那不是解决难题的最有效方法.因此,我开始研究其他人解决此问题的不同方法.最适合我的一项技术是Knuth的Dancing Links算法.(When I started planning out the code to solve the puzzle, I thought I would implement it the way a human would. That is by using several techniques to arrive at the answer for each empty cell. But as I went along, I determined that that was not the most efficient way to solving a puzzle. So I started researching the different ways other people have solved this problem. One of the techniques that stood out for me was Knuth’s Dancing Links algorithm.) 我一直以为Sudoku是在9 x 9的网格上进行游戏的,细分为3 x 3的较小网格.但是在研究过程中,我发现(I had always thought that Sudoku was played on a 9 x 9 grid that was subdivided into 3 x 3 smaller grids. But during my research, I discovered that there are) 几种变化(several variations) 数独游戏.也许在将来,我将扩展该游戏,使其包含其他一些变体,例如4 x 4网格甚至16 x 16网格.(to the Sudoku game. Perhaps in the future, I will extend this game to incorporate some of the other variants like the 4 x 4 grid or even the 16 x 16 grid.) 从.NET开始,无论使用哪种语言,数组都基于零.在整个代码中,我使用索引1到9而不是0到8来简化事情.本质上,我忽略了数组中从零开始的元素.(Starting with .NET, arrays are all zero based regardless of language used. Throughout my code, I used the indices 1 through 9 instead of 0 through 8 to make things simpler for me. Essentially, I ignore the zero based element of the array.) 在规划网格时,我决定使用Excel的单元格寻址方式.那是[col] [row].外观如下:(When planning the grid, I decided to use Excel’s way of addressing the cells. And that is by [col][row]. Here is how it looks:)
' +--------+--------+--------+
' |11 21 31|41 51 61|71 81 91|
' |12 22 32|42 52 62|72 82 92|
' |13 23 33|43 53 63|73 83 93|
' +--------+--------+--------+
' |14 24 34|44 54 64|74 84 94|
' |15 25 35|45 55 65|75 85 95|
' |16 26 36|46 56 66|76 86 96|
' +--------+--------+--------+
' |17 27 37|47 57 67|77 87 97|
' |18 28 38|48 58 68|78 88 98|
' |19 29 39|49 59 69|79 89 99|
' +--------+--------+--------+
这是我处理每个3 x 3网格的方法:(Here is how I addressed each 3 x 3 grid:)
' +--------+--------+--------+
' |.. .. ..|.. .. ..|.. .. ..|
' |.. 1 ..|.. 2 ..|.. 3 ..|
' |.. .. ..|.. .. ..|.. .. ..|
' +--------+--------+--------+
' |.. .. ..|.. .. ..|.. .. ..|
' |.. 4 ..|.. 5 ..|.. 6 ..|
' |.. .. ..|.. .. ..|.. .. ..|
' +--------+--------+--------+
' |.. .. ..|.. .. ..|.. .. ..|
' |.. 7 ..|.. 8 ..|.. 9 ..|
' |.. .. ..|.. .. ..|.. .. ..|
' +--------+--------+--------+
我考虑过编写自定义代码(I thought about coding a custom) Collections
的课程(class for the) Model
.但是为了简单起见,我决定不这么做.(. But to keep things simple, I decided not to.)
诱人的使用(It is tempting to use) For Each
当遍历数组或数据列表时.但是,有一个陷阱.该元素只是原始副本.因此,如果您需要操作对象,则需要使用(when looping through an array or list of data. However, there is a catch. The element is just a copy of the original. So, if you need to manipulate the object, you need to use a) For =
循环代替.例如:清除数组时,很想这样做:(loop instead. For example: when clearing out the array, it is tempting to do it this way:)
For Each Item As CellStateClass In _uCells
Item = Nothing
Next
但是,因为我们正在操纵数组的实际元素,所以我们需要这样做:(But because we are manipulating the actual elements of the array, we need to do it this way instead:)
For I As Int32 = 0 To 80
_uCells(I) = Nothing
Next
正如Singleton模式和(As demonstrated by the Singleton pattern and the) SyncLock/Mutex
声明,没有正确或错误的方法来编码解决方案.有时,这只是个人喜好问题.这是(statements, there is no right or wrong way to code a solution. Sometimes, it is just a matter of personal preference. This is where the)编程艺术(Art of Programming)发挥作用.(comes into play.)
在我多年前参与的代码审查中,一个初级程序员编写了这段代码:(During a code review I participated in many years ago, a junior programmer wrote this piece of code:)
Dim bFlag as Boolean
...
Select Case bFlag
Case True
' Do something
Case Else
' Do something else
End Select
我们花了一些时间来讨论编码这样一条语句的优点.不用说,此代码通过了技术审查,因为它可以正常工作.就个人而言,我永远不会被那样写死于代码.对我来说,首选方法是:(We spent some time debating the merits of coding such a statement. Needless to say, this code passed the review since technically, it works. Personally, I would never be caught dead writing code like that. For me, the preferred method is:)
If bFlag Then
' Do something
Else
' Do something else
End If
以下是我在此版本的游戏中忽略的功能列表:(Here is a list of features that I left out in this version of the game:)
- 当…的时候 “(When the “)拼图完成(Puzzle Complete)对话框弹出,在后台添加某种烟花汇演.(” dialog pops up, add some kind of fireworks display in the background.)
- 当用户在游戏中途退出时,保存游戏状态,并在下次程序加载时恢复游戏状态.(Save the state of game when the user quits in the middle of a game and restore it the next time the program loads.)
- 当用户暂停游戏时,请在隐藏游戏的空白面板上涂鸦.(When the user pauses the game, doodle something on the blank panel that hides the game.)
- 跟踪每个级别的10个最佳时间.(Keep track of the 10 best times per level.)
- 允许用户更改配色方案或游戏外观.(Allow the user to change the color scheme or look of the game.)
- 实施打印例程,以便用户可以打印出游戏网格.(Implement the print routine so the user can print out the game grid.)
- 实施遮罩图案,使其在垂直轴上对称.(Implement the masking pattern so that it is symmetrical on the vertical axis.)
- 使预生成的游戏数可变.现在,它被硬编码为5.(Make the number of pre-generated games variable. Right now, it is hard coded to 5.)
- 在主屏幕的右下角显示每个级别生成的游戏数量.(Display at the bottom right corner of the main screen, the number of games that have been generated per level.)
历史(History)
- 2014-10-24:代码/文章的第一个版本(2014-10-24: First release of the code/article)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
VB.NET Windows VS2013 Dev 新闻 翻译