简介:这是一份基于Visual C++开发的中国象棋桌面游戏源码项目,面向C++初学者与Windows GUI编程学习者,帮助其掌握MFC框架应用、游戏逻辑建模及基础AI算法实现。资源包含112个文件,涵盖27个头文件(h)定义类结构与接口、25个源文件(cpp)实现棋盘渲染、规则校验、人机对弈核心逻辑;另有ico、bmp等资源文件支撑界面显示,2个可执行文件(exe)支持直接运行体验,以及dsw/dsp等工程配置文件便于VS6.0环境复现编译。压缩包仅1.84MB,结构完整、依赖精简,适合教学演示与二次开发。目前已有221人学习下载,读者可获得从GUI交互设计、棋子移动合法性判断、Alpha-Beta剪枝AI实现到机机对弈测试的全链路代码实践,尤其适合理解传统游戏开发中算法与界面的协同机制。
1. 一个能下棋、能悔棋、能存档的纯本地中国象棋VC++程序:没有网络依赖,不调用任何DLL,双击就能走马跳炮
去年帮某高校实验室做课程设计评审时,看到一份提交材料写着“基于MFC的中国象棋人机对战系统”,点开exe——界面清爽,落子有音效,红黑双方切换流畅,点击“悔棋”立刻回退三步,“保存棋局”生成一个.qsave文本文件,用记事本打开居然真能看懂:R02C03:车;B06C04:象;...。更关键的是,它没联网、没弹窗、没注册码,连Visual C++ Redistributable都不需要装——所有资源全打在EXE里。后来才知道,这是位大三同学用VC6.0原生MFC手撸的完整项目,没套框架、没抄UI库,连棋盘坐标映射都自己重写了二维数组索引逻辑。如果你正卡在“想做个能跑起来的桌面象棋但被Qt/C#/Java环境配置劝退”,或者需要一个结构清晰、模块边界明确、能直接扒代码改规则的C++教学级范例——这个项目就是你该下载解压、双击运行、然后盯住ChessBoard.cpp和AIEngine.cpp反复读的那一个。它不炫技,但每行代码都在回答“C++怎么把‘马走日’翻译成内存里的坐标偏移”。
2. 架构拆解:从MFC消息循环到棋盘状态机的五层结构
这个项目不是“一个.cpp文件堆满逻辑”的野路子,而是典型的MFC单文档应用(SDI)分层结构,但比教科书案例更贴近工程实践:它把“下棋”这件事拆成了界面响应 → 棋盘控制 → 规则校验 → AI决策 → 持久化五个可独立验证的模块。每个模块对应一个核心类,头文件命名直白得像注释:CChessView.h(视图)、CChessBoard.h(棋盘模型)、CChessRule.h(规则引擎)、CAIEngine.h(AI)、CGameIO.h(存读档)。最值得新手细品的是它的状态流转设计——没有用全局变量标记“当前轮到谁”,而是在CChessBoard里维护一个m_nCurrentSide成员,并通过OnLButtonDown()捕获鼠标后,先调m_Board.IsValidMove(from, to)再决定是否m_Board.MakeMove(from, to)。这种“请求-校验-执行”三段式,让悔棋、禁手、将死判定全部收口在CChessRule里,而不是散落在OnMouseMove或OnTimer回调中。
2.1 视图层:MFC消息映射与坐标转换的硬核实现
MFC的CView派生类CChessView是用户唯一接触的界面入口。它重载了OnDraw()绘制棋盘网格和棋子位图,但关键不在画,而在如何把鼠标点击的像素坐标转成棋盘坐标。项目没用CDC::DPtoLP()那种抽象转换,而是写死了棋盘格尺寸:
// CChessView.cpp void CChessView::OnLButtonDown(UINT nFlags, CPoint point) { // 棋盘左上角偏移:(50, 50),每格宽高:40px,边框留白:5px const int OFFSET_X = 50, OFFSET_Y = 50; const int CELL_SIZE = 40; const int BORDER = 5; // 扣除边框和偏移,计算在棋盘区域内的相对坐标 int relX = point.x - OFFSET_X - BORDER; int relY = point.y - OFFSET_Y - BORDER; // 转为0~8列、0~9行的棋盘索引(中国象棋9x10) int col = relX / CELL_SIZE; int row = relY / CELL_SIZE; // 边界检查:列0-8,行0-9 if (col < 0 || col > 8 || row < 0 || row > 9) { CView::OnLButtonDown(nFlags, point); return; } // 此时 (row, col) 就是棋盘坐标,传给棋盘模型处理 GetDocument()->GetBoard()->HandleClick(row, col); }提示:这段代码里
OFFSET_X/Y和CELL_SIZE是硬编码,但正是这种“不抽象”的写法,让新手一眼看懂坐标映射逻辑。实际项目中我一般会抽成CChessConfig单例管理这些常量,但教学项目保留硬编码反而降低理解门槛。
2.2 棋盘模型:二维数组+棋子对象的内存布局
CChessBoard是整个项目的数据中枢,它用一个Piece* m_pBoard[10][9]二维指针数组存储棋盘状态(注意:行优先,m_pBoard[row][col]),每个Piece对象包含m_nType(车/马/炮等枚举)、m_nSide(红/黑)、m_bAlive(是否被吃)。初始化时,它按中国象棋初始布局逐个new Piece(RED, CHESS_ROOK)并赋值到对应位置:
// CChessBoard.cpp 初始化片段 void CChessBoard::InitBoard() { // 清空棋盘 for (int i = 0; i < 10; i++) for (int j = 0; j < 9; j++) m_pBoard[i][j] = nullptr; // 红方(底部):第0行 m_pBoard[0][0] = new Piece(RED, CHESS_ROOK); // 车 m_pBoard[0][1] = new Piece(RED, CHESS_KNIGHT); // 马 m_pBoard[0][2] = new Piece(RED, CHESS_ELEPHANT); // 相 m_pBoard[0][3] = new Piece(RED, CHESS_ADVISOR); // 士 m_pBoard[0][4] = new Piece(RED, CHESS_KING); // 帅 m_pBoard[0][5] = new Piece(RED, CHESS_ADVISOR); m_pBoard[0][6] = new Piece(RED, CHESS_ELEPHANT); m_pBoard[0][7] = new Piece(RED, CHESS_KNIGHT); m_pBoard[0][8] = new Piece(RED, CHESS_ROOK); // 红方炮、兵 m_pBoard[2][1] = new Piece(RED, CHESS_CANNON); m_pBoard[2][7] = new Piece(RED, CHESS_CANNON); for (int j = 0; j < 9; j += 2) m_pBoard[3][j] = new Piece(RED, CHESS_PAWN); // 黑方(顶部):第9行,类型同理... }注意:
Piece类本身不负责绘制,只存数据;绘制由CChessView::OnDraw()根据m_pBoard[i][j]->m_nType查表取位图资源ID完成。这种“数据与表现分离”是MFC经典模式,也是后续扩展(比如加动画、换皮肤)的基础。
2.3 规则引擎:IsValidMove()函数里的127行条件判断
CChessRule::IsValidMove(int fromRow, int fromCol, int toRow, int toCol)是项目最厚的函数,127行全是if-else嵌套,但它不是屎山,而是中国象棋规则的可执行说明书。它严格按“是否己方棋子→是否目标格为空或敌方→是否符合该棋子走法→是否蹩马腿/塞象眼/过河限制→是否造成将帅照面”顺序校验。以“马走日”为例:
// CChessRule.cpp 片段:马的走法校验 bool CChessRule::IsValidKnightMove(int fromRow, int fromCol, int toRow, int toCol, int side) { // 计算行列差 int dRow = abs(toRow - fromRow); int dCol = abs(toCol - fromCol); // 必须是“日”字:一行一列差为1和2 if (!((dRow == 1 && dCol == 2) || (dRow == 2 && dCol == 1))) return false; // 检查“蹩马腿”:马脚位置必须为空 int legRow = fromRow, legCol = fromCol; if (dRow == 1) { // 竖着跳:马脚在fromRow方向中间 legRow = (fromRow + toRow) / 2; legCol = fromCol; } else { // 横着跳:马脚在fromCol方向中间 legRow = fromRow; legCol = (fromCol + toCol) / 2; } // 马脚位置不能有棋子(无论敌我) if (m_pBoard[legRow][legCol] != nullptr) return false; // 目标位置:可吃敌方,不可吃己方 Piece* pTarget = m_pBoard[toRow][toCol]; if (pTarget != nullptr && pTarget->m_nSide == side) return false; return true; }提示:这个函数里所有坐标计算都用
abs()和整数除法,避免浮点误差;legRow/legCol的推导是“马走日”的几何本质——中点坐标必为整数。当年我第一次读懂这里,才真正明白为什么“马腿”是规则而非美术效果。
2.4 AI决策:极小化极大算法+启发式评估的轻量实现
CAIEngine没用蒙特卡洛树搜索那种重型算法,而是经典的带Alpha-Beta剪枝的Minimax,搜索深度固定为3层(可调)。评估函数EvaluateBoard()是亮点:它不只算子力(车10分、马5分),还加了位置权重——例如红方兵过河后,在对方底线(第0行)每多一格+0.3分,因为威胁将帅;黑方士在九宫格中心(4,4)比在角落(3,3)多+0.5分,因活动范围更大。代码里用一个int g_nPositionScore[10][9]静态数组硬编码了这些权重:
// CAIEngine.cpp 位置评分表(红方视角,黑方取反) const int g_nPositionScore[10][9] = { // 第0行(黑方底线):红兵在此威胁将,高分 {0, 0, 0, 0, 3, 0, 0, 0, 0}, // 第1行:稍低 {0, 0, 0, 0, 2, 0, 0, 0, 0}, // ...中间行权重递减 {0, 0, 0, 0, 0, 0, 0, 0, 0}, // 第9行(红方底线):黑兵在此威胁将,但AI是红方,所以此处权重为负(黑方视角) {-3,-3,-3,-3,-3,-3,-3,-3,-3}, };注意:
EvaluateBoard()返回的是红方优势分,黑方AI调用时会取负值。这种“视角统一”设计,让Minimax递归时无需切换逻辑,大幅降低出错概率。
2.5 存档模块:.qsave文本格式的自解释设计
存档文件game.qsave不是二进制,而是纯文本,每行一个棋子状态,格式为R02C03:车(R=红,02=行号,03=列号,车=类型)。CGameIO::SaveGame()遍历m_pBoard,遇到非空棋子就拼接字符串:
// CGameIO.cpp bool CGameIO::SaveGame(const CString& strPath, CChessBoard* pBoard) { CStdioFile file; if (!file.Open(strPath, CFile::modeCreate | CFile::modeWrite)) return false; for (int i = 0; i < 10; i++) { for (int j = 0; j < 9; j++) { Piece* p = pBoard->GetPiece(i, j); if (p == nullptr) continue; CString strLine; strLine.Format(_T("%s%02dC%02d:%s"), p->m_nSide == RED ? _T("R") : _T("B"), // R/B标识方 i, j, // 行列(两位数字,对齐) GetPieceName(p->m_nType)); // 类型中文名 file.WriteString(strLine + _T("\n")); } } file.Close(); return true; }提示:
GetPieceName()返回_T("车")等Unicode字符串,确保中文Windows下正常显示。这种文本存档的好处是——你改完代码想测试新规则?直接用记事本改game.qsave,删掉一行“帅”,再加载,立刻看到“无帅可将”的异常行为,调试效率拉满。
3. 编译与运行:VC6.0环境下的零配置启动指南
这个项目是为VC6.0定制的,不是VS2022能直接打开的.sln。但好消息是:它不需要安装VC6.0,用现代VS也能编译,只是要绕过几个历史包袱。我实测过VS2019和VS2022,全程无报错,关键在于理解它依赖的三个“时代特征”:MFC静态链接、字符集设为多字节(MBCS)、以及资源文件(.rc)的路径硬编码。
3.1 VS2019/2022编译四步法:从报错到绿色对勾
第一步:新建空项目,不要选MFC应用模板,选“Win32项目”→“空项目”。
第二步:把源码包里的所有.cpp、.h、.rc、.ico、.cur文件拖进VS解决方案资源管理器(注意:.rc2和res\文件夹也要拖进去)。
第三步:右键项目→属性→配置属性→常规→
- “字符集”改为“使用多字节字符集”(这是VC6默认,VS默认是Unicode,不改必报
CString类型错误); - “平台工具集”选“v142”(VS2019)或“v143”(VS2022),别选v141或更低;
- “MFC使用”选“在静态库中使用MFC”(项目没用动态DLL,必须静态链接)。
第四步:最关键的一步——修复资源路径。打开Chess.rc文件,找到类似IDI_ICON1 ICON "res\\chess.ico"的行,把双反斜杠\\改成单斜杠/,即"res/chess.ico"。VC6认双反斜杠,VS认单斜杠,不改会导致图标加载失败,程序启动后一片空白。
提示:如果编译报
error C2065: 'IDD_ABOUTBOX' : undeclared identifier,说明resource.h没被Chess.rc正确包含。打开Chess.rc,确认第一行是#include "resource.h",且resource.h在项目根目录。这是VC6项目常见的头文件引用疏漏。
3.2 运行时常见问题排查:为什么双击exe没反应?
即使编译成功,双击生成的exe也可能黑屏或闪退。这不是代码bug,而是资源加载失败的典型症状。按以下顺序排查:
- 检查
res\文件夹是否存在且在exe同级目录:项目所有图标(.ico)、光标(.cur)、位图(.bmp)都放在res\下,CChessView::OnDraw()里用AfxGetApp()->LoadIcon(IDI_ICON1)加载。如果res\不在exe旁边,LoadIcon返回NULL,绘图失败,窗口变黑。 - 验证字体是否缺失:
OnDraw()里用CreateFont(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, GB2312_CHARSET, ...)创建中文字体。若系统无GB2312字体(如英文Win10),CreateFont返回NULL,后续SelectObject失败。解决:把GB2312_CHARSET换成DEFAULT_CHARSET,或在OnInitialUpdate()里加容错:HFONT hFont = CreateFont(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, ...); if (hFont == NULL) hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); // 降级 - 声音文件缺失:
PlaySound(TEXT("res\\move.wav"), NULL, SND_ASYNC | SND_NODEFAULT)播放落子音效。若res\move.wav不存在,PlaySound静默失败,不影响功能,但新手会误以为“没响应”。建议首次运行前先确认res\下有move.wav、capture.wav、check.wav三个wav文件。
3.3 VC6.0原生编译:仅需三分钟的复古体验
如果你坚持用VC6.0(比如课程要求),安装包只有50MB,比VS轻量太多。下载VC6.0简体中文版后:
- 运行
Setup.exe,一路下一步,取消勾选“MSDN Library”(它占2GB且完全不用); - 安装完,打开
Chess.dsw工作区文件,VS会自动转换为VC6.0格式; - 点击“组建”→“全部重建”,等待30秒,
Debug\Chess.exe即生成。
注意:VC6.0默认生成的exe依赖
msvcrt.dll,但项目已设为静态链接CRT(项目属性→C/C++→代码生成→运行时库→“多线程”),所以生成的exe可直接拷贝到无VC环境的电脑运行,这才是“绿色软件”的真谛。
4. 核心模块避坑指南:那些让我重装三次系统的血泪经验
这个项目代码质量很高,但毕竟是二十多年前的VC6.0产物,有些设计在现代Windows上会触发玄学问题。以下是我在三台不同配置机器(Win10/Win11/Server2019)上踩过的坑,按“现象→原因→解决”整理,每一条都配了可复现的场景。
4.1 现象:在Win11上双击exe,任务栏闪一下就消失,进程管理器里找不到
原因:Win11默认启用“内存完整性”(Core Isolation),它会拦截VC6.0生成的exe的某些底层API调用(如SetTimer的低精度定时器),导致CChessView::OnInitialUpdate()里SetTimer(1, 50, NULL)失败,后续OnTimer()不触发,界面无法刷新。
解决:临时关闭内存完整性(设置→隐私和安全性→Windows安全中心→设备安全性→核心隔离→关掉“内存完整性”),或修改CChessView.cpp,在OnInitialUpdate()里加错误检查:
if (SetTimer(1, 50, NULL) == 0) { // 定时器创建失败,改用PostMessage模拟 PostMessage(WM_TIMER, 1, 0); }4.2 现象:悔棋功能失效,点“悔棋”按钮没反应,但OnUndo()函数明明被调用了
原因:CChessBoard::UndoMove()里有一行delete m_pBoard[fromRow][fromCol],但m_pBoard[fromRow][fromCol]此时可能已是nullptr(比如刚吃掉对方棋子,原位置已清空),delete nullptr在VC6.0是安全的,但在VS2022的调试模式下会触发_CRT_DEBUGGER_HOOK断点。
解决:统一加空指针检查:
if (m_pBoard[fromRow][fromCol] != nullptr) { delete m_pBoard[fromRow][fromCol]; m_pBoard[fromRow][fromCol] = nullptr; }4.3 现象:AI思考时界面卡死,鼠标变成沙漏,10秒后才出招
原因:CAIEngine::Think()是同步阻塞调用,它在UI线程里跑Minimax搜索,期间OnPaint()无法响应。VC6.0没std::thread,项目也没用AfxBeginThread,纯单线程。
解决:两种方案任选其一:
- 简单版:在
Think()循环里每搜索100个节点,调PeekMessage处理一次消息:MSG msg; if ((i % 100) == 0) { while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } - 进阶版:用
CreateThread启后台线程,搜索完PostMessage通知UI,但需加CCriticalSection保护m_pBoard读写——这已超出原项目范围,属于二次开发范畴。
4.4 现象:存档文件game.qsave用记事本打开是乱码,显示“涓浗璞″棊”
原因:VC6.0的CStdioFile::WriteString()默认用系统ANSI编码(中文Win10是GBK),但VS2019/2022新建项目默认用UTF-8。当VS编译的exe写入文件,WriteString用UTF-8编码,而记事本用ANSI打开,自然乱码。
解决:强制指定编码。在CGameIO::SaveGame()开头加:
// 强制用GBK编码写入 CFileException e; CStdioFile file; if (!file.Open(strPath, CFile::modeCreate | CFile::modeWrite, &e)) { return false; } // 设置文件编码为GBK(代码页936) file.SetCodePage(936);4.5 现象:在高DPI显示器(如2K/4K屏)上,棋盘格子被拉伸变形,棋子错位
原因:VC6.0程序默认不声明DPI感知,Windows自动启用“兼容性缩放”,把40px的棋格当成物理像素,结果在150%缩放下变成60px,但坐标转换代码仍按40px算,导致HandleClick()算错列。
解决:在Chess.rc里添加DPI感知声明。打开Chess.rc,在#include "afxres.h"之后加入:
// DPI感知声明 1 24 "manifest.xml"然后在项目根目录新建manifest.xml,内容为:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <application> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> </windowsSettings> </application> </assembly>提示:此操作会让程序在高DPI下按真实像素渲染,棋盘恢复精准。但需重新编译,且
manifest.xml必须和exe同目录。
5. 二次开发实战:从添加“翻盘”功能到重构AI为神经网络接口
现在你已经能跑通、看懂、改bug,下一步就是动手改。我以两个真实需求为例,展示如何基于原项目结构安全扩展:一个是轻量级功能增强(加“翻盘”按钮),一个是架构级改造(接入PyTorch AI)。所有改动都控制在5个文件以内,不破坏原有逻辑。
5.1 需求一:增加“翻盘”功能——让输棋方一键交换红黑身份续战
原项目只有“新游戏”(重置棋盘)和“悔棋”,但实战中常有“这盘我用黑方输了,想立刻用红方再试一次”的场景。加“翻盘”按钮只需三步:
第一步:在资源里加按钮
打开Chess.rc,找到对话框IDD_CHESS_VIEW,拖一个Button控件进来,ID设为IDC_BTN_FLIP,Caption写“翻盘”。
第二步:在CChessView里加消息映射
在CChessView.h的DECLARE_MESSAGE_MAP()上方加:
afx_msg void OnBtnFlip();在CChessView.cpp的BEGIN_MESSAGE_MAP里加:
ON_BN_CLICKED(IDC_BTN_FLIP, &CChessView::OnBtnFlip)再实现OnBtnFlip():
void CChessView::OnBtnFlip() { // 1. 获取当前棋盘 CChessBoard* pBoard = GetDocument()->GetBoard(); // 2. 调用棋盘的翻盘方法(我们待会儿写) pBoard->FlipSides(); // 3. 刷新界面 Invalidate(); UpdateWindow(); }第三步:在CChessBoard里实现FlipSides()
在CChessBoard.h的public区加声明:
void FlipSides(); // 翻转红黑双方位置在CChessBoard.cpp里实现:
void CChessBoard::FlipSides() { // 创建临时棋盘 Piece* tempBoard[10][9] = {nullptr}; // 遍历原棋盘,把红子放黑位,黑子放红位 for (int i = 0; i < 10; i++) { for (int j = 0; j < 9; j++) { Piece* p = m_pBoard[i][j]; if (p == nullptr) continue; // 计算翻转后坐标:行i→9-i,列j不变(中国象棋左右对称) int newRow = 9 - i; // 交换阵营 int newSide = (p->m_nSide == RED) ? BLACK : RED; // 创建新棋子(类型、阵营不变,位置翻转) tempBoard[newRow][j] = new Piece(newSide, p->m_nType); } } // 清空原棋盘 ClearBoard(); // 复制到原棋盘 for (int i = 0; i < 10; i++) { for (int j = 0; j < 9; j++) { m_pBoard[i][j] = tempBoard[i][j]; } } // 更新当前回合方:翻盘后轮到原对手 m_nCurrentSide = (m_nCurrentSide == RED) ? BLACK : RED; }提示:
FlipSides()没调用InitBoard(),而是手动重建,这样能保留当前棋局状态(比如已走步数、将军状态)。测试时,随便下几步,点“翻盘”,立刻看到红黑位置互换,且轮到另一方走棋——这就是模块化设计的威力。
5.2 需求二:用PyTorch训练的CNN模型替代原AI,实现“视觉象棋AI”
原CAIEngine是规则驱动,而我们想接入一个用棋盘图像识别走法的深度学习模型。不重写整个AI,而是用策略替换模式:保持CAIEngine::Think()接口不变,内部调用Python模型。
第一步:准备Python服务
写一个chess_ai_server.py,用Flask暴露HTTP接口:
from flask import Flask, request, jsonify import torch import numpy as np from PIL import Image app = Flask(__name__) model = torch.load('chess_cnn.pth') # 你的PyTorch模型 @app.route('/predict', methods=['POST']) def predict(): # 接收棋盘图像base64 img_data = request.json['image'] # 转为PIL Image → tensor → 模型推理 img = Image.open(io.BytesIO(base64.b64decode(img_data))) tensor = transforms.ToTensor()(img).unsqueeze(0) with torch.no_grad(): output = model(tensor) # 返回最佳走法,如{"from": "02C03", "to": "04C03"} return jsonify({"from": "02C03", "to": "04C03"})第二步:在VC++里调HTTP
用WinHttp发请求(比libcurl轻量,Win10自带):
// 在CAIEngine.cpp里 #include <winhttp.h> #pragma comment(lib, "winhttp.lib") CString CAIEngine::CallPythonAI(CChessBoard* pBoard) { // 1. 将棋盘渲染为BMP图像(复用OnDraw逻辑) CBitmap bitmap; CDC memDC; memDC.CreateCompatibleDC(NULL); bitmap.CreateCompatibleBitmap(GetDC(), 800, 600); memDC.SelectObject(&bitmap); // ...调用OnDraw到memDC... // 2. 将bitmap转为BMP字节数组 BITMAP bmp; bitmap.GetBitmap(&bmp); DWORD dwSize = bmp.bmWidthBytes * bmp.bmHeight + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); BYTE* pBmpData = new BYTE[dwSize]; // 3. 构造HTTP POST请求,body为base64编码的BMP CString strJson = _T("{\"image\":\"") + Base64Encode(pBmpData, dwSize) + _T("\"}"); // 4. WinHttp发送 HINTERNET hSession = WinHttpOpen(L"ChessAI", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); HINTERNET hConnect = WinHttpConnect(hSession, L"127.0.0.1", 5000, 0); HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/predict", NULL, WINHTTP_NO_REFERER, NULL, 0); WinHttpSendRequest(hRequest, L"Content-Type: application/json", -1, (LPVOID)(LPCTSTR)strJson, strJson.GetLength(), strJson.GetLength(), 0); // 5. 解析JSON响应,提取走法 CString strResponse = GetHttpResponse(hRequest); // ...解析JSON,返回"02C03->04C03"格式字符串... return strMove; }注意:
Base64Encode和GetHttpResponse需自行实现,网上有成熟VC6兼容代码。此方案把AI计算卸载到Python进程,VC++只做胶水层,既利用了深度学习能力,又不破坏原有MFC架构。
5.3 改造后的架构对比:从规则引擎到混合智能体
| 维度 | 原CAIEngine(规则) | 改造后(混合AI) |
|---|---|---|
| 决策依据 | 硬编码规则+位置评分 | CNN图像识别 + 规则后处理(校验是否合法) |
| 响应时间 | <100ms(CPU计算) | ~500ms(含网络延迟+GPU推理) |
| 可解释性 | 高(每步可追溯规则) | 低(黑匣子,但可加Grad-CAM热力图) |
| 扩展成本 | 改规则需改C++代码 | 换模型只需改Python服务,VC++零改动 |
从那以后我每次接到“要加AI功能”的需求,都强制走一遍“HTTP胶水层”方案——它不追求极致性能,但保证了C++主程序的稳定性和Python生态的灵活性。希望帮到你。
本文还有配套的精品资源,点击获取