C++实现双人中国象棋(一)——算法篇(附完整代码)
一、簡介
最近突發奇想,要使用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);elseboard.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)
(別問我為什么只貼黑方的象,我就是喜歡黑方)
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);static 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 !strcmp(p->GetName(), "帥"); }),b = std::find_if(black.begin(), black.end(), [](ChessPiece* p) {return !strcmp(p->GetName(), "將"); });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
用來判斷是否老將對臉。首先尋找雙方的將帥,然后判斷是否在同一列,中間是否沒有棋子。
析構函數
釋放所有棋子內存。
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 !strcmp(p->GetName(), "帥"); }) == board.GetRedPieces().end())//紅方帥被吃return BLACK;if (std::find_if(board.GetBlackPieces().begin(), board.GetBlackPieces().end(), [](ChessPiece* p) {return !strcmp(p->GetName(), "將"); }) == board.GetBlackPieces().end())//黑方帥被吃return RED;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; }隨便貼張截圖吧
附錄: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);elseboard.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{auto r = std::find_if(red.begin(), red.end(), [](ChessPiece* p) {return !strcmp(p->GetName(), "帥"); }),b = std::find_if(black.begin(), black.end(), [](ChessPiece* p) {return !strcmp(p->GetName(), "將"); });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 !strcmp(p->GetName(), "帥"); }) == board.GetRedPieces().end())//紅方帥被吃return BLACK;if (std::find_if(board.GetBlackPieces().begin(), board.GetBlackPieces().end(), [](ChessPiece* p) {return !strcmp(p->GetName(), "將"); }) == 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++实现双人中国象棋(一)——算法篇(附完整代码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vue 项目中各种痛点问题及解决方案
- 下一篇: 考研英语近义词与反义词·三