基于QT(C++)实现(界面)实现的五子棋游戏
Qt小游戏开发五子棋带AI功能写了一个带AI的五子棋小游戏AI的表现还可以~1.预览2.步骤整体的代码结构一个游戏逻辑类一个UI类2.1定义游戏数据结构// 游戏类型双人还是AI目前固定让AI下黑子 enum GameType { PERSON, BOT }; // 游戏状态 enum GameStatus { PLAYING, WIN, DEAD }; // 棋盘尺寸 const int kBoardSizeNum 15; class GameModel { public: GameModel(); public: std::vectorstd::vectorint gameMapVec; // 存储当前游戏棋盘和棋子的情况,空白为0白子1黑子-1 std::vectorstd::vectorint scoreMapVec; // 存储各个点位的评分情况作为AI下棋依据 bool playerFlag; // 标示下棋方 GameType gameType; // 游戏模式 GameStatus gameStatus; // 游戏状态 void startGame(GameType type); // 开始游戏 void calculateScore(); // 计算评分 void actionByPerson(int row, int col); // 人执行下棋 void actionByAI(int clickRow, int clickCol); // 机器执行下棋 void updateGameMap(int row, int col); // 每次落子后更新游戏棋盘 bool isWin(int row, int col); // 判断游戏是否胜利 bool isDeadGame(); // 判断是否和棋 };2.2游戏逻辑1初始化void GameModel::startGame(GameType type) { gameType type; // 初始棋盘 gameMapVec.clear(); for (int i 0; i kBoardSizeNum; i) { std::vectorint lineBoard; for (int j 0; j kBoardSizeNum; j) lineBoard.push_back(0); gameMapVec.push_back(lineBoard); } // 如果是AI模式需要初始化评分数组 if (gameType BOT) { scoreMapVec.clear(); for (int i 0; i kBoardSizeNum; i) { std::vectorint lineScores; for (int j 0; j kBoardSizeNum; j) lineScores.push_back(0); scoreMapVec.push_back(lineScores); } } // 己方下为true,对方下位false playerFlag true; }棋盘格子初始化AI评分初始化设置一些状态2判断输赢bool GameModel::isWin(int row, int col) { // 横竖斜四种大情况每种情况都根据当前落子往后遍历5个棋子有一种符合就算赢 // 水平方向 for (int i 0; i 5; i) { // 往左5个往右匹配4个子20种情况 if (col - i 0 col - i 4 kBoardSizeNum gameMapVec[row][col - i] gameMapVec[row][col - i 1] gameMapVec[row][col - i] gameMapVec[row][col - i 2] gameMapVec[row][col - i] gameMapVec[row][col - i 3] gameMapVec[row][col - i] gameMapVec[row][col - i 4]) return true; } // 竖直方向(上下延伸4个) for (int i 0; i 5; i) { if (row - i 0 row - i 4 kBoardSizeNum gameMapVec[row - i][col] gameMapVec[row - i 1][col] gameMapVec[row - i][col] gameMapVec[row - i 2][col] gameMapVec[row - i][col] gameMapVec[row - i 3][col] gameMapVec[row - i][col] gameMapVec[row - i 4][col]) return true; } // 左斜方向 for (int i 0; i 5; i) { if (row i kBoardSizeNum row i - 4 0 col - i 0 col - i 4 kBoardSizeNum gameMapVec[row i][col - i] gameMapVec[row i - 1][col - i 1] gameMapVec[row i][col - i] gameMapVec[row i - 2][col - i 2] gameMapVec[row i][col - i] gameMapVec[row i - 3][col - i 3] gameMapVec[row i][col - i] gameMapVec[row i - 4][col - i 4]) return true; } // 右斜方向 for (int i 0; i 5; i) { if (row - i 0 row - i 4 kBoardSizeNum col - i 0 col - i 4 kBoardSizeNum gameMapVec[row - i][col - i] gameMapVec[row - i 1][col - i 1] gameMapVec[row - i][col - i] gameMapVec[row - i 2][col - i 2] gameMapVec[row - i][col - i] gameMapVec[row - i 3][col - i 3] gameMapVec[row - i][col - i] gameMapVec[row - i 4][col - i 4]) return true; } return false; }这里判断输赢每次下了一个子之后就沿着这个子周围的八个方向延伸4个子然后判断有没有连成5子的总共20种情况都列出来就行。3判断僵局bool GameModel::isDeadGame() { // 所有空格全部填满 for (int i 1; i kBoardSizeNum; i) for (int j 1; j kBoardSizeNum; j) { if (!(gameMapVec[i][j] 1 || gameMapVec[i][j] -1)) return false; } return true; }4电脑AI的设计五子棋AI的算法设计有很多这里采用评分函数的方法。基本思想构造一个对应棋盘格子的 评分数组遍历棋盘针对每个空白位子计算该位子的评分值最后找到具有最大评分值的格子落子如果有多个格子的评分一样就随机选取一个。评分算法针对某个空白位往周围八个方向延伸记录玩家或者电脑连成线的子个数如果遇到空白位则停止循环并记录空白位个数最后根据连成线的子个数和两端空白位的个数给当前统计的空白格子加分比如如果玩家有三个子连成了就加多分或者电脑这边有4个子连成了就多加分反正趋势就是尽量遏制玩家并且保持电脑自身的进攻。这当中涉及到的每种情况加分标准需要自己去调整参数调整的参数越好AI下棋的水平就越厉害。核心算法代码void GameModel::actionByAI(int clickRow, int clickCol) { // 计算评分 calculateScore(); // 从评分中找出最大分数的位置 int maxScore 0; std::vectorstd::pairint, int maxPoints; for (int row 1; row kBoardSizeNum; row) for (int col 1; col kBoardSizeNum; col) { // 前提是这个坐标是空的 if (gameMapVec[row][col] 0) { if (scoreMapVec[row][col] maxScore) // 找最大的数和坐标 { maxPoints.clear(); maxScore scoreMapVec[row][col]; maxPoints.push_back(std::make_pair(row, col)); } else if (scoreMapVec[row][col] maxScore) // 如果有多个最大的数都存起来 maxPoints.push_back(std::make_pair(row, col)); } } // 随机落子如果有多个点的话 srand((unsigned)time(0)); int index rand() % maxPoints.size(); std::pairint, int pointPair maxPoints.at(index); clickRow pointPair.first; // 记录落子点 clickCol pointPair.second; updateGameMap(clickRow, clickCol); } // 最关键的计算评分函数 void GameModel::calculateScore() { // 统计玩家或者电脑连成的子 int personNum 0; // 玩家连成子的个数 int botNum 0; // AI连成子的个数 int emptyNum 0; // 各方向空白位的个数 // 清空评分数组 scoreMapVec.clear(); for (int i 0; i kBoardSizeNum; i) { std::vectorint lineScores; for (int j 0; j kBoardSizeNum; j) lineScores.push_back(0); scoreMapVec.push_back(lineScores); } // 计分此处是完全遍历其实可以用bfs或者dfs加减枝降低复杂度通过调整权重值调整AI智能程度以及攻守风格 for (int row 0; row kBoardSizeNum; row) for (int col 0; col kBoardSizeNum; col) { // 空白点就算 if (row 0 col 0 gameMapVec[row][col] 0) { // 遍历周围八个方向 for (int y -1; y 1; y) for (int x -1; x 1; x) { // 重置 personNum 0; botNum 0; emptyNum 0; // 原坐标不算 if (!(y 0 x 0)) { // 每个方向延伸4个子 // 对玩家白子评分正反两个方向 for (int i 1; i 4; i) { if (row i * y 0 row i * y kBoardSizeNum col i * x 0 col i * x kBoardSizeNum gameMapVec[row i * y][col i * x] 1) // 玩家的子 { personNum; } else if (row i * y 0 row i * y kBoardSizeNum col i * x 0 col i * x kBoardSizeNum gameMapVec[row i * y][col i * x] 0) // 空白位 { emptyNum; break; } else // 出边界 break; } for (int i 1; i 4; i) { if (row - i * y 0 row - i * y kBoardSizeNum col - i * x 0 col - i * x kBoardSizeNum gameMapVec[row - i * y][col - i * x] 1) // 玩家的子 { personNum; } else if (row - i * y 0 row - i * y kBoardSizeNum col - i * x 0 col - i * x kBoardSizeNum gameMapVec[row - i * y][col - i * x] 0) // 空白位 { emptyNum; break; } else // 出边界 break; } if (personNum 1) // 杀二 scoreMapVec[row][col] 10; else if (personNum 2) // 杀三 { if (emptyNum 1) scoreMapVec[row][col] 30; else if (emptyNum 2) scoreMapVec[row][col] 40; } else if (personNum 3) // 杀四 { // 量变空位不一样优先级不一样 if (emptyNum 1) scoreMapVec[row][col] 60; else if (emptyNum 2) scoreMapVec[row][col] 110; } else if (personNum 4) // 杀五 scoreMapVec[row][col] 10100; // 进行一次清空 emptyNum 0; // 对AI黑子评分 for (int i 1; i 4; i) { if (row i * y 0 row i * y kBoardSizeNum col i * x 0 col i * x kBoardSizeNum gameMapVec[row i * y][col i * x] 1) // 玩家的子 { botNum; } else if (row i * y 0 row i * y kBoardSizeNum col i * x 0 col i * x kBoardSizeNum gameMapVec[row i * y][col i * x] 0) // 空白位 { emptyNum; break; } else // 出边界 break; } for (int i 1; i 4; i) { if (row - i * y 0 row - i * y kBoardSizeNum col - i * x 0 col - i * x kBoardSizeNum gameMapVec[row - i * y][col - i * x] -1) // AI的子 { botNum; } else if (row - i * y 0 row - i * y kBoardSizeNum col - i * x 0 col - i * x kBoardSizeNum gameMapVec[row - i * y][col - i * x] 0) // 空白位 { emptyNum; break; } else // 出边界 break; } if (botNum 0) // 普通下子 scoreMapVec[row][col] 5; else if (botNum 1) // 活二 scoreMapVec[row][col] 10; else if (botNum 2) { if (emptyNum 1) // 死三 scoreMapVec[row][col] 25; else if (emptyNum 2) scoreMapVec[row][col] 50; // 活三 } else if (botNum 3) { if (emptyNum 1) // 死四 scoreMapVec[row][col] 55; else if (emptyNum 2) scoreMapVec[row][col] 100; // 活四 } else if (botNum 4) scoreMapVec[row][col] 10000; // 活五 } } } } }2.3游戏界面类用于绘图人机交互class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent 0); ~MainWindow(); protected: // 绘制 void paintEvent(QPaintEvent *event); // 监听鼠标移动情况方便落子 void mouseMoveEvent(QMouseEvent *event); // 实际落子 void mouseReleaseEvent(QMouseEvent *event); private: GameModel *game; // 游戏指针 GameType game_type; // 存储游戏类型 int clickPosRow, clickPosCol; // 存储将点击的位置 void initGame(); void checkGame(int y, int x); private slots: void chessOneByPerson(); // 人执行 void chessOneByAI(); // AI下棋 void initPVPGame(); void initPVEGame(); };2.4游戏界面控制1启动游戏void MainWindow::initGame() { // 初始化游戏模型 game new GameModel; initPVPGame(); } void MainWindow::initPVPGame() { game_type PERSON; game-gameStatus PLAYING; game-startGame(game_type); update(); } void MainWindow::initPVEGame() { game_type BOT; game-gameStatus PLAYING; game-startGame(game_type); update(); }分成两种模式绑定信号槽用于模式切换2鼠标移动void MainWindow::mouseMoveEvent(QMouseEvent *event) { // 通过鼠标的hover确定落子的标记 int x event-x(); int y event-y(); // 棋盘边缘不能落子 if (x kBoardMargin kBlockSize / 2 x size().width() - kBoardMargin y kBoardMargin kBlockSize / 2 y size().height()- kBoardMargin) { // 获取最近的左上角的点 int col x / kBlockSize; int row y / kBlockSize; int leftTopPosX kBoardMargin kBlockSize * col; int leftTopPosY kBoardMargin kBlockSize * row; // 根据距离算出合适的点击位置,一共四个点根据半径距离选最近的 clickPosRow -1; // 初始化最终的值 clickPosCol -1; int len 0; // 计算完后取整就可以了 // 确定一个误差在范围内的点且只可能确定一个出来 len sqrt((x - leftTopPosX) * (x - leftTopPosX) (y - leftTopPosY) * (y - leftTopPosY)); if (len kPosDelta) { clickPosRow row; clickPosCol col; } len sqrt((x - leftTopPosX - kBlockSize) * (x - leftTopPosX - kBlockSize) (y - leftTopPosY) * (y - leftTopPosY)); if (len kPosDelta) { clickPosRow row; clickPosCol col 1; } len sqrt((x - leftTopPosX) * (x - leftTopPosX) (y - leftTopPosY - kBlockSize) * (y - leftTopPosY - kBlockSize)); if (len kPosDelta) { clickPosRow row 1; clickPosCol col; } len sqrt((x - leftTopPosX - kBlockSize) * (x - leftTopPosX - kBlockSize) (y - leftTopPosY - kBlockSize) * (y - leftTopPosY - kBlockSize)); if (len kPosDelta) { clickPosRow row 1; clickPosCol col 1; } } // 存了坐标后也要重绘 update(); }这里面涉及到一个算法移动数标时根据半径实时计算距离最近的可点击格子并作标记。3下棋动作void MainWindow::mouseReleaseEvent(QMouseEvent *event) { // 人下棋并且不能抢机器的棋 if (!(game_type BOT !game-playerFlag)) { chessOneByPerson(); // 如果是人机模式需要调用AI下棋 if (game-gameType BOT !game-playerFlag) { // 用定时器做一个延迟 QTimer::singleShot(kAIDelay, this, SLOT(chessOneByAI())); } } } void MainWindow::chessOneByPerson() { // 根据当前存储的坐标下子 // 只有有效点击才下子并且该处没有子 if (clickPosRow ! -1 clickPosCol ! -1 game-gameMapVec[clickPosRow][clickPosCol] 0) { game-actionByPerson(clickPosRow, clickPosCol); QSound::play(CHESS_ONE_SOUND); // 重绘 update(); } } void MainWindow::chessOneByAI() { game-actionByAI(clickPosRow, clickPosCol); QSound::play(CHESS_ONE_SOUND); update(); }鼠标点击和AI落子都要记录坐标AI的动作用定时器加了一个延时(4)绘图void MainWindow::paintEvent(QPaintEvent *event) { QPainter painter(this); // 绘制棋盘 painter.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿 // QPen pen; // 调整线条宽度 // pen.setWidth(2); // painter.setPen(pen); for (int i 0; i kBoardSizeNum 1; i) { painter.drawLine(kBoardMargin kBlockSize * i, kBoardMargin, kBoardMargin kBlockSize * i, size().height() - kBoardMargin); painter.drawLine(kBoardMargin, kBoardMargin kBlockSize * i, size().width() - kBoardMargin, kBoardMargin kBlockSize * i); } QBrush brush; brush.setStyle(Qt::SolidPattern); // 绘制落子标记(防止鼠标出框越界) if (clickPosRow 0 clickPosRow kBoardSizeNum clickPosCol 0 clickPosCol kBoardSizeNum game-gameMapVec[clickPosRow][clickPosCol] 0) { if (game-playerFlag) brush.setColor(Qt::white); else brush.setColor(Qt::black); painter.setBrush(brush); painter.drawRect(kBoardMargin kBlockSize * clickPosCol - kMarkSize / 2, kBoardMargin kBlockSize * clickPosRow - kMarkSize / 2, kMarkSize, kMarkSize); } // 绘制棋子 for (int i 0; i kBoardSizeNum; i) for (int j 0; j kBoardSizeNum; j) { if (game-gameMapVec[i][j] 1) { brush.setColor(Qt::white); painter.setBrush(brush); painter.drawEllipse(kBoardMargin kBlockSize * j - kRadius, kBoardMargin kBlockSize * i - kRadius, kRadius * 2, kRadius * 2); } else if (game-gameMapVec[i][j] -1) { brush.setColor(Qt::black); painter.setBrush(brush); painter.drawEllipse(kBoardMargin kBlockSize * j - kRadius, kBoardMargin kBlockSize * i - kRadius, kRadius * 2, kRadius * 2); } } // 判断输赢 if (clickPosRow 0 clickPosRow kBoardSizeNum clickPosCol 0 clickPosCol kBoardSizeNum (game-gameMapVec[clickPosRow][clickPosCol] 1 || game-gameMapVec[clickPosRow][clickPosCol] -1)) { if (game-isWin(clickPosRow, clickPosCol) game-gameStatus PLAYING) { qDebug() win; game-gameStatus WIN; QSound::play(WIN_SOUND); QString str; if (game-gameMapVec[clickPosRow][clickPosCol] 1) str white player; else if (game-gameMapVec[clickPosRow][clickPosCol] -1) str black player; QMessageBox::StandardButton btnValue QMessageBox::information(this, congratulations, str win!); // 重置游戏状态否则容易死循环 if (btnValue QMessageBox::Ok) { game-startGame(game_type); game-gameStatus PLAYING; } } } // 判断死局 if (game-isDeadGame()) { QSound::play(LOSE_SOUND); QMessageBox::StandardButton btnValue QMessageBox::information(this, oops, dead game!); if (btnValue QMessageBox::Ok) { game-startGame(game_type); game-gameStatus PLAYING; } } }注意QWindow只有在不加UI文件情况下才能用这个绘图函数在每一次update调用后实时检测游戏输赢和僵局。3.截图♻️ 资源大小101KB➡️资源下载https://download.csdn.net/download/s1t16/87425291注更多内容可关注微信公众号【神仙别闹】如当前文章或代码侵犯了您的权益请私信作者删除
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2494122.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!