sudoku me_Sudoku,一个完整的MFC应用程序。 第11部分

sudoku me

介绍: (Introduction:)

对话框(2)无模式对话框和辅助线程。 处理线程之间共享的数据。 递归函数。

Continuing from the tenth article about sudoku.  

继续关于数独的第十篇文章。

Last article we worked with a modal dialog to help maintain information in the database.  This time we will be working with a modeless dialog to help ‘solve’ a game of Sudoku.  That assistance is provided in a worker thread.

上一篇文章,我们使用模式对话框来帮助维护数据库中的信息。 这次,我们将使用无模式对话框来帮助“解决”数独游戏。 该帮助是在工作线程中提供的。

From the resource editor add a new dialog and change the caption to ‘Solve’ (properties menu of the dialog) and the ID to ID_DLG_SOLVE.  A modeless dialog needs the default OK and Cancel behaviours to be stopped. The default behaviour calls EndDialog which is not what should happen, the dialog should be dismissed with a call to DestroyWindow.  From the resource editor, on this new dialog, double click on the OK button.  The add new class wizard should start, enter CDlgSolve as the name and leave other options as defaults.  Click finish and the .h and .cpp files are created for us.  You might notice that no OnOK handler was created – so go back to the resource editor and double click the OK button again.  Now we have a function OnBnClickedOk() (earlier versions of Visual Studio created an OnOK function) which has one line of code – OnOK();  We need to comment this line out.  Repeat for the Cancel button so we have the following:

在资源编辑器中,添加一个新对话框,并将标题更改为“解决”(对话框的属性菜单),将ID更改为ID_DLG_SOLVE。 无模式对话框需要停止默认的“确定”和“取消”行为。 默认行为是调用EndDialog,这不是应该发生的情况,应通过调用DestroyWindow来关闭对话框。 在资源编辑器的新对话框中,双击“确定”按钮。 应该会启动“添加新类”向导,输入CDlgSolve作为名称,其他选项保留为默认值。 单击完成,将为我们创建.h和.cpp文件。 您可能会注意到没有创建OnOK处理程序–因此,请回到资源编辑器并再次双击OK按钮。 现在我们有了一个函数OnBnClickedOk()(Visual Studio的早期版本创建了一个OnOK函数),它具有一行代码– OnOK(); 我们需要对此行进行注释。 重复单击“取消”按钮,这样我们可以得到:

void CDlgSolve::OnBnClickedOk()
{
    // modeless dialog - do not call OnOK
    //OnOK();
}

void CDlgSolve::OnBnClickedCancel()
{
    // modeless dialog - do not call OnCancel
    //OnCancel();
}

Now we can go back to the resource editor, select the OK and Cancel buttons and delete them.

现在,我们可以返回到资源编辑器,选择“确定”和“取消”按钮并将其删除。

Let us now use this to demonstrate some behaviour about dialogs.  From the resource editor open the main menu for the app, then the edit sub menu, select the ‘Solve’ menu point and add handlers for both the COMMAND ad UPDATE_COMMAND_UI events, they should be in the CSudokuView.  This solve function should be available for a game, if there is no game then the Info variable of the document is an empty string.

现在让我们用它来演示一些有关对话框的行为。 在资源编辑器中,打开应用程序的主菜单,然后在编辑子菜单中,选择“解决”菜单点,并为两个COMMAND ad UPDATE_COMMAND_UI事件添加处理程序,它们应位于CSudokuView中。 此求解函数应可用于游戏,如果没有游戏,则文档的Info变量为空字符串。

void CSudokuView::OnUpdateEditSolve(CCmdUI *pCmdUI)
{
    pCmdUI->Enable(!GetDocument()->GetInfo().IsEmpty());    //Game being played - available to be solved
}

Now just for example purposes:

现在仅出于示例目的:

void CSudokuView::OnEditSolve()
{
    CDlgSolve dlg;
    dlg.DoModal();
}

Don’t forget to add the #include for the header or else we get an error on compilation.  Now press F5 key to compile and run.  Open a random game and click the ‘solve’ button.  We now get the dialog displayed.  Try to click the main application window – you can’t can you?  This is how a modal dialog behaves, it disables the other windows in the application – forces you to respond to this window and dismiss it before you can continue.  So now we can close this window, press the enter key (nothing), esc key (nothing) click on the little red ‘x’ at the top right of the window – aaargh nothing.  Don’t panic, go back to the IDE and press shift + F5 keys – this stops debugging and closes the app.

不要忘记为标题添加#include,否则编译时会出错。 现在按F5键编译并运行。 打开一个随机游戏,然后单击“解决”按钮。 现在,我们将显示对话框。 尝试单击主应用程序窗口-不能吗? 这是模态对话框的行为方式,它禁用了应用程序中的其他窗口–迫使您响应该窗口并在继续之前将其关闭。 因此,现在我们可以关闭此窗口,按Enter键(无),esc键(无)单击窗口右上方的红色小“ x” –什么也没有。 不要惊慌,回到IDE并按shift + F5键-这将停止调试并关闭应用程序。

What is going on?  On a dialog if you press the enter key the default behaviour is to act as if the OK button was pressed – and we have disabled that.  Pressing the esc key has the default behaviour of the cancel button and we have stopped that.  The little red ‘x’ ?  Well that is the same as the cancel button.

到底是怎么回事? 在对话框中,如果您按Enter键,则默认行为是就像按下“确定”按钮一样–我们已将其禁用。 按下esc键具有取消按钮的默认行为,我们已经停止了该行为。 小小的红色“ x”? 好吧,这与取消按钮相同。

Let’s fix the problem (that we can’t close the window) and at the same time also start the dialog as a modeless dialog.  We will enable the dismissal of the dialog by the esc key and the red ‘x’.  We need to modify the OnCancel routine to call DestroyWindow:

让我们解决问题(无法关闭窗口),同时将对话框作为无模式对话框启动。 我们将使用esc键和红色的“ x”启用对对话框的关闭。 我们需要修改OnCancel例程以调用DestroyWindow:

void CDlgSolve::OnBnClickedCancel()
{
    // modeless dialog - do not call OnOK
    //OnCancel();
    DestroyWindow();
}

Now should you try the dialog again it will disappear with the esc key press but it also crashes – because this is now how you dismiss a modeless dialog NOT a modal dialog.

现在,您应该再次尝试对话框,如果按esc键,该对话框将消失,但也会崩溃-因为这是您关闭无模式对话框而不是模式对话框的方式。

Back to the OnEditSolve function – next attempt.

返回OnEditSolve函数–下次尝试。

void CSudokuView::OnEditSolve()
{
    CDlgSolve dlg;
    dlg.Create(CDlgSolve::IDD);
    dlg.ShowWindow(SW_SHOW);
}

Try it again and you briefly see the dialog appear then disappear.  This is another mistake that people make with a modeless dialog – the code runs, the dialog is created then the code continues running (unlike a modal dialog – test it, put a breakpoint on DoModal and single step, it only continues after the DoModal finishes when the dialog is dismissed).  So in this case the code runs and the variable dlg goes out of scope and is destroyed.  <This is actually a common problem, especially with numbers of UI objects (fonts, brushes…) which one sees as created locally then selected into a window or DC, then the programmer wonders why the UI doesn’t use the new resource.>   The solution for our dialog is simple (well there are two ways, the other having the CDlgSolve variable a member of the class – declare in the .h file) in that we use the new keyword to create a new block of memory:

再试一次,您会短暂地看到对话框出现,然后消失。 这是人们使用无模式对话框会犯的另一个错误 –代码运行,创建对话框然后代码继续运行(与模式对话框不同–测试它,在DoModal上设置一个断点,然后一步执行,仅在DoModal完成后才继续取消对话框时)。 因此,在这种情况下,代码运行并且变量dlg超出范围并被破坏。 <这实际上是一个普遍的问题,尤其是对于许多UI对象(字体,笔刷...),人们认为它们是本地创建的,然后被选择到窗口或DC中,然后程序员想知道为什么UI不使用新资源。>对话框的解决方案很简单(有两种方法,另一种方法是使CDlgSolve变量成为该类的成员–在.h文件中声明),因为我们使用new关键字创建了一个新的内存块:

void CSudokuView::OnEditSolve()
{
    CDlgSolve* pDlg = new CDlgSolve;
    pDlg->Create(CDlgSolve::IDD);
    pDlg->ShowWindow(SW_SHOW);
}

Great – or is it?  If you run the app, show the dialog then close the app you will see in the output window of the IDE the message – ‘Detected memory leaks!’.  

很好–是吗? 如果运行该应用程序,则显示对话框,然后关闭该应用程序,您将在IDE的输出窗口中看到以下消息:“检测到内存泄漏!”。

We can’t just delete the newly assigned memory before the function exits, fortunately the destruction of a window takes a route so that a virtual function called PostNcDestroy is called.  From the class view we select the properties of the class CDlgSolve, then click the button for overrides and add a handler for PostNcDestroy. At this point the window components are released, now we can release the memory:

我们不能只是在函数退出之前删除新分配的内存,所幸的是,销毁窗口需要一条路线,因此调用了称为PostNcDestroy的虚拟函数。 从类视图中,我们选择类CDlgSolve的属性,然后单击替代按钮并为PostNcDestroy添加处理程序。 此时释放了窗口组件,现在我们可以释放内存了:

void CDlgSolve::PostNcDestroy()
{
	CDialog::PostNcDestroy();
	delete this;	//modelesss dialog, release memory assigned with new
}

For those of you that don’t like the ‘new’ and ‘delete’ operations belonging to different classes the  ‘delete this’ in the PostNcDestroy is found in the MFC samples supplied by Microsoft.

对于不喜欢属于不同类的“ new”和“ delete”操作的那些人,Microsoft提供的MFC示例中提供了PostNcDestroy中的“ delete this”。

Now we have a functional modeless dialog without any memory leaks.  A lot of work to display a dialog – so what not use a modal one as before?  Simple, display a modal dialog such as the Maintenance dialog and the main app is locked to mouse and keyboard.  Now display the solve dialog – the main app can still be interacted with.

现在,我们有了功能正常的无模式对话框,没有任何内存泄漏。 显示对话框需要做很多工作-那么如何不像以前那样使用模式对话框呢? 很简单,显示一个模式对话框,例如“维护”对话框,并且主应用程序已锁定在鼠标和键盘上。 现在显示“求解”对话框-主应用程序仍可以与之交互。

Now we want to ‘solve’ the game.  Now I could run the code in the modeless dialog because the main app can still respond to user input but doing some computationally intensive work is a good place for using a worker thread.  (It also allows me to show you how to interact between threads with MFC based code).

现在我们要“解决”游戏。 现在,我可以在无模式对话框中运行代码,因为主应用程序仍可以响应用户输入,但是进行一些计算量大的工作是使用辅助线程的好地方。 (它还使我向您展示了如何与基于MFC的代码在线程之间进行交互)。

We need the details of the game to be solved, so we will modify the constructor of the solve dialog and pass the details in as it is created.  The solve dialog also requires member variables to store the details and some code to display the game – very similar to the modal dialog displaying the current game.  I’ll just post the code here and won’t explain further.

我们需要解决游戏的细节,因此我们将修改“解决”对话框的构造函数,并在创建游戏时将其传递给我们。 “求解”对话框还需要成员变量来存储详细信息,并需要一些代码来显示游戏-与显示当前游戏的模式对话框非常相似。 我将代码发布在这里,不再赘述。

void CSudokuView::OnEditSolve()
{
    char arGame[82];
    ZeroMemory(arGame, 82);
    for(int i = 0; i < 81; i++)
        arGame[i] = '0' + m_arWndButtons[i].GetValue();

    CDlgSolve* pDlg = new CDlgSolve(m_arGame);
    pDlg->Create(CDlgSolve::IDD);
    pDlg->ShowWindow(SW_SHOW);
}
class CDlgSolve : public CDialog
{
    DECLARE_DYNAMIC(CDlgSolve)

public:
    CDlgSolve(LPCTSTR cpszGame, CWnd* pParent = NULL);   // custom constructor
    virtual ~CDlgSolve();

// Dialog Data
    enum { IDD = IDD_DLG_SOLVE };

protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnBnClickedOk();
    afx_msg void OnBnClickedCancel();
protected:
    virtual void PostNcDestroy();
private:
    CString m_szGame;
public:
    afx_msg void OnPaint();
};
#include "stdafx.h"
#include "Sudoku.h"
#include "DlgSolve.h"
#include "SudokuDCPainter.h"


// CDlgSolve dialog

IMPLEMENT_DYNAMIC(CDlgSolve, CDialog)

CDlgSolve::CDlgSolve(LPCTSTR cpszGame, CWnd* pParent /*=NULL*/)
    : CDialog(CDlgSolve::IDD, pParent)
    , m_szGame(cpszGame)
{

}

CDlgSolve::~CDlgSolve()
{
}

void CDlgSolve::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
}


BEGIN_MESSAGE_MAP(CDlgSolve, CDialog)
    ON_BN_CLICKED(IDOK, &CDlgSolve::OnBnClickedOk)
    ON_BN_CLICKED(IDCANCEL, &CDlgSolve::OnBnClickedCancel)
    ON_WM_PAINT()
END_MESSAGE_MAP()


// CDlgSolve message handlers

void CDlgSolve::OnBnClickedOk()
{
    // modeless dialog - do not call OnOK
    //OnOK();
}

void CDlgSolve::OnBnClickedCancel()
{
    // modeless dialog - do not call OnCancel
    //OnCancel();
    DestroyWindow();
}

void CDlgSolve::PostNcDestroy()
{
    CDialog::PostNcDestroy();
    delete this;    //modelesss dialog, release memory assigned with new
}

void CDlgSolve::OnPaint()
{
    CPaintDC dc(this); // device context for painting
    // TODO: Add your message handler code here
    // Do not call CDialog::OnPaint() for painting messages

    CSudokuDCPainter painter;
    painter.Paint(this, &dc, IDC_STATIC_BOUNDARY, m_szGame);
}

You can now compile, load a random game and click the ‘solve’ option – you should see the dialog appear with the game plus any modifications you have made to it.  Leave the solve dialog alone and select another random game and click on ‘solve’ again.  A second dialog appears with this second game.  If you close the main app then both the ‘solve’ dialogs also close and in the IDE there is no warning about memory leaks.

现在,您可以编译,加载随机游戏,然后单击“解决”选项-您应该会看到对话框随游戏一起显示以及您对该游戏所做的任何修改。 保留求解对话框,然后选择另一个随机游戏,然后再次单击“求解”。 第二个对话框出现第二个对话框。 如果关闭主应用程序,则两个“解决”对话框也将关闭,并且在IDE中,没有关于内存泄漏的警告。

Now for the worker thread.  

现在是工作线程。

Add a handler for the OnInitDialog function of the CDlgSolve – properties of the class…  Here we will start the thread.  We need to pass some information to the thread, the game to be solved and the window handle of the solve dialog for receiving information back.  Note a very important point.  MFC is NOT thread safe and passing MFC objects such as CWnd pointers between threads is dangerous.  Sometimes it might work, other times one runs into thread specific information in the MFC object which for the other thread is NULL and boom!!  Your app crashes.  Unfortunately, even here at EE, one sees experts suggesting passing MFC objects between threads is OK.

为CDlgSolve的OnInitDialog函数添加处理程序–类的属性…在这里,我们将启动线程。 我们需要将一些信息传递给线程,要解决的游戏以及解决对话框的窗口句柄,以接收信息。 请注意非常重要的一点。 MFC不是线程安全的,在线程之间传递MFC对象(例如CWnd指针)很危险。 有时它可能会起作用,有时它会遇到MFC对象中特定于线程的信息,而对于另一个线程,该信息为NULL! 您的应用崩溃了。 不幸的是,即使在EE上,也有人看到专家建议在线程之间传递MFC对象是可以的。

struct stSolveInfo
{
    HWND hWndParent;
    char szGame[82];
    char szGameInProgress[82];
    bool bSuccess;
    unsigned long ulCounter;
    CCriticalSection csAccessControl;
    CEvent ceCloseThread;
    CEvent ceThreadIsFinished;
};

Above are the details of the information package to be shared (passed) to the thread.  The HWND is for passing messages from the thread to the dialog via the window handle (which is NOT an MFC object, it is a native windows variable).  The character array containing the original game and also one for a game being processed in the thread.  A Boolean flag to determine if the game is solved and an unsigned long to give an indication of how many attempts have been made to find a solution.  Next comes a CriticalSection – this will force one thread to wait if the other thread is performing some action inside the critical section.  This is typically reading/writing into a common variable.  Finally two CEvent objects, these allow an interthread communication.  An event can be raised (set to ‘true’ or ‘false’) in one thread and checked in the other thread.  The checking mechanism is rather neat, one can specify a wait time.  For the use here one event will instruct the thread is has to stop working – the event is checked each time the thread goes through a loop or periodic action.  The other is set when the thread actually finishes so the dialog ‘knows’ the thread is ended.  This is required should the user close the ‘solve’ dialog whilst the thread is running.  One can see that implemented in the OnDestroy function of the CSolveDialog.  I also pass a custom message from the thread to the dialog to indicate the thread is working.  The dialog uses the critical section to prevent writing to the character array by the thread whilst the dialog is reading and displaying that.

上面是要共享(传递)给线程的信息包的详细信息。 HWND用于通过窗口句柄(不是MFC对象,它是本机Windows变量)将消息从线程传递到对话框。 包含原始游戏的字符数组,以及在线程中正在处理的游戏的字符数组。 一个布尔型标志,用于确定游戏是否已解决;一个无符号的长整数,用于指示进行了多少次尝试来寻找解决方案。 接下来是CriticalSection –这将迫使一个线程等待,如果另一个线程正在关键部分内执行某些操作。 这通常是读取/写入公共变量。 最后是两个CEvent对象,它们允许线程间通信。 可以在一个线程中引发事件(将其设置为“ true”或“ false”),并在另一个线程中进行检查。 检查机制相当简洁,可以指定等待时间。 为了在此使用,一个事件将指示线程必须停止工作–每次线程执行循环或周期性操作时都会检查该事件。 另一个在线程实际完成时设置,因此对话框“知道”线程已结束。 如果用户在线程运行时关闭“解决”对话框,则这是必需的。 可以看到在CSolveDialog的OnDestroy函数中实现了该功能。 我还将自定义消息从线程传递到对话框,以指示线程正在工作。 对话框使用关键部分来防止在对话框读取和显示字符数组时通过线程写入字符数组。

解决游戏的线程功能: (The thread function to solve the game:)

我将使用两种方法。 第一种是蒙特卡洛方法(随机数)。 实际上这完全不适合此操作。 它将找到解决方案。 也许在一秒钟之内,也许宇宙首先过期了。 考虑– 81个单元格。 假设每个9块中有3个为其分配了一个数字,从而剩下54个单元格。 现在我们在每个单元格中放置一个随机数1..9并进行测试,并说我们每秒可以执行1000次。 为简单起见,我们假设是10个随机数,因此将10的幂乘以54除以1000秒来探索每种可能性。在最坏的情况下,如果最后一种可能性是解决方案,则不会重复。 但是,它很好地显示了系统正在运行,并且在线程正在运行时,“解决”对话框对用户输入做出了响应。

The second method uses recursion and brute force.  I put a value into a cell, providing it doesn’t have a value already, and then I go to the next cell (top left start, work along row 1 then row 2 until bottom right is reached).  At each cell I check what values would be allowed (ie. Not used in any block that cell belongs to).  If there are no possible value then one, or more, of the numbers already set are false so I return from the recursive function to effectively undo the last change.  Eventually it will fill the grid and a solution is found, or all possibilities are exhausted and no solution exists.

第二种方法使用递归和蛮力。 我将一个值放入一个单元格中,前提是该值尚无值,然后转到下一个单元格(从左上角开始,先从第1行开始,然后到第2行直到到达右下角)。 在每个单元格中,我检查允许的值(即,该单元格所属的任何块中均未使用)。 如果没有可能的值,则已经设置的一个或多个数字为false,因此我从递归函数返回以有效撤消上一次更改。 最终它将填满网格并找到解决方案,或者所有可能性都被耗尽并且不存在解决方案。

I also use a #define to allow a quick switching between the two methods, just comment the #define out or uncomment it.  You should see the code in the editor change colour to indicate which code segments will be considered for compilation.

我还使用#define允许在两种方法之间快速切换,只需注释掉#define或取消注释即可。 您应该在编辑器中看到代码更改颜色,以指示将考虑进行编译的代码段。

Here is the code for the header file

这是头文件的代码

#pragma once

// CDlgSolve dialog
#include "afxmt.h"        //For the CCriticalSection, CEvent classes

struct stSolveInfo
{
    HWND hWndParent;
    char szGame[82];
    char szGameInProgress[82];
    bool bSuccess;
    unsigned long ulCounter;
    CCriticalSection csAccessControl;
    CEvent ceCloseThread;
    CEvent ceThreadIsFinished;
};

class CDlgSolve : public CDialog
{
    DECLARE_DYNAMIC(CDlgSolve)

public:
    CDlgSolve(LPCTSTR cpszGame, CWnd* pParent = NULL);   // custom constructor
    virtual ~CDlgSolve();

// Dialog Data
    enum { IDD = IDD_DLG_SOLVE };

protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnBnClickedOk();
    afx_msg void OnBnClickedCancel();
protected:
    virtual void PostNcDestroy();
private:
    CString m_szGame;
public:
    afx_msg void OnPaint();
    virtual BOOL OnInitDialog();

private:
    afx_msg LRESULT OnSolveProgress(WPARAM wParam, LPARAM lParam);
    stSolveInfo m_stSolveInfo;
public:
    afx_msg void OnDestroy();
};

And for the implementation

并为实施

// DlgSolve.cpp : implementation file
//

#include "stdafx.h"
#include "Sudoku.h"
#include "DlgSolve.h"
#include "SudokuDCPainter.h"
#include "randomnumber.h"
#include "grid.h"

//comment out the following to switch between mote carlo and brute force methodds in the thread.
#define __MONTE_CARLO_THREAD__


UINT SolveSudokuProc(LPVOID pParam);
void FillTempGame(CGrid& grid, stSolveInfo *pstSolveInfo, bool bGameSuccess);

#ifndef __MONTE_CARLO_THREAD__
bool ProcessCell(CGrid& grid, int iCell, stSolveInfo *pstSolveInfo);
#endif



// CDlgSolve dialog
#define SOLVE_PROGRESS (WM_USER+105)

IMPLEMENT_DYNAMIC(CDlgSolve, CDialog)

CDlgSolve::CDlgSolve(LPCTSTR cpszGame, CWnd* pParent /*=NULL*/)
    : CDialog(CDlgSolve::IDD, pParent)
    , m_szGame(cpszGame)
{

}

CDlgSolve::~CDlgSolve()
{
}

void CDlgSolve::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
}


BEGIN_MESSAGE_MAP(CDlgSolve, CDialog)
    ON_BN_CLICKED(IDOK, &CDlgSolve::OnBnClickedOk)
    ON_BN_CLICKED(IDCANCEL, &CDlgSolve::OnBnClickedCancel)
    ON_WM_PAINT()
    ON_MESSAGE(SOLVE_PROGRESS, &CDlgSolve::OnSolveProgress)
    ON_WM_DESTROY()
END_MESSAGE_MAP()


// CDlgSolve message handlers

void CDlgSolve::OnBnClickedOk()
{
    // modeless dialog - do not call OnOK
    //OnOK();
}

void CDlgSolve::OnBnClickedCancel()
{
    // modeless dialog - do not call OnCancel
    //OnCancel();
    DestroyWindow();
}

void CDlgSolve::PostNcDestroy()
{
    CDialog::PostNcDestroy();
    delete this;    //modelesss dialog, release memory assigned with new
}

void CDlgSolve::OnPaint()
{
    CPaintDC dc(this); // device context for painting
    // TODO: Add your message handler code here
    // Do not call CDialog::OnPaint() for painting messages

    CSudokuDCPainter painter;
    painter.Paint(this, &dc, IDC_STATIC_BOUNDARY, m_szGame);
}

BOOL CDlgSolve::OnInitDialog()
{
    CDialog::OnInitDialog();

    m_stSolveInfo.hWndParent = GetSafeHwnd();
    ZeroMemory(m_stSolveInfo.szGame, sizeof(m_stSolveInfo.szGame));
    strcpy(m_stSolveInfo.szGame, m_szGame);
    memcpy(m_stSolveInfo.szGameInProgress, m_stSolveInfo.szGame, 82);
    m_stSolveInfo.bSuccess = false;
    m_stSolveInfo.ulCounter = 0;
    m_stSolveInfo.ceCloseThread.ResetEvent();
    m_stSolveInfo.ceThreadIsFinished.ResetEvent();

    CWinThread* pThread = AfxBeginThread(SolveSudokuProc, &m_stSolveInfo, 0, 0, CREATE_SUSPENDED);
    if(pThread  != NULL)
    {
        pThread->m_bAutoDelete = true;
        pThread->ResumeThread();
    }

    return TRUE;  // return TRUE unless you set the focus to a control
    // EXCEPTION: OCX Property Pages should return FALSE
}

LRESULT CDlgSolve::OnSolveProgress(WPARAM wParam, LPARAM lParam)
{
    //Remove multiple copies of the message should there be any in the queue
    //else this function is being called continuously and hogging the main thread of the app
    MSG msg;
    while(PeekMessage(&msg, NULL, SOLVE_PROGRESS, SOLVE_PROGRESS, PM_REMOVE));

    CString s;

    m_stSolveInfo.csAccessControl.Lock();
    m_szGame = m_stSolveInfo.szGameInProgress;

    if(m_stSolveInfo.bSuccess)
        s.LoadString(IDS_SUCCESS);
    else
    {
        if(m_stSolveInfo.ulCounter == (unsigned long)-1)
            s.LoadString(IDS_FAIL);
        else
            s.Format(IDS_TRIES, m_stSolveInfo.ulCounter);
    }
    m_stSolveInfo.csAccessControl.Unlock();

    SetDlgItemText(IDC_STATIC_PROGRESS, s);

    Invalidate();
    UpdateWindow();

    return 1;
}

void CDlgSolve::OnDestroy()
{
    // Signal the thread to close
    m_stSolveInfo.ceCloseThread.SetEvent();

    //Wait for it actually to be closed
    ::WaitForSingleObject(m_stSolveInfo.ceThreadIsFinished.m_hObject, INFINITE);

    CDialog::OnDestroy();
}

#ifdef __MONTE_CARLO_THREAD__

UINT SolveSudokuProc(LPVOID pParam)
{
    //The pParam needs to be cast to the actual type of object it is
    stSolveInfo *pstSolveInfo = (stSolveInfo*)pParam;

    //Initialise the grid
    CGrid grid;
    int iRow, iCol, iCell, iValue;
    for(iRow = 0; iRow < 9; iRow++)
    {
        for(iCol = 0; iCol < 9; iCol++)
        {
            iCell = iRow * 9 + iCol;
            grid.AddCell(iRow, iCol, (UINT_PTR)iCell);
        }
    }

    CRandomNumber random;

    while(true)
    {
        //If the solve dialog is closing we need to stop the thread
        if(::WaitForSingleObject(pstSolveInfo->ceCloseThread.m_hObject, 0) == WAIT_OBJECT_0)
        {
            pstSolveInfo->ceThreadIsFinished.SetEvent();
            return 0;
        }

        //Fill the grid
        for(iRow = 0; iRow < 9; iRow++)
        {
            for(iCol = 0; iCol < 9; iCol++)
            {
                iCell = iRow * 9 + iCol;
                iValue = pstSolveInfo->szGame[iCell] - '0';

                if(iValue == 0)
                    grid.SetValue((UINT_PTR)iCell, random.GetRandom(1, 9));
                else
                    grid.SetValue((UINT_PTR)iCell, iValue);
            }
        }

        //Now check if the game is valid
        bool bGameSuccess = true;
        for(iCell = 0; iCell < 81; iCell++)
        {
            if(!grid.CheckValid((UINT_PTR)iCell))
            {
                bGameSuccess = false;
                break;
            }
        }

        //Fill the game in progress info
        FillTempGame(grid, pstSolveInfo, bGameSuccess);

        //Inform parent, OCCASIONALLY else the main thread is overloaded
        if(bGameSuccess || ((++pstSolveInfo->ulCounter & 0xFF) == 0))
        {
            if(bGameSuccess)
                pstSolveInfo->ulCounter = (unsigned long)-1;
            PostMessage(pstSolveInfo->hWndParent, SOLVE_PROGRESS, 0, 0);
        }

        if(bGameSuccess)    //task finished
            break;    
    }

    pstSolveInfo->ceThreadIsFinished.SetEvent();
    return 0;
}

#else

UINT SolveSudokuProc(LPVOID pParam)
{
    //The pParam needs to be cast to the actual type of object it is
    stSolveInfo *pstSolveInfo = (stSolveInfo*)pParam;

    //Initialise the grid
    CGrid grid;
    int iRow, iCol, iCell, iValue;
    for(iRow = 0; iRow < 9; iRow++)
    {
        for(iCol = 0; iCol < 9; iCol++)
        {
            iCell = iRow * 9 + iCol;
            grid.AddCell(iRow, iCol, (UINT_PTR)iCell);

            iValue = pstSolveInfo->szGame[iCell] - '0';
            grid.SetValue((UINT_PTR)iCell, iValue);
        }
    }

    unsigned long ulCounter = 0;
    iCell = 0;

    //Start the recursive procedure
    ProcessCell(grid, iCell, pstSolveInfo);

    //Now check if the game is valid
    bool bGameSuccess = true;
    for(iCell = 0; iCell < 81; iCell++)
    {
        if(!grid.CheckValid((UINT_PTR)iCell))
        {
            bGameSuccess = false;
            break;
        }
    }

    //Fill the game in progress info
    FillTempGame(grid, pstSolveInfo, bGameSuccess);
    pstSolveInfo->ulCounter = (unsigned long)-1;

    PostMessage(pstSolveInfo->hWndParent, SOLVE_PROGRESS, 0, 0);

    pstSolveInfo->ceThreadIsFinished.SetEvent();
    return 0;
}

bool ProcessCell(CGrid& grid, int iCell, stSolveInfo *pstSolveInfo)
{
    //If the solve dialog is closing we need to stop the thread
    if(::WaitForSingleObject(pstSolveInfo->ceCloseThread.m_hObject, 0) == WAIT_OBJECT_0)
    {
        pstSolveInfo->ceThreadIsFinished.SetEvent();
        return true;    //unwind the recursion stack
    }

    //we work through each cell and use the possible values it could have
    //and recurse into this function with the next cell
    //either we get no possible values allowed or eventually the solution of the game
    if(iCell == 81) //The cell before (81) had a possible value so the game is solved
        return true;    //unwind the recursion stack

    //If this has a 0 then we need to find what is possible for it and start the processing OR we move to the next cell
    bool bResult = false;
    if(grid.GetValue(iCell) == 0)
    {
        bool bAllowed[9]; 
        FillMemory(bAllowed, sizeof(bAllowed), true);    //initially all are allowed
        grid.PrepareAllows(iCell, bAllowed);

        //Now loop through the allowed values, set to that then move to next cell via recursion
        for(int i = 0; i < 9; i++)
        {
            if(bAllowed[i])
            {
                //This is a possible, set the cell value
                grid.SetValue(iCell, i+1);    //zero based counting - the value is actually one greater than the index
                bResult = ProcessCell(grid, iCell + 1, pstSolveInfo);
                if(bResult == true)
                    break;
            }
        }
        
        if(bResult == false)
        {
            //unset the value here prior to returning
            grid.SetValue(iCell, 0);
        }
    }
    else
        bResult = ProcessCell(grid, iCell + 1, pstSolveInfo);


    //when starting to unwind we pump the attempted counter to the parent
    //note this is really just to give a crude feedback
    pstSolveInfo->ulCounter++;
    if((pstSolveInfo->ulCounter & 0xFF) == 0)    //every 256 increments
    {
        //Fill the game in progress info
        FillTempGame(grid, pstSolveInfo, false);

        PostMessage(pstSolveInfo->hWndParent, SOLVE_PROGRESS, 0, 0);
    }

    return bResult;
}

#endif

void FillTempGame(CGrid& grid, stSolveInfo *pstSolveInfo, bool bGameSuccess)
{
    pstSolveInfo->csAccessControl.Lock();
    for(int iCell = 0; iCell < 81; iCell++)
    {
        pstSolveInfo->szGameInProgress[iCell] = '0' + grid.GetValue((UINT_PTR)iCell);
    }
    pstSolveInfo->bSuccess = bGameSuccess;
    pstSolveInfo->csAccessControl.Unlock();
}

Note that I don’t use a critical section to prevent simultaneous read/write to the counter.  For our purposes here it doesn’t make any difference if the thread overwrites the counter as it is partially read.

请注意,我不使用关键部分来防止同时读取/写入计数器。 就我们的目的而言,如果线程在部分读取时覆盖了计数器,则没有任何区别。

结论(1): (Conclusion (1):)

We have seen how to create and destroy modeless dialogs.

我们已经看到了如何创建和销毁无模式对话框。

We have shared data between threads and communicated with MFC objects from a different thread.

我们在线程之间共享数据,并与其他线程中的MFC对象进行通信。

We have seen recursive functions in operation.

我们已经看到了递归函数的运行。

We have seen a 'Monte Carlo' technique in operation.

我们已经看到了运行中的“蒙特卡洛”技术。

结论(2): (Conclusion (2):)

The set of articles has been about writing code, not about design.  The design (data, business, user tiers) has not been adressed.

这组文章是关于编写代码,而不是设计。 设计(数据,业务,用户层)尚未解决。

At one point there was a bug which we had to find.  It was totally due to making COPIES of data - don't do that in the real world.  It is hard to describe just how bad that can be.

在某一时刻,我们必须找到一个错误。 这完全是由于制作了COPIES数据-请勿在现实世界中这样做。 很难描述到底有多糟。

Also not making the copy as we did would actually have made some bits easier to code - but then I couldn't demonstrate a technique that I wanted to.

同样,不像我们那样制作副本实际上会使一些代码更容易编写-但后来我无法展示我想要的技术。

So, is this application good or bad?

那么,此应用程序是好是坏?

It works and does exactly what it should do and does it well. Good.

它可以正常工作,并且应该做的很好。 好。

It is not easy to make fundamental changes.  Bad (but that is due to the structure of the app).

进行根本性的改变并不容易。 不好(但这是由于应用程序的结构)。

Oh, and don't try to use it for testing if a game just designed is 'solvable' - this app just finds the [bold]first[/bold] possible solution if one exists, it doesn't check for a second or further possible solution.

哦,不要尝试使用它来测试刚刚设计的游戏是否“可解决”-此应用只会找到[bold] first [/ bold]可能的解决方案(如果存在),则不会检查第二或进一步的可能解决方案。

This article concludes the suite of articles.  I hope it has been of interest and even educational.  I may well use this as a base for demonstrating further programming techniques and use of the Visual Studio IDE.

本文总结了这套文章。 我希望它引起人们的兴趣,甚至具有教育意义。 我可能会以此为基础来演示进一步的编程技术和Visual Studio IDE的使用。

Click here for the source code for this article单击此处获取本文的源代码

Previous article in the series is here:  Sudoku in MFC: Part 10

该系列中的上一篇文章在这里: MFC中的Sudoku:第10部分

Two points to bear in mind.

需要牢记两点。

You may use the code but you are not allowed to distribute the resulting application either for free or for a reward (monetary or otherwise).  At least not without my express permission.

您可以使用代码,但不能免费或以奖励(货币或其他方式)分发结果应用程序。 至少没有我的明确许可。

I will perform some things to demonstrate a point – it is not to be taken as that meaning it is a ‘best’ practice, in fact an alternative might be simpler and suitable.  Some points in the code would even be called poor design and a possible source of errors.

我将做一些事情来说明一个观点–不能认为它是“最佳”实践,实际上,另一种选择可能更简单,更合适。 代码中的某些要点甚至被称为不良设计和可能的错误源。

翻译自: https://www.experts-exchange.com/articles/3831/Sudoku-a-complete-MFC-application-Part-11.html

sudoku me

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值