├── Chess_Game.pro ├── Chess_Game.pro.user ├── README.md ├── gamewindow.ui ├── img-for-readme.png ├── include ├── Chess.h ├── GameWindow.h └── ui_gamewindow.h ├── main.cpp └── src ├── Chess.cpp └── GameWindow.cpp /Chess_Game.pro: -------------------------------------------------------------------------------- 1 | QT -= gui 2 | QT += widgets 3 | CONFIG += c++17 console 4 | CONFIG -= app_bundle 5 | 6 | # You can make your code fail to compile if it uses deprecated APIs. 7 | # In order to do so, uncomment the following line. 8 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 9 | 10 | SOURCES += \ 11 | main.cpp \ 12 | src/Chess.cpp \ 13 | src/GameWindow.cpp 14 | 15 | # Default rules for deployment. 16 | qnx: target.path = /tmp/$${TARGET}/bin 17 | else: unix:!android: target.path = /opt/$${TARGET}/bin 18 | !isEmpty(target.path): INSTALLS += target 19 | 20 | HEADERS += \ 21 | include/Chess.h \ 22 | include/GameWindow.h \ 23 | include/ui_gamewindow.h 24 | 25 | FORMS += \ 26 | gamewindow.ui 27 | -------------------------------------------------------------------------------- /Chess_Game.pro.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EnvironmentId 7 | {b1ba53d0-eae8-4a87-9aa0-eb8592d83d61} 8 | 9 | 10 | ProjectExplorer.Project.ActiveTarget 11 | 0 12 | 13 | 14 | ProjectExplorer.Project.EditorSettings 15 | 16 | true 17 | false 18 | true 19 | 20 | Cpp 21 | 22 | CppGlobal 23 | 24 | 25 | 26 | QmlJS 27 | 28 | QmlJSGlobal 29 | 30 | 31 | 2 32 | UTF-8 33 | false 34 | 4 35 | false 36 | 80 37 | true 38 | true 39 | 1 40 | false 41 | true 42 | false 43 | 0 44 | true 45 | true 46 | 0 47 | 8 48 | true 49 | false 50 | 1 51 | true 52 | true 53 | true 54 | *.md, *.MD, Makefile 55 | false 56 | true 57 | 58 | 59 | 60 | ProjectExplorer.Project.PluginSettings 61 | 62 | 63 | true 64 | false 65 | true 66 | true 67 | true 68 | true 69 | 70 | 71 | 0 72 | true 73 | 74 | true 75 | true 76 | Builtin.DefaultTidyAndClazy 77 | 4 78 | 79 | 80 | 81 | true 82 | 83 | 84 | 85 | 86 | ProjectExplorer.Project.Target.0 87 | 88 | Desktop 89 | Desktop Qt 6.3.1 MinGW 64-bit 90 | Desktop Qt 6.3.1 MinGW 64-bit 91 | qt.qt6.631.win64_mingw_kit 92 | 0 93 | 0 94 | 0 95 | 96 | 0 97 | D:\Chess_Game\build-Chess_Game-Desktop_Qt_6_3_1_MinGW_64_bit-Debug 98 | D:/Chess_Game/build-Chess_Game-Desktop_Qt_6_3_1_MinGW_64_bit-Debug 99 | 100 | 101 | true 102 | QtProjectManager.QMakeBuildStep 103 | false 104 | 105 | 106 | 107 | true 108 | Qt4ProjectManager.MakeStep 109 | 110 | 2 111 | Build 112 | Build 113 | ProjectExplorer.BuildSteps.Build 114 | 115 | 116 | 117 | true 118 | Qt4ProjectManager.MakeStep 119 | clean 120 | 121 | 1 122 | Clean 123 | Clean 124 | ProjectExplorer.BuildSteps.Clean 125 | 126 | 2 127 | false 128 | 129 | false 130 | 131 | Debug 132 | Qt4ProjectManager.Qt4BuildConfiguration 133 | 2 134 | 135 | 136 | D:\Chess_Game\build-Chess_Game-Desktop_Qt_6_3_1_MinGW_64_bit-Release 137 | D:/Chess_Game/build-Chess_Game-Desktop_Qt_6_3_1_MinGW_64_bit-Release 138 | 139 | 140 | true 141 | QtProjectManager.QMakeBuildStep 142 | false 143 | 144 | 145 | 146 | true 147 | Qt4ProjectManager.MakeStep 148 | 149 | 2 150 | Build 151 | Build 152 | ProjectExplorer.BuildSteps.Build 153 | 154 | 155 | 156 | true 157 | Qt4ProjectManager.MakeStep 158 | clean 159 | 160 | 1 161 | Clean 162 | Clean 163 | ProjectExplorer.BuildSteps.Clean 164 | 165 | 2 166 | false 167 | 168 | false 169 | 170 | Release 171 | Qt4ProjectManager.Qt4BuildConfiguration 172 | 0 173 | 0 174 | 175 | 176 | 0 177 | D:\Chess_Game\build-Chess_Game-Desktop_Qt_6_3_1_MinGW_64_bit-Profile 178 | D:/Chess_Game/build-Chess_Game-Desktop_Qt_6_3_1_MinGW_64_bit-Profile 179 | 180 | 181 | true 182 | QtProjectManager.QMakeBuildStep 183 | false 184 | 185 | 186 | 187 | true 188 | Qt4ProjectManager.MakeStep 189 | 190 | 2 191 | Build 192 | Build 193 | ProjectExplorer.BuildSteps.Build 194 | 195 | 196 | 197 | true 198 | Qt4ProjectManager.MakeStep 199 | clean 200 | 201 | 1 202 | Clean 203 | Clean 204 | ProjectExplorer.BuildSteps.Clean 205 | 206 | 2 207 | false 208 | 209 | false 210 | 211 | Profile 212 | Qt4ProjectManager.Qt4BuildConfiguration 213 | 0 214 | 0 215 | 0 216 | 217 | 3 218 | 219 | 220 | 0 221 | Deploy 222 | Deploy 223 | ProjectExplorer.BuildSteps.Deploy 224 | 225 | 1 226 | 227 | false 228 | ProjectExplorer.DefaultDeployConfiguration 229 | 230 | 1 231 | 232 | true 233 | true 234 | true 235 | 236 | 2 237 | 238 | Qt4ProjectManager.Qt4RunConfiguration:D:/Chess_Game/Chess_Game/Chess_Game.pro 239 | D:/Chess_Game/Chess_Game/Chess_Game.pro 240 | false 241 | true 242 | true 243 | false 244 | true 245 | D:/Chess_Game/build-Chess_Game-Desktop_Qt_6_3_1_MinGW_64_bit-Debug 246 | 247 | 1 248 | 249 | 250 | 251 | ProjectExplorer.Project.TargetCount 252 | 1 253 | 254 | 255 | ProjectExplorer.Project.Updater.FileVersion 256 | 22 257 | 258 | 259 | Version 260 | 22 261 | 262 | 263 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chess-Game-2020 2 | This is the new repo of the original project. 3 | 4 | ```Use Qt Creator 6.3 to open the project, then build and run the code.``` 5 | 6 | Two years ago, I did this academic project for my c++ practice course, but there were too many "unnecessary" parts for the chess game itself, such as login or register module. Although these are redundant for the core contribution of the project, but are helpful to show my workload to get an 'A'. 7 | 8 | Therefore, I only retain the core part of the project: the alpha-beta algorithm and the chess board panel painted by Qt. 9 | 10 | I have written [a blog](https://blog.csdn.net/weixin_45157679/article/details/108405709) in Chinese.Here is the game panel: 11 | 12 |
13 | -------------------------------------------------------------------------------- /gamewindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GameWindow 6 | 7 | 8 | 9 | 0 10 | 0 11 | 800 12 | 600 13 | 14 | 15 | 16 | MainWindow 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /img-for-readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JitingLiu/Chess-Game-2020/7d11c4b2d308679809f9c0689dfeef59a9bd7643/img-for-readme.png -------------------------------------------------------------------------------- /include/Chess.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "Chess.h" //vector definition using struct position 7 | 8 | using namespace std; 9 | 10 | #define N 15 //15*15的棋盘 11 | 12 | extern const char chessBoardFlag; //棋盘标志 13 | extern const char chessFlag1; // 玩家1 14 | extern const char chessFlag2; // 玩家2的棋子标志 15 | 16 | typedef struct Position { //坐标 17 | int row; //行 18 | int col; //列 19 | }Position; 20 | 21 | extern vector vec1; //存 player1 的下棋信息 22 | extern vector vec2; //存 player2 的下棋信息 23 | extern vector vec3; //存 player1 + player2 的下棋信息 24 | extern vector vec_blank; //存未下棋的位置 25 | extern vector vec_all; //棋盘的所有位置 26 | 27 | class Chess{ 28 | private: 29 | char chessBoard[N][N]; //棋盘 30 | public: 31 | Chess(); //默认构造函数 32 | Chess(char chessBoard[][N]); //构造函数 33 | Chess(const Chess& chess); //拷贝构造函数 34 | ~Chess(){} // 35 | void Init_chessboard(); //棋盘初始化函数 36 | void take_step(Position& pos, int player, char flag); //走一步 37 | int judge_postition(const Position& pos); //判断坐标的合法性 38 | int judge_victory(Position pos, char flag); //判断是否有玩家获胜 39 | char get_coordinate(int, int); //返回(x,y)的信息 40 | 41 | void init_vector(); //容器初始化 42 | void AI_chess(Position& pos, char flag); //AI走 43 | int alpha_beta(int h, int player, int alpha, int beta); //alpha-beta剪枝 44 | void order(); //离最后落子的邻居位置最有可能是最优点 45 | bool has_neightnor(vector::iterator vter); // 判断点 pt 是否有邻居点 46 | long int evaluate(int); //评估函数, 主要是评估当前棋局的得分 47 | bool match_vector(vector& temp, vector& shape); //括号是否匹配 48 | int cal_score(int m, int n, int x_direct, int y_direct, vector& enemy_list, 49 | vector& my_list); //每个方向上的分值计算 50 | }; 51 | 52 | -------------------------------------------------------------------------------- /include/GameWindow.h: -------------------------------------------------------------------------------- 1 | #ifndef GAMEWINDOW_H 2 | #define GAMEWINDOW_H 3 | 4 | #include 5 | #include "Chess.h" 6 | namespace Ui { 7 | class GameWindow; 8 | } 9 | 10 | class GameWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit GameWindow(QWidget *parent = 0); 16 | ~GameWindow(); 17 | void paintEvent(QPaintEvent* event); //绘图 18 | void mouseMoveEvent(QMouseEvent *event); //鼠标移动事件 19 | void mouseReleaseEvent(QMouseEvent* event); // 实际落子 20 | bool is_win(Position& pos, int player, char flag); //判断具体哪位玩家赢 21 | Chess* get_game() 22 | { 23 | return game; 24 | } 25 | private: 26 | Ui::GameWindow *ui; 27 | Chess* game; // 游戏 28 | 29 | }; 30 | 31 | extern int moveX, moveY, lastx, lasty; 32 | 33 | #endif // GAMEWINDOW_H 34 | -------------------------------------------------------------------------------- /include/ui_gamewindow.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************** 2 | ** Form generated from reading UI file 'gamewindow.ui' 3 | ** 4 | ** Created by: Qt User Interface Compiler version 5.9.2 5 | ** 6 | ** WARNING! All changes made in this file will be lost when recompiling UI file! 7 | ********************************************************************************/ 8 | 9 | #ifndef UI_GAMEWINDOW_H 10 | #define UI_GAMEWINDOW_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | QT_BEGIN_NAMESPACE 22 | 23 | class Ui_GameWindow 24 | { 25 | public: 26 | QMenuBar *menubar; 27 | QWidget *centralwidget; 28 | QStatusBar *statusbar; 29 | 30 | void setupUi(QMainWindow *GameWindow) 31 | { 32 | if (GameWindow->objectName().isEmpty()) 33 | GameWindow->setObjectName(QStringLiteral("GameWindow")); 34 | GameWindow->resize(800, 600); 35 | menubar = new QMenuBar(GameWindow); 36 | menubar->setObjectName(QStringLiteral("menubar")); 37 | GameWindow->setMenuBar(menubar); 38 | centralwidget = new QWidget(GameWindow); 39 | centralwidget->setObjectName(QStringLiteral("centralwidget")); 40 | GameWindow->setCentralWidget(centralwidget); 41 | statusbar = new QStatusBar(GameWindow); 42 | statusbar->setObjectName(QStringLiteral("statusbar")); 43 | GameWindow->setStatusBar(statusbar); 44 | 45 | retranslateUi(GameWindow); 46 | 47 | QMetaObject::connectSlotsByName(GameWindow); 48 | } // setupUi 49 | 50 | void retranslateUi(QMainWindow *GameWindow) 51 | { 52 | GameWindow->setWindowTitle(QApplication::translate("GameWindow", "MainWindow", Q_NULLPTR)); 53 | } // retranslateUi 54 | 55 | }; 56 | 57 | namespace Ui { 58 | class GameWindow: public Ui_GameWindow {}; 59 | } // namespace Ui 60 | 61 | QT_END_NAMESPACE 62 | 63 | #endif // UI_GAMEWINDOW_H 64 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "./include/GameWindow.h" 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | GameWindow w; 8 | w.show(); 9 | return a.exec(); 10 | } 11 | -------------------------------------------------------------------------------- /src/Chess.cpp: -------------------------------------------------------------------------------- 1 | /*Chess.cpp 此文件为Chess类的实现*/ 2 | 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "./include/Chess.h" 9 | 10 | #define inf 99999999 //无穷大值 11 | #define Depth 3 //搜索深度为3 12 | 13 | using namespace std; 14 | 15 | //give a definition 16 | const char chessBoardFlag = 'w'; // 棋盘标志 17 | const char chessFlag1 = 'o'; // 玩家1 18 | const char chessFlag2 = 'x'; // 玩家2的棋子标志 19 | 20 | //definition of vector 21 | vector vec1 = {}; //存 player1 的下棋信息 22 | vector vec2 = {}; //存 player2 的下棋信息 23 | vector vec3 = {}; //存 player1 + player2 的下棋信息 24 | vector vec_blank = {}; //存未下棋的位置 25 | vector vec_all = {}; //棋盘的所有位置 26 | 27 | Chess::Chess() //构造函数 28 | { 29 | Init_chessboard(); 30 | 31 | } 32 | 33 | Chess::Chess(char chessBoard[][N]) //带参数的构造函数 34 | { 35 | for (int i = 0; i < N ; ++i) 36 | { 37 | for (int j = 0; j < N ; ++j) 38 | { 39 | this->chessBoard[i][j] = chessBoard[i][j];//赋值 40 | } 41 | } 42 | 43 | } 44 | 45 | Chess::Chess(const Chess& chess) //拷贝构造函数 46 | { 47 | for (int i = 0; i < N ; ++i) 48 | { 49 | for (int j = 0; j < N ; ++j) 50 | { 51 | this->chessBoard[i][j] = chess.chessBoard[i][j];//赋值 52 | } 53 | } 54 | } 55 | 56 | void Chess::Init_chessboard() //初始化棋盘 57 | { 58 | for (int i = 0; i < N ; ++i) 59 | { 60 | for (int j = 0; j < N ; ++j) 61 | { 62 | chessBoard[i][j] = chessBoardFlag;//每个位置初始化为空 63 | } 64 | } 65 | 66 | } 67 | 68 | void Chess::init_vector() //容器初始化 69 | { 70 | //对 vector 初始化 71 | vec_blank.clear(); //先清空 72 | vec_all.clear(); //先清空 73 | for (int i = 0; i < N; ++i) 74 | { 75 | for (int j = 0; j < N; ++j) 76 | { 77 | Position pos; 78 | pos.row = i; 79 | pos.col = j; 80 | if (chessBoard[i][j] == chessBoardFlag) //没人走 81 | { 82 | vec_blank.push_back(pos); 83 | vec_all.push_back(pos); 84 | } 85 | else if (chessBoard[i][j] == chessFlag1) //AI走的位置 86 | { 87 | vec_all.push_back(pos); 88 | } 89 | else if (chessBoard[i][j] == chessFlag2) //玩家走的位置 90 | { 91 | vec_all.push_back(pos); 92 | } 93 | } 94 | } 95 | } 96 | 97 | Position *p = new Position(); //记录next step 98 | void Chess::AI_chess(Position& pos, char flag) //AI走 99 | { 100 | init_vector(); 101 | alpha_beta(1, Depth, -inf, inf); 102 | if (judge_postition(*p)) 103 | { 104 | chessBoard[p->row][p->col] = chessFlag1; 105 | pos.row = p->row; 106 | pos.col = p->col; 107 | } 108 | 109 | } 110 | 111 | 112 | //负值极大算法搜索 alpha + beta剪枝, 合并极大节点和极小节点两种情况, 减少代码量 113 | //在一盘棋局中, 若到棋手A走棋, alpha 相当于棋手A得到的最好的值, 对于棋手A的值, 从对手的角度看就要取负值. 114 | int Chess::alpha_beta(int player, int h, int alpha, int beta) 115 | //h搜索深度,player=1表示AI,player=0表示人类 116 | { 117 | if (vec1.begin() != vec1.end() && vec2.begin() != vec2.end())//不空 118 | { 119 | vector::iterator pter1 = vec1.end()-1; 120 | vector::iterator pter2 = vec2.end()-1; //判断最新添加后是否赢棋 121 | if (h == 1 || judge_victory(*pter1,chessFlag1) || judge_victory(*pter2, chessFlag2)) 122 | { 123 | //若到达深度 或是出现胜负 124 | return evaluate(player); 125 | } 126 | } 127 | 128 | if (vec3.begin() == vec3.end()) //尚未下棋。居中坐 129 | { 130 | vector::iterator vter = vec_blank.begin(); 131 | vec_blank.erase(vter + 7 * 15 + 7); //先删除居中棋子 132 | (*p).row = 7; //第八行第八列,执先 133 | (*p).col = 7; 134 | return 0; 135 | } 136 | 137 | // vec_blank 为可以落子的集合 138 | order(); //按搜索顺序排序 提高剪枝效率 139 | 140 | //遍历每一个候选步 141 | vector::iterator vter = vec_blank.begin(); 142 | while (vter != vec_blank.end()) 143 | { 144 | int tempv = vter - vec_blank.begin(); 145 | //如果要评估的位置没有相邻的子, 则不去评估 减少计算 146 | if (!has_neightnor(vter)) 147 | { 148 | vter++; 149 | continue; 150 | } 151 | 152 | Position *pos = new Position(); 153 | pos->col = (*vter).col; 154 | pos->row = (*vter).row; 155 | //判断是否为ai走棋 156 | if (player){ 157 | vec1.push_back(*pos); 158 | vec_blank.erase(vter); 159 | } 160 | else{ 161 | vec2.push_back(*pos); 162 | vec_blank.erase(vter); //从空白记录删除 163 | } 164 | vec3.push_back(*pos); 165 | 166 | //返回到上一层节点时,会给出分数的相反数(因为返回的值相当于是在对手的选择下对手对当前棋盘的评估分数 167 | //而作为他的对立方, 要把分数取反 168 | //alpha可以视为当前情况下,当前棋手可以得到的最好值,当前棋手得到最好值 == 对手不愿意接受的最差值 169 | //因为对手需要不断提高 170 | int value = -alpha_beta(!player, h - 1, -beta, -alpha); 171 | 172 | vector::iterator vter1 = vec1.end() - 1; 173 | vector::iterator vter2 = vec2.end() - 1; 174 | vector::iterator vter3 = vec3.end() - 1; 175 | vter = vec_blank.insert(vec_blank.begin() + tempv, *pos); 176 | //从空白记录原先位置插入 177 | //将刚才走棋移除 178 | if (player) { //是AI 179 | vec1.erase(vter1); 180 | } 181 | else{ //是玩家 182 | vec2.erase(vter2); 183 | } 184 | vec3.erase(vter3); 185 | //vec3.erase(vter); 这么写不行,超出范围 186 | 187 | delete pos; 188 | 189 | if (value > alpha) 190 | { 191 | //当depth == DEPTH时, 由于在循环内不断迭代, 总会在考虑后三步棋的情况下逐渐找到最好的走子方式; 192 | if (h == Depth) //找到路径了 193 | { 194 | p->row = (*vter).row; 195 | p->col = (*vter).col; 196 | } 197 | 198 | //alpha + beta剪枝点 199 | //当当前value > beta 时相当于 对手的 value < -alpha, 对手肯定不会考虑这个选择 200 | if (value >= beta) 201 | { 202 | //因为当前 depth中 仍需要遍历候选点, return 之后直接返回上层, 上层将返回值取负值 203 | //因为当调用该函数时alpah > - beta, 必定返回上层迭代时, value < alpha, 达到剪枝目的 204 | return beta; 205 | } 206 | 207 | alpha = value; //更新alpha值 208 | 209 | } 210 | vter++; 211 | 212 | } 213 | 214 | return alpha; 215 | 216 | } 217 | 218 | 219 | //离最后落子的邻居位置最有可能是最优点 220 | void Chess::order() 221 | { 222 | // vec3.end()-1 表示 vec3 中最后一个落子 223 | 224 | vector::iterator last_pt = vec3.end()-1; //实时添加 225 | //在最后落子附近搜索是否有可以落子的点 226 | for (int i = -1; i < 2; ++i) { 227 | for (int j = -1; j < 2; ++j) { 228 | //当 i = j = 0 是表示最后落子, 因为在其附近搜索所以舍弃 i = j = 0; 229 | if ( (i == 0 && j == 0) || (*last_pt).row + i < 0 || (*last_pt).row + i > 14|| 230 | (*last_pt).col + j < 0 || (*last_pt).col + j > 14) 231 | { 232 | continue; 233 | } 234 | vector::iterator vter = vec_blank.begin(); 235 | for (; vter < vec_blank.end(); vter++) { 236 | if (chessBoard[(*last_pt).row + i][(*last_pt).col + j] != chessBoardFlag) 237 | { 238 | break; //该位置不是空 239 | } 240 | //当最后落子点附近有可选点时, 先从删除, 然后从候选点列表首部插入 241 | if (((*last_pt).row + i)== (*vter).row && ((*last_pt).col + j) == (*vter).col) 242 | { 243 | vec_blank.erase(vter); 244 | Position* pos = new Position (); 245 | (*pos).row = (*last_pt).row + i; 246 | (*pos).col = (*last_pt).row + j; 247 | vec_blank.insert(vec_blank.begin(), *pos); 248 | break; 249 | } 250 | } 251 | } 252 | } 253 | } 254 | 255 | bool Chess::has_neightnor(vector::iterator pter) // 判断点 *vter 是否有邻居点 256 | { 257 | vector::iterator vter = vec3.begin(); 258 | if (vter == vec3.end()) 259 | { 260 | return false; //棋局尚未开始,一定没有邻居点 261 | } 262 | for (int i = -1; i < 2; ++i){ 263 | for (int j = -1; j < 2; ++j){ 264 | //当 i = j = 0 是表示最后落子, 因为在其附近搜索所以舍弃 i = j = 0; 265 | if ((i == 0 && j == 0) || (*pter).row + i < 0 || (*pter).row + i > 14 || 266 | (*pter).col + j < 0 || (*pter).col + j > 14){ 267 | continue; 268 | } 269 | vter = vec3.begin(); //循环前重置 270 | while (vter != vec3.end()) 271 | { 272 | if (((*pter).row + i) == (*vter).row && ((*pter).col + j) == (*vter).col){ 273 | return true; 274 | } 275 | vter++; 276 | } 277 | 278 | } 279 | } 280 | return false; 281 | 282 | } 283 | 284 | //棋型的评估分数, 表示当棋局为不同形状时的得分 285 | vector shape_score[15] = { {50, 0, 1, 1, 0, 0}, 286 | {50, 0, 0, 1, 1, 0,}, 287 | {500, 1, 1, 0, 1, 0}, 288 | {500, 0, 0, 1, 1, 1}, 289 | {500, 1, 1, 1, 0, 0}, 290 | {500, 0, 1, 1, 1, 0}, 291 | {500, 0, 1, 0, 1, 1, 0}, 292 | {500, 0, 1, 1, 0, 1, 0}, 293 | {50000, 1, 1, 1, 0, 1}, 294 | {50000, 1, 1, 0, 1, 1}, 295 | {50000, 1, 0, 1, 1, 1}, 296 | {50000, 1, 1, 1, 1, 0}, 297 | {50000, 0, 1, 1, 1, 1}, 298 | {50000, 0, 1, 1, 1, 1, 0}, 299 | {inf, 1, 1, 1, 1, 1} }; 300 | 301 | int ratio = 3; // 进攻的系数 大于1 防守型, 小于1 进攻型 302 | 303 | long int Chess::evaluate(int player) //评估函数, 主要是评估当前棋局的得分 304 | { 305 | //evaluate 为启发式的评价函数,alpha + beta搜索是相当于在假设双方在同一评价函数函数情况下才有效 306 | long int total_score = 0; 307 | vector my_list;//假定初始值 308 | vector enemy_list; 309 | 310 | if (player) { //是AI方 311 | my_list = vec1; 312 | enemy_list = vec2; 313 | } 314 | else { 315 | my_list = vec2; 316 | enemy_list = vec1; 317 | } 318 | 319 | //算自己的得分 320 | long int my_score = 0; 321 | 322 | vector::iterator pter = my_list.begin(); 323 | for(; pter != my_list.end(); ++pter) 324 | { 325 | int m = (*pter).row; 326 | int n = (*pter).col; 327 | my_score += cal_score(m, n, 0, 1, enemy_list, my_list); 328 | my_score += cal_score(m, n, 1, 0, enemy_list, my_list); 329 | my_score += cal_score(m, n, 1, 1, enemy_list, my_list); 330 | my_score += cal_score(m, n, -1, 1, enemy_list, my_list); 331 | } 332 | 333 | //算敌人的得分, 并减去 334 | long int enemy_score = 0; 335 | 336 | pter = enemy_list.begin(); 337 | for (; pter != enemy_list.end(); ++pter) 338 | { 339 | int m = (*pter).row; 340 | int n = (*pter).col; 341 | enemy_score += cal_score(m, n, 0, 1, my_list, enemy_list); 342 | enemy_score += cal_score(m, n, 1, 0, my_list, enemy_list); 343 | enemy_score += cal_score(m, n, 1, 1, my_list, enemy_list); 344 | enemy_score += cal_score(m, n, -1, 1, my_list, enemy_list); 345 | } 346 | 347 | 348 | //本方分数 减去 敌方分数 349 | total_score = my_score - (int)(enemy_score * ratio); 350 | 351 | return total_score; 352 | 353 | } 354 | 355 | 356 | 357 | int Chess::cal_score(int m, int n, int x_direct, int y_direct, vector& enemy_list, 358 | vector& my_list) //每个方向上的分值计算 359 | { 360 | int max_score = 0; //如果没有匹配的模型返回空值 361 | 362 | //在落子点 左右方向上循环查找得分形状 363 | for (int i = -5; i < 1; ++i) 364 | { 365 | vector temp{}; //临时变量 366 | for (int j = 0; j < 6; ++j) 367 | { 368 | if (m + (i + j) * x_direct < 0 || m + (i + j) * x_direct >14 || 369 | n + (i + j) * y_direct < 0 || n + (i + j) * y_direct > 14)//在棋盘内判断 370 | { 371 | continue; //有一个在棋盘外, 372 | } 373 | vector::iterator pter1 = enemy_list.begin(); 374 | vector::iterator pter2 = my_list.begin(); 375 | while (pter1 != enemy_list.end() || pter2 != my_list.end()) 376 | { 377 | 378 | if (m + (i + j) * x_direct == (*pter1).row && n + (i + j) * y_direct == (*pter1).col) { 379 | //说明该位置是敌方棋子 380 | temp.push_back(2); 381 | break; // 退出,下一轮循环 382 | } 383 | else if (m + (i + j) * x_direct == (*pter2).row && n + (i + j) * y_direct == (*pter2).col) { 384 | //说明该位置是我方棋子 385 | temp.push_back(1); 386 | break; 387 | } 388 | 389 | if (pter1 == enemy_list.end()-1){ 390 | if (pter2 == my_list.end()-1) { 391 | //说明该位置是双方均没有落子 392 | temp.push_back(0); 393 | break; //均为空,继续退出循环 394 | } 395 | else { 396 | pter2++; 397 | } 398 | } 399 | else { 400 | pter1++; 401 | if (pter2 != my_list.end()-1) { 402 | pter2++; 403 | } 404 | } 405 | } 406 | } 407 | if (temp.size() < 5) 408 | { 409 | continue; //凑不成五个,不用计算分数了 410 | } 411 | 412 | vector tmp_shap5 = { temp[0], temp[1], temp[2], temp[3], temp[4] }; //记录连五个的情况 413 | vector tmp_shap6 = { temp[0], temp[1], temp[2], temp[3], temp[4] }; //记录连六个的情况 414 | if (temp.size() == 6) 415 | { 416 | tmp_shap6.push_back(temp[5]);//防止越界 417 | } 418 | 419 | for (int k = 0; k < 15; k++) 420 | { 421 | if (match_vector(tmp_shap5, shape_score[k]) || match_vector(tmp_shap6, shape_score[k])) { 422 | //模型记录中五个的情况不会成为6个的齐头的字串 423 | if (shape_score[k][0] > max_score) 424 | { 425 | max_score = shape_score[k][0]; //复制 426 | } 427 | 428 | } 429 | 430 | } 431 | if (max_score == inf) { 432 | //判断出输赢了,可以结束了 433 | break; 434 | } 435 | } 436 | 437 | return max_score; 438 | 439 | } 440 | 441 | bool Chess::match_vector(vector& temp, vector& shape) 442 | { 443 | vector::iterator iter1 = temp.begin(); 444 | vector::iterator iter2 = shape.begin() + 1; //第一个位置是分数 445 | while (iter1 != temp.end() && iter2 != shape.end()) 446 | //只要有一个到头就不判断,模型记录中5个的情况不会成为6个的齐头的字串 447 | { 448 | if (*iter1 != *iter2) 449 | return false; //模型不匹配 450 | else{ //继续 451 | ++iter1; 452 | ++iter2; 453 | } 454 | } 455 | return true; 456 | } 457 | 458 | void Chess::take_step (Position& pos, int player, char flag) 459 | { 460 | chessBoard[pos.row][pos.col] = flag; 461 | } 462 | 463 | int Chess::judge_postition(const Position& pos) //判断坐标的合法性 464 | { 465 | //在棋盘上 466 | if (pos.row >= 0 && pos.row < N && pos.col >= 0 && pos.col < N) { 467 | //所在位置为空(没有棋子) 468 | if (chessBoard[pos.row][pos.col] == chessBoardFlag) { 469 | return 1; //合法 470 | } 471 | } 472 | return 0; //非法 473 | } 474 | 475 | int Chess::judge_victory(Position pos, char flag) //判断是否有玩家获胜 476 | { 477 | int begin = 0; 478 | int end = 0; 479 | 480 | //判断行是否满足条件 481 | (pos.col - 4) >= 0 ? begin = (pos.col - 4) : begin = 0;//往前探4个位置 482 | (pos.col + 4) >= N ? end = N - 1: end = (pos.col + 4);//往后探4个位置 483 | for (int i = pos.row, j = begin; j + 4 <= end; ++j) 484 | { 485 | if (chessBoard[i][j] == flag && chessBoard[i][j + 1] == flag && 486 | chessBoard[i][j + 2] == flag && chessBoard[i][j + 3] == flag && 487 | chessBoard[i][j + 4] == flag)//该棋子周围是否出现五子连珠 488 | return 1; 489 | 490 | } 491 | 492 | //判断列是否满足条件 493 | (pos.row - 4) >= 0 ? begin = (pos.row - 4) : begin = 0; 494 | (pos.row + 4) >= N ? end = N - 1: end = (pos.row + 4); 495 | for (int j = pos.col, i = begin; i + 4 <= end; ++i) { 496 | if (chessBoard[i][j] == flag && chessBoard[i + 1][j] == flag && 497 | chessBoard[i + 2][j] == flag && chessBoard[i + 3][j] == flag && 498 | chessBoard[i + 4][j] == flag)//该棋子周围是否出现五子连珠 499 | return 1; 500 | 501 | } 502 | 503 | //判断主对角线是否满足条件 504 | int length = 0; //相对长度 505 | int start = 0; 506 | int finish = 0; 507 | pos.row > pos.col ? length = pos.col : length = pos.row; //选择一个较小的值作限界 508 | if (length > 4) 509 | { 510 | length = 4; 511 | } 512 | begin = pos.row - length; //横坐标起始位置 513 | start = pos.col - length; //纵坐标起始位置 514 | 515 | pos.row > pos.col ? length = N - pos.row - 1 : length = N - pos.col - 1; 516 | if (length > 4) 517 | { 518 | length = 4; 519 | } 520 | end = pos.row + length; //横坐标结束位置 521 | finish = pos.col + length; //纵坐标结束位置 522 | 523 | for (int i = begin, j = start; (i + 4 <= end) && (j + 4 <= finish); ++i, ++j) 524 | { 525 | if (chessBoard[i][j] == flag && chessBoard[i + 1][j + 1] == flag && 526 | chessBoard[i + 2][j + 2] == flag && chessBoard[i + 3][j + 3] == flag && 527 | chessBoard[i + 4][j + 4] == flag) 528 | return 1; 529 | } 530 | //判断副对角线是否满足条件 531 | pos.row > (N - pos.col - 1) ? length = N - pos.col - 1 : length = pos.row; 532 | if (length > 4) 533 | { 534 | length = 4; 535 | } 536 | begin = pos.row - length; //横坐标起始位置 537 | start = pos.col + length; //纵坐标起始位置 538 | 539 | (N - pos.row -1) > pos.col ? length = pos.col : length = N - pos.row - 1; 540 | if (length > 4) 541 | { 542 | length = 4; 543 | } 544 | end = pos.row + length; //横坐标结束位置 545 | finish = pos.col - length; //纵坐标结束位置 546 | 547 | for (int i = begin, j = start; (i + 4 <= end) && (j - 4 >= finish); ++i, --j) 548 | { 549 | if (chessBoard[i][j] == flag && chessBoard[i + 1][j - 1] == flag && 550 | chessBoard[i + 2][j - 2] == flag && chessBoard[i + 3][j - 3] == flag && 551 | chessBoard[i + 4][j - 4] == flag) 552 | return 1; 553 | } 554 | 555 | //该棋局尚未开始 556 | for (int x = 0; x < N; ++x) 557 | { 558 | for (int y = 0; y < N; ++y) 559 | { 560 | if (chessBoard[x][y] == chessBoardFlag) 561 | { 562 | return 0; //未下棋 563 | } 564 | } 565 | } 566 | return -1; //和局 567 | 568 | } 569 | 570 | 571 | char Chess::get_coordinate(int x, int y) //返回(x,y)的信息 572 | { 573 | return chessBoard[x][y]; 574 | 575 | } 576 | -------------------------------------------------------------------------------- /src/GameWindow.cpp: -------------------------------------------------------------------------------- 1 | //本文件为游戏窗口 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "./include/GameWindow.h" 11 | #include "./include/ui_gamewindow.h" 12 | #include "./include/Chess.h" 13 | 14 | using namespace std; 15 | 16 | int moveX = 0, moveY = 0, lastx = 0, lasty = 0; 17 | 18 | GameWindow::GameWindow(QWidget *parent) : 19 | QMainWindow(parent), 20 | ui(new Ui::GameWindow) 21 | { 22 | ui->setupUi(this); 23 | //设置界面大小和背景 24 | game = new Chess(); 25 | this->setFixedSize(660,660);//设置窗口大小 26 | setWindowTitle("五子棋"); 27 | } 28 | 29 | GameWindow::~GameWindow() 30 | { 31 | delete ui; 32 | } 33 | 34 | void GameWindow::mouseReleaseEvent(QMouseEvent* event) //下棋动作 35 | { 36 | Position player1; // AI 37 | Position player2; // 玩家 38 | 39 | // 玩家执棋 40 | player2.row = moveX; 41 | player2.col = moveY; 42 | if (game->judge_postition(player2) == 1){ //判断坐标是否合法 43 | game->take_step(player2, 2, chessFlag2); //玩家走 44 | vec2.push_back(player2); //添加记录 45 | vec3.push_back(player2); 46 | } 47 | update(); 48 | lastx = player2.row; //记录最后下棋的位置 49 | lasty = player2.col; 50 | // 判断输赢 51 | if (is_win(player2, 2, chessFlag2)){ //玩家 52 | game->Init_chessboard(); //清空 53 | update(); //再来一局 54 | return; 55 | } 56 | 57 | game->AI_chess(player1, chessFlag1); 58 | // 如果是人机模式,需要调用AI下棋 59 | vec1.push_back(player1); //添加记录 60 | vec3.push_back(player1); 61 | update(); // 重绘 62 | lastx = player1.row; // 记录最后下棋的位置 63 | lasty = player1.col; 64 | // 判断输赢 65 | if (is_win(player1, 1, chessFlag1)){ //AI 66 | game->Init_chessboard(); //清空 67 | update(); //再来一局 68 | } 69 | 70 | 71 | } 72 | 73 | bool GameWindow::is_win(Position& pos, int player, char flag) //判断具体哪位玩家赢 74 | { 75 | if (game->judge_victory(pos, flag) != 0) 76 | { 77 | //判断有无人获胜 78 | if (game->judge_victory(pos, flag) == 1) 79 | { 80 | QString str; 81 | //判断是否有人获胜,1表示获胜 82 | if (player == 1) { 83 | str = "Black player"; //黑子是玩家1 84 | } 85 | else { // player == 2, 是玩家2 86 | str = "White player"; //白子是玩家2 87 | } 88 | //是否再来一局 89 | QMessageBox msg(this);//对话框设置父组件 90 | msg.setWindowTitle("Congratulations!");//对话框标题 91 | msg.setText(str +" win!\nAnother Game or Not?");//对话框提示文本 92 | msg.setStandardButtons(QMessageBox::Ok | QMessageBox:: No );//对话框上的按钮 93 | if(msg.exec() == QMessageBox::Ok){ //如果再来一局 94 | update(); 95 | game->Init_chessboard(); //清空 96 | } 97 | else{ 98 | this->close(); //退出 99 | } 100 | } 101 | return true; //有人获胜 102 | 103 | } 104 | 105 | return false; //没人获胜 或者再来一局 106 | 107 | } 108 | 109 | void GameWindow::mouseMoveEvent(QMouseEvent *event) //鼠标移动事件 110 | { 111 | moveX = (event->position().y()-40)/40; 112 | moveY = (event->position().x()-20)/40; 113 | update(); //更新后就重画 114 | } 115 | 116 | void GameWindow::paintEvent(QPaintEvent* event) 117 | { 118 | //绘制棋盘边界边框,棋盘内颜色 119 | QPainter painter(this); //画板 120 | painter.setRenderHint(QPainter::Antialiasing,true); //抗锯齿 121 | 122 | this->setAutoFillBackground(true); 123 | QPalette palette ; //画背景色 124 | palette.setColor(QPalette::Window, QColor("#B1723C")); 125 | this->setPalette(palette); 126 | 127 | centralWidget()->setMouseTracking(true);//接受鼠标的移动 128 | setMouseTracking(true); 129 | 130 | QPen pen = painter.pen(); //画笔 131 | pen.setColor(QColor("#8D5822")); //棋盘边界边框 132 | pen.setWidth(7); 133 | painter.setPen(pen); 134 | 135 | QBrush brush; //画刷,棋盘内颜色 136 | brush.setColor(QColor("#EEC085")); 137 | brush.setStyle(Qt::SolidPattern); 138 | painter.setBrush(brush); 139 | painter.drawRect(20,40,600,600); 140 | 141 | //绘制棋盘 142 | pen.setColor(Qt::black); 143 | pen.setWidth(1); 144 | painter.setPen(pen); //黑线 145 | for(int i = 0; i < 15; ++i) 146 | { 147 | painter.drawLine(40+i*40,60,40+i*40,620);//纵线,QT的坐标60最上,620最下 148 | painter.drawLine(40,60+i*40,600,60+i*40);//横线,QT的坐标40最左,600最右 149 | } 150 | 151 | //绘制棋盘中间的五个黑点 152 | brush.setColor(Qt::black); //五个小黑点 153 | painter.setBrush(brush); 154 | painter.drawRect(115,175,10,10); 155 | painter.drawRect(475,175,10,10); 156 | painter.drawRect(155,495,10,10); 157 | painter.drawRect(475,495,10,10); 158 | painter.drawRect(315,335,10,10); 159 | 160 | //循环遍历,画棋子 161 | painter.setPen(Qt::NoPen); //去掉pen,避免画出的棋子边缘带线条 162 | for(int i=0; i < 15; ++i) 163 | { 164 | for(int j=0; j <15; ++j) 165 | { 166 | if(game->get_coordinate(i,j) == chessFlag1)// 黑子,AI 167 | { 168 | brush.setColor(Qt::black); 169 | painter.setBrush(brush); 170 | //在第i行,第j列画一个圆形 171 | painter.drawEllipse(QPoint((j+1)*40,(i+1)*40+20), 18, 18); 172 | } 173 | else if(game->get_coordinate(i,j) == chessFlag2)//白子,玩家2 174 | { 175 | brush.setColor(Qt::white); 176 | painter.setBrush(brush); 177 | painter.drawEllipse(QPoint((j+1)*40,(i+1)*40+20), 18, 18); 178 | } 179 | } 180 | } 181 | 182 | pen.setColor(Qt::red); 183 | pen.setWidth(1); 184 | painter.setPen(pen); 185 | //鼠标移动后,当前红方框标识 186 | if((moveX*40+40)>=20 && (moveX*40+40)<=620 && (moveY*40+20)>=40 && (moveY*40+20)<=640) 187 | { 188 | painter.drawLine((moveY+1)*40-20,(moveX+1)*40,(moveY+1)*40-10,(moveX+1)*40); 189 | painter.drawLine((moveY+1)*40+20,(moveX+1)*40,(moveY+1)*40+10,(moveX+1)*40); 190 | painter.drawLine((moveY+1)*40-20,(moveX+1)*40+40,(moveY+1)*40-10,(moveX+1)*40+40); 191 | painter.drawLine((moveY+1)*40+20,(moveX+1)*40+40,(moveY+1)*40+10,(moveX+1)*40+40); 192 | painter.drawLine((moveY+1)*40-20,(moveX+1)*40,(moveY+1)*40-20,(moveX+1)*40+10); 193 | painter.drawLine((moveY+1)*40+20,(moveX+1)*40,(moveY+1)*40+20,(moveX+1)*40+10); 194 | painter.drawLine((moveY+1)*40-20,(moveX+1)*40+40,(moveY+1)*40-20,(moveX+1)*40+30); 195 | painter.drawLine((moveY+1)*40+20,(moveX+1)*40+40,(moveY+1)*40+20,(moveX+1)*40+30); 196 | 197 | } 198 | //lastx,lasty记录最后一个落子的位置 199 | //画最后一个落子位置 200 | 201 | painter.drawLine((lasty+1)*40-6,(lastx+1)*40+20,(lasty+1)*40-1,(lastx+1)*40+20); 202 | painter.drawLine((lasty+1)*40+1,(lastx+1)*40+20,(lasty+1)*40+6,(lastx+1)*40+20); 203 | painter.drawLine((lasty+1)*40,(lastx+1)*40+14,(lasty+1)*40,(lastx+1)*40+19); 204 | painter.drawLine((lasty+1)*40,(lastx+1)*40+21,(lasty+1)*40,(lastx+1)*40+26); 205 | 206 | 207 | } 208 | --------------------------------------------------------------------------------