完全自制的五子棋人机对战游戏(VC++实现)
五子棋工作文档
1说明:
这个程序在创建初期的时候是有一个写的比较乱的文档的,但是很可惜回学校的时候没有带回来……所以现在赶紧整理一下,不然再过一段时间就忘干净了。
最初这个程序是受老同学所托做的,一开始的时候要求要人人对战和人机对战,但是大家都很明白,所谓的人人对战就是简单那的GDI绘图罢了,那些基础函数用好了自然没问题。而人机对战则需要一定的棋盘分析能力,做起来还是很复杂的。当时受时间限制,第一个版本是我用了两天时间做的一个人人对战,直接就给她发过去了,用来应付她的实习,因为我当时也不确定人机对战能不能做出来。不过之后我一直在做,毕竟之前没做过,算是一次尝试。之后貌似过了9天吧,才完成了核心函数:GetAIPoint。用这么长时间一个是因为没做过另外当时在家里还要帮家里干活,刨去干活加上打游戏的时间,平均下来每天的编码时间不到3个小时。不过去我还是用了不少的时间来思考棋盘的分析的。走了不少弯路,吸取了不少教训,感觉收获还是挺大的。但是比较悲剧的是,我后来发现这个程序有内存泄露问题,问题貌似处在DrawChess函数里,因为无棋子的重绘并不会增加内存总量,看官若有兴趣就帮我找找看吧,我是没找到到底哪里出了问题……
程序运行截图演示:


2程序主要数据结构以及函数:
//使用结构体有利于以后的数据扩展
/*
status 参数是用来表示当前这个点的状态的,0表示白子,1表示黑子 -1表示尚无子
后两个参数是用来追踪前一个点的,用于悔棋
*/
typedef struct
{
INT status;
//悔棋 专用
INT PrePointx;
INT PrePointy;
INT nVisit_flag;
}Chess;
typedef struct
{
POINT startpos;//起始地点
POINT endpos;//终止地点
INT length;//长度
INT ChessType;//黑白子的辨别
INT EffectLevel;//棋子线的影响力,这个值的优先级判定应该和长度相关联进行判断,可以考虑通过使用一个公式来计算
}ChessLine;
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
INT g_nbase_x = ;
INT g_nbase_y = ;
Chess g_ChessTable[CHESS_LINE_NUM][CHESS_LINE_NUM];//作为全局变量的数据表
BOOL w_b_turn = ;//下棋顺序的控制变量
INT nxPosForChessTable = -;//悔棋专用
INT nyPosForChessTable = -;//悔棋专用
INT nRestart_Flag;//默认初始化的值为0,应该是重启游戏的标志位
ChessLine BestLine;//白黑的最长有效线即可
INT DrawMode = ;//0常规模式 1调试模式
INT PlayMode = ;//游戏模式,分为人人对战0和人机对战1
//使用vector等模板时,还需要注意命名空间的问题
std::vector<ChessLine> w_ChessLineBuffer;//这个变量用于存储所有的棋子线,白色
std::vector<ChessLine> b_ChessLineBuffer;//黑色 void DrawTable(HDC hdc, int base_x = , int base_y = );
void WinRectConvert(RECT * rect);
void DrawChess(HDC hdc, int x, int y, int w_or_b = );//0为白子,1为黑子
void GlobalInitial();//全局初始化函数
void DrwaChessOnTable(HDC hdc);
INT IsWin(int x, int y);
INT TellWhoWin(HWND hWnd, INT n, RECT * rect);
void BkBitmap(HDC hdc, RECT * rect);
void DrawInfo(HDC hdc, ChessLine * cl, INT length);
void GetALLLine(INT w_or_b);//根据棋盘全局信息来获取对应颜色的最大长度线
INT GetMaxValCLAddr(ChessLine * parray, INT N);//返回最大值的数字地址
ChessLine * GetChessMaxSubLine(INT x, INT y, INT nColor, BOOL IfRtnVal);//获取单个点的最长线函数
void AddIntoBuf(ChessLine * pcl,INT w_or_b);
void ChessLineInitial(ChessLine * pcl, POINT * pstartpos, INT n, INT nColor);
inline void DeleteCL(ChessLine * pcl);
void DrawVecInfo(HDC hdc, std::vector<ChessLine> * pvcl);
ChessLine * GetBestLine(INT nColor);
INT GetValidSEDirection(POINT SP, POINT EP);//获取有效的方向,返回值 0,1,2,3分别对应2-6, 3-7, 4-0,5-1,
POINT GetAIPoint();//根据GetBestLine返回的黑白两棋子线情况来判断棋子的位置
POINT GetSinglePoint();
INT IsValidSinglePoint(int x, int y);
可以看到,好多好多的函数~我现在也觉得有点头晕,不过这样就更有必要对这个程序进行整理了。
3 程序调用层析如下:
绘图消息:

鼠标左键消息:

刚才意外的发现了我在家的时候做的那个文档,虽然比较乱,但是还是较好的体现了一部分我的设计思想历程,等会贴在最后面的附录中。
上面的函数层次图主要还是为了表明函数之间的调用关系,这样便于理清之间的功能关系。另外我发现在设计数据类型时使用结构体或者类是一个非常好的选择,在数据扩充上有很大的优势,我在这个工程中就因此受益。
4 AI部分核心函数设计部分思想
首先就是如何看待棋盘上的棋子,如何根据棋盘上的棋子来分析出一个比较合适点作为下一步的选择。
我的程序中棋盘的大小是15*15的,这个还是那个同学和我说的,最初我设计的是19*19的,因为家里的显示器分辨率比较高,放得下。X轴和Y轴各15条线,棋盘的棋子可以简单的通过一个15*15的结构数组来表示,每个结构表示棋子的状态。这个结构如下:
/*
status 参数是用来表示当前这个点的状态的,0表示白子,1表示黑子 -1表示尚无子
后两个参数是用来追踪前一个点的,用于悔棋
*/
typedef struct
{
INT status;
//悔棋 专用
INT PrePointx;
INT PrePointy;
INT nVisit_flag;
}Chess;
上面的这个结构中,status的作用就不多说了,后面的参数用处还是挺有意思的。PrePoint如其意思一样,就是之前的那个点。第一个点的这个参数的值均为-1,用来标识无效点。而第1个点之后的所有的点这个参数的值均为前一点的坐标值。我觉得我的这个设计除了有点浪费内存,别的嘛,效率和效果还都是挺不错的。这样只要根据这两个值,就能按照原路找回之前的点,从而实现悔棋以及其一套连续的操作。
最后一个参数是废弃的,之前想通过每个点做一个标记来实现点的方向的记录,不过后来的代码实现表明这个是比较困难的,有更好的方法来实现,也就是我的程序中现在所使用的方法。
typedef struct
{
POINT startpos;//起始地点
POINT endpos;//终止地点
INT length;//长度
INT ChessType;//黑白子的辨别
INT EffectLevel;//棋子线的影响力,这个值的优先级判定应该和长度相关联进行判断,可以考虑通过使用一个公式来计算
}ChessLine;
上面的这个结构是用来表示棋子线的结构,其组成包括:起点和终点,长度,棋子的颜色,以及棋子线两端是否有效。
而棋子线的优先级也可以通过一个很简单的公式计算出来,即优先级为length + EffectLevel的结果来表示。需要注意的是,EffectLevel的值是不会等于0的,这个在线检查函数中专门进行了处理,因为EffectLeve==0,意味着这条线是一条废线,两端都被堵死了,直接抛弃。
由上面的两个结构你可以初步了解我对于整个棋盘上信息的抽象方法。
5 人人对战的核心函数(IsWin)
在进行人人对战的时候,核心函数其实就是要对棋盘上的棋子进行分析,判断是否存在已经大于或等于长度为5的棋子线。
棋盘上每一个点,都可以分为4个方向,或者8个小方向。
最简单的想法就是对棋盘上每一个点都进行计算,如果存在这样一个点,就获取其颜色,然后返回就可以了,由此即可判断出谁赢了。但是仔细想想,这样完全没必要,因为能赢与否,与刚下的点是必然有联系的。所以在进行检测的时候,只需要检测当前刚刚下的这个点就足够了。想明白了没?这样一来,效率非常高,完全避免了无谓的操作。
IsWin函数的参数是棋盘上的坐标,然后通过坐标值访问全局变量棋盘二维数组,做四个方向的检查,从-4到+4的坐标偏移。针对越界情况专门进行了处理。但是不排除存在bug。
人人对战的核心函数就这样,没别的。在AI模式下,这个函数依旧能够用来判断输赢结果。
6 人机对战核心函数POINT GetAIPoint();
根据上面的函数层次图,能够知道在这个函数中调用了4个重要的功能函数,先看下GetAiPoint函数的部分源代码:
POINT GetAIPoint()//根据GetBestLine返回的黑白两棋子线情况来判断棋子的位置
{
//先获取全部的线。
GetALLLine();
GetALLLine();
//这里曾造成内存泄露,原因是返回路径会切断删除函数的调用
ChessLine * pw_cl = GetBestLine();//白子 人方
ChessLine * pb_cl = GetBestLine();//黑子 AI
ChessLine * pfinal_cl;
POINT rtnpos = {-, -};
if(pw_cl != NULL && pb_cl != NULL)
{
//防守优先
if(pw_cl->EffectLevel + pw_cl->length >= pb_cl->EffectLevel + pb_cl->length)
pfinal_cl = pw_cl;
else
pfinal_cl = pb_cl;
}
else if(pw_cl == NULL && pb_cl != NULL)
pfinal_cl = pb_cl;
else if(pb_cl == NULL && pw_cl != NULL)
pfinal_cl = pw_cl;
else //在上面的两个ChessLine都获取不到的时候,需要做的是,尝试去获取一个单独的点。
{
POINT SingleFinalPoint = GetSinglePoint();
return SingleFinalPoint;
}
最先调用的函数是GetAllLine函数。这个函数的功能是查找全部的有效的线并将其添加到棋子线的vector容器中。参数是棋子颜色,0表示白色,1表示黑色。
看下这个函数的代码:
void GetALLLine(INT w_or_b)//这个函数应该只处理一个点
{
//现在看,不用进行8个方向的查询,而是只需要做4个方向的查询即可,比如:1234,剩下的0567用其他的点来检测
//八个方向为上下左右以及其45度角
//8时钟方向,上位0,顺时针,从0 - 7
/*
7 0 1
6 2
5 4 3
*/
//这两个变量都设计为数组,是因为8个方向的数据,都存储处理还是可以的
//一种比较节约空间的方法是设置临时变量,存储当前结果,与上一结果相比,这样就不需要8个变量,而仅仅是两个了。
ChessLine * pCL;
INT MaxLength = ;
POINT MaxStartPos = {};
POINT MaxEndPos = {};
//memset(ArrayLength, 0, sizeof(ArrayLength));//嘿,看代码的,你应该知道我这么用是合法的吧?
//这段代码中有一部分代码应该函数化
if( == w_or_b)
w_ChessLineBuffer.clear();
else
b_ChessLineBuffer.clear();
for(int i = ;i < CHESS_LINE_NUM;++ i)
{
for(int j = ;j < CHESS_LINE_NUM; ++ j)
{
pCL = GetChessMaxSubLine(i, j, w_or_b, FALSE);
if(pCL == NULL)
continue;
if(pCL->length > MaxLength)
{
MaxLength = pCL->length;
MaxStartPos = pCL->startpos;
MaxEndPos = pCL->endpos;
}
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函数:
POINT GetSinglePoint()
{
//所谓singlepoint,就是8个相邻点中没有任何一点是同色点。
//函数返回值为从0-7中的有效点中的一个随机点
INT npos;
POINT rtnpoint = {-, -};
for(int i = ;i < CHESS_LINE_NUM;++ i)
{
for(int j = ;j < CHESS_LINE_NUM;++ j)
{
if(g_ChessTable[i][j].status != -)
{
npos = IsValidSinglePoint(i, j);
if(npos == -)
continue;
switch(npos)
{
//这里的代码直接return,就不用再break了
case :
rtnpoint.x = i - ;
rtnpoint.y = j - ;
break;
case :
rtnpoint.x = i;
rtnpoint.y = j - ;
break;
case :
rtnpoint.x = i + ;
rtnpoint.y = j - ;
break;
case :
rtnpoint.x = i - ;
rtnpoint.y = j;
break;
case :
rtnpoint.x = i + ;
rtnpoint.y = j;
break;
case :
rtnpoint.x = i - ;
rtnpoint.y = j + ;
break;
case :
rtnpoint.x = i;
rtnpoint.y = j + ;
break;
case :
rtnpoint.x = i + ;
rtnpoint.y = j + ;
break;
}
return rtnpoint;
}
}
}
return rtnpoint;
}
从中还能发现又调用了一个函数:IsValidSinglePoint。如果点合法,会返回一个随机的方向值,0-7,即8个小方向。若非法,则返回-1。
接下来再看这个函数实现:
INT IsValidSinglePoint(int x, int y)
{
assert(x >= && y >= && x < CHESS_LINE_NUM && y < CHESS_LINE_NUM);
char checkflag[] = {};//纯标记位
if(x - >= )//一次查三个点
{
if(y - >= )
{
if(g_ChessTable[x - ][y - ].status == -)
checkflag[] = ;
}
if(g_ChessTable[x - ][y].status == -)
checkflag[] = ;
if(y + < CHESS_LINE_NUM)
{
if(g_ChessTable[x - ][y + ].status == -)
checkflag[] = ;
}
}
if(y - >= && g_ChessTable[x][y - ].status == -)
checkflag[] = ;
if(y + < CHESS_LINE_NUM && g_ChessTable[x][y + ].status == -) {
checkflag[] = ;
}
if(x + < CHESS_LINE_NUM)
{
if(g_ChessTable[x + ][y].status == -)
checkflag[] = ;
if(y + < CHESS_LINE_NUM)
{
if(g_ChessTable[x + ][y + ].status == -)
checkflag[] = ;
}
if(y - >= )
{
if(g_ChessTable[x + ][y - ].status == -)
checkflag[] = ;
}
}
/*调试部分
INT nrtn = 0;
for(int i = 0;i < 8;++ i)
{
if(checkflag[i] == 1)
{
nrtn |= 1 << (i * 4);
}
}*/
INT nCounterofValidPoint = ;
for(int i = ;i < ;++ i)
{
if(checkflag[i] == )
nCounterofValidPoint ++;
}
if(!nCounterofValidPoint)
return -;
srand(time());
INT nUpSection = rand() % ;//在这倒是正好能用作地址上限
INT UpSearch = nUpSection;
INT DownSearch = nUpSection;
for(;UpSearch < || DownSearch >= ;)
{
if(UpSearch < )
{
if(checkflag[UpSearch] == )
return UpSearch;
else
UpSearch ++;
}
if(DownSearch >= )
{
if(checkflag[DownSearch] == )
return DownSearch;
else
DownSearch --;
}
}
}
看起来一个功能简单的函数,其实要做的操作还是不少的。因为除了要将合法的点对号入座,还要以随机的形式取出来,代码并不是很简单。
由此,整个工程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程序代码
// WuZiQi20130716.cpp : Defines the entry point for the application.
//
/*
曲敬原创建于2013年07月16日
*/
#include "stdafx.h"
#include "WuZiQi20130716.h"
#include <vector>//没辙,容器还是C++好用,纯SDK程序算是破产了
#include <assert.h>
#include <ctime>
#include <cstdlib>
#define MAX_LOADSTRING 100
#define TABLE_SQUARE_LENGTH 35
#define CHESS_LENGTH 30
#define CHESS_LINE_NUM 15
// Global Variables:
HINSTANCE hInst; // current instance
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
//使用结构体有利于以后的数据扩展
/*
status 参数是用来表示当前这个点的状态的,0表示白子,1表示黑子 -1表示尚无子
后两个参数是用来追踪前一个点的,用于悔棋
*/
typedef struct
{
INT status;
//悔棋 专用
INT PrePointx;
INT PrePointy;
INT nVisit_flag;
}Chess;
typedef struct
{
POINT startpos;//起始地点
POINT endpos;//终止地点
INT length;//长度
INT ChessType;//黑白子的辨别
INT EffectLevel;//棋子线的影响力,这个值的优先级判定应该和长度相关联进行判断,可以考虑通过使用一个公式来计算
}ChessLine;
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
INT g_nbase_x = ;
INT g_nbase_y = ;
Chess g_ChessTable[CHESS_LINE_NUM][CHESS_LINE_NUM];//作为全局变量的数据表
BOOL w_b_turn = ;//下棋顺序的控制变量
INT nxPosForChessTable = -;//悔棋专用
INT nyPosForChessTable = -;//悔棋专用
INT nRestart_Flag;//默认初始化的值为0,应该是重启游戏的标志位
ChessLine BestLine;//白黑的最长有效线即可
INT DrawMode = ;//0常规模式 1调试模式
INT PlayMode = ;//游戏模式,分为人人对战0和人机对战1
//使用vector等模板时,还需要注意命名空间的问题
std::vector<ChessLine> w_ChessLineBuffer;//这个变量用于存储所有的棋子线,白色
std::vector<ChessLine> b_ChessLineBuffer;//黑色
void DrawTable(HDC hdc, int base_x = , int base_y = );
void WinRectConvert(RECT * rect);
void DrawChess(HDC hdc, int x, int y, int w_or_b = );//0为白子,1为黑子
void GlobalInitial();//全局初始化函数
void DrwaChessOnTable(HDC hdc);
INT IsWin(int x, int y);
INT TellWhoWin(HWND hWnd, INT n, RECT * rect);
void BkBitmap(HDC hdc, RECT * rect);
void DrawInfo(HDC hdc, ChessLine * cl, INT length);
void GetALLLine(INT w_or_b);//根据棋盘全局信息来获取对应颜色的最大长度线
INT GetMaxValCLAddr(ChessLine * parray, INT N);//返回最大值的数字地址
ChessLine * GetChessMaxSubLine(INT x, INT y, INT nColor, BOOL IfRtnVal);//获取单个点的最长线函数
void AddIntoBuf(ChessLine * pcl,INT w_or_b);
void ChessLineInitial(ChessLine * pcl, POINT * pstartpos, INT n, INT nColor);
inline void DeleteCL(ChessLine * pcl);
void DrawVecInfo(HDC hdc, std::vector<ChessLine> * pvcl);
ChessLine * GetBestLine(INT nColor);
INT GetValidSEDirection(POINT SP, POINT EP);//获取有效的方向,返回值 0,1,2,3分别对应2-6, 3-7, 4-0,5-1,
POINT GetAIPoint();//根据GetBestLine返回的黑白两棋子线情况来判断棋子的位置
POINT GetSinglePoint();
INT IsValidSinglePoint(int x, int y);
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: Place code here.
MSG msg;
HACCEL hAccelTable;
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WUZIQI20130716, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WUZIQI20130716));
// Main message loop:
while (GetMessage(&msg, NULL, , ))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
// COMMENTS:
//
// 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
// with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = ;
wcex.cbWndExtra = ;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WUZIQI20130716));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WUZIQI20130716);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, , CW_USEDEFAULT, , NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
RECT winrect;
INT nlxPos;
INT nlyPos;
INT nDBPosx;
INT nDBPosy;
INT IORtmpx;//给IDM_OPTION_REGRET消息用的
INT IORtmpy;
POINT AIPoint;
HMENU SubMenu;
switch (message)
{
case WM_CREATE:
GlobalInitial();
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
case IDM_OPTION_REGRET:
//这一步是专门给悔棋用的
//根据当前节点的指向,进行退解
if(nxPosForChessTable < || nyPosForChessTable < )
break;
//下面这段代码还挺好使的
IORtmpx = nxPosForChessTable;
IORtmpy = nyPosForChessTable;
g_ChessTable[IORtmpx][IORtmpy].status = -;
nxPosForChessTable = g_ChessTable[IORtmpx][IORtmpy].PrePointx;
nyPosForChessTable = g_ChessTable[IORtmpx][IORtmpy].PrePointy;
//清理工作
g_ChessTable[IORtmpx][IORtmpy].PrePointx = -;
g_ChessTable[IORtmpx][IORtmpy].PrePointy = -;
(++w_b_turn) %= ;//再次变成0或1
InvalidateRect(hWnd, NULL, TRUE);
break;
case ID_OPTION_PLAYMODE:
(++ PlayMode) %= ;
SubMenu= GetSubMenu(GetMenu(hWnd), );
if(PlayMode == )
CheckMenuItem(SubMenu, , MF_CHECKED|MF_BYPOSITION);
else
CheckMenuItem(SubMenu, , MF_UNCHECKED|MF_BYPOSITION);
GlobalInitial();
InvalidateRect(hWnd, NULL, TRUE);
break;
case ID_OPTION_AIWATCH:
SubMenu= GetSubMenu(GetMenu(hWnd), );
(++ DrawMode) %= ;
if(DrawMode == )
CheckMenuItem(SubMenu, , MF_CHECKED|MF_BYPOSITION);
else
CheckMenuItem(SubMenu, , MF_UNCHECKED|MF_BYPOSITION);
InvalidateRect(hWnd, NULL, TRUE);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
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);
break;
case WM_ERASEBKGND:
//这块代码就是为了进行消息拦截,因为我并不需要把屏幕背景重新刷一遍,那样会导致闪屏
break;
case WM_DESTROY:
PostQuitMessage();
break;
case WM_LBUTTONDOWN:
nlxPos = LOWORD(lParam) - g_nbase_x;
nlyPos = HIWORD(lParam) - g_nbase_y;
//部分初始化
GetWindowRect(hWnd, &winrect);
WinRectConvert(&winrect);
//做完了减法,一定要判断结果是否依旧大于0;
if(nlxPos <= || nlyPos <= )
return ;
//这两个除法主要是获取左上角的坐标,用来转换到棋盘数据对应的地址,同时下棋
nDBPosx = nlxPos / TABLE_SQUARE_LENGTH;
nDBPosy = nlyPos / TABLE_SQUARE_LENGTH;
if(nDBPosx >= CHESS_LINE_NUM || nDBPosy >= CHESS_LINE_NUM)
return ;
//坐标判定有效之后,还需要对当前点的数据否有效进行检测
if(g_ChessTable[nDBPosx][nDBPosy].status != -)
return ;
else
{
g_ChessTable[nDBPosx][nDBPosy].status = w_b_turn;
g_ChessTable[nDBPosx][nDBPosy].PrePointx = nxPosForChessTable;
g_ChessTable[nDBPosx][nDBPosy].PrePointy = nyPosForChessTable;
//复制完成后,再更新前点坐标
nxPosForChessTable = nDBPosx;
nyPosForChessTable = nDBPosy;
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 != - && AIPoint.y != -)
g_ChessTable[AIPoint.x][AIPoint.y].status = ((++w_b_turn) %= );//顺便执行了
g_ChessTable[AIPoint.x][AIPoint.y].PrePointx = nxPosForChessTable;
g_ChessTable[AIPoint.x][AIPoint.y].PrePointy = nyPosForChessTable;
//前点坐标更新
nxPosForChessTable = AIPoint.x;
nyPosForChessTable = AIPoint.y;
if(DrawMode == )
DrawChess(GetDC(hWnd), AIPoint.x * TABLE_SQUARE_LENGTH + g_nbase_x, AIPoint.y * TABLE_SQUARE_LENGTH + g_nbase_y, w_b_turn);
else
InvalidateRect(hWnd, NULL, TRUE);
TellWhoWin(hWnd, IsWin(AIPoint.x, AIPoint.y), &winrect);
}
//绘图部分
(++w_b_turn) %= ;//再次变成0或1;
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return ;
}
// Message handler for about box.
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;
}
break;
}
return (INT_PTR)FALSE;
}
void DrawTable(HDC hdc, int base_x, int base_y)
{
int nsquarelength = TABLE_SQUARE_LENGTH;
int nTableOffset = TABLE_SQUARE_LENGTH / ;
//画竖表格
for(int i = ;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 - ) * nsquarelength + base_y + nTableOffset);
}
//画横表格
for(int i = ;i < CHESS_LINE_NUM;++ i)
{
MoveToEx(hdc, base_x + nTableOffset, i * nsquarelength + base_y + nTableOffset, NULL);
LineTo(hdc, (CHESS_LINE_NUM - ) * nsquarelength + base_x + nTableOffset, i * nsquarelength + base_y + nTableOffset);
}
}
void DrwaChessOnTable(HDC hdc)
{
for(int i = ;i < CHESS_LINE_NUM;++ i)
for(int j = ;j < CHESS_LINE_NUM;++ j)
{
if(g_ChessTable[i][j].status != -)
{
DrawChess(hdc, i * TABLE_SQUARE_LENGTH + g_nbase_x, j * TABLE_SQUARE_LENGTH + g_nbase_y, g_ChessTable[i][j].status);
}
}
}
void DrawChess(HDC hdc, int x, int y, int w_or_b)//0为白子,1为黑子
{
DWORD chesscolor;
if(w_or_b == )
{
chesscolor = RGB(, , );//灰色,因为棋盘颜色背景还未选好
}
else
{
chesscolor = RGB(, , );
}
HBRUSH ChessBrush = CreateSolidBrush(chesscolor);
HBRUSH OldBrush = (HBRUSH)SelectObject(hdc, ChessBrush);
//下面这两行的+2是根据效果手动确定的,效果还不错。
Ellipse(hdc, x + , y + , x + CHESS_LENGTH, y + CHESS_LENGTH);
ChessBrush = (HBRUSH)SelectObject(hdc, OldBrush);
assert(DeleteObject(ChessBrush) != );
}
void WinRectConvert(RECT * rect)
{
rect->bottom -= rect->top;
rect->right -= rect->left;
rect->left = ;
rect->top = ;
}
void GlobalInitial()
{
//初始化19*19的结构数组
for(int i = ;i < CHESS_LINE_NUM;++ i)
for(int j = ;j < CHESS_LINE_NUM;++ j)
{
g_ChessTable[i][j].status = -;
//因为0 0 这个点是有效的坐标,因此初始化为-1用来表示无效点
g_ChessTable[i][j].PrePointx = -;
g_ChessTable[i][j].PrePointy = -;
g_ChessTable[i][j].nVisit_flag = ;//该参数表明此节点节点是否已经访问过。0未访问 1访问
}
w_ChessLineBuffer.clear();
b_ChessLineBuffer.clear();
;
}
INT IsWin(int x, int y)
{
//这个逻辑要仔细的想一下
//首先 在这段代码里 我很想说 如果每次都是对整个棋盘进行检查,实在是太笨了。
//毕竟每次要做的,仅仅是检查当前这点关联单位是否满足条件,而且,只关心上一点的颜色即可
int nTheColor = w_b_turn;
int CheckCounter = ;
//行检查
int xStartPos;
if(x - >= )
xStartPos = x - ;
else
xStartPos = ;
int xEndPos;
if(x + < CHESS_LINE_NUM)
xEndPos = x + ;
else
xEndPos = (CHESS_LINE_NUM - );
CheckCounter = ;
for(int i = xStartPos;i <= xEndPos;++ i)
{
if(g_ChessTable[i][y].status == nTheColor)
{
CheckCounter++;
if(CheckCounter >= )
{
CheckCounter = ;
return nTheColor;
}
}
else
{
CheckCounter = ;
}
}
//列检查
int yStartPos;
if(y - >= )
yStartPos = y - ;
else
yStartPos = ;
int yEndPos;
if(y + < CHESS_LINE_NUM)
yEndPos = y + ;
else
yEndPos = (CHESS_LINE_NUM - );
CheckCounter = ;
for(int i = yStartPos;i <= yEndPos;++ i)
{
if(g_ChessTable[x][i].status == nTheColor)
{
CheckCounter++;
if(CheckCounter >= )
{
CheckCounter = ;
return nTheColor;
}
}
else
{
CheckCounter = ;
}
}
//左上角到右下角检查
CheckCounter = ;
for(int i = -;i <= ;++ i)
{
if(x + i < || y + i < || x + i >= CHESS_LINE_NUM || y + i >= CHESS_LINE_NUM)
{
continue;
}
else
{
if(g_ChessTable[x + i][y + i].status == nTheColor)
{
CheckCounter ++;
if(CheckCounter >= )
{
CheckCounter = ;
return nTheColor;
}
}
else
{
CheckCounter = ;
}
}
}
//右上角到左下角检查
CheckCounter = ;
for(int i = -;i <= ;++ i)
{
if(x - i < || y + i < || x - i >= CHESS_LINE_NUM || y + i >= CHESS_LINE_NUM)
{
continue;
}
else
{
if(g_ChessTable[x - i][y + i].status == nTheColor)
{
CheckCounter ++;
if(CheckCounter >= )
{
CheckCounter = ;
return nTheColor;
}
}
else
{
CheckCounter = ;
}
}
}
return -;
}
INT TellWhoWin(HWND hWnd, INT n, RECT * rect)
{
//SetBkMode(hdc, TRANSPARENT);这个透明参数,想了想 还是算了,背景不透明更好一点。
/*把这段代码注释掉的原因是因为目前画面做的还不够好,这样还不如直接使用messagebox函数
rect->top += rect->bottom / 2;
LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lf.lfHeight = 50;
HFONT hfont = CreateFontIndirect(&lf);
HFONT OldFont = (HFONT)SelectObject(hdc, hfont);*/
switch(n)
{
case :
//打出来白方胜
//DrawText(hdc, _T("白方胜"), 3, rect, DT_CENTER);
MessageBeep(-);
MessageBox(hWnd, _T("白方胜"), _T("Notice"), );
break;
case :
//DrawText(hdc, _T("黑方胜"), 3, rect, DT_CENTER);
MessageBeep(-);
MessageBox(hWnd, _T("黑方胜"), _T("Notice"), );
//这个自然就是黑方胜了
break;
default:
//DeleteObject(SelectObject(hdc,OldFont));
return ;
break;//这个break虽然没用,但是看着毕竟还是舒服点
}
//DeleteObject(SelectObject(hdc,OldFont));
GlobalInitial();
InvalidateRect(hWnd, NULL, TRUE);//擦写屏幕
//
return ;
}
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, , , rect->right - rect->left, rect->bottom - rect->top, htmpdc, , , SRCCOPY);
hPicBitmap = (HBITMAP)SelectObject(htmpdc, OldBitmap);
DeleteObject(hPicBitmap);
DeleteDC(htmpdc);
}
void DrawInfo(HDC hdc, ChessLine * cl, INT length)
{
TCHAR WORD[];
TCHAR TMPWORD[];//三个应该就够用了
for(int i = ;i < length;++ i)
{
if(cl[i].ChessType == )
wcscpy(TMPWORD, _T("白方"));
else
wcscpy(TMPWORD, _T("黑方"));
wsprintf(WORD, _T("%s:StartPos x:%d y:%dEndPos x:%d y%d:%Length: %d"),
TMPWORD,
cl[i].startpos.x,
cl[i].startpos.y,
cl[i].endpos.x,
cl[i].endpos.y,
cl[i].length
);
TextOut(hdc, ,i * , WORD, );
TextOut(hdc, ,i * + , WORD + ,wcslen(WORD) - );
}
}
POINT AIDeal(INT posx, INT posy)//因为大多数的变量都是全局变量,所以不需要很多参数。这两个参数是刚刚按下的点
{
POINT tmppoint;
return tmppoint;
}
void GetALLLine(INT w_or_b)//这个函数应该只处理一个点
{
//现在看,不用进行8个方向的查询,而是只需要做4个方向的查询即可,比如:1234,剩下的0567用其他的点来检测
//八个方向为上下左右以及其45度角
//8时钟方向,上位0,顺时针,从0 - 7
/*
7 0 1
6 2
5 4 3
*/
/*这个方法是有缺陷的,正常方法应该是对每个点都进行遍历,换言之,应该对点使用递归函数*/
//0方向的全部线查找
//这两个变量都设计为数组,是因为8个方向的数据,都存储处理还是可以的
//一种比较节约空间的方法是设置临时变量,存储当前结果,与上一结果相比,这样就不需要8个变量,而仅仅是两个了。
ChessLine * pCL;
INT MaxLength = ;
POINT MaxStartPos = {};
POINT MaxEndPos = {};
//memset(ArrayLength, 0, sizeof(ArrayLength));//嘿,看代码的,你应该知道我这么用是合法的吧?
//这段代码中有一部分代码应该函数化
if( == w_or_b)
w_ChessLineBuffer.clear();
else
b_ChessLineBuffer.clear();
for(int i = ;i < CHESS_LINE_NUM;++ i)
{
for(int j = ;j < CHESS_LINE_NUM; ++ j)
{
pCL = GetChessMaxSubLine(i, j, w_or_b, FALSE);
if(pCL == NULL)
continue;
if(pCL->length > MaxLength)
{
MaxLength = pCL->length;
MaxStartPos = pCL->startpos;
MaxEndPos = pCL->endpos;
}
DeleteCL(pCL);
}
}
}
INT GetMaxValCLAddr(ChessLine * parray, INT N)
{
if(parray == NULL && N <= )
return -;//用来表示无效的数字
INT maxval = parray[].length;
INT nrtnaddr = ;
for(int i = ;i < N;++ i)
{
if(maxval < parray[i].length)
{
maxval = parray[i].length;
nrtnaddr = i;
}
}
return nrtnaddr;
}
ChessLine * GetChessMaxSubLine(INT x, INT y, INT nColor, BOOL IfRtnVal)
{
INT CheckNum = ;
POINT StartPos;
ChessLine JudgeLine[];
//判断点是否合法
if(nColor != g_ChessTable[x][y].status)
return NULL;//放弃当前点
//当前点合法后,开始8个方向的遍历
//初始化
StartPos.x = x;
StartPos.y = y;
//一旦这个点被选入,初始长度肯定至少是1
ChessLineInitial(JudgeLine, &StartPos, , nColor);
JudgeLine[].endpos = StartPos;
//2方向
INT nRight = StartPos.x + ;
while(nRight < CHESS_LINE_NUM && g_ChessTable[nRight][StartPos.y].status == nColor)
{
JudgeLine[].length++;
nRight++;//向右查找
}
//保存对应的点
JudgeLine[].endpos.x = nRight - ;
JudgeLine[].endpos.y = StartPos.y;
//检测线两端的情况,数据存储在Effectivelevel中
//线左端方向的查找
if(JudgeLine[].startpos.x - >= )//边界判断的前提条件
{
if(g_ChessTable[JudgeLine[].startpos.x - ][JudgeLine[].startpos.y].status == -)
JudgeLine[].EffectLevel ++;
else if(g_ChessTable[JudgeLine[].startpos.x - ][JudgeLine[].startpos.y].status == nColor)
{
//线点存在重复的线将被抛弃
JudgeLine[].length = ;//这样AddIntoBuf函数会自动抛弃该值
}
}
//线右端查找
if(JudgeLine[].endpos.x + < CHESS_LINE_NUM)
{
if(g_ChessTable[JudgeLine[].endpos.x + ][JudgeLine[].endpos.y].status == -)
JudgeLine[].EffectLevel ++;
else if(g_ChessTable[JudgeLine[].endpos.x + ][JudgeLine[].endpos.y].status == nColor)
{
JudgeLine[].length = ;//这样AddIntoBuf函数会自动抛弃该值
}
}
if(JudgeLine[].EffectLevel != )
AddIntoBuf(&JudgeLine[], nColor);
//3方向
INT nRightDownOffset = ;//右下方向的偏移地址
while(StartPos.x + nRightDownOffset < CHESS_LINE_NUM && \
StartPos.y + nRightDownOffset < CHESS_LINE_NUM && \
g_ChessTable[StartPos.x + nRightDownOffset][StartPos.y + nRightDownOffset].status == nColor)
{
JudgeLine[].length++;
nRightDownOffset++;
}
//保存对应的点
JudgeLine[].endpos.x = StartPos.x + nRightDownOffset - ;
JudgeLine[].endpos.y = StartPos.y + nRightDownOffset - ;
//右下和左上方向查找
if(JudgeLine[].startpos.x - >= && JudgeLine[].startpos.y - >= )
{
if(g_ChessTable[JudgeLine[].startpos.x - ][JudgeLine[].startpos.y - ].status == -)
JudgeLine[].EffectLevel ++;
else if(g_ChessTable[JudgeLine[].startpos.x - ][JudgeLine[].startpos.y - ].status == nColor)
{
JudgeLine[].length = ;
}
}
if(JudgeLine[].startpos.x + < CHESS_LINE_NUM && JudgeLine[].startpos.y + < CHESS_LINE_NUM)
{
if(g_ChessTable[JudgeLine[].endpos.x + ][JudgeLine[].endpos.y + ].status == -)
JudgeLine[].EffectLevel ++;
else if(g_ChessTable[JudgeLine[].endpos.x + ][JudgeLine[].endpos.y + ].status == nColor)
{
JudgeLine[].length = ;
}
}
if(JudgeLine[].EffectLevel != )
AddIntoBuf(&JudgeLine[], nColor);
//4方向
INT nDown = StartPos.y + ;
while(nDown < CHESS_LINE_NUM && g_ChessTable[StartPos.x][nDown].status == nColor)
{
JudgeLine[].length++;
nDown++;//向下查找
}
//保存对应的点
JudgeLine[].endpos.x = StartPos.x;
JudgeLine[].endpos.y = nDown - ;
//上下两个方向的查找
//上 -
if(JudgeLine[].startpos.y - >= )
{
if(g_ChessTable[JudgeLine[].startpos.x][JudgeLine[].startpos.y - ].status == -)
JudgeLine[].EffectLevel ++;
else if(g_ChessTable[JudgeLine[].startpos.x][JudgeLine[].startpos.y - ].status == nColor)
{
JudgeLine[].length = ;
}
}
//下 +
if(JudgeLine[].endpos.y + < CHESS_LINE_NUM)
{
if(g_ChessTable[JudgeLine[].endpos.x][JudgeLine[].endpos.y + ].status == -)
JudgeLine[].EffectLevel ++;
else if(g_ChessTable[JudgeLine[].endpos.x][JudgeLine[].endpos.y + ].status == nColor)
{
JudgeLine[].length = ;
}
}
if(JudgeLine[].EffectLevel != )
AddIntoBuf(&JudgeLine[], nColor);
//5方向
INT nLeftDownOffset = ;//左下方向偏移地址,x -;y +
while(StartPos.x - nLeftDownOffset >= && \
StartPos.y + nLeftDownOffset < CHESS_LINE_NUM && \
g_ChessTable[StartPos.x - nLeftDownOffset][StartPos.y + nLeftDownOffset].status == nColor)
{
JudgeLine[].length++;
nLeftDownOffset++;
}
JudgeLine[].endpos.x = StartPos.x - (nLeftDownOffset - );//为了逻辑清楚,就先这么写了
JudgeLine[].endpos.y = StartPos.y + nLeftDownOffset - ;
//左下右上方向
//右上
if(JudgeLine[].startpos.y - >= && JudgeLine[].startpos.x + < CHESS_LINE_NUM)
{
if(g_ChessTable[JudgeLine[].startpos.x + ][JudgeLine[].startpos.y - ].status == -)
JudgeLine[].EffectLevel ++;
else if(g_ChessTable[JudgeLine[].startpos.x + ][JudgeLine[].startpos.y - ].status == nColor)
{
JudgeLine[].length = ;
}
}
//左下
if(JudgeLine[].endpos.y + < CHESS_LINE_NUM && JudgeLine[].endpos.x - >= )
{
if(g_ChessTable[JudgeLine[].endpos.x - ][JudgeLine[].endpos.y + ].status == -)
JudgeLine[].EffectLevel ++;
else if(g_ChessTable[JudgeLine[].endpos.x - ][JudgeLine[].endpos.y + ].status == nColor)
{
JudgeLine[].length = ;
}
}
if(JudgeLine[].EffectLevel != )
AddIntoBuf(&JudgeLine[], nColor);
//这段代码算是暂时废弃的
if(IfRtnVal)
{
ChessLine * pFinalLine = new ChessLine;
if(pFinalLine == NULL)
return NULL;
INT MaxLengthAddr = GetMaxValCLAddr(JudgeLine, CheckNum);
if(MaxLengthAddr == -)
{
delete pFinalLine;
return NULL;
}
*pFinalLine = JudgeLine[MaxLengthAddr];
return pFinalLine;
}
return NULL;
}
void AddIntoBuf(ChessLine * pcl,INT w_or_b)
{
switch(w_or_b)
{
case ://白色
if(pcl->length > )
w_ChessLineBuffer.push_back(*pcl);
break;
case :
if(pcl->length > )
b_ChessLineBuffer.push_back(*pcl);
break;
}
}
void ChessLineInitial(ChessLine * pcl, POINT * pstartpos, INT n, INT nColor)
{
if(pcl == NULL || pstartpos == NULL)
return;
for(int i = ;i < n;++ i)
{
pcl[i].length = ;
pcl[i].startpos = *pstartpos;
pcl[i].ChessType = nColor;
pcl[i].EffectLevel = ;//最低级
}
}
void DeleteCL(ChessLine * pcl)
{
delete pcl;
}
void DrawVecInfo(HDC hdc, std::vector<ChessLine> * pvcl)
{
TCHAR wcBuf[];
TCHAR tmpbuf[];
POINT tmppoint = GetAIPoint();
INT num_w = pvcl->size();
assert(num_w >= );
INT num_b = (pvcl + )->size();
assert(num_b >= );
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, , , tmpbuf, wcslen(tmpbuf));
wsprintf(tmpbuf, _T("AI x:%d y:%d"), tmppoint.x, tmppoint.y);
TextOut(hdc, , , tmpbuf, wcslen(tmpbuf));
for(int i = ;i < num_w;++ i)
{
wsprintf(wcBuf, _T("SP x:%d y:%d;EP x:%d y:%d;Len:%d;EL:%d;HL:%d"),
pvcl[][i].startpos.x, pvcl[][i].startpos.y,
pvcl[][i].endpos.x, pvcl[][i].endpos.y,
pvcl[][i].length,
pvcl[][i].EffectLevel,
pvcl[][i].length + pvcl[][i].EffectLevel);
TextOut(hdc, , (i+) * , wcBuf, wcslen(wcBuf));
}
for(int i = ;i < num_b;++ i)
{
wsprintf(wcBuf, _T("SP x:%d y:%d;EP x:%d y:%d;Len:%d;EL:%d;HL:%d"),
pvcl[][i].startpos.x, pvcl[][i].startpos.y,
pvcl[][i].endpos.x, pvcl[][i].endpos.y,
pvcl[][i].length,
pvcl[][i].EffectLevel,
pvcl[][i].length + pvcl[][i].EffectLevel);
TextOut(hdc, , (i+) * , wcBuf, wcslen(wcBuf));
}
}
ChessLine * GetBestLine(INT nColor)
{
ChessLine * pcl = new ChessLine;
if(pcl == NULL)
return NULL;
std::vector<ChessLine> * pvcl;
if(nColor == )
pvcl = &w_ChessLineBuffer;
else
pvcl = &b_ChessLineBuffer;
INT nsize = pvcl->size();
if(nsize == )
return NULL;
//删除没用的线
//线还是先不删了,擅自修改vector的大小会引发大量的越界问题
/*
std::vector<ChessLine>::iterator pvcl_itstart = pvcl->begin();
std::vector<ChessLine>::iterator pvcl_itend = pvcl->end();
for(int i = 0;i < nsize;)
{
if(pvcl_itstart[i].EffectLevel == 0)
{
pvcl->erase(pvcl_itstart + i);
nsize --;
continue;
}
i++;
}*/
//然后使用优先级判断公式 length + EffectLevel
//先获取最大值
INT num_cl = pvcl->size();
if(num_cl == )
return NULL;
INT nMax = ;
INT nMaxAddr = ;
for(int i = ;i < num_cl;++ i)
{
if((*pvcl)[i].EffectLevel + (*pvcl)[i].length > nMax && (*pvcl)[i].EffectLevel != )
{
nMax = (*pvcl)[i].EffectLevel + (*pvcl)[i].length;
nMaxAddr = i;
}
}
*pcl = (*pvcl)[nMaxAddr];
return pcl;
}
POINT GetAIPoint()//根据GetBestLine返回的黑白两棋子线情况来判断棋子的位置
{
//先获取全部的线。
GetALLLine();
GetALLLine();
//这里曾造成内存泄露,原因是返回路径会切断删除函数的调用
ChessLine * pw_cl = GetBestLine();//白子 人方
ChessLine * pb_cl = GetBestLine();//黑子 AI
ChessLine * pfinal_cl;
POINT rtnpos = {-, -};
if(pw_cl != NULL && pb_cl != NULL)
{
//防守优先
if(pw_cl->EffectLevel + pw_cl->length >= pb_cl->EffectLevel + pb_cl->length)
pfinal_cl = pw_cl;
else
pfinal_cl = pb_cl;
}
else if(pw_cl == NULL && pb_cl != NULL)
pfinal_cl = pb_cl;
else if(pb_cl == NULL && pw_cl != NULL)
pfinal_cl = pw_cl;
else //在上面的两个ChessLine都获取不到的时候,需要做的是,尝试去获取一个单独的点。
{
POINT SingleFinalPoint = GetSinglePoint();
return SingleFinalPoint;
}
//这个是测试用数据,全局变量
BestLine = *pfinal_cl;
switch(GetValidSEDirection(pfinal_cl->startpos, pfinal_cl->endpos))
{
case ://2-6
//
if(g_ChessTable[pfinal_cl->startpos.x - ][pfinal_cl->startpos.y].status == -
&& pfinal_cl->startpos.x - >= )
{
rtnpos.x = pfinal_cl->startpos.x - ;
rtnpos.y = pfinal_cl->startpos.y;
}
else if(pfinal_cl->endpos.x + < CHESS_LINE_NUM)
{
rtnpos.x = pfinal_cl->endpos.x + ;
rtnpos.y = pfinal_cl->endpos.y;
}
break;
case ://3-7
if(g_ChessTable[pfinal_cl->startpos.x - ][pfinal_cl->startpos.y - ].status == -)
{
rtnpos.x = pfinal_cl->startpos.x - ;
rtnpos.y = pfinal_cl->startpos.y - ;
}
else
{
rtnpos.x = pfinal_cl->endpos.x + ;
rtnpos.y = pfinal_cl->endpos.y + ;
}
//return rtnpos;
break;
case ://4-0
if(g_ChessTable[pfinal_cl->startpos.x][pfinal_cl->startpos.y - ].status == -
&& pfinal_cl->startpos.y - >=
&& pfinal_cl->endpos.y + < CHESS_LINE_NUM)
{
rtnpos.x = pfinal_cl->startpos.x;
rtnpos.y = pfinal_cl->startpos.y - ;
}
else
{
rtnpos.x = pfinal_cl->endpos.x;
rtnpos.y = pfinal_cl->endpos.y + ;
}
//return rtnpos;
break;
case ://5-1
if(g_ChessTable[pfinal_cl->startpos.x + ][pfinal_cl->startpos.y - ].status == -
&& pfinal_cl->startpos.x + < CHESS_LINE_NUM
&& pfinal_cl->startpos.y - >= )
{
rtnpos.x = pfinal_cl->startpos.x + ;
rtnpos.y = pfinal_cl->startpos.y - ;
}
else
{
rtnpos.x = pfinal_cl->endpos.x - ;
rtnpos.y = pfinal_cl->endpos.y + ;
}
//return rtnpos;
break;
}
DeleteCL(pw_cl);
DeleteCL(pb_cl);
return rtnpos;
}
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;
/*
7(-1,1) 0(0,1) 1(1,1)
6(-1,0) 2(1,0)
5(-1,-1)4(0,-1) 3(1,-1)
*/
if(ndirx > )
{
if(ndiry == )//2(1,0)
return ;
else//3(1,-1)
return ;
}
else if(ndirx == )
return ;
else
return ;
}
POINT GetSinglePoint()
{
//所谓singlepoint,就是8个相邻点中没有任何一点是同色点。
//函数返回值为从0-7中的有效点中的一个随机点
INT npos;
POINT rtnpoint = {-, -};
for(int i = ;i < CHESS_LINE_NUM;++ i)
{
for(int j = ;j < CHESS_LINE_NUM;++ j)
{
if(g_ChessTable[i][j].status != -)
{
npos = IsValidSinglePoint(i, j);
if(npos == -)
continue;
switch(npos)
{
//这里的代码直接return,就不用再break了
case :
rtnpoint.x = i - ;
rtnpoint.y = j - ;
break;
case :
rtnpoint.x = i;
rtnpoint.y = j - ;
break;
case :
rtnpoint.x = i + ;
rtnpoint.y = j - ;
break;
case :
rtnpoint.x = i - ;
rtnpoint.y = j;
break;
case :
rtnpoint.x = i + ;
rtnpoint.y = j;
break;
case :
rtnpoint.x = i - ;
rtnpoint.y = j + ;
break;
case :
rtnpoint.x = i;
rtnpoint.y = j + ;
break;
case :
rtnpoint.x = i + ;
rtnpoint.y = j + ;
break;
}
return rtnpoint;
}
}
}
return rtnpoint;
}
INT IsValidSinglePoint(int x, int y)
{
assert(x >= && y >= && x < CHESS_LINE_NUM && y < CHESS_LINE_NUM);
char checkflag[] = {};//纯标记位
if(x - >= )//一次查三个点
{
if(y - >= )
{
if(g_ChessTable[x - ][y - ].status == -)
checkflag[] = ;
}
if(g_ChessTable[x - ][y].status == -)
checkflag[] = ;
if(y + < CHESS_LINE_NUM)
{
if(g_ChessTable[x - ][y + ].status == -)
checkflag[] = ;
}
}
if(y - >= && g_ChessTable[x][y - ].status == -)
checkflag[] = ;
if(y + < CHESS_LINE_NUM && g_ChessTable[x][y + ].status == -)
{
checkflag[] = ;
}
if(x + < CHESS_LINE_NUM)
{
if(g_ChessTable[x + ][y].status == -)
checkflag[] = ;
if(y + < CHESS_LINE_NUM)
{
if(g_ChessTable[x + ][y + ].status == -)
checkflag[] = ;
}
if(y - >= )
{
if(g_ChessTable[x + ][y - ].status == -)
checkflag[] = ;
}
}
/*调试部分
INT nrtn = 0;
for(int i = 0;i < 8;++ i)
{
if(checkflag[i] == 1)
{
nrtn |= 1 << (i * 4);
}
}*/
INT nCounterofValidPoint = ;
for(int i = ;i < ;++ i)
{
if(checkflag[i] == )
nCounterofValidPoint ++;
}
if(!nCounterofValidPoint)
return -;
srand(time());
INT nUpSection = rand() % ;//在这倒是正好能用作地址上限
INT UpSearch = nUpSection;
INT DownSearch = nUpSection;
for(;UpSearch < || DownSearch >= ;)
{
if(UpSearch < )
{
if(checkflag[UpSearch] == )
return UpSearch;
else
UpSearch ++;
}
if(DownSearch >= )
{
if(checkflag[DownSearch] == )
return DownSearch;
else
DownSearch --;
}
}
}
完全自制的五子棋人机对战游戏(VC++实现)的更多相关文章
- js实现五子棋人机对战源码
indexhtml <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...
- Pyhton实践项目之(一)五子棋人机对战
1 """五子棋之人机对战""" 2 3 import random 4 import sys 5 6 import pygame 7 im ...
- 介绍一款Android小游戏--交互式人机对战五子棋
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6589025 学习Android系统开发之余,编 ...
- Python:游戏:五子棋之人机对战
本文代码基于 python3.6 和 pygame1.9.4. 五子棋比起我之前写的几款游戏来说,难度提高了不少.如果是人与人对战,那么,电脑只需要判断是否赢了就可以.如果是人机对战,那你还得让电脑知 ...
- HTML5+JS 《五子飞》游戏实现(八)人机对战
要想实现人机对战,就必须让电脑自动下棋,而且要知道自动去查找对方的棋子,看看有没有可以挑一对的,有没有可以夹一个的,这样下起来才有意思. 当电脑用户下完棋后,电脑应立即搜索用户的棋子,然后如果没有被吃 ...
- 基于Udp的五子棋对战游戏
引言 本文主要讲述在局域网内,使用c#基于Udp协议编写一个对战的五子棋游戏.主要从Udp的使用.游戏的绘制.对战的逻辑这三个部分来讲解. 开发环境:vs2013,.Net4.0,在文章的末尾提供源代 ...
- java 五子棋之人机对战思路详解
最近做了五子棋,记录下自己完成五子棋的人机对战的思路. 首先,思路是这样的:每当人手动下一颗棋子(黑子)的时候,应当遍历它周围棋子的情况,并赋予周围棋子一定的权值,当在机器要下棋子(白子)守护之前,会 ...
- "人机"对战:电脑太简单了,我是射手 skr~skr~skr
9月17日,2018 世界人工智能大会在上海拉开帷幕.在 SAIL 榜单入围项目中,我看到了小爱同学.小马智行.微软小冰.腾讯觅影等等,这不仅让我大开了眼界,也不禁让我感慨 AI 的发展神速.犹记得去 ...
- python3 井字棋 GUI - 人机对战、机器对战 (threading、tkinter库)
python3 井字棋 GUI - 人机对战.机器对战 功能 GUI界面 人机对战(可选择机器先走) 机器对战(50局) 流程图 内核 棋盘 [0][1][2] [3][4][5] [6][7][8] ...
随机推荐
- android activity之间传递返回值
activity A,跳转至 Activity B ,A传参数user_name给B,然后B再返回修改后的参数user_name给A 首先A传user_name给B Intent input_B = ...
- 2、onclickListener冲突
事情是这样的. 我在activity中同时使用普通按钮和对话框按钮,并都设置点击时候的回调函数,由于都要用到onclickListener,但是两者却不是一个文件,无法同时import,这就是本文出现 ...
- char a[] = "hello"; char c[] = {'h','e','l','l','o'}; int b[] = {1, 2, 3, 4, 5};的长度区别,及内存中空间开辟情况
1, char a[] = "hello"; char c[] = {'h','e','l','l','o'}; int b[] = {1, 2, 3, 4, 5}; 数组是开辟一 ...
- python 单元测试-unittest
参考资料:https://docs.python.org/3.4/library/unittest.html#module-unittest 一张图解决问题: 涉及5块内容:case.suite.lo ...
- Sprite Kit 入门教程
Sprite Kit 入门教程 Ray Wenderlich on September 30, 2013 Tweet 这篇文章还可以在这里找到 英语, 日语 If you're new here, ...
- Discuz! X3.1去除内置门户导航/portal.php尾巴的方法
方法: 打开文件 /source/admincp/admincp_domain.php 查找 [php] if(!empty($domain) && in_array($domain, ...
- php中用于接受表单数据的$_request与$_post、$_get
一.$_request与$_post.$_get的区别和特点 $_REQUEST[]具用$_POST[] $_GET[]的功能,但是$_REQUEST[]比较慢.通过post和get方法提交的所有数据 ...
- 彻底理解js中this的指向
首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象(这句话有些问题,后面会解释为什么会有问题,虽然 ...
- H5+ and mui学习记录
基础 1.H5+ 定义实现了一些调用原生方法的对象 2.其他的原生方法可以通过Native.js调用 webview 3.webview是调用原生界面的H5+对象 4.单个webview只承载单个页面 ...
- VMware下Ubuntu与宿主Windows共享文件夹
概述1.安装VMware Tool2.设置共享 步骤开始安装VMware Tool 显示如下画面(如果宿主无法访问外网,可能会出现一个更新失败,可以无视之) 通过下列命令解压.执行,分别是下面的tar ...