今天我们来完成五子棋初级和中级AI的实现,以及讲解一下扫棋的方式,先来讲一下基本的原理,AI(电脑)是怎么下棋的呢?
首先,我们来了解一下五子棋 的基本棋型
最常见的基本棋型大体有以下几种:连五,活四,冲四,活三,眠三,活二,眠二。
①连五:顾名思义,五颗同色棋子连在一起,不需要多讲。
图2-1
②活四:有两个连五点(即有两个点可以形成五),图中白点即为连五点。
稍微思考一下就能发现活四出现的时候,如果对方单纯过来防守的话,是已经无法阻止自己连五了。
图2-2
③冲四:有一个连五点,如下面三图,均为冲四棋型。图中白点为连五点。
相对比活四来说,冲四的威胁性就小了很多,因为这个时候,对方只要跟着防守在那个唯一的连五点上,冲四就没法形成连五。
图2-3 图2-4 图2-5
④活三:可以形成活四的三,如下图,代表两种最基本的活三棋型。图中白点为活四点。
活三棋型是我们进攻中最常见的一种,因为活三之后,如果对方不以理会,将可以下一手将活三变成活四,而我们知道活四是已经无法单纯防守住了。所以,当我们面对活三的时候,需要非常谨慎对待。在自己没有更好的进攻手段的情况下,需要对其进行防守,以防止其形成可怕的活四棋型。
图2-6 图2-7
其中图2-7中间跳着一格的活三,也可以叫做跳活三。
⑤眠三:只能够形成冲四的三,如下各图,分别代表最基础的六种眠三形状。图中白点代表冲四点。眠三的棋型与活三的棋型相比,危险系数下降不少,因为眠三棋型即使不去防守,下一手它也只能形成冲四,而对于单纯的冲四棋型,我们知道,是可以防守住的。
图2-8 图2-9 图2-10
2-11 图2-12 图2-13
如上所示,眠三的形状是很丰富的。对于初学者,在下棋过程中,很容易忽略不常见的眠三形状,例如图2-13所示的眠三。
有新手学了活三眠三后,会提出疑问,说活三也可以形成冲四啊,那岂不是也可以叫眠三?
会提出这个问题,说明对眠三定义看得不够仔细:眠三的的定义是,只能够形成冲四的三。而活三可以形成眠三,但也能够形成活四。
此外,在五子棋中,活四棋型比冲四棋型具有更大的优势,所以,我们在既能够形成活四又能够形成冲四时,会选择形成活四。
温馨提示:学会判断一个三到底是活三还是眠三是非常重要的。所以,需要好好体会。
后边禁手判断的时候也会有所应用。
⑥活二:能够形成活三的二,如下图,是三种基本的活二棋型。图中白点为活三点。
活二棋型看起来似乎很无害,因为他下一手棋才能形成活三,等形成活三,我们再防守也不迟。但其实活二棋型是非常重要的,尤其是在开局阶段,我们形成较多的活二棋型的话,当我们将活二变成活三时,才能够令自己的活三绵绵不绝微风里,让对手防不胜防。
图2-14 图2-15 图2-16
⑦眠二:能够形成眠三的二。图中四个为最基本的眠二棋型,细心且喜欢思考的同学会根据眠三介绍中的图2-13找到与下列四个基本眠二棋型都不一样的眠二。图中白点为眠三点。
图2-17 图2-18
图2-19 图2-20
根据之前的扫棋方式,电脑会进去判定下一步将下的子,形成的棋谱,然后根据棋谱的各种棋型进行打分
下面我把根据这几种图谱的自己做的打分表给大家看一下
Dictionary<string, float> toScore = new Dictionary<string, float>(); //定义一个字典存储打分表
void Start()
{
toScore.Add("aa___", 100); //眠二
toScore.Add("a_a__", 100);
toScore.Add("___aa", 100);
toScore.Add("__a_a", 100);
toScore.Add("a__a_", 100);
toScore.Add("_a__a", 100);
toScore.Add("a___a", 100);
toScore.Add("__aa__", 500); //活二 "_aa___"
toScore.Add("_a_a_", 500);
toScore.Add("_a__a_", 500);
toScore.Add("_aa__", 500);
toScore.Add("__aa_", 500);
toScore.Add("a_a_a", 1000); // bool lfirst = true, lstop,rstop = false int AllNum = 1
toScore.Add("aa__a", 1000);
toScore.Add("_aa_a", 1000);
toScore.Add("a_aa_", 1000);
toScore.Add("_a_aa", 1000);
toScore.Add("aa_a_", 1000);
toScore.Add("aaa__", 1000); //眠三
toScore.Add("_aa_a_", 9000); //跳活三
toScore.Add("_a_aa_", 9000);
toScore.Add("_aaa_", 10000); //活三
toScore.Add("a_aaa", 15000); //冲四
toScore.Add("aaa_a", 15000); //冲四
toScore.Add("_aaaa", 15000); //冲四
toScore.Add("aaaa_", 15000); //冲四
toScore.Add("aa_aa", 15000); //冲四
toScore.Add("_aaaa_", 1000000); //活四
toScore.Add("aaaaa", float.MaxValue); //连五
}
//下面是具体的扫棋方式
public override void CheckOneLine(int[]pos,int[]offset,int chess){
int Allnum = 1;
bool lfrist true,lstop = false,rstop;
string str ="a";
int li =-offset[0],lj=-offset[1];
int ri = offset[0],rj =offset[1];
While(Allnum<7||!lstop||!rstop)
{
if(lfirst)
{
//左边
if((pos[0]+li>=0&&pos[0]+li<15)&&
pos[0]+lj>=0&&pos[0]+lj<15)
{
if(ChessBoard.intance.grid [pos[0]+li,pos[0]+lj]== chess)
{
Allnum++;
str+="a";
}
else if(ChessBoard.intance.grid[pos[0]+li,pos[0]+lj ==0)
{
Allnum++;
str +="_";
if(!rstop) lfirst = false;
}
else
{
lstop = true;
if(!rstop) lfirst = false;
}
li -=offset[0];lj-=offset[1];
}
else
{
lstop = true;
if(!rstop) lfirst = false;
}
}
else
{
//右边
if((pos[0]+li>=0&&pos[0]+li<15)&&
pos[0]+lj>=0&&pos[0]+lj<15)
{
if(ChessBoard.intance.grid [pos[0]+ri,pos[0]+rj]== chess)
{
Allnum++;
str+="a";
}
else if(ChessBoard.intance.grid[pos[0]+ri,pos[0]+rj ==0)
{
Allnum++;
str +="_";
if(!lstop) lfirst = true;
}
else
{
rstop = true;
if(!lstop) lfirst = true;
}
ri +=offset[0];rj+=offset[1];
}
else
{
rstop = true;
if(!lstop) lfirst = true;
}
}
}
string cmpStr ="";
foreach(var keyInfo in toScore)
{
if(str.Contains(keyInfo.key))
{
if(cmpStr!=null)
{
if(toScore[keyInfo.key]>cmpStr[keyInfo.key])
{
cmpStr[keyInfo.key] = toScore[keyInfo.key];
}
}
else
{
cmpStr = keyInfo.key;
}
}
}
if(cmpStr!=null)
{
toScore[pos[i],pos[j]]+=toScore[cmpStr];
}
}