五子棋基本玩法-AI实现
参考: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 initializationTransform 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 framevoid 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;//黑 是1if (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
https://baike.baidu.com/item/%E5%8D%9A%E5%BC%88%E6%A0%91%E5%90%AF%E5%8F%91%E5%BC%8F%E6%90%9C%E7%B4%A2/19480042
五子棋基本玩法-AI实现相关推荐
- AI视觉组仙人一步之高级玩法——从Python回归C语言
开心的程序猿@NXP 2021-02-04 Thursday 读过之前两篇的童鞋们,想来已经开始着手开发属于自己的AI视觉应用了,当然,手中还没有OpenART套件的朋友们,也不用着急,可以先参照 ...
- 十亿红包还不够,揭秘快手春节四大技术玩法:AI/AR/MR都被装进App,为了这个春晚真拼了...
郭一璞 发自 凹非寺 量子位 报道 | 公众号 QbitAI 要不大家现在都喜欢科技互联网公司呢. 如今每年春节,这些公司都是绞尽脑汁.争前恐后想给全国人民拜年.互动,发红包. 这不,BAT之后,今年 ...
- 谷歌AI的七个“不正经”玩法,个个能玩一整天
郭一璞 问耕 发自 凹非寺 量子位 报道 | 公众号 QbitAI 谁说AI只能搞一些一本正经的工作? 谷歌最近就放出了几只比较搞笑的AI,可以完成一些不走寻常路的工作,比如:帮你实现靠脸吃饭,让你 ...
- 小程序源码:AI微信小程序源码下载人脸照片AI转换动漫照片全新源码安装简单无需服务器域名-多玩法安装简单
这是一款AI人脸转动漫的一款微信小程序源码 该款小程序源码无需服务器和域名 搭建安装简单 有多种风格转换模式可自选 安装教程: 首先解压源码然后把源码上传到微信开发者工具打开 另外设置一个合法域名 合 ...
- 2023 最新 抖音AI换脸表情包小程序变现玩法项目
单条视频变现1万 普通人也能轻松玩转 表情包小程序变现在抖音的玩法一直越来越好,最近新出的AI换脸表情包玩法更有意思,可以预见的是这是一个有红利期的,短平快的项目,刚开始大家都会特别感兴趣,都会把自己 ...
- 美颜、美妆、美体…AI美颜SDK还有这些玩法?
AI技术经过多年的发展,早已今昔非比,例如在美颜领域的应用,更是让美颜SDK的可玩性和技术性提高了很大一个层次,可以说是"划时代"的升级迭代.在接入AI美颜SDK的短视频.直播等社 ...
- 《花雕学AI》23:中文调教ChatGPT的秘诀:体验测试与通用案例,解锁无限有趣玩法!
引言: 你有没有想过和一台智能机器人聊天?你有没有想过让一台智能机器人为你创作诗歌.故事或歌曲?你有没有想过让一台智能机器人陪你玩游戏.学习或社交?如果你的答案是肯定的,那么你一定会对ChatGPT感 ...
- 跨时代的AI新品——JetMax机械臂,带来人工智能新玩法!
作为一家秉承初心的AI教育机器人公司,我们一直为大家提供各种有趣且开源的AI机器人产品. 2020年下半年,我们幻尔的工程师们打算在机器人上注入更多高级的人工智能元素,打造出一个跨时代的.更高级的AI ...
- 让虞书欣、李诞拍到停不下来!AR+AI双引擎的互动小游戏,如何打开IP新玩法?...
有眼尖的朋友已经发现,近期爱奇艺的热播剧们有了新"看"法:在日常追剧的间隙,结合了IP要素的趣味视频互动特效小游戏玩法吸引了百万用户参与互动的热潮,更激发了The9全员.李诞等娱乐 ...
最新文章
- R语言与数据分析:时间序列简单介绍
- 默认网关及route print
- Web使用热敏打印小票(IE环境)
- 树莓派要mysql的密码_树莓派raspberry Pi 3B+系统中安装mysql过程中不提示输入密码,安装完后如何设置密码...
- 破坏计算机信息系统功能罪,破坏计算机信息系统罪
- python并发编程2-进程
- python读取配置文件获取所有键值对_python读取配置文件
- android 初始化语言,25.Android init language (安卓初始化语言)
- linux判断值相等_Shell字符串比较相等、不相等方法小结【转】
- 零基础学python数据分析_Python学习指南:使用Python学习数据分析
- javascript 将毫秒值转换为天-小时-分钟-秒钟
- 工控项目开发框架介绍
- 使用JSONP解决同源限制问题
- Python模块的使用
- 源码安装php5.5
- 网页设计html轮播代码,20行js代码实现网页轮播图效果
- Java面试--观察者模式
- [Java] 身份证号码验证
- 昆明oracle考试点,Oracle认证考试知识点:修改sid的步骤
- 2017.10.23 Arduino Atmel EFM32低功耗监测