C#五子棋游戏开发完整源码项目实战
简介:五子棋是一款经典两人对弈游戏,规则简洁但富有策略性,是学习算法设计、图形界面开发与事件驱动编程的理想实践项目。本C#五子棋源程序涵盖Windows Forms界面构建、二维数组棋盘管理、棋子落子逻辑、连珠判断、胜负判定及基础AI对战功能,适合C#初学者深入理解面向对象编程与GUI应用开发。通过该项目的学习与实践,开发者可全面掌握桌面应用程序的开发流程与核心机制,提升综合编程能力。
五子棋开发全栈实践:从C#基础到AI智能对战
你有没有试过在某个深夜,打开电脑想下一盘五子棋,却发现找不到一个能陪你“斗智斗勇”的对手?或者自己写了个小游戏,结果逻辑混乱、界面卡顿、悔棋都像在拆炸弹?🤯 其实啊, 做一个看似简单的五子棋程序,背后藏着的可不只是“放个子、连五个”这么简单 。它是一扇门——通向现代软件工程核心思想的大门。
今天我们就来搞点“狠活”,不只教你画个棋盘、点个按钮,而是带你从零开始,用 C# + Windows Forms 搭建一套真正专业级的五子棋系统。你会看到:基础语法如何支撑复杂逻辑,面向对象怎样让代码优雅又健壮,图形界面为何需要双缓冲防闪烁,甚至还能给你的游戏塞进一个会“思考”的AI大脑🧠!
准备好了吗?Let’s go!🚀
🧱 基础语法:不是“Hello World”,而是游戏骨架
很多人学编程都卡在“学会了语法却不会写项目”这个坎上。其实关键在于—— 要把语言特性当成工具箱里的螺丝刀和扳手,而不是背诵说明书 。
比如我们做个15×15的标准五子棋盘,第一反应就是:
int[,] board = new int[15, 15];
这行代码看着平平无奇,但它干了三件大事:
- int[,] 是二维数组,模拟真实棋盘的坐标系;
- [15,15] 定义大小,避免后期频繁调整;
- 初始化为0,代表“空位”。
接下来判断能不能落子?一句话搞定:
if (board[x, y] == 0) { /* 可以下 */ }
别小看这个条件判断,它是整个游戏安全性的第一道防线。但如果你直接到处写 board[x,y]==0 ,等哪天你想加个“禁手规则”或“特殊地形”,改起来就得满世界找硬编码……😅 所以聪明的做法是把它封装成方法:
public bool IsEmpty(int row, int col) => grid[row, col] == 0;
你看,就这么一点点封装意识,就已经悄悄迈进了“好代码”的门槛。
至于胜利判定?总不能让用户自己数吧 😅 那就得靠循环遍历四个方向(横、竖、主对角线、副对角线):
for (int i = -4; i <= 4; i++)
{
if (GetPiece(row + i, col) != piece) break;
if (i == 4 && count == 5) return true;
}
这些看似简单的 if 和 for ,组合起来就成了游戏运行的“神经系统”。它们不炫酷,但不可或缺——就像盖房子的地基,看不见却撑得起整栋楼。
💡 小贴士:初学者常犯的错误是把所有逻辑塞进一个类里,最后变成“上帝类”。记住一句话: 每一行代码都应该待在它最舒服的地方 。
🔗 面向对象设计:谁该干什么事?
现在问题来了:上面那些功能,到底放在哪儿合适?
假设你把棋盘状态、胜负判断、玩家信息全都扔进 Form1.cs ,那不出三天,这个文件就会膨胀到上千行,任何人想改个按钮颜色都得提心吊胆——生怕一动就崩 😰
这就是为什么我们需要 面向对象编程(OOP) 。它的本质不是什么高深理论,而是 给人类大脑减负的一种思维方式 :把复杂系统拆成几个各司其职的小部件,每个部件只关心自己的事。
🎯 核心三剑客:Board、Player、GameManager
想象一下真实的五子棋对局,有哪些“角色”参与?
- 棋盘(Board) :负责记录每一步落在哪;
- 玩家(Player) :决定什么时候轮到我下,我下哪儿;
- 裁判/主持人(GameManager) :控制流程、切换回合、宣布胜负。
这三个角色,正好对应三个类。
✅ Board 类:数据容器 + 行为封装
先来看棋盘类的设计。很多人一开始会这样写:
public class Board
{
public int[,] Grid; // 错!太暴露了!
}
这样做等于把家门钥匙交给所有人,谁都能随便改你的棋盘状态。正确的做法是“藏起来”:
public class Board
{
private const int Size = 15;
private int[,] grid;
public int Size => Size; // 只读属性暴露尺寸
public Board() => grid = new int[Size, Size];
public bool IsEmpty(int row, int col) => grid[row, col] == 0;
public int GetCell(int row, int col) => grid[row, col];
public bool PlacePiece(int row, int col, int piece)
{
if (row < 0 || row >= Size || col < 0 || col >= Size) return false;
if (!IsEmpty(row, col)) return false;
grid[row, col] = piece;
return true;
}
}
注意几个细节:
- private 保护内部数组;
- 构造函数确保每次新建棋盘都是干净的;
- PlacePiece 返回 bool ,方便调用者知道操作是否成功;
- 使用 => 简化单行方法,代码更清爽。
| 方法 | 功能 | 时间复杂度 |
|---|---|---|
IsEmpty() |
判断位置是否为空 | O(1) |
GetCell() |
获取某位置棋子类型 | O(1) |
PlacePiece() |
落子并校验合法性 | O(1) |
Clear() |
重置棋盘 | O(n²) |
这种设计下,外部代码永远无法直接修改 grid ,只能通过接口操作——这才是真正的“封装”。
✅ Player 类:不只是名字和颜色
玩家也不只是个标签。他要有身份、有行为、还能知道自己是不是该出招了:
public enum PlayerColor { Black = 1, White = 2 }
public class Player
{
public string Name { get; }
public PlayerColor Color { get; }
public bool IsActive { get; set; } // 当前是否轮到该玩家
public Player(string name, PlayerColor color)
{
Name = name;
Color = color;
IsActive = false;
}
public (int row, int col)? GetMove(Board board)
{
var emptyCells = new List<(int, int)>();
for (int i = 0; i < board.Size; i++)
for (int j = 0; j < board.Size; j++)
if (board.IsEmpty(i, j))
emptyCells.Add((i, j));
if (emptyCells.Count == 0) return null;
var rnd = new Random();
return emptyCells[rnd.Next(emptyCells.Count)];
}
}
这里有几个值得玩味的设计点:
- 用了元组 (int row, int col) 返回坐标,语义清晰;
- GetMoveSafe 版本避免无限循环,在终局阶段更稳定;
- 返回 ? (可空值),明确表达“可能没地方下了”的意图;
- 把“随机落子”作为默认策略,未来可以轻松替换成 AI 算法。
🤔 思考题:为什么
Random不做成静态字段?因为多线程环境下会有竞争问题!每次 new 一个虽然性能略低,但绝对安全。
✅ GameManager:游戏世界的“中央控制器”
有了棋盘和玩家,谁来指挥全局?
答案就是 GameManager 。它像是交响乐的指挥家,协调各个乐器奏出和谐旋律:
public class GameManager
{
private Board board;
private Player player1, player2;
private Player currentPlayer;
public event Action<GameResult> OnGameEnded;
public event Action OnBoardUpdated;
public GameManager()
{
board = new Board();
player1 = new Player("人类", PlayerColor.Black);
player2 = new Player("AI", PlayerColor.White);
currentPlayer = player1;
}
public bool MakeMove(int row, int col)
{
if (!board.PlacePiece(row, col, (int)currentPlayer.Color))
return false;
OnBoardUpdated?.Invoke();
if (CheckWin(row, col))
{
OnGameEnded?.Invoke(new GameResult(currentPlayer, WinReason.FiveInRow));
return true;
}
SwitchTurn();
return true;
}
private void SwitchTurn() =>
currentPlayer = currentPlayer == player1 ? player2 : player1;
}
重点来了: 事件机制(Event) 的引入,彻底解耦了逻辑层和表现层!
什么意思呢?以前你可能在 MakeMove 里直接写 pictureBox1.Invalidate() ,但现在不需要了。UI 层只需要“订阅”这两个事件:
gameManager.OnBoardUpdated += () => pictureBox1.Invalidate();
gameManager.OnGameEnded += result => MessageBox.Show($"{result.Winner.Name} 赢了!");
这样一来, GameManager 根本不知道 UI 存不存在,它可以被用于命令行版、Web版、甚至服务器后台匹配系统 —— 高度复用性就这么来了 。
下面是类关系图,直观展示三者协作方式:
classDiagram
class Board {
-int[,] grid
+bool PlacePiece()
+bool IsEmpty()
}
class Player {
+string Name
+PlayerColor Color
+bool IsActive
+GetMove()
}
class GameManager {
-Board board
-Player player1, player2
+MakeMove()
+SwitchTurn()
+event OnGameEnded
}
GameManager --> Board : uses
GameManager --> Player : controls
看到没?没有双向依赖,没有全局变量,每个类职责分明。这才是工业级代码的样子!
🖼️ 图形界面:不只是好看,更要流畅
再厉害的底层逻辑,如果界面卡成PPT,用户也不会买账。Windows Forms 虽然是“老技术”,但它依然是快速构建桌面应用的利器。
🖼️ PictureBox:绘制棋盘的艺术
我们选 PictureBox 作为主画布,因为它支持自定义绘图。关键是要处理好 Paint 事件:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
int cellSize = 40;
Pen gridPen = Pens.Black;
for (int i = 0; i <= 15; i++)
{
g.DrawLine(gridPen, 0, i * cellSize, 600, i * cellSize); // 横线
g.DrawLine(gridPen, i * cellSize, 0, i * cellSize, 600); // 竖线
}
// 天元点(中心黑点)
g.FillEllipse(Brushes.Black, 297, 297, 6, 6);
}
但这里有坑⚠️:每次重绘都会创建新 Graphics ,如果你在这里不断 new Pen(...) ,内存迟早爆炸!
✅ 正确姿势:把 Pen 和 Brush 提升为窗体字段,并在窗体关闭时释放资源:
private Pen gridPen = new Pen(Color.Black, 1);
private Brush blackBrush = Brushes.Black;
// 最后别忘了 Dispose
protected override void Dispose(bool disposing)
{
if (disposing)
{
gridPen.Dispose();
blackBrush.Dispose();
}
base.Dispose(disposing);
}
⚙️ 双缓冲:告别屏幕闪烁
当你频繁调用 Invalidate() (比如鼠标悬停预览落子),画面会疯狂闪动。这是因为 Windows 默认采用“直接绘制”模式,一边擦旧图一边画新图,肉眼就能看出撕裂感。
解决方案:开启双缓冲!
this.SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.DoubleBuffer,
true);
原理很简单:先在内存中画一张完整的图,画好了再一次性“贴”到屏幕上。虽然多占点内存,但换来丝般顺滑的体验,值!
| 控制样式 | 作用 |
|---|---|
AllPaintingInWmPaint |
禁止自动擦背景 |
UserPaint |
允许手动绘图 |
DoubleBuffer |
启用离屏缓冲 |
📏 坐标转换:从像素到格子
用户点击的是屏幕坐标(X=312, Y=405),但我们需要的是棋盘索引(row=10, col=7)。怎么转?
private Point GetBoardPointFromScreen(Point screenPoint)
{
int row = screenPoint.Y / cellSize;
int col = screenPoint.X / cellSize;
if (row >= 0 && row < 15 && col >= 0 && col < 15)
return new Point(row, col);
else
return new Point(-1, -1); // 无效位置
}
注意这里用了整除 / ,自动向下取整,刚好对应网格定位。再加上边界检查,防止越界访问数组。
流程如下:
graph LR
A[鼠标点击] --> B{获取Screen Point}
B --> C[调用GetBoardPointFromScreen]
C --> D[计算Row/Col]
D --> E[越界检测]
E -->|有效| F[提交落子请求]
E -->|无效| G[忽略操作]
🧩 布局管理:适应各种分辨率
别让你的游戏只能在一个固定窗口运行!使用 TableLayoutPanel 或 FlowLayoutPanel 让控件自动排列:
flowPanel.Dock = DockStyle.Bottom;
flowPanel.Controls.Add(btnRestart);
flowPanel.Controls.Add(btnExit);
配合 Anchor 和 Dock 属性,实现响应式布局:
| 属性 | 效果 |
|---|---|
Dock=Fill |
填充父容器 |
Anchor=All |
四边随窗口缩放 |
AutoSize=true |
自动匹配内容大小 |
还有 DPI 缩放问题也不能忽视,在 Main() 中加上:
Application.SetHighDpiMode(HighDpiMode.SystemAware);
否则在高分屏上文字糊成一片,用户体验直接归零。
🐭 事件驱动:让程序“活”起来
GUI 应用的本质是“等待用户动作”。这就引出了 事件驱动模型 —— 整个程序围绕事件展开,而不是顺序执行。
🖱️ 鼠标点击处理全流程
当玩家点击棋盘时,发生了什么?
chessBoardPanel.MouseClick += ChessBoardPanel_MouseClick;
private void ChessBoardPanel_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
var (row, col) = ConvertToBoardIndex(e.X, e.Y);
if (!IsValidMove(row, col)) return;
gameManager.MakeMove(row, col);
}
其中 IsValidMove 包含三重防护:
private bool IsValidMove(int row, int col)
{
if (row < 0 || row >= 15 || col < 0 || col >= 15) return false;
if (_gameManager.IsGameOver) return false;
if (!_board.IsEmpty(row, col)) return false;
return true;
}
层层过滤,确保只有合法操作才能进入核心逻辑。
完整流程如下:
flowchart TD
A[MouseClick Event Triggered] --> B{Is Left Button?}
B -- No --> Z[Ignore]
B -- Yes --> C[Extract X,Y Coordinates]
C --> D[Convert to Row,Col Index]
D --> E{Within Board Bounds?}
E -- No --> Z
E -- Yes --> F{Game Over?}
F -- Yes --> Z
F -- No --> G{Position Empty?}
G -- No --> Z
G -- Yes --> H[Call MakeMove]
H --> I[Update Board State]
I --> J[Refresh UI]
每一个判断都是系统的“守门员”,守住稳定性底线。
🤖 AI 对战:给游戏一颗会思考的大脑
没有 AI 的五子棋,就像没有对手的拳击手。我们来加个智能引擎,让它不仅能下,还能“算计”。
🌲 Minimax 算法:AI 的决策树
Minimax 是博弈类 AI 的经典算法。它的思路很朴素: 我走一步,假设对手走最优,然后我再走最优……一直推演若干步,选最终评分最高的那个开局走法 。
简化实现如下:
public (int r, int c) GetBestMove(Board board, Player ai)
{
int bestScore = int.MinValue;
(int r, int c) bestMove = (-1, -1);
foreach (var move in board.GetAvailableMoves())
{
board.MakeMove(move.r, move.c, ai);
int score = Minimax(board, 4, false, ai, int.MinValue, int.MaxValue);
board.UndoMove(move.r, move.c);
if (score > bestScore)
{
bestScore = score;
bestMove = move;
}
}
return bestMove;
}
递归部分包含 Alpha-Beta 剪枝,大幅提升效率:
private int Minimax(Board board, int depth, bool isMax, Player ai, int alpha, int beta)
{
if (depth == 0 || board.IsGameOver())
return Evaluate(board, ai);
if (isMax)
{
int maxEval = int.MinValue;
foreach (var m in board.GetMoves())
{
board.MakeMove(m, ai);
int eval = Minimax(board, depth-1, false, ai, alpha, beta);
board.UndoMove(m);
maxEval = Math.Max(maxEval, eval);
alpha = Math.Max(alpha, eval);
if (beta <= alpha) break; // 剪枝
}
return maxEval;
}
else
{
// 对手回合,取最小值
}
}
💡 提示:搜索深度
DEPTH=4是平衡点,更深虽更强但更慢。
🧮 评估函数:教AI“看局势”
光会算步不行,还得懂“形势”。我们定义几种关键棋型并打分:
| 棋型 | 分数 | 示例 |
|---|---|---|
| 五连珠 | 100000 | 🟡🟡🟡🟡🟡 |
| 活四 | 50000 | ○🟡🟡🟡○ |
| 冲四 | 10000 | ●🟡🟡🟡○ |
| 活三 | 5000 | ○🟡🟡○ |
| 眠三 | 800 | ●🟡🟡○ |
可以用位运算加速检测,比如将一行转为64位整数,用掩码匹配:
long LIVE_FOUR_MASK = 0b_011110L;
bool isLiveFour = (line & LIVE_FOUR_MASK) == LIVE_FOUR_MASK;
效率比逐个循环判断高出一个数量级!
🧩 完整项目结构:模块化才是王道
最后来看看整个项目的组织方式:
GomokuGame/
├── Program.cs
├── Models/
│ ├── Board.cs
│ ├── Player.cs
│ └── MoveRecord.cs
├── AI/
│ ├── AIPlayer.cs
│ └── EvaluationEngine.cs
├── Forms/
│ ├── MainForm.cs
│ └── GamePanel.cs
├── Managers/
│ └── GameManager.cs
└── Utilities/
├── Serializer.cs
└── Logger.cs
每一层各司其职,新人接手也能快速定位代码。配合单元测试,维护成本大大降低。
🔚 结语:从小游戏到大思维
你以为我们只是做了一个五子棋?No no no 🙅♂️
我们实际上演练了一整套 现代软件开发的核心范式 :
- 基础语法 → 构建最小可用单元
- 面向对象 → 实现高内聚低耦合
- 事件驱动 → 解耦逻辑与界面
- 设计模式 → 提升可扩展性
- AI算法 → 探索智能化可能
这套思维不仅能用来做游戏,还能迁移到 Web 开发、移动应用、后台服务……任何复杂的系统设计中。
所以啊,下次有人说“我会写 HelloWorld”,你可以微微一笑:“那你会写一盘五子棋吗?” 😉
简介:五子棋是一款经典两人对弈游戏,规则简洁但富有策略性,是学习算法设计、图形界面开发与事件驱动编程的理想实践项目。本C#五子棋源程序涵盖Windows Forms界面构建、二维数组棋盘管理、棋子落子逻辑、连珠判断、胜负判定及基础AI对战功能,适合C#初学者深入理解面向对象编程与GUI应用开发。通过该项目的学习与实践,开发者可全面掌握桌面应用程序的开发流程与核心机制,提升综合编程能力。
更多推荐



所有评论(0)