数独GUI程序项目实现

Posted 血夜之末

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数独GUI程序项目实现相关的知识,希望对你有一定的参考价值。

数独GUI程序项目实现

导语:最近玩上了数独这个游戏,但是找到的几个PC端数独游戏都有点老了。。。我就想自己做一个数独小游戏,也是一个不错的选择。

前期我在网上简单地查看了一些数独游戏的界面,代码。好好地了解了一下现在数独游戏的大概的框架。当然,我自己写的小游戏,也许没那么好。但是我一定会一点点升级这个小游戏的。

目前,我做的游戏是V1.0版本的,只能说实现了这个游戏的基本功能:可以进行数独游戏、可以更换背景色以及一些其他的基本功能。接下来,在空余时间,我会进行对其中一Studying功能的实现,就是数独独有数学逻辑学习。因为我发现现在的数独游戏都只是简单地游戏而已,并没有教游戏者如何去破解数独。比如摒除法、余数法、区块法、数对法这些方法都没有显式教给游戏者。这样对于游戏者的进步是不利的。一方面游戏者是为了娱乐,那么正常的Playing模式可以满足,另一方面游戏者是为了锻炼自己的大脑,那么额外的studying模式就很有必要了。想一想还有一点小激动呢。

 

 

一、项目素材:

一个游戏首先要有一个不错的界面,毕竟界面的友好度是重要决定因素。我挑选了战锤40K的几张图片作为背景图片。

游戏界面大小设定为576*576,即(64*9)*(64*9),数独9*9格子中,每个格子大小为64*64。所以编辑后的每个图片大小为576*576。

 

1.1  开始界面的图片选择:

 

 

1.2  编辑后的开始界面:

 

 

2.1  胜利界面的图片选择:

 

 

 

2.2  编辑后的胜利界面:

 

 

3.1  失败界面的图片选择:

 

 

3.2  编辑后的失败界面:

 

 

4  备注:

还有一些诸如说明界面的选择,就不展现了。

 

 

二、代码解释:

接下来的是有关于其中代码的解释。

 1.1  头文件代码:

#include "stdafx.h"
//标准应用程序框架的拓展头文件。将一些MFC标准头文件包含其中。加快编译速度。

#include "Sudo.h"
//这个是一个独立的头文件,用于处理一些诸如确立stdafx.h位置等语句。
#include "MapManager.h"
//主函数的接口函数,确立了CmapManager类的构造/析构函数,展现了程序大致框架。
#include "coordinate.h"
//坐标计算的头文件

#include <time.h>
//获取系统时间的头文件

#include <stdlib.h>
//标准库头文件,如malloc()、calloc()、system()、free()、rand()、exit()等

#ifdef _DEBUG
//如果宏定义了_DEBUG,则执行#ifdef与#endif之间的语句

#undef THIS_FILE
//取消之前对THIS_FILE的宏定义

static char THIS_FILE[]=__FILE__;
//静态设置全局

#define new DEBUG_NEW
//宏定义new为DEBUG_NEW

#endif
//与#ifdef连用。
//该连用将分配内存时的new转换为DEBUG_NEW,这样会在内存中保留源文件名和行号。
//这样在发生内存泄漏时,便于我们调试,找出问题代码。
//表示这也算是我学到的。。。还有这种套路了。

1.2  解释:

头文件的整合编译,加快了程序的速度。

 

2.1  Create函数:

 1 BOOL CMapManager::Create(CWnd *wnd, int width, int height)
 2 {
 3     Window = wnd;
 4     MapRect.SetRect(0, 0, width, height);
 5 
 6     /* 载入必要的图片 */
 7     /* 先不载入底图,因为不同状态的底图不一样 */
 8     if (!View.Create(width, height, 24)
 9         || !Number.LoadBmp("image\\\\Number2.bmp")
10         || !Cursor.LoadBmp("image\\\\Cursor2.bmp"))
11         //确保图片,光标等的读取没有问题。
12     {
13         return FALSE;
14     }
15     //任意一个函数返回FALSE,便输出FALSE。
16     //确保将数字图片载入。
17     
18     /* 初始化基本数据 */
19     CursorPos = 0;
20     //光标
21 
22     topPos    = 0 * 192;
23     belowPos  = 1 * 192;
24     status    = -1;
25     difficulty = EASY;
26     
27     /* 开始主菜单 */
28     StartMainMenu();
29     return TRUE;
30 }

2.2  解释:

这是程序的主入口函数,载入主要数据并初始化。其中*wnd取得主窗口的指针,主要用于得到主窗口句柄。至于width与height表示view类的宽度和高度,都是在头文件中确立的固定值。

 

3.1  DrawMap函数:

void CMapManager::DrawMap()
{
    InitMap();
        //初始化地图
    DrawNumber();

}

3.2  解释:

用于绘制游戏时的底图。

 

4.1  DrawCursor函数:

 1 void CMapManager::DrawCursor()
 2 {
 3     CPoint point(CursorPos.x * GRID_WIDTH, CursorPos.y * GRID_HEIGHT);
 4     if(status == GAME)
 5     {
 6         MCursor.SetDrawPos(point + CPoint(2, 2));
 7     }
 8     else if(status == MAINMENU)
 9     {
10         MCursor.SetDrawPos(CPoint(5 * GRID_WIDTH, point.y));
11     }
12     else if(status == OPTION)
13     {
14         /* 绘制选定的前景和背景对应的方块上的光标 */
15         int index = topPos / 192;
16         CPoint LT(3  * GRID_WIDTH, (1 + index / 5) * GRID_HEIGHT);
17         MCursor.SetDrawPos(LT + CPoint((index % 5) * GRID_WIDTH, 0));
18         MCursor.Draw(View);
19 
20         index = belowPos / 192;
21         LT.y = (4 + index / 5) * GRID_HEIGHT;
22         MCursor.SetDrawPos(LT + CPoint((index % 5) * GRID_WIDTH, 0));
23         MCursor.Draw(View);
24 
25         MCursor.SetDrawPos(point);
26     }
27 
28     MCursor.Draw(View);
29 }

4.2  解释:

用于绘制光标。

 

5.1  DrawMap函数:

 1 void CMapManager::DrawMap(int x, int y, BOOL drawNumber /*= TRUE*/)
 2 {
 3     CPoint point(x * GRID_WIDTH, y * GRID_HEIGHT);
 4         //经过这样简单的变换,获得了所需要的指定格的原点
 5         /*根据状态改变*/
 6     if(status == GAME)
 7     {    
 8         if (Map[y][x].isOrigin)
 9         {
10             /* 题目中给定的数字填充背景方块 */
11             MBgBelow.SetDrawPos(point);
12             MBgBelow.SetSrcPos(CPoint(belowPos + x % 3 * 
13 
14 GRID_WIDTH, y % 3 * GRID_HEIGHT));
15             MBgBelow.Draw(View);
16         }
17         else
18         {
19             /* 玩家写上的数字填充前景方块 */
20             MBgTop.SetDrawPos(point);
21             MBgTop.SetSrcPos(CPoint(topPos + x % 3 * GRID_WIDTH, y 
22 
23 % 3 * GRID_HEIGHT));
24             MBgTop.Draw(View);
25         }
26         if (drawNumber)
27         {
28             DrawNumber(x, y);
29         }
30     }    
31     else if(status == MAINMENU)
32     {
33         View.Copy(BG, point, CSize(128, 64), point);
34     }
35     else if(status == OPTION)
36     {
37         View.Copy(BG, point, CSize(64, 64), point);
38     }
39 }

5.2  解释:

这也是一个DrawMap函数,但是它的参数列表与之前的那个DrawMap函数不同。这个函数绘制指定格的底图,并设定是否重绘其上的数字。

 

6.1  DrawNumber函数:

 1 void CMapManager::DrawNumber()
 2 {
 3     for (int i=0; i<9; i++)
 4     {
 5         for (int j=0; j<9; j++)
 6         {
 7             if(Map[i][j].num != 0)
 8             {
 9                 DrawNumber(j, i);
10             }
11         }
12     }
13 }

6.2  解释:

该函数用以绘制开始状态时的所有数字(其实也就1-9),也就是问题一开始显示的数字。

 

7.1  DrawNumber函数:

 1 void CMapManager::DrawNumber(int x, int y)
 2 //注意这个是有参数的。
 3 {
 4     CPoint point(x * GRID_WIDTH, y * GRID_HEIGHT);
 5     if(Map[y][x].num > 0 && Map[y][x].num < 10)
 6         //确保输入的数字并没有超出范围。如果没有这个限制,将会导致程序出现错误。
 7     {
 8         View.Mix(Number, point, CSize(GRID_WIDTH, GRID_HEIGHT), 
 9             CPoint((Map[y][x].num) * GRID_WIDTH));
10     }
11 }

7.2  解释:

这也是一个DrawNumber函数,但是参数列表与之前不同。该函数是为了绘制指定位置的数字。即我们输入的数字,将会通过这个函数来绘制出来。

 

8.1  LoadMap函数:

 1 void CMapManager::LoadMap()
 2 {
 3     /* 打开题目文件 */
 4     CFile file;
 5     file.Open("MapData.txt", CFile::modeRead);
 6     
 7     /* 初始化随机种子 */
 8     srand((unsigned)time(0));
 9 
10     /* 根据难度来偏移 */
11     /* 81代表一题的81个数,8代表每一题前的号码"#000# "加上一回车换行符共8
12 
13 个字符 */
14     if (difficulty == MIDDLE)
15     {
16         /* 中等难度偏移EASY个 */
17         file.Seek(sizeof(char) * EASY * (81 + 8), CFile::current);
18     }
19     else if (difficulty == HARD)
20     {
21         /* 困难难度偏移(EASY + MIDDLE)个 */
22         file.Seek(sizeof(char) * (EASY + MIDDLE) * (81 + 8), 
23 
24 CFile::current);
25     }
26 
27     /* 随机得到题号(这是在偏移基础上的题号) */
28     int No = rand() % difficulty;
29     /* 根据题号偏移 */
30     /* 6代表题目前的号码"#000# "6个字符 */
31     file.Seek(sizeof(char) * No * (81 + 8) + 6, CFile::current);
32      
33     /* 读入题目 */
34     char txt[81];
35     file.Read(&txt, sizeof(char) * 81);
36     
37     /* 根据题目初始化Map */
38     for (int i=0; i<9; i++)
39     {
40         for (int j=0; j<9; j++)
41         {
42              Map[i][j].num = txt[i * 9 + j] - \'0\';
43             Map[i][j].isOrigin = (Map[i][j].num == 0? FALSE : 
44 
45 TRUE);
46         }
47     }
48 
49     /* 关闭文件 */
50     file.Close();
51 }

8.2  解释:

打开题目文件,并通过了解程序难度设置,来读取相应难度的题目。

 

9.1  InitMap函数:

 1 void CMapManager::InitMap()
 2 {
 3     for (int i=0; i<9; i++)
 4     {
 5         for (int j=0; j<9; j++)
 6         {
 7             if(Map[i][j].isOrigin == TRUE)
 8             {
 9                 MBgBelow.SetDrawPos(IndexToPoint(j, i));
10                 MBgBelow.SetSrcPos(CPoint(belowPos + j % 3 * 
11 
12 GRID_WIDTH, 
13                     i % 3 * GRID_HEIGHT));
14                 MBgBelow.Draw(View);
15             }
16             else
17             {
18                 MBgTop.SetDrawPos(IndexToPoint(j, i));
19                 MBgTop.SetSrcPos(CPoint(topPos + j % 3 * 
20 
21 GRID_WIDTH, 
22                     i % 3 * GRID_HEIGHT));
23                 MBgTop.Draw(View);
24             }
25         }
26     }
27 }

9.2  解释:

根据题目组装整个底图。

 

10.1  CheckFinish函数:

 1 BOOL CMapManager::CheckFinish()
 2 {
 3     for (int i=0; i<9; i++)
 4     {
 5         for (int j=0; j<9; j++)
 6         {
 7             if (Map[i][j].num == 0)
 8             {
 9                 return FALSE;
10             }
11         }
12     }
13     return TRUE;
14 }

10.2  解释:

检查是否完成题目,也就是是否所有的空白格子都被写入数字。不过正确与否无关。

 

11.1  CheckRow函数:

 1 BOOL CMapManager::CheckRow(int row)
 2 {
 3     /* 这里要说明一下 */
 4     /* 如果这一行是正确的,那它必然包括123456789,9个数字 */
 5     /* 这里采用位与运算检查 */
 6     /* 其中check = 0000 0001 1111 1111,后9位为1 */
 7     /* 假设当前位置的数字为3,则tmp = 1 << 2 = 0000 0000 0000 0100 */
 8    
 9     DWORD check = 0x01FF;
10     DWORD tmp = 0;
11     for (int i=0; i<9; i++)
12     {
13         tmp = 1 << (Map[row][i].num - 1);
14         check &= ~tmp;
15     }
16     return check;
17 }

11.2  解释:

检查某一行的数据是否正确(1-9都有且唯一)。另外,重点是位运算的应用,确实很好。

 

12.1  CheckCol函数:

 1 BOOL CMapManager::CheckCol(int col)
 2 {
 3     DWORD check = 0x01FF;
 4     DWORD tmp = 0;
 5     for (int i=0; i<9; i++)
 6     {
 7         tmp = 1 << (Map[i][col].num - 1);
 8         check &= ~tmp;
 9     }
10     return check;
11 }

12.2  解释:

检查某一列的数据是否正确(1-9都有且唯一)。

 

13.1  CheckGrid函数:

 1 BOOL CMapManager::CheckGrid(int grid)
 2 {
 3     DWORD check = 0x01FF;
 4     DWORD tmp = 0;
 5     int top = grid / 3 * 3;
 6     int left = (grid % 3) * 3;
 7     for (int i=top; i<top+3; i++)
 8     {
 9         for (int j=left; j<left+3; j++)
10         {
11             tmp = 1 << (Map[i][j].num - 1);
12             check &= ~tmp;
13         }
14     }
15     return check;
16 }

13.2  解释:

检查某一个九宫格内的数据是否正确(1-9都有且唯一)。

 

14.1  CheckSuccess函数:

 1 BOOL CMapManager::CheckSuccess()
 2 {
 3     int i;
 4     for (i=0; i<9; i++)
 5     {
 6         if(CheckRow(i) != 0 || CheckCol(i) != 0 || CheckGrid(i) != 0)
 7         {
 8             return FALSE;
 9         }
10     }
11     return TRUE;
12 }

14.2  解释:

检查答案是否正确。

 

15.1  Show函数:

 1 void CMapManager::Show(CDC* dc)
 2 {
 3     /* 以下的绘制函数不会重绘整个底图 */
 4     /* 只绘制需重绘的地方 */
 5     switch(status)
 6     {
 7     case MAINMENU:
 8         ShowMainMenu();
 9         break;
10     case GAME:
11         ShowGame();
12         break;
13     case OPTION:
14         ShowOption();
15         break;
16     default:
17         break;
18     }
19     if (View.IsOK())
20     {
21         View.Draw(*dc, 0, 0, View.Width(), View.Height());
22     }
23 }

15.2  解释:

主要的显示函数。根据状态的不同显示不同界面,如开始,胜利,失败,游戏,选项等等。其中dc表示设备上下文句柄。以上是关于数独GUI程序项目实现的主要内容,如果未能解决你的问题,请参考以下文章

BIT软工个人项目-数独

软件工程——数独 代码分析报告1

基于第二次数独游戏,添加GUI界面

软件工程-个人项目 数独终局的一些想法

软件工程——数独 数独GUI用户手册

软件工程基础个人个人项目 数独终局声称与解数独问题的控制台程序