项目效果展示

走法产生

  • 如何产生
  1. 走法产生是指将一个局面的所有可能的走法罗列出来的那一部分程序。也就是用来告诉其他部分下一部分可以网哪里走的模块。在象棋里,象可以走田,你就需要检查与这个象相关联的象位上是否有自己的棋,并且要检查其间的象眼上是否有棋子。而兵则要注意是不可后退并一次只能前进一步。假定现在有一个轮到红方走棋的局面。要列举出红方所有合乎规则的走法。
  2. 首先要扫面棋盘,如果某一个位置上是一个红方棋子,则根据该棋子的类型找出该棋子的所有可走位。例如该棋子是马,检查马的纵横和横纵方向上的距离分别为1和2的所有位置是否有红方棋子,如某位置没有,则还要检查该方向上与马的纵横距离均为1的位置是否有棋子。如没有,该位置就是前述红马的一个合法走步,其他的棋子也要经历各自的判断程序,最后找出所有合法的走步。
  3. 一个象棋程序走法产生的代码如下:
void GenerateAllMove(BYTE position[10][9],int nColor)
{int i,j;for(i = 0;i < 10;i++)  //循环扫描传入的9*10 BYTE 的棋盘 {for(j = 0;j < 9; j++){if(GetColor(position[i][j]) == nColor){//只处理指定颜色的棋子switch(position[i][j]){case RED_KING:GenR_KingMove(position,i,j); //产生红将的走法case BLACK_KING:GenR_KingMove(position,i,j);  //产生黑将的走法case RED_PAWN:GenR_PawnMove(position,i,j); //产生红兵的走法case BLACK_PAWN:GenB_PawnMove(position,i,j);  //产生黑兵的走法..............//其他棋子的类似处理}}}}//end fo switch
}//end of if//产生黑兵的走法
void GenB_PawnMove(BYTE position[10][9],int i,int j)
{int x,y;y = i-1;x = j;//如果是向前方向并且没有本方棋子阻挡,就是合法走法if(y > 0&& ! IsSameSide(nChessID,position[x][y]))AddMove(j,i,x,y);  //将起点和终点加入可走步队列if(i < 5){//如果兵以过河y=i;x=j+1; //向右横走if(x < 9 && !IsSameSide(nChessID,position[y][x]))AddMove(j,i,x,y);  //将起点和终点加入可走步队列x = j-1; //向左横走if(x >= 0&& !IsSameSide(nChessID,position[y][x]))AddMove(j,i,x,y);  //将起点和终点加入可走步队列}
}
void GenR_PawnMove(int position,int i,in tj);  //产生红兵的走法
{............
} 

上述c 程序片段基于9 * 10二维数组的棋盘定义,并隐含默认黑方初始在0到4行,红方初始在5到9行。在这个小小的片段里,我们可以看出仅仅一个兵的走法就需要做出若干判断后才可以完成。

有些程序员为了去除函数调用的开销,将分别判断的小函数去掉,而将所有判断写在一个长长的switch当中来代替,这在一定程度上提高了走法产生的速度。

  • 效率问题
  1. 走法产生通常伴随着搜索进行,并且调用频繁。但是像中国象棋或国际象棋这样有复杂规则棋类,走法的产生往往导致大量繁琐的判断。这在一定程度上形成了程序的性能瓶颈。
  2. 在象棋中,每一种棋子的移动规则各有不同,这样的情形导致了较为复杂的判断。但是我们可以将每种棋子在某一位置上的最大可走步建成一个数据库。在产生走法时直接从中取出数据,进行少量判断就可以算出该棋子的合法走步。
  3. 象的走法是田字格,并且象眼要空白。一般的做法是这样:

1、先检查该棋子周围与该棋子横纵坐标差的绝对值均为2的位置是否落在己方半边棋盘上,如某个点超出己方半边棋盘,将其去除。

2、检查剩下的位置上是否有己方棋子,如有将其去除。

3、检查剩下的位置方向上与该棋子横纵坐标差的绝对值均为1的象眼上是否有棋子,如有将其去除。

4、剩下的位置即是合法走不。

而如果将象的可走位及象眼位置存入数据库,枚举象在某一位置的合法走步就只需如下操作:

1、从库中取出该位置上象的可走位置,检查可走位置上有无己方棋子。

2、从库中取出该位置上象的象眼位置,检查象眼上有无棋子。

显然,此操作的过程更为简单,但在去数据库中的数据时必须有快速的定址方法,否则次操作未必有速度上的优势。实际上,由于每一位置上只能有一个棋子,我们可以轻易地使用棋子位置作为数据库中的定址依据而快速的数据读取。

第一章        绘制棋盘

  • 画10条横线
  • 画9条竖线
  • 画将军的九宫格
#include "Board.h"
#include <QPainter>Board::Board(QWidget *parent) :QWidget(parent)
{
}void Board::paintEvent(QPaintEvent *)
{QPainter painter(this);int d = 40;// 画10横线for(int i=1; i<=10; ++i){painter.drawLine(QPoint(d, i*d), QPoint(9*d, i*d));}// 画9竖线for(int i=1; i<=9; ++i){if(i==1 || i==9)painter.drawLine(QPoint(i*d, d), QPoint(i*d, 10*d));else{painter.drawLine(QPoint(i*d, d), QPoint(i*d, 5*d));painter.drawLine(QPoint(i*d, 6*d), QPoint(i*d, 10*d));}}// 九宫格painter.drawLine(QPoint(4*d, 1*d), QPoint(6*d, 3*d));painter.drawLine(QPoint(6*d, 1*d), QPoint(4*d, 3*d));painter.drawLine(QPoint(4*d, 8*d), QPoint(6*d, 10*d));painter.drawLine(QPoint(6*d, 8*d), QPoint(4*d, 10*d));}

第二章         初始化棋子

  • 绘制32个棋子
#include "Board.h"
#include <QPainter>
#include <QMouseEvent>Board::Board(QWidget *parent) :QWidget(parent)
{for(int i=0; i<32; ++i){_s[i].init(i);}_selectid = -1;_bRedTurn = true;
}void Board::paintEvent(QPaintEvent *)
{QPainter painter(this);int d = 40;_r = d/2;// 画10横线for(int i=1; i<=10; ++i){painter.drawLine(QPoint(d, i*d), QPoint(9*d, i*d));}// 画9竖线for(int i=1; i<=9; ++i){if(i==1 || i==9)painter.drawLine(QPoint(i*d, d), QPoint(i*d, 10*d));else{painter.drawLine(QPoint(i*d, d), QPoint(i*d, 5*d));painter.drawLine(QPoint(i*d, 6*d), QPoint(i*d, 10*d));}}// 九宫格painter.drawLine(QPoint(4*d, 1*d), QPoint(6*d, 3*d));painter.drawLine(QPoint(6*d, 1*d), QPoint(4*d, 3*d));painter.drawLine(QPoint(4*d, 8*d), QPoint(6*d, 10*d));painter.drawLine(QPoint(6*d, 8*d), QPoint(4*d, 10*d));// 绘制32个棋子for(int i=0;i <32; ++i){drawStone(painter, i);}}QPoint Board::center(int row, int col)
{QPoint ret;ret.rx() = (col+1)* _r*2;ret.ry() = (row+1)* _r*2;return ret;
}QPoint Board::center(int id)
{return center(_s[id]._row, _s[id]._col);
}void Board::drawStone(QPainter& painter, int id)
{if(_s[id]._dead)return;QPoint c = center(id);QRect rect = QRect(c.x()-_r, c.y()-_r, _r*2, _r*2);if(id ==_selectid)painter.setBrush(QBrush(Qt::gray));elsepainter.setBrush(QBrush(Qt::yellow));painter.setPen(Qt::black);painter.drawEllipse(center(id), _r, _r);if(_s[id]._red)painter.setPen(Qt::red);painter.setFont(QFont("system", _r, 700));painter.drawText(rect, _s[id].getText(), QTextOption(Qt::AlignCenter));}
// 效率不高,应该改进
bool Board::getRowCol(QPoint pt, int &row, int &col)
{for(row=0; row<=9; row++){for(col=0; col<=8; col++){QPoint c = center(row, col);int dx = c.x() - pt.x();int dy = c.y() - pt.y();int dist = dx*dx+dy*dy;if(dist < _r*_r)return true;}}return false;
}
bool Board::canMove1(int moveid, int row, int col, int killid)
{/*1.首先目标位置在九宫内2.移动的步长是一个格子*/if(_s[moveid]._red){if(row > 2)return false;}else{if(row < 7)return false;}if(col < 3) return false;if(col > 5) return false;int dr = _s[moveid]._row - row;int dc = _s[moveid]._col - col;int d = abs(dr)*10 + abs(dc); // 12, 21   22   10, 1if(d == 1 || d == 10)return true;return false;}
bool Board::canMove2(int moveid, int row, int col, int killid)
{if(_s[moveid]._red){if(row > 2)return false;}else{if(row < 7)return false;}if(col < 3) return false;if(col > 5) return false;int dr = _s[moveid]._row - row;int dc = _s[moveid]._col - col;int d = abs(dr)*10 + abs(dc); // 12, 21   22   10, 1if(d == 11)return true;return false;}
bool Board::canMove3(int moveid, int row, int col, int killid)
{return true;
}
bool Board::canMove4(int moveid, int row, int col, int killid)
{return true;
}
bool Board::canMove5(int moveid, int row, int col, int killid)
{return true;
}
bool Board::canMove6(int moveid, int row, int col, int killid)
{return true;
}
bool Board::canMove7(int moveid, int row, int col, int killid)
{return true;
}bool Board::canMove(int moveid, int row, int col, int killid)
{if(_s[moveid]._red == _s[killid]._red)//moveid和killid颜色相同){//换选择_selectid = killid;update();return false;}switch(_s[moveid]._type){case Stone::JIANG:return canMove1(moveid, row, col, killid);break;case Stone::SHI:return canMove2(moveid, row, col, killid);break;case Stone::XIANG:return canMove3(moveid, row, col, killid);break;case Stone::CHE:return canMove4(moveid, row, col, killid);break;case Stone::MA:return canMove5(moveid, row, col, killid);break;case Stone::PAO:return canMove6(moveid, row, col, killid);break;case Stone::BING:return canMove7(moveid, row, col, killid);break;}return true;}void Board::mouseReleaseEvent(QMouseEvent *ev)
{QPoint pt = ev->pos();// 将pt转化成象棋的行列值// 判断这个行列值上面有没有棋子int row, col;bool bRet = getRowCol(pt, row, col);if(bRet == false) // 点到棋盘外return;int i;int clickid = -1;for(i=0;i<32;++i){if(_s[i]._row == row && _s[i]._col == col && _s[i]._dead== false){break;}}if(i<32){clickid = i;}if(_selectid == -1){if(clickid != -1){if(_bRedTurn == _s[clickid]._red){_selectid = clickid;update();}}}else{if(canMove(_selectid, row, col, clickid)){/*走棋*/_s[_selectid]._row = row;_s[_selectid]._col = col;if(clickid != -1){_s[clickid]._dead = true;}_selectid = -1;_bRedTurn = !_bRedTurn;update();}}}

第三章        走棋 象棋走棋规则和轮流规则

  • 返回象棋棋盘行列对应的行列值
  • 判断这个行列值上面有没有棋子
  • 点到棋盘外就false
void Board::mouseReleaseEvent(QMouseEvent *ev)
{QPoint pt = ev->pos();// 将pt转化成象棋的行列值// 判断这个行列值上面有没有棋子int row, col;bool bRet = getRowCol(pt, row, col);if(bRet == false) // 点到棋盘外return;int i;int clickid = -1;for(i=0;i<32;++i){if(_s[i]._row == row && _s[i]._col == col && _s[i]._dead== false){break;}}if(i<32){clickid = i;}if(_selectid == -1){if(clickid != -1){if(_bRedTurn == _s[clickid]._red){_selectid = clickid;update();}}}else{if(canMove(_selectid, row, col, clickid)){/*走棋*/_s[_selectid]._row = row;_s[_selectid]._col = col;if(clickid != -1){_s[clickid]._dead = true;}_selectid = -1;_bRedTurn = !_bRedTurn;update();}}}

第四章

  • 一步象棋人工智能
  • 两步的人工智能
  • 走N步的人工智能
  • 优化-剪枝算法
  • 最小值最大值算法
#include "SingleGame.h"
#include <QTimer>
void SingleGame::click(int id, int row, int col)
{if(!this->_bRedTurn)return;Board::click(id, row, col);if(!this->_bRedTurn){/* 启动0.1秒定时器,在0.1秒后电脑再思考 */QTimer::singleShot(100, this, SLOT(computerMove()));}}
void SingleGame::computerMove()
{Step* step = getBestMove();moveStone(step->_moveid, step->_killid,step->_rowTo, step->_colTo);delete step;update();
}void SingleGame::getAllPossibleMove(QVector<Step *> &steps)
{int min=16, max=32;if(this->_bRedTurn){min = 0, max = 16;}for(int i=min; i<max; ++i){if(_s[i]._dead) continue;for(int row=0; row<=9; ++row){for(int col=0; col<=8; ++col){int killid = this->getStoneId(row, col);if(sameColor(killid, i)) continue;if(canMove(i, killid, row, col)){saveStep(i, killid, row, col, steps);}}}}}void SingleGame::fakeMove(Step* step)
{killStone(step->_killid);moveStone(step->_moveid, step->_rowTo, step->_colTo);
}void SingleGame::unfakeMove(Step* step)
{reliveStone(step->_killid);moveStone(step->_moveid, step->_rowFrom, step->_colFrom);
}/* 评价局面分 */
int SingleGame::calcScore()
{int redTotalScore = 0;int blackTotalScore = 0;//enum TYPE{CHE, MA, PAO, BING, JIANG, SHI, XIANG};static int chessScore[] = {100, 50, 50, 20, 1500, 10, 10};// 黑棋分的总数 - 红旗分的总数for(int i=0; i<16; ++i){if(_s[i]._dead) continue;redTotalScore += chessScore[_s[i]._type];}for(int i=16; i<32; ++i){if(_s[i]._dead) continue;blackTotalScore += chessScore[_s[i]._type];}return blackTotalScore - redTotalScore;}int SingleGame::getMaxScore(int level, int curMinScore)
{if(level == 0) return calcScore();// 1.看看有那些步骤可以走QVector<Step*> steps;getAllPossibleMove(steps);   // 是红旗的possiblemoveint maxScore = -100000;while(steps.count()){Step* step = steps.back();steps.removeLast();fakeMove(step);int score = getMinScore(level-1, maxScore);unfakeMove(step);delete step;if(score >= curMinScore){while(steps.count()){Step* step = steps.back();steps.removeLast();delete step;}return score;}if(score > maxScore){maxScore = score;}}return maxScore;}int SingleGame::getMinScore(int level, int curMaxScore)
{if(level == 0) return calcScore();// 1.看看有那些步骤可以走QVector<Step*> steps;getAllPossibleMove(steps);   // 是红旗的possiblemoveint minScore = 100000;while(steps.count()){Step* step = steps.back();steps.removeLast();fakeMove(step);int score = getMaxScore(level-1, minScore);unfakeMove(step);delete step;if(score <= curMaxScore){while(steps.count()){Step* step = steps.back();steps.removeLast();delete step;}return score;}if(score < minScore){minScore = score;}}return minScore;}Step* SingleGame::getBestMove()
{/*2.试着走一下3.评估走的结果4.取最好的结果作为参考*/// 1.看看有那些步骤可以走QVector<Step*> steps;getAllPossibleMove(steps);// 2.试着走一下// 3.评估走的结果int maxScore = -100000;Step* ret = NULL;while(steps.count()){Step* step = steps.back();steps.removeLast();fakeMove(step);int score = getMinScore(_level-1, maxScore);unfakeMove(step);if(score > maxScore){maxScore = score;if(ret) delete ret;ret = step;}else{delete step;}}// 4.取最好的结果作为参考return ret;}

第五章

  • 网络版本的实现
  • android移植

以下是 NetGame.h:

#ifndef NETGAME_H
#define NETGAME_H#include "Board.h"
#include <QTcpServer>
#include <QTcpSocket>/*1) 执红方还是黑方,这个信息有服务器发出,客户端接收第一个字节固定是1,第二个字节是1,或者0,1表示接收方走红旗,0表示走黑棋2)点击信息第一个字节固定是2,第二个字节是row,第三个字节是col,第四个字节是点击的棋子id
*/
class NetGame : public Board
{Q_OBJECT
public:NetGame(bool server);~NetGame();QTcpServer* _server;QTcpSocket* _socket;void click(int id, int row, int col);public slots:void slotNewConnection();void slotRecv();};#endif // NETGAME_H

以下是NetGame.cpp :

#include "NetGame.h"
#include <QDebug>
NetGame::NetGame(bool server)
{_server = NULL;_socket = NULL;if(server){_server = new QTcpServer(this);_server->listen(QHostAddress::Any, 9999);connect(_server, SIGNAL(newConnection()),this, SLOT(slotNewConnection()));}else{_socket = new QTcpSocket(this);_socket->connectToHost(QHostAddress("127.0.0.1"), 9999);connect(_socket, SIGNAL(readyRead()),this, SLOT(slotRecv()));}}NetGame::~NetGame()
{}void NetGame::click(int id, int row, int col)
{if(_selectid == -1 && id != -1){if(_s[id]._red != _bSide)return;}Board::click(id, row, col);/* 发送给对方 */char buf[4];buf[0] = 2;buf[1] = 9-row;buf[2] = 8-col;buf[3] = id;_socket->write(buf, 4);}void NetGame::slotRecv()
{QByteArray ba = _socket->readAll();char cmd = ba[0];if(cmd == 1){// 初始化char data = ba[1];init(data==1);}else if(cmd==2){int row = ba[1];int col = ba[2];int id = ba[3];Board::click(id, row, col);}
}void NetGame::slotNewConnection()
{if(_socket) return;_socket = _server->nextPendingConnection();connect(_socket, SIGNAL(readyRead()),this, SLOT(slotRecv()));/* 给对方发送数据 */char buf[2];buf[0] = 1;buf[1] = 0;_socket->write(buf, 2);init(buf[1]==0);}

总结

人人对战,还是比较简单,只要能够做到精确计算坐标点,就可以实现。

最重要的是人机对战的方面,需要用到比如最大值最小值的算法,还要用到剪枝+优化的算法,还是比较难。第一次接触算法的人可能就比较简单,所以我想发表一下感言:懂数学的人真的能走遍天下。

中国象棋(人机博弈)相关推荐

  1. 课程设计两连发之中国象棋人机博弈

    我今年大四,明年就毕业了.料想为课程设计写的这两个程序以后也不会再拿出来看了,索性就开源了.希望可以帮助到大家,可以当课程设计交差,也可以优化一下发扬光大. 这个前后大概花了两个星期.写好到目前为止还 ...

  2. 中国象棋人机博弈程序(扁平化棋局) C语言实现

    为什么80%的码农都做不了架构师?>>>    平台是Visual studio 2013 windows8.1  64位 先来一个效果图吧,原先是挺清晰的,但是200K以上不能上传 ...

  3. 扁平化c语言教程,中国象棋人机博弈程序(扁平化棋局) C语言实现

    平台是Visual studio 2013 windows8.1  64位 先来一个效果图吧,原先是挺清晰的,但是200K以上不能上传,所以搞模糊了,这个界面是扁平风格的,个人感觉不错,很佩服作者,我 ...

  4. 基于QT实现的alpha-beta剪枝算法搜索的象棋人机博弈游戏

    中国象棋是一个古老的而富有智慧的游戏,而中国象棋博弈程序是将计算机知识和中国象棋知识结合起来的一种新型的游戏方式.它以一种全新的人机博弈方式突破了以往传统象棋游戏只能人与人对战的限制,使得这个古老的游 ...

  5. java象棋人机代码_中国象棋人机对弈Java版源码

    [实例简介] 中国象棋人机对弈Java版源码,包含人工智能实现(含多个难度级别,采用α-β迭代搜索算法) [实例截图] [核心代码] 中国象棋人机对弈Java版源码 ├── boards │   ├─ ...

  6. 《游戏学习》纯JS中国象棋人机对战html游戏源码

    源码下载地址:chinese_chess.zip_象棋机器人对战js-网络游戏文档类资源-CSDN下载 下载解压后,文件如下 html页面源码如下 <!DOCTYPE html> < ...

  7. java 明棋妙重新循环_Java实现中国象棋(人机对战)

    目录 简介 成品视频 实现思路 界面实现分为了三块 棋盘抽象类 按钮组抽象类 棋子绘制接口 棋盘界面实现 棋子的实现 按钮组的实现 监听工厂和监听类 棋盘绘制类的实现 开始游戏实现 停止游戏实现 游戏 ...

  8. python中国象棋人机大战_还记得浪潮杯首届象棋人机大战吗?五位高手被电脑18回合打败了...

    [DhtmlXQ] [DhtmlXQ_init]500,350[/DhtmlXQ_init] [DhtmlXQ_title]浪潮杯人机大战,电脑vs五位高手[/DhtmlXQ_title] [Dhtm ...

  9. 浪潮杯首届中国象棋人机大战背景 [服务器新闻动态] IT.com.cn IT

    人工智能(AI)领域的快速发展,将对社会带来重大的影响,这引发了许多激烈的争论.一些人认为,它将驱动经济增长,为改善生活质量带来数不清的机会.尽管我们相信那些最深的恐惧其实是夸大其词的,但认知技术这一 ...

  10. Pygame实战:中国象棋人机对抗赛今开战、谁占上风?要不要来一盘试试?

最新文章

  1. 元宇宙不是下一代互联网,而是人类群体思维空间或梦境世界的具现
  2. 克隆复制可使用原型( Prototype)设计模式
  3. Protocol Buffer基本语法
  4. 一个5节点的polardb mysql_POLARDB问题
  5. oracle表空间 设置,Oracle表空间怎么设置和管理
  6. html5 drawimage参数,HTML canvas
  7. MySQL中的isnull、ifnull和nullif函数用法
  8. 6.高性能MySQL --- 查询性能优化(1)
  9. OMP算法的物理意义表示
  10. 游戏设计入门——游戏程序框架设计
  11. Java 编程问题:九、函数式编程——深入研究
  12. win下使用curl
  13. Python实现AES加密与解密
  14. egret 图文并排
  15. 【RuoYi-Vue-Plus】扩展笔记 01 - 集成 JavaMail 发送邮件(源码)
  16. python 批量图片局部高斯模糊(图片水印处理)
  17. python循环展示大写字母_python调用大写函数python中字典的循环遍历的两种方式
  18. 互动媒体技术——《代码本色》习作二:向量
  19. 田野调查手记·浮山篇(一)
  20. web自动化--python+selenium自动化

热门文章

  1. 备考2个月如何一次性通过信息系统项目管理师
  2. 百度之星 度度熊的午饭时光
  3. HTML基本语法格式(元素、标签)
  4. 如何在html中使用 es6语法让浏览器识别
  5. 叮铃铃~:前端铃声组件easy-ring 1.0版本文档
  6. k620显卡linux驱动下载,NVIDIA英伟达Quadro系列专业显卡官方驱动
  7. Server JRE 简介
  8. 项目管理软件-禅道-内网部署
  9. Android UI 自定义控件大全
  10. Java —— 连接邮箱,读取收件箱中的邮件