C#带你玩扫雷(附源码)

扫雷游戏,大家都应该玩过吧!其实规则也很简单,可是我们想自己实现一个扫雷,我们应该怎么做呢?

Step1: 知晓游戏原理

扫雷就是要把所有非地雷的格子揭开即胜利;踩到地雷格子就算失败。游戏主区域由很多个方格组成。使用鼠标左键随机点击一个方格,方格即被打开并显示出方格中的数字;方格中数字则表示其周围的8个方格隐藏了几颗雷;如果点开的格子为空白格,即其周围有0颗雷,则其周围格子自动打开;如果其周围还有空白格,则会引发连锁反应;在你认为有雷的格子上,点击右键即可标记雷;如果一个已打开格子周围所有的雷已经正确标出,则可以在此格上同时点击鼠标左右键以打开其周围剩余的无雷格。

1代表1的上下左右及斜角合计有一颗雷,依次轮推,2则有2颗,3则有3颗..

在确实是炸弹的方格上点了旗子,就安全了,不是炸弹的被点了旗子,后面会被炸死的..问号就先不确定这里有没有炸弹,不会存在点错了被炸死的状况..

Step2: 由step1可知,游戏由格子组成,翻译成代码语言就叫做数组,也就是游戏地图就是一个二维数组。格子对象,格子的值即当前雷的数量,那么此时我们暂定雷的数字标识为-1。除此之外,格子对象还有是否被显示,显示当前雷数量等属性,那么我们大概可以定义这样一个类:

 public class CellBlockRole
  {
    /// <summary>
    /// 位于游戏地图中的坐标点X
    /// </summary>
    public int X { get; set; }

    /// <summary>
    /// 位于游戏地图中的坐标点Y
    /// </summary>
    public int Y { get; set; }

    /// <summary>
    /// 是否展示最后格子所代表的结果
    /// </summary>
    public bool IsShowResult { get; set; } = false;

    /// <summary>
    /// 是否计算数字结果
    /// </summary>
    public bool IsComputeResult { get; set; } = false;

    /// <summary>
    /// 是否已经展示过计算结果了
    /// </summary>
    public bool IsHasShowComputed { get; set; } = false;

    /// <summary>
    /// 当前的格子的角色数字, -1:地雷,其他当前雷的数量
    /// </summary>
    public int Number { set; get; } = 0;

    /// <summary>
    /// 是否被Flag标识
    /// </summary>
    public bool IsFlag { get; set; } = false;

    /// <summary>
    /// 是否是雷
    /// </summary>
    public bool IsBoom => Number == -1;

  }

绘制游戏UI画面,见代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using SweeperLibrary.Properties;
using Timer = System.Threading.Timer;

namespace SweeperLibrary
{
  public delegate void OnGameOverDelegate();

  public delegate void OnShowANumberDelegate();

  public delegate void OnPublishGameTimeDelegate(string timeDescription);

  public partial class GameView : UserControl
  {

    /// <summary>
    /// 游戏结束事件
    /// </summary>
    public event OnGameOverDelegate OnGameOverEvent;

    /// <summary>
    /// 当一个格子被点击时,显示当前数字的事件
    /// </summary>
    public event OnShowANumberDelegate OnShowANumberEvent;

    /// <summary>
    /// 发布当前游戏的时间
    /// </summary>
    public event OnPublishGameTimeDelegate OnPublishGameTimeEvent;

    /// <summary>
    /// 游戏绘制地图的每个格子的大小
    /// </summary>
    public static readonly int CellSize = 40;

    /// <summary>
    /// 游戏规模N*N
    /// </summary>
    public static readonly int GameCellCount = 10;

    /// <summary>
    /// 移动方向坐标点改变的数组
    /// </summary>
    public static readonly int[][] MoveDirectionPoints = {
      new[]{-1, -1},
      new[] {0, -1},
      new[] {1, -1},
      new[] {1, 0},
      new[] {1, 1},
      new[] {0, 1},
      new[] {-1, 1},
      new[] {-1, 0}
    };

    /// <summary>
    /// 随机数雷生成对象
    /// </summary>
    private static readonly Random random = new Random(Guid.NewGuid().GetHashCode());
    /// <summary>
    /// 游戏地图标识数组
    /// </summary>
    private CellBlockRole[][] gameMap = new CellBlockRole[GameCellCount][];

    /// <summary>
    /// 雷的数量,默认为10
    /// </summary>
    public int BoomCount { get; set; } = 10;

    /// <summary>
    /// 游戏开始时间
    /// </summary>
    private DateTime gameStartTime;

    /// <summary>
    /// 计时定时器
    /// </summary>
    private System.Windows.Forms.Timer gameTimer = new System.Windows.Forms.Timer();

    public GameView()
    {
      InitializeComponent();
      SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
      SetStyle(ControlStyles.AllPaintingInWmPaint, true);
      InitGame(); //默认游戏已经开始
      SetGameTimer(); //设置游戏定时器
    }

    private void GameView_Paint(object sender, PaintEventArgs e)
    {
      Width = GameCellCount + 1 + GameCellCount * CellSize;
      Height = GameCellCount + 1 + GameCellCount * CellSize;
      //绘制游戏界面
      Graphics graphics = e.Graphics;
      graphics.Clear(Color.WhiteSmoke);
      if (gameMap != null && gameMap.Length > 0 && gameMap[0] != null && gameMap[0].Length > 0)
      {
        for (int y = 0; y < GameCellCount; y++)
        {
          for (int x = 0; x < GameCellCount; x++)
          {
            int dx = x + 1 + x * CellSize,
              dy = y + 1 + y * CellSize;
            CellBlockRole cellBlockRole = gameMap[y][x];
            graphics.FillRectangle(new SolidBrush(cellBlockRole.IsShowResult ? Color.LightSlateGray : Color.WhiteSmoke),
              dx, dy, CellSize, CellSize);
            graphics.DrawRectangle(new Pen(Color.LightGray), dx, dy, CellSize, CellSize);
            if (cellBlockRole.IsShowResult && cellBlockRole.Number != 0)
            {
              switch (cellBlockRole.Number)
              {
                case -1: //雷
                  graphics.DrawImage(Image.FromHbitmap(Resources.boom.GetHbitmap()), new RectangleF(dx, dy, CellSize, CellSize));
                  break;
                default: //数字
                  string drawText = cellBlockRole.Number.ToString();
                  Font textFont = new Font(FontFamily.GenericSansSerif, 12, FontStyle.Bold);
                  SizeF textSize = graphics.MeasureString(drawText, textFont);
                  graphics.DrawString(drawText, textFont, new SolidBrush(Color.White),
                    dx + (CellSize - textSize.Width) / 2, dy + (CellSize - textSize.Height) / 2);
                  break;
              }
            }
          }
        }
      }
    }

    private void GameView_MouseDown(object sender, MouseEventArgs e)
    {
      int px = (e.X - 1) / (CellSize + 1),
        py = (e.Y - 1) / (CellSize + 1);
      switch (e.Button)
      {
        case MouseButtons.Left: //鼠标左键
          if (!gameMap[py][px].IsShowResult)
          {
            if (gameMap[py][px].IsBoom)
            {
              new Thread(() =>
              {
                ShowAllCellBlockRoleNumber();
                if (this.InvokeRequired)
                {
                  MethodInvoker del = Invalidate;
                  this.Invoke(del);
                } else
                {
                  Invalidate();
                }
              }).Start();
              gameTimer.Stop();
              OnGameOverEvent?.Invoke();
            } else
            {
              new Thread(() =>
              {
                ShowNeiborhoodCellRolesByPosi(px, py);
                if (this.InvokeRequired)
                {
                  MethodInvoker del = Invalidate;
                  this.Invoke(del);
                } else
                {
                  Invalidate();
                }
              }).Start();
              OnShowANumberEvent?.Invoke();
            }
          }
          break;
        case MouseButtons.Right: //鼠标右键
          break;
      }
    }

    /// <summary>
    /// 初始化游戏
    /// </summary>
    private void InitGame()
    {
      new Thread(() =>
      {
        InitGameMap();
        GenerateBooms();
        if (this.InvokeRequired)
        {
          MethodInvoker del = Invalidate;
          this.Invoke(del);
        } else
        {
          Invalidate();
        }
      }).Start();
    }

    /// <summary>
    /// 设置游戏定时器
    /// </summary>
    private void SetGameTimer()
    {
      gameTimer.Interval = 1000;
      gameTimer.Enabled = true;
      gameTimer.Tick += (sender, args) =>
      {
        long dMillisecond = DateTime.Now.Millisecond - gameStartTime.Millisecond;
        long hour = dMillisecond / 60 / 60 / 1000;
        long minute = (dMillisecond - hour * (60 * 60 * 1000)) / (60 * 1000);
        long second = ((dMillisecond - hour * (60 * 60 * 1000)) % (60 * 1000)) / 1000;
        OnPublishGameTimeEvent?.Invoke((hour > 0 ? (hour > 9 ? hour.ToString() : "0" + hour) + ":" : "")
                        + (minute > 9 ? minute.ToString() : "0" + minute) + ":" + (second > 9 ? second.ToString() : "0" + second));
      };
    }

    /// <summary>
    /// 初始化游戏地图
    /// </summary>
    private void InitGameMap()
    {
      for (int i = 0; i < GameCellCount; i++)
      {
        gameMap[i] = new CellBlockRole[GameCellCount];
        for (int j = 0; j < GameCellCount; j++)
        {
          gameMap[i][j] = new CellBlockRole
          {
            X = j,
            Y = i
          };
        }
      }
      gameStartTime = DateTime.Now;
      gameTimer.Start();
    }

    /// <summary>
    /// 重置游戏地图
    /// </summary>
    public void ResetGameMap()
    {
      new Thread(() =>
      {
        for (int i = 0; i < GameCellCount; i++)
        {
          for (int j = 0; j < GameCellCount; j++)
          {
            gameMap[i][j].X = j;
            gameMap[i][j].Y = i;
            gameMap[i][j].Number = 0;
            gameMap[i][j].IsShowResult = false;
            gameMap[i][j].IsComputeResult = false;
            gameMap[i][j].IsHasShowComputed = false;

          }
        }
        GenerateBooms(); //生成一些雷
        if (this.InvokeRequired)
        {
          MethodInvoker del = Invalidate;
          this.Invoke(del);
        } else
        {
          Invalidate();
        }
      }).Start();
      gameStartTime = DateTime.Now;
      gameTimer.Start();
    }

    /// <summary>
    /// 随机生成一些地雷
    /// </summary>
    public void GenerateBooms()
    {
      for (int i = 0; i < BoomCount; i++)
      {
        int boomNumberIndex = random.Next(0, GameCellCount * GameCellCount - 1); //生成随机数的范围
        int boomX = boomNumberIndex % GameCellCount,
          boomY = boomNumberIndex / GameCellCount;
        if (gameMap[boomY][boomX].Number == 0)
          gameMap[boomY][boomX].Number = -1; //-1表示雷
        else // 已经存在雷了,所以要重新处理
          i--;
      }
      MakeAllNumberComputeInCellRole(0, 0); //默认从坐标(0,0)开始
    }

    /// <summary>
    /// 显示所有的格子的信息
    /// </summary>
    private void ShowAllCellBlockRoleNumber()
    {
      for (int i = 0; i < GameCellCount; i++)
      {
        for (int j = 0; j < GameCellCount; j++)
        {
          gameMap[i][j].IsShowResult = true;
        }
      }
    }

    /// <summary>
    /// 显示某点周边所有格子的数字
    /// </summary>
    /// <param name="posiX">X轴坐标</param>
    /// <param name="posiY">Y轴坐标</param>
    private void ShowNeiborhoodCellRolesByPosi(int posiX, int posiY)
    {
      gameMap[posiY][posiX].IsShowResult = true;
      gameMap[posiY][posiX].IsHasShowComputed = true;
      int boomCount = GetBoomCountInNeiborhood(posiX, posiY);
      if (boomCount == 0) //如果周围没有雷,则翻开所有8个方向的相关数字
      {
        for (int i = 0; i < MoveDirectionPoints.Length; i++)
        {
          int[] itemPosi = MoveDirectionPoints[i];
          int rx = posiX + itemPosi[0],
            ry = posiY + itemPosi[1];
          bool isNotOutIndexRange = rx >= 0 && rx < GameCellCount && ry >= 0 && ry < GameCellCount;
          if (isNotOutIndexRange) //防止坐标溢出
          {
            gameMap[ry][rx].IsShowResult = true;
            if (!gameMap[ry][rx].IsHasShowComputed && gameMap[ry][rx].Number == 0)
              ShowNeiborhoodCellRolesByPosi(rx, ry);
          }
        }
      }
    }

    /// <summary>
    /// 获取某点附近的雷数量
    /// </summary>
    /// <param name="posiX">X轴坐标点</param>
    /// <param name="posiY">Y轴坐标点</param>
    /// <returns></returns>
    private int GetBoomCountInNeiborhood(int posiX, int posiY)
    {
      int boomCount = 0;
      for (int i = 0; i < MoveDirectionPoints.Length; i++)
      {
        int[] itemPosi = MoveDirectionPoints[i];
        int rx = posiX + itemPosi[0],
          ry = posiY + itemPosi[1];
        bool isNotOutIndexRange = rx >= 0 && rx < GameCellCount && ry >= 0 && ry < GameCellCount;
        if (isNotOutIndexRange && gameMap[ry][rx].IsBoom) //防止坐标溢出
        {
          boomCount++;
        }
      }
      return boomCount;
    }

    /// <summary>
    /// 计算每个格子的数字标识
    /// </summary>
    /// <param name="posiX">X轴坐标</param>
    /// <param name="posiY">Y轴坐标</param>
    private void MakeAllNumberComputeInCellRole(int posiX, int posiY)
    {
      int boomCount = GetBoomCountInNeiborhood(posiX, posiY);
      if (boomCount != 0) //如果周围没有雷,则计算周围的8个方向的格子
      {
        gameMap[posiY][posiX].Number = boomCount;
      } else
      {
        if (!gameMap[posiY][posiX].IsBoom)
          gameMap[posiY][posiX].Number = 0;
      }
      gameMap[posiY][posiX].IsComputeResult = true;
      for (int i = 0; i < MoveDirectionPoints.Length; i++)
      {
        int[] itemPosi = MoveDirectionPoints[i];
        int rx = posiX + itemPosi[0],
          ry = posiY + itemPosi[1];
        bool isNotOutIndexRange = rx >= 0 && rx < GameCellCount && ry >= 0 && ry < GameCellCount;
        if (isNotOutIndexRange && !gameMap[ry][rx].IsComputeResult && !gameMap[ry][rx].IsBoom) //防止坐标溢出
        {
          MakeAllNumberComputeInCellRole(rx, ry);
        }
      }
    }

  }

}

主要代码已经实现,现已知现有代码的定时器由问题,暂时不支持Flag(旗子标识)。当然代码中还有其他不足的地方,游戏持续优化中。。。

源代码地址:MineSweeper-CShape_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解从零开始---用C#制作扫雷游戏

    学C#的原因其实挺简单的,因为一直对游戏挺感兴趣,查了下比较流行的游戏引擎Unity的主要开发语言是C#,所以就决定从C#入手,学学面向对象的编程方法. 以前基本都做的是嵌入式开发,做嵌入式久了,基本上只用C语言,C语言面向过程的特性在嵌入式编程这种资源极度受限的情况确实十分有利,但这种方式在面对大型软件的开发的时候就很难胜任了.编程的模式其实是一种思维习惯,习惯久了以后,想改变确实是一个艰难的过程··· 说起C#,其实在大学的时候学过一个学期,说来惭愧那时候倒也没把它当一门面向对象的语言(其实

  • 基于jQuery实现带动画效果超炫酷的弹出对话框(附源码下载)

    这是一款基于jQuery的弹出对话框插件,这个jQuery对话框插件的最大特点是弹出和关闭都带有非常炫酷的动画特效,比如旋转飞入.上下抖动飞入等.效果图如下: 效果演示    源码下载 html代码: <div class="container"> <h1>jQuery gDialog Plugin Exampels</h1> <button class="btn demo-1">Alert Dialog Box<

  • 一篇文章带你入门Springboot沙箱环境支付宝支付(附源码)

    目录 0.前言 1.效果展示 2.技术栈介绍 3.前期准备 第一步:申请一个沙箱测试账号 第二步:电脑下载一个支付宝提供的客户端用于生成RSA2 第三步:手机下载 [沙箱版支付宝] 4.后端搭建 项目目录结构 pom.xml application.yml application-alipay.proerties Order订单实体类 Service层 Controller层 配置类 跨域拦截器配置以及注册 启动spirngboot项目 支付操作的页面: 支付完成后支付宝回调的页面: 启动前端项

  • C语言实现扫雷游戏详解(附源码)

    目录 1.游戏的功能 2.游戏实现的基本思路 2.1实现菜单给玩家选择 2.2初始化棋盘 2.3数组大小的问题 2.4对棋盘赋值 2.5打印棋盘 2.6布置雷 2.7排查雷 3.代码基本实现部分 3.1主函数部分 3.2 初始化棋盘 3.3对两个棋盘进行赋值 3.4打印棋盘 3.5布置雷 3.6排查雷  3.7函数声明 4.扫雷游戏的源代码 总结 1.游戏的功能 游戏的主要功能有 1:棋盘内有若干个雷 2:玩家输入要排查雷的坐标 3:在玩家输入的坐标处显示周围八个坐标有几个雷 3:若玩家将所有的

  • C语言实现扫雷游戏详解(附源码)

    目录 1.游戏的功能 2.游戏实现的基本思路 2.1实现菜单给玩家选择 2.2初始化棋盘 2.3数组大小的问题 2.4对棋盘赋值 2.5打印棋盘 2.6布置雷 2.7排查雷 3.代码基本实现部分 3.1主函数部分 3.2 初始化棋盘 3.3对两个棋盘进行赋值 3.4打印棋盘 3.5布置雷 3.6排查雷  3.7函数声明 4.扫雷游戏的源代码 总结 1.游戏的功能 游戏的主要功能有 1:棋盘内有若干个雷 2:玩家输入要排查雷的坐标 3:在玩家输入的坐标处显示周围八个坐标有几个雷 3:若玩家将所有的

  • C# 实现俄罗斯方块(附源码)

    概述 俄罗斯方块(Tetris)是一款由俄罗斯人阿列克谢·帕基特诺夫发明的休闲游戏,帕基特诺夫爱玩拼图,从拼图游戏里得到灵感,设计出了俄罗斯方块.由于上手简单.老少皆宜,从而家喻户晓,风靡世界.本文简述如何通过C#来实现俄罗斯方块,仅供学习分享使用,如有不足之处,还请指正. 涉及知识点 BackgroundWorker 在单独的线程上执行操作(主要执行比较耗时的操作). Action .NetFramework自带的一个委托方法. TableLayoutPanel  表示一个面板,它可以在一个由

  • 基于Python实现贪吃蛇小游戏(附源码)

    目录 前言 主要设计 应用知识点 1.python知识点 2.pygamezero知识点 功能截图 代码实现 1.蛇的表示 2.蛇的前进移动 3.控制移动方向 4.游戏失败 5.食物的随机出现 6.游戏得分 源码 总结 前言 这几年人工智能技术大发展,Python因此几乎成了第一位的语言.实际上,多年来,它不仅在软件工程师中得到广泛使用,也是各行业通用的专家语言,就是说,不管孩子以后做什么,都可能用得着.准备针对我自己上小学的孩子,每周抽出些时间,通过学习他们感兴趣的小游戏,逐步把python知

  • 基于JS快速实现导航下拉菜单动画效果附源码下载

    这是一个带变形动画特效的下拉导航菜单特效.该导航菜单在菜单项之间切换时,下拉菜单会快速的根据菜单内容的大小来动态变形,显示合适的下拉菜单大小,效果非常棒. 快速的导航下拉菜单动画效果如下所示: 效果演示         源码下载 HTML 该导航菜单的HTML结构如下: <header class="cd-morph-dropdown"> <a href="#0" class="nav-trigger">Open Nav&

  • JS实现快速的导航下拉菜单动画效果附源码下载

    这是一个带变形动画特效的下拉导航菜单特效.该导航菜单在菜单项之间切换时,下拉菜单会快速的根据菜单内容的大小来动态变形,显示合适的下拉菜单大小,效果非常棒. 查看演示     下载源码 HTML 该导航菜单的HTML结构如下: <header class="cd-morph-dropdown"> <a href="#0" class="nav-trigger">Open Nav<span aria-hidden=&qu

  • 基于JavaScript实现添加到购物车效果附源码下载

    我们有很多种方法实现将商品添加到购物车,通常的做法是点击"添加到购物车"按钮,会跳转到购物车,在购物车里可以点击"结算"按钮进行结算.而今天我给大家介绍一个更友好的解决方案. 查看演示 下载源码 默认情况下,购物车是隐藏不可见的,当用户点击添加到购物车按钮后,商品信息会添加到购物车,购物车会以按钮的形式出现在页面右下角,点击按钮则会展开购物车,显示购物车中的商品信息,同时也可以对购物车中的商品进行删除或者结算等操作.用户也可以暂时关闭购物车继续购物. HTML结构

  • 自定义Android六边形进度条(附源码)

    本文实例讲述了Android自定义圆形进度条,分享给大家供大家参考.具体如下: 大家也可以参考这两篇文章进行学习: <自定义Android圆形进度条(附源码)>   <Android带进度的圆形进度条> 运行效果截图如下: 主要代码: package com.sxc.hexagonprogress; import java.util.Random; import android.content.Context; import android.content.res.ColorSta

随机推荐