完全自制的五子棋人机对战游戏(VC++实现)
五子棋工作文档
1说明:
这个程序在创建初期的时候是有一个写的比较乱的文档的,但是很可惜回学校的时候没有带回来……所以现在赶紧整理一下,不然再过一段时间就忘干净了。
最初这个程序是受老同学所托做的,一开始的时候要求要人人对战和人机对战,但是大家都很明白,所谓的人人对战就是简单那的GDI绘图罢了,那些基础函数用好了自然没问题。而人机对战则需要一定的棋盘分析能力,做起来还是很复杂的。当时受时间限制,第一个版本是我用了两天时间做的一个人人对战,直接就给她发过去了,用来应付她的实习,因为我当时也不确定人机对战能不能做出来。不过之后我一直在做,毕竟之前没做过,算是一次尝试。之后貌似过了9天吧,才完成了核心函数:GetAIPoint。用这么长时间一个是因为没做过另外当时在家里还要帮家里干活,刨去干活加上打游戏的时间,平均下来每天的编码时间不到3个小时。不过去我还是用了不少的时间来思考棋盘的分析的。走了不少弯路,吸取了不少教训,感觉收获还是挺大的。但是比较悲剧的是,我后来发现这个程序有内存泄露问题,问题貌似处在DrawChess函数里,因为无棋子的重绘并不会增加内存总量,看官若有兴趣就帮我找找看吧,我是没找到到底哪里出了问题……
程序运行截图演示:
2程序主要数据结构以及函数:
1 //使用结构体有利于以后的数据扩展 2 /* 3 status 参数是用来表示当前这个点的状态的,0表示白子,1表示黑子 -1表示尚无子 4 后两个参数是用来追踪前一个点的,用于悔棋 5 */ 6 typedef struct 7 { 8 INT status; 9 //悔棋 专用 10 INT PrePointx; 11 INT PrePointy; 12 INT nVisit_flag; 13 }Chess; 14 typedef struct 15 { 16 POINT startpos;//起始地点 17 POINT endpos;//终止地点 18 INT length;//长度 19 INT ChessType;//黑白子的辨别 20 INT EffectLevel;//棋子线的影响力,这个值的优先级判定应该和长度相关联进行判断,可以考虑通过使用一个公式来计算 21 }ChessLine; 22 // Forward declarations of functions included in this code module: 23 ATOM MyRegisterClass(HINSTANCE hInstance); 24 BOOL InitInstance(HINSTANCE, int); 25 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 26 INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); 27 INT g_nbase_x = 300; 28 INT g_nbase_y = 10; 29 Chess g_ChessTable[CHESS_LINE_NUM][CHESS_LINE_NUM];//作为全局变量的数据表 30 BOOL w_b_turn = 0;//下棋顺序的控制变量 31 INT nxPosForChessTable = -1;//悔棋专用 32 INT nyPosForChessTable = -1;//悔棋专用 33 INT nRestart_Flag;//默认初始化的值为0,应该是重启游戏的标志位 34 ChessLine BestLine;//白黑的最长有效线即可 35 INT DrawMode = 0;//0常规模式 1调试模式 36 INT PlayMode = 0;//游戏模式,分为人人对战0和人机对战1 37 //使用vector等模板时,还需要注意命名空间的问题 38 std::vector<ChessLine> w_ChessLineBuffer;//这个变量用于存储所有的棋子线,白色 39 std::vector<ChessLine> b_ChessLineBuffer;//黑色 40 41 void DrawTable(HDC hdc, int base_x = 0, int base_y = 0); 42 void WinRectConvert(RECT * rect); 43 void DrawChess(HDC hdc, int x, int y, int w_or_b = 0);//0为白子,1为黑子 44 void GlobalInitial();//全局初始化函数 45 void DrwaChessOnTable(HDC hdc); 46 INT IsWin(int x, int y); 47 INT TellWhoWin(HWND hWnd, INT n, RECT * rect); 48 void BkBitmap(HDC hdc, RECT * rect); 49 void DrawInfo(HDC hdc, ChessLine * cl, INT length); 50 void GetALLLine(INT w_or_b);//根据棋盘全局信息来获取对应颜色的最大长度线 51 INT GetMaxValCLAddr(ChessLine * parray, INT N);//返回最大值的数字地址 52 ChessLine * GetChessMaxSubLine(INT x, INT y, INT nColor, BOOL IfRtnVal);//获取单个点的最长线函数 53 void AddIntoBuf(ChessLine * pcl,INT w_or_b); 54 void ChessLineInitial(ChessLine * pcl, POINT * pstartpos, INT n, INT nColor); 55 inline void DeleteCL(ChessLine * pcl); 56 void DrawVecInfo(HDC hdc, std::vector<ChessLine> * pvcl); 57 ChessLine * GetBestLine(INT nColor); 58 INT GetValidSEDirection(POINT SP, POINT EP);//获取有效的方向,返回值 0,1,2,3分别对应2-6, 3-7, 4-0,5-1, 59 POINT GetAIPoint();//根据GetBestLine返回的黑白两棋子线情况来判断棋子的位置 60 POINT GetSinglePoint(); 61 INT IsValidSinglePoint(int x, int y);
可以看到,好多好多的函数~我现在也觉得有点头晕,不过这样就更有必要对这个程序进行整理了。
3 程序调用层析如下:
绘图消息:
鼠标左键消息:
刚才意外的发现了我在家的时候做的那个文档,虽然比较乱,但是还是较好的体现了一部分我的设计思想历程,等会贴在最后面的附录中。
上面的函数层次图主要还是为了表明函数之间的调用关系,这样便于理清之间的功能关系。另外我发现在设计数据类型时使用结构体或者类是一个非常好的选择,在数据扩充上有很大的优势,我在这个工程中就因此受益。
4 AI部分核心函数设计部分思想
首先就是如何看待棋盘上的棋子,如何根据棋盘上的棋子来分析出一个比较合适点作为下一步的选择。
我的程序中棋盘的大小是15*15的,这个还是那个同学和我说的,最初我设计的是19*19的,因为家里的显示器分辨率比较高,放得下。X轴和Y轴各15条线,棋盘的棋子可以简单的通过一个15*15的结构数组来表示,每个结构表示棋子的状态。这个结构如下:
1 /* 2 3 status 参数是用来表示当前这个点的状态的,0表示白子,1表示黑子 -1表示尚无子 4 5 后两个参数是用来追踪前一个点的,用于悔棋 6 7 */ 8 9 typedef struct 10 11 { 12 13 INT status; 14 15 //悔棋 专用 16 17 INT PrePointx; 18 19 INT PrePointy; 20 21 INT nVisit_flag; 22 23 }Chess;
上面的这个结构中,status的作用就不多说了,后面的参数用处还是挺有意思的。PrePoint如其意思一样,就是之前的那个点。第一个点的这个参数的值均为-1,用来标识无效点。而第1个点之后的所有的点这个参数的值均为前一点的坐标值。我觉得我的这个设计除了有点浪费内存,别的嘛,效率和效果还都是挺不错的。这样只要根据这两个值,就能按照原路找回之前的点,从而实现悔棋以及其一套连续的操作。
最后一个参数是废弃的,之前想通过每个点做一个标记来实现点的方向的记录,不过后来的代码实现表明这个是比较困难的,有更好的方法来实现,也就是我的程序中现在所使用的方法。
1 typedef struct 2 3 { 4 5 POINT startpos;//起始地点 6 7 POINT endpos;//终止地点 8 9 INT length;//长度 10 11 INT ChessType;//黑白子的辨别 12 13 INT EffectLevel;//棋子线的影响力,这个值的优先级判定应该和长度相关联进行判断,可以考虑通过使用一个公式来计算 14 15 }ChessLine;
上面的这个结构是用来表示棋子线的结构,其组成包括:起点和终点,长度,棋子的颜色,以及棋子线两端是否有效。
而棋子线的优先级也可以通过一个很简单的公式计算出来,即优先级为length + EffectLevel的结果来表示。需要注意的是,EffectLevel的值是不会等于0的,这个在线检查函数中专门进行了处理,因为EffectLeve==0,意味着这条线是一条废线,两端都被堵死了,直接抛弃。
由上面的两个结构你可以初步了解我对于整个棋盘上信息的抽象方法。
5 人人对战的核心函数(IsWin)
在进行人人对战的时候,核心函数其实就是要对棋盘上的棋子进行分析,判断是否存在已经大于或等于长度为5的棋子线。
棋盘上每一个点,都可以分为4个方向,或者8个小方向。
最简单的想法就是对棋盘上每一个点都进行计算,如果存在这样一个点,就获取其颜色,然后返回就可以了,由此即可判断出谁赢了。但是仔细想想,这样完全没必要,因为能赢与否,与刚下的点是必然有联系的。所以在进行检测的时候,只需要检测当前刚刚下的这个点就足够了。想明白了没?这样一来,效率非常高,完全避免了无谓的操作。
IsWin函数的参数是棋盘上的坐标,然后通过坐标值访问全局变量棋盘二维数组,做四个方向的检查,从-4到+4的坐标偏移。针对越界情况专门进行了处理。但是不排除存在bug。
人人对战的核心函数就这样,没别的。在AI模式下,这个函数依旧能够用来判断输赢结果。
6 人机对战核心函数POINT GetAIPoint();
根据上面的函数层次图,能够知道在这个函数中调用了4个重要的功能函数,先看下GetAiPoint函数的部分源代码:
1 POINT GetAIPoint()//根据GetBestLine返回的黑白两棋子线情况来判断棋子的位置 2 3 { 4 5 //先获取全部的线。 6 7 GetALLLine(0); 8 9 GetALLLine(1); 10 11 //这里曾造成内存泄露,原因是返回路径会切断删除函数的调用 12 13 ChessLine * pw_cl = GetBestLine(0);//白子 人方 14 15 ChessLine * pb_cl = GetBestLine(1);//黑子 AI 16 17 ChessLine * pfinal_cl; 18 19 POINT rtnpos = {-1, -1}; 20 21 if(pw_cl != NULL && pb_cl != NULL) 22 23 { 24 25 //防守优先 26 27 if(pw_cl->EffectLevel + pw_cl->length >= pb_cl->EffectLevel + pb_cl->length) 28 29 pfinal_cl = pw_cl; 30 31 else 32 33 pfinal_cl = pb_cl; 34 35 } 36 37 else if(pw_cl == NULL && pb_cl != NULL) 38 39 pfinal_cl = pb_cl; 40 41 else if(pb_cl == NULL && pw_cl != NULL) 42 43 pfinal_cl = pw_cl; 44 45 else //在上面的两个ChessLine都获取不到的时候,需要做的是,尝试去获取一个单独的点。 46 47 { 48 49 POINT SingleFinalPoint = GetSinglePoint(); 50 51 return SingleFinalPoint; 52 53 }
最先调用的函数是GetAllLine函数。这个函数的功能是查找全部的有效的线并将其添加到棋子线的vector容器中。参数是棋子颜色,0表示白色,1表示黑色。
看下这个函数的代码:
1 void GetALLLine(INT w_or_b)//这个函数应该只处理一个点 2 3 { 4 5 //现在看,不用进行8个方向的查询,而是只需要做4个方向的查询即可,比如:1234,剩下的0567用其他的点来检测 6 7 //八个方向为上下左右以及其45度角 8 9 //8时钟方向,上位0,顺时针,从0 - 7 10 11 /* 12 13 7 0 1 14 15 6 2 16 17 5 4 3 18 19 */ 20 21 //这两个变量都设计为数组,是因为8个方向的数据,都存储处理还是可以的 22 23 //一种比较节约空间的方法是设置临时变量,存储当前结果,与上一结果相比,这样就不需要8个变量,而仅仅是两个了。 24 25 ChessLine * pCL; 26 27 28 29 INT MaxLength = 0; 30 31 POINT MaxStartPos = {0}; 32 33 POINT MaxEndPos = {0}; 34 35 //memset(ArrayLength, 0, sizeof(ArrayLength));//嘿,看代码的,你应该知道我这么用是合法的吧? 36 37 //这段代码中有一部分代码应该函数化 38 39 if(0 == w_or_b) 40 41 w_ChessLineBuffer.clear(); 42 43 else 44 45 b_ChessLineBuffer.clear(); 46 47 for(int i = 0;i < CHESS_LINE_NUM;++ i) 48 49 { 50 51 for(int j = 0;j < CHESS_LINE_NUM; ++ j) 52 53 { 54 55 pCL = GetChessMaxSubLine(i, j, w_or_b, FALSE); 56 57 if(pCL == NULL) 58 59 continue; 60 61 if(pCL->length > MaxLength) 62 63 { 64 65 MaxLength = pCL->length; 66 67 MaxStartPos = pCL->startpos; 68 69 MaxEndPos = pCL->endpos; 70 71 } 72 73 DeleteCL(pCL); 74 75 } 76 77 } 78 79 }
代码中的注释可以好好的看一看,在方向的选择上,8个小方向,只要每个点都能一次查找其中的4个方向,就已经足够了。因为剩余4个方向的查找会被低地址点的查找覆盖掉,代码实际结果表明也是这样的。
另外这个函数中,还调用了一个较为关键的函数:GetChessMaxSubLine。这个函数可以说是一个功能很强大的,实现承上启下作用的一个函数。在这个函数中,完成了单个点的棋子线查找,避免重叠的棋子线,以及将合法的棋子线添加到容器的重要工作,这里每一步都很关键,直接决定棋盘数据抽象的效率和有效性。对于这个函数,我修改了数次,才最终确定下来。这个函数中使用了不少的技巧,读的时候要好好看注释。在GetAllLine函数中存在一定的代码冗余,起因就是过多次的修改。
GetAllLine函数调用后,会将对应颜色的有效棋子线全部放到对应颜色棋子的vector容器中,确实做到了get all lines。
接下来调用的函数是GetBestLine函数。这个函数的功能就很简单了,就是遍历vector容器,获取到最好的一条线。
那么此时你应该会有疑问了:如何判定一条线的好坏?
首先要说明的是,对于两端都被堵住了的线,是不存在于vector容器中的。因为这样的线一点用都没有。从vector中读出一条线的结构体之后,可以根据线长度和线影响力这两个成员变量的和来进行衡量的。长度不用多解释,线影响力就是棋子线两端的可下点的数目。这个是五子棋中比较有趣的特点,根据这两个值的和,就能很有效的得到一条线的优先级了。然后依此来获取到整个容器中最好的线。这就是GetBestLine函数的功能。
在获取到最佳线之后,需要对黑子最佳线和白字最佳线进行对比。这里我在AI设计中优先防守,所以只要黑子线不大于白字,就确定白子最佳线为要进行下一步处理的线。(白子为AI棋子)
在获取了要进一步处理的线之后,只要根据这条线得到一个合法的点就可以了。这个没太多可说的了,调用GetValidSEDirection后获取到方向,然后根据始发点和终点进行相应的地址偏移就可以了。
其实这里有一个很有趣的地方,就是我根本就没怎么关注最佳线到底是人方下的还是AI的,但是一样能实现其功能。因为获取到最佳线之后,如果是AI线,那么就能进一步扩大优势;如果是人方的线,就能够对其进行堵截。巧妙吧?
至此GetAiPoint函数的核心思想套路已经讲差不多了,至少我这个发明人算是想起来了整体的构架~
7 GetSinglePoint是干什么用的?
两点一线,如果只是单独的一个点,是不能算成线的哦~所以对于单个且独立的棋子点,并没有作为线来计算并加入到容器中。但是在刚刚下棋的时候,毫无疑问只有一个点……这个时候用GetBestLine函数获取到的指针都是空的,怎么办?
为了应对这种情况,我专门设计了GetSinglePoint函数来解决问题。在GetAiPoint函数中可以看到,在两个线指针都是空的时候,会调用GetSinglePoint函数从棋盘二维数组中专门找一个独立的点,然后返回这个点周边的一个有效的坐标值,而且需要注意的是,这个坐标是有效范围内随机的!为了实现这个我还颇为费了一点心思呢。看看GetSinglePoint函数:
1 POINT GetSinglePoint() 2 { 3 //所谓singlepoint,就是8个相邻点中没有任何一点是同色点。 4 //函数返回值为从0-7中的有效点中的一个随机点 5 INT npos; 6 POINT rtnpoint = {-1, -1}; 7 for(int i = 0;i < CHESS_LINE_NUM;++ i) 8 { 9 for(int j = 0;j < CHESS_LINE_NUM;++ j) 10 { 11 if(g_ChessTable[i][j].status != -1) 12 { 13 npos = IsValidSinglePoint(i, j); 14 if(npos == -1) 15 continue; 16 switch(npos) 17 { 18 //这里的代码直接return,就不用再break了 19 case 0: 20 rtnpoint.x = i - 1; 21 rtnpoint.y = j - 1; 22 break; 23 case 1: 24 rtnpoint.x = i; 25 rtnpoint.y = j - 1; 26 break; 27 case 2: 28 rtnpoint.x = i + 1; 29 rtnpoint.y = j - 1; 30 break; 31 case 3: 32 rtnpoint.x = i - 1; 33 rtnpoint.y = j; 34 break; 35 case 4: 36 rtnpoint.x = i + 1; 37 rtnpoint.y = j; 38 break; 39 case 5: 40 rtnpoint.x = i - 1; 41 rtnpoint.y = j + 1; 42 break; 43 case 6: 44 rtnpoint.x = i; 45 rtnpoint.y = j + 1; 46 break; 47 case 7: 48 rtnpoint.x = i + 1; 49 rtnpoint.y = j + 1; 50 break; 51 } 52 return rtnpoint; 53 } 54 } 55 } 56 return rtnpoint; 57 }
从中还能发现又调用了一个函数:IsValidSinglePoint。如果点合法,会返回一个随机的方向值,0-7,即8个小方向。若非法,则返回-1。
接下来再看这个函数实现:
1 INT IsValidSinglePoint(int x, int y) 2 { 3 assert(x >= 0 && y >=0 && x < CHESS_LINE_NUM && y < CHESS_LINE_NUM); 4 char checkflag[8] = {0};//纯标记位 5 if(x - 1 >= 0)//一次查三个点 6 { 7 if(y - 1 >= 0) 8 { 9 if(g_ChessTable[x - 1][y - 1].status == -1) 10 checkflag[0] = 1; 11 } 12 if(g_ChessTable[x - 1][y].status == -1) 13 checkflag[3] = 1; 14 if(y + 1 < CHESS_LINE_NUM) 15 { 16 if(g_ChessTable[x - 1][y + 1].status == -1) 17 checkflag[5] = 1; 18 } 19 } 20 if(y - 1 >= 0 && g_ChessTable[x][y - 1].status == -1) 21 checkflag[1] = 1; 22 if(y + 1 < CHESS_LINE_NUM && g_ChessTable[x][y + 1].status == -1) 23 24 { 25 checkflag[6] = 1; 26 } 27 if(x + 1 < CHESS_LINE_NUM) 28 { 29 if(g_ChessTable[x + 1][y].status == -1) 30 checkflag[4] = 1; 31 if(y + 1 < CHESS_LINE_NUM) 32 { 33 if(g_ChessTable[x + 1][y + 1].status == -1) 34 checkflag[7] = 1; 35 } 36 if(y - 1 >= 0) 37 { 38 if(g_ChessTable[x + 1][y - 1].status == -1) 39 checkflag[2] = 1; 40 } 41 } 42 /*调试部分 43 INT nrtn = 0; 44 for(int i = 0;i < 8;++ i) 45 { 46 if(checkflag[i] == 1) 47 { 48 nrtn |= 1 << (i * 4); 49 } 50 }*/ 51 INT nCounterofValidPoint = 0; 52 for(int i = 0;i < 8;++ i) 53 { 54 if(checkflag[i] == 1) 55 nCounterofValidPoint ++; 56 } 57 if(!nCounterofValidPoint) 58 return -1; 59 srand(time(0)); 60 INT nUpSection = rand() % 8;//在这倒是正好能用作地址上限 61 INT UpSearch = nUpSection; 62 INT DownSearch = nUpSection; 63 for(;UpSearch < 8 || DownSearch >= 0;) 64 { 65 if(UpSearch < 8) 66 { 67 if(checkflag[UpSearch] == 1) 68 return UpSearch; 69 else 70 UpSearch ++; 71 } 72 if(DownSearch >= 0) 73 { 74 if(checkflag[DownSearch] == 1) 75 return DownSearch; 76 else 77 DownSearch --; 78 } 79 } 80 }
看起来一个功能简单的函数,其实要做的操作还是不少的。因为除了要将合法的点对号入座,还要以随机的形式取出来,代码并不是很简单。
由此,整个工程AI的核心实现基本介绍完毕。
附录A 比较杂乱的最初版工作日记
五子棋工作日记
20130716创建
棋盘布局:
初步估计为19*19的布局,这样应该差不多。
每个棋子的大小尺寸暂时设计为30*30个像素,应该可以的。
期盼的网格大小为35*35,棋子放置在棋盘焦点上。
数据表示:
除了棋盘布局之外,还需要一个棋盘上的数据表示矩阵,-1表示可以下,0表示白子,1表示黑子。
并且需要处理
坐标转换问题:
首先,画出来的棋盘经过了基础坐标的偏移。
目前的问题是,坐标对应不上。鼠标坐标的位置是基于表格的。
坐标对应很简单,方案如下:
首先,按正常的思路去画坐标,然后,在网格的范围中来正常的画出棋子,棋子的坐标为左上角,但是,要画在网格中间。
鼠标点击上,依旧要以网格作为确定范围,点击后在相应位置画出棋子。
以上的任务完成之后呢,效果应该是:
用鼠标点击网格,在对应的网格的中间画出棋子。
上述完成后,只要简单一步:将制表函数的顶点坐标向右下角方向偏移半个网格长度。
然后下棋的效果就出来了
Win32 SDK背景图片的处理经验
之前给程序贴图片,用的都是MFC的类来进行操作。今天用了一把SDK,感觉,还是挺不错的。代码只有简简单单的这么几行,具体如下:
HDC htmpdc = CreateCompatibleDC(hdc);
//HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rect->right - rect->right, rect->bottom - rect->top);
HBITMAP hPicBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BKBITMAP));
SelectObject(htmpdc, hPicBitmap);
BitBlt(hdc, 0, 0, rect->right - rect->left, rect->bottom - rect->top, htmpdc, 300, 200, SRCCOPY);
DeleteObject(hPicBitmap);
DeleteDC(htmpdc);
首先,先调用CreateCompatibleBitmap函数来创建一个memory DC。然后再调用LoadBitmap函数获取资源中的一张图片,这个函数调用完成后,会获取到一个位图句柄。
接下来将其选入内存DC中。
最后调用BitBlt函数,把数据复制到我们从beginpaint函数中得到的hdc里面。
最后清理工作。
接下来应该做一下我自己的AI了。
五子棋AI思路:
首先,遇到未堵塞的对方三连点要立刻进行封堵。
在自己有优势不如对方的时候,对对方进行封堵。
在优势相当或者大于对方的时候,进行进攻。
优势的判断问题:
如何确定自己是优势还是劣势?
优势应该为自己方可用的多连节点数多于对方的可用多连节点数。
判断可用多连节点
这个刚刚做完,其实对一个点的检查,只要满足其中8个方向里4个防线就可以了,方向如下:
//8时时钟方向,上为0 顺时针,从0 - 7
/*
7 0 1
6 2
5 4 3
*/
我在做的时候只做了其中的2 3 4 5其中的四个方向。
方向查找代码:
万恶的unicode……
//2方¤?向¨°
INT nRight = StartPos.x + 1;
while(nRight < CHESS_LINE_NUM && g_ChessTable[nRight][StartPos.y].status == w_or_b)
{
ArrayLength[0]++;
nRight++;//向¨°右®¨°查¨¦找¨°
}
//保À¡ê存ä?对?应®|的Ì?点Ì?
ArrayEndPos[0].x = nRight - 1;
ArrayEndPos[0].y = StartPos.y;
//3方¤?向¨°
INT nRightDownOffset = 1;//右®¨°下?方¤?向¨°的Ì?偏?移°?地Ì?址¡¤
while(StartPos.x + nRightDownOffset < CHESS_LINE_NUM && \
StartPos.y + nRightDownOffset < CHESS_LINE_NUM && \
g_ChessTable[StartPos.x + nRightDownOffset][StartPos.y + nRightDownOffset].status == w_or_b)
{
ArrayLength[1]++;
nRightDownOffset++;
}
//保À¡ê存ä?对?应®|的Ì?点Ì?
ArrayEndPos[1].x = StartPos.x + nRightDownOffset - 1;
ArrayEndPos[1].y = StartPos.y + nRightDownOffset - 1;
//4方¤?向¨°
INT nDown = StartPos.y + 1;
while(nDown < CHESS_LINE_NUM && g_ChessTable[StartPos.x][nDown].status == w_or_b)
{
ArrayLength[2]++;
nDown++;//向¨°下?查¨¦找¨°
}
//保À¡ê存ä?对?应®|的Ì?点Ì?
ArrayEndPos[2].x = StartPos.x;
ArrayEndPos[2].y = nDown - 1;
//5方¤?向¨°
INT nLeftDownOffset = 1;//左Á¨®下?方¤?向¨°偏?移°?地Ì?址¡¤,ê?x -;ê?y +
while(StartPos.x + nLeftDownOffset < CHESS_LINE_NUM && \
StartPos.y + nLeftDownOffset < CHESS_LINE_NUM && \
g_ChessTable[StartPos.x - nLeftDownOffset][StartPos.y + nLeftDownOffset].status == w_or_b)
{
ArrayLength[3]++;
nLeftDownOffset++;
}
ArrayEndPos[3].x = StartPos.x - (nLeftDownOffset - 1);//为a了¢?逻?辑-清?楚t,ê?就¨ª先¨¨这a么¡ä写¡ä了¢?
ArrayEndPos[3].y = StartPos.y + nLeftDownOffset - 1;
INT MaxLengthAddr = GetMaxValnAddr(ArrayLength, 4);
if(MaxLengthAddr == -1)
return;
现在在棋盘数据扫描上,已经能够按照要求获取到最长的有效棋子线了,但是,还不能对最长棋子线的两端是否封闭进行检测。
初步估计要做的工作是在获取当前点的最长棋子线后,根据其索引地址或者斜率计算的方式计算出来其可扩展方向,然后再判断扩展方向上是否有对方的棋子或者己方的棋子占据,有点小复杂。
另外现在的棋子长度线检测是针对所有的线全部进行半规模检测,也就是只检查帮个方向,由此,倒也可以在一定程度上提高效率。
之前的那种递归算法,也不是不可以,但是,那是另外一个思路了。我这个效率低一点,但是代码还比较好写。
2013-7-23 22:07
刚才遇到了一个溢出错误,但是中断代码中并没有提示,给了我很大的困惑,因为在代码中并没有提示说异常出在了什么地方。
不过在调试信息的输出栏中,我看到了有关于vector的异常信息,位置在932行处。我去看了之后,发现了如下的代码:
#if _ITERATOR_DEBUG_LEVEL == 2
if (size() <= _Pos)
{ // report error
_DEBUG_ERROR("vector subscript out of range");
_SCL_SECURE_OUT_OF_RANGE;
}
_DEBUG_ERROR就是932行之所在。
第一次看到的时候并没有很放在心上,但是后来我发现,这段代码的意思,就是访问越界的一个判断。
常规数组并没有提供这个功能,但是,作为泛型编程模板的vector,提供了这个能力。而我的代码触发这个异常的原因近乎可笑,是在复制代码的时候,有一个数忘记了更改,也就是0和1之差别
就是这个数的差别,会在白子线少于黑子线的时候,导致对白子线数组的越界访问。就这么简单。
现在做AI,代码渐渐的已经膨胀到了900行,但是,我还真是没什么欣喜的感觉。代码越多,越难维护。看着现在的这个代码,感觉,别人估计是看不懂的。
附录B程序代码
1 // WuZiQi20130716.cpp : Defines the entry point for the application. 2 3 // 4 5 /* 6 7 曲敬原创建于2013年07月16日 8 9 */ 10 11 #include "stdafx.h" 12 13 #include "WuZiQi20130716.h" 14 15 #include <vector>//没辙,容器还是C++好用,纯SDK程序算是破产了 16 17 #include <assert.h> 18 19 #include <ctime> 20 21 #include <cstdlib> 22 23 #define MAX_LOADSTRING 100 24 25 #define TABLE_SQUARE_LENGTH 35 26 27 #define CHESS_LENGTH 30 28 29 #define CHESS_LINE_NUM 15 30 31 // Global Variables: 32 33 HINSTANCE hInst; // current instance 34 35 TCHAR szTitle[MAX_LOADSTRING]; // The title bar text 36 37 TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name 38 39 //使用结构体有利于以后的数据扩展 40 41 /* 42 43 status 参数是用来表示当前这个点的状态的,0表示白子,1表示黑子 -1表示尚无子 44 45 后两个参数是用来追踪前一个点的,用于悔棋 46 47 */ 48 49 typedef struct 50 51 { 52 53 INT status; 54 55 //悔棋 专用 56 57 INT PrePointx; 58 59 INT PrePointy; 60 61 INT nVisit_flag; 62 63 }Chess; 64 65 typedef struct 66 67 { 68 69 POINT startpos;//起始地点 70 71 POINT endpos;//终止地点 72 73 INT length;//长度 74 75 INT ChessType;//黑白子的辨别 76 77 INT EffectLevel;//棋子线的影响力,这个值的优先级判定应该和长度相关联进行判断,可以考虑通过使用一个公式来计算 78 79 }ChessLine; 80 81 // Forward declarations of functions included in this code module: 82 83 ATOM MyRegisterClass(HINSTANCE hInstance); 84 85 BOOL InitInstance(HINSTANCE, int); 86 87 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 88 89 INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); 90 91 INT g_nbase_x = 300; 92 93 INT g_nbase_y = 10; 94 95 Chess g_ChessTable[CHESS_LINE_NUM][CHESS_LINE_NUM];//作为全局变量的数据表 96 97 BOOL w_b_turn = 0;//下棋顺序的控制变量 98 99 INT nxPosForChessTable = -1;//悔棋专用 100 101 INT nyPosForChessTable = -1;//悔棋专用 102 103 INT nRestart_Flag;//默认初始化的值为0,应该是重启游戏的标志位 104 105 ChessLine BestLine;//白黑的最长有效线即可 106 107 INT DrawMode = 0;//0常规模式 1调试模式 108 109 INT PlayMode = 0;//游戏模式,分为人人对战0和人机对战1 110 111 //使用vector等模板时,还需要注意命名空间的问题 112 113 std::vector<ChessLine> w_ChessLineBuffer;//这个变量用于存储所有的棋子线,白色 114 115 std::vector<ChessLine> b_ChessLineBuffer;//黑色 116 117 118 119 void DrawTable(HDC hdc, int base_x = 0, int base_y = 0); 120 121 void WinRectConvert(RECT * rect); 122 123 void DrawChess(HDC hdc, int x, int y, int w_or_b = 0);//0为白子,1为黑子 124 125 void GlobalInitial();//全局初始化函数 126 127 void DrwaChessOnTable(HDC hdc); 128 129 INT IsWin(int x, int y); 130 131 INT TellWhoWin(HWND hWnd, INT n, RECT * rect); 132 133 void BkBitmap(HDC hdc, RECT * rect); 134 135 void DrawInfo(HDC hdc, ChessLine * cl, INT length); 136 137 void GetALLLine(INT w_or_b);//根据棋盘全局信息来获取对应颜色的最大长度线 138 139 INT GetMaxValCLAddr(ChessLine * parray, INT N);//返回最大值的数字地址 140 141 ChessLine * GetChessMaxSubLine(INT x, INT y, INT nColor, BOOL IfRtnVal);//获取单个点的最长线函数 142 143 void AddIntoBuf(ChessLine * pcl,INT w_or_b); 144 145 void ChessLineInitial(ChessLine * pcl, POINT * pstartpos, INT n, INT nColor); 146 147 inline void DeleteCL(ChessLine * pcl); 148 149 void DrawVecInfo(HDC hdc, std::vector<ChessLine> * pvcl); 150 151 ChessLine * GetBestLine(INT nColor); 152 153 INT GetValidSEDirection(POINT SP, POINT EP);//获取有效的方向,返回值 0,1,2,3分别对应2-6, 3-7, 4-0,5-1, 154 155 POINT GetAIPoint();//根据GetBestLine返回的黑白两棋子线情况来判断棋子的位置 156 157 POINT GetSinglePoint(); 158 159 INT IsValidSinglePoint(int x, int y); 160 161 162 163 int APIENTRY _tWinMain(HINSTANCE hInstance, 164 165 HINSTANCE hPrevInstance, 166 167 LPTSTR lpCmdLine, 168 169 int nCmdShow) 170 171 { 172 173 UNREFERENCED_PARAMETER(hPrevInstance); 174 175 UNREFERENCED_PARAMETER(lpCmdLine); 176 177 178 179 // TODO: Place code here. 180 181 MSG msg; 182 183 HACCEL hAccelTable; 184 185 186 187 // Initialize global strings 188 189 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); 190 191 LoadString(hInstance, IDC_WUZIQI20130716, szWindowClass, MAX_LOADSTRING); 192 193 MyRegisterClass(hInstance); 194 195 196 197 // Perform application initialization: 198 199 if (!InitInstance (hInstance, nCmdShow)) 200 201 { 202 203 return FALSE; 204 205 } 206 207 208 209 hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WUZIQI20130716)); 210 211 212 213 // Main message loop: 214 215 while (GetMessage(&msg, NULL, 0, 0)) 216 217 { 218 219 if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) 220 221 { 222 223 TranslateMessage(&msg); 224 225 DispatchMessage(&msg); 226 227 } 228 229 } 230 231 232 233 return (int) msg.wParam; 234 235 } 236 237 238 239 240 241 242 243 // 244 245 // FUNCTION: MyRegisterClass() 246 247 // 248 249 // PURPOSE: Registers the window class. 250 251 // 252 253 // COMMENTS: 254 255 // 256 257 // This function and its usage are only necessary if you want this code 258 259 // to be compatible with Win32 systems prior to the 'RegisterClassEx' 260 261 // function that was added to Windows 95. It is important to call this function 262 263 // so that the application will get 'well formed' small icons associated 264 265 // with it. 266 267 // 268 269 ATOM MyRegisterClass(HINSTANCE hInstance) 270 271 { 272 273 WNDCLASSEX wcex; 274 275 276 277 wcex.cbSize = sizeof(WNDCLASSEX); 278 279 280 281 wcex.style = CS_HREDRAW | CS_VREDRAW; 282 283 wcex.lpfnWndProc = WndProc; 284 285 wcex.cbClsExtra = 0; 286 287 wcex.cbWndExtra = 0; 288 289 wcex.hInstance = hInstance; 290 291 wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WUZIQI20130716)); 292 293 wcex.hCursor = LoadCursor(NULL, IDC_ARROW); 294 295 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 296 297 wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WUZIQI20130716); 298 299 wcex.lpszClassName = szWindowClass; 300 301 wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); 302 303 304 305 return RegisterClassEx(&wcex); 306 307 } 308 309 310 311 // 312 313 // FUNCTION: InitInstance(HINSTANCE, int) 314 315 // 316 317 // PURPOSE: Saves instance handle and creates main window 318 319 // 320 321 // COMMENTS: 322 323 // 324 325 // In this function, we save the instance handle in a global variable and 326 327 // create and display the main program window. 328 329 // 330 331 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) 332 333 { 334 335 HWND hWnd; 336 337 338 339 hInst = hInstance; // Store instance handle in our global variable 340 341 342 343 hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, 344 345 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); 346 347 348 349 if (!hWnd) 350 351 { 352 353 return FALSE; 354 355 } 356 357 358 359 ShowWindow(hWnd, nCmdShow); 360 361 UpdateWindow(hWnd); 362 363 364 365 return TRUE; 366 367 } 368 369 370 371 // 372 373 // FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) 374 375 // 376 377 // PURPOSE: Processes messages for the main window. 378 379 // 380 381 // WM_COMMAND - process the application menu 382 383 // WM_PAINT - Paint the main window 384 385 // WM_DESTROY - post a quit message and return 386 387 // 388 389 // 390 391 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 392 393 { 394 395 int wmId, wmEvent; 396 397 PAINTSTRUCT ps; 398 399 HDC hdc; 400 401 RECT winrect; 402 403 INT nlxPos; 404 405 INT nlyPos; 406 407 INT nDBPosx; 408 409 INT nDBPosy; 410 411 INT IORtmpx;//给IDM_OPTION_REGRET消息用的 412 413 INT IORtmpy; 414 415 POINT AIPoint; 416 417 HMENU SubMenu; 418 419 switch (message) 420 421 { 422 423 case WM_CREATE: 424 425 GlobalInitial(); 426 427 break; 428 429 case WM_COMMAND: 430 431 wmId = LOWORD(wParam); 432 433 wmEvent = HIWORD(wParam); 434 435 // Parse the menu selections: 436 437 switch (wmId) 438 439 { 440 441 case IDM_ABOUT: 442 443 DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); 444 445 break; 446 447 case IDM_EXIT: 448 449 DestroyWindow(hWnd); 450 451 break; 452 453 case IDM_OPTION_REGRET: 454 455 //这一步是专门给悔棋用的 456 457 //根据当前节点的指向,进行退解 458 459 if(nxPosForChessTable < 0 || nyPosForChessTable < 0) 460 461 break; 462 463 //下面这段代码还挺好使的 464 465 IORtmpx = nxPosForChessTable; 466 467 IORtmpy = nyPosForChessTable; 468 469 g_ChessTable[IORtmpx][IORtmpy].status = -1; 470 471 nxPosForChessTable = g_ChessTable[IORtmpx][IORtmpy].PrePointx; 472 473 nyPosForChessTable = g_ChessTable[IORtmpx][IORtmpy].PrePointy; 474 475 //清理工作 476 477 g_ChessTable[IORtmpx][IORtmpy].PrePointx = -1; 478 479 g_ChessTable[IORtmpx][IORtmpy].PrePointy = -1; 480 481 (++w_b_turn) %= 2;//再次变成0或1 482 483 InvalidateRect(hWnd, NULL, TRUE); 484 485 break; 486 487 case ID_OPTION_PLAYMODE: 488 489 (++ PlayMode) %= 2; 490 491 SubMenu= GetSubMenu(GetMenu(hWnd), 1); 492 493 if(PlayMode == 1) 494 495 CheckMenuItem(SubMenu, 1, MF_CHECKED|MF_BYPOSITION); 496 497 else 498 499 CheckMenuItem(SubMenu, 1, MF_UNCHECKED|MF_BYPOSITION); 500 501 GlobalInitial(); 502 503 InvalidateRect(hWnd, NULL, TRUE); 504 505 break; 506 507 case ID_OPTION_AIWATCH: 508 509 SubMenu= GetSubMenu(GetMenu(hWnd), 1); 510 511 (++ DrawMode) %= 2; 512 513 if(DrawMode == 1) 514 515 CheckMenuItem(SubMenu, 2, MF_CHECKED|MF_BYPOSITION); 516 517 else 518 519 CheckMenuItem(SubMenu, 2, MF_UNCHECKED|MF_BYPOSITION); 520 521 InvalidateRect(hWnd, NULL, TRUE); 522 523 break; 524 525 default: 526 527 return DefWindowProc(hWnd, message, wParam, lParam); 528 529 } 530 531 break; 532 533 case WM_PAINT: 534 535 hdc = BeginPaint(hWnd, &ps); 536 537 GetWindowRect(hWnd, &winrect); 538 539 540 541 WinRectConvert(&winrect); 542 543 //防闪屏处理 544 545 //FillRect(hdc, &winrect, (HBRUSH)GetStockObject(WHITE_BRUSH)); 546 547 BkBitmap(hdc, &winrect); 548 549 550 551 //DrawChess(hdc, 10, 10, 0); 552 553 //根据棋盘对应数据来画棋棋子 554 555 // TODO: Add any drawing code here... 556 557 EndPaint(hWnd, &ps); 558 559 break; 560 561 case WM_ERASEBKGND: 562 563 //这块代码就是为了进行消息拦截,因为我并不需要把屏幕背景重新刷一遍,那样会导致闪屏 564 565 break; 566 567 case WM_DESTROY: 568 569 PostQuitMessage(0); 570 571 break; 572 573 case WM_LBUTTONDOWN: 574 575 nlxPos = LOWORD(lParam) - g_nbase_x; 576 577 nlyPos = HIWORD(lParam) - g_nbase_y; 578 579 //部分初始化 580 581 GetWindowRect(hWnd, &winrect); 582 583 WinRectConvert(&winrect); 584 585 //做完了减法,一定要判断结果是否依旧大于0; 586 587 if(nlxPos <= 0 || nlyPos <= 0) 588 589 return 0; 590 591 //这两个除法主要是获取左上角的坐标,用来转换到棋盘数据对应的地址,同时下棋 592 593 nDBPosx = nlxPos / TABLE_SQUARE_LENGTH; 594 595 nDBPosy = nlyPos / TABLE_SQUARE_LENGTH; 596 597 if(nDBPosx >= CHESS_LINE_NUM || nDBPosy >= CHESS_LINE_NUM) 598 599 return 0; 600 601 //坐标判定有效之后,还需要对当前点的数据否有效进行检测 602 603 if(g_ChessTable[nDBPosx][nDBPosy].status != -1) 604 605 return 0; 606 607 else 608 609 { 610 611 g_ChessTable[nDBPosx][nDBPosy].status = w_b_turn; 612 613 g_ChessTable[nDBPosx][nDBPosy].PrePointx = nxPosForChessTable; 614 615 g_ChessTable[nDBPosx][nDBPosy].PrePointy = nyPosForChessTable; 616 617 //复制完成后,再更新前点坐标 618 619 nxPosForChessTable = nDBPosx; 620 621 nyPosForChessTable = nDBPosy; 622 623 DrawChess(GetDC(hWnd), nDBPosx * TABLE_SQUARE_LENGTH + g_nbase_x, nDBPosy * TABLE_SQUARE_LENGTH + g_nbase_y, w_b_turn); 624 625 TellWhoWin(hWnd, IsWin(nDBPosx, nDBPosy), &winrect); 626 627 } 628 629 //这里我打算改成GetAIPoint函数执行全部的AI函数调用,包括相关的数据显示 630 631 if(PlayMode)//1的时候执行人机对战 632 633 { 634 635 AIPoint = GetAIPoint(); 636 637 if(AIPoint.x != -1 && AIPoint.y != -1) 638 639 g_ChessTable[AIPoint.x][AIPoint.y].status = ((++w_b_turn) %= 2);//顺便执行了 640 641 g_ChessTable[AIPoint.x][AIPoint.y].PrePointx = nxPosForChessTable; 642 643 g_ChessTable[AIPoint.x][AIPoint.y].PrePointy = nyPosForChessTable; 644 645 //前点坐标更新 646 647 nxPosForChessTable = AIPoint.x; 648 649 nyPosForChessTable = AIPoint.y; 650 651 if(DrawMode == 0) 652 653 DrawChess(GetDC(hWnd), AIPoint.x * TABLE_SQUARE_LENGTH + g_nbase_x, AIPoint.y * TABLE_SQUARE_LENGTH + g_nbase_y, w_b_turn); 654 655 else 656 657 InvalidateRect(hWnd, NULL, TRUE); 658 659 TellWhoWin(hWnd, IsWin(AIPoint.x, AIPoint.y), &winrect); 660 661 } 662 663 //绘图部分 664 665 (++w_b_turn) %= 2;//再次变成0或1; 666 667 668 669 break; 670 671 default: 672 673 return DefWindowProc(hWnd, message, wParam, lParam); 674 675 } 676 677 return 0; 678 679 } 680 681 682 683 // Message handler for about box. 684 685 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 686 687 { 688 689 UNREFERENCED_PARAMETER(lParam); 690 691 switch (message) 692 693 { 694 695 case WM_INITDIALOG: 696 697 return (INT_PTR)TRUE; 698 699 700 701 case WM_COMMAND: 702 703 if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) 704 705 { 706 707 EndDialog(hDlg, LOWORD(wParam)); 708 709 return (INT_PTR)TRUE; 710 711 } 712 713 break; 714 715 } 716 717 return (INT_PTR)FALSE; 718 719 } 720 721 722 723 void DrawTable(HDC hdc, int base_x, int base_y) 724 725 { 726 727 int nsquarelength = TABLE_SQUARE_LENGTH; 728 729 int nTableOffset = TABLE_SQUARE_LENGTH / 2; 730 731 //画竖表格 732 733 for(int i = 0;i < CHESS_LINE_NUM;++ i) 734 735 { 736 737 MoveToEx(hdc, i * nsquarelength + base_x + nTableOffset, base_y + nTableOffset, NULL); 738 739 LineTo(hdc, i * nsquarelength + base_x + nTableOffset, (CHESS_LINE_NUM - 1) * nsquarelength + base_y + nTableOffset); 740 741 } 742 743 //画横表格 744 745 for(int i = 0;i < CHESS_LINE_NUM;++ i) 746 747 { 748 749 MoveToEx(hdc, base_x + nTableOffset, i * nsquarelength + base_y + nTableOffset, NULL); 750 751 LineTo(hdc, (CHESS_LINE_NUM - 1) * nsquarelength + base_x + nTableOffset, i * nsquarelength + base_y + nTableOffset); 752 753 } 754 755 } 756 757 758 759 void DrwaChessOnTable(HDC hdc) 760 761 { 762 763 for(int i = 0;i < CHESS_LINE_NUM;++ i) 764 765 for(int j = 0;j < CHESS_LINE_NUM;++ j) 766 767 { 768 769 if(g_ChessTable[i][j].status != -1) 770 771 { 772 773 DrawChess(hdc, i * TABLE_SQUARE_LENGTH + g_nbase_x, j * TABLE_SQUARE_LENGTH + g_nbase_y, g_ChessTable[i][j].status); 774 775 } 776 777 } 778 779 } 780 781 void DrawChess(HDC hdc, int x, int y, int w_or_b)//0为白子,1为黑子 782 783 { 784 785 DWORD chesscolor; 786 787 if(w_or_b == 0) 788 789 { 790 791 chesscolor = RGB(255, 255, 255);//灰色,因为棋盘颜色背景还未选好 792 793 } 794 795 else 796 797 { 798 799 chesscolor = RGB(0, 0, 0); 800 801 } 802 803 HBRUSH ChessBrush = CreateSolidBrush(chesscolor); 804 805 HBRUSH OldBrush = (HBRUSH)SelectObject(hdc, ChessBrush); 806 807 //下面这两行的+2是根据效果手动确定的,效果还不错。 808 809 Ellipse(hdc, x + 2, y + 2, x + CHESS_LENGTH, y + CHESS_LENGTH); 810 811 ChessBrush = (HBRUSH)SelectObject(hdc, OldBrush); 812 813 assert(DeleteObject(ChessBrush) != 0); 814 815 } 816 817 818 819 void WinRectConvert(RECT * rect) 820 821 { 822 823 rect->bottom -= rect->top; 824 825 rect->right -= rect->left; 826 827 rect->left = 0; 828 829 rect->top = 0; 830 831 } 832 833 834 835 void GlobalInitial() 836 837 { 838 839 //初始化19*19的结构数组 840 841 for(int i = 0;i < CHESS_LINE_NUM;++ i) 842 843 for(int j = 0;j < CHESS_LINE_NUM;++ j) 844 845 { 846 847 g_ChessTable[i][j].status = -1; 848 849 //因为0 0 这个点是有效的坐标,因此初始化为-1用来表示无效点 850 851 g_ChessTable[i][j].PrePointx = -1; 852 853 g_ChessTable[i][j].PrePointy = -1; 854 855 856 857 g_ChessTable[i][j].nVisit_flag = 0;//该参数表明此节点节点是否已经访问过。0未访问 1访问 858 859 } 860 861 w_ChessLineBuffer.clear(); 862 863 b_ChessLineBuffer.clear(); 864 865 ; 866 867 } 868 869 870 871 INT IsWin(int x, int y) 872 873 { 874 875 //这个逻辑要仔细的想一下 876 877 //首先 在这段代码里 我很想说 如果每次都是对整个棋盘进行检查,实在是太笨了。 878 879 //毕竟每次要做的,仅仅是检查当前这点关联单位是否满足条件,而且,只关心上一点的颜色即可 880 881 882 883 int nTheColor = w_b_turn; 884 885 int CheckCounter = 0; 886 887 //行检查 888 889 int xStartPos; 890 891 if(x - 4 >= 0) 892 893 xStartPos = x - 4; 894 895 else 896 897 xStartPos = 0; 898 899 int xEndPos; 900 901 if(x + 4 < CHESS_LINE_NUM) 902 903 xEndPos = x + 4; 904 905 else 906 907 xEndPos = (CHESS_LINE_NUM - 1); 908 909 CheckCounter = 0; 910 911 for(int i = xStartPos;i <= xEndPos;++ i) 912 913 { 914 915 if(g_ChessTable[i][y].status == nTheColor) 916 917 { 918 919 CheckCounter++; 920 921 if(CheckCounter >= 5) 922 923 { 924 925 CheckCounter = 0; 926 927 return nTheColor; 928 929 } 930 931 } 932 933 else 934 935 { 936 937 CheckCounter = 0; 938 939 } 940 941 } 942 943 //列检查 944 945 int yStartPos; 946 947 if(y - 4 >= 0) 948 949 yStartPos = y - 4; 950 951 else 952 953 yStartPos = 0; 954 955 int yEndPos; 956 957 if(y + 4 < CHESS_LINE_NUM) 958 959 yEndPos = y + 4; 960 961 else 962 963 yEndPos = (CHESS_LINE_NUM - 1); 964 965 CheckCounter = 0; 966 967 for(int i = yStartPos;i <= yEndPos;++ i) 968 969 { 970 971 if(g_ChessTable[x][i].status == nTheColor) 972 973 { 974 975 CheckCounter++; 976 977 if(CheckCounter >= 5) 978 979 { 980 981 CheckCounter = 0; 982 983 return nTheColor; 984 985 } 986 987 } 988 989 else 990 991 { 992 993 CheckCounter = 0; 994 995 } 996 997 } 998 999 //左上角到右下角检查 1000 1001 CheckCounter = 0; 1002 1003 for(int i = -4;i <= 4;++ i) 1004 1005 { 1006 1007 if(x + i < 0 || y + i < 0 || x + i >= CHESS_LINE_NUM || y + i >= CHESS_LINE_NUM) 1008 1009 { 1010 1011 continue; 1012 1013 } 1014 1015 else 1016 1017 { 1018 1019 if(g_ChessTable[x + i][y + i].status == nTheColor) 1020 1021 { 1022 1023 CheckCounter ++; 1024 1025 if(CheckCounter >= 5) 1026 1027 { 1028 1029 CheckCounter = 0; 1030 1031 return nTheColor; 1032 1033 } 1034 1035 } 1036 1037 else 1038 1039 { 1040 1041 CheckCounter = 0; 1042 1043 } 1044 1045 } 1046 1047 } 1048 1049 //右上角到左下角检查 1050 1051 CheckCounter = 0; 1052 1053 for(int i = -4;i <= 4;++ i) 1054 1055 { 1056 1057 if(x - i < 0 || y + i < 0 || x - i >= CHESS_LINE_NUM || y + i >= CHESS_LINE_NUM) 1058 1059 { 1060 1061 continue; 1062 1063 } 1064 1065 else 1066 1067 { 1068 1069 if(g_ChessTable[x - i][y + i].status == nTheColor) 1070 1071 { 1072 1073 CheckCounter ++; 1074 1075 if(CheckCounter >= 5) 1076 1077 { 1078 1079 CheckCounter = 0; 1080 1081 return nTheColor; 1082 1083 } 1084 1085 } 1086 1087 else 1088 1089 { 1090 1091 CheckCounter = 0; 1092 1093 } 1094 1095 } 1096 1097 } 1098 1099 return -1; 1100 1101 } 1102 1103 1104 1105 INT TellWhoWin(HWND hWnd, INT n, RECT * rect) 1106 1107 { 1108 1109 //SetBkMode(hdc, TRANSPARENT);这个透明参数,想了想 还是算了,背景不透明更好一点。 1110 1111 /*把这段代码注释掉的原因是因为目前画面做的还不够好,这样还不如直接使用messagebox函数 1112 1113 rect->top += rect->bottom / 2; 1114 1115 LOGFONT lf; 1116 1117 memset(&lf, 0, sizeof(lf)); 1118 1119 lf.lfHeight = 50; 1120 1121 HFONT hfont = CreateFontIndirect(&lf); 1122 1123 HFONT OldFont = (HFONT)SelectObject(hdc, hfont);*/ 1124 1125 switch(n) 1126 1127 { 1128 1129 case 0: 1130 1131 //打出来白方胜 1132 1133 //DrawText(hdc, _T("白方胜"), 3, rect, DT_CENTER); 1134 1135 MessageBeep(-1); 1136 1137 MessageBox(hWnd, _T("白方胜"), _T("Notice"), 0); 1138 1139 break; 1140 1141 case 1: 1142 1143 //DrawText(hdc, _T("黑方胜"), 3, rect, DT_CENTER); 1144 1145 MessageBeep(-1); 1146 1147 MessageBox(hWnd, _T("黑方胜"), _T("Notice"), 0); 1148 1149 //这个自然就是黑方胜了 1150 1151 break; 1152 1153 default: 1154 1155 //DeleteObject(SelectObject(hdc,OldFont)); 1156 1157 return 0; 1158 1159 break;//这个break虽然没用,但是看着毕竟还是舒服点 1160 1161 } 1162 1163 //DeleteObject(SelectObject(hdc,OldFont)); 1164 1165 GlobalInitial(); 1166 1167 InvalidateRect(hWnd, NULL, TRUE);//擦写屏幕 1168 1169 // 1170 1171 return 1; 1172 1173 } 1174 1175 1176 1177 void BkBitmap(HDC hdc, RECT * rect) 1178 1179 { 1180 1181 HDC htmpdc = CreateCompatibleDC(hdc); 1182 1183 //HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rect->right - rect->right, rect->bottom - rect->top); 1184 1185 HBITMAP hPicBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BKBITMAP)); 1186 1187 HBITMAP OldBitmap = (HBITMAP)SelectObject(htmpdc, hPicBitmap); 1188 1189 1190 1191 //代码整合的尝试 1192 1193 DrawTable(htmpdc, g_nbase_x, g_nbase_y); 1194 1195 DrwaChessOnTable(htmpdc); 1196 1197 1198 1199 //调试专用 1200 1201 SetBkMode(htmpdc, TRANSPARENT); 1202 1203 //DrawInfo(htmpdc, MaxOfw_bLine, 2); 1204 1205 if(DrawMode) 1206 1207 DrawVecInfo(htmpdc, &w_ChessLineBuffer); 1208 1209 1210 1211 BitBlt(hdc, 0, 0, rect->right - rect->left, rect->bottom - rect->top, htmpdc, 0, 0, SRCCOPY); 1212 1213 hPicBitmap = (HBITMAP)SelectObject(htmpdc, OldBitmap); 1214 1215 DeleteObject(hPicBitmap); 1216 1217 DeleteDC(htmpdc); 1218 1219 } 1220 1221 1222 1223 void DrawInfo(HDC hdc, ChessLine * cl, INT length) 1224 1225 { 1226 1227 TCHAR WORD[100]; 1228 1229 TCHAR TMPWORD[3];//三个应该就够用了 1230 1231 for(int i = 0;i < length;++ i) 1232 1233 { 1234 1235 if(cl[i].ChessType == 0) 1236 1237 wcscpy(TMPWORD, _T("白方")); 1238 1239 else 1240 1241 wcscpy(TMPWORD, _T("黑方")); 1242 1243 wsprintf(WORD, _T("%s:StartPos x:%d y:%dEndPos x:%d y%d:%Length: %d"), 1244 1245 TMPWORD, 1246 1247 cl[i].startpos.x, 1248 1249 cl[i].startpos.y, 1250 1251 cl[i].endpos.x, 1252 1253 cl[i].endpos.y, 1254 1255 cl[i].length 1256 1257 ); 1258 1259 TextOut(hdc, 0,i * 100, WORD, 3); 1260 1261 TextOut(hdc, 0,i * 100 + 20, WORD + 3,wcslen(WORD) - 3); 1262 1263 } 1264 1265 } 1266 1267 POINT AIDeal(INT posx, INT posy)//因为大多数的变量都是全局变量,所以不需要很多参数。这两个参数是刚刚按下的点 1268 1269 { 1270 1271 POINT tmppoint; 1272 1273 return tmppoint; 1274 1275 } 1276 1277 1278 1279 void GetALLLine(INT w_or_b)//这个函数应该只处理一个点 1280 1281 { 1282 1283 //现在看,不用进行8个方向的查询,而是只需要做4个方向的查询即可,比如:1234,剩下的0567用其他的点来检测 1284 1285 //八个方向为上下左右以及其45度角 1286 1287 //8时钟方向,上位0,顺时针,从0 - 7 1288 1289 /* 1290 1291 7 0 1 1292 1293 6 2 1294 1295 5 4 3 1296 1297 */ 1298 1299 /*这个方法是有缺陷的,正常方法应该是对每个点都进行遍历,换言之,应该对点使用递归函数*/ 1300 1301 //0方向的全部线查找 1302 1303 1304 1305 //这两个变量都设计为数组,是因为8个方向的数据,都存储处理还是可以的 1306 1307 //一种比较节约空间的方法是设置临时变量,存储当前结果,与上一结果相比,这样就不需要8个变量,而仅仅是两个了。 1308 1309 ChessLine * pCL; 1310 1311 1312 1313 INT MaxLength = 0; 1314 1315 POINT MaxStartPos = {0}; 1316 1317 POINT MaxEndPos = {0}; 1318 1319 //memset(ArrayLength, 0, sizeof(ArrayLength));//嘿,看代码的,你应该知道我这么用是合法的吧? 1320 1321 //这段代码中有一部分代码应该函数化 1322 1323 if(0 == w_or_b) 1324 1325 w_ChessLineBuffer.clear(); 1326 1327 else 1328 1329 b_ChessLineBuffer.clear(); 1330 1331 for(int i = 0;i < CHESS_LINE_NUM;++ i) 1332 1333 { 1334 1335 for(int j = 0;j < CHESS_LINE_NUM; ++ j) 1336 1337 { 1338 1339 pCL = GetChessMaxSubLine(i, j, w_or_b, FALSE); 1340 1341 if(pCL == NULL) 1342 1343 continue; 1344 1345 if(pCL->length > MaxLength) 1346 1347 { 1348 1349 MaxLength = pCL->length; 1350 1351 MaxStartPos = pCL->startpos; 1352 1353 MaxEndPos = pCL->endpos; 1354 1355 } 1356 1357 DeleteCL(pCL); 1358 1359 } 1360 1361 } 1362 1363 } 1364 1365 INT GetMaxValCLAddr(ChessLine * parray, INT N) 1366 1367 { 1368 1369 if(parray == NULL && N <= 0) 1370 1371 return -1;//用来表示无效的数字 1372 1373 INT maxval = parray[0].length; 1374 1375 INT nrtnaddr = 0; 1376 1377 for(int i = 1;i < N;++ i) 1378 1379 { 1380 1381 if(maxval < parray[i].length) 1382 1383 { 1384 1385 maxval = parray[i].length; 1386 1387 nrtnaddr = i; 1388 1389 } 1390 1391 } 1392 1393 return nrtnaddr; 1394 1395 } 1396 1397 1398 1399 ChessLine * GetChessMaxSubLine(INT x, INT y, INT nColor, BOOL IfRtnVal) 1400 1401 { 1402 1403 INT CheckNum = 4; 1404 1405 POINT StartPos; 1406 1407 ChessLine JudgeLine[8]; 1408 1409 //判断点是否合法 1410 1411 if(nColor != g_ChessTable[x][y].status) 1412 1413 return NULL;//放弃当前点 1414 1415 //当前点合法后,开始8个方向的遍历 1416 1417 //初始化 1418 1419 StartPos.x = x; 1420 1421 StartPos.y = y; 1422 1423 //一旦这个点被选入,初始长度肯定至少是1 1424 1425 ChessLineInitial(JudgeLine, &StartPos, 8, nColor); 1426 1427 JudgeLine[0].endpos = StartPos; 1428 1429 //2方向 1430 1431 INT nRight = StartPos.x + 1; 1432 1433 while(nRight < CHESS_LINE_NUM && g_ChessTable[nRight][StartPos.y].status == nColor) 1434 1435 { 1436 1437 JudgeLine[0].length++; 1438 1439 nRight++;//向右查找 1440 1441 } 1442 1443 //保存对应的点 1444 1445 JudgeLine[0].endpos.x = nRight - 1; 1446 1447 JudgeLine[0].endpos.y = StartPos.y; 1448 1449 //检测线两端的情况,数据存储在Effectivelevel中 1450 1451 //线左端方向的查找 1452 1453 if(JudgeLine[0].startpos.x - 1 >= 0)//边界判断的前提条件 1454 1455 { 1456 1457 if(g_ChessTable[JudgeLine[0].startpos.x - 1][JudgeLine[0].startpos.y].status == -1) 1458 1459 JudgeLine[0].EffectLevel ++; 1460 1461 else if(g_ChessTable[JudgeLine[0].startpos.x - 1][JudgeLine[0].startpos.y].status == nColor) 1462 1463 { 1464 1465 //线点存在重复的线将被抛弃 1466 1467 JudgeLine[0].length = 0;//这样AddIntoBuf函数会自动抛弃该值 1468 1469 } 1470 1471 } 1472 1473 //线右端查找 1474 1475 if(JudgeLine[0].endpos.x + 1 < CHESS_LINE_NUM) 1476 1477 { 1478 1479 if(g_ChessTable[JudgeLine[0].endpos.x + 1][JudgeLine[0].endpos.y].status == -1) 1480 1481 JudgeLine[0].EffectLevel ++; 1482 1483 else if(g_ChessTable[JudgeLine[0].endpos.x + 1][JudgeLine[0].endpos.y].status == nColor) 1484 1485 { 1486 1487 JudgeLine[0].length = 0;//这样AddIntoBuf函数会自动抛弃该值 1488 1489 } 1490 1491 } 1492 1493 if(JudgeLine[0].EffectLevel != 0) 1494 1495 AddIntoBuf(&JudgeLine[0], nColor); 1496 1497 1498 1499 //3方向 1500 1501 INT nRightDownOffset = 1;//右下方向的偏移地址 1502 1503 while(StartPos.x + nRightDownOffset < CHESS_LINE_NUM && \ 1504 1505 StartPos.y + nRightDownOffset < CHESS_LINE_NUM && \ 1506 1507 g_ChessTable[StartPos.x + nRightDownOffset][StartPos.y + nRightDownOffset].status == nColor) 1508 1509 { 1510 1511 JudgeLine[1].length++; 1512 1513 nRightDownOffset++; 1514 1515 } 1516 1517 //保存对应的点 1518 1519 JudgeLine[1].endpos.x = StartPos.x + nRightDownOffset - 1; 1520 1521 JudgeLine[1].endpos.y = StartPos.y + nRightDownOffset - 1; 1522 1523 //右下和左上方向查找 1524 1525 if(JudgeLine[1].startpos.x - 1 >= 0 && JudgeLine[1].startpos.y - 1 >= 0) 1526 1527 { 1528 1529 if(g_ChessTable[JudgeLine[1].startpos.x - 1][JudgeLine[1].startpos.y - 1].status == -1) 1530 1531 JudgeLine[1].EffectLevel ++; 1532 1533 else if(g_ChessTable[JudgeLine[1].startpos.x - 1][JudgeLine[1].startpos.y - 1].status == nColor) 1534 1535 { 1536 1537 JudgeLine[1].length = 0; 1538 1539 } 1540 1541 } 1542 1543 if(JudgeLine[1].startpos.x + 1 < CHESS_LINE_NUM && JudgeLine[1].startpos.y + 1 < CHESS_LINE_NUM) 1544 1545 { 1546 1547 if(g_ChessTable[JudgeLine[1].endpos.x + 1][JudgeLine[1].endpos.y + 1].status == -1) 1548 1549 JudgeLine[1].EffectLevel ++; 1550 1551 else if(g_ChessTable[JudgeLine[1].endpos.x + 1][JudgeLine[1].endpos.y + 1].status == nColor) 1552 1553 { 1554 1555 JudgeLine[1].length = 0; 1556 1557 } 1558 1559 } 1560 1561 if(JudgeLine[1].EffectLevel != 0) 1562 1563 AddIntoBuf(&JudgeLine[1], nColor); 1564 1565 1566 1567 //4方向 1568 1569 INT nDown = StartPos.y + 1; 1570 1571 while(nDown < CHESS_LINE_NUM && g_ChessTable[StartPos.x][nDown].status == nColor) 1572 1573 { 1574 1575 JudgeLine[2].length++; 1576 1577 nDown++;//向下查找 1578 1579 } 1580 1581 //保存对应的点 1582 1583 JudgeLine[2].endpos.x = StartPos.x; 1584 1585 JudgeLine[2].endpos.y = nDown - 1; 1586 1587 //上下两个方向的查找 1588 1589 //上 - 1590 1591 if(JudgeLine[2].startpos.y - 1 >= 0) 1592 1593 { 1594 1595 if(g_ChessTable[JudgeLine[2].startpos.x][JudgeLine[2].startpos.y - 1].status == -1) 1596 1597 JudgeLine[2].EffectLevel ++; 1598 1599 else if(g_ChessTable[JudgeLine[2].startpos.x][JudgeLine[2].startpos.y - 1].status == nColor) 1600 1601 { 1602 1603 JudgeLine[2].length = 0; 1604 1605 } 1606 1607 } 1608 1609 //下 + 1610 1611 if(JudgeLine[2].endpos.y + 1 < CHESS_LINE_NUM) 1612 1613 { 1614 1615 if(g_ChessTable[JudgeLine[2].endpos.x][JudgeLine[2].endpos.y + 1].status == -1) 1616 1617 JudgeLine[2].EffectLevel ++; 1618 1619 else if(g_ChessTable[JudgeLine[2].endpos.x][JudgeLine[2].endpos.y + 1].status == nColor) 1620 1621 { 1622 1623 JudgeLine[2].length = 0; 1624 1625 } 1626 1627 } 1628 1629 if(JudgeLine[2].EffectLevel != 0) 1630 1631 AddIntoBuf(&JudgeLine[2], nColor); 1632 1633 1634 1635 //5方向 1636 1637 INT nLeftDownOffset = 1;//左下方向偏移地址,x -;y + 1638 1639 while(StartPos.x - nLeftDownOffset >= 0 && \ 1640 1641 StartPos.y + nLeftDownOffset < CHESS_LINE_NUM && \ 1642 1643 g_ChessTable[StartPos.x - nLeftDownOffset][StartPos.y + nLeftDownOffset].status == nColor) 1644 1645 { 1646 1647 JudgeLine[3].length++; 1648 1649 nLeftDownOffset++; 1650 1651 } 1652 1653 JudgeLine[3].endpos.x = StartPos.x - (nLeftDownOffset - 1);//为了逻辑清楚,就先这么写了 1654 1655 JudgeLine[3].endpos.y = StartPos.y + nLeftDownOffset - 1; 1656 1657 //左下右上方向 1658 1659 //右上 1660 1661 if(JudgeLine[3].startpos.y - 1 >= 0 && JudgeLine[3].startpos.x + 1 < CHESS_LINE_NUM) 1662 1663 { 1664 1665 if(g_ChessTable[JudgeLine[3].startpos.x + 1][JudgeLine[3].startpos.y - 1].status == -1) 1666 1667 JudgeLine[3].EffectLevel ++; 1668 1669 else if(g_ChessTable[JudgeLine[3].startpos.x + 1][JudgeLine[3].startpos.y - 1].status == nColor) 1670 1671 { 1672 1673 JudgeLine[3].length = 0; 1674 1675 } 1676 1677 } 1678 1679 //左下 1680 1681 if(JudgeLine[3].endpos.y + 1 < CHESS_LINE_NUM && JudgeLine[3].endpos.x - 1 >= 0) 1682 1683 { 1684 1685 if(g_ChessTable[JudgeLine[3].endpos.x - 1][JudgeLine[3].endpos.y + 1].status == -1) 1686 1687 JudgeLine[3].EffectLevel ++; 1688 1689 else if(g_ChessTable[JudgeLine[3].endpos.x - 1][JudgeLine[3].endpos.y + 1].status == nColor) 1690 1691 { 1692 1693 JudgeLine[3].length = 0; 1694 1695 } 1696 1697 } 1698 1699 if(JudgeLine[3].EffectLevel != 0) 1700 1701 AddIntoBuf(&JudgeLine[3], nColor); 1702 1703 //这段代码算是暂时废弃的 1704 1705 if(IfRtnVal) 1706 1707 { 1708 1709 ChessLine * pFinalLine = new ChessLine; 1710 1711 if(pFinalLine == NULL) 1712 1713 return NULL; 1714 1715 INT MaxLengthAddr = GetMaxValCLAddr(JudgeLine, CheckNum); 1716 1717 if(MaxLengthAddr == -1) 1718 1719 { 1720 1721 delete pFinalLine; 1722 1723 return NULL; 1724 1725 } 1726 1727 *pFinalLine = JudgeLine[MaxLengthAddr]; 1728 1729 return pFinalLine; 1730 1731 } 1732 1733 return NULL; 1734 1735 } 1736 1737 void AddIntoBuf(ChessLine * pcl,INT w_or_b) 1738 1739 { 1740 1741 switch(w_or_b) 1742 1743 { 1744 1745 case 0://白色 1746 1747 if(pcl->length > 1) 1748 1749 w_ChessLineBuffer.push_back(*pcl); 1750 1751 break; 1752 1753 case 1: 1754 1755 if(pcl->length > 1) 1756 1757 b_ChessLineBuffer.push_back(*pcl); 1758 1759 break; 1760 1761 } 1762 1763 } 1764 1765 void ChessLineInitial(ChessLine * pcl, POINT * pstartpos, INT n, INT nColor) 1766 1767 { 1768 1769 if(pcl == NULL || pstartpos == NULL) 1770 1771 return; 1772 1773 for(int i = 0;i < n;++ i) 1774 1775 { 1776 1777 pcl[i].length = 1; 1778 1779 pcl[i].startpos = *pstartpos; 1780 1781 pcl[i].ChessType = nColor; 1782 1783 pcl[i].EffectLevel = 0;//最低级 1784 1785 } 1786 1787 } 1788 1789 void DeleteCL(ChessLine * pcl) 1790 1791 { 1792 1793 delete pcl; 1794 1795 } 1796 1797 1798 1799 void DrawVecInfo(HDC hdc, std::vector<ChessLine> * pvcl) 1800 1801 { 1802 1803 TCHAR wcBuf[100]; 1804 1805 TCHAR tmpbuf[100]; 1806 1807 POINT tmppoint = GetAIPoint(); 1808 1809 INT num_w = pvcl->size(); 1810 1811 assert(num_w >= 0); 1812 1813 INT num_b = (pvcl + 1)->size(); 1814 1815 assert(num_b >= 0); 1816 1817 wsprintf(tmpbuf, _T("SP x:%d y:%d;EP x:%d y:%d;Len:%d;EL:%d;HL:%d"), 1818 1819 BestLine.startpos.x, 1820 1821 BestLine.startpos.y, 1822 1823 BestLine.endpos.x, 1824 1825 BestLine.endpos.y, 1826 1827 BestLine.length, 1828 1829 BestLine.EffectLevel, 1830 1831 BestLine.length + BestLine.EffectLevel 1832 1833 ); 1834 1835 TextOut(hdc, 0, 0, tmpbuf, wcslen(tmpbuf)); 1836 1837 wsprintf(tmpbuf, _T("AI x:%d y:%d"), tmppoint.x, tmppoint.y); 1838 1839 TextOut(hdc, 900, 0, tmpbuf, wcslen(tmpbuf)); 1840 1841 for(int i = 0;i < num_w;++ i) 1842 1843 { 1844 1845 wsprintf(wcBuf, _T("SP x:%d y:%d;EP x:%d y:%d;Len:%d;EL:%d;HL:%d"), 1846 1847 pvcl[0][i].startpos.x, pvcl[0][i].startpos.y, 1848 1849 pvcl[0][i].endpos.x, pvcl[0][i].endpos.y, 1850 1851 pvcl[0][i].length, 1852 1853 pvcl[0][i].EffectLevel, 1854 1855 pvcl[0][i].length + pvcl[0][i].EffectLevel); 1856 1857 TextOut(hdc, 0, (i+1) * 15, wcBuf, wcslen(wcBuf)); 1858 1859 } 1860 1861 for(int i = 0;i < num_b;++ i) 1862 1863 { 1864 1865 wsprintf(wcBuf, _T("SP x:%d y:%d;EP x:%d y:%d;Len:%d;EL:%d;HL:%d"), 1866 1867 pvcl[1][i].startpos.x, pvcl[1][i].startpos.y, 1868 1869 pvcl[1][i].endpos.x, pvcl[1][i].endpos.y, 1870 1871 pvcl[1][i].length, 1872 1873 pvcl[1][i].EffectLevel, 1874 1875 pvcl[1][i].length + pvcl[1][i].EffectLevel); 1876 1877 TextOut(hdc, 900, (i+1) * 15, wcBuf, wcslen(wcBuf)); 1878 1879 } 1880 1881 } 1882 1883 1884 1885 ChessLine * GetBestLine(INT nColor) 1886 1887 { 1888 1889 ChessLine * pcl = new ChessLine; 1890 1891 if(pcl == NULL) 1892 1893 return NULL; 1894 1895 std::vector<ChessLine> * pvcl; 1896 1897 if(nColor == 0) 1898 1899 pvcl = &w_ChessLineBuffer; 1900 1901 else 1902 1903 pvcl = &b_ChessLineBuffer; 1904 1905 INT nsize = pvcl->size(); 1906 1907 if(nsize == 0) 1908 1909 return NULL; 1910 1911 //删除没用的线 1912 1913 //线还是先不删了,擅自修改vector的大小会引发大量的越界问题 1914 1915 /* 1916 1917 std::vector<ChessLine>::iterator pvcl_itstart = pvcl->begin(); 1918 1919 std::vector<ChessLine>::iterator pvcl_itend = pvcl->end(); 1920 1921 for(int i = 0;i < nsize;) 1922 1923 { 1924 1925 if(pvcl_itstart[i].EffectLevel == 0) 1926 1927 { 1928 1929 pvcl->erase(pvcl_itstart + i); 1930 1931 nsize --; 1932 1933 continue; 1934 1935 } 1936 1937 i++; 1938 1939 }*/ 1940 1941 1942 1943 //然后使用优先级判断公式 length + EffectLevel 1944 1945 //先获取最大值 1946 1947 INT num_cl = pvcl->size(); 1948 1949 if(num_cl == 0) 1950 1951 return NULL; 1952 1953 INT nMax = 1; 1954 1955 INT nMaxAddr = 0; 1956 1957 for(int i = 0;i < num_cl;++ i) 1958 1959 { 1960 1961 if((*pvcl)[i].EffectLevel + (*pvcl)[i].length > nMax && (*pvcl)[i].EffectLevel != 0) 1962 1963 { 1964 1965 nMax = (*pvcl)[i].EffectLevel + (*pvcl)[i].length; 1966 1967 nMaxAddr = i; 1968 1969 } 1970 1971 } 1972 1973 1974 1975 *pcl = (*pvcl)[nMaxAddr]; 1976 1977 return pcl; 1978 1979 } 1980 1981 1982 1983 POINT GetAIPoint()//根据GetBestLine返回的黑白两棋子线情况来判断棋子的位置 1984 1985 { 1986 1987 //先获取全部的线。 1988 1989 GetALLLine(0); 1990 1991 GetALLLine(1); 1992 1993 //这里曾造成内存泄露,原因是返回路径会切断删除函数的调用 1994 1995 ChessLine * pw_cl = GetBestLine(0);//白子 人方 1996 1997 ChessLine * pb_cl = GetBestLine(1);//黑子 AI 1998 1999 ChessLine * pfinal_cl; 2000 2001 POINT rtnpos = {-1, -1}; 2002 2003 if(pw_cl != NULL && pb_cl != NULL) 2004 2005 { 2006 2007 //防守优先 2008 2009 if(pw_cl->EffectLevel + pw_cl->length >= pb_cl->EffectLevel + pb_cl->length) 2010 2011 pfinal_cl = pw_cl; 2012 2013 else 2014 2015 pfinal_cl = pb_cl; 2016 2017 } 2018 2019 else if(pw_cl == NULL && pb_cl != NULL) 2020 2021 pfinal_cl = pb_cl; 2022 2023 else if(pb_cl == NULL && pw_cl != NULL) 2024 2025 pfinal_cl = pw_cl; 2026 2027 else //在上面的两个ChessLine都获取不到的时候,需要做的是,尝试去获取一个单独的点。 2028 2029 { 2030 2031 POINT SingleFinalPoint = GetSinglePoint(); 2032 2033 return SingleFinalPoint; 2034 2035 } 2036 2037 //这个是测试用数据,全局变量 2038 2039 BestLine = *pfinal_cl; 2040 2041 switch(GetValidSEDirection(pfinal_cl->startpos, pfinal_cl->endpos)) 2042 2043 { 2044 2045 case 0://2-6 2046 2047 //2 2048 2049 if(g_ChessTable[pfinal_cl->startpos.x - 1][pfinal_cl->startpos.y].status == -1 2050 2051 && pfinal_cl->startpos.x - 1 >= 0) 2052 2053 { 2054 2055 rtnpos.x = pfinal_cl->startpos.x - 1; 2056 2057 rtnpos.y = pfinal_cl->startpos.y; 2058 2059 } 2060 2061 else if(pfinal_cl->endpos.x + 1 < CHESS_LINE_NUM) 2062 2063 { 2064 2065 rtnpos.x = pfinal_cl->endpos.x + 1; 2066 2067 rtnpos.y = pfinal_cl->endpos.y; 2068 2069 } 2070 2071 break; 2072 2073 case 1://3-7 2074 2075 if(g_ChessTable[pfinal_cl->startpos.x - 1][pfinal_cl->startpos.y - 1].status == -1) 2076 2077 { 2078 2079 rtnpos.x = pfinal_cl->startpos.x - 1; 2080 2081 rtnpos.y = pfinal_cl->startpos.y - 1; 2082 2083 } 2084 2085 else 2086 2087 { 2088 2089 rtnpos.x = pfinal_cl->endpos.x + 1; 2090 2091 rtnpos.y = pfinal_cl->endpos.y + 1; 2092 2093 } 2094 2095 //return rtnpos; 2096 2097 break; 2098 2099 case 2://4-0 2100 2101 if(g_ChessTable[pfinal_cl->startpos.x][pfinal_cl->startpos.y - 1].status == -1 2102 2103 && pfinal_cl->startpos.y - 1>= 0 2104 2105 && pfinal_cl->endpos.y + 1 < CHESS_LINE_NUM) 2106 2107 { 2108 2109 rtnpos.x = pfinal_cl->startpos.x; 2110 2111 rtnpos.y = pfinal_cl->startpos.y - 1; 2112 2113 } 2114 2115 else 2116 2117 { 2118 2119 rtnpos.x = pfinal_cl->endpos.x; 2120 2121 rtnpos.y = pfinal_cl->endpos.y + 1; 2122 2123 } 2124 2125 //return rtnpos; 2126 2127 break; 2128 2129 case 3://5-1 2130 2131 if(g_ChessTable[pfinal_cl->startpos.x + 1][pfinal_cl->startpos.y - 1].status == -1 2132 2133 && pfinal_cl->startpos.x + 1 < CHESS_LINE_NUM 2134 2135 && pfinal_cl->startpos.y - 1 >= 0) 2136 2137 { 2138 2139 rtnpos.x = pfinal_cl->startpos.x + 1; 2140 2141 rtnpos.y = pfinal_cl->startpos.y - 1; 2142 2143 } 2144 2145 else 2146 2147 { 2148 2149 rtnpos.x = pfinal_cl->endpos.x - 1; 2150 2151 rtnpos.y = pfinal_cl->endpos.y + 1; 2152 2153 } 2154 2155 //return rtnpos; 2156 2157 break; 2158 2159 } 2160 2161 DeleteCL(pw_cl); 2162 2163 DeleteCL(pb_cl); 2164 2165 return rtnpos; 2166 2167 } 2168 2169 2170 2171 INT GetValidSEDirection(POINT SP, POINT EP)//获取有效的方向,返回值 0,1,2,3分别对应2-6, 3-7, 4-0,5-1, 2172 2173 { 2174 2175 //终点减去起始点 2176 2177 INT ndirx = EP.x - SP.x; 2178 2179 INT ndiry = EP.y - SP.y; 2180 2181 /* 2182 2183 7(-1,1) 0(0,1) 1(1,1) 2184 2185 6(-1,0) 2(1,0) 2186 2187 5(-1,-1)4(0,-1) 3(1,-1) 2188 2189 */ 2190 2191 if(ndirx > 0) 2192 2193 { 2194 2195 if(ndiry == 0)//2(1,0) 2196 2197 return 0; 2198 2199 else//3(1,-1) 2200 2201 return 1; 2202 2203 } 2204 2205 else if(ndirx == 0) 2206 2207 return 2; 2208 2209 else 2210 2211 return 3; 2212 2213 } 2214 2215 POINT GetSinglePoint() 2216 2217 { 2218 2219 //所谓singlepoint,就是8个相邻点中没有任何一点是同色点。 2220 2221 //函数返回值为从0-7中的有效点中的一个随机点 2222 2223 INT npos; 2224 2225 POINT rtnpoint = {-1, -1}; 2226 2227 for(int i = 0;i < CHESS_LINE_NUM;++ i) 2228 2229 { 2230 2231 for(int j = 0;j < CHESS_LINE_NUM;++ j) 2232 2233 { 2234 2235 if(g_ChessTable[i][j].status != -1) 2236 2237 { 2238 2239 npos = IsValidSinglePoint(i, j); 2240 2241 if(npos == -1) 2242 2243 continue; 2244 2245 switch(npos) 2246 2247 { 2248 2249 //这里的代码直接return,就不用再break了 2250 2251 case 0: 2252 2253 rtnpoint.x = i - 1; 2254 2255 rtnpoint.y = j - 1; 2256 2257 break; 2258 2259 case 1: 2260 2261 rtnpoint.x = i; 2262 2263 rtnpoint.y = j - 1; 2264 2265 break; 2266 2267 case 2: 2268 2269 rtnpoint.x = i + 1; 2270 2271 rtnpoint.y = j - 1; 2272 2273 break; 2274 2275 case 3: 2276 2277 rtnpoint.x = i - 1; 2278 2279 rtnpoint.y = j; 2280 2281 break; 2282 2283 case 4: 2284 2285 rtnpoint.x = i + 1; 2286 2287 rtnpoint.y = j; 2288 2289 break; 2290 2291 case 5: 2292 2293 rtnpoint.x = i - 1; 2294 2295 rtnpoint.y = j + 1; 2296 2297 break; 2298 2299 case 6: 2300 2301 rtnpoint.x = i; 2302 2303 rtnpoint.y = j + 1; 2304 2305 break; 2306 2307 case 7: 2308 2309 rtnpoint.x = i + 1; 2310 2311 rtnpoint.y = j + 1; 2312 2313 break; 2314 2315 } 2316 2317 return rtnpoint; 2318 2319 } 2320 2321 } 2322 2323 } 2324 2325 return rtnpoint; 2326 2327 } 2328 2329 2330 2331 INT IsValidSinglePoint(int x, int y) 2332 2333 { 2334 2335 assert(x >= 0 && y >=0 && x < CHESS_LINE_NUM && y < CHESS_LINE_NUM); 2336 2337 char checkflag[8] = {0};//纯标记位 2338 2339 if(x - 1 >= 0)//一次查三个点 2340 2341 { 2342 2343 if(y - 1 >= 0) 2344 2345 { 2346 2347 if(g_ChessTable[x - 1][y - 1].status == -1) 2348 2349 checkflag[0] = 1; 2350 2351 } 2352 2353 if(g_ChessTable[x - 1][y].status == -1) 2354 2355 checkflag[3] = 1; 2356 2357 if(y + 1 < CHESS_LINE_NUM) 2358 2359 { 2360 2361 if(g_ChessTable[x - 1][y + 1].status == -1) 2362 2363 checkflag[5] = 1; 2364 2365 } 2366 2367 } 2368 2369 if(y - 1 >= 0 && g_ChessTable[x][y - 1].status == -1) 2370 2371 checkflag[1] = 1; 2372 2373 if(y + 1 < CHESS_LINE_NUM && g_ChessTable[x][y + 1].status == -1) 2374 2375 2376 2377 { 2378 2379 checkflag[6] = 1; 2380 2381 } 2382 2383 if(x + 1 < CHESS_LINE_NUM) 2384 2385 { 2386 2387 if(g_ChessTable[x + 1][y].status == -1) 2388 2389 checkflag[4] = 1; 2390 2391 if(y + 1 < CHESS_LINE_NUM) 2392 2393 { 2394 2395 if(g_ChessTable[x + 1][y + 1].status == -1) 2396 2397 checkflag[7] = 1; 2398 2399 } 2400 2401 if(y - 1 >= 0) 2402 2403 { 2404 2405 if(g_ChessTable[x + 1][y - 1].status == -1) 2406 2407 checkflag[2] = 1; 2408 2409 } 2410 2411 } 2412 2413 /*调试部分 2414 2415 INT nrtn = 0; 2416 2417 for(int i = 0;i < 8;++ i) 2418 2419 { 2420 2421 if(checkflag[i] == 1) 2422 2423 { 2424 2425 nrtn |= 1 << (i * 4); 2426 2427 } 2428 2429 }*/ 2430 2431 INT nCounterofValidPoint = 0; 2432 2433 for(int i = 0;i < 8;++ i) 2434 2435 { 2436 2437 if(checkflag[i] == 1) 2438 2439 nCounterofValidPoint ++; 2440 2441 } 2442 2443 if(!nCounterofValidPoint) 2444 2445 return -1; 2446 2447 srand(time(0)); 2448 2449 INT nUpSection = rand() % 8;//在这倒是正好能用作地址上限 2450 2451 INT UpSearch = nUpSection; 2452 2453 INT DownSearch = nUpSection; 2454 2455 for(;UpSearch < 8 || DownSearch >= 0;) 2456 2457 { 2458 2459 if(UpSearch < 8) 2460 2461 { 2462 2463 if(checkflag[UpSearch] == 1) 2464 2465 return UpSearch; 2466 2467 else 2468 2469 UpSearch ++; 2470 2471 } 2472 2473 if(DownSearch >= 0) 2474 2475 { 2476 2477 if(checkflag[DownSearch] == 1) 2478 2479 return DownSearch; 2480 2481 else 2482 2483 DownSearch --; 2484 2485 } 2486 2487 } 2488 2489 }