尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C语言课程设计:EasyX五子棋完整源码与实现解析

C语言课程设计:EasyX五子棋完整源码与实现解析 简介这份资源是面向C语言初学者与课程设计学习者的五子棋项目源码包借助轻量级EasyX图形库把C语言基础语法与图形界面编程结合起来解决初学者难以将语法知识落地为可视化小项目的问题。包内共50个文件以tlog、log、pdb、ilk等Visual Studio编译调试中间文件为主另含cpp源码、vcxproj工程文件、sln解决方案、exe可执行程序及一张背景图片压缩包约8.81MB可直接用VS打开编译运行。项目围绕二维数组表示棋盘、鼠标事件获取落子位置、横竖及双对角线五连判断等核心逻辑展开并涉及窗口创建、图形绘制、颜色设置与文本提示等EasyX常用接口适合作为课程设计参考或入门练手。目前已有3191人学习下载读者可借此理解图形界面编程与游戏状态切换、重置棋盘等完整流程积累调试与代码组织经验。1. 用 EasyX 把五子棋跑起来一份能直接编译的 C 语言课程设计很多人第一次做 C 语言课程设计卡住的不是算法而是「怎么把黑白棋子画到屏幕上」。控制台里 printf 一个棋盘落子靠输入坐标交上去老师一眼就看出是凑数的。这份 Gobang 资源解决的正是这个断层它用 EasyX 图形库把五子棋做成了带窗口、能鼠标点击、有背景图的完整程序源码、工程文件、图片素材都在包里。EasyX 是基于 Windows API 封装的轻量图形库对 C 语言入门的人友好不需要先啃 Win32 那套消息循环就能画出图形界面。这份资源适合正在做课程设计、想找一个能跑通、能改、能讲清楚原理的参考项目的人。下面我按「拿到包怎么编译 → 棋盘和落子怎么实现 → 胜负判断怎么写 → 哪里容易翻车」的顺序拆一遍。2. 从 Gobang.zip 到可运行工程编译环境与文件结构2.1 先看清包里有什么别急着双击 exe解压 Gobang.zip 之后目录里混着源码、工程配置、编译产物和素材第一次看容易懵。我把它分成三类类别文件作用源码Դ.cpp主程序所有游戏逻辑都在这里工程配置Gobang.vcxproj、Gobang.vcxproj.filters、Gobang.sln、Gobang.v11.suoVisual Studio 工程文件决定编译哪些源文件、用什么配置素材Background1.jpg棋盘背景图运行时加载编译产物Debug 目录、Gobang.exe、Gobang.ilk、Gobang.pdb、Gobang.sdf、debug.log上一次编译留下的中间文件和可执行文件可以删掉重新生成那个Դ.cpp文件名是编码问题导致的乱码原本应该是「源.cpp」之类的名字。这不影响编译但如果你要重命名记得同步改 vcxproj 里的引用否则工程会报找不到源文件。Gobang.sln是解决方案入口用 Visual Studio 打开它而不是单独打开 cpp。提示Debug 目录和 .ilk/.pdb/.sdf 都是中间产物换机器或换 VS 版本后建议先「清理解决方案」再重新生成避免旧产物干扰。2.2 装 EasyX 并确认头文件能被找到EasyX 不是标准库得单独装。它的安装包会把graphics.h新版也有easyx.h和对应的 lib 文件放进 VS 的 include 和 lib 目录。装完之后代码里#include graphics.h才能被找到。常见做法是去 EasyX 官网下载对应你 VS 版本的安装程序运行后它会自动检测已安装的 Visual Studio 并完成配置。如果你用的是较新的 VS装完最好新建一个空项目写两行测试代码确认能编译#include graphics.h // EasyX 主头文件 #include conio.h int main() { initgraph(640, 480); // 创建 640x480 的图形窗口 circle(320, 240, 100); // 在窗口中心画一个半径 100 的圆 _getch(); // 等待按键防止窗口一闪而过 closegraph(); // 关闭图形窗口释放资源 return 0; }这段代码的逻辑很直白initgraph负责创建窗口并进入图形模式之后所有绘图函数都画在这个窗口上circle的四个参数是圆心 x、圆心 y、半径_getch()来自 conio.h作用是暂停不然程序画完立刻退出你什么都看不到closegraph收尾。如果这一步报「无法打开 graphics.h」说明 EasyX 没装好或 VS 版本不匹配先解决这个再谈五子棋。2.3 用 VS 打开 sln 并处理常见编译报错用 Visual Studio 打开Gobang.sln解决方案资源管理器里应该能看到那个 cpp 文件。直接按 F5 或「本地 Windows 调试器」编译运行。如果报错大概率是这几类第一类是字符集问题。EasyX 老版本对 Unicode 支持不完善工程属性里「配置属性 → 高级 → 字符集」如果设成 Unicode某些文本输出函数会报类型不匹配。改成「使用多字节字符集」通常能过。第二类是源文件编码。那个乱码文件名的 cpp 如果里面含中文注释而文件保存编码和 VS 解析编码不一致会报一堆莫名其妙的语法错误。用 VS 打开后「文件 → 高级保存选项」确认是 GB2312 或 UTF-8 带签名。第三类是链接错误提示找不到某个 EasyX 函数。这基本是库没链接上检查工程属性里附加依赖项是否包含 EasyX 的 lib。# 如果不想用 VS也可以用命令行编译需已配置好 EasyX 的 include/lib 路径 cl /EHsc /I C:\EasyX\include Դ.cpp /link /LIBPATH:C:\EasyX\lib easyx.lib命令行方式适合想搞清楚编译链路的人/I指定头文件目录/link /LIBPATH指定库目录最后链接 easyx.lib。不过课程设计场景下直接用 VS 图形界面更省事命令行当备选。3. 棋盘绘制与鼠标落子二维数组和 GetMouse 怎么配合3.1 用二维数组表示棋盘状态五子棋的棋盘本质是一个二维网格。标准是 15×15 个交叉点每个点有三种状态空、黑棋、白棋。用二维数组最自然#define SIZE 15 // 棋盘 15x15 #define EMPTY 0 // 空位 #define BLACK 1 // 黑棋 #define WHITE 2 // 白棋 int board[SIZE][SIZE]; // 全局棋盘数组0 表示空 // 初始化把所有位置清空 void initBoard() { for (int i 0; i SIZE; i) for (int j 0; j SIZE; j) board[i][j] EMPTY; }这里用宏定义而不是魔法数字是为了后面判断胜负时可读。board[i][j]的 i 是行、j 是列对应屏幕上的纵坐标和横坐标。数组开成全局的是因为绘制、落子、判胜多个函数都要访问它传参反而啰嗦。初始化必须做否则数组里是随机值程序会画出莫名其妙的棋子。3.2 把数组坐标换算成屏幕像素棋盘画在窗口上窗口有像素坐标数组有行列坐标两者要建立映射。假设每个格子间距 40 像素棋盘左上角留 40 像素边距#define GRID 40 // 格子间距像素 #define MARGIN 40 // 棋盘边距像素 // 把数组行列 (row, col) 换算成屏幕像素坐标 void getPixel(int row, int col, int* x, int* y) { *x MARGIN col * GRID; // 横坐标 边距 列号 * 间距 *y MARGIN row * GRID; // 纵坐标 边距 行号 * 间距 }getPixel是绘制和鼠标点击共用的换算函数保证「画在哪」和「点在哪」用的是同一套坐标。如果绘制用一套公式、点击判断用另一套就会出现点不准的玄学问题。GRID 和 MARGIN 这两个参数决定了棋盘大小改大改小要同步调整窗口尺寸否则棋盘会超出窗口或被裁掉。3.3 画棋盘线和棋子有了坐标换算画棋盘就是循环画横线和竖线void drawBoard() { setlinecolor(BLACK); // 线条颜色设为黑色 for (int i 0; i SIZE; i) { int x1, y1, x2, y2; getPixel(i, 0, x1, y1); // 第 i 行最左点 getPixel(i, SIZE - 1, x2, y2); // 第 i 行最右点 line(x1, y1, x2, y2); // 画横线 getPixel(0, i, x1, y1); // 第 i 列最上点 getPixel(SIZE - 1, i, x2, y2); // 第 i 列最下点 line(x1, y1, x2, y2); // 画竖线 } }setlinecolor设置后续画线的颜色line画一条从 (x1,y1) 到 (x2,y2) 的直线。循环里 i 同时当行号和列号用一次循环画一条横线加一条竖线15 次循环就把网格画完了。棋子用fillcircle画实心圆void drawPiece(int row, int col, int color) { int x, y; getPixel(row, col, x, y); setfillcolor(color BLACK ? BLACK : WHITE); // 根据颜色画黑或白 fillcircle(x, y, GRID / 2 - 2); // 半径略小于半格留缝隙 }半径取GRID/2 - 2是为了让棋子之间留一点空隙看起来不粘连。如果直接用GRID/2相邻棋子会挨在一起视觉上分不清。3.4 用 GetMouse 监听落子并做合法性检查EasyX 的鼠标消息用GetMouse获取它返回后通过传出参数拿到坐标和按键状态void playGame() { int currentPlayer BLACK; // 黑棋先手 while (1) { MOUSEMSG msg GetMouse(); // 阻塞等待鼠标消息 if (msg.uMsg WM_LBUTTONDOWN) // 只处理左键按下 { // 把像素坐标反算成最近的交叉点 int col (msg.x - MARGIN GRID / 2) / GRID; int row (msg.y - MARGIN GRID / 2) / GRID; // 边界检查点太靠边可能算出越界坐标 if (row 0 || row SIZE || col 0 || col SIZE) continue; // 合法性检查该位置必须为空 if (board[row][col] ! EMPTY) continue; board[row][col] currentPlayer; // 落子 drawPiece(row, col, currentPlayer); // 画出来 if (checkWin(row, col, currentPlayer)) // 判断胜负 { // 弹出提示并结束 MessageBox(GetHWnd(), _T(获胜), _T(游戏结束), MB_OK); break; } currentPlayer (currentPlayer BLACK) ? WHITE : BLACK; // 换手 } } }反算坐标时加了GRID/2再整除这是四舍五入的关键鼠标点在第 3 和第 4 个交叉点中间时应该算离得近的那个。不加这个偏移点偏一点就会落到相邻格。边界检查和空位检查缺一不可前者防止数组越界越界写会破坏内存程序可能直接崩后者防止重复落子。GetHWnd()拿到窗口句柄给 MessageBox 用保证弹窗挂在游戏窗口上。4. 胜负判断四个方向扫描与边界处理4.1 为什么只从落子点向四个方向数判断五子连珠最直觉的做法是遍历整个棋盘找连续五个。但每落一子就全盘扫描效率低且代码啰嗦。更合理的思路是只有刚落的这颗子可能形成五连所以从它出发沿四个方向横、竖、两条对角线分别向两边数同色棋子任意方向凑够五个就赢。四个方向可以用方向向量表示// 四个方向横、竖、主对角线、副对角线 int dx[4] {0, 1, 1, 1}; int dy[4] {1, 0, 1, -1};(dx, dy)表示从当前点往某个方向走一步的增量。横方向是列加一、行不变竖方向是行加一、列不变主对角线行列都加一副对角线行加一、列减一。每个方向要往正反两边都数所以实际扫描时用dx和-dx两个方向。4.2 单方向计数与胜负判定// 从 (row, col) 沿方向 (dr, dc) 数同色连续棋子个数 int countDirection(int row, int col, int dr, int dc, int color) { int count 0; int r row dr, c col dc; // 从相邻点开始 while (r 0 r SIZE c 0 c SIZE board[r][c] color) { count; r dr; c dc; } return count; } // 判断刚落下的子是否形成五连 int checkWin(int row, int col, int color) { for (int d 0; d 4; d) { // 正方向 反方向 自己这一颗 int total 1 countDirection(row, col, dx[d], dy[d], color) countDirection(row, col, -dx[d], -dy[d], color); if (total 5) return 1; // 任意方向达到五个即获胜 } return 0; }countDirection的 while 条件里边界检查必须放在数组访问之前C 语言的短路特性保证了越界时不会去读board[r][c]。这是血泪经验如果写成先访问再判断边界越界读可能不崩但读到脏数据导致误判胜负。checkWin里total从 1 开始因为落下的这颗子本身算一个再加上两个方向数出来的。用 5而不是 5是为了兼容长连六连、七连也算赢的规则具体看你的课程要求。4.3 边界与长连的取舍标准五子棋对长连有不同规则有的算赢有的算禁手。课程设计一般简化处理 5就判赢代码简单也不容易出争议。但你要在报告里说清楚自己采用的规则别让老师问住。边界处理上countDirection每次走一步都检查r和c是否在[0, SIZE)范围内这是防止数组越界的核心。棋盘边缘的棋子往外的方向一步就出界while 直接不进入count 保持 0逻辑自然正确。如果你把边界检查漏了程序在边角落子时可能读到相邻内存的随机值出现「明明没连五个却判赢」的翻车现场。注意board是全局数组checkWin里读它没问题但如果你把棋盘改成局部变量传参记得传的是数组首地址函数内对它的修改会影响原数组这正是我们想要的。5. 避坑与排查编译、坐标、判胜里最容易翻车的地方5.1 编译报错「无法打开 graphics.h」现象VS 里一编译就提示找不到 graphics.h或者链接时提示 unresolved external symbol。原因EasyX 没安装或者装的是 32 位版本而工程是 64 位反之亦然再或者安装时没检测到你的 VS 版本。解决确认 EasyX 安装程序支持你当前的 VS 版本重新运行安装。然后在工程属性里核对平台x86/x64和 EasyX 库的位数一致。装完新建空项目用第 2.2 节的测试代码验证通过了再回来编译五子棋。5.2 鼠标点击位置和棋子对不上现象点某个交叉点棋子画在隔壁或者点边缘时程序崩溃。原因像素坐标反算成数组坐标时没做四舍五入或者没做边界检查导致数组越界。解决反算公式里加GRID/2再整除实现四舍五入。落子前必须检查row、col是否在[0, SIZE)内越界的直接continue丢弃。这两个检查是配套的少一个都会出问题。5.3 程序运行后窗口一闪而过现象双击 exe 或按 F5窗口出现一瞬间就没了。原因图形程序执行完main就退出没有等待用户操作。控制台程序会自动停住图形程序不会。解决在closegraph()之前加_getch()或system(pause)暂停。游戏主循环本身是while(1)不会退出但如果你在调试某个只画一帧的函数就得手动加暂停。5.4 胜负判断在边角误报现象在棋盘边缘落子明明没连成五个却提示获胜。原因countDirection里边界检查和数组访问的顺序写反了越界读到了脏数据。解决把边界条件写在的最前面利用短路特性保证越界时不会访问数组。检查你的 while 条件是不是r 0 r SIZE c 0 c SIZE board[r][c] color这个顺序任何一项调换都可能出问题。5.5 背景图加载失败或显示错位现象程序能跑但背景是黑的或者图片被拉伸变形。原因Background1.jpg的路径不对相对路径依赖工作目录或者加载后没按窗口尺寸缩放。解决用相对路径时确保图片和 exe 在同一目录或者用绝对路径调试。加载图片用loadimage显示用putimage如果图片尺寸和窗口不匹配先putimage再画棋盘或者用SetWorkingImage做缩放。课程设计里背景图不是必须的加载失败可以先注释掉保证核心功能跑通。6. 把这份代码改成你自己的换棋盘尺寸与加悔棋拿到能跑的代码只是起点课程设计要体现你的工作量改几个参数、加一两个功能答辩时才有东西讲。最省力的两个改动是换棋盘尺寸和加悔棋。换尺寸只动两个宏SIZE改成 13 或 19GRID相应调整让棋盘铺满窗口。但要注意initgraph的窗口宽高得跟着算窗口宽 2*MARGIN (SIZE-1)*GRID。如果你只改 SIZE 不改窗口棋盘会画到窗口外面。改完记得重新测一遍边角落子和胜负判断因为边界逻辑对尺寸敏感。加悔棋需要记录落子历史。用一个栈存每步的(row, col, color)typedef struct { int row, col, color; } Move; Move history[SIZE * SIZE]; // 最多 SIZE*SIZE 步 int moveCount 0; // 当前步数 // 落子时压栈 void pushMove(int row, int col, int color) { history[moveCount].row row; history[moveCount].col col; history[moveCount].color color; moveCount; } // 悔棋弹出最后一步清空该位置重绘 void undo() { if (moveCount 0) return; // 没棋可悔 moveCount--; int r history[moveCount].row; int c history[moveCount].col; board[r][c] EMPTY; // 数组状态清空 // 重绘该位置先用背景色盖掉棋子再补画棋盘线 int x, y; getPixel(r, c, x, y); setfillcolor(WHITE); // 假设背景是白色 solidrectangle(x - GRID/2, y - GRID/2, x GRID/2, y GRID/2); // 补画经过该点的横竖线简化处理实际要按棋盘样式重画 }悔棋的难点不在数据结构在重绘。EasyX 没有图层概念你盖掉棋子后原本被棋子遮住的棋盘线也没了得补画。简单做法是悔棋后整盘重绘清屏、画背景、画棋盘、遍历board数组把所有棋子重画一遍。这样代码简单不易错代价是重绘开销大一点但五子棋棋盘小完全能接受。我一般会再加一个「重新开始」按钮或按键逻辑就是initBoard()清空数组、清屏、重画棋盘、moveCount归零。这几个功能加起来代码量不大但答辩时能讲出「状态管理」「重绘策略」这些点比只交一个原始版本强。从那以后我每次拿到别人的图形程序都强制先跑通最小绘图测试确认库和环境没问题再去看业务逻辑。环境问题占调试时间的一半以上先排除它后面才顺。希望帮到你。本文还有配套的精品资源点击获取
返回列表