人机博弈作业.docx
《人机博弈作业.docx》由会员分享,可在线阅读,更多相关《人机博弈作业.docx(15页珍藏版)》请在冰豆网上搜索。
人机博弈作业
2013-2014学年2学期
人机博弈程序的实现
学 号
姓名
专业计算机科学与技术
学院计算机与信息工程学院
基于人工智能理论的五子棋人机对弈
摘要
人工智能(ArtificialIntelligence),英文缩写为AI。
它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。
人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器,该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。
人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。
人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大,但没有一个统一的定义。
人工智能是对人的意识、思维的信息过程的模拟。
人工智能不是人的智能,但能像人那样思考、也可能超过人的智能。
但是这种会自我思考的高级人工智能还需要科学理论和工程上的突破。
人工智能是一门极富挑战性的科学,从事这项工作的人必须懂得计算机知识,心理学和哲学。
人工智能是包括十分广泛的科学,它由不同的领域组成,如机器学习,计算机视觉等等,总的说来,人工智能研究的一个主要目标是使机器能够胜任一些通常需要人类智能才能完成的复杂工作。
但不同的时代、不同的人对这种“复杂工作”的理解是不同的。
计算机人机对弈也是其中之一。
作为人智能研究的一个重要分支,计算机博弈是检验人工水平的一个重要方面。
它的研究为人工智能带来了很多重要的方法和理论,产生了广泛的社会影响和学术影响。
五子棋人机对弈是计算机博弈中的一种。
研究其计算机算法,可以让我们看到人工智能的初影,也有助于我们人脑的开发。
五子棋是我国发明的,研究它可以让更多的外国人了解,有助于我国优秀文化的推广。
[关键词]:
人工智能,计算人机对弈,五子棋,算法
第一章概述
当电脑进入我们的生活中,许多与相关学科都欣欣的向上发展。
典型的有电子商务、电子邮件等。
当然也有人智能了。
人们在惊叹机器人高效的工作时,也会想起自己聪明的一面。
人工智能也这方面也就深受我们喜爱。
1.1背景分析
五子棋是起源于中国古代的传统黑白棋种之一。
现代五子棋日文称之为“连珠”,英译为“Ren-ju”,英文称之为“Gobang”或“FIR”(FiveinaRow的缩写),亦有“连五子”、“五子连”、“串珠”、“五目”、“五目碰”、“五格”等多种称谓。
五子棋不仅能增强思维能力,提高智力,而且变化多端,非常富有趣味性和消遣性,因此为人民群众所喜闻乐见。
另一方面,人工智能也在最近几年发展迅速。
人们不断的研究出机器人之类的,使它能胜任人们能做的一些复杂的事,或是一些人们不适宜做的事,如水下探测等。
而随着人们工作的加大,运动的时间也越来越少,大部分呆在家里或是工作的地方。
这样人们就通过上网、购物等之类的事来取代生活中的不足。
当然也有一部分群体在玩游戏之类的了。
而五子棋由于其经典、易学等。
深受人们的喜爱。
人们在玩的同时也增加了自己的智慧。
1.2国内外现状
国内外研究五子棋的算法不少。
有递归法、二叉树等。
当然我所讨论的是一般的算了法。
无论何种算法,其大体遵循两条原则:
1.使规则更加自然流畅,更容易被人接受。
2.使棋的内容更加丰富多彩。
而对于五子棋来说,所面临的困境归根结底是来自于其最本质的特点,也是目前一切规则的共同之处:
连五终局(注意是连五终局而不一定是连五获胜,因为连五一方有可能违反长连禁手而被判负)。
这个特点产生的结果就是使很多着法绝对化,从而极大地缩减了棋局的变化空间。
当别的棋类中的棋子在大多数时间里都可以在棋盘上自由驰骋时,五子棋的棋子却经常为了应付对方的冲四和活三而不得不疲于奔命。
计算机对不同棋类研究程度的对比很能说明问题;对中象和国象的研究也足可匹敌人类中的顶尖高手;而对五子棋则动辄是“地毯终结”,以摧腐拉朽之势把这片领域中的未知之处一个又一个无情碾碎。
不可否认这也是很多人认为五子棋“简单低级”的一个重要原因。
面对这样的窘境,有的人提出一种大胆的设想,认为既然“连五终局”是造成五子棋变化简单的罪魁祸首,那么就应该将其摒弃,用其它的方法比如说看最后谁连五的数量更多来判定胜负。
对于这样的想法,我认为大家应该以一种开放的心态去看待,即使是持反对态度,也要理解提出此类想法的人为了使五子棋更具活力,更加精彩而花费的心思,不要粗暴地给人家扣上一顶“荒诞不经”的帽子完事。
目前五子棋在规则方面遇到的困境不是简单采用某种现行规则,甚至不是对现行规则进行一些枝节上的修改能解决得了的。
必须要有大胆的、飞跃性的变革才能使奄奄一息的五子棋有脱胎换骨的变化。
至于具体的变革方式,我这个五子棋的门外汉怕是给不出什么像样的建议,还得靠你们这些内行来想办法。
要说实现这一目标的具体方案,最好是能开一个全国乃至全世界范围内的规则统一大会,列举出几种切实可行的全新规则并经过充分讨论后,以投票方式选出最终的统一规则。
也许有人觉得这个方案离现实太过遥远,不过我倒觉得此方案比统一于现有的有禁或无禁方案要现实可行的多,因为如果在目前某派所掌握的规则下实现统一,另一派会有严重的被欺压和被掠夺感,从而在心理上产生强烈的抗拒。
第二章环境介绍
VisualC++是MicrosoftC/C++7.0之后推出的新一代程序开发工具,它不仅继承C++的特性,同时具备可视化程序语言(VisualProgrammingLanguage)及程序产生器的概念。
既然是面向对象语言,当然会提供系统基础类给程序员再使用(Reuse)。
在VisualC++中,延袭MicrosoftC/C++7.0的作法,将原有的SDK函数重新封装在适当的类中,构造了“基础类函数库(MicrosoftFoundationClassLibrary,简称MFC)”,它提供了许多现成的框架对象,可供在使用VisualC++设计应用程序是使用。
此外,还提供“ClassWizard”与“AppWizard”可帮助构造基本的程序框架,从软件再使用的技术观点而言,已由SDK函数库方式经过面向对象程序设计方式进展到自动产生模板相关类程序代码方式(Framework)。
第三章系统简要代码
背景只显示一次。
其代码如下:
CBitmapbj;//存放背景图
CDCmembj;//内存变量背景
membj.CreateCompatibleDC(&dc);//创建相容的内存变量
bj.LoadBitmap(IDB_BJ);//加载背景图
membj.SelectObject(&bj);//选择背景图
//初始时只需显示一次背景就可
if(0==flagbj){
//若是左键单击了之后才可重新绘图,目的是白子只重新绘制一次
if(1==flaglb){
dc.BitBlt(0,0,700,700,&membj,0,0,SRCCOPY);
}
//置为1表示不再显示背景
flagbj=1;
}
首先要进行初始化,先对棋盘及分数进行初始化。
//0表示无棋子,1表示为电脑的棋子,2表示为玩家的棋子
for(i=0;i<19;i++){
for(j=0;j<19;j++){
pscore[i][j]=0;//玩家各得分为0
cscore[i][j]=0;//电脑各得分为0
borad[i][j]=0;//各空格置为无子
}
}
//各获胜组合中的棋子数为0
for(k=0;k<1020;k++){
wcount[0][k]=0;//电脑获胜组合数的个数
wcount[1][k]=0;//玩家获胜组合数的个数
}
然后要对获胜组合进行初始化。
//初始时各种获胜组合中的值都为false
for(i=0;i<19;i++){
for(j=0;j<19;j++){
for(k=0;k<1020;k++){
cmp[i][j][k]=false;
ply[i][j][k]=false;
}
}
}
//行的组合情况,count的初始值为0
for(i=0;i<19;i++){
for(j=0;j<15;j++){
for(k=0;k<5;k++){
cmp[i][j+k][count]=true;
ply[i][j+k][count]=true;
}
count++;
}
}
//列的组合情况
for(i=0;i<15;i++){
for(j=0;j<19;j++){
for(k=0;k<5;k++){
cmp[i+k][j][count]=true;
ply[i+k][j][count]=true;
}
count++;
}
}
//正斜的组合情况
for(i=0;i<15;i++){
for(j=0;j<15;j++){
for(k=0;k<5;k++){
cmp[i+k][j+k][count]=true;
ply[i+k][j+k][count]=true;
}
count++;
}
}
//反斜的组合情况
for(i=0;i<15;i++){
for(j=18;j>=4;j--){
for(k=0;k<5;k++){
cmp[i+k][j-k][count]=true;
ply[i+k][j-k][count]=true;
}
count++;
}
}
进行初始化后,此时start、bply置为ture,bcmp置为false。
即对每一种获胜组合中的情况进行初始化。
之后游戏开始,玩家可下子,电脑不可下子。
先玩家下子,转入到系统函数OnLButtonDown()。
如下所示
voidmainFrame:
:
OnLButtonDown(UINTnFlags,CPointpoint){
//玩家单击时的动作
if(start&&bply){//若开始后且轮到玩家下子则进行判断
if(point.x>=38&&point.y&&point.x<=602&&point.y<=602){
mp=(int)floor((point.x-36)/30);
np=(int)floor((point.y-36)/30);
//空格才可下子
if(0==borad[mp][np]){
//置为玩家的子
borad[mp][np]=2;
for(k=0;k<1020;k++){
//玩家子个数加1
if(-1!
=wcount[1][k]&&ply[mp][np][k])
wcount[1][k]++;
//电脑置为不可能获胜
if(cmp[mp][np][k]){
cmp[mp][np][k]=false;
wcount[0][k]=-1;
}
}
flaglb=1;//玩家下子了可以贴白子与tishi中的flaglb形成合作关系
bply=false;//玩家不可下子
bcmp=true;//电脑可以下子
}
}
}
CFrameWnd:
:
OnLButtonDown(nFlags,point);
}
在此函数中,首先要判断下子的位置是否在五子棋的棋盘区域中。
即使其成立point.x>=38&&point.y&&point.x<=602&&point.y<=602。
若在区域中,就调用了系统中的函数floor(),用于对键单击时的位置取整,来得到单击进的位置。
之后还要判断所下了位置是否是空子,若是空子才可下子。
若为空,则此格即为玩家的子,borad[][]的值置为2(2表示为玩家的子)。
玩家下子后,电脑的布局就要改变。
这时对1020种获胜情况一一来进行讨论。
若是玩家的子且在此获胜组合中有可能获胜,则wcount[1][]中的个数加一,若是电脑的获胜组合中也为true,则此时wcount[0][]置为-1,表示在此获胜组合中不可能获胜,并把对应的cmp[][][]的值置为false,表示电脑在此获胜组合中不可能获胜。
玩家下完之后,此时就轮到电脑下子了。
bply置为true,bcmp置为false,表示玩家下完之后是电脑下子。
玩家下完之后,这时就是轮到电脑下子。
但在下子前判断下当前的状况。
先贴子。
//玩家下完之后就重新贴子
for(i=0;i<19;i++){
for(j=0;j<19;j++){
//电脑为红子
if(1==borad[i][j])
dc.BitBlt(i*30+36,j*30+36,24,24,&memcs,0,0,SRCCOPY);
//玩家为绿子
if(2==borad[i][j])
dc.BitBlt(i*30+36,j*30+36,24,24,&memps,0,0,SRCCOPY);
}
}
刚刚玩家下完之后,只是内部的置为玩家的子,但是用户没有看到。
此时若显示出来,则可了解到玩家的子确实下了。
这时电脑就进入了计算阶段。
这也是此系统的核心代码。
此函数放置在OnTimer()中。
如下所示:
//是否和棋
tie=tieGame();
if(tie&&start)
{start=false;//游戏结束
bcmp=false;//电脑不可下子
bply=false;//玩家不档下子
MessageBox("朋友,水平不错,竟能与电脑和棋哦……\n(按F1开始,F12退出!
)");
flagbj=0;//重新显示背景图片
}
//是否电脑获胜
cwin=cmpGame();
if(cwin&&start)
{start=false;//游戏结束
bcmp=false;//电脑不可下子
bply=false;//玩家不档下子
MessageBox("朋友,失败乃成功之母,请再接再励!
\n(按F1开始,F12退出!
)");
flagbj=0;//重新显示背景图片
}
//是否玩家获胜
pwin=plyGame();
if(pwin&&start)
{start=false;//游戏结束
bcmp=false;//电脑不可下子
bply=false;//玩家不档下子
MessageBox("朋友,真牛!
一下就赢了,要不收我作徒弟吧~~\n(按F1开始,F12退出!
)");
flagbj=0;//重新显示背景图片
}
先判断是否是和棋,若是则暂停程序。
当然在此,判断和棋,我是调用了和棋的函数tieGame()。
其内容如下:
BOOLmainFrame:
:
tieGame(){
//若有空格则不为和棋
for(i=0;i<19;i++){
for(j=0;j<19;j++){
if(0==borad[i][j]){
returnfalse;
}
}
}
//若有一方胜也不为和棋
for(k=0;k<1020;k++){
if(5==wcount[0][k]||5==wcount[1][k]){
returnfalse;
}
}
//否则为和棋
returntrue;
}
要讨论19X19格了中的每一个格子。
若每一个格子都不为空,即borad[][]!
=0,并且在获胜组合中没有一方获胜,则此时电脑即为和棋,返回true,否则都为false。
这样就可实现和棋的判断了。
若是和棋,则此时start置为flase,表示游戏暂时停止。
并弹出对话框显示和棋。
若不为和棋,则判断是否是电脑获胜,其也调用判断电脑获胜的函数cmpGame(),其内容如下:
//是否玩家获胜
BOOLmainFrame:
:
plyGame(){
for(k=0;k<1020;k++){
if(5==wcount[1][k])//有五子棋相连则获胜
returntrue;
}
returnfalse;
}
其实也挺容易的,只要看下wcount[1][]中是否有一种情况中的个数为5,若有此时就返回true,否则返回flase。
若是电脑获胜,则也弹出对话框显示电脑获胜。
这之后便把start也置为false。
若电脑不获胜,则继续判断是否玩家获胜,此时同样调用函数cmpGame()来处理。
如下所示:
BOOLmainFrame:
:
cmpGame(){
for(k=0;k<1020;k++){
if(5==wcount[0][k])//有五子棋相连则获胜
returntrue;
}
returnfalse;
}
若在电脑的获胜组合中有一个组合的个数为5,则电脑获胜,这时就弹出对话框,显示电脑获胜,游戏时放暂停状态。
否则继续执行。
若玩家下子后没有和棋、电脑获胜或是玩家获胜,则此时就轮到电脑下子了。
首先统计下当前分数,看下当前的得分情况。
如下:
voidmainFrame:
:
countScore(){
for(i=0;i<19;i++){
for(j=0;j<19;j++){
//初始值为0
pscore[i][j]=0;
cscore[i][j]=0;
//若为空则计算分数
if(0==borad[i][j]){
//各种情况一一讨论
for(k=0;k<1020;k++){
//若是玩家
if(ply[i][j][k]){
switch(wcount[1][k]){
case0:
//零个子情况
pscore[i][j]+=1;
break;
case1:
//一个子情况
pscore[i][j]+=5;
break;
case2:
//二个子情况
pscore[i][j]+=10;
break;
case3:
//三个子情况
pscore[i][j]+=30;break;
case4:
//四个子情况
pscore[i][j]+=120;
break;
}
}
//若是电脑
if(cmp[i][j][k]){
switch(wcount[0][k]){
case0:
cscore[i][j]+=1;
break;
case1:
cscore[i][j]+=2;
break;
case2:
cscore[i][j]+=8;
break;
case3:
cscore[i][j]+=32;
break;
case4:
cscore[i][j]+=128;
break;
}
}
}
}
}
}
}
参考文献
[1]张海藩,牟永敏.面向对象程序设计实用教程.北京:
清华大学出版社
[2]VisualC++项目开发实践.李强贾云霞编著中国铁道出版社
[3]初级职业连珠五子棋廿四种开局——作者:
彭建国,那威编著
[4]连珠五子棋入门——作者:
彭建国,那威著