参考:http://game.onegreen.net/wzq/HTML/142336.html
对于正式接触五子棋时间不长的朋友来说,了解和掌握一些基本棋型的名称及其特点是非常重要的。不仅可以加深对棋的理解,更重要的是可以方便自己与其他棋友交流。
最常见的基本棋型大体有以下几种:连五,活四,冲四,活三,眠三,活二,眠二。
①连五:顾名思义,五颗同色棋子连在一起,不需要多讲。
图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
由此 可以确定 所有的棋型,连五,活四,冲四,活三,眠三,活二,眠二。
每一种棋型对应不同的分数,依次递减
在C#里 可以用 一个字典,提前存储起来
public class ChessAI :MonoBehaviour
{//分数字典
protected Dictionary<string, float> toScore = new Dictionary<string, float>();
public ChessType mChessType = ChessType.Black;//表示下棋的类型
protected float[,] score = new float[15, 15];//棋盘里 棋子对应的分数的二维数组,
void Start()
{
//眠二
toScore.Add("aa__", 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("a_a__", 100);
//活二
toScore.Add("__aa__", 500);
toScore.Add("__aa_", 500);
toScore.Add("_a_a_", 500);
toScore.Add("_a__a_", 500);
toScore.Add("_aa__", 500);
//眠3
//toScore.Add("__aaa", 1000);
toScore.Add("a_a_a", 1000);
toScore.Add("_aa_a", 1000);
toScore.Add("a_aa_", 1000);
toScore.Add("_a_aa", 1000);
toScore.Add("aa_a_", 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_aaaa", 15000);//冲四
toScore.Add("aa_aa", 15000);
toScore.Add("_aaaa", 15000);
toScore.Add("aaa_a", 15000);
toScore.Add("aaaa_", 15000);
toScore.Add("_aaaa_", 100000);//活四
toScore.Add("aaaaa",float.MaxValue);//连五
}
protected virtual void FixedUpdate()
{
//下棋
if (ChessBoard.Instance.turn==mChessType&& ChessBoard.Instance.timer>0.3f)
{
PlayerChess();
}
}
//设置分数
public void SetScore(int[] pos)
{
score[pos[0], pos[1]] = 0;
//黑棋 加入评分
CheckOneLine(pos, new int[2] { 1, 0 }, 1);
CheckOneLine(pos, new int[2] { 1, 1 }, 1);
CheckOneLine(pos, new int[2] { 1, -1 }, 1);
CheckOneLine(pos, new int[2] { 0, 1 }, 1);
//白棋 加入评分
CheckOneLine(pos, new int[2] { 1, 0 }, 2);
CheckOneLine(pos, new int[2] { 1, 1 }, 2);
CheckOneLine(pos, new int[2] { 1, -1 }, 2);
CheckOneLine(pos, new int[2] { 0, 1 }, 2);
}
/// <summary>
/// 用来检查一行里 棋子的分数,上左, 左斜右斜,是右偏移量决定的,这里分别对左边,和右边进行检测。
/// </summary>
/// <param name="pos">当前下棋的位置</param>
/// <param name="offset">偏移量,0是x,1是y</param>
/// <param name="chess">棋子类型</param>
public virtual void CheckOneLine(int[] pos, int[] offset, int chess)
{
bool LFirst = true, lStop=false, rStop = false;//LFirst 是否扫左边,LStop 左边停止
int AllNum = 1;//记录一共扫了多少课棋子,扫到7课退出循环
string str = "a";
int ri = offset[0], rj = offset[1];//右边的棋子的位置 i是X位置, j是Y位置
int li = -offset[0], lj = -offset[1];//左边的棋子的位置
while (AllNum<7 && (!lStop||!rStop))
{
//左边
if (LFirst)
{
//边界处理
if ((pos[0] + li >= 0 && pos[0] + li < 15) &&
pos[1] + lj >= 0 && pos[1] + lj < 15&&!lStop)
{
//碰到 chess棋
if (ChessBoard.Instance.grids[pos[0] + li, pos[1] + lj] == chess)
{
AllNum++;
str = "a" + str;
}
else if (ChessBoard.Instance.grids[pos[0] + li, pos[1] + lj] == 0)
{
AllNum++;
//空位置
str = "_" + str;
//如果右边没 停止 就 往右边
if (!rStop)
LFirst = false;
}
else
{
lStop = true; //停左边, 遍历右边
if (!rStop)
LFirst = false;
//chess的对手棋
}
li -= offset[0]; //寻找下个 位置
lj -= offset[1];
}
else //出边界 停止 遍历右边
{
lStop = true; //停左边, 遍历右边
if (!rStop)
LFirst = false;
}
}
else //右边
{
//边界处理
if ((pos[0] + ri >= 0 && pos[0] + ri < 15) &&
pos[1] + rj >= 0 && pos[1] + rj < 15 &&!LFirst )
{
//碰到 chess棋
if (ChessBoard.Instance.grids[pos[0] + ri, pos[1] + rj] == chess)
{
AllNum++;
str += "a" ;
}
else if (ChessBoard.Instance.grids[pos[0] + ri, pos[1] + rj] == 0)
{
AllNum++;
//空位置
str += "_" ;
//如果右边没 停止 就 往右边
if (!lStop)
LFirst = true;
}
else
{
rStop = true; //停左边, 遍历右边
if (!lStop)
LFirst = true;
//chess的对手棋
}
ri += offset[0]; //寻找下个 位置
rj += offset[1];
}
else //出边界 停止 遍历右边
{
rStop = true; //停左边, 遍历右边
if (!lStop)
LFirst = true;
//chess的对手棋
}
}
}
string cmpStr = "";
foreach (var keyVar in toScore )
{
//因为 str遍历出来是7位 可能有多种情况, 遍历取出 所有情况里边 值大的
if (str.Contains(keyVar.Key ))
{
if (cmpStr != "")
{
if (toScore[keyVar.Key]>toScore[cmpStr] )
{
cmpStr = keyVar.Key;
}
}
else
{//第一次
cmpStr = keyVar.Key;
}
}
}
if (cmpStr!="")
{
score[pos[0], pos[1]] += toScore[cmpStr];
}
}
public void PlayerChess()
{
if (ChessBoard.Instance.chessStack.Count == 0)
{
if (ChessBoard.Instance.PlayChess(new int[2] { 7, 7 }))
ChessBoard.Instance.timer = 0;
return;
}
float maxScore = 0;
int[] maxPos = new int[2] { 0, 0 };
//遍历棋盘里的棋子 寻找一个最佳的下棋位置,根据平方函数算出,
for (int i = 0; i < 15; i++)
{
for (int j = 0; j < 15; j++)
{
if (ChessBoard.Instance.grids[i, j] == 0)
{
SetScore(new int[2] { i, j });
if (score[i, j] >= maxScore)
{
maxPos[0] = i;
maxPos[1] = j;
maxScore = score[i, j];
}
}
}
}
if (ChessBoard.Instance.PlayChess(maxPos))
ChessBoard.Instance.timer = 0;
}
}
这里的a就代表 黑棋子,_代表空位置
扫棋的算法比较简单,这里以AI为黑子 为例。
先定义一个String变量 str="a", 表示落子,和一个变量Num来保存一共扫了多少个,
先从左边开始扫,遇到黑棋子,就让它 str="a"+str;左边扫,扫到这个棋子"a",肯定是在落子的左边,再让变量Num++
扫到左边为空的 情况,就让它str="_"+str;再让变量Num++,遇到白子或者边界,就开始扫右边,处理情况和扫左边类似,只不过在遇到黑子的时候,让字符 str+="a";因为扫到的黑子是在落子的右边的,因此,"a"在落子的右边,遇到空子也一样;
pos是当前落子的位置, offset是偏移量,用来应付不同的方法,上下左右,左斜右斜,chess是棋子类型,AI不仅要计算出自己的棋子的最佳落子位置,也要计算出对手的最佳落子位置,抢在对方之前落子。
这里棋盘是15*15的,ChessBoard.Instance.grids保存了所有的棋子,
CheckOneLine(int[] pos, int[] offset, int chess) 这里的pos
public virtual void CheckOneLine(int[] pos, int[] offset, int chess)
{
bool LFirst = true, lStop=false, rStop = false;
int AllNum = 1;
string str = "a";
int ri = offset[0], rj = offset[1];//右边 i是X位置, j是Y位置
int li = -offset[0], lj = -offset[1];//左边
while (AllNum<7 && (!lStop||!rStop))
{
//左边
if (LFirst)
{
//边界处理
if ((pos[0] + li >= 0 && pos[0] + li < 15) &&
pos[1] + lj >= 0 && pos[1] + lj < 15&&!lStop)
{
//碰到 chess棋
if (ChessBoard.Instance.grids[pos[0] + li, pos[1] + lj] == chess)
{
AllNum++;
str = "a" + str;
}
else if (ChessBoard.Instance.grids[pos[0] + li, pos[1] + lj] == 0)
{
AllNum++;
//空位置
str = "_" + str;
//如果右边没 停止 就 往右边
if (!rStop)
LFirst = false;
}
else
{
lStop = true; //停左边, 遍历右边
if (!rStop)
LFirst = false;
//chess的对手棋
}
li -= offset[0]; //寻找下个 位置
lj -= offset[1];
}
else //出边界 停止 遍历右边
{
lStop = true; //停左边, 遍历右边
if (!rStop)
LFirst = false;
}
}
else //右边
{
//边界处理
if ((pos[0] + ri >= 0 && pos[0] + ri < 15) &&
pos[1] + rj >= 0 && pos[1] + rj < 15 &&!LFirst )
{
//碰到 chess棋
if (ChessBoard.Instance.grids[pos[0] + ri, pos[1] + rj] == chess)
{
AllNum++;
str += "a" ;
}
else if (ChessBoard.Instance.grids[pos[0] + ri, pos[1] + rj] == 0)
{
AllNum++;
//空位置
str += "_" ;
//如果右边没 停止 就 往右边
if (!lStop)
LFirst = true;
}
else
{
rStop = true; //停左边, 遍历右边
if (!lStop)
LFirst = true;
//chess的对手棋
}
ri += offset[0]; //寻找下个 位置
rj += offset[1];
}
else //出边界 停止 遍历右边
{
rStop = true; //停左边, 遍历右边
if (!lStop)
LFirst = true;
//chess的对手棋
}
}
}
string cmpStr = "";
foreach (var keyVar in toScore )
{
//因为 str遍历出来是7位 可能有多种情况, 遍历取出 所有情况里边 值大的
if (str.Contains(keyVar.Key ))
{
if (cmpStr != "")
{
if (toScore[keyVar.Key]>toScore[cmpStr] )
{
cmpStr = keyVar.Key;
}
}
else
{//第一次
cmpStr = keyVar.Key;
}
}
}
if (cmpStr!="")
{
score[pos[0], pos[1]] += toScore[cmpStr];
}
}
public class ChessBoard : MonoBehaviour
{
public static ChessBoard Instance { get { return _instance; } }
static ChessBoard _instance;
public ChessType turn = ChessType.Black;
public int[,] grids;//存 1 和2 ,表示黑棋和白棋
public GameObject[] prefabs;
public float timer = 0;
public bool gameStart = true;
public Stack< Transform> chessStack = new Stack<Transform>();//先入后出。
// Use this for initialization
Transform parent;
private void Awake()
{
if (Instance==null)
{
_instance = this;
}
grids = new int[15, 15];
parent = GameObject.Find("Parent").transform;
}
private void FixedUpdate()
{
timer += Time.deltaTime;
}
// Update is called once per frame
void Update ()
{
}
/// <summary>
///
/// </summary>
/// <param name="pos">落子的位置</param>
/// <returns></returns>
public bool PlayChess(int[] pos)
{
if (!gameStart) { return false; }
if (pos[0] < 0 || pos[0] > 14 || pos[1] < 0 || pos[1] > 14)
{
return false;
}
//判断 当前点是否为0 是否下过棋
if (grids[pos[0], pos[1]] != 0) return false;
//黑 是1
if (turn == ChessType.Black)
{//生成黑棋
GameObject go= Instantiate(prefabs[0], new Vector3(pos[0] - 7f, pos[1] - 7f, 0), Quaternion.identity);
chessStack.Push(go.transform);
go.transform.SetParent(parent);
grids[pos[0], pos[1]] = 1;
//判断胜负
if (CheckWiner(pos))
{
GameEnd();
}
// 下轮
turn = ChessType.White;
}
else if (turn == ChessType.White)
{
//白棋
GameObject go= Instantiate(prefabs[1], new Vector3(pos[0] - 7f, pos[1] - 7f, 0), Quaternion.identity);
chessStack.Push(go.transform);
go.transform.SetParent(parent);
grids[pos[0], pos[1]] = 2;
//判断胜负
if (CheckWiner(pos))
{
GameEnd();
}
turn = ChessType.Black;
}
return true;
}
public bool CheckWiner(int[] pos)
{
//横
if (CheckOneLine(pos, new int[2] { 1, 0 })) return true;
//竖
if (CheckOneLine(pos, new int[2] { 0, 1 })) return true;
//左斜
if (CheckOneLine(pos, new int[2] { 1,1 })) return true;
//右斜
if (CheckOneLine(pos, new int[2] { 1,-1 })) return true;
return false;
}
void GameEnd()
{
gameStart = false;
Debug.Log(turn + "胜利");
}
/// <summary>
///
/// </summary>
/// <param name="pos">落子的位置</param>
/// <param name="offest">偏移量</param>
/// <returns></returns>
public void RetractChess()
{
if (chessStack.Count>1)
{
Transform tran = chessStack.Pop();//出栈
grids[(int) (tran.position.x + 7), (int)(tran.position.y + 7)] = 0;
Destroy(tran.gameObject);
tran = chessStack.Pop();//出栈
grids[(int)(tran.position.x + 7), (int)(tran.position.y + 7)] = 0;
Destroy(tran.gameObject);
}
}
public bool CheckOneLine(int[] pos,int[] offest)
{
int linkNum = 1;
//i为x j 为y 左边
for (int i = offest[0], j = offest[1];( i + pos[0] >= 0 && i + pos[0] < 15 )&& (j+pos[1] >= 0 && j + pos[1] < 15); i += offest[0], j += offest[1])
{
if ((int)turn== grids[i+pos[0],j+pos[1]])
{
linkNum++;
}
else
{
break;
}
}
//右边边
for (int i =-offest[0], j = -offest[1]; (i + pos[0] >= 0 && i + pos[0] < 15) && (j + pos[1] >= 0 && j + pos[1] < 15); i -= offest[0], j -= offest[1])
{
if ((int)turn == grids[i + pos[0], j + pos[1]])
{
linkNum++;
}
else
{
break;
}
}
if (linkNum>4)
{
return true;
}
return false;
}
}
public enum ChessType
{
Watch,
Black,
White,
}
用博弈树 和极小极大MiniMax算法,和剪枝算法, 可以实现更加智能的AI