WinformGDI+ entry-level example - minesweeper game (with source code)

the whole idea:

The game interface of minesweeper made me think of two-dimensional arrays from the beginning. In fact, using two-dimensional arrays to define game data is indeed the most human way of thinking. (Square class will be explained later)

//游戏数据
private readonly Square[,] _gameData;

With this beginning, the next step is to fill the data of the two-dimensional array. For the data, my initial idea is to use int or enumeration. Of course, this is feasible, but there is a problem involved in high coupling, all operations will be in High-level implementation, difficult to maintain.

So we use a Square class to represent a small square area.

/// <summary>
/// 表示游戏中一个方块区
/// </summary>
public sealed class Square
...

The state of the block area is represented as an enumeration:

/// <summary>
/// 方块区状态
/// </summary>
public enum SquareStatus
{
    /// <summary>
    /// 闲置
    /// </summary>
    Idle,
    /// <summary>
    /// 已打开
    /// </summary>
    Opened,
    /// <summary>
    /// 已标记
    /// </summary>
    Marked,
    /// <summary>
    /// 已质疑
    /// </summary>
    Queried,
    /// <summary>
    /// 游戏结束
    /// </summary>
    GameOver,
    /// <summary>
    /// 标记失误(仅在游戏结束时用于绘制)
    /// </summary>
    MarkMissed
}

The Game class is used to represent a game, which includes game data, game level, number of minefields, and minelaying methods.

/// <summary>
/// 游戏对象
/// </summary>
public sealed class Game
...

Difficulties to break:

The game is not big, and there are not many difficulties involved, but for readers who are new to GDI+, some places are still more troublesome.

Logic Difficulty 1: Bray

The minesweeper game has an additional rule, that is, the first click will not step on the minefield no matter what. Because of this rule, we cannot make the minelaying operation before the first click. So we start the game assuming that all squares are free of mines.

/// <summary>
/// 开始游戏
/// </summary>
public void Start()
{
    //假设所有方块区均非雷区
    for (int i = 0; i < _gameData.GetLength(0); i++)
        for (int j = 0; j < _gameData.GetLength(1); j++)
            _gameData[i, j] = new Square(new Point(i, j), false, 0);
}

Then, Bray on the first click after opening.

/// <summary>
/// 布雷
/// </summary>
/// <param name="startPt">首次单击点</param>
private void Mine(Point startPt)
{
    Size area = new Size(_gameData.GetLength(0), _gameData.GetLength(1));
    List<Point> excluded = new List<Point> { startPt };

    //随机创建雷区
    for (int i = 0; i < _minesCount; i++)
    {
        Point pt = GetRandomPoint(area, excluded);
        _gameData[pt.X, pt.Y] = new Square(pt, true, 0);
        excluded.Add(pt);
    }

    //创建非雷区
    for (int i = 0; i < _gameData.GetLength(0); i++)
        for (int j = 0; j < _gameData.GetLength(1); j++)
            if (!_gameData[i, j].Mined)//非雷区
            {
                int minesAround = EnumSquaresAround(new Point(i, j)).Cast<Square>().Count(square => square.Mined);//周围雷数

                _gameData[i, j] = new Square(new Point(i, j), false, minesAround);
            }

    _gameStarted = true;
}

Create a minefield first, and then create a non-minefield, so that when we create a non-minefield, we can calculate the number of mines around the non-minefield. To enumerate the surrounding squares, we use yield to create an enumerator.

/// <summary>
/// 枚举周围所有方块区
/// </summary>
/// <param name="squarePt">原方块区</param>
/// <returns>枚举数</returns>
private IEnumerable EnumSquaresAround(Point squarePt)
{
    int i = squarePt.X, j = squarePt.Y;

    //周围所有方块区
    for (int x = i - 1; x <= i + 1; ++x)//横向
    {
        if (x < 0 || x >= _gameData.GetLength(0))//越界
            continue;

        for (int y = j - 1; y <= j + 1; ++y)//纵向
        {
            if (y < 0 || y >= _gameData.GetLength(1))//越界
                continue;

            if (x == squarePt.X && y == squarePt.Y)//排除自身
                continue;

            yield return _gameData[x, y];
        }
    }
}

Logical difficulty 2: When there is no minefield (blank) around the click area, automatically open all surrounding non-minefields in batches

//如果是空白区,则递归相邻的所有空白区
if (_gameData[logicalPt.X, logicalPt.Y].MinesAround == 0)
    AutoOpenAround(logicalPt);
/// <summary>
/// 自动打开周围非雷区方块(递归)
/// </summary>
/// <param name="squarePt">原方块逻辑坐标</param>
private void AutoOpenAround(Point squarePt)
{
    //遍历周围方块
    foreach (Square square in EnumSquaresAround(squarePt))
    {
        if (square.Mined || square.Status == Square.SquareStatus.Marked || square.Status == Square.SquareStatus.Opened)
            continue;

        square.LeftClick();//打开
        //周围无雷区
        if (square.MinesAround == 0)
            AutoOpenAround(square.Location);//递归打开
    }
}

Drawing Difficulty 1: Double Buffering to Overcome Flicker

From the structure of the two-dimensional array, we need to traverse the entire two-dimensional array, and then draw each Square to the winform, but this will cause a strong flickering effect. Because it is real-time drawing, each step of drawing will be displayed on the window in real time, so the effect we see is that a block area appears on the window.

In order to overcome this unfriendly flicker, double buffering appeared. The idea is to create a buffer (usually a bitmap in memory), first draw all the square areas to this bitmap, and after the drawing is complete, put the bitmap The image is pasted on the form, and the final effect will no longer flicker.

//窗口图面
private readonly Graphics _wndGraphics;
//缓冲区
private readonly Bitmap _buffer;
//缓冲区图面
private readonly Graphics _bufferGraphics;
/// <summary>
/// 绘制一帧
/// </summary>
public void Draw()
{
    for (int i = 0; i < _gameData.GetLength(0); i++)
        for (int j = 0; j < _gameData.GetLength(1); j++)
            _gameData[i, j].Draw(_bufferGraphics);

    _wndGraphics.DrawImage(_buffer, new Point(_gameFieldOffset.Width, _gameFieldOffset.Height));
}

Summarize:

So far, all the difficulties have been basically broken. For the complete code, please refer to the attachment. The code is based on the imitation of the Windows XP version of minesweeper. The author's ability is limited.

Source code:

http://git.oschina.net/muxiangovo/Mine

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325830521&siteId=291194637