一步一步实现一个完整的围棋游戏
一、画棋盘
可以理解为简单的字符画技巧:第一行、中间与最后一行不同;对于每一行,第一列,中间与最后一列不同。
画星位是有一点技巧的,重点理解这个公式的含义: ((i-4)%6==0 && (j-4)%6==0)
#include<iostream>
using namespace std;
int main(){cout<<"●○"<< endl;// 第1行 cout << "┏ ";for (int i=2; i<=18; i++){cout << "┯ ";}cout << "┓ "<< endl;// 中间17行 for (int i=1; i<=19; i++){cout << "┠ ";for (int j=2; j<=18; j++){if ((i-4)%6==0 && (j-4)%6==0){cout<<"╋ ";} else {cout<<"┼ ";} }cout << "┨ ";cout << endl; } // 第19行 cout << "┗ ";for (int i=2; i<=18; i++){cout << "┷ ";}cout << "┛ "<< endl;return 0;// cout<<"╝╗╔╚║═╧╢╟╤┼╋";// ┯┷┠┨┗┏┓┛┼╋
}
二、落子
为了让效果好一些,首先加入一些颜色。另外,为了方便使用,增加了鼠标操作的支持。这两部分代码的功能在其他文章有论述,这里略去。到目前为止,仍然是在建立基本的框架。
一开始,我们使用左键落子为白色,右键落子为黑色的简单策略。
#include<iostream>
#include "mousetool.cpp"
#include "tools.cpp"
using namespace std;
void drawBoard(){// 第1行 cout << "┏ ";for (int i=2; i<=18; i++){cout << "┯ ";}cout << "┓ "<< endl;// 中间17行 for (int i=2; i<=18; i++){cout << "┠ ";for (int j=2; j<=18; j++){if ((i-4)%6==0 && (j-4)%6==0){cout<<"╋ ";} else {cout<<"┼ ";} }cout << "┨ ";cout << endl; } // 第19行 cout << "┗ ";for (int i=2; i<=18; i++){cout << "┷ ";}cout << "┛ "<< endl;
}
void click(int a, int x, int y){x=x/2*2;if (y>=19 || x>=38){return;}gotoxy(x,y);if (a==1){setColor(15,6);cout << "●";} else {setColor(0,6);cout << "●";}
}
int main(){initMouse();hideCursor();addEvent(1, click);addEvent(2, click);setColor(1,6);drawBoard();listenMouse();//cout<<"●○"<< endl;return 0;// cout<<"╝╗╔╚║═╧╢╟╤┼╋";// ┯┷┠┨┗┏┓┛┼╋
}
三、单键落子
两键落子使用非常不方便,我们希望换成单键落子。因为围棋是一人一手,记录当前落子方的颜色,即可实现单键落子。算法的思路是状态设置。
代码简单,略(可直接下载)
四、增加提子功能
由于采用了单键落子,另外一个键就空下来。现在左键落子,我们事先右键提子功能。
会下围棋的同学都知道,提子一次可以提起多个子,显然提子时一键提起全部相连的棋子是比较方便合理的操作。
从结构上,这里开始使用二维数组存储落子信息。从算法的角度来说,重点是简单的递归。只展示关键部分逻辑。
void pullStone(int a, int x, int y){if (x<0 || y<0 || x>18 || y>18){return;}//cout << a << " " << x << " " << y << endl;if (arr[x][y]==a){arr[x][y]=0;remove(x,y);pullStone(a, x-1, y);pullStone(a, x+1, y);pullStone(a, x, y-1);pullStone(a, x, y+1);}
}
五、自动提子
手工提子和实物状态是相似的,但计算机上理应更方便一些,我们尝试实现自动提子功能。自动提子的关键在于判断一团相连棋子是否有气。
当每次落子后,查看它周围四个方向的棋子,是否存在没有气的情况,如果有,则提起。
这里的算法重点是深度优先搜索。函数noQi,判断某个位置的棋子是否“没有气”。深搜的一个标志性操作是修改状态与恢复状态。
bool noQi(int a, int x, int y){if (x<0 || y<0 || x>18 || y>18){return true;} //cout << a << " " << x << " " << y << endl;if (arr[x][y]==0){return false;} if (arr[x][y]==3-a){return true;} if (arr[x][y]==a){arr[x][y]=-a;bool ret= true;for (int i=-1; i<=1; i++){for (int j=-1; j<=1; j++){if (i*j==0){int x1= x+i;int y1= y+j;ret= ret && noQi(a, x1, y1);}}} arr[x][y]=a;return ret;} return true;
}
六、无气禁入
有了前面的基础,现在可以实现围棋的一个落子规则,落子的位置不能没有气。这个规则就否决了《天龙八部》中虚竹自撞一气直接送掉整条大龙的办法,围棋并不允许这样落子。
当然,落子于无气之处,但是可以直接提子,这就是允许的。 所以我们首先判断是否能提子,能够提子则无条件允许落子,否则检测这枚落子是否“无气”,来决定是否允许落子。
核心逻辑如下:
// 首先预落子arr[x][y]=gSide;// 检查周围是否存在无气的对方棋子,如果有,则提起 int c= 3-gSide, cnt=0;cnt+=pull(c, x-1, y);cnt+=pull(c, x+1, y);cnt+=pull(c, x, y-1);cnt+=pull(c, x, y+1);if (cnt>0){// 有提子的情况下,无条件可以落子,继续操作 } else {// 没有提子,则本子落下不能是无气状态 if (noQi(gSide, x, y)){// 无气,则取消操作 arr[x][y]=0;return; }}
七、最后一步,实现打劫的判断
前面的逻辑已经比较完善,除了打劫。打劫的时候,虽然满足落子的时候可以提子,但不能立即提起对方刚刚提过来的子。但是打二还一的情况又是允许的。那么打劫规则应当怎样实现呢?
算法思路是这样的:首先如果刚刚对方的落子导致了提子,并且提子是一个子,则记录这个落子的位置。那么这个时候,本方的落子如果仍然只提一个子,而且提的子恰好是这个子,则不允许。
在前面的基础上,算法逻辑修改如下:
// 首先预落子arr[x][y]=gSide;// 检查周围是否存在无气的对方棋子,如果有,则提起 int c= 3-gSide, cnt=0;cnt+=pull(c, x-1, y);cnt+=pull(c, x+1, y);cnt+=pull(c, x, y-1);cnt+=pull(c, x, y+1);if (cnt>1){// 有提多子的情况下,无条件可以落子,继续操作 } else if (cnt==1){// 恰好提一子,先看所提子是否是刚刚落下的 if (arr[prex][prey]==0){// 提刚刚落下的子,首先还原状态,再看本子是否无气 arr[prex][prey]=c;if (noQi(gSide, x, y)){// 如果无气,说明是打劫状态,则不能提,还原场景 putStone(c, prex, prey);arr[x][y]=0;return;} else {// 有气状态,则允许,再次还原 arr[prex][prey]=0; }} } else {// 没有提子,则本子落下不能是无气状态 if (noQi(gSide, x, y)){// 无气,则取消操作 arr[x][y]=0;return; }}
八、未实现的功能
功能的实现是没有穷尽的,我们这个小软件实现了完整的单机热座对弈功能。但没有记录棋谱的功能,更没有读谱的功能。最令人发指的是,竟然没有收官后自动数子的功能。有兴趣的同学可以自行尝试,这不是一个非常简单的功能。
一步一步实现一个完整的围棋游戏相关推荐
- 【Unity教程】创建一个完整的驾驶游戏
专业游戏设计 你会学到什么 在unity HDRP创建一个完整的驾驶游戏 定制不同类型的汽车 将人工智能汽车和人工智能航路点系统添加到你的赛道上 添加汽车展厅菜单以解锁和购买新车 在Blender中设 ...
- SwiftUI 精品项目之完整Go围棋游戏App支持在线对战OGS Alamofire SocketIO(教程含源码)
实战需求 SwiftUI 精品项目之完整Go围棋App支持在线OGS Alamofire SocketIO 本文价值与收获 看完本文后,您将能够作出下面的界面 基础知识 本项目是一个完全由Swift和 ...
- HTML5 canvas 游戏设计:创建一个经典的魔塔游戏
整个项目都已经开源,项目地址:https://github.com/m8705/MAGIC-TOWER-JS 注:这是我高中时候的作品,BUG 很多,已经不再更新了.下载项目到本地就能玩. 前言 魔塔 ...
- JavaXYQ 1.4 M1 - 完整的RPG游戏
JavaXYQ 1.4 M1补充了游戏任务系统.游戏存档.商店.背景音乐.场景内自动寻路及整合游戏脚本等,改进了UI框架.人物8方向行走算法等,使之成为一个完整的RPG游戏. 近期会继续完善游戏的制作 ...
- 只需八步,做一个完整的数据分析
公众号后台回复"图书",了解更多号主新书内容作者:小熊妹来源:码工小熊 很多小伙伴不清楚做数据分析的流程,经常疑惑:到底做到什么程度才算是一个完整的分析?其实,数据分析是有标准模板 ...
- 如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文
本系列所有文章 如何一步一步用DDD设计一个电商网站(一)-- 先理解核心概念 如何一步一步用DDD设计一个电商网站(二)-- 项目架构 如何一步一步用DDD设计一个电商网站(三)-- 初涉核心域 如 ...
- button active 跳转到另一个页面_一步一步实现一个古诗词网站(四)——首页
汪小黑:一步一步实现一个古诗词网站(三)--首页zhuanlan.zhihu.com 在上篇文章中,我们一步一步的实现了我们的静态首页,从中学习到了页面布局方面的知识. 在这篇文章中,我们将使用 J ...
- 如何一步一步用DDD设计一个电商网站(十三)—— 领域事件扩展
本系列所有文章 如何一步一步用DDD设计一个电商网站(一)-- 先理解核心概念 如何一步一步用DDD设计一个电商网站(二)-- 项目架构 如何一步一步用DDD设计一个电商网站(三)-- 初涉核心域 如 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(五)——一步一步教你如何撸Dapr之状态管理...
状态管理和上一章的订阅发布都算是Dapr相较于其他服务网格框架来讲提供的比较特异性的内容,今天我们来讲讲状态管理. 目录: 一.通过Dapr实现一个简单的基于.net的微服务电商系统 二.通过Dapr ...
最新文章
- 利用lrz、lsz工具在linux与windows之间传输文件
- 北京内推 | 京东推荐招聘内容推荐研发工程师(2022届校招)
- php多个构造方法,php多构造器的实例代码
- python3.7不能用_解决Python3.7.0 SSL低版本导致Pip无法使用问题
- mba案例分析_2020年(第八届)MBA企业案例分析实践课程暨大赛完美收官!
- 【转载】合理规划您的硬盘分区
- cruzer php sandisk 闪迪u盘量产工具_闪迪u3量产工具下载|
- 中国移动日渐步履蹒跚,中国电信在5G商用上取得领先优势
- 如何禁止Apache静态文件缓存
- ROS学习【2】-----ubuntu16.04中进行ROS通信编程(话题编程)
- 我的青春谁做主经典台词
- linux 系统睡眠.休眠命令
- Twig中控制保留小数位数
- screenfull全屏显示
- 软科计算机科学与工程专业,2019上海软科世界一流学科排名计算机科学与工程专业排名利物浦大学排名第301-400...
- Matlab常用代码---持续更新
- 动环监控系统机房智能监测系统
- 430分计算机专业好的大学,2021高考430分左右上什么大学好
- Linux0.11内核源码解析-setup.s
- Spring Boot 速记教程