用C#开发五子棋游戏是个特别适合初学者的项目,既能学习窗体程序开发,又能理解游戏逻辑的实现。我刚开始学C#时就做过这个项目,当时踩了不少坑,现在把经验都整理出来分享给大家。
首先需要准备Visual Studio开发环境,社区版就完全够用。新建一个Windows窗体应用项目,我习惯用.NET Framework 4.7.2版本,兼容性比较好。项目创建好后,你会看到一个默认的Form1窗体,这就是我们的主战场。
游戏开发离不开几个核心组件:
建议先把窗体大小设为710×640像素,这个尺寸放15×15的棋盘正合适。我在MainSize类里定义了这些常量,后面修改起来特别方便:
csharp复制class MainSize {
public static int FormWidth => 710;
public static int FormHeight => 640;
public static int CBoardWidth => 600;
// 其他尺寸定义...
}
棋盘是五子棋的脸面,画得好游戏体验直接提升一个档次。我试过两种绘制方式:纯代码绘制和使用棋盘图片。新手建议先用代码绘制,理解原理后再换图片。
用Graphics画棋盘时,关键是要计算好每个格子的间距。我设置每个格子宽40像素,这样15×15的棋盘总共需要600×600像素的空间。画线时要注意:
csharp复制Pen pen = new Pen(Color.FromArgb(192, 166, 107)); // 木质颜色
for(int i=0; i<15; i++){
// 画横线
g.DrawLine(pen, 20, i*40+20, 580, i*40+20);
// 画竖线
g.DrawLine(pen, i*40+20, 20, i*40+20, 580);
}
后来我改用棋盘图片后发现视觉效果更好,特别是加了木质纹理后。这里有个小技巧:使用DrawImage方法时,要处理好图片的拉伸问题。我通过设置三个定位点来保持图片比例:
csharp复制Bitmap boardImg = new Bitmap("board.png");
g.DrawImage(boardImg,
new Point[] {
new Point(0,0),
new Point(600,0),
new Point(0,600)
});
棋子是游戏的核心交互元素,我花了最多时间优化这部分。最开始画的棋子就是个纯色圆,看起来特别假。后来加了渐变效果和抗锯齿处理,质感立马就上来了。
每个棋子的直径设为37像素,比格子间距小3像素,这样看起来比较舒服。黑子用深灰到黑色的线性渐变,白子用白到浅灰的渐变:
csharp复制// 黑子渐变
var blackBrush = new LinearGradientBrush(
new Point(20,0), new Point(20,40),
Color.FromArgb(122,122,122), Color.Black);
// 白子渐变
var whiteBrush = new LinearGradientBrush(
new Point(20,0), new Point(20,40),
Color.White, Color.FromArgb(204,204,204));
抗锯齿处理是关键,不加的话棋子边缘会有明显锯齿。设置这两个属性就能解决:
csharp复制g.SmoothingMode = SmoothingMode.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
鼠标点击处理要注意坐标转换。点击位置需要转换为棋盘坐标:
csharp复制int x = e.X / 40; // 40是格子宽度
int y = e.Y / 40;
if(CheckBoard[x,y] == 0){ // 该位置无棋子
// 落子逻辑...
}
游戏状态管理我用了个二维数组CheckBoard[15,15],0表示空位,1黑子,2白子。每次落子后都要更新这个数组,并检查是否获胜。
胜负判断是最烧脑的部分。需要检查四个方向:水平、垂直、左上到右下、右上到左下。我的实现方式是遍历棋盘,对每个棋子检查四个方向是否有五连珠:
csharp复制bool CheckWin(int[,] board, int player){
for(int i=0; i<15; i++){
for(int j=0; j<15; j++){
if(board[j,i] == player){
// 检查右侧
if(j<11 && board[j+1,i]==player && ...)
return true;
// 检查下方
if(i<11 && board[j,i+1]==player && ...)
return true;
// 检查右下
if(j<11 && i<11 && board[j+1,i+1]==player && ...)
return true;
// 检查左下
if(j>3 && i<11 && board[j-1,i+1]==player && ...)
return true;
}
}
}
return false;
}
游戏流程控制我用了几个状态变量:
基本的游戏功能实现后,我开始琢磨怎么让界面更友好。首先是加了游戏状态提示,用Label显示当前回合:
csharp复制label1.Text = ChessCheck ? "黑子回合" : "白子回合";
然后是重新开始确认对话框,避免误操作:
csharp复制if(MessageBox.Show("确认重新开始?","提示",MessageBoxButtons.OKCancel)
== DialogResult.OK)
{
InitializeGame();
}
我还在Paint事件中加了棋子重绘逻辑,这样窗口最小化再恢复时,棋子不会消失:
csharp复制private void panel1_Paint(object sender, PaintEventArgs e){
ChessBoard.DrawCB(e.Graphics);
Chess.ReDrawC(panel1, CheckBoard);
}
最后给棋子落子加了音效,使用System.Media命名空间:
csharp复制SoundPlayer player = new SoundPlayer("drop.wav");
player.Play();
开发过程中我遇到几个典型问题,这里分享下解决方案:
棋子显示不全:因为计算棋子中心坐标时少减了半径值。正确的计算方式应该是:
csharp复制int centerX = x * 40 + 20 - 18; // 20是偏移,18是半径
数组越界异常:鼠标点击棋盘边缘时会发生。解决方法是在MouseDown事件中加try-catch块。
画面闪烁问题:在窗体构造函数中设置这两个属性:
csharp复制this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint, true);
内存泄漏:记得释放Graphics对象和Brush资源:
csharp复制using(var g = panel1.CreateGraphics()){
// 绘图操作...
}
性能优化方面,我有几个心得:
基础版本完成后,可以尝试这些扩展功能:
悔棋功能:用Stack保存每一步的棋盘状态:
csharp复制Stack<int[,]> history = new Stack<int[,]>();
// 落子前保存状态
history.Push((int[,])CheckBoard.Clone());
游戏回放:记录每一步的坐标和玩家,用Timer控制回放速度。
AI对战:实现简单的评分算法,电脑会根据当前局面选择最优落子点。
网络对战:用Socket实现双人对战,需要设计简单的通信协议。
皮肤系统:允许玩家自定义棋盘和棋子样式,通过配置文件加载资源。
我后来给棋子加了阴影效果,使用PathGradientBrush:
csharp复制var path = new GraphicsPath();
path.AddEllipse(rect);
var pgb = new PathGradientBrush(path);
pgb.CenterColor = Color.FromArgb(100,0,0,0);
pgb.SurroundColors = new Color[]{Color.Transparent};
g.FillEllipse(pgb, rect);
开发完成后,可以通过ClickOnce发布应用程序。在项目属性→发布中设置:
也可以生成安装包:
记得测试不同分辨率下的显示效果,我在MainSize类里定义的尺寸在不同DPI下可能需要调整:
csharp复制// 根据DPI缩放
float scale = panel1.CreateGraphics().DpiX / 96f;
int actualSize = (int)(CBoardGap * scale);
开发过程中我积累了一些实用技巧: