欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 艺术 > C++/C的OpenCV和五子棋(Gomoku)算法

C++/C的OpenCV和五子棋(Gomoku)算法

2025/6/23 4:57:57 来源:https://blog.csdn.net/m0_54069809/article/details/148804115  浏览:    关键词:C++/C的OpenCV和五子棋(Gomoku)算法

项目架构 (Project Architecture)

我们将项目分解为四个主要部分,每个部分用一个类来表示,以保持代码的模块化和整洁性。

  1. GameBoard 类 (棋盘逻辑)

    • 职责: 管理棋盘的内部状态,如哪个位置有棋子,判断输赢,放置棋子等。它不关心视觉或AI,只负责游戏规则。
    • 成员: 一个15x15的二维数组来存储棋盘状态 (0: 空, 1: 黑棋, 2: 白棋)。
    • 方法: place_stone(), check_win(), is_valid_move(), reset()
  2. GomokuAI 类 (AI算法)

    • 职责: 决定AI下一步该走哪里。这是程序的大脑。
    • 核心算法: 我们将使用一个经典的启发式评估函数 (Heuristic Evaluation Function)。对于更高级的版本,可以使用Minimax算法 + Alpha-Beta剪枝
    • 方法: find_best_move()
  3. Vision 类 (计算机视觉)

    • 职责: 使用OpenCV处理来自摄像头的图像。包括识别棋盘网格、检测棋子位置、识别用户的落子。
    • 方法: detect_board_grid(), detect_stones(), get_human_move().
  4. main.cpp (主程序)

    • 职责: 程序的入口,负责初始化各个模块,并控制主游戏循环 (Game Loop)。

第一步:项目设置与环境

  1. 安装依赖:

    • C++ 编译器: 如 g++ (Linux/MinGW) 或 MSVC (Windows)。
    • OpenCV: 下载并安装OpenCV库 (推荐版本 4.x)。
    • CMake: 用于管理项目和依赖,是C++项目的标准构建工具。
  2. 创建项目结构:

    GomokuAI/
    ├── CMakeLists.txt
    ├── src/
    │   ├── main.cpp
    │   ├── GameBoard.h
    │   ├── GameBoard.cpp
    │   ├── GomokuAI.h
    │   ├── GomokuAI.cpp
    │   ├── Vision.h
    │   ├── Vision.cpp
    └── ... (build目录等)
    
  3. 编写 CMakeLists.txt:
    这是告诉CMake如何构建你的项目的配置文件。

    cmake_minimum_required(VERSION 3.10)
    project(GomokuAI)set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)# 找到OpenCV库
    # 你可能需要设置 OpenCV_DIR 环境变量指向你的OpenCV安装目录下的build文件夹
    find_package(OpenCV REQUIRED)
    include_directories(${OpenCV_INCLUDE_DIRS})# 添加源文件
    add_executable(GomokuAI src/main.cppsrc/GameBoard.cppsrc/GomokuAI.cppsrc/Vision.cpp)# 链接OpenCV库
    target_link_libraries(GomokuAI ${OpenCV_LIBS})
    

第二步:实现 GameBoard 类 (游戏规则)

这是最基础的部分,定义了五子棋的规则。

GameBoard.h

#pragma once
#include <vector>const int BOARD_SIZE = 15;enum class Player { EMPTY = 0, BLACK = 1, WHITE = 2 };class GameBoard {
public:GameBoard();void place_stone(int row, int col, Player player);bool check_win(int row, int col);bool is_valid_move(int row, int col) const;void reset();const std::vector<std::vector<Player>>& get_board_state() const;private:std::vector<std::vector<Player>> board;Player last_player;int check_line(int r, int c, int dr, int dc); // 辅助检查函数
};

GameBoard.cpp

#include "GameBoard.h"GameBoard::GameBoard() {reset();
}void GameBoard::reset() {board.assign(BOARD_SIZE, std::vector<Player>(BOARD_SIZE, Player::EMPTY));last_player = Player::EMPTY;
}void GameBoard::place_stone(int row, int col, Player player) {if (is_valid_move(row, col)) {board[row][col] = player;last_player = player;}
}bool GameBoard::is_valid_move(int row, int col) const {return row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] == Player::EMPTY;
}// 核心:胜利条件检测
bool GameBoard::check_win(int row, int col) {if (last_player == Player::EMPTY) return false;// 检查四个方向: 水平, 垂直, 左上到右下, 右上到左下if (check_line(row, col, 0, 1) >= 5) return true; // 水平if (check_line(row, col, 1, 0) >= 5) return true; // 垂直if (check_line(row, col, 1, 1) >= 5) return true; // 左上-右下if (check_line(row, col, 1, -1) >= 5) return true; // 右上-左下return false;
}// 辅助函数,检查一个方向上的连子数
int GameBoard::check_line(int r, int c, int dr, int dc) {int count = 1;Player p = board[r][c];// 向正方向搜索for (int i = 1; i < 5; ++i) {int nr = r + i * dr;int nc = c + i * dc;if (nr >= 0 && nr < BOARD_SIZE && nc >= 0 && nc < BOARD_SIZE && board[nr][nc] == p) {count++;} else {break;}}// 向反方向搜索for (int i = 1; i < 5; ++i) {int nr = r - i * dr;int nc = c - i * dc;if (nr >= 0 && nr < BOARD_SIZE && nc >= 0 && nc < BOARD_SIZE && board[nr][nc] == p) {count++;} else {break;}}return count;
}const std::vector<std::vector<Player>>& GameBoard::get_board_state() const {return board;
}

第三步:实现 GomokuAI 类 (AI核心)

我们将使用基于棋型评分的启发式方法。AI会遍历所有可以落子的空位,计算每个位置的分数,然后选择分数最高的位置。

评分标准示例:

  • 连五: 100000分
  • 活四: 10000分 (两端都没被堵住的四个子)
  • 冲四: 1000分 (一端被堵住的四个子)
  • 活三: 1000分 (两端都没被堵住的三个子)
  • 眠三: 100分 (一端被堵住的三个子)
  • …等等

AI需要为自己(白棋)和对手(黑棋)都计算分数,通常一个位置的总分 = AI在此处落子后的得分 + 对手在此处落子后的得分

GomokuAI.h

#pragma once
#include "GameBoard.h"
#include <opencv2/core.hpp>class GomokuAI {
public:GomokuAI();// 寻找最佳落子点,返回cv::Point(col, row)cv::Point find_best_move(const GameBoard& board);private:// 计算在(row, col)落子后的棋盘得分int evaluate_board(const GameBoard& board, Player player);int evaluate_point(const GameBoard& board, int row, int col, Player player);// 评估一个方向上的棋型int analyze_line(char line[9], Player player);
};

GomokuAI.cpp (核心思路)

#include "GomokuAI.h"
#include <limits>// ... (构造函数等)cv::Point GomokuAI::find_best_move(const GameBoard& current_board) {int max_score = -std::numeric_limits<int>::max();cv::Point best_move(-1, -1);const auto& board_state = current_board.get_board_state();for (int r = 0; r < BOARD_SIZE; ++r) {for (int c = 0; c < BOARD_SIZE; ++c) {if (board_state[r][c] == Player::EMPTY) {// 尝试在此处为AI落子并计算分数GameBoard temp_board = current_board;temp_board.place_stone(r, c, Player::WHITE); // AI是白棋int ai_score = evaluate_point(temp_board, r, c, Player::WHITE);// 尝试在此处为玩家落子并计算分数(防守分)temp_board = current_board;temp_board.place_stone(r, c, Player::BLACK); // 玩家是黑棋int human_score = evaluate_point(temp_board, r, c, Player::BLACK);int current_score = ai_score + human_score;if (current_score > max_score) {max_score = current_score;best_move = cv::Point(c, r);}}}}return best_move;
}// 这是一个简化的评估函数,你需要详细实现它
int GomokuAI::evaluate_point(const GameBoard& board, int row, int col, Player player) {// 实际的实现会更复杂,需要检查4个方向(水平、垂直、2个对角线)// 并根据棋型(活四、冲四、活三等)返回分数。// 例如,如果(row, col)处落子后形成了一个活四,就返回10000分。// 这是整个AI最核心、最复杂的部分。// 你可以搜索 "Gomoku evaluation function" 来获取详细的棋型和分数设计。int score = 0;// ... 详细的棋型匹配和评分逻辑 ...return score; 
}

第四步:实现 Vision 类 (视觉处理)

这是最具挑战性的部分。

Vision.h

#pragma once
#include <opencv2/opencv.hpp>
#include "GameBoard.h"class Vision {
public:Vision();bool initialize_camera(int index = 0);bool detect_board_grid(const cv::Mat& frame, std::vector<cv::Point2f>& grid_intersections);GameBoard detect_stones(const cv::Mat& frame, const std::vector<cv::Point2f>& grid_intersections);// 通过对比前后两帧的棋盘状态,找出新落子的位置cv::Point get_human_move(const GameBoard& prev_board, const GameBoard& current_board);private:cv::VideoCapture cap;
};

Vision.cpp (实现思路)

  1. 棋盘网格检测 detect_board_grid():

    • 图像预处理: cv::cvtColor转为灰度图, cv::GaussianBlur去噪, cv::Canny边缘检测。
    • 霍夫直线检测: cv::HoughLinesP 找到图像中的所有直线。
    • 直线筛选与分类:
      • 根据斜率将直线分为水平线和垂直线。
      • 对水平线按y坐标排序,对垂直线按x坐标排序。
      • 去除距离过近的重复直线,最终应该得到大约15条水平线和15条垂直线。
    • 计算交点: 计算这些水平线和垂直线的交点,这些交点就是棋盘的落子点。
  2. 棋子检测 detect_stones():

    • 遍历所有棋盘交点。
    • 在每个交点周围取一个小的ROI(感兴趣区域)。
    • 方法一 (颜色): 计算ROI内的平均像素值。如果很低(暗),就是黑子;如果很高(亮),就是白子;如果在中间,就是空的。
    • 方法二 (霍夫圆检测): cv::HoughCircles可以直接在图像中检测圆形。你可以用它来找到所有的黑子和白子,然后将它们的位置映射到最近的棋盘交点上。这个方法通常更鲁棒。
  3. 人类落子检测 get_human_move():

    • 在游戏循环中,保存上一帧检测到的棋盘状态 prev_board
    • 获取当前帧的棋盘状态 current_board
    • 遍历棋盘,找到那个在 prev_board 中为 EMPTY,但在 current_board 中变为 BLACK 的位置,这就是人类玩家的落子。

第五步:整合到 main.cpp

主程序负责把所有模块串联起来,形成完整的游戏流程。

main.cpp (伪代码)

#include "GameBoard.h"
#include "GomokuAI.h"
#include "Vision.h"
#include <opencv2/opencv.hpp>enum class GameState { HUMAN_TURN, AI_TURN, GAME_OVER };int main() {// 1. 初始化GameBoard board;GomokuAI ai;Vision vision;if (!vision.initialize_camera()) {std::cerr << "Error: Cannot open camera." << std::endl;return -1;}cv::Mat frame;std::vector<cv::Point2f> grid_points;GameState game_state = GameState::HUMAN_TURN;GameBoard last_detected_board;// 校准阶段:先检测到稳定的棋盘网格std.cout << "请将棋盘完整放入摄像头视野内,按'c'键进行校准..." << std::endl;while(true) {cap >> frame;if (frame.empty()) break;// 尝试检测棋盘,并在frame上画出预览// ...cv::imshow("Calibration", frame_with_preview);if (cv::waitKey(10) == 'c') {if (vision.detect_board_grid(frame, grid_points)) {std::cout << "校准成功!" << std::endl;break;} else {std::cout << "校准失败,请调整角度和光照后重试..." << std::endl;}}}cv::destroyWindow("Calibration");// 2. 主游戏循环while (true) {cap >> frame;if (frame.empty()) break;// 绘制棋盘和当前状态// draw_board(frame, grid_points, board.get_board_state());if (game_state == GameState::HUMAN_TURN) {GameBoard current_detected_board = vision.detect_stones(frame, grid_points);cv::Point human_move = vision.get_human_move(last_detected_board, current_detected_board);if (human_move.x != -1 && board.is_valid_move(human_move.y, human_move.x)) {board.place_stone(human_move.y, human_move.x, Player::BLACK);last_detected_board = current_detected_board;if (board.check_win(human_move.y, human_move.x)) {std::cout << "你赢了!" << std::endl;game_state = GameState::GAME_OVER;} else {game_state = GameState::AI_TURN;}}}else if (game_state == GameState::AI_TURN) {cv::Point ai_move = ai.find_best_move(board);board.place_stone(ai_move.y, ai_move.x, Player::WHITE);// 在屏幕上清晰地标记出AI的落子位置// draw_ai_move_suggestion(frame, grid_points[ai_move.y * 15 + ai_move.x]);if (board.check_win(ai_move.y, ai_move.x)) {std::cout << "AI赢了!" << std::endl;game_state = GameState::GAME_OVER;} else {game_state = GameState::HUMAN_TURN;// AI落子后,需要更新last_detected_board以防止错误检测last_detected_board = vision.detect_stones(frame, grid_points); }}cv::imshow("Gomoku AI", frame);char key = (char)cv::waitKey(20);if (key == 27) break; // ESC退出if (key == 'r') { // 'r'键重开游戏board.reset();game_state = GameState::HUMAN_TURN;last_detected_board.reset();}}return 0;
}

进阶与完善 (Roadmap)

  1. AI 强化: 实现 Minimax 算法和 Alpha-Beta 剪枝,让AI具备多步预判能力。
  2. 视觉鲁棒性: 提升棋盘和棋子检测的稳定性,例如使用透视变换(cv::getPerspectiveTransformcv::warpPerspective)将倾斜的棋盘图像校正为俯视图。
  3. 更好的UI: 在窗口中更清晰地绘制棋盘、序号、AI思考的提示等。
  4. 人机博弈: 允许人类选择执黑或执白,或者实现悔棋功能。

这个项目挑战与乐趣并存,祝你编码愉快!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词