软件设计 2017-06-19
数独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表示设备上下文句柄。
16.1 ShowMainMenu函数:
1 void CMapManager::ShowMainMenu() 2 { 3 CRect ButtonRect; 4 ButtonRect.SetRect(IndexToPoint(5, 4), IndexToPoint(7, 7)); 5 if (ButtonRect.PtInRect(IndexToPoint(CursorPos))) 6 { 7 DrawCursor(); 8 } 9 }
16.2 解释:
主界面显示函数(即开始界面显示函数)。
17.1 ShowGame函数:
void CMapManager::ShowGame() { if(MapRect.PtInRect(IndexToPoint(CursorPos))) { DrawMap(CursorPos.x, CursorPos.y, FALSE); DrawNumber(CursorPos.x, CursorPos.y); DrawCursor(); } }
17.2 解释:
游戏状态的显示函数。
18.1 ShowOption函数:
1 void CMapManager::ShowOption() 2 { 3 CRect optionRect1; 4 CRect optionRect2; 5 optionRect1.SetRect(IndexToPoint(3, 1), IndexToPoint(8, 3)); 6 optionRect2.SetRect(IndexToPoint(3, 4), IndexToPoint(8, 6)); 7 if (optionRect1.PtInRect(IndexToPoint(CursorPos)) || 8 optionRect2.PtInRect(IndexToPoint(CursorPos))) 9 { 10 DrawCursor(); 11 } 12 }
18.2 解释:
选项状态的显示函数。
19.1 Process函数:
void CMapManager::Process(CALLBACKTYPE type, void *data) { switch(status) { case MAINMENU: ProcessMenu(type, data); break; case GAME: ProcessGame(type, data); break; case OPTION: ProcessOption(type, data); break; case WIN: ProcessWin(type, data); break; default: break; } InvalidateRect(Window->m_hWnd, MapRect, FALSE); }
19.2 解释:
主处理器。根据状态不同,将反馈的参数进行不同的操作 。程序里的所有事件(鼠标时间、键盘事件)都是由process函数来处理。
20.1 ProcessGame函数:
void CMapManager::ProcessGame(CALLBACKTYPE type, void *data) { switch(type) { case ONMOUSEMOVE: GameMouseMove(*(CPoint*)data); break; case ONCHAR: GameChar(*(UINT*)data); break; default: break; } }
20.2 解释:
游戏状态下的事件处理器。
21.1 ProcessMenu函数:
void CMapManager::ProcessMenu(CALLBACKTYPE type, void *data) { switch(type) { case ONLBUTTONDOWN: MenuLButtonDown(*(CPoint*)data); break; case ONMOUSEMOVE: MenuMouseMove(*(CPoint*)data); break; case ONLBUTTONUP: MenuLButtonUp(*(CPoint*)data); break; default: break; } }
21.2 解释:
主界面的事件处理器。
22.1 ProcessOption函数:
void CMapManager::ProcessOption(CALLBACKTYPE type, void *data) { switch(type) { case ONMOUSEMOVE: OptionMouseMove(*(CPoint*)data); break; case ONLBUTTONDOWN: OptionLButtonDown(*(CPoint*)data); break; default: break; } }
22.2 解释:
选项界面的事件处理器。
23.1 ProcessWin函数:
void CMapManager::ProcessWin(CALLBACKTYPE type, void *data) { switch(type) { case ONLBUTTONDOWN: WinLButtonDown(*(CPoint*)data); break; default: break; } }
23.2 解释:
胜利状态的事件处理器。
24.1 GameMouseMove函数:
void CMapManager::GameMouseMove(CPoint point) { if(MapRect.PtInRect(point)) { CPoint Pos = PointToIndex(point); if (CursorPos != Pos) { DrawMap(CursorPos.x, CursorPos.y); CursorPos = Pos; } } }
24.2 解释:
之前游戏状态下的鼠标移动事件处理器。
25.1 GameChar函数:
1 void CMapManager::GameChar(UINT nChar) 2 { 3 int x = CursorPos.x; 4 int y = CursorPos.y; 5 6 /* 不能修改题目给定的数字 */ 7 if (Map[y][x].isOrigin) 8 { 9 return; 10 } 11 12 switch(nChar) 13 { 14 case '0': 15 Map[y][x].num = 0; 16 break; 17 case '1': 18 Map[y][x].num = 1; 19 break; 20 case '2': 21 Map[y][x].num = 2; 22 break; 23 case '3': 24 Map[y][x].num = 3; 25 break; 26 case '4': 27 Map[y][x].num = 4; 28 break; 29 case '5': 30 Map[y][x].num = 5; 31 break; 32 case '6': 33 Map[y][x].num = 6; 34 break; 35 case '7': 36 Map[y][x].num = 7; 37 break; 38 case '8': 39 Map[y][x].num = 8; 40 break; 41 case '9': 42 Map[y][x].num = 9; 43 break; 44 /* ESC键返回主界面 */ 45 case VK_ESCAPE: 46 Return(); 47 break; 48 default: 49 break; 50 } 51 52 if (CheckFinish()) 53 { 54 if(CheckSuccess()) 55 { 56 StartWin(); 57 //AfxMessageBox("You are good!"); 58 } 59 else 60 { 61 StartDefeat(); 62 //AfxMessageBox("You are wrong!"); 63 } 64 } 65 }
25.2 解释:
游戏状态下的键盘输入事件处理器。输入的ASCII码。如果试图修改游戏题目数据,就会返回NULL。
26.1 MenuLButtonDown函数:
1 void CMapManager::MenuLButtonDown(CPoint point) 2 { 3 CRect ButtonRect; 4 ButtonRect.SetRect(IndexToPoint(5, 4), IndexToPoint(7, 7)); 5 if (ButtonRect.PtInRect(point)) 6 { 7 /* 这里本来要绘制鼠标按下效果,但程序速度太快,根本显示不了 */ 8 DrawMap(5, CursorPos.y); 9 MCursor.SetSrcPos(192, 0); 10 MCursor.Draw(View); 11 InvalidateRect(Window->m_hWnd, MapRect, FALSE); 12 13 /* 开始游戏按钮 */ 14 if ((CRect(IndexToPoint(5, 4), IndexToPoint(7, 5))).PtInRect 15 16 (point)) 17 { 18 StartGame(); 19 } 20 21 /* 选项按钮 */ 22 if ((CRect(IndexToPoint(5, 5), IndexToPoint(7, 6))).PtInRect 23 24 (point)) 25 { 26 StartOption(); 27 } 28 29 /* 结束游戏按钮 */ 30 if ((CRect(IndexToPoint(5, 6), IndexToPoint(7, 7))).PtInRect 31 32 (point)) 33 { 34 EndGame(); 35 MenuLButtonUp(point); 36 } 37 } 38 }
26.2 解释:
主界面状态下的鼠标单击事件处理器。
27.1 MenuMouseMove函数:
1 void CMapManager::MenuMouseMove(CPoint point) 2 { 3 CRect ButtonRect; 4 ButtonRect.SetRect(IndexToPoint(5, 4), IndexToPoint(7, 7)); 5 if (ButtonRect.PtInRect(point)) 6 { 7 DrawMap(5, CursorPos.y); 8 CursorPos = PointToIndex(point); 9 } 10 }
27.2 解释:
主界面状态下的鼠标移动事件处理器。
28.1 MenuLButtonUp函数:
1 void CMapManager::MenuLButtonUp(CPoint point) 2 { 3 CRect ButtonRect; 4 ButtonRect.SetRect(IndexToPoint(5, 4), IndexToPoint(7, 7)); 5 if (ButtonRect.PtInRect(point)) 6 { 7 DrawMap(5, CursorPos.y); 8 MCursor.SetSrcPos(64, 0);
9 MCursor.Draw(View); 10 } 11 }
28.2 解释:
主界面状态下的鼠标左键弹起事件处理器。
29.1 OptionMouseMove函数:
void CMapManager::OptionMouseMove(CPoint point) { if (CRect(IndexToPoint(3, 1), IndexToPoint(8, 3)).PtInRect(point) || CRect(IndexToPoint(3, 4), IndexToPoint(6, 6)).PtInRect(point)) { DrawMap(CursorPos.x, CursorPos.y); CursorPos = PointToIndex(point); } }
29.2 解释:
选项状态下的鼠标移动事件处理器。
30.1 OptionLButtonDown函数:
1 void CMapManager::OptionLButtonDown(CPoint point) 2 { 3 int index; 4 /* 前景方块 */ 5 if (CRect(IndexToPoint(3, 1), IndexToPoint(8, 3)).PtInRect(point)) 6 { 7 index = topPos / 192; 8 DrawMap(3 + index % 5, (1 + index / 5)); 9 10 topPos = ((point.y / GRID_HEIGHT - 1) * 5 + (point.x / 11 12 GRID_WIDTH - 3)) * 192; 13 } 14 /* 背景方块 */ 15 else if (CRect(IndexToPoint(3, 4), IndexToPoint(8, 6)).PtInRect 16 17 (point)) 18 { 19 index = belowPos / 192; 20 DrawMap(3 + index % 5, (4 + index / 5)); 21 22 belowPos = ((point.y / GRID_HEIGHT - 4) * 5 + (point.x / 23 24 GRID_WIDTH - 3)) * 192; 25 } 26 /* 返回按钮 */ 27 else if (CRect(IndexToPoint(6, 7), IndexToPoint(8, 8)).PtInRect 28 29 (point)) 30 { 31 StartMainMenu(); 32 } 33 }
30.2 解释:
选项状态 下的鼠标单击事件处理器。
31.1 WinLButtonDown函数:
1 void CMapManager::WinLButtonDown(CPoint point) 2 { 3 /* 继续按钮 */ 4 if (CRect(IndexToPoint(0, 8), IndexToPoint(3, 9)).PtInRect(point)) 5 { 6 StartGame(); 7 } 8 /* 返回按钮 */ 9 else if (CRect(IndexToPoint(5, 8), IndexToPoint(8, 9)).PtInRect 10 11 (point)) 12 { 13 StartMainMenu(); 14 } 15 }
31.2 解释:
胜利状态下的单击事件处理器。
32.1 defeatLButtonDown函数:
1 void CMapManager::DefeatLButtonDown(CPoint point) 2 { 3 /* 继续按钮 */ 4 if (CRect(IndexToPoint(0, 8), IndexToPoint(3, 9)).PtInRect(point)) 5 { 6 StartGame(); 7 } 8 /* 返回按钮 */ 9 else if (CRect(IndexToPoint(5, 8), IndexToPoint(8, 9)).PtInRect 10 11 (point)) 12 { 13 StartMainMenu(); 14 } 15 }
32.2 解释:
失败状态下的单击事件处理器。
33.1 StartGame函数:
1 void CMapManager::StartGame() 2 { 3 /* 开始游戏 */ 4 if(status != GAME) 5 { 6 if(!BG.LoadBmp("image\\BG.bmp")) 7 { 8 EndGame(); 9 } 10 status = GAME; 11 MBgTop.Set(&BG, CPoint(0, 0), CSize(64, 64), CPoint(0, 0)); 12 MBgBelow.Set(&BG, CPoint(0, 0), CSize(64, 64), CPoint(192, 0)); 13 14 MCursor.SetSrcPos(2, 2); 15 MCursor.SetSize(CSize(28, 28)); 16 LoadMap(); 17 DrawMap(); 18 } 19 /* 重开游戏 */ 20 else if(status == GAME) 21 { 22 LoadMap(); 23 DrawMap(); 24 } 25 }
33.2 解释:
跳转至开始游戏(转为游戏状态)。
34.1 StartMainMenu函数:
1 void CMapManager::StartMainMenu() 2 { 3 if(status != MAINMENU) 4 { 5 if(!BG.LoadBmp("image\\Menu3.bmp")) 6 { 7 EndGame(); 8 } 9 View.Copy(BG, CPoint(0, 0), CSize(288, 288), CPoint(0, 0)); 10 MCursor.Set(&Cursor, CPoint(0, 0), CSize(64, 64), CPoint(64, 11 12 0)); 13 status = MAINMENU; 14 } 15 }
34.2 解释:
跳转至开始主界面(转为主界面状态)。
35.1 StartOption函数:
1 void CMapManager::StartOption() 2 { 3 if(status != OPTION) 4 { 5 if(!BG.LoadBmp("image\\Option.bmp")) 6 { 7 EndGame(); 8 } 9 status = OPTION; 10 View.Copy(BG, CPoint(0, 0), CSize(288, 288), CPoint(0, 0)); 11 12 13 MCursor.SetSrcPos(0, 0); 14 MCursor.SetSize(CSize(64, 64)); 15 16 int index = topPos / 192; 17 CPoint LT(3 * GRID_WIDTH, (1 + index / 5) * GRID_HEIGHT); 18 MCursor.SetDrawPos(LT + CPoint((index % 5) * GRID_WIDTH, 0)); 19 MCursor.Draw(View); 20 21 index = belowPos / 192; 22 LT.y = (4 + index / 5) * GRID_HEIGHT; 23 MCursor.SetDrawPos(LT + CPoint((index % 5) * GRID_WIDTH, 0)); 24 MCursor.Draw(View); 25 } 26 }
35.2 解释:
跳转至开始选项(转为选项状态)。
36.1 StartWin函数:
1 void CMapManager::StartWin() 2 { 3 if (status != WIN) 4 { 5 if(!BG.LoadBmp("image\\Win.bmp")) 6 { 7 EndGame(); 8 } 9 status = WIN; 10 View.Copy(BG, CPoint(0, 0), CSize(288, 288), CPoint(0, 0)); 11 } 12 }
36.2 解释:
跳转至开始胜利(转为胜利状态)。
37.1 StartDefeat函数:
1 void CMapManager::StartDefeat() 2 { 3 if (status != DEFEAT) 4 { 5 if(!BG.LoadBmp("image\\Defeat.bmp")) 6 { 7 EndGame(); 8 } 9 status = DEFEAT; 10 View.Copy(BG, CPoint(0, 0), CSize(288, 288), CPoint(0, 0)); 11 } 12 }
37.2 解释:
跳转至开始失败(转为失败状态)。
38.1 EndGame函数:
1 void CMapManager::EndGame() 2 { 3 if (AfxMessageBox("离开游戏吗?", MB_YESNO) == IDYES) 4 { 5 PostQuitMessage(0); 6 } 7 }
38.2 解释:
结束游戏。
39.1 Return函数:
void CMapManager::Return() { StartMainMenu(); }
39.2 解释:
返回到主界面。
40.1 SetDifficulty函数:
void CMapManager::SetDifficulty(int dif) {
Difficulty = dif; 4 }
40.2 解释:
用以设定游戏难度。
41.1 GetDifficulty函数:
int CMapManager::GetDifficulty() { return Difficulty; }
41.2 解释:
用以取得游戏当前难度。
42 备注:
主体代码就这些。其他代码就不注释了。至于完整代码及程序,我之后会找个地方上传试试。
三、总结
1 参考:
程序的设计参考了多个已完成程序,以及部分程序的代码。其中有啊古的题库,结构、《算法宝典》的位运算、fzhman有关虚函数的博客等等。
2 图片:
图片采用的的是战锤40k的传说风格的壁纸以及暗黑三勇天使的壁纸。
3 感悟:
题目中虚函数的应用(令我回想起Java。。。)让我有了与虚函数的真正接触。(表示以前只是在一些网站要求上看到过,表示完全没遇到过。)
四、后续版本:
对之后版本V2.0已经有了一定的想法,不过许多问题还在思考、完善中。
1 Studying模式的实现:
其实这个模式主要就是能够实现数独解法的同步展示。同时,我不想将这个做成一个鸡肋的教学展示。但是这个模式一开始就有了一个难点,不是特定解法的算法实现,而是如何确定当前先解哪个点,用哪个解法解。
对于点的确定,我认为应该从信息量的角度来解决。首先任何一个空白点的解决,基本都离不开其所在九宫格及九宫格所在的行、列。那么当这么三者区域内的数字越多,往往信息量越大,与此同时,解出来的可能性就越高。当然,通过对数独游戏的体验以及事后的具体分析后,我发现其实并不是数字越多就一定信息量越大。因为当数字多时,常常会有重复的信息量,导致最终总的信息量下降。所以在这里还需要做一个总信息量计算的算法。但是考虑到计算量的问题,之后需要细致分析是否需要简化操作,或者说就用数字多少来表示信息量。
至于对解法的选择,我认为应当对解法设置优先级。优先级的确立应当以该解法所需要的计算资源、内存资源等等。同时,更需要注意的是游戏者对这个算法的接受程度。因为,有时候,计算机资源消耗低的解法,并不一定就适应人脑的选择。毕竟这款游戏最终的目的是服务游戏者。
点与解法的选择问题,还需要设置一个算法,来决定解法与点的综合优先级。这个算法可以复杂、细致,也可以简单到呈线性,但是必须有。
2 界面的修改:
因为上述模式的实现,以及处于美化界面的需要,游戏需要更为宽广的界面。上述模式,经过我和朋友的简单商议后,觉得应当添加一个解释说明的文字区域。与此同时,我们需要通过1-9与A-I两个坐标轴来确定数独内的点。当然,对于解法涉及到的行、列、九宫格等区域,我会考虑用鲜明的颜色将需要的区域标识出来。
上述就是我对这款游戏的V2.0的希冀所在。
希望,我可以很好的解决掉它。虽然估计需要花费不少的时间。。。。