简介这份C#五子棋大学项目源码配套数据库面向计算机专业学生与C#初学者用于课程设计、毕业项目参考或自学练手。项目完整实现人机对战与局域网联机两种模式涵盖Minimax、Alpha-Beta剪枝AI算法、TCP/IP网络通信、SQLite数据库存储玩家信息与对战记录并包含BGM播放、悔棋、Windows Forms界面交互等模块是理解面向对象编程与网络编程的实战案例。压缩包共200个文件约53.01MB以53个cs源码文件、47个png界面素材、33个resx与30个resources资源文件为主另含9个dll依赖库、4个mp3背景音乐、3个config配置及mdf、ldf数据库文件结构完整可直接运行。目前已有615人学习下载适合对照源码梳理项目架构、复用AI与联机逻辑或作为课程答辩的参考方案。1. 从一份 C# 五子棋源码说起为什么它值得你花一个下午拆一遍如果你正在找一份能跑起来的 C# 五子棋项目大概率不是为了交作业而是想搞清楚一件事一个带人机对战和双人本地对战的棋类程序从落子、判胜到 AI 决策到底是怎么串起来的。这份源码加数据库的打包资源正好覆盖了这条链路。它适合三类人刚学完 C# 基础语法、想找一个完整项目练手的学生需要快速搭一个棋类 Demo 做课程设计或答辩的开发者以及想看看极小极大搜索和评分函数怎么落地到棋盘上的熟手。五子棋规则简单但要把人机对战做得不弱智、把胜负判定写得不出错里面有不少细节值得拆。下面我按实际复现顺序把这份资源从结构到关键实现讲透。2. 拆开源码包项目结构、数据库与运行环境拿到一个 C# 项目第一件事不是急着 F5而是先看清楚它由哪些部分组成。五子棋这类项目通常分三层界面层负责棋盘绘制和鼠标交互逻辑层负责落子、判胜和回合切换AI 层负责在轮到电脑时算出该下哪里。数据库这一块很多同学会疑惑——五子棋要数据库干嘛常见做法是用它存棋谱、存用户战绩或者存一个开局库。这份资源带了数据库说明它不只是个单机玩具而是有记录和对局管理的能力。2.1 目录结构与各文件职责解压后你大概率会看到类似这样的结构不同版本命名略有差异以实际为准FiveChess/ ├── FiveChess.sln # 解决方案文件 ├── FiveChess/ │ ├── Program.cs # 程序入口 │ ├── Forms/ │ │ ├── MainForm.cs # 主窗体棋盘绘制与交互 │ │ └── MainForm.Designer.cs │ ├── Core/ │ │ ├── Board.cs # 棋盘数据结构与落子逻辑 │ │ ├── Judge.cs # 胜负判定 │ │ └── AIEngine.cs # 人机对战核心 │ ├── Data/ │ │ ├── DbHelper.cs # 数据库访问封装 │ │ └── GameRecord.cs # 棋谱实体 │ └── App.config # 连接字符串配置 └── Database/ └── fivechess.sql # 建库建表脚本这个划分是典型的分层思路。Board.cs里通常用一个二维数组表示棋盘比如int[,] grid new int[15,15]0 表示空1 表示黑子2 表示白子。Judge.cs负责每次落子后判断是否五连。AIEngine.cs是人机对战的大脑。DbHelper.cs封装了连接和增删改查避免在窗体代码里到处写 SQL。提示如果你的 Visual Studio 打开后提示缺少引用先检查.csproj里引用的数据库驱动版本常见的是System.Data.SqlClient或MySql.Data两者不通用。2.2 数据库脚本怎么执行数据库部分通常是一个.sql文件里面包含建库、建表和初始数据。以 SQL Server 为例执行方式有两种在 SSMS 里直接打开脚本运行或者用命令行sqlcmd。执行前先确认连接字符串。-- fivechess.sql 典型内容 CREATE DATABASE FiveChessDB; GO USE FiveChessDB; GO CREATE TABLE GameRecord ( Id INT IDENTITY(1,1) PRIMARY KEY, PlayerBlack NVARCHAR(50), PlayerWhite NVARCHAR(50), Winner NVARCHAR(10), Moves NVARCHAR(MAX), -- 存储棋谱格式如 7,7;8,8;7,8 CreateTime DATETIME DEFAULT GETDATE() );建表后App.config里的连接字符串要对应改connectionStrings add nameFiveChessConn connectionStringServer.;DatabaseFiveChessDB;Integrated SecurityTrue; providerNameSystem.Data.SqlClient/ /connectionStringsServer.表示本机默认实例Integrated SecurityTrue表示用 Windows 身份验证省去账号密码。如果你用的是 MySQL连接字符串和驱动都要换DbHelper.cs里的SqlConnection也要改成MySqlConnection这是新手最容易翻车的地方——脚本是 SQL Server 的代码却引了 MySQL 的包运行直接报错。2.3 运行环境与首次编译环境上这份项目一般需要 .NET Framework 4.x 和对应版本的 Visual Studio。打开.sln后先右键解决方案还原 NuGet 包如果有再生成。首次编译常见报错是找不到数据库驱动解决办法是在 NuGet 里装对应包或者手动引用 DLL。# 如果用命令行编译先还原再生成 nuget restore FiveChess.sln msbuild FiveChess.sln /p:ConfigurationReleasenuget restore负责把依赖拉下来msbuild负责编译。Release 配置会做优化AI 搜索速度会快一些。编译通过后直接运行主窗体应该能正常显示棋盘。如果棋盘画出来了但点击没反应多半是鼠标事件没绑定或者坐标换算写错了这个放到后面排查章节讲。3. 棋盘与胜负判定二维数组、坐标换算和四个方向扫描界面能跑起来只是第一步真正决定这个五子棋能不能玩的是底层逻辑。棋盘数据结构、鼠标坐标到数组下标的换算、以及每次落子后的胜负判定这三块是地基。地基不稳AI 再强也白搭。这一章把这三块拆开讲代码可以直接对照你的源码看。3.1 棋盘数据结构与落子合法性棋盘用二维数组是最直接的做法。15×15 是标准五子棋棋盘数组下标从 0 到 14。public class Board { public const int Size 15; private int[,] grid new int[Size, Size]; // 0空 1黑 2白 public int GetPiece(int row, int col) { return grid[row, col]; } // 落子返回是否成功 public bool PlacePiece(int row, int col, int player) { if (row 0 || row Size || col 0 || col Size) return false; // 越界 if (grid[row, col] ! 0) return false; // 该位置已有棋子 grid[row, col] player; return true; } public void Undo(int row, int col) { grid[row, col] 0; // 悔棋用 } }PlacePiece里两个判断缺一不可越界检查和占位检查。很多新手写的版本只检查占位结果鼠标点到棋盘边缘外面时数组越界直接崩。Undo方法是给悔棋和 AI 搜索回溯用的AI 在试算时会频繁落子再撤销没有这个方法搜索就没法进行。3.2 鼠标坐标到棋盘下标的换算窗体上画的棋盘有边距和格子宽度鼠标点击的是像素坐标必须换算成数组下标。假设棋盘左上角起点是(offsetX, offsetY)每格宽度cellSize。private void MainForm_MouseClick(object sender, MouseEventArgs e) { // 换算成最近的交叉点 int col (int)Math.Round((e.X - offsetX) / (double)cellSize); int row (int)Math.Round((e.Y - offsetY) / (double)cellSize); if (row 0 || row Board.Size || col 0 || col Board.Size) return; // 点在棋盘外忽略 if (!board.PlacePiece(row, col, currentPlayer)) return; // 位置被占忽略 this.Invalidate(); // 触发重绘 CheckWin(row, col, currentPlayer); }这里用Math.Round而不是直接取整是因为玩家点击的是交叉点附近四舍五入才能落到最近的格子上。offsetX和offsetY是棋盘绘制时的起始偏移必须和绘制代码里的值一致否则会出现「点这里下那里」的玄学偏移。Invalidate()触发OnPaint重绘棋子才会显示出来。3.3 胜负判定的四方向扫描五子棋判胜的核心是以刚落的子为中心向四个方向检查是否有连续五个同色。四个方向分别是横、竖、左斜、右斜。每个方向要往正反两侧数。public static bool CheckWin(int[,] grid, int row, int col, int player) { // 四个方向横、竖、主对角、副对角 int[][] dirs new int[][] { new int[]{0, 1}, // 水平 new int[]{1, 0}, // 垂直 new int[]{1, 1}, // 主对角 new int[]{1, -1} // 副对角 }; foreach (var d in dirs) { int count 1; // 包含当前子 // 正方向 for (int i 1; i 5; i) { int r row d[0] * i, c col d[1] * i; if (r 0 || r 15 || c 0 || c 15) break; if (grid[r, c] ! player) break; count; } // 反方向 for (int i 1; i 5; i) { int r row - d[0] * i, c col - d[1] * i; if (r 0 || r 15 || c 0 || c 15) break; if (grid[r, c] ! player) break; count; } if (count 5) return true; } return false; }dirs数组定义了四个方向的增量。每个方向先往正方向数再往反方向数count从 1 开始是因为当前落子本身算一个。只要某个方向累计达到 5 就判胜。这里有个容易忽略的点循环上界写i 5而不是i 5因为加上当前子最多只需要再数 4 个。边界检查r 0 || r 15必须在取数组值之前做否则越界异常。注意判定必须在落子之后立刻调用并且传入刚落子的坐标。如果传错坐标或者延迟判定会出现「已经五连了但没结束」的情况。4. 人机对战核心评分函数、极小极大与搜索深度双人本地对战只要把落子、判胜、回合切换串起来就行真正有技术含量的是人机对战。这份源码的 AI 部分常见实现是「评分函数 极小极大搜索 Alpha-Beta 剪枝」这套组合。理解这套东西你才能知道为什么电脑有时候下得挺聪明有时候又像个傻子以及怎么调参数让它变强。4.1 评分函数给每个空位打分AI 要选点先得知道每个空位值多少分。评分函数的思路是假设我在这个位置落子能形成什么样的棋型根据棋型给分。常见棋型从高到低五连、活四、冲四、活三、眠三、活二。// 简化版评分统计某点在四个方向上能形成的连子数 private int EvaluatePoint(int row, int col, int player) { int score 0; int[][] dirs { new[]{0,1}, new[]{1,0}, new[]{1,1}, new[]{1,-1} }; foreach (var d in dirs) { int count 1; // 假设已落子 int block 0; // 被堵的端数 // 正方向 for (int i 1; i 4; i) { int r row d[0]*i, c col d[1]*i; if (r 0 || r 15 || c 0 || c 15) { block; break; } if (grid[r, c] player) count; else if (grid[r, c] 0) break; else { block; break; } } // 反方向同理 // ...略 score GetScoreByCount(count, block); } return score; } private int GetScoreByCount(int count, int block) { if (count 5) return 100000; if (count 4 block 0) return 10000; // 活四 if (count 4 block 1) return 1000; // 冲四 if (count 3 block 0) return 1000; // 活三 if (count 3 block 1) return 100; // 眠三 if (count 2 block 0) return 100; // 活二 return 10; }count是连子数block是被对方棋子或边界堵住的端数。活四两端都空几乎必胜给最高分冲四一端被堵次之活三再低一档。这套分值不是绝对的你可以根据实测调整。关键是让 AI 优先堵对方的活三和冲四同时自己争取活四。4.2 极小极大搜索与 Alpha-Beta 剪枝单点评分只能看一步要看得更远就得用搜索。极小极大Minimax的思路是我走一步假设对方走最优应对我再走最优如此递归下去选一个最终对我最有利的分支。Alpha-Beta 剪枝是在这个过程中砍掉不可能更优的分支减少计算量。private int Minimax(int depth, int alpha, int beta, bool isMax) { if (depth 0 || IsGameOver()) return EvaluateBoard(); // 到达深度或终局返回局面分 var moves GenerateCandidateMoves(); // 候选点不是全部空位 if (isMax) // AI 回合取最大 { int best int.MinValue; foreach (var m in moves) { grid[m.Row, m.Col] aiPlayer; int val Minimax(depth - 1, alpha, beta, false); grid[m.Row, m.Col] 0; // 回溯 best Math.Max(best, val); alpha Math.Max(alpha, best); if (beta alpha) break; // 剪枝 } return best; } else // 玩家回合取最小 { int best int.MaxValue; foreach (var m in moves) { grid[m.Row, m.Col] humanPlayer; int val Minimax(depth - 1, alpha, beta, true); grid[m.Row, m.Col] 0; best Math.Min(best, val); beta Math.Min(beta, best); if (beta alpha) break; // 剪枝 } return best; } }depth是搜索深度一般设 2 到 4。深度越大 AI 越强但计算量指数增长。alpha是当前已知的最大下界beta是最小上界当beta alpha时说明这个分支不可能影响最终决策直接break剪掉。GenerateCandidateMoves很关键——如果遍历全部 225 个空位搜索会慢到没法玩常见做法是只考虑已有棋子周围两格内的空位把候选点压到几十个。4.3 搜索深度与性能的取舍深度和性能是一对矛盾。深度 2 的 AI 基本只能看一步容易被活三骗深度 4 能看两步棋力明显提升但每步可能要算几百毫秒甚至更久。实际调参时我一般会先设深度 2 保证流畅再逐步加到 4观察落子延迟。搜索深度大致候选点数单步耗时普通 PC棋力感受230501050 ms能堵活三偶尔漏冲四3305050200 ms攻防均衡43050200800 ms较强但可能卡顿如果深度 4 卡顿明显可以加迭代加深先算深度 2再算深度 3、4超时就用上一层的结果。这样至少不会让界面假死。另一个优化是候选点排序把评分高的点排在前面Alpha-Beta 剪枝效率会高很多。提示AI 落子如果放在 UI 线程里算深度一高界面就卡死。常见做法是用Task.Run把搜索放到后台线程算完再回到 UI 线程落子。5. 避坑与排查数据库连接、坐标偏移和 AI 卡顿前面讲的是正常路径但实际跑这份源码时翻车点往往集中在几个地方。这一章按「现象 → 原因 → 解决」列几条我踩过的坑你对照排查能省不少时间。5.1 启动报数据库连接失败现象程序一启动就弹异常提示无法连接数据库或登录失败。原因通常是连接字符串和实际数据库不匹配或者数据库服务没启动。解决先确认数据库服务在运行再检查App.config里的Server和Database名称是否和脚本里建的一致。如果用 SQL Server ExpressServer要写成.\SQLEXPRESS而不是.。Windows 身份验证失败时可以临时改成 SQL 账号密码验证确认是不是权限问题。5.2 点击棋盘落子位置偏移现象鼠标点在某处棋子却下在隔壁格甚至更远。原因一般是坐标换算里的offsetX、offsetY和绘制代码不一致或者cellSize取错。解决把绘制棋盘和坐标换算的代码放在一起看确保两者用的是同一组常量。建议把这些值定义成类字段只写一次两边引用同一个变量。如果棋盘有缩放还要考虑缩放比例。5.3 AI 落子后界面卡死现象轮到电脑走棋时界面无响应几秒甚至更久。原因搜索在 UI 线程同步执行深度越大越明显。解决把 AI 搜索放到后台线程用await Task.Run(() ai.GetBestMove())算完再更新棋盘。注意后台线程不能直接操作 UI 控件要用Invoke或await回到 UI 线程。5.4 胜负判定漏判或误判现象明明五连了却没结束或者没连五却判赢了。原因判定方向写漏、边界检查缺失或者判定时传入了错误的坐标。解决确认四个方向都覆盖边界检查在取值之前判定调用紧跟在落子之后并传入刚落子的row、col。可以写个单元测试手动构造五连局面验证。5.5 悔棋后 AI 状态错乱现象悔棋之后 AI 不走了或者走了奇怪的位置。原因悔棋只撤销了棋盘上的子没有同步撤销回合状态或 AI 内部缓存。解决悔棋时把currentPlayer也回退如果 AI 有缓存候选点或搜索树一并清掉。简单做法是悔棋后重新计算不依赖旧状态。6. 进阶技巧用开局库和置换表把 AI 再提一档把基础版本跑通之后如果你想让这个人机对战更耐玩有两个投入产出比很高的方向开局库和置换表。开局库解决的是「前几步别乱下」置换表解决的是「重复局面别重算」。开局库的做法是预先存一批常见开局的前若干步AI 在开局阶段直接查库落子不走搜索。这样既快又稳避免 AI 第一步下在天元旁边这种尴尬位置。数据可以存在数据库里正好用上这份资源的数据库部分——建一张OpeningBook表字段存局面哈希和推荐落点。CREATE TABLE OpeningBook ( Id INT IDENTITY(1,1) PRIMARY KEY, BoardHash CHAR(64), -- 局面哈希 BestMove VARCHAR(10), -- 推荐落点 row,col Weight INT DEFAULT 1 -- 权重用于随机选择 );置换表则是搜索过程中的缓存。同一个局面可能通过不同走法到达如果每次都重算就浪费了。用一个字典存「局面哈希 → 搜索结果」搜索前先查表。private Dictionarystring, int transTable new Dictionarystring, int(); private string GetBoardHash() { var sb new StringBuilder(); for (int r 0; r 15; r) for (int c 0; c 15; c) sb.Append(grid[r, c]); return sb.ToString(); } // 在 Minimax 开头查表 string key GetBoardHash() _ depth; if (transTable.ContainsKey(key)) return transTable[key]; // ... 搜索逻辑 ... transTable[key] best; return best;哈希用字符串拼接最简单但 225 个字符的字符串做键内存和比较开销都不小。讲究一点可以用 Zobrist 哈希把每个位置的每种棋子映射成一个随机数局面哈希就是所有棋子随机数的异或增量更新很快。不过对于五子棋这种规模字符串哈希在深度 4 以内也够用先跑通再优化。验证 AI 有没有变强我一般用固定局面回放摆几个已知的活三、冲四局面看 AI 能不能正确堵或成五。再就是自己跟它下几盘感受一下它会不会漏堵。调参的时候一次只改一个变量改完记录结果不然你根本不知道是哪个改动起了作用。从那以后我每次拿到带 AI 的棋类项目都强制先跑一遍固定局面回放确认搜索和评分没写反再去看界面。这个习惯帮我省了很多「以为 AI 很聪明其实它在乱下」的返工。希望帮到你。本文还有配套的精品资源点击获取