五子棋连胜六个前面被堵1v1算不算连胜赢

我的五子棋人工智能算法
近期完成了一个支持人机对战的五子棋游戏。虽然比较简单,而且对于常下五子棋的人来说,计算机的胜率往往低于50%,但无论如何,我对人工智能一块还是比较满意,所以发表出来,以期抛砖引玉,和大家共同探讨。
对于五子棋游戏来说,人工智能的核心思想是:分析棋盘上所有的空白棋格,并找出最应该下在哪里(我认为所谓人工智能,就是将人的智能用计算机语言来描述而已。所以我的所有思想,均是从我自己的下棋经验中总结出来的)。
首先,为了叙述方便,做如下约定(可能不是很专业):假如有连续的同色三子,并且两头没有对手的封堵,称为活三;如果两头均被封堵,称为死三;如果有一头被封堵,我称为冲三。
由于直接分析棋盘非常抽象,难以表述,所以我采用的方法是,假设有一子下在当前棋格,然后考虑该子对棋盘格局的影响。这样有了参照点,就便于思考。
找到一个空白棋格后,我先假设己方走一子(这个考虑的是攻击性),然后在某一方向上会出现5大类情况:5连、4连、3连、2连、1连(即原来有4-0子);每一个大类又分3种情况:活、冲、死。所以一共有15种情况。一共4个方向(横、竖、左斜、右斜),所以一共有4*15=60种情况。然后再假设对方走一子(预计对手走该子会造成什么影响,如果对手的攻击性很高,必须提前走该子以防御对手),也有60种情况。故每个空白棋格,必须考虑120种情况。
根据我的下棋经验具体分来,120种情况可以分为3大类:无论己方还是对方,如果下一子后能够形成5连,无论活、冲、死,均应该具有最高的优先级(因为此时已经能决定胜负了),另外无论己方还是对手,活4,冲4的优先级都比较高,必须优先作出响应。这一类情况称为绝对优先(关于对方活5的情况比较特殊。按理来说将要出现活5是因为现在棋盘已经出现对手活四,此时已经没有处理的意义了,但由于此时胜负只在1步之间:如果己方也能出现5连(由后面的优先级公式可以看出,己方5连优先级大于敌方5连),那么此时己方先赢;如果没有5连,那己方必输,无论对敌方进行封堵还是另外走其他子都不影响最终结果。故为了计算优先级的方便,将对方活5也放在绝对优先里);除了死5外,任何死棋均没有处理的意义(两头都被堵了),另外1连的情况也没有处理的意义(这意味着周围没有同色子,我认为没有人会这样走),所以均应该具有最低的优先级,称为绝对不优先;除了这两类以外的所有情况,均称为普通优先。
以己方横向为例,分析一下我的代码: int NumOfC//有几枚连续己方子 int m,n; int NumOfNC//对方在几端进行了封堵
//己方横向 NumOfChess = 1;
//己方已经下了一子(在当前格) NumOfNChess = 0; m = x - 1;
//当前棋格的左边,还有没有己方的子 while (m &= 0
&& m_board[m][y] == m_FlagRival) {
NumOfChess ++;
//如果己方最左边子的左边,已经没有空白棋格,即可认定左端已经被封堵 if (m & 0) NumOfNChess++;//左边是边界
if (m_board[m][y] != 0) NumOfNChess++;//左边是对手的棋 }
m = x + 1;
//同理考察右边 while (m & MAX
&& m_board[m][y] == m_FlagRival) {
NumOfChess ++;
m++; } if (m &= MAX) NumOfNChess++; else {
if (m_board[m][y] != 0) NumOfNChess++; }
//己方横向一共可能有15种情况,先分析此时是不是绝对不优先的情况 if((NumOfChess & 5 && NumOfNChess == 2) || NumOfChess == 1)
m_iTempBuf[0] = 0; //当排除绝对不优先的情况后,计算此时的优先级(是否绝对优先,需要在后面判断) else m_iTempBuf[0] = NumOfChess * 10 + 3 - NumOfNC
如果是敌方横向,计算优先级的公式稍有变化: else m_iTempBuf[4] = NumOfChess * 10 - 3 - NumOfNC其他方向的代码类似。
在这里,m_iTempBuf是一个整型数组,前8位(下标0-7)分别存储己方4个方向和敌方4个方向的优先级。当全部计算完之后,继续进行如下处理: m_iTempBuf[8] = 0;
//该位计算攻击力之和 //计算攻击力之和 for(m = 0; m & 4; m++)
m_iTempBuf[8] += m_iTempBuf[m];
//找出最大优先级,并存入tempPriority int tempPriority = m_iTempBuf[0]; for (m = 1; m & 8; m++) {
if (m_iTempBuf[m] & tempPriority)
tempPriority = m_iTempBuf[m]; }
int Priority = tempPriority * 4;//单方向最大优先级的返回值 //如果找出的最大优先级是绝对优先级,则当前棋格的优先级就是Priority(其值*4) if (tempPriority & 36)
//用上述计算优先级的公式计算出的值大于36即为绝对优先级
return P //否则返回的优先级为4个方向攻击力之和与单方向最大优先级中的最大值 //(此时明白为什么需要*4了吧。攻击力之和是4个方向优先级之和。要正确比较攻击力之和与单向优先级哪个更高,必须将单向的值*4) else
return (Priority & m_iTempBuf[8] ? Priority : m_iTempBuf[8]);
此时计算的是某一点的优先级。遍历棋盘,计算各个空白点的优先级,然后找出优先级最大的点,就可以走棋了。
当然,我的算法肯定还比较粗糙。各位高手如果思考过五子棋人工智能算法,请不吝赐教,或者共同讨论。我的邮箱是
没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!五子棋工作文档
&&&&& 这个程序在创建初期的时候是有一个写的比较乱的文档的,但是很可惜回学校的时候没有带回来&&所以现在赶紧整理一下,不然再过一段时间就忘干净了。
&&&&&& 最初这个程序是受老同学所托做的,一开始的时候要求要人人对战和人机对战,但是大家都很明白,所谓的人人对战就是简单那的GDI绘图罢了,那些基础函数用好了自然没问题。而人机对战则需要一定的棋盘分析能力,做起来还是很复杂的。当时受时间限制,第一个版本是我用了两天时间做的一个人人对战,直接就给她发过去了,用来应付她的实习,因为我当时也不确定人机对战能不能做出来。不过之后我一直在做,毕竟之前没做过,算是一次尝试。之后貌似过了9天吧,才完成了核心函数:GetAIPoint。用这么长时间一个是因为没做过另外当时在家里还要帮家里干活,刨去干活加上打游戏的时间,平均下来每天的编码时间不到3个小时。不过去我还是用了不少的时间来思考棋盘的分析的。走了不少弯路,吸取了不少教训,感觉收获还是挺大的。但是比较悲剧的是,我后来发现这个程序有内存泄露问题,问题貌似处在DrawChess函数里,因为无棋子的重绘并不会增加内存总量,看官若有兴趣就帮我找找看吧,我是没找到到底哪里出了问题&&
&&&&&& 程序运行截图演示:
2程序主要数据结构以及函数:
1 //使用结构体有利于以后的数据扩展
3 status 参数是用来表示当前这个点的状态的,0表示白子,1表示黑子 -1表示尚无子
4 后两个参数是用来追踪前一个点的,用于悔棋
6 typedef struct
//悔棋 专用
INT nVisit_
14 typedef struct
POINT//起始地点
POINT//终止地点
INT ChessT//黑白子的辨别
INT EffectL//棋子线的影响力,这个值的优先级判定应该和长度相关联进行判断,可以考虑通过使用一个公式来计算
21 }ChessL
22 // Forward declarations of functions included in this code module:
MyRegisterClass(HINSTANCE hInstance);
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_F//默认初始化的值为0,应该是重启游戏的标志位
34 ChessLine BestL//白黑的最长有效线即可
35 INT DrawMode = 0;//0常规模式 1调试模式
36 INT PlayMode = 0;//游戏模式,分为人人对战0和人机对战1
37 //使用vector等模板时,还需要注意命名空间的问题
38 std::vector&ChessLine& w_ChessLineB//这个变量用于存储所有的棋子线,白色
39 std::vector&ChessLine& b_ChessLineB//黑色
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的结构数组来表示,每个结构表示棋子的状态。这个结构如下:
3 status 参数是用来表示当前这个点的状态的,0表示白子,1表示黑子 -1表示尚无子
5 后两个参数是用来追踪前一个点的,用于悔棋
9 typedef struct
//悔棋 专用
INT nVisit_
上面的这个结构中,status的作用就不多说了,后面的参数用处还是挺有意思的。PrePoint如其意思一样,就是之前的那个点。第一个点的这个参数的值均为-1,用来标识无效点。而第1个点之后的所有的点这个参数的值均为前一点的坐标值。我觉得我的这个设计除了有点浪费内存,别的嘛,效率和效果还都是挺不错的。这样只要根据这两个值,就能按照原路找回之前的点,从而实现悔棋以及其一套连续的操作。
最后一个参数是废弃的,之前想通过每个点做一个标记来实现点的方向的记录,不过后来的代码实现表明这个是比较困难的,有更好的方法来实现,也就是我的程序中现在所使用的方法。
1 typedef struct
POINT//起始地点
POINT//终止地点
INT ChessT//黑白子的辨别
INT EffectL//棋子线的影响力,这个值的优先级判定应该和长度相关联进行判断,可以考虑通过使用一个公式来计算
15 }ChessL
上面的这个结构是用来表示棋子线的结构,其组成包括:起点和终点,长度,棋子的颜色,以及棋子线两端是否有效。
而棋子线的优先级也可以通过一个很简单的公式计算出来,即优先级为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返回的黑白两棋子线情况来判断棋子的位置
//先获取全部的线。
GetALLLine(0);
GetALLLine(1);
//这里曾造成内存泄露,原因是返回路径会切断删除函数的调用
ChessLine * pw_cl = GetBestLine(0);//白子 人方
ChessLine * pb_cl = GetBestLine(1);//黑子 AI
ChessLine * pfinal_
POINT rtnpos = {-1, -1};
if(pw_cl != NULL && pb_cl != NULL)
//防守优先
if(pw_cl-&EffectLevel + pw_cl-&length &= pb_cl-&EffectLevel + pb_cl-&length)
pfinal_cl = pw_
pfinal_cl = pb_
else if(pw_cl == NULL && pb_cl != NULL)
pfinal_cl = pb_
else if(pb_cl == NULL && pw_cl != NULL)
pfinal_cl = pw_
else //在上面的两个ChessLine都获取不到的时候,需要做的是,尝试去获取一个单独的点。
POINT SingleFinalPoint = GetSinglePoint();
return SingleFinalP
最先调用的函数是GetAllLine函数。这个函数的功能是查找全部的有效的线并将其添加到棋子线的vector容器中。参数是棋子颜色,0表示白色,1表示黑色。
看下这个函数的代码:
1 void GetALLLine(INT w_or_b)//这个函数应该只处理一个点
//现在看,不用进行8个方向的查询,而是只需要做4个方向的查询即可,比如:1234,剩下的0567用其他的点来检测
//八个方向为上下左右以及其45度角
//8时钟方向,上位0,顺时针,从0 - 7
//这两个变量都设计为数组,是因为8个方向的数据,都存储处理还是可以的
//一种比较节约空间的方法是设置临时变量,存储当前结果,与上一结果相比,这样就不需要8个变量,而仅仅是两个了。
ChessLine * pCL;
INT MaxLength = 0;
POINT MaxStartPos = {0};
POINT MaxEndPos = {0};
//memset(ArrayLength, 0, sizeof(ArrayLength));//嘿,看代码的,你应该知道我这么用是合法的吧?
//这段代码中有一部分代码应该函数化
if(0 == w_or_b)
w_ChessLineBuffer.clear();
b_ChessLineBuffer.clear();
for(int i = 0;i & CHESS_LINE_NUM;++ i)
for(int j = 0;j & CHESS_LINE_NUM; ++ j)
pCL = GetChessMaxSubLine(i, j, w_or_b, FALSE);
if(pCL == NULL)
if(pCL-&length & MaxLength)
MaxLength = pCL-&
MaxStartPos = pCL-&
MaxEndPos = pCL-&
DeleteCL(pCL);
代码中的注释可以好好的看一看,在方向的选择上,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()
//所谓singlepoint,就是8个相邻点中没有任何一点是同色点。
//函数返回值为从0-7中的有效点中的一个随机点
POINT rtnpoint = {-1, -1};
for(int i = 0;i & CHESS_LINE_NUM;++ i)
for(int j = 0;j & CHESS_LINE_NUM;++ j)
if(g_ChessTable[i][j].status != -1)
npos = IsValidSinglePoint(i, j);
if(npos == -1)
switch(npos)
//这里的代码直接return,就不用再break了
rtnpoint.x = i - 1;
rtnpoint.y = j - 1;
rtnpoint.x =
rtnpoint.y = j - 1;
rtnpoint.x = i + 1;
rtnpoint.y = j - 1;
rtnpoint.x = i - 1;
rtnpoint.y =
rtnpoint.x = i + 1;
rtnpoint.y =
rtnpoint.x = i - 1;
rtnpoint.y = j + 1;
rtnpoint.x =
rtnpoint.y = j + 1;
rtnpoint.x = i + 1;
rtnpoint.y = j + 1;
从中还能发现又调用了一个函数:IsValidSinglePoint。如果点合法,会返回一个随机的方向值,0-7,即8个小方向。若非法,则返回-1。
接下来再看这个函数实现:
1 INT IsValidSinglePoint(int x, int y)
assert(x &= 0 && y &=0 && x & CHESS_LINE_NUM && y & CHESS_LINE_NUM);
char checkflag[8] = {0};//纯标记位
if(x - 1 &= 0)//一次查三个点
if(y - 1 &= 0)
if(g_ChessTable[x - 1][y - 1].status == -1)
checkflag[0] = 1;
if(g_ChessTable[x - 1][y].status == -1)
checkflag[3] = 1;
if(y + 1 & CHESS_LINE_NUM)
if(g_ChessTable[x - 1][y + 1].status == -1)
checkflag[5] = 1;
if(y - 1 &= 0 && g_ChessTable[x][y - 1].status == -1)
checkflag[1] = 1;
if(y + 1 & CHESS_LINE_NUM && g_ChessTable[x][y + 1].status == -1)
checkflag[6] = 1;
if(x + 1 & CHESS_LINE_NUM)
if(g_ChessTable[x + 1][y].status == -1)
checkflag[4] = 1;
if(y + 1 & CHESS_LINE_NUM)
if(g_ChessTable[x + 1][y + 1].status == -1)
checkflag[7] = 1;
if(y - 1 &= 0)
if(g_ChessTable[x + 1][y - 1].status == -1)
checkflag[2] = 1;
/*调试部分
INT nrtn = 0;
for(int i = 0;i & 8;++ i)
if(checkflag[i] == 1)
nrtn |= 1 && (i * 4);
INT nCounterofValidPoint = 0;
for(int i = 0;i & 8;++ i)
if(checkflag[i] == 1)
nCounterofValidPoint ++;
if(!nCounterofValidPoint)
return -1;
srand(time(0));
INT nUpSection = rand() % 8;//在这倒是正好能用作地址上限
INT UpSearch =
INT DownSearch = nUpS
for(;UpSearch & 8 || DownSearch &= 0;)
if(UpSearch & 8)
if(checkflag[UpSearch] == 1)
return UpS
UpSearch ++;
if(DownSearch &= 0)
if(checkflag[DownSearch] == 1)
return DownS
DownSearch --;
看起来一个功能简单的函数,其实要做的操作还是不少的。因为除了要将合法的点对号入座,还要以随机的形式取出来,代码并不是很简单。
由此,整个工程AI的核心实现基本介绍完毕。
附录A 比较杂乱的最初版工作日记
五子棋工作日记
棋盘布局:
&&&&&& 初步估计为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++;//向&&右&&&查&&找&&
&&&&&&&&&& }
&&&&&&&&&& //保&A&&存&?对?应&|的&I?点&I?
&&&&&&&&&& ArrayEndPos[0].x = nRight - 1;
&&&&&&&&&& ArrayEndPos[0].y = StartPos.y;
&&&&&&&&&& //3方&?向&&
&&&&&&&&&& INT nRightDownOffset = 1;//右&&&下?方&?向&&的&I?偏?移&?地&I?址&&
&&&&&&&&&& 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++;
&&&&&&&&&& }
&&&&&&&&&& //保&A&&存&?对?应&|的&I?点&I?
&&&&&&&&&& 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++;//向&&下?查&&找&&
&&&&&&&&&& }
&&&&&&&&&& //保&A&&存&?对?应&|的&I?点&I?
&&&&&&&&&& ArrayEndPos[2].x = StartPos.x;
&&&&&&&&&& ArrayEndPos[2].y = nDown - 1;
&&&&&&&&&& //5方&?向&&
&&&&&&&&&& INT nLeftDownOffset = 1;//左&A&&下?方&?向&&偏?移&?地&I?址&&,&?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)
&&&&&&&&&&&&&
&& 现在在棋盘数据扫描上,已经能够按照要求获取到最长的有效棋子线了,但是,还不能对最长棋子线的两端是否封闭进行检测。
&& 初步估计要做的工作是在获取当前点的最长棋子线后,根据其索引地址或者斜率计算的方式计算出来其可扩展方向,然后再判断扩展方向上是否有对方的棋子或者己方的棋子占据,有点小复杂。
&& 另外现在的棋子长度线检测是针对所有的线全部进行半规模检测,也就是只检查帮个方向,由此,倒也可以在一定程度上提高效率。
&& 之前的那种递归算法,也不是不可以,但是,那是另外一个思路了。我这个效率低一点,但是代码还比较好写。
刚才遇到了一个溢出错误,但是中断代码中并没有提示,给了我很大的困惑,因为在代码中并没有提示说异常出在了什么地方。
不过在调试信息的输出栏中,我看到了有关于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 // WuZiQi.cpp : Defines the entry point for the application.
7 曲敬原创建于日
11 #include "stdafx.h"
13 #include "WuZiQi.h"
15 #include &vector&//没辙,容器还是C++好用,纯SDK程序算是破产了
17 #include &assert.h&
19 #include &ctime&
21 #include &cstdlib&
23 #define MAX_LOADSTRING 100
25 #define TABLE_SQUARE_LENGTH 35
27 #define CHESS_LENGTH 30
29 #define CHESS_LINE_NUM 15
31 // Global Variables:
33 HINSTANCE hI
// current instance
35 TCHAR szTitle[MAX_LOADSTRING];
// The title bar text
37 TCHAR szWindowClass[MAX_LOADSTRING];
// the main window class name
39 //使用结构体有利于以后的数据扩展
43 status 参数是用来表示当前这个点的状态的,0表示白子,1表示黑子 -1表示尚无子
45 后两个参数是用来追踪前一个点的,用于悔棋
49 typedef struct
//悔棋 专用
INT nVisit_
65 typedef struct
POINT//起始地点
POINT//终止地点
INT ChessT//黑白子的辨别
INT EffectL//棋子线的影响力,这个值的优先级判定应该和长度相关联进行判断,可以考虑通过使用一个公式来计算
79 }ChessL
81 // Forward declarations of functions included in this code module:
MyRegisterClass(HINSTANCE hInstance);
InitInstance(HINSTANCE, int);
87 LRESULT CALLBACK
WndProc(HWND, UINT, WPARAM, LPARAM);
89 INT_PTR CALLBACK
About(HWND, UINT, WPARAM, LPARAM);
91 INT g_nbase_x = 300;
93 INT g_nbase_y = 10;
95 Chess g_ChessTable[CHESS_LINE_NUM][CHESS_LINE_NUM];//作为全局变量的数据表
97 BOOL w_b_turn = 0;//下棋顺序的控制变量
99 INT nxPosForChessTable = -1;//悔棋专用
101 INT nyPosForChessTable = -1;//悔棋专用
103 INT nRestart_F//默认初始化的值为0,应该是重启游戏的标志位
105 ChessLine BestL//白黑的最长有效线即可
107 INT DrawMode = 0;//0常规模式 1调试模式
109 INT PlayMode = 0;//游戏模式,分为人人对战0和人机对战1
111 //使用vector等模板时,还需要注意命名空间的问题
113 std::vector&ChessLine& w_ChessLineB//这个变量用于存储所有的棋子线,白色
115 std::vector&ChessLine& b_ChessLineB//黑色
119 void DrawTable(HDC hdc, int base_x = 0, int base_y = 0);
121 void WinRectConvert(RECT * rect);
123 void DrawChess(HDC hdc, int x, int y, int w_or_b = 0);//0为白子,1为黑子
125 void GlobalInitial();//全局初始化函数
127 void DrwaChessOnTable(HDC hdc);
129 INT IsWin(int x, int y);
131 INT TellWhoWin(HWND hWnd, INT n, RECT * rect);
133 void BkBitmap(HDC hdc, RECT * rect);
135 void DrawInfo(HDC hdc, ChessLine * cl, INT length);
137 void GetALLLine(INT w_or_b);//根据棋盘全局信息来获取对应颜色的最大长度线
139 INT GetMaxValCLAddr(ChessLine * parray,
INT N);//返回最大值的数字地址
141 ChessLine * GetChessMaxSubLine(INT x, INT y, INT nColor, BOOL IfRtnVal);//获取单个点的最长线函数
143 void AddIntoBuf(ChessLine * pcl,INT w_or_b);
145 void ChessLineInitial(ChessLine * pcl, POINT * pstartpos, INT n, INT nColor);
147 inline void DeleteCL(ChessLine * pcl);
149 void DrawVecInfo(HDC hdc, std::vector&ChessLine& * pvcl);
151 ChessLine * GetBestLine(INT nColor);
153 INT GetValidSEDirection(POINT SP, POINT EP);//获取有效的方向,返回值 0,1,2,3分别对应2-6, 3-7, 4-0,5-1,
155 POINT GetAIPoint();//根据GetBestLine返回的黑白两棋子线情况来判断棋子的位置
157 POINT GetSinglePoint();
159 INT IsValidSinglePoint(int x, int y);
163 int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
lpCmdLine,
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: Place code here.
HACCEL hAccelT
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WUZIQI, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
return FALSE;
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WUZIQI));
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
TranslateMessage(&msg);
DispatchMessage(&msg);
return (int) msg.wP
FUNCTION: MyRegisterClass()
PURPOSE: Registers the window class.
This function and its usage are only necessary if you want this code
to be compatible with Win32 systems prior to the 'RegisterClassEx'
function that was added to Windows 95. It is important to call this function
so that the application will get 'well formed' small icons associated
269 ATOM MyRegisterClass(HINSTANCE hInstance)
WNDCLASSEX
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style
= CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc
wcex.cbClsExtra
wcex.cbWndExtra
wcex.hInstance
wcex.hIcon
= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WUZIQI));
wcex.hCursor
= LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground
= (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName
= MAKEINTRESOURCE(IDC_WUZIQI);
wcex.lpszClassName
= szWindowC
wcex.hIconSm
= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
FUNCTION: InitInstance(HINSTANCE, int)
PURPOSE: Saves instance handle and creates main window
In this function, we save the instance handle in a global variable and
create and display the main program window.
331 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
hInst = hI // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
return FALSE;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
Processes messages for the main window.
WM_COMMAND
- process the application menu
- Paint the main window
WM_DESTROY
- post a quit message and return
391 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
int wmId, wmE
PAINTSTRUCT
INT IOR//给IDM_OPTION_REGRET消息用的
HMENU SubM
switch (message)
case WM_CREATE:
GlobalInitial();
case WM_COMMAND:
= LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
case IDM_EXIT:
DestroyWindow(hWnd);
case IDM_OPTION_REGRET:
//这一步是专门给悔棋用的
//根据当前节点的指向,进行退解
if(nxPosForChessTable & 0 ||
nyPosForChessTable & 0)
//下面这段代码还挺好使的
IORtmpx = nxPosForChessT
IORtmpy = nyPosForChessT
g_ChessTable[IORtmpx][IORtmpy].status = -1;
nxPosForChessTable = g_ChessTable[IORtmpx][IORtmpy].PreP
nyPosForChessTable = g_ChessTable[IORtmpx][IORtmpy].PreP
//清理工作
g_ChessTable[IORtmpx][IORtmpy].PrePointx = -1;
g_ChessTable[IORtmpx][IORtmpy].PrePointy = -1;
(++w_b_turn) %= 2;//再次变成0或1
InvalidateRect(hWnd, NULL, TRUE);
case ID_OPTION_PLAYMODE:
(++ PlayMode) %= 2;
SubMenu= GetSubMenu(GetMenu(hWnd), 1);
if(PlayMode == 1)
CheckMenuItem(SubMenu, 1, MF_CHECKED|MF_BYPOSITION);
CheckMenuItem(SubMenu, 1, MF_UNCHECKED|MF_BYPOSITION);
GlobalInitial();
InvalidateRect(hWnd, NULL, TRUE);
case ID_OPTION_AIWATCH:
SubMenu= GetSubMenu(GetMenu(hWnd), 1);
(++ DrawMode) %= 2;
if(DrawMode == 1)
CheckMenuItem(SubMenu, 2, MF_CHECKED|MF_BYPOSITION);
CheckMenuItem(SubMenu, 2, MF_UNCHECKED|MF_BYPOSITION);
InvalidateRect(hWnd, NULL, TRUE);
return DefWindowProc(hWnd, message, wParam, lParam);
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
GetWindowRect(hWnd, &winrect);
WinRectConvert(&winrect);
//防闪屏处理
//FillRect(hdc, &winrect, (HBRUSH)GetStockObject(WHITE_BRUSH));
BkBitmap(hdc, &winrect);
//DrawChess(hdc, 10, 10, 0);
//根据棋盘对应数据来画棋棋子
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
case WM_ERASEBKGND:
//这块代码就是为了进行消息拦截,因为我并不需要把屏幕背景重新刷一遍,那样会导致闪屏
case WM_DESTROY:
PostQuitMessage(0);
case WM_LBUTTONDOWN:
nlxPos = LOWORD(lParam) - g_nbase_x;
nlyPos = HIWORD(lParam) - g_nbase_y;
//部分初始化
GetWindowRect(hWnd, &winrect);
WinRectConvert(&winrect);
//做完了减法,一定要判断结果是否依旧大于0;
if(nlxPos &= 0 || nlyPos &= 0)
//这两个除法主要是获取左上角的坐标,用来转换到棋盘数据对应的地址,同时下棋
nDBPosx = nlxPos / TABLE_SQUARE_LENGTH;
nDBPosy = nlyPos / TABLE_SQUARE_LENGTH;
if(nDBPosx &= CHESS_LINE_NUM || nDBPosy &= CHESS_LINE_NUM)
//坐标判定有效之后,还需要对当前点的数据否有效进行检测
if(g_ChessTable[nDBPosx][nDBPosy].status != -1)
g_ChessTable[nDBPosx][nDBPosy].status = w_b_
g_ChessTable[nDBPosx][nDBPosy].PrePointx = nxPosForChessT
g_ChessTable[nDBPosx][nDBPosy].PrePointy = nyPosForChessT
//复制完成后,再更新前点坐标
nxPosForChessTable = nDBP
nyPosForChessTable = nDBP
DrawChess(GetDC(hWnd), nDBPosx * TABLE_SQUARE_LENGTH + g_nbase_x, nDBPosy * TABLE_SQUARE_LENGTH + g_nbase_y, w_b_turn);
TellWhoWin(hWnd, IsWin(nDBPosx, nDBPosy), &winrect);
//这里我打算改成GetAIPoint函数执行全部的AI函数调用,包括相关的数据显示
if(PlayMode)//1的时候执行人机对战
AIPoint = GetAIPoint();
if(AIPoint.x != -1 && AIPoint.y != -1)
g_ChessTable[AIPoint.x][AIPoint.y].status = ((++w_b_turn) %= 2);//顺便执行了
g_ChessTable[AIPoint.x][AIPoint.y].PrePointx = nxPosForChessT
g_ChessTable[AIPoint.x][AIPoint.y].PrePointy = nyPosForChessT
//前点坐标更新
nxPosForChessTable = AIPoint.x;
nyPosForChessTable = AIPoint.y;
if(DrawMode == 0)
DrawChess(GetDC(hWnd), AIPoint.x * TABLE_SQUARE_LENGTH + g_nbase_x, AIPoint.y * TABLE_SQUARE_LENGTH + g_nbase_y, w_b_turn);
InvalidateRect(hWnd, NULL, TRUE);
TellWhoWin(hWnd, IsWin(AIPoint.x, AIPoint.y), &winrect);
//绘图部分
(++w_b_turn) %= 2;//再次变成0或1;
return DefWindowProc(hWnd, message, wParam, lParam);
683 // Message handler for about box.
685 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
UNREFERENCED_PARAMETER(lParam);
switch (message)
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
return (INT_PTR)FALSE;
723 void DrawTable(HDC hdc,
int base_x, int base_y)
int nsquarelength = TABLE_SQUARE_LENGTH;
int nTableOffset = TABLE_SQUARE_LENGTH / 2;
//画竖表格
for(int i = 0;i & CHESS_LINE_NUM;++ i)
MoveToEx(hdc, i * nsquarelength + base_x + nTableOffset, base_y + nTableOffset, NULL);
LineTo(hdc, i * nsquarelength + base_x + nTableOffset, (CHESS_LINE_NUM - 1) * nsquarelength + base_y + nTableOffset);
//画横表格
for(int i = 0;i & CHESS_LINE_NUM;++ i)
MoveToEx(hdc, base_x + nTableOffset, i * nsquarelength + base_y + nTableOffset, NULL);
LineTo(hdc, (CHESS_LINE_NUM - 1) * nsquarelength + base_x + nTableOffset, i * nsquarelength + base_y + nTableOffset);
759 void DrwaChessOnTable(HDC hdc)
for(int i = 0;i & CHESS_LINE_NUM;++ i)
for(int j = 0;j & CHESS_LINE_NUM;++ j)
if(g_ChessTable[i][j].status != -1)
DrawChess(hdc, i * TABLE_SQUARE_LENGTH + g_nbase_x, j * TABLE_SQUARE_LENGTH + g_nbase_y, g_ChessTable[i][j].status);
781 void DrawChess(HDC hdc, int x, int y, int w_or_b)//0为白子,1为黑子
if(w_or_b == 0)
chesscolor = RGB(255, 255, 255);//灰色,因为棋盘颜色背景还未选好
chesscolor = RGB(0, 0, 0);
HBRUSH ChessBrush = CreateSolidBrush(chesscolor);
HBRUSH OldBrush = (HBRUSH)SelectObject(hdc, ChessBrush);
//下面这两行的+2是根据效果手动确定的,效果还不错。
Ellipse(hdc, x + 2, y + 2, x + CHESS_LENGTH, y + CHESS_LENGTH);
ChessBrush = (HBRUSH)SelectObject(hdc, OldBrush);
assert(DeleteObject(ChessBrush) != 0);
819 void WinRectConvert(RECT * rect)
rect-&bottom -= rect-&
rect-&right -= rect-&
rect-&left = 0;
rect-&top = 0;
835 void GlobalInitial()
//初始化19*19的结构数组
for(int i = 0;i & CHESS_LINE_NUM;++ i)
for(int j = 0;j & CHESS_LINE_NUM;++ j)
g_ChessTable[i][j].status = -1;
//因为0 0 这个点是有效的坐标,因此初始化为-1用来表示无效点
g_ChessTable[i][j].PrePointx = -1;
g_ChessTable[i][j].PrePointy = -1;
g_ChessTable[i][j].nVisit_flag = 0;//该参数表明此节点节点是否已经访问过。0未访问 1访问
w_ChessLineBuffer.clear();
b_ChessLineBuffer.clear();
871 INT IsWin(int x, int y)
//这个逻辑要仔细的想一下
//首先 在这段代码里 我很想说 如果每次都是对整个棋盘进行检查,实在是太笨了。
//毕竟每次要做的,仅仅是检查当前这点关联单位是否满足条件,而且,只关心上一点的颜色即可
int nTheColor = w_b_
int CheckCounter = 0;
int xStartP
if(x - 4 &= 0)
xStartPos = x - 4;
xStartPos = 0;
if(x + 4 & CHESS_LINE_NUM)
xEndPos = x + 4;
xEndPos = (CHESS_LINE_NUM - 1);
CheckCounter = 0;
for(int i = xStartPi &= xEndP++ i)
if(g_ChessTable[i][y].status == nTheColor)
CheckCounter++;
if(CheckCounter &= 5)
CheckCounter = 0;
return nTheC
CheckCounter = 0;
int yStartP
if(y - 4 &= 0)
yStartPos = y - 4;
yStartPos = 0;
if(y + 4 & CHESS_LINE_NUM)
yEndPos = y + 4;
yEndPos = (CHESS_LINE_NUM - 1);
CheckCounter = 0;
for(int i = yStartPi &= yEndP++ i)
if(g_ChessTable[x][i].status == nTheColor)
CheckCounter++;
if(CheckCounter &= 5)
CheckCounter = 0;
return nTheC
CheckCounter = 0;
//左上角到右下角检查
CheckCounter = 0;
for(int i = -4;i &= 4;++ i)
if(x + i & 0 || y + i & 0 || x + i &= CHESS_LINE_NUM || y + i &= CHESS_LINE_NUM)
if(g_ChessTable[x + i][y + i].status == nTheColor)
CheckCounter ++;
if(CheckCounter &= 5)
CheckCounter = 0;
return nTheC
CheckCounter = 0;
//右上角到左下角检查
CheckCounter = 0;
for(int i = -4;i &= 4;++ i)
if(x - i & 0 || y + i & 0 || x - i &= CHESS_LINE_NUM || y + i &= CHESS_LINE_NUM)
if(g_ChessTable[x - i][y + i].status == nTheColor)
CheckCounter ++;
if(CheckCounter &= 5)
CheckCounter = 0;
return nTheC
CheckCounter = 0;
return -1;
1105 INT TellWhoWin(HWND hWnd, INT n, RECT * rect)
//SetBkMode(hdc, TRANSPARENT);这个透明参数,想了想 还是算了,背景不透明更好一点。
/*把这段代码注释掉的原因是因为目前画面做的还不够好,这样还不如直接使用messagebox函数
rect-&top += rect-&bottom / 2;
memset(&lf, 0, sizeof(lf));
lf.lfHeight = 50;
HFONT hfont = CreateFontIndirect(&lf);
HFONT OldFont = (HFONT)SelectObject(hdc, hfont);*/
//打出来白方胜
//DrawText(hdc, _T("白方胜"), 3, rect, DT_CENTER);
MessageBeep(-1);
MessageBox(hWnd, _T("白方胜"), _T("Notice"), 0);
//DrawText(hdc, _T("黑方胜"), 3, rect, DT_CENTER);
MessageBeep(-1);
MessageBox(hWnd, _T("黑方胜"), _T("Notice"), 0);
//这个自然就是黑方胜了
//DeleteObject(SelectObject(hdc,OldFont));
break;//这个break虽然没用,但是看着毕竟还是舒服点
//DeleteObject(SelectObject(hdc,OldFont));
GlobalInitial();
InvalidateRect(hWnd, NULL, TRUE);//擦写屏幕
1177 void BkBitmap(HDC hdc, RECT * rect)
HDC htmpdc = CreateCompatibleDC(hdc);
//HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rect-&right - rect-&right, rect-&bottom - rect-&top);
HBITMAP hPicBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BKBITMAP));
HBITMAP OldBitmap = (HBITMAP)SelectObject(htmpdc, hPicBitmap);
//代码整合的尝试
DrawTable(htmpdc, g_nbase_x, g_nbase_y);
DrwaChessOnTable(htmpdc);
//调试专用
SetBkMode(htmpdc, TRANSPARENT);
//DrawInfo(htmpdc, MaxOfw_bLine, 2);
if(DrawMode)
DrawVecInfo(htmpdc, &w_ChessLineBuffer);
BitBlt(hdc, 0, 0, rect-&right - rect-&left, rect-&bottom - rect-&top, htmpdc, 0, 0, SRCCOPY);
hPicBitmap = (HBITMAP)SelectObject(htmpdc, OldBitmap);
DeleteObject(hPicBitmap);
DeleteDC(htmpdc);
1223 void DrawInfo(HDC hdc, ChessLine * cl, INT length)
TCHAR WORD[100];
TCHAR TMPWORD[3];//三个应该就够用了
for(int i = 0;i &++ i)
if(cl[i].ChessType == 0)
wcscpy(TMPWORD, _T("白方"));
wcscpy(TMPWORD, _T("黑方"));
wsprintf(WORD, _T("%s:StartPos x:%d y:%dEndPos x:%d y%d:%Length: %d"),
cl[i].startpos.x,
cl[i].startpos.y,
cl[i].endpos.x,
cl[i].endpos.y,
cl[i].length
TextOut(hdc, 0,i * 100, WORD, 3);
TextOut(hdc, 0,i * 100 + 20, WORD + 3,wcslen(WORD) - 3);
1267 POINT AIDeal(INT posx, INT posy)//因为大多数的变量都是全局变量,所以不需要很多参数。这两个参数是刚刚按下的点
1279 void GetALLLine(INT w_or_b)//这个函数应该只处理一个点
//现在看,不用进行8个方向的查询,而是只需要做4个方向的查询即可,比如:1234,剩下的0567用其他的点来检测
//八个方向为上下左右以及其45度角
//8时钟方向,上位0,顺时针,从0 - 7
/*这个方法是有缺陷的,正常方法应该是对每个点都进行遍历,换言之,应该对点使用递归函数*/
//0方向的全部线查找
//这两个变量都设计为数组,是因为8个方向的数据,都存储处理还是可以的
//一种比较节约空间的方法是设置临时变量,存储当前结果,与上一结果相比,这样就不需要8个变量,而仅仅是两个了。
ChessLine * pCL;
INT MaxLength = 0;
POINT MaxStartPos = {0};
POINT MaxEndPos = {0};
//memset(ArrayLength, 0, sizeof(ArrayLength));//嘿,看代码的,你应该知道我这么用是合法的吧?
//这段代码中有一部分代码应该函数化
if(0 == w_or_b)
w_ChessLineBuffer.clear();
b_ChessLineBuffer.clear();
for(int i = 0;i & CHESS_LINE_NUM;++ i)
for(int j = 0;j & CHESS_LINE_NUM; ++ j)
pCL = GetChessMaxSubLine(i, j, w_or_b, FALSE);
if(pCL == NULL)
if(pCL-&length & MaxLength)
MaxLength = pCL-&
MaxStartPos = pCL-&
MaxEndPos = pCL-&
DeleteCL(pCL);
1365 INT GetMaxValCLAddr(ChessLine * parray,
if(parray == NULL && N &= 0)
return -1;//用来表示无效的数字
INT maxval = parray[0].
INT nrtnaddr = 0;
for(int i = 1;i & N;++ i)
if(maxval & parray[i].length)
maxval = parray[i].
nrtnaddr =
1399 ChessLine * GetChessMaxSubLine(INT x, INT y, INT nColor, BOOL IfRtnVal)
INT CheckNum = 4;
POINT StartP
ChessLine JudgeLine[8];
//判断点是否合法
if(nColor != g_ChessTable[x][y].status)
return NULL;//放弃当前点
//当前点合法后,开始8个方向的遍历
StartPos.x =
StartPos.y =
//一旦这个点被选入,初始长度肯定至少是1
ChessLineInitial(JudgeLine, &StartPos, 8, nColor);
JudgeLine[0].endpos = StartP
INT nRight = StartPos.x + 1;
while(nRight & CHESS_LINE_NUM && g_ChessTable[nRight][StartPos.y].status == nColor)
JudgeLine[0].length++;
nRight++;//向右查找
//保存对应的点
JudgeLine[0].endpos.x = nRight - 1;
JudgeLine[0].endpos.y = StartPos.y;
//检测线两端的情况,数据存储在Effectivelevel中
//线左端方向的查找
if(JudgeLine[0].startpos.x - 1 &= 0)//边界判断的前提条件
if(g_ChessTable[JudgeLine[0].startpos.x - 1][JudgeLine[0].startpos.y].status == -1)
JudgeLine[0].EffectLevel ++;
else if(g_ChessTable[JudgeLine[0].startpos.x - 1][JudgeLine[0].startpos.y].status == nColor)
//线点存在重复的线将被抛弃
JudgeLine[0].length = 0;//这样AddIntoBuf函数会自动抛弃该值
//线右端查找
if(JudgeLine[0].endpos.x + 1 & CHESS_LINE_NUM)
if(g_ChessTable[JudgeLine[0].endpos.x + 1][JudgeLine[0].endpos.y].status == -1)
JudgeLine[0].EffectLevel ++;
else if(g_ChessTable[JudgeLine[0].endpos.x + 1][JudgeLine[0].endpos.y].status == nColor)
JudgeLine[0].length = 0;//这样AddIntoBuf函数会自动抛弃该值
if(JudgeLine[0].EffectLevel != 0)
AddIntoBuf(&JudgeLine[0], nColor);
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 == nColor)
JudgeLine[1].length++;
nRightDownOffset++;
//保存对应的点
JudgeLine[1].endpos.x = StartPos.x + nRightDownOffset - 1;
JudgeLine[1].endpos.y = StartPos.y + nRightDownOffset - 1;
//右下和左上方向查找
if(JudgeLine[1].startpos.x - 1 &= 0 && JudgeLine[1].startpos.y - 1 &= 0)
if(g_ChessTable[JudgeLine[1].startpos.x - 1][JudgeLine[1].startpos.y - 1].status == -1)
JudgeLine[1].EffectLevel ++;
else if(g_ChessTable[JudgeLine[1].startpos.x - 1][JudgeLine[1].startpos.y - 1].status == nColor)
JudgeLine[1].length = 0;
if(JudgeLine[1].startpos.x + 1 & CHESS_LINE_NUM && JudgeLine[1].startpos.y + 1 & CHESS_LINE_NUM)
if(g_ChessTable[JudgeLine[1].endpos.x + 1][JudgeLine[1].endpos.y + 1].status == -1)
JudgeLine[1].EffectLevel ++;
else if(g_ChessTable[JudgeLine[1].endpos.x + 1][JudgeLine[1].endpos.y + 1].status == nColor)
JudgeLine[1].length = 0;
if(JudgeLine[1].EffectLevel != 0)
AddIntoBuf(&JudgeLine[1], nColor);
INT nDown = StartPos.y + 1;
while(nDown & CHESS_LINE_NUM && g_ChessTable[StartPos.x][nDown].status == nColor)
JudgeLine[2].length++;
nDown++;//向下查找
//保存对应的点
JudgeLine[2].endpos.x = StartPos.x;
JudgeLine[2].endpos.y = nDown - 1;
//上下两个方向的查找
if(JudgeLine[2].startpos.y - 1 &= 0)
if(g_ChessTable[JudgeLine[2].startpos.x][JudgeLine[2].startpos.y - 1].status == -1)
JudgeLine[2].EffectLevel ++;
else if(g_ChessTable[JudgeLine[2].startpos.x][JudgeLine[2].startpos.y - 1].status == nColor)
JudgeLine[2].length = 0;
if(JudgeLine[2].endpos.y + 1 & CHESS_LINE_NUM)
if(g_ChessTable[JudgeLine[2].endpos.x][JudgeLine[2].endpos.y + 1].status == -1)
JudgeLine[2].EffectLevel ++;
else if(g_ChessTable[JudgeLine[2].endpos.x][JudgeLine[2].endpos.y + 1].status == nColor)
JudgeLine[2].length = 0;
if(JudgeLine[2].EffectLevel != 0)
AddIntoBuf(&JudgeLine[2], nColor);
INT nLeftDownOffset = 1;//左下方向偏移地址,x -;y +
while(StartPos.x - nLeftDownOffset &= 0 && \
StartPos.y + nLeftDownOffset & CHESS_LINE_NUM && \
g_ChessTable[StartPos.x - nLeftDownOffset][StartPos.y + nLeftDownOffset].status == nColor)
JudgeLine[3].length++;
nLeftDownOffset++;
JudgeLine[3].endpos.x = StartPos.x - (nLeftDownOffset - 1);//为了逻辑清楚,就先这么写了
JudgeLine[3].endpos.y = StartPos.y + nLeftDownOffset - 1;
//左下右上方向
if(JudgeLine[3].startpos.y - 1 &= 0 && JudgeLine[3].startpos.x + 1 & CHESS_LINE_NUM)
if(g_ChessTable[JudgeLine[3].startpos.x + 1][JudgeLine[3].startpos.y - 1].status == -1)
JudgeLine[3].EffectLevel ++;
else if(g_ChessTable[JudgeLine[3].startpos.x + 1][JudgeLine[3].startpos.y - 1].status == nColor)
JudgeLine[3].length = 0;
if(JudgeLine[3].endpos.y + 1 & CHESS_LINE_NUM && JudgeLine[3].endpos.x - 1 &= 0)
if(g_ChessTable[JudgeLine[3].endpos.x - 1][JudgeLine[3].endpos.y + 1].status == -1)
JudgeLine[3].EffectLevel ++;
else if(g_ChessTable[JudgeLine[3].endpos.x - 1][JudgeLine[3].endpos.y + 1].status == nColor)
JudgeLine[3].length = 0;
if(JudgeLine[3].EffectLevel != 0)
AddIntoBuf(&JudgeLine[3], nColor);
//这段代码算是暂时废弃的
if(IfRtnVal)
ChessLine * pFinalLine = new ChessL
if(pFinalLine == NULL)
return NULL;
INT MaxLengthAddr = GetMaxValCLAddr(JudgeLine, CheckNum);
if(MaxLengthAddr == -1)
delete pFinalL
return NULL;
*pFinalLine = JudgeLine[MaxLengthAddr];
return pFinalL
return NULL;
1737 void AddIntoBuf(ChessLine * pcl,INT w_or_b)
switch(w_or_b)
case 0://白色
if(pcl-&length & 1)
w_ChessLineBuffer.push_back(*pcl);
if(pcl-&length & 1)
b_ChessLineBuffer.push_back(*pcl);
1765 void ChessLineInitial(ChessLine * pcl, POINT * pstartpos, INT n, INT nColor)
if(pcl == NULL || pstartpos == NULL)
for(int i = 0;i &++ i)
pcl[i].length = 1;
pcl[i].startpos = *
pcl[i].ChessType = nC
pcl[i].EffectLevel = 0;//最低级
1789 void DeleteCL(ChessLine * pcl)
1799 void DrawVecInfo(HDC hdc, std::vector&ChessLine& * pvcl)
TCHAR wcBuf[100];
TCHAR tmpbuf[100];
POINT tmppoint = GetAIPoint();
INT num_w = pvcl-&size();
assert(num_w &= 0);
INT num_b = (pvcl + 1)-&size();
assert(num_b &= 0);
wsprintf(tmpbuf, _T("SP x:%d y:%d;EP x:%d y:%d;Len:%d;EL:%d;HL:%d"),
BestLine.startpos.x,
BestLine.startpos.y,
BestLine.endpos.x,
BestLine.endpos.y,
BestLine.length,
BestLine.EffectLevel,
BestLine.length + BestLine.EffectLevel
TextOut(hdc, 0, 0, tmpbuf, wcslen(tmpbuf));
wsprintf(tmpbuf, _T("AI x:%d y:%d"), tmppoint.x, tmppoint.y);
TextOut(hdc, 900, 0, tmpbuf, wcslen(tmpbuf));
for(int i = 0;i & num_w;++ i)
wsprintf(wcBuf, _T("SP x:%d y:%d;EP x:%d y:%d;Len:%d;EL:%d;HL:%d"),
pvcl[0][i].startpos.x, pvcl[0][i].startpos.y,
pvcl[0][i].endpos.x, pvcl[0][i].endpos.y,
pvcl[0][i].length,
pvcl[0][i].EffectLevel,
pvcl[0][i].length + pvcl[0][i].EffectLevel);
TextOut(hdc, 0, (i+1) * 15, wcBuf, wcslen(wcBuf));
for(int i = 0;i & num_b;++ i)
wsprintf(wcBuf, _T("SP x:%d y:%d;EP x:%d y:%d;Len:%d;EL:%d;HL:%d"),
pvcl[1][i].startpos.x, pvcl[1][i].startpos.y,
pvcl[1][i].endpos.x, pvcl[1][i].endpos.y,
pvcl[1][i].length,
pvcl[1][i].EffectLevel,
pvcl[1][i].length + pvcl[1][i].EffectLevel);
TextOut(hdc, 900, (i+1) * 15, wcBuf, wcslen(wcBuf));
1885 ChessLine * GetBestLine(INT nColor)
ChessLine * pcl = new ChessL
if(pcl == NULL)
return NULL;
std::vector&ChessLine& *
if(nColor == 0)
pvcl = &w_ChessLineB
pvcl = &b_ChessLineB
INT nsize = pvcl-&size();
if(nsize == 0)
return NULL;
//删除没用的线
//线还是先不删了,擅自修改vector的大小会引发大量的越界问题
std::vector&ChessLine&::iterator pvcl_itstart = pvcl-&begin();
std::vector&ChessLine&::iterator pvcl_itend = pvcl-&end();
for(int i = 0;i &)
if(pvcl_itstart[i].EffectLevel == 0)
pvcl-&erase(pvcl_itstart + i);
//然后使用优先级判断公式 length + EffectLevel
//先获取最大值
INT num_cl = pvcl-&size();
if(num_cl == 0)
return NULL;
INT nMax = 1;
INT nMaxAddr = 0;
for(int i = 0;i & num_++ i)
if((*pvcl)[i].EffectLevel + (*pvcl)[i].length & nMax && (*pvcl)[i].EffectLevel != 0)
nMax = (*pvcl)[i].EffectLevel + (*pvcl)[i].
nMaxAddr =
*pcl = (*pvcl)[nMaxAddr];
1983 POINT GetAIPoint()//根据GetBestLine返回的黑白两棋子线情况来判断棋子的位置
//先获取全部的线。
GetALLLine(0);
GetALLLine(1);
//这里曾造成内存泄露,原因是返回路径会切断删除函数的调用
ChessLine * pw_cl = GetBestLine(0);//白子 人方
ChessLine * pb_cl = GetBestLine(1);//黑子 AI
ChessLine * pfinal_
POINT rtnpos = {-1, -1};
if(pw_cl != NULL && pb_cl != NULL)
//防守优先
if(pw_cl-&EffectLevel + pw_cl-&length &= pb_cl-&EffectLevel + pb_cl-&length)
pfinal_cl = pw_
pfinal_cl = pb_
else if(pw_cl == NULL && pb_cl != NULL)
pfinal_cl = pb_
else if(pb_cl == NULL && pw_cl != NULL)
pfinal_cl = pw_
else //在上面的两个ChessLine都获取不到的时候,需要做的是,尝试去获取一个单独的点。
POINT SingleFinalPoint = GetSinglePoint();
return SingleFinalP
//这个是测试用数据,全局变量
BestLine = *pfinal_
switch(GetValidSEDirection(pfinal_cl-&startpos, pfinal_cl-&endpos))
case 0://2-6
if(g_ChessTable[pfinal_cl-&startpos.x - 1][pfinal_cl-&startpos.y].status == -1
&& pfinal_cl-&startpos.x - 1 &= 0)
rtnpos.x = pfinal_cl-&startpos.x - 1;
rtnpos.y = pfinal_cl-&startpos.y;
else if(pfinal_cl-&endpos.x + 1 & CHESS_LINE_NUM)
rtnpos.x = pfinal_cl-&endpos.x + 1;
rtnpos.y = pfinal_cl-&endpos.y;
case 1://3-7
if(g_ChessTable[pfinal_cl-&startpos.x - 1][pfinal_cl-&startpos.y - 1].status == -1)
rtnpos.x = pfinal_cl-&startpos.x - 1;
rtnpos.y = pfinal_cl-&startpos.y - 1;
rtnpos.x = pfinal_cl-&endpos.x + 1;
rtnpos.y = pfinal_cl-&endpos.y + 1;
case 2://4-0
if(g_ChessTable[pfinal_cl-&startpos.x][pfinal_cl-&startpos.y - 1].status == -1
&& pfinal_cl-&startpos.y - 1&= 0
&& pfinal_cl-&endpos.y + 1 & CHESS_LINE_NUM)
rtnpos.x = pfinal_cl-&startpos.x;
rtnpos.y = pfinal_cl-&startpos.y - 1;
rtnpos.x = pfinal_cl-&endpos.x;
rtnpos.y = pfinal_cl-&endpos.y + 1;
case 3://5-1
if(g_ChessTable[pfinal_cl-&startpos.x + 1][pfinal_cl-&startpos.y - 1].status == -1
&& pfinal_cl-&startpos.x + 1 & CHESS_LINE_NUM
&& pfinal_cl-&startpos.y - 1 &= 0)
rtnpos.x = pfinal_cl-&startpos.x + 1;
rtnpos.y = pfinal_cl-&startpos.y - 1;
rtnpos.x = pfinal_cl-&endpos.x - 1;
rtnpos.y = pfinal_cl-&endpos.y + 1;
DeleteCL(pw_cl);
DeleteCL(pb_cl);
2171 INT GetValidSEDirection(POINT SP, POINT EP)//获取有效的方向,返回值 0,1,2,3分别对应2-6, 3-7, 4-0,5-1,
//终点减去起始点
INT ndirx = EP.x - SP.x;
INT ndiry = EP.y - SP.y;
0(0,1) 1(1,1)
5(-1,-1)4(0,-1)
if(ndirx & 0)
if(ndiry == 0)//2(1,0)
else//3(1,-1)
else if(ndirx == 0)
2215 POINT GetSinglePoint()
//所谓singlepoint,就是8个相邻点中没有任何一点是同色点。
//函数返回值为从0-7中的有效点中的一个随机点
POINT rtnpoint = {-1, -1};
for(int i = 0;i & CHESS_LINE_NUM;++ i)
for(int j = 0;j & CHESS_LINE_NUM;++ j)
if(g_ChessTable[i][j].status != -1)
npos = IsValidSinglePoint(i, j);
if(npos == -1)
switch(npos)
//这里的代码直接return,就不用再break了
rtnpoint.x = i - 1;
rtnpoint.y = j - 1;
rtnpoint.x =
rtnpoint.y = j - 1;
rtnpoint.x = i + 1;
rtnpoint.y = j - 1;
rtnpoint.x = i - 1;
rtnpoint.y =
rtnpoint.x = i + 1;
rtnpoint.y =
rtnpoint.x = i - 1;
rtnpoint.y = j + 1;
rtnpoint.x =
rtnpoint.y = j + 1;
rtnpoint.x = i + 1;
rtnpoint.y = j + 1;
2331 INT IsValidSinglePoint(int x, int y)
assert(x &= 0 && y &=0 && x & CHESS_LINE_NUM && y & CHESS_LINE_NUM);
char checkflag[8] = {0};//纯标记位
if(x - 1 &= 0)//一次查三个点
if(y - 1 &= 0)
if(g_ChessTable[x - 1][y - 1].status == -1)
checkflag[0] = 1;
if(g_ChessTable[x - 1][y].status == -1)
checkflag[3] = 1;
if(y + 1 & CHESS_LINE_NUM)
if(g_ChessTable[x - 1][y + 1].status == -1)
checkflag[5] = 1;
if(y - 1 &= 0 && g_ChessTable[x][y - 1].status == -1)
checkflag[1] = 1;
if(y + 1 & CHESS_LINE_NUM && g_ChessTable[x][y + 1].status == -1)
checkflag[6] = 1;
if(x + 1 & CHESS_LINE_NUM)
if(g_ChessTable[x + 1][y].status == -1)
checkflag[4] = 1;
if(y + 1 & CHESS_LINE_NUM)
if(g_ChessTable[x + 1][y + 1].status == -1)
checkflag[7] = 1;
if(y - 1 &= 0)
if(g_ChessTable[x + 1][y - 1].status == -1)
checkflag[2] = 1;
/*调试部分
INT nrtn = 0;
for(int i = 0;i & 8;++ i)
if(checkflag[i] == 1)
nrtn |= 1 && (i * 4);
INT nCounterofValidPoint = 0;
for(int i = 0;i & 8;++ i)
if(checkflag[i] == 1)
nCounterofValidPoint ++;
if(!nCounterofValidPoint)
return -1;
srand(time(0));
INT nUpSection = rand() % 8;//在这倒是正好能用作地址上限
INT UpSearch =
INT DownSearch = nUpS
for(;UpSearch & 8 || DownSearch &= 0;)
if(UpSearch & 8)
if(checkflag[UpSearch] == 1)
return UpS
UpSearch ++;
if(DownSearch &= 0)
if(checkflag[DownSearch] == 1)
return DownS
DownSearch --;
阅读(...) 评论()}

我要回帖

更多关于 五子棋必赢 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信