C++实现双人中国象棋(一)——算法篇(附完整代码)

这篇具有很好参考价值的文章主要介绍了C++实现双人中国象棋(一)——算法篇(附完整代码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、简介

最近突发奇想,要使用C++做一个双人象棋的程序,昨天肝了一天,终于把算法部分完成了,下面把开发过程中的经验分享一下。

开发环境:Visual Studio 2019
语言标准:C++11及以上
纠错:暂无

二、准备工作

知识要求:

  • 熟练掌握C++语言面向对象编程的知识(继承,多态)

  • 掌握STL的基本操作

  • 了解中国象棋基本规则(不会还有人不知道中国象棋规则吧!)
    C++实现双人中国象棋(一)——算法篇(附完整代码)
    既然都知道了,下面说一个大家可能没注意过的点:

  • 象棋棋盘尺寸为9×10,9列10行


象棋摆法
C++实现双人中国象棋(一)——算法篇(附完整代码)

三、程序框架

由于这是双人象棋,所以算法主要就是判断胜负(容易实现)和判断棋子能否走到某个地方(难点)。这篇博客主要就介绍这两个问题。


程序主要由以下几个类组成:

  • 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类中包含一个棋盘的引用是非常有必要的。当然,每个棋子都有自己的坐标,所以也必须有一个坐标属性。但这里有一个需要注意的地方:
C++实现双人中国象棋(一)——算法篇(附完整代码)
上面说的两个属性必须是protected,不能是private,因为子类不能访问父类的private属性!

成员函数

构造函数

构造函数非常简单,就是初始化一些变量,并且把自己添加到ChessBoard类里。

CanMoveTo

判断能否移动到指定地点(但不移动),纯虚函数。

GetName

获取棋子名称,纯虚函数。

CanCrossTheRiver

棋子能否过河,纯虚函数。

MoveTo

移动到指定地点,并返回是否成功。因为所有棋子移动的流程都是判断是否可以移动->移到指定位置->吃掉原有棋子(如果有),所以这个函数没必要是虚函数,直接按照流程来即可。注意,在非虚函数中调用虚函数是可行且有效的。


在开始下面的之前,我先说一下,为了查找各个中国象棋棋子的英文名称,我焦头烂额地百度了大半天,才找到一个比较靠谱的。下面的英文名称就是按照这个来的。

車(Rook)

C++实现双人中国象棋(一)——算法篇(附完整代码)車大概是象棋中最厉害的棋子,所以很多新手都喜欢用,并且流传下了“新手玩车,熟手玩炮,老手玩马”的谚语(这么看我是熟手😂)。下面我们就先来实现車。

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)

C++实现双人中国象棋(一)——算法篇(附完整代码)

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)

C++实现双人中国象棋(一)——算法篇(附完整代码)

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)

C++实现双人中国象棋(一)——算法篇(附完整代码)
(别问我为什么只贴黑方的象,我就是喜欢黑方)

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)

C++实现双人中国象棋(一)——算法篇(附完整代码)

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)

C++实现双人中国象棋(一)——算法篇(附完整代码)

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。

帥/將

C++实现双人中国象棋(一)——算法篇(附完整代码)

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(方便增删)。

成员函数

构造函数

C++实现双人中国象棋(一)——算法篇(附完整代码)
如图是象棋各个棋子开局时的位置,左上角的車坐标为(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;
}

随便贴张截图吧
C++实现双人中国象棋(一)——算法篇(附完整代码)

附录: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;
		}
	};
}

下期预告

在下一篇博客,我会把这个中国象棋做成可以使用界面操作的,欢迎大家关注!
C++实现双人中国象棋(一)——算法篇(附完整代码)文章来源地址https://www.toymoban.com/news/detail-439857.html

到了这里,关于C++实现双人中国象棋(一)——算法篇(附完整代码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 从0实现基于Alpha zero的中国象棋AI(会分为多个博客,此处讲解蒙特卡洛树搜索)

    ​ 题主对于阿尔法狗的实现原理好奇,加上毕业在即,因此选择中国象棋版的阿尔法zero,阿尔法zero是阿尔法狗的升级版。在完成代码编写的历程中,深刻感受到深度学习环境的恶劣,网络上固然资料繁多,但要么水平不行,不知所云,要么国外课程,门槛过高。因而碰壁良

    2024年02月06日
    浏览(51)
  • 从0开始写中国象棋-创建棋盘与棋子

    考虑到象棋程序,其实就是数据结构与算法实现。 所以和界面相关的QT部分我们先放一放。 我们从控制台版本开始。这样大家更容易接受,也不影响开发。 后面我们会把控制台嫁接到QT上完成完整的游戏,那时候自然就水到渠成了。 中国象棋的棋盘是一个宽9列,长 5+5 = 10

    2024年02月07日
    浏览(37)
  • 中国象棋AI库AlphaZero_ChineseChess

    AlphaZero_ChineseChess是一个基于AlphaZero算法的中国象棋AI库,它是开源的,使用Python语言编写,托管在GitHub上。以下是对AlphaZero_ChineseChess库的详细介绍: 算法原理 AlphaZero_ChineseChess基于AlphaZero算法,这是一种基于自我对弈的强化学习算法,能够让AI自主学习棋局的优劣、评估策略

    2024年02月14日
    浏览(122)
  • FireMonkey3D之中国象棋程序(二)制定规则

    声明:本程序设计参考象棋巫师源码(开发工具dephi 11,建议用delphi 10.3以上版本)。 本章目标: 实现中国象棋规则 上一章我们设计了图形界面,可以开始轮流走棋了。但是,由于没有按中国象棋的规则进行限制,所有的棋子都可以在棋盘上随意走动,这章我们开始制定行棋

    2024年01月20日
    浏览(31)
  • FireMonkey3D之中国象棋程序(一)界面设计

    声明:本程序设计参考象棋巫师源码(开发工具dephi 11,建议用delphi 10.3以上版本)。 本章目标: 制作一个可操作的图形界面 第一步我们设计图形界面,显示初始化棋局。效果如下图:  我们先做个3D象棋子控件(请看我的博客关于FireMonkey3D的文章:万能控件Mesh详解),源码

    2024年01月20日
    浏览(43)
  • FireMonkey3D之中国象棋程序设计(五)置换表

    声明:本程序设计参考象棋巫师源码(开发工具dephi 11,建议用delphi 10.3以上版本)。 这一章主要介绍置换表。本章目标: 实现置换表; 采用置换表走法、杀手走法等多种启发方式。 5.1  置换表 没有置换表,就称不上是完整的计算机博弈程序。在搜索过程中,某个搜索结果

    2024年01月20日
    浏览(38)
  • FireMonkey3D之中国象棋程序设计(四)水平效应、检查重复局面

    声明:本程序设计参考象棋巫师源码(开发工具dephi 11,建议用delphi 10.3以上版本)。 上一章我们的程序终于会走棋了,不过很多时候它很低能。由于水平线效应,任何变化都只搜索固定的深度。还有,有时它会长将。我们能做哪些改进呢? 本章的目标: 用Zobrist校验码技术

    2024年01月20日
    浏览(38)
  • AI人工智能(调包侠)速成之路十五(中国象棋AI网络机器人:AI模型部署)

    神经网络模型动态加解密的技术这个以后再写吧  书接上文: AI人工智能(调包侠)速成之路十四(中国象棋AI网络机器人:AI技术综合应用实现) 神经网络模型的存储格式         我们训练的神经网络就是一堆模拟神经元的参数集合,给(他/她/它)一个输入信息就会得到

    2024年01月23日
    浏览(50)
  • C++的MFC实现Bresenham算法画直线,从菜单和鼠标响应开始包含代码的完整良心教程

    首先在菜单栏中加入这个工具 然后给他一个ID,注意要全大写   在类视图中右键你的view,选择属性   在消息栏添加鼠标消息,此时会自动添加一个空函数体。    在事件栏添加鼠标事件,为按下菜单栏按钮的时候添加要做的事情。此时也会生成一个空函数体叫做void CMFCApp

    2024年02月06日
    浏览(44)
  • OJ刷题---[算法课动态规划]走网格(C++完整代码)

    m行n列的网格,从左上角(1,1)出发,每一步只能向下或者向右,问共有多少种方法可以走到右下角(m,n); 输入参数 m n (1=m=10 1=n=10) 输出多少种走法 比如: 输入:2 3 输出:3 输入:5 5 输出:70 完整代码(C++): 结果: 注意:最后调用的时候,是调用sum(m,n),而不是sum(m+1

    2024年02月07日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包