简介这份资源面向C语言初学者与图形编程入门者是一套基于EasyX图形库实现的五子棋游戏完整工程用于解决从控制台程序过渡到图形界面开发时缺少可运行范例的问题。压缩包共145个文件、约57.36MB以jpg界面截图、tlog与log构建日志、obj与pdb编译中间文件为主另有8个mp3与1个wav音频素材、6个cpp源文件与5个h头文件以及sln解决方案和vcxproj工程配置可直接用Visual Studio打开编译运行。已有1688人学习下载。项目覆盖棋盘绘制、落子判定、胜负检测等核心逻辑并涉及人机对战中的Minimax与Alpha-Beta剪枝思路、人人对战的状态更新以及基于socket的联机通信设计读者可据此理解EasyX函数调用方式、游戏状态管理与搜索算法的落地写法适合作为课程设计或C语言综合实践的参考工程。1. 从一块黑窗口到能落子的棋盘这个 C 语言五子棋项目到底能干什么很多人第一次接触图形编程都是从控制台里那个黑窗口开始的——字符画棋盘、坐标输入落子能跑但实在谈不上好看。这个基于 EasyX 图形库的 C 语言五子棋项目解决的正是这个尴尬它把控制台里的字符棋盘搬进了真正的图形窗口鼠标点击落子、棋盘格线绘制、黑白棋子渲染、胜负判定一整套流程都跑在 Windows 原生窗口里。对刚学完 C 语言基础语法、想找一个能看得见摸得着的练手项目的人来说它比刷一百道算法题更有成就感对想入门图形界面编程的开发者它也是一块很好的敲门砖。整个项目不依赖复杂框架核心就是 C 语言加 EasyX编译环境常见做法是 Visual Studio 配 EasyX 库代码量不大但五脏俱全。2. 技术选型与工程结构为什么是 C 语言加 EasyX 这套组合2.1 选型逻辑轻量图形库配纯 C 的取舍先说清楚这个项目为什么用 EasyX 而不是别的。EasyX 是 Windows 平台上一个面向 C/C 的轻量图形库它把 Windows GDI 那套繁琐的窗口创建、消息循环、绘图接口封装成了几十个直观的函数比如initgraph开窗口、circle画圆、line画线、getmouse取鼠标消息。对 C 语言学习者来说这意味着不用先啃 Win32 API 那一大坨WNDCLASS、RegisterClass、CreateWindow的样板代码就能直接进入“画东西”的正题。代价也很明确EasyX 只跑在 Windows 上跨平台就别想了它也不是游戏引擎没有精灵、动画、物理这些概念复杂项目撑不住。但五子棋这种回合制、静态绘制为主的场景恰好落在它的舒适区里。棋盘是固定的 15×15 网格棋子是圆交互只有鼠标点击刷新频率极低EasyX 完全够用。我一般会把这类项目定位成“图形编程的第一课”而不是“游戏开发的起点”心态摆正了后面踩坑也少。从工程角度看纯 C 加 EasyX 还有一个隐性好处没有类、没有模板、没有智能指针所有状态都是全局数组和结构体调试时变量看得见摸得着。棋盘状态就是一个二维数组落子就是改数组元素胜负判定就是遍历数组逻辑链路短出问题容易定位。这对新手极其友好也是这个项目值得拿来拆的原因。2.2 目录与文件组织一个 main.c 撑起的工程这类项目的目录结构通常很朴素常见做法是单文件或少量文件组织核心逻辑集中在一个main.c里配合 EasyX 的头文件和库文件。一个典型的工程目录长这样GoBang/ ├── GoBang.sln # Visual Studio 解决方案文件 ├── GoBang/ │ ├── main.c # 全部核心逻辑初始化、绘制、交互、判定 │ ├── easyx.h # EasyX 头文件安装后由库提供 │ ├── easyx.lib # EasyX 静态库32/64 位需匹配 │ └── res/ │ └── board_bg.png # 可选棋盘背景图资源需要说明的是easyx.h和easyx.lib一般不需要手动拷进工程目录安装 EasyX 时它会自动配置到 Visual Studio 的包含路径和库路径里工程里直接#include easyx.h就能用。上面列出来只是让你知道依赖在哪。如果你的工程编译时报“无法打开 easyx.h”八成是 EasyX 没装或者装到了另一个 VS 版本下这是新手第一个高频翻车点后面避坑章节会细说。单文件组织的好处是阅读成本低打开main.c从上往下读就是完整流程坏处是代码一多就乱。所以我在拆这个项目时会建议按职责在心里给它分块常量与全局状态、初始化、绘制、交互、判定、主循环。哪怕物理上还是一个文件逻辑上分清楚二次开发时才不会改一处崩一片。2.3 核心数据结构棋盘状态怎么存五子棋的状态模型简单到极致一个二维数组就够了。常见做法是用int board[15][15]0 表示空位1 表示黑子2 表示白子。棋盘格数用宏定义方便以后改成 19×19 之类#include easyx.h #include stdio.h #define BOARD_SIZE 15 // 15 路棋盘 #define GRID_PX 40 // 每格像素宽度 #define MARGIN 40 // 棋盘四周留白 #define WIN_COUNT 5 // 连成几子获胜 // 棋盘状态0 空1 黑2 白 int board[BOARD_SIZE][BOARD_SIZE] {0}; // 当前轮到谁落子1 黑 2 白 int currentPlayer 1; // 游戏是否结束 int gameOver 0;这里几个参数值得展开说。GRID_PX决定棋盘在窗口里占多大40 像素一格配 15 路棋盘区域就是 560×560加上两边各 40 的留白窗口开 640×640 左右比较舒服。MARGIN是棋盘左上角到窗口边缘的距离绘制时所有坐标都要加上它做偏移。WIN_COUNT单独抽出来是为了以后想玩四子棋、六子棋时只改一个宏。currentPlayer和gameOver是典型的全局状态纯 C 项目里这么写很常见虽然不够“工程化”但胜在直观。提示棋盘数组用全局变量而不是局部变量传参是这个项目规模下的合理选择。等你想加悔棋、存档功能时全局状态反而方便整体快照不必层层传指针。3. 核心模块拆解从开窗口到判胜负的完整链路3.1 初始化与棋盘绘制把网格画出来EasyX 的入口是initgraph一行代码开窗口。棋盘绘制的核心是把数组坐标映射成屏幕像素坐标然后画横线、竖线、星位点。先看初始化与坐标映射void initGame(void) { // 开一个 640x640 的窗口背景白色 initgraph(640, 640); setbkcolor(WHITE); cleardevice(); setlinecolor(BLACK); setlinestyle(PS_SOLID, 2); // 实线线宽 2 像素 } // 数组下标 (row, col) 转屏幕像素坐标 int toPixelX(int col) { return MARGIN col * GRID_PX; } int toPixelY(int row) { return MARGIN row * GRID_PX; }initgraph(640, 640)创建窗口并进入绘图模式之后所有绘图函数都作用在这个窗口上。setbkcolor设背景色cleardevice用背景色清屏这两步顺序不能反——先设色再清屏否则清出来还是默认的黑底。setlinestyle(PS_SOLID, 2)把线型设为 2 像素实线棋盘线太细在屏幕上会发虚。坐标映射函数是整个绘制和交互的枢纽。数组的(row, col)是逻辑坐标屏幕的(x, y)是物理坐标两者靠MARGIN index * GRID_PX换算。这个换算必须双向一致画棋子用它鼠标点击反算落子位置也用它一旦两边公式对不上就会出现“点这儿子落那儿”的玄学 bug。接着画棋盘void drawBoard(void) { int i; // 画 15 条横线和 15 条竖线 for (i 0; i BOARD_SIZE; i) { line(toPixelX(0), toPixelY(i), toPixelX(BOARD_SIZE - 1), toPixelY(i)); line(toPixelX(i), toPixelY(0), toPixelX(i), toPixelY(BOARD_SIZE - 1)); } // 画五个星位天元与四角星 int stars[5][2] {{3,3},{3,11},{11,3},{11,11},{7,7}}; setfillcolor(BLACK); for (i 0; i 5; i) { int cx toPixelX(stars[i][1]); int cy toPixelY(stars[i][0]); solidcircle(cx, cy, 4); // 半径 4 像素的实心圆 } }line(x1, y1, x2, y2)画线段横线固定 y 变 x竖线固定 x 变 y循环 15 次正好铺满。星位是五子棋棋盘的传统标记solidcircle画实心圆点半径 4 像素在 40 像素格宽下比例合适。这里stars数组存的是(row, col)取用时注意stars[i][1]是列、stars[i][0]是行别写反了——这种下标顺序错误在调试时特别隐蔽因为画出来只是位置偏了程序不报错。3.2 鼠标交互与落子点击怎么变成数组里的一个数EasyX 取鼠标消息用GetMouseMsg它返回一个MOUSEMSG结构体里面有消息类型、坐标、按键状态。落子逻辑要做三件事判断是不是左键按下、把像素坐标反算成棋盘下标、检查该位置是否合法。void handleClick(void) { MOUSEMSG msg GetMouseMsg(); if (msg.uMsg ! WM_LBUTTONDOWN) return; // 只处理左键按下 // 像素坐标反算最近的棋盘交叉点 int col (msg.x - MARGIN GRID_PX / 2) / GRID_PX; int row (msg.y - MARGIN GRID_PX / 2) / GRID_PX; // 越界或已有子则忽略 if (row 0 || row BOARD_SIZE || col 0 || col BOARD_SIZE) return; if (board[row][col] ! 0 || gameOver) return; board[row][col] currentPlayer; drawPiece(row, col, currentPlayer); if (checkWin(row, col, currentPlayer)) { gameOver 1; showResult(currentPlayer); } else { currentPlayer 3 - currentPlayer; // 1 变 22 变 1 } }反算公式里的 GRID_PX / 2是关键鼠标点不一定正好落在交叉点上加上半格再整除相当于四舍五入到最近的交叉点手感会好很多。不加这个偏移玩家必须精确点到交叉点才生效体验极差。越界检查用row 0 || row BOARD_SIZE这种写法注意是不是因为下标最大是BOARD_SIZE - 1。currentPlayer 3 - currentPlayer是个小技巧1 和 2 互相切换比写 if-else 简洁。drawPiece负责画棋子checkWin负责判定showResult弹结果职责分得很清。这里有个容易忽略的点gameOver之后必须拦住后续点击否则玩家还能继续落子棋盘就乱了。画棋子的函数本身很简单void drawPiece(int row, int col, int player) { int cx toPixelX(col); int cy toPixelY(row); setfillcolor(player 1 ? BLACK : WHITE); setlinecolor(BLACK); solidcircle(cx, cy, GRID_PX / 2 - 3); // 半径略小于半格 }半径取GRID_PX / 2 - 3留 3 像素间隙棋子之间不会粘连视觉上更清爽。白子填充白色但描边黑色否则在白底上看不见——这是新手常犯的错白棋画完“消失”了。3.3 胜负判定四个方向扫描的经典写法五子棋判定没有捷径就是以刚落下的子为中心向横、竖、两条斜线四个方向数连子。每个方向要往正反两边数加起来够 5 就赢。int countDirection(int row, int col, int dr, int dc, int player) { int count 0; int r row dr, c col dc; while (r 0 r BOARD_SIZE c 0 c BOARD_SIZE board[r][c] player) { count; r dr; c dc; } return count; } int checkWin(int row, int col, int player) { // 四个方向横、竖、主对角、副对角 int dirs[4][2] {{0,1},{1,0},{1,1},{1,-1}}; int i; for (i 0; i 4; i) { int total 1; // 包含当前这颗子 total countDirection(row, col, dirs[i][0], dirs[i][1], player); total countDirection(row, col, -dirs[i][0], -dirs[i][1], player); if (total WIN_COUNT) return 1; } return 0; }countDirection从当前子出发沿(dr, dc)方向一路数同色子遇到边界或异色就停。checkWin对四个方向各数正反两次加上当前子本身总数达到WIN_COUNT即胜。方向数组dirs里{1,-1}是副对角线左下到右上注意列方向是负的写错符号会导致斜线判定失效而且这种 bug 只在特定棋型下暴露测试时容易漏。注意判定只需检查刚落下的那颗子不需要全盘扫描。全盘扫描虽然也能出结果但每次落子都遍历 225 个格子纯属浪费而且逻辑更容易写错。3.4 主循环消息驱动的基本骨架EasyX 程序的主循环通常是一个while加消息处理配合BeginBatchDraw/FlushBatchDraw做双缓冲避免闪烁。这个项目的绘制量小简单写法也能跑但加上双缓冲更稳int main(void) { initGame(); drawBoard(); BeginBatchDraw(); // 开启批量绘图减少闪烁 while (!gameOver) { handleClick(); // 内部阻塞等待鼠标消息 FlushBatchDraw(); // 把这一帧画出来 } EndBatchDraw(); _getch(); // 等按键再关窗口方便看结果 closegraph(); return 0; }BeginBatchDraw之后所有绘图先画到内存缓冲FlushBatchDraw一次性刷到屏幕这样棋子不会一颗颗“蹦”出来。handleClick里的GetMouseMsg是阻塞的没消息就停在那CPU 占用低适合这种回合制交互。_getch()来自conio.h作用是让结果画面停住不然窗口一闪就关玩家根本看不清谁赢了。closegraph释放图形资源养成习惯。4. 编译运行与避坑新手最容易翻车的几个地方4.1 环境搭建EasyX 装对版本比什么都重要这个项目跑不起来九成问题出在环境上。EasyX 的安装包要和 Visual Studio 的版本、工程的位数x86/x64都匹配。常见做法是去 EasyX 官网下载安装程序它会自动检测本机的 VS 版本并配置头文件和库路径。装完之后新建一个空项目把main.c加进去直接编译即可。需要提醒的是EasyX 只支持 Windows 和 MSVC 系编译器用 MinGW、GCC 或者 Clang 是编不过的因为它的库是 MSVC 格式的.lib。如果你习惯用 VS Code 配 MinGW这个项目会让你很痛苦老老实实换 Visual Studio 更省时间。另外工程属性里的“平台”要和 EasyX 库的位数一致x64 工程配 32 位库会报链接错误。4.2 常见问题排查现象、原因与解决现象一编译报“无法打开源文件 easyx.h”。原因通常是 EasyX 没装或者装到了另一个 VS 版本目录下当前工程用的 VS 找不到。解决办法是确认安装时勾选的 VS 版本与当前打开工程的版本一致不一致就重装 EasyX或者手动把easyx.h所在目录加到工程属性的“附加包含目录”。现象二链接报“无法解析的外部符号 initgraph”。这是库没链上多半是位数不匹配或库路径没配。检查工程平台是 x86 还是 x64确认 EasyX 对应位数的.lib在“附加依赖项”里如果手动配置过核对“附加库目录”路径有没有写错。现象三窗口一闪就没了。主函数末尾没加_getch()之类的等待程序跑完直接closegraph退出。补上等待按键的代码即可。也有人用Sleep硬等但那样体验差还是等按键更自然。现象四点击落子位置偏移。反算公式和绘制公式不一致或者MARGIN、GRID_PX改了一处没改另一处。血泪经验是把坐标换算统一收进toPixelX/toPixelY和反算函数里任何地方都不许手写MARGIN col * GRID_PX改参数时只改宏定义。现象五白棋画上去看不见。白子填充白色但没设描边色白底白子自然隐形。解决是画白子前setlinecolor(BLACK)用solidcircle画填充圆时描边会跟着走或者改用fillcircle加circle组合。4.3 运行验证怎么确认它真的跑对了跑通的标准不是“窗口出来了”而是完整走一遍对局黑先白后交替落子、点击已有子的位置无反应、边界外点击无反应、横竖斜四个方向各连成五子都能正确判胜、分出胜负后不能再落子。建议专门测一下副对角线方向因为{1,-1}这个方向最容易写错符号。再测一下棋盘四个角和边缘的落子验证越界判断没有把合法位置挡掉。这几项过了核心逻辑基本就稳了。5. 二次开发改造从能玩到好玩几个可落地的方向这个项目骨架小改造空间反而大。我按投入产出比排几个方向都是能在一两个晚上搞定的。第一个是悔棋。核心思路是维护一个落子历史栈每次落子把(row, col, player)压栈悔棋时弹栈、把board[row][col]清 0、重绘棋盘。重绘整盘比局部擦除省心15×15 的绘制量对 EasyX 来说毫无压力。注意悔棋要一次退两步双方各一步否则会打乱轮次。第二个是人机对战。最简版是评分表法对每个空位分别计算若下黑子、若下白子的四方向连子得分取攻守加权最高的位置。不用上极小化极大搜索一个像样的评分函数就能让 AI 有模有样。关键是把checkWin里的方向扫描逻辑复用过来改成“假设落子后能连几个”。第三个是界面美化。棋盘背景换成木纹图loadimage加载 PNG棋子用带高光的图片替代solidcircle落子加个简单的缩放动画。EasyX 支持putimage贴图改造量集中在绘制函数逻辑层不用动。第四个是状态持久化。把board数组和currentPlayer直接fwrite到文件读档时fread回来再重绘几十行代码就能实现存档读档。全局状态在这里反而成了优势一个结构体快照全搞定。改造时有个习惯我一直保持任何涉及坐标换算的改动改完必须手动点一遍棋盘四个角和中心。因为坐标 bug 不会报错只会让棋子悄悄偏几像素肉眼不盯着看根本发现不了。从那以后我每次动MARGIN或GRID_PX都强制走一遍“四角加中心”的点击验证省下了不少返工。希望这个项目能帮你把 C 语言从黑窗口带进图形世界玩起来再改起来。本文还有配套的精品资源点击获取