一、简介
最近突发奇想,要使用C++做一个双人象棋的程序,昨天肝了一天,终于把算法部分完成了,下面把开发过程中的经验分享一下。
开发环境:Visual Studio 2019
语言标准:C++11及以上
纠错:暂无
二、准备工作
知识要求:
-
熟练掌握C++语言面向对象编程的知识(继承,多态)
-
掌握STL的基本操作
-
了解中国象棋基本规则(不会还有人不知道中国象棋规则吧!)
既然都知道了,下面说一个大家可能没注意过的点: -
象棋棋盘尺寸为9×10,9列10行
象棋摆法
三、程序框架
由于这是双人象棋,所以算法主要就是判断胜负(容易实现)和判断棋子能否走到某个地方(难点)。这篇博客主要就介绍这两个问题。
程序主要由以下几个类组成:
- Point
记录棋盘中的一个坐标,并且附带基本功能,如判断在红方区域还是黑方区域,是否在九宫格中等。 - ChessBoard
棋盘类,管理所有棋子。 - ChessPiece
所有棋子的基类,也是一个抽象类。 - 七种棋子分别对应的七个类
都是ChessPiece的子类。 - ChessGame
- 象棋游戏类,管理先后手、胜负等。
关于棋子的存储
关于棋子的存储,有两种方式,一是把红黑双方棋子分别存储到两个容器中,优点是便于知道一方还有哪些棋子,但知道坐标查找棋子则很困难。
还有一种方法,是用一个10×9的数组(0-4红方区域,5-9黑方区域,这一点很重要),分别存储每个棋子或空(nullptr),优点是知道坐标便于查找棋子,但知道一方还有哪些棋子则很困难。
为了方便,我们把这两种方法综合起来,既分别存储双方棋子,又存储棋盘状态。
四、代码实现
以下代码均在Chess.h中。
一些常量的声明
const bool BLACK = 0, RED = 1;
const uint8_t NONE = 2;
Point
最简单的一个类,无脑写就行。
class Point
{
public:
int8_t x, y;
Point(int8_t nx, int8_t ny) :x(nx), y(ny) {}
bool ColorOfTheArea()const//判断在红方区域还是黑方区域
{
if (y <= 4)
return RED;
return BLACK;
}
bool IsInNinePalaces()const//是否在九宫格中
{
return x >= 3 && x <= 5 && (y <= 2 || y >= 7);
}
};
bool operator==(const Point& a, const Point& b)
{
return a.x == b.x && a.y == b.y;
}
ChessPiece
class ChessPiece
{
protected:
Point pt;
ChessBoard& board;
public:
const bool cl;
ChessPiece(const Point& point, bool color, ChessBoard& chessboard) :pt(point), cl(color), board(chessboard)
{
if (cl == BLACK)
board.black.push_back(this);
else
board.red.push_back(this);
board.GetChess(pt) = this;
}
const Point& GetPoint()const
{
return pt;
}
virtual bool CanMoveTo(const Point& point)const = 0;
virtual const char* GetName()const = 0;
virtual const bool CanCrossTheRiver()const = 0;
bool MoveTo(const Point& point)
{
if (CanMoveTo(point))
{
board.GetChess(pt) = nullptr;
pt.x = point.x;
pt.y = point.y;
board.RemoveChess(point);//删除目的地棋子(如果有)
board.GetChess(point) = this;
return true;
}
return false;
}
};
成员变量
棋盘和棋子紧密相关,棋子必须依附于棋盘而存在,棋盘也必须包含棋子。所以,在ChessPiece类中包含一个棋盘的引用是非常有必要的。当然,每个棋子都有自己的坐标,所以也必须有一个坐标属性。但这里有一个需要注意的地方:
上面说的两个属性必须是protected,不能是private,因为子类不能访问父类的private属性!
成员函数
构造函数
构造函数非常简单,就是初始化一些变量,并且把自己添加到ChessBoard类里。
CanMoveTo
判断能否移动到指定地点(但不移动),纯虚函数。
GetName
获取棋子名称,纯虚函数。
CanCrossTheRiver
棋子能否过河,纯虚函数。
MoveTo
移动到指定地点,并返回是否成功。因为所有棋子移动的流程都是判断是否可以移动->移到指定位置->吃掉原有棋子(如果有),所以这个函数没必要是虚函数,直接按照流程来即可。注意,在非虚函数中调用虚函数是可行且有效的。
在开始下面的之前,我先说一下,为了查找各个中国象棋棋子的英文名称,我焦头烂额地百度了大半天,才找到一个比较靠谱的。下面的英文名称就是按照这个来的。
車(Rook)
車大概是象棋中最厉害的棋子,所以很多新手都喜欢用,并且流传下了“新手玩车,熟手玩炮,老手玩马”的谚语(这么看我是熟手😂)。下面我们就先来实现車。
class Rook :public ChessPiece//車
{
public:
Rook(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)//目的地没有棋子或有对方棋子
{
if (point.x == pt.x)
{
if (point.y < pt.y)
{
for (uint8_t i = point.y + 1; i < pt.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
return false;
}
}
else
{
for (uint8_t i = pt.y + 1; i < point.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
return false;
}
}
return true;
}
else if (point.y == pt.y)
{
if (point.x < pt.x)
{
for (uint8_t i = point.x + 1; i < pt.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
return false;
}
}
else
{
for (uint8_t i = pt.x + 1; i < point.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
return false;
}
}
return true;
}
}
return false;
}
virtual const char* GetName()const
{
return "車";
}
virtual const bool CanCrossTheRiver()const
{
return true;
}
};
CanMoveTo
車的走法是直线行走任意格,所在位置和目的地中间不能有棋子,可行处可吃敌子。根据这条规则就可以写出以上代码,挺好理解的。
馬(Horse)
class Horse :public ChessPiece//馬
{
public:
Horse(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
static const Point s[8] = { {2,1},{2,-1},{-2,1},{-2,-1},{1,2},{-1,2},{1,-2},{-1,-2} },
u[8] = { {1,0},{1,0},{-1,0},{-1,0},{0,1},{0,1},{0,-1},{0,-1} };//马可以到达的八个点和蹩马腿的八个点
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)
{
for (size_t i = 0; i < 8; i++)
{
if (point == Point(pt.x + s[i].x, pt.y + s[i].y) && board.GetChess(Point(pt.x + u[i].x, pt.y + u[i].y)) == nullptr)
{
return true;
}
}
}
return false;
}
virtual const char* GetName()const
{
return "馬";
}
virtual const bool CanCrossTheRiver()const
{
return true;
}
};
CanMoveTo
馬的走法是走“日”字,可行处即可吃子。因为馬最多有八个可以到达的地点,所以最简单的方法是一一列举出来,判断是否与参数相同即可。当然,还有一个条件,就是相应的“蹩马腿”的点必须没有棋子。
炮(Cannon)
class Cannon :public ChessPiece//炮
{
public:
Cannon(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
//第一种走法:直线移动,不吃子
if (board.GetChess(point) == nullptr)//目的地没有棋子
{
if (point.x == pt.x)
{
if (point.y < pt.y)
{
for (uint8_t i = point.y + 1; i < pt.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
return false;
}
}
else
{
for (uint8_t i = pt.y + 1; i < point.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
return false;
}
}
}
else if (point.y == pt.y)
{
if (point.x < pt.x)
{
for (uint8_t i = point.x + 1; i < pt.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
return false;
}
}
else if (pt.x < point.x)
{
for (uint8_t i = pt.x + 1; i < point.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
return false;
}
}
}
return true;
}
else if (board.GetChess(point)->cl != this->cl)//第二种走法:吃子
{
uint8_t count = 0;
if (point.x == pt.x)
{
if (point.y < pt.y)
{
for (uint8_t i = point.y + 1; i < pt.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
count++;
}
}
else
{
for (uint8_t i = pt.y + 1; i < point.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
count++;
}
}
}
else if (point.y == pt.y)
{
if (point.x < pt.x)
{
for (uint8_t i = point.x + 1; i < pt.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
count++;
}
}
else if (pt.x < point.x)
{
for (uint8_t i = pt.x + 1; i < point.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
count++;
}
}
}
if (count == 1)
return true;
}
return false;
}
virtual const char* GetName()const
{
return "炮";
}
virtual const bool CanCrossTheRiver()const
{
return true;
}
};
CanMoveTo
炮有两种基本走法:一是像車一样直线行走任意格,所在位置和目的地中间不能有棋子,但不能吃子;一是直线行走任意格,所在位置和目的地中间有且只有一个棋子(敌方我方均可),必须吃子。第一种走法的代码和車的基本一样;第二种也基本相同,只是“中间没有棋子”的条件改成了“棋子个数为1”。
相/象(Elephant)
(别问我为什么只贴黑方的象,我就是喜欢黑方)
class Elephant :public ChessPiece//相/象
{
public:
Elephant(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
static const Point s[4] = { {2,2},{2,-2},{-2,2},{-2,-2} }, u[4] = { {1,1},{1,-1},{-1,1},{-1,-1} };//象可以到达的四个点和蹩象眼的八个点
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)
{
if (cl == point.ColorOfTheArea())//在我方范围内
{
for (size_t i = 0; i < 4; i++)
{
if (point == Point(pt.x + s[i].x, pt.y + s[i].y) && board.GetChess(Point(pt.x + u[i].x, pt.y + u[i].y)) == nullptr)
{
return true;
}
}
}
}
return false;
}
virtual const char* GetName()const
{
return cl == BLACK ? "象" : "相";
}
virtual const bool CanCrossTheRiver()const
{
return false;
}
};
CanMoveTo
和馬的原理完全一样,不过要注意象不能过河。
士(Adviser)
class Adviser :public ChessPiece//士
{
public:
Adviser(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
static const Point s[4] = { {1,1},{1,-1},{-1,1},{-1,-1} };//士可以到达的四个点
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)
{
if (cl == point.ColorOfTheArea() && point.IsInNinePalaces())
{
for (size_t i = 0; i < 4; i++)
{
if (point == Point(pt.x + s[i].x, pt.y + s[i].y))
{
return true;
}
}
}
}
return false;
}
virtual const char* GetName()const
{
return "士";
}
virtual const bool CanCrossTheRiver()const
{
return false;
}
};
CanMoveTo
没啥说头,和象的基本一样,只不过去掉了蹩象眼的限制,而且不能出九宫格。
兵/卒(Pawn)
class Pawn :public ChessPiece//兵/卒
{
public:
Pawn(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)
{
int8_t front = (cl == RED ? 1 : -1);
if (cl == pt.ColorOfTheArea())//没过河
return point == Point(pt.x, pt.y + front);
const Point s[3] = { {0,front},{1,0},{-1,0} };
for (size_t i = 0; i < 4; i++)
{
if (point == Point(pt.x + s[i].x, pt.y + s[i].y))
{
return true;
}
}
}
return false;
}
virtual const char* GetName()const
{
return cl == BLACK ? "卒" : "兵";
}
virtual const bool CanCrossTheRiver()const
{
return true;
}
};
CanMoveTo
兵的移动分两种情况:没过河只能前进一格,过河后可以前进或向左、向右一格,所以需要分类讨论。还要注意,红黑双方前进的方向是不同的,红方纵坐标+1,黑方-1。
帥/將
class King :public ChessPiece//將/帥
{
public:
King(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
static const Point s[4] = { {0,1},{0,-1},{1,0},{-1,0} };//將可以到达的四个点
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)
{
if (point.IsInNinePalaces() && point.ColorOfTheArea() == cl)//在我方九宫格内
{
for (size_t i = 0; i < 4; i++)
{
if (point == Point(pt.x + s[i].x, pt.y + s[i].y))
{
return true;
}
}
}
}
return false;
}
virtual const char* GetName()const
{
return cl == BLACK ? "將" : "帥";
}
virtual const bool CanCrossTheRiver()const
{
return false;
}
};
CanMoveTo
和士的又雷同了,只是把可以到的几个点数值变了变。
ChessBoard
棋子类都写完了,就可以添加棋盘类了。
class ChessBoard
{
private:
friend class ChessPiece;
ChessPiece* board[10][9];//红方纵坐标0-4,黑方纵坐标5-9,很重要!
std::list<ChessPiece*> red, black;
public:
ChessBoard();
const std::list<ChessPiece*>& GetRedPieces()const
{
return red;
}
const std::list<ChessPiece*>& GetBlackPieces()const
{
return black;
}
ChessPiece*& GetChess(const Point& point)
{
return board[point.y][point.x];
}
ChessPiece* const& GetChess(const Point& point)const
{
return board[point.y][point.x];
}
void RemoveChess(const Point& point);
bool KingsFaceToFace()const;
~ChessBoard();
};
ChessBoard::ChessBoard()
{
memset(board, 0, sizeof(board));
new King(Point(4, 0), RED, *this);
new King(Point(4, 9), BLACK, *this);
new Adviser(Point(3, 0), RED, *this);
new Adviser(Point(5, 0), RED, *this);
new Adviser(Point(3, 9), BLACK, *this);
new Adviser(Point(5, 9), BLACK, *this);
new Elephant(Point(2, 0), RED, *this);
new Elephant(Point(6, 0), RED, *this);
new Elephant(Point(2, 9), BLACK, *this);
new Elephant(Point(6, 9), BLACK, *this);
new Horse(Point(1, 0), RED, *this);
new Horse(Point(7, 0), RED, *this);
new Horse(Point(1, 9), BLACK, *this);
new Horse(Point(7, 9), BLACK, *this);
new Rook(Point(0, 0), RED, *this);
new Rook(Point(8, 0), RED, *this);
new Rook(Point(0, 9), BLACK, *this);
new Rook(Point(8, 9), BLACK, *this);
new Cannon(Point(1, 2), RED, *this);
new Cannon(Point(7, 2), RED, *this);
new Cannon(Point(1, 7), BLACK, *this);
new Cannon(Point(7, 7), BLACK, *this);
new Pawn(Point(0, 3), RED, *this);
new Pawn(Point(2, 3), RED, *this);
new Pawn(Point(4, 3), RED, *this);
new Pawn(Point(6, 3), RED, *this);
new Pawn(Point(8, 3), RED, *this);
new Pawn(Point(0, 6), BLACK, *this);
new Pawn(Point(2, 6), BLACK, *this);
new Pawn(Point(4, 6), BLACK, *this);
new Pawn(Point(6, 6), BLACK, *this);
new Pawn(Point(8, 6), BLACK, *this);
}
bool ChessBoard::KingsFaceToFace() const
{
auto r = std::find_if(red.begin(), red.end(), [](ChessPiece* p) {return dynamic_cast<King*>(p) != nullptr; }),
b = std::find_if(black.begin(), black.end(), [](ChessPiece* p) {return dynamic_cast<King*>(p) != nullptr; });
if (r != red.end() && b != black.end())
{
if ((*r)->GetPoint().x == (*b)->GetPoint().x)
{
for (uint8_t i = (*r)->GetPoint().y + 1; i < (*b)->GetPoint().y; i++)
{
if (GetChess(Point((*r)->GetPoint().x, i)))
return false;
}
return true;
}
}
return false;
}
ChessBoard::~ChessBoard()
{
for (ChessPiece* p : red)
delete p;
for (ChessPiece* p : black)
delete p;
}
成员变量
根据之前说的棋子存储方法,开一个数组和两个list(方便增删)。
成员函数
构造函数
如图是象棋各个棋子开局时的位置,左上角的車坐标为(0,0),往右横坐标递增,往下纵坐标递增(和平面直角坐标系不太一样)。构造函数就是用来初始化这些位置的。
首先清空棋盘,然后分别new出双方每一个棋子。由于ChessPiece类的构造函数中会把棋子自动添加到ChessBoard类相关容器里面,所以直接new就可以。
GetChess
返回对应坐标的棋子。由于数组下标是先行后列的,与平时习惯不符,所以要转换一下。
RemoveChess
删除指定坐标的棋子。先从所在的list中删除,然后释放内存(delete),最后把board对应元素设为nullptr(没有棋子)。
KingsFaceToFace
用来判断是否老将对脸。首先寻找双方的将帅,然后判断是否在同一列,中间是否没有棋子。寻找将帅的时候用到了dynamic_cast的特性,这是基类指针转为子类指针的类型转换关键字,如果指针指向的对象是相应的对象,返回转换后的结果;如果指针执行的对象不是相应子类的对象,返回nullptr。
析构函数
释放所有棋子内存。
ChessGame
class ChessGame
{
private:
bool nextPlayer;
ChessBoard board;
public:
ChessGame() :nextPlayer(RED) {}
const ChessBoard& GetBoard()const
{
return board;
}
bool Move(const Point& a, const Point& b)
{
if (board.GetChess(a) && board.GetChess(a)->cl == nextPlayer)
{
if (board.GetChess(a)->MoveTo(b))
{
nextPlayer = !nextPlayer;
return true;
}
return false;
}
return false;
}
uint8_t GetWinner()const
{
if (board.KingsFaceToFace())
return nextPlayer;
if (std::find_if(board.GetRedPieces().begin(), board.GetRedPieces().end(), [](ChessPiece* p) {return dynamic_cast<King*>(p) != nullptr; }) == board.GetRedPieces().end())//红方帅被吃
return BLACK;
if (std::find_if(board.GetBlackPieces().begin(), board.GetBlackPieces().end(), [](ChessPiece* p) {return dynamic_cast<King*>(p) != nullptr; }) == board.GetBlackPieces().end())//黑方帅被吃
return RED;
if (std::count_if(board.GetRedPieces().begin(), board.GetRedPieces().end(), [](ChessPiece* p) {return p->CanCrossTheRiver(); }) +
std::count_if(board.GetBlackPieces().begin(), board.GetBlackPieces().end(), [](ChessPiece* p) {return p->CanCrossTheRiver(); }) == 0)
return DRAW;//双方都不能过河,平局
return NONE;
}
bool GetNextPlayer()const
{
return nextPlayer;
}
};
成员变量
nextPlayer用来记录下一手是红方还是黑方,board就是棋盘。
成员函数
Move
和ChessPiece类的MoveTo基本相同,不过这里限制了轮到哪一方只能走哪一方的棋子。
GetWinner
首先判断是否老将对脸,如果是,下一手玩家胜利。然后搜索双方将帅,如果其中一方没有,对方胜利。如果双方都没有可以过河的棋子,平局。否则还没决出胜负。
五、测试代码
用上述代码写了个简单的象棋程序测试,但这样输入坐标很不方便,所以下期会用MFC做个鼠标操作的。
#include <iostream>
#include "Chess.h"
#include<Windows.h>
using namespace std;
using namespace ChineseChess;
void put(const ChessBoard& b)
{
for (int y = 0; y < 10; y++)
{
for (int x = 0; x < 9; x++)
{
cout.width(3);
ChessPiece* p = b.GetChess(Point(x, y));
if(p)
{
if (p->cl == BLACK)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_INTENSITY
| FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE);
}
else
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_INTENSITY
| FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
| FOREGROUND_RED);
}
cout << p->GetName();
}
else
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_INTENSITY
| FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
| FOREGROUND_GREEN);
cout << "十";
}
}
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY
| FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
cout << endl;
}
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY
| FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
int main()
{
ChessGame game;
put(game.GetBoard());
while (1)
{
int x, y, i, j;
cout <<"轮到"<<(game.GetNextPlayer()== RED?"红":"黑") << "方了,请输入要移动棋子的坐标和目标点坐标(输入-1退出):";
cin >> x >> y >> i >> j;
if (x == -1)
break;
while (!game.Move(Point(x,y),Point(i,j)))
{
cout << "输入有误,请重新输入:";
cin >> x >> y >> i >> j;
}
put(game.GetBoard());
auto winner = game.GetWinner();
if (winner == BLACK)
{
cout << "黑方获胜!";
break;
}
else if (winner == RED)
{
cout << "红方获胜!";
break;
}
else if (winner == DRAW)
{
cout << "平局!";
break;
}
}
return 0;
}
随便贴张截图吧
文章来源:https://www.toymoban.com/news/detail-439857.html
附录:Chess.h完整代码
#pragma once
#include<cstdint>
#include<list>
#include<stdexcept>
#include<algorithm>
namespace ChineseChess
{
const bool BLACK = 0, RED = 1;
const uint8_t DRAW = 2, NONE = 3;
class Point
{
public:
int8_t x, y;
Point(int8_t nx, int8_t ny) :x(nx), y(ny) {}
bool ColorOfTheArea()const//判断在红方区域还是黑方区域
{
if (y <= 4)
return RED;
return BLACK;
}
bool IsInNinePalaces()const//是否在九宫格中
{
return x >= 3 && x <= 5 && (y <= 2 || y >= 7);
}
};
bool operator==(const Point& a, const Point& b)
{
return a.x == b.x && a.y == b.y;
}
class ChessPiece;
class ChessBoard
{
private:
friend class ChessPiece;
ChessPiece* board[10][9];//红方纵坐标0-4,黑方纵坐标5-9,很重要!
std::list<ChessPiece*> red, black;
public:
ChessBoard();
const std::list<ChessPiece*>& GetRedPieces()const
{
return red;
}
const std::list<ChessPiece*>& GetBlackPieces()const
{
return black;
}
ChessPiece*& GetChess(const Point& point)
{
return board[point.y][point.x];
}
ChessPiece* const& GetChess(const Point& point)const
{
return board[point.y][point.x];
}
void RemoveChess(const Point& point);
bool KingsFaceToFace()const;
~ChessBoard();
};
class ChessPiece
{
protected:
Point pt;
ChessBoard& board;
public:
const bool cl;
ChessPiece(const Point& point, bool color, ChessBoard& chessboard) :pt(point), cl(color), board(chessboard)
{
if (cl == BLACK)
board.black.push_back(this);
else
board.red.push_back(this);
board.GetChess(pt) = this;
}
const Point& GetPoint()const
{
return pt;
}
virtual bool CanMoveTo(const Point& point)const = 0;
virtual const char* GetName()const = 0;
virtual const bool CanCrossTheRiver()const = 0;
bool MoveTo(const Point& point)
{
if (CanMoveTo(point))
{
board.GetChess(pt) = nullptr;
pt.x = point.x;
pt.y = point.y;
board.RemoveChess(point);//删除目的地棋子(如果有)
board.GetChess(point) = this;
return true;
}
return false;
}
};
void ChessBoard::RemoveChess(const Point& point)
{
if (GetChess(point))
{
if (GetChess(point)->cl == RED)
{
red.erase(std::find(red.begin(), red.end(), GetChess(point)));
}
else
{
black.erase(std::find(black.begin(), black.end(), GetChess(point)));
}
delete GetChess(point);
GetChess(point) = nullptr;
}
}
class Rook :public ChessPiece//車
{
public:
Rook(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)//目的地没有棋子或有对方棋子
{
if (point.x == pt.x)
{
if (point.y < pt.y)
{
for (uint8_t i = point.y + 1; i < pt.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
return false;
}
}
else
{
for (uint8_t i = pt.y + 1; i < point.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
return false;
}
}
return true;
}
else if (point.y == pt.y)
{
if (point.x < pt.x)
{
for (uint8_t i = point.x + 1; i < pt.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
return false;
}
}
else
{
for (uint8_t i = pt.x + 1; i < point.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
return false;
}
}
return true;
}
}
return false;
}
virtual const char* GetName()const
{
return "車";
}
virtual const bool CanCrossTheRiver()const
{
return true;
}
};
class Horse :public ChessPiece//馬
{
public:
Horse(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
static const Point s[8] = { {2,1},{2,-1},{-2,1},{-2,-1},{1,2},{-1,2},{1,-2},{-1,-2} },
u[8] = { {1,0},{1,0},{-1,0},{-1,0},{0,1},{0,1},{0,-1},{0,-1} };//马可以到达的八个点和蹩马腿的八个点
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)
{
for (size_t i = 0; i < 8; i++)
{
if (point == Point(pt.x + s[i].x, pt.y + s[i].y) && board.GetChess(Point(pt.x + u[i].x, pt.y + u[i].y)) == nullptr)
{
return true;
}
}
}
return false;
}
virtual const char* GetName()const
{
return "馬";
}
virtual const bool CanCrossTheRiver()const
{
return true;
}
};
class Cannon :public ChessPiece//炮
{
public:
Cannon(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
//第一种走法:直线移动,不吃子
if (board.GetChess(point) == nullptr)//目的地没有棋子
{
if (point.x == pt.x)
{
if (point.y < pt.y)
{
for (uint8_t i = point.y + 1; i < pt.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
return false;
}
}
else
{
for (uint8_t i = pt.y + 1; i < point.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
return false;
}
}
return true;
}
else if (point.y == pt.y)
{
if (point.x < pt.x)
{
for (uint8_t i = point.x + 1; i < pt.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
return false;
}
}
else if (pt.x < point.x)
{
for (uint8_t i = pt.x + 1; i < point.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
return false;
}
}
return true;
}
}
else if (board.GetChess(point)->cl != this->cl)//第二种走法:吃子
{
uint8_t count = 0;
if (point.x == pt.x)
{
if (point.y < pt.y)
{
for (uint8_t i = point.y + 1; i < pt.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
count++;
}
}
else
{
for (uint8_t i = pt.y + 1; i < point.y; ++i)
{
if (board.GetChess(Point(point.x, i)))//中间有棋子
count++;
}
}
}
else if (point.y == pt.y)
{
if (point.x < pt.x)
{
for (uint8_t i = point.x + 1; i < pt.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
count++;
}
}
else if (pt.x < point.x)
{
for (uint8_t i = pt.x + 1; i < point.x; ++i)
{
if (board.GetChess(Point(i, point.y)))//中间有棋子
count++;
}
}
}
if (count == 1)
return true;
}
return false;
}
virtual const char* GetName()const
{
return "炮";
}
virtual const bool CanCrossTheRiver()const
{
return true;
}
};
class Elephant :public ChessPiece//相/象
{
public:
Elephant(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
static const Point s[4] = { {2,2},{2,-2},{-2,2},{-2,-2} }, u[4] = { {1,1},{1,-1},{-1,1},{-1,-1} };//象可以到达的四个点和蹩象眼的八个点
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)
{
if (cl == point.ColorOfTheArea())//在我方范围内
{
for (size_t i = 0; i < 4; i++)
{
if (point == Point(pt.x + s[i].x, pt.y + s[i].y) && board.GetChess(Point(pt.x + u[i].x, pt.y + u[i].y)) == nullptr)
{
return true;
}
}
}
}
return false;
}
virtual const char* GetName()const
{
return cl == BLACK ? "象" : "相";
}
virtual const bool CanCrossTheRiver()const
{
return false;
}
};
class Adviser :public ChessPiece//士
{
public:
Adviser(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
static const Point s[4] = { {1,1},{1,-1},{-1,1},{-1,-1} };//士可以到达的四个点
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)
{
if (cl == point.ColorOfTheArea() && point.IsInNinePalaces())
{
for (size_t i = 0; i < 4; i++)
{
if (point == Point(pt.x + s[i].x, pt.y + s[i].y))
{
return true;
}
}
}
}
return false;
}
virtual const char* GetName()const
{
return "士";
}
virtual const bool CanCrossTheRiver()const
{
return false;
}
};
class Pawn :public ChessPiece//兵/卒
{
public:
Pawn(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)
{
int8_t front = (cl == RED ? 1 : -1);
if (cl == pt.ColorOfTheArea())//没过河
return point == Point(pt.x, pt.y + front);
const Point s[3] = { {0,front},{1,0},{-1,0} };
for (size_t i = 0; i < 3; i++)
{
if (point == Point(pt.x + s[i].x, pt.y + s[i].y))
{
return true;
}
}
}
return false;
}
virtual const char* GetName()const
{
return cl == BLACK ? "卒" : "兵";
}
virtual const bool CanCrossTheRiver()const
{
return true;
}
};
class King :public ChessPiece//將/帥
{
public:
King(const Point& point, bool color, ChessBoard& chessboard) :ChessPiece(point, color, chessboard) {}
virtual bool CanMoveTo(const Point& point)const override
{
static const Point s[4] = { {0,1},{0,-1},{1,0},{-1,0} };//將可以到达的四个点
if (board.GetChess(point) == nullptr || board.GetChess(point)->cl != this->cl)
{
if (point.IsInNinePalaces() && point.ColorOfTheArea() == cl)//在我方九宫格内
{
for (size_t i = 0; i < 4; i++)
{
if (point == Point(pt.x + s[i].x, pt.y + s[i].y))
{
return true;
}
}
}
}
return false;
}
virtual const char* GetName()const
{
return cl == BLACK ? "將" : "帥";
}
virtual const bool CanCrossTheRiver()const
{
return false;
}
};
ChessBoard::ChessBoard()
{
memset(board, 0, sizeof(board));
new King(Point(4, 0), RED, *this);
new King(Point(4, 9), BLACK, *this);
new Adviser(Point(3, 0), RED, *this);
new Adviser(Point(5, 0), RED, *this);
new Adviser(Point(3, 9), BLACK, *this);
new Adviser(Point(5, 9), BLACK, *this);
new Elephant(Point(2, 0), RED, *this);
new Elephant(Point(6, 0), RED, *this);
new Elephant(Point(2, 9), BLACK, *this);
new Elephant(Point(6, 9), BLACK, *this);
new Horse(Point(1, 0), RED, *this);
new Horse(Point(7, 0), RED, *this);
new Horse(Point(1, 9), BLACK, *this);
new Horse(Point(7, 9), BLACK, *this);
new Rook(Point(0, 0), RED, *this);
new Rook(Point(8, 0), RED, *this);
new Rook(Point(0, 9), BLACK, *this);
new Rook(Point(8, 9), BLACK, *this);
new Cannon(Point(1, 2), RED, *this);
new Cannon(Point(7, 2), RED, *this);
new Cannon(Point(1, 7), BLACK, *this);
new Cannon(Point(7, 7), BLACK, *this);
new Pawn(Point(0, 3), RED, *this);
new Pawn(Point(2, 3), RED, *this);
new Pawn(Point(4, 3), RED, *this);
new Pawn(Point(6, 3), RED, *this);
new Pawn(Point(8, 3), RED, *this);
new Pawn(Point(0, 6), BLACK, *this);
new Pawn(Point(2, 6), BLACK, *this);
new Pawn(Point(4, 6), BLACK, *this);
new Pawn(Point(6, 6), BLACK, *this);
new Pawn(Point(8, 6), BLACK, *this);
}
bool ChessBoard::KingsFaceToFace() const
{
//dynamic_cast转换失败返回nullptr
auto r = std::find_if(red.begin(), red.end(), [](ChessPiece* p) {return dynamic_cast<King*>(p) != nullptr; }),
b = std::find_if(black.begin(), black.end(), [](ChessPiece* p) {return dynamic_cast<King*>(p) != nullptr; });
if (r != red.end() && b != black.end())
{
if ((*r)->GetPoint().x == (*b)->GetPoint().x)
{
for (uint8_t i = (*r)->GetPoint().y + 1; i < (*b)->GetPoint().y; i++)
{
if (GetChess(Point((*r)->GetPoint().x, i)))
return false;
}
return true;
}
}
return false;
}
ChessBoard::~ChessBoard()
{
for (ChessPiece* p : red)
delete p;
for (ChessPiece* p : black)
delete p;
}
class ChessGame
{
private:
bool nextPlayer;
ChessBoard board;
public:
ChessGame() :nextPlayer(RED) {}
const ChessBoard& GetBoard()const
{
return board;
}
bool Move(const Point& a, const Point& b)
{
if (board.GetChess(a) && board.GetChess(a)->cl == nextPlayer)
{
if (board.GetChess(a)->MoveTo(b))
{
nextPlayer = !nextPlayer;
return true;
}
return false;
}
return false;
}
uint8_t GetWinner()const
{
if (board.KingsFaceToFace())
return nextPlayer;
if (std::find_if(board.GetRedPieces().begin(), board.GetRedPieces().end(), [](ChessPiece* p) {return dynamic_cast<King*>(p) != nullptr; }) == board.GetRedPieces().end())//红方帅被吃
return BLACK;
if (std::find_if(board.GetBlackPieces().begin(), board.GetBlackPieces().end(), [](ChessPiece* p) {return dynamic_cast<King*>(p) != nullptr; }) == board.GetBlackPieces().end())//黑方帅被吃
return RED;
if (std::count_if(board.GetRedPieces().begin(), board.GetRedPieces().end(), [](ChessPiece* p) {return p->CanCrossTheRiver(); }) +
std::count_if(board.GetBlackPieces().begin(), board.GetBlackPieces().end(), [](ChessPiece* p) {return p->CanCrossTheRiver(); }) == 0)
return DRAW;//双方都不能过河,平局
return NONE;
}
bool GetNextPlayer()const
{
return nextPlayer;
}
};
}
下期预告
在下一篇博客,我会把这个中国象棋做成可以使用界面操作的,欢迎大家关注!
文章来源地址https://www.toymoban.com/news/detail-439857.html
到了这里,关于C++实现双人中国象棋(一)——算法篇(附完整代码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!