本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:【C#七巧板游戏源码】是一个基于C#语言的图形化益智游戏项目,旨在帮助初学者掌握C#在GUI编程、事件处理、图形绘制与游戏逻辑实现方面的核心技能。该游戏模拟传统七巧板拼图玩法,通过拖拽、旋转七个几何块完成指定图案,涵盖Windows Forms界面设计、Graphics绘图技术、对象封装、用户交互处理及基础算法应用。本项目经过完整实现,适合作为C#学习者从基础语法到综合项目实践的过渡案例,助力开发者提升面向对象编程能力和游戏开发思维。

C#七巧板游戏开发:从语法到算法的全栈实现

在当今这个图形化应用无处不在的时代,我们似乎已经习惯了那些流畅动画、绚丽特效的现代UI。但你有没有想过,一个看似简单的拼图游戏——比如童年记忆里的七巧板——背后究竟藏着多少技术细节?🤔

别小看这七个几何块!它们可不只是“拖来拖去”那么简单。要让这些三角形、正方形和菱形真正“活起来”,你需要一套完整的编程思维体系:从最基础的变量定义,到面向对象设计,再到事件驱动机制与复杂求解算法……整个过程就像一场代码世界的“智力闯关”。

而这,正是C#语言的魅力所在——它既能让你写出清晰易懂的基础逻辑,又能支撑起完整的游戏架构。今天,咱们就一起动手,用C#打造一款 可交互、能自动求解、界面美观 的七巧板小游戏。准备好了吗?🎮✨


一、起点:变量与数据类型的智慧选择

所有伟大的程序都始于一行声明。

想象一下你要描述一个拼图块的状态。它在哪?转了多少度?有没有被选中?这些问题的答案,其实就是一组状态变量。而在C#中,我们要做的第一件事就是为这些状态找到最合适的数据类型。

int x, y;               // 拼图块的位置坐标(整数就够了)
double angle;           // 旋转角度(浮点更精确)
bool isSelected;        // 是否被鼠标选中(布尔值最直观)

是不是很简单?但这背后其实是一次 工程决策

  • 为什么位置用 int 而不用 float ?因为屏幕像素是离散的,没必要搞得太精细;
  • 为什么角度用 double ?旋转计算涉及三角函数,精度越高越不容易累积误差;
  • bool 类型则完美表达了“开关”状态,比用0/1或字符串判断更安全高效。

接着,我们需要管理多个拼图块。这时候集合就派上用场了:

List<TangramPiece> pieces = new List<TangramPiece>();

通过 for 循环初始化七种标准形状,并存入列表:

for (int i = 0; i < 7; i++)
{
    var piece = CreatePieceByIndex(i);
    pieces.Add(piece);
}

用户操作呢?鼠标点击判断用 if-else 分支:

if (IsPointInPolygon(mousePos, piece.Vertices))
{
    piece.IsSelected = true;
}

键盘命令处理用 switch-case

switch (e.KeyCode)
{
    case Keys.R:
        selectedPiece.Rotate(Math.PI / 4); break;
    case Keys.D:
        selectedPiece.Reset(); break;
}

甚至还可以读取预设布局配置文件,使用字符串解析技术加载初始状态:

string configLine = "LargeTriangle|300,400|1.57"; // 类型|位置|角度
string[] parts = configLine.Split('|');

看到没?短短几行代码里,已经涵盖了 变量声明、集合操作、条件判断、循环控制、字符串处理 等核心语法要素。它们不是孤立的知识点,而是构建真实项目的“积木块”。

💡 小贴士:好的命名能让代码自解释。 x/y 不如 position.X/position.Y 直观; flag1 远不如 isSelected 易懂。


二、进阶:把拼图块变成“有生命的对象”

如果你还停留在“一堆变量+一堆方法”的阶段,那你的代码很快就会变得像打结的耳机线一样难理清。真正的高手,懂得如何 封装

把拼图块抽象成类

每个拼图块都不只是几个坐标的组合,它是有“个性”的:知道自己长什么样、现在在哪、朝哪个方向转、是什么颜色……

于是我们创建一个 TangramPiece 类:

public class TangramPiece
{
    private Point[] _originalVertices;   // 原始顶点(不变形)
    private Point[] _vertices;          // 当前顶点(经过变换)
    private float _rotationAngle;
    private Point _position;
    private bool _isSelected;
    private Color _fillColor;
    private string _pieceType;

    public Point Position 
    { 
        get => _position; 
        set 
        { 
            _position = value; 
            UpdateTransformedVertices(); 
        } 
    }

    public float RotationAngle 
    { 
        get => _rotationAngle; 
        set 
        { 
            _rotationAngle = value; 
            UpdateTransformedVertices(); 
        } 
    }

    // 其他属性略...
}

注意到没有?每次修改位置或角度,都会自动触发 UpdateTransformedVertices() 方法重新计算顶点。这就是所谓的“副作用可控”——外部只需关心“我要移动”,内部自己搞定“怎么重绘”。

这种设计不仅减少了出错概率,也让调试更容易:只要改了状态,画面一定更新。

构造函数决定“出生方式”

不同的拼图块有不同的尺寸比例。两个大三角、一个小三角、一个平行四边形……它们的原始顶点当然不一样。

所以我们在构造函数里根据类型生成对应的形状:

public TangramPiece(string pieceType, Point startPos, Color color)
{
    _pieceType = pieceType;
    _position = startPos;
    _fillColor = color;
    _rotationAngle = 0f;

    switch (pieceType)
    {
        case "LargeTriangle":
            _originalVertices = new[]
            {
                new Point(0, 0),
                new Point(80, 0),
                new Point(0, 80)
            };
            break;
        case "Square":
            _originalVertices = new[]
            {
                new Point(0, 0),
                new Point(40, 0),
                new Point(40, 40),
                new Point(0, 40)
            };
            break;
        // 更多样式省略...
        default:
            throw new ArgumentException("无效的拼图类型!");
    }

    _vertices = (Point[])_originalVertices.Clone();
    UpdateTransformedVertices();
}

这里有个关键技巧:我们保存了 _originalVertices ,而不是直接拿它当当前顶点。这样无论你怎么旋转缩放,都能基于原始形状重新计算,避免浮点运算带来的累积变形误差。

🧠 经验之谈:图形变换中最怕“越变越歪”。记住一句话:“ 永远从原图出发做变换 ”。

行为封装:让它动起来!

光有静态属性还不够,拼图块得能响应用户的操作才行。

我们给它加上几个公共方法:

public void Move(int dx, int dy)
{
    _position.X += dx;
    _position.Y += dy;
    UpdateTransformedVertices();
}

public void Rotate(float radians)
{
    _rotationAngle += radians;
    UpdateTransformedVertices();
}

public void Reset()
{
    _position = new Point(0, 0);
    _rotationAngle = 0f;
    UpdateTransformedVertices();
}

这些方法看起来简单,但意义重大:它们构成了 行为接口 。将来无论是鼠标拖动、键盘控制还是AI自动摆放,都可以通过调用这几个方法完成,完全不需要知道内部是怎么算的。

再来看核心的顶点变换逻辑:

private void UpdateTransformedVertices()
{
    double cos = Math.Cos(_rotationAngle);
    double sin = Math.Sin(_rotationAngle);
    Point center = ComputeCenter(_originalVertices);

    for (int i = 0; i < _originalVertices.Length; i++)
    {
        int x = _originalVertices[i].X - center.X;
        int y = _originalVertices[i].Y - center.Y;

        _vertices[i] = new Point(
            _position.X + (int)(x * cos - y * sin),
            _position.Y + (int)(x * sin + y * cos)
        );
    }
}

公式很数学,但我们不妨把它拆解成自然语言理解:

  1. 找到图形中心;
  2. 把每个点相对于中心偏移;
  3. 应用旋转矩阵;
  4. 加上整体位置,得到最终屏幕坐标。

🧠 流程图来帮你理清思路:

graph TD
    A[开始] --> B[获取原始顶点]
    B --> C[计算局部中心]
    C --> D[对每个顶点: 减去中心]
    D --> E[应用旋转变换]
    E --> F[加上屏幕位置]
    F --> G[得到最终顶点]
    G --> H[结束]

这样一来,哪怕你不懂数学公式,也能看懂每一步在干什么。这才是高质量代码该有的样子!


三、高阶玩法:继承与多态让系统更具扩展性

假设有一天你想加个新功能:五巧板、十巧板、甚至自定义拼图?如果所有逻辑都塞在一个 TangramPiece 类里,那维护起来简直是噩梦。

聪明的做法是引入 类层次结构

定义基类,提取共性

我们先做一个抽象基类 Piece

public abstract class Piece
{
    protected Point Position { get; set; }
    protected float RotationAngle { get; set; }
    protected Color FillColor { get; set; }
    protected bool IsSelected { get; set; }

    public abstract void Draw(Graphics g);
    public abstract void Rotate(float radians);
    public virtual void Move(int dx, int dy)
    {
        Position.X += dx;
        Position.Y += dy;
    }
    public abstract Rectangle GetBounds();
}

然后让各种具体图形继承它:

public class TrianglePiece : Piece { /* 实现特定逻辑 */ }
public class SquarePiece : Piece     { /* 实现特定逻辑 */ }
public class ParallelogramPiece : Piece { /* 实现特定逻辑 */ }

现在主程序只需要维护一个统一的集合:

private List<Piece> _pieces = new List<Piece>();

// 添加不同类型的拼图块
_pieces.Add(new TrianglePiece(...));
_pieces.Add(new SquarePiece(...));

绘图时也无需区分类型:

foreach (var piece in _pieces)
{
    piece.Draw(e.Graphics); // 自动调用对应子类的方法!
}

这就是 多态 的力量:同一句代码,执行的是不同的行为。

子类名称 特殊属性 是否重写Draw 多态优势
TrianglePiece 等腰/直角标记 统一调用Draw实现差异渲染
SquarePiece 边长 支持按需优化纹理填充
Parallelogram 斜边倾斜角度 可加入剪切变换支持动态变形

你会发现,新增一种拼图类型根本不需要改动主流程,只要实现对应的子类即可。👏

差异化渲染提升视觉体验

虽然GDI+提供了通用的 FillPolygon DrawPolygon ,但我们可以在子类中玩点花样。

比如给大三角加个渐变色:

public override void Draw(Graphics g)
{
    using (var brush = new LinearGradientBrush(
        new Point(0, 0), new Point(80, 80), Color.Red, Color.DarkRed))
    using (var pen = new Pen(Color.Gold, 3) { DashStyle = DashStyle.Dash })
    {
        g.FillPolygon(brush, _vertices);
        g.DrawPolygon(pen, _vertices);
    }
}
  • 渐变填充让图形更有立体感;
  • 金色虚线边框突出重点拼图块;
  • 所有资源都在 using 块中释放,防止内存泄漏。

而普通的小三角可能就保持简洁风格:

g.FillPolygon(Brushes.Blue, _vertices);
g.DrawPolygon(Pens.Black, _vertices);

这样,玩家一眼就能分辨出哪些是关键部件。


四、画布上的艺术:GDI+绘图实战技巧

Windows Forms 虽然是老牌框架,但它的 GDI+ 绘图能力依然强大。只不过有个问题—— 频繁重绘会导致闪烁

不信你试试快速拖动拼图块,画面可能会“抖”得很厉害。怎么办?

双缓冲技术拯救视觉体验

解决办法就是开启双缓冲:

this.SetStyle(
    ControlStyles.AllPaintingInWmPaint |
    ControlStyles.UserPaint |
    ControlStyles.DoubleBuffer,
    true);

或者更高级的方式,在自定义控件中启用复合窗口样式:

protected override CreateParams CreateParams
{
    get
    {
        var cp = base.CreateParams;
        cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
        return cp;
    }
}

效果对比如下:

技术 CPU占用 画面流畅性 开发复杂度
无缓冲 差(明显闪烁) 简单
双缓冲 中等
WS_EX_COMPOSITED 简单

推荐优先使用 SetStyle 方式,简单有效。

在Paint事件中绘制一切

所有自定义绘图都应在 Paint 事件中进行:

private void canvasPanel_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;

    foreach (var piece in _pieces)
    {
        piece.Draw(g);
    }
}

注意几点:

  • Graphics 对象由系统提供,不能手动 new
  • 必须在 Paint 中调用,否则可能无效;
  • 若想强制刷新画面,调用 Invalidate() 即可触发重绘。

精细化控制线条与填充

除了基本的绘制,我们还能通过 Pen Brush 控制细节:

using (var pen = new Pen(Color.DeepPink, 3))
{
    pen.DashStyle = DashStyle.Dot;
    pen.EndCap = LineCap.RoundAnchor;
    g.DrawPolygon(pen, points);
}

参数说明:

  • Color : 线条颜色;
  • Width : 线宽(像素);
  • DashStyle : 虚线模式;
  • EndCap : 端点形状, RoundAnchor 提供圆形终点。

填充也可以多样化:

// 渐变填充
using (var brush = new PathGradientBrush(points))
{
    brush.CenterColor = Color.White;
    brush.SurroundColors = new[] { Color.Gray };
    g.FillPolygon(brush, points);
}

📊 各类填充方式使用占比示意:

pie
    title 填充方式使用占比
    “SolidBrush” : 50
    “LinearGradientBrush” : 30
    “HatchBrush” : 10
    “TextureBrush” : 10

合理运用这些工具,不仅能提升美感,还能传达状态信息——比如选中的拼图块用红色描边,错误放置的显示阴影警告等等。


五、事件驱动:让拼图“听你的话”

再漂亮的图形,没有交互也只是张静态图。而 Windows Forms 的精髓就在于它的 事件驱动模型

主窗体生命周期管理

Form 的生命周期有几个关键节点:

  • 构造函数:初始化组件;
  • Load 事件:加载资源、创建对象、绑定事件;
  • Closing :清理资源;
  • Disposed :彻底销毁。

我们通常在 Load 事件里做初始化:

private void OnFormLoad(object sender, EventArgs e)
{
    InitializePieces();
    SetupEventHandlers();
    this.DoubleBuffered = true;
}

这样做有两个好处:

  1. 避免在构造函数中执行耗时操作,影响启动速度;
  2. 确保控件已完全创建后再访问其属性。

PictureBox:专用绘图层

虽然可以直接在 Form 上绘图,但更好的做法是使用 PictureBox 作为独立画布:

canvasBox = new PictureBox
{
    Dock = DockStyle.Fill,
    SizeMode = PictureBoxSizeMode.Normal,
    BackColor = Color.LightGray
};

canvasBox.Paint += OnCanvasPaint;
canvasBox.MouseDown += OnCanvasMouseDown;
canvasBox.MouseMove += OnCanvasMouseMove;
canvasBox.MouseUp += OnCanvasMouseUp;

this.Controls.Add(canvasBox);

优点包括:

  • 独立的事件隔离;
  • 支持精确的命中测试;
  • 易于调整大小和布局。

功能按钮绑定操作

常见的功能按钮如“重置”、“提示”、“撤销”等,只需绑定 Click 事件:

btnReset.Click += (s, e) => ResetGame();
btnHint.Click += (s, e) => ShowSolutionHint();

其中 ResetGame() 可以这样实现:

private void ResetGame()
{
    foreach (var piece in pieces)
        piece.Reset();

    canvasBox.Invalidate(); // 触发重绘
}

简洁明了,且易于扩展新功能。


六、拖拽交互:丝滑手感的秘密

拼图的核心玩法就是拖动。这看似简单,实则暗藏玄机。

MouseDown:选中目标

private void OnCanvasMouseDown(object sender, MouseEventArgs e)
{
    if (e.Button != MouseButtons.Left) return;

    for (int i = pieces.Count - 1; i >= 0; i--)
    {
        if (pieces[i].ContainsPoint(e.Location))
        {
            selectedPiece = pieces[i];
            dragOffset = new Point(e.X - piece.Position.X, e.Y - piece.Position.Y);
            BringToFront(piece); // 提升层级
            break;
        }
    }
}

关键点:

  • 逆序遍历 :确保顶层拼图块优先被选中;
  • ContainsPoint() 使用射线法判断点是否在多边形内;
  • dragOffset 记录相对偏移,避免拖动跳跃。

MouseMove:实时更新

private void OnCanvasMouseMove(object sender, MouseEventArgs e)
{
    if (selectedPiece == null || e.Button != MouseButtons.Left) return;

    selectedPiece.Position = new Point(e.X - dragOffset.X, e.Y - dragOffset.Y);
    canvasBox.Invalidate();
}

每帧只更新一个对象,配合双缓冲,轻松实现60FPS流畅拖动。

MouseUp:自动吸附与校验

松开鼠标后可以加入智能吸附:

private void OnCanvasMouseUp(object sender, MouseEventArgs e)
{
    if (selectedPiece == null) return;

    var center = selectedPiece.GetCenter();
    if (targetZone.Contains(center))
    {
        selectedPiece.SnapTo(targetZone);
        CheckPuzzleCompletion();
    }

    selectedPiece = null;
    canvasBox.Invalidate();
}

🎯 拖拽状态机清晰表达交互逻辑:

stateDiagram-v2
    [*] --> Idle
    Idle --> Dragging: MouseDown + HitTest Success
    Dragging --> Idle: MouseUp
    Dragging --> Dragging: MouseMove
    Idle --> Idle: MouseMove (No Selection)

七、键盘辅助与撤销机制

除了鼠标,键盘快捷键也很重要,尤其是 Ctrl+Z 撤销操作。

我们可以重写 ProcessCmdKey 来捕获全局按键:

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    switch (keyData)
    {
        case Keys.Control | Keys.Z:
            UndoLastMove(); return true;
        case Keys.Control | Keys.Y:
            RedoLastMove(); return true;
        default:
            return base.ProcessCmdKey(ref msg, keyData);
    }
}

为了支持撤销,需要用栈保存历史状态:

private Stack<TangramGameState> undoStack = new();
private Stack<TangramGameState> redoStack = new();

public class TangramGameState
{
    public List<PieceSnapshot> Snapshots { get; }

    public TangramGameState(List<TangramPiece> pieces)
    {
        Snapshots = pieces.Select(p => new PieceSnapshot(p)).ToList();
    }

    public void Apply(List<TangramPiece> pieces)
    {
        for (int i = 0; i < pieces.Count; i++)
            pieces[i].Restore(Snapshots[i]);
    }
}

每次操作前 SaveState() ,撤销时弹出并恢复,就这么简单!


八、自动求解:AI来帮你破关

你以为这就完了?不,我们的目标是做一个 既能玩又能解 的智能七巧板!

回溯法求解器

将目标区域划分为网格:

public class GridCell
{
    public bool IsOccupied { get; set; }
    public int PieceId { get; set; } = -1;
}

GridCell[,] grid = new GridCell[20, 20];

每种拼图块在不同旋转下生成模板:

public class PieceTemplate
{
    public List<(int dx, int dy)> ShapeOffsets { get; set; }
}

递归尝试所有可能性:

bool Solve(int pieceIndex)
{
    if (pieceIndex >= pieces.Count) return true;

    foreach (var template in GetRotatedTemplates(piece))
    {
        foreach (var pos in FindValidPositions(template))
        {
            if (CanPlace(template, pos.x, pos.y))
            {
                PlacePiece(...);
                if (Solve(pieceIndex + 1)) return true;
                RemovePiece(...); // 回溯
            }
        }
    }
    return false;
}

🧠 求解流程图:

graph TD
    A[开始求解] --> B{所有拼图块已放置?}
    B -->|是| C[返回成功]
    B -->|否| D[选取下一个拼图块]
    D --> E[枚举旋转状态]
    E --> F[计算模板偏移]
    F --> G[查找合法起始位置]
    G --> H{能否放置?}
    H -->|否| I[尝试下一位置]
    H -->|是| J[标记网格占用]
    J --> K[递归求解剩余拼图块]
    K --> L{成功?}
    L -->|是| M[返回真]
    L -->|否| N[释放网格占用]
    N --> I

性能优化策略

  • 按面积降序排序 :先放大的,剪枝更快;
  • 包围盒预判 :快速排除不可能情况;
  • 空洞检测 :若出现无法填满的小洞,立即回溯;
  • 位运算加速 :用 ulong 表示一行网格,提升匹配效率。

结合这些技巧,原本需要几小时穷举的问题,现在几秒就能出结果!


九、项目结构与发布部署

最后,别忘了良好的组织结构:

TangramGame/
├── UI/
│   ├── MainForm.cs
│   └── GamePanel.cs
├── Logic/
│   ├── Solver/
│   ├── Pieces/
│   └── GameSession.cs
├── Data/
│   ├── Levels/
│   └── Images/
├── Utilities/
│   └── GeometryHelper.cs
└── Program.cs

发布时使用 Visual Studio 的“发布向导”,勾选“单文件打包”,一键生成 .exe

加上全局异常捕获,保证稳定性:

Application.ThreadException += (s, e) =>
{
    MessageBox.Show($"发生未处理异常:{e.Exception.Message}");
};

结语:从小游戏看大世界

你看,一个小小的七巧板游戏,竟然牵扯出这么多知识点:

  • 基础语法 → 面向对象 → 图形绘制 → 事件处理 → 算法设计 → 内存管理 → 项目结构

它像一座桥梁,连接着编程初学者与资深开发者之间的鸿沟。🌈

更重要的是,这个过程教会我们一件事: 优秀的软件,从来都不是堆砌功能,而是层层递进的设计哲学

下次当你看到某个“简单”的应用时,不妨问一句:它的背后,藏着多少看不见的努力?😉


🚀 动手建议
- 先实现基本拖拽功能;
- 再加入旋转和重置;
- 最后挑战自动求解模块;
- 发朋友圈晒成果,收获点赞无数!😎

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:【C#七巧板游戏源码】是一个基于C#语言的图形化益智游戏项目,旨在帮助初学者掌握C#在GUI编程、事件处理、图形绘制与游戏逻辑实现方面的核心技能。该游戏模拟传统七巧板拼图玩法,通过拖拽、旋转七个几何块完成指定图案,涵盖Windows Forms界面设计、Graphics绘图技术、对象封装、用户交互处理及基础算法应用。本项目经过完整实现,适合作为C#学习者从基础语法到综合项目实践的过渡案例,助力开发者提升面向对象编程能力和游戏开发思维。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐