欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

练手WPF(三)——扫雷小游戏的简易实现(上)

程序员文章站 2022-07-02 18:06:43
一、创建项目1.创建WPF项目,设置初始化窗口大小(初级难度):高x宽为430x350。2.添加文件夹Images,并添加相关图片。 3.xaml中引入图片资源。 4.添加窗口元素(1)菜单 (2)在菜单之后,之前添加其他界面元素 其中两个Image用于显示时钟和地雷数图例, ......

一、创建项目
1.创建wpf项目,设置初始化窗口大小(初级难度):高x宽为430x350。
2.添加文件夹images,并添加相关图片。

练手WPF(三)——扫雷小游戏的简易实现(上)
3.xaml中引入图片资源。

<window.resources>
    <bitmapimage x:key="imgspace" urisource="/images/space.bmp"/>
    <bitmapimage x:key="imgmine" urisource="/images/mine.png"/>
    <bitmapimage x:key="imgnum1_8" urisource="/images/num1_8.png"/>
    <bitmapimage x:key="imgforeground" urisource="/images/foreimg.png"/>
    <bitmapimage x:key="imgmineortimer" urisource="/images/mineandtimer.png"/>
    <bitmapimage x:key="imgbomb" urisource="/images/bomb.png"/>
</window.resources>

4.添加窗口元素
(1)菜单

<dockpanel>
        <menu dockpanel.dock="top">
            <menuitem header="游戏(_g)">
                <menuitem x:name="menugamestart" header="开始游戏(_s)" click="menugamestart_click"/>
                <separator/>
                <menuitem x:name="menugameexit" header="退出游戏(_x)" click="menugameexit_click"/>
            </menuitem>
            <menuitem header="级别(_l)">
                <menuitem header="初级(_l)" x:name="menulowlevel" ischecked="true" click="menulowlevel_click"/>
                <menuitem header="中级(_m)" x:name="menumiddlelevel" ischecked="false" click="menumiddlelevel_click"/>
                <menuitem header="高级(_h)" x:name="menuhighlevel" ischecked="false" click="menuhighlevel_click"/>
            </menuitem>
            <menuitem header="选项(_o)">
                <menuitem  x:name="menuoptionsmusic" header="音乐音效(_s)" ischeckable="true" ischecked="true"
                          click="menuoptionsmusic_click"/>
                <separator/>
                <menuitem x:name="menushowallmine" header="显示地雷(_o)" click="menushowallmine_click"/>
                <menuitem x:name="menurestoremap" header="继续游戏(_f)" click="menurestoremap_click"/>
                <separator />
                <menuitem x:name="menuresetdata" header="重置记录(_r)" click="menuresetdata_click" />
            </menuitem>
            <menuitem header="帮助(_h)">
                <menuitem x:name="menuhelpabout" header="关于(_a)..." click="menuhelpabout_click"/>
            </menuitem>
        </menu>


</dockpanel>

(2)在菜单之后,</dockpanel>之前添加其他界面元素

<grid margin="3" dockpanel.dock="top">
            <grid.rowdefinitions>
                <rowdefinition height="40"/>
                <rowdefinition height="*"/>
            </grid.rowdefinitions>
            <stackpanel grid.row="0" background="skyblue" verticalalignment="center">
                <grid margin="2 2 2 2">
                    <grid.columndefinitions>
                        <columndefinition width="*"/>
                        <columndefinition width="*"/>
                    </grid.columndefinitions>
                    <stackpanel grid.column="0" horizontalalignment="right" margin="0 0 60 0" orientation="horizontal">
                        <image x:name="imgclock" width="35" height="35" />
                        <border height="30" width="60" cornerradius="6" borderthickness="1" verticalalignment="center" background="#333333">
                            <textblock x:name="textblocktime" text="" verticalalignment="center" horizontalalignment="center" 
                                   fontsize="14" foreground="aliceblue"/>
                        </border>
                    </stackpanel>

                    <stackpanel grid.column="1" horizontalalignment="left" orientation="horizontal"  margin="40 0 0 0">
                        <image x:name="imgminenum" width="35" height="35" />
                        
                        <border height="30" width="60" cornerradius="6" borderthickness="1" verticalalignment="center" background="#333333">
                            <textblock x:name="textblockminenum" text="" verticalalignment="center" horizontalalignment="center" 
                               fontsize="14" foreground="aliceblue"/>
                        </border>
                    </stackpanel>
                </grid>
            </stackpanel>
            <stackpanel grid.row="1" horizontalalignment="left" verticalalignment="top">
                <canvas x:name="backcanvas" width="315" height="315" background="lightcyan" margin="10" />
            </stackpanel>
            <stackpanel grid.row="1" horizontalalignment="left" verticalalignment="top">
                <canvas x:name="forecanvas" width="315" height="315"  margin="10"
                        mouseleftbuttondown="forecanvas_mouseleftbuttondown"
                        mouserightbuttondown="forecanvas_mouserightbuttondown"/>
            </stackpanel>
        </grid>

其中两个image用于显示时钟和地雷数图例,其后两个textblock分别用于显示读秒数和剩余地雷数。
两个重叠的canvas,分别显示底层图块和前景图块。底层图块游戏开始后是固定的,其中会显示随机分布的地雷及其周边地雷数值,而前景图块可以通过鼠标点击进行移除或标记为问号或小红旗。

二、载入图片资源

1、添加一个静态图片帮助类imagehelper,方便以后的图片切片操作。

public static class imagehelper
{
    /// <summary>
    /// bitmapsource图片源剪切操作函数
    /// </summary>
    /// <param name="bmpsource">等待剪切的源</param>
    /// <param name="cut">剪切矩形</param>
    /// <returns>已剪切的图片源</returns>
    public static bitmapsource cutimage(bitmapsource bmpsource, int32rect cut)
    {
        return new croppedbitmap(bmpsource, cut);
    }

    /// <summary>
    /// 将bitmapimage转换为bitmapsource
    /// </summary>
    /// <param name="bitmapimage">待转换的bitmapimage</param>
    /// <returns>bitmapsource</returns>
    public static bitmapsource bitmapimagetobitmapsource(bitmapimage bitmapimage)
    {
        imagesource imagesource = bitmapimage;
        bitmap bitmap = imagesourcetobitmap(imagesource);
        bitmapsource bitmapsource = bitmaptobitmapimage(bitmap);
     bitmap.dispose(); return bitmapsource; } /// <summary> /// 将imagesource转为bitmap /// </summary> /// <param name="imagesource"></param> /// <returns></returns> public static system.drawing.bitmap imagesourcetobitmap(imagesource imagesource) { bitmapsource m = (bitmapsource)imagesource; bitmap bmp = new bitmap(m.pixelwidth, m.pixelheight, system.drawing.imaging.pixelformat.format32bpppargb); system.drawing.imaging.bitmapdata data = bmp.lockbits(new rectangle(system.drawing.point.empty, bmp.size), system.drawing.imaging.imagelockmode.writeonly, system.drawing.imaging.pixelformat.format32bpppargb); m.copypixels(int32rect.empty, data.scan0, data.height * data.stride, data.stride); bmp.unlockbits(data); return bmp; } /// <summary> /// 将bitmap转为bitmapimage /// </summary> /// <param name="bitmap"></param> /// <returns></returns> public static bitmapimage bitmaptobitmapimage(bitmap bitmap) { using (memorystream stream = new memorystream()) { bitmap.save(stream, system.drawing.imaging.imageformat.png); stream.position = 0; bitmapimage result = new bitmapimage(); result.begininit(); result.cacheoption = bitmapcacheoption.onload; result.streamsource = stream; result.endinit(); result.freeze(); return result; } } }

2、添加几个字段变量

主程序mainwindow.xaml.cs中添加:

private bitmapsource _bmpspace, _bmpmine, _bmpnum1_8, _bmpforeground, _bmpmineortimer, _bmpbomb;
private system.drawing.size _cellsize = new system.drawing.size(35, 35);

3、获取图片资源

private void getimageresource()
{
    bitmapimage bmpspace = (bitmapimage)tryfindresource("imgspace");
    bitmapimage bmpmine = (bitmapimage)tryfindresource("imgmine");
    bitmapimage bmpnum1_8 = (bitmapimage)tryfindresource("imgnum1_8");
    bitmapimage bmpforeground = (bitmapimage)tryfindresource("imgforeground");
    bitmapimage bmpmineortimer = (bitmapimage)tryfindresource("imgmineortimer");
    bitmapimage bmpbomb = (bitmapimage)tryfindresource("imgbomb");

    _bmpspace = imagehelper.bitmapimagetobitmapsource(bmpspace);
    _bmpmine = imagehelper.bitmapimagetobitmapsource(bmpmine);
    _bmpnum1_8 = imagehelper.bitmapimagetobitmapsource(bmpnum1_8);
    _bmpforeground = imagehelper.bitmapimagetobitmapsource(bmpforeground);
    _bmpmineortimer = imagehelper.bitmapimagetobitmapsource(bmpmineortimer);
    _bmpbomb = imagehelper.bitmapimagetobitmapsource(bmpbomb);
}

 

4、将界面中用到的时钟和地图数这两张图片设置到位。

private void settimerandmineimage()
{
    imgclock.source = imagehelper.cutimage(_bmpmineortimer, new int32rect(0, 0, _cellsize.width, _cellsize.height));
    imgminenum.source = imagehelper.cutimage(_bmpmineortimer, new int32rect(1 * _cellsize.width, 0, _cellsize.width, _cellsize.height));
}

 

5、将上述两个方法添加到构造方法中。

运行程序,如图1。

练手WPF(三)——扫雷小游戏的简易实现(上)

 

三、设置状态枚举

添加一个myenum.cs类文件,让主cs文件看起来简练一点。内容如下:

// 游戏级别
public enum level
{
    simple,
    normal,
    hard
};

// 前景状态
public enum forestate
{
    none,           // 无
    normal,         // 正常覆盖
    flag,           // 红旗
    question        // 问号
};

// 底层状态
public enum backstate
{
    mine = -1,      // 地雷
    blank = 0,      // 空
};

// 游戏状态
public enum gamestate
{
    none,           // 未开始
    stop,           // 已停止
    start,          // 已开始
    pause           // 已暂停
};

 

四、添加gamelevel类,定义游戏级别相关参数

public class gamelevel
{
    public int _minenum { get; set; }      // 地雷数
    public int _rowgrid { get; set; }      // 横格子数
    public int _colgrid { get; set; }      // 纵格子数

    public gamelevel(int currlevel, int minenum, int rowgrid, int colgrid)
    {
        _minenum = minenum;
        _rowgrid = rowgrid;
        _colgrid = colgrid;
    }
}

 

五、定义主游戏数据变量

private int[,] _backdata = null;            // 底部背景数据(雷-1,0为空白,数值(1-8)周围雷数)
private int[,] _foredata = null;            // 前景数据(1:正常盖住;2:红旗;3:问号)
private image[,] _backimage = null;         // 底图图片数组
private image[,] _foreimage = null;         // 上方图片数组

private gamestate _gamestate = gamestate.none;

private random rnd = new random();          // 随机数

private gamelevel _gamelevel;
private level _level = level.simple;


 

另外还用到几个计时器,看后面的注释

// 计时
private system.diagnostics.stopwatch _stopwatchgame = new system.diagnostics.stopwatch(); // 游戏过程读秒
private dispatchertimer _timersettimetext = new dispatchertimer();      // 更新读秒文本框
private dispatchertimer _timerwinanim = new dispatchertimer();     // 用于胜利时动画
private dispatchertimer _timerbomb = new dispatchertimer();            // 用于踩雷动画

将计时器初始化放在构造方法中

// 计时器
_timersettimetext.interval = new timespan(0, 0, 1);
_timersettimetext.tick += _timersettimetext_tick;

// 动画计时器
_timerwinanim.interval = new timespan(0, 0, 0, 0, 300);
_timerwinanim.tick += _timerwinanim_tick;

// 用户踩雷后的动画
_timerbomb.interval = new timespan(0, 0, 0, 0, 200);
_timerbomb.tick += _timerbomb_tick;

 

六、初始化游戏状态

private void initialgamestate()
{
    _timersettimetext.stop();
    _timerwinanim.stop();

    _stopwatchgame.reset();
    _stopwatchgame.stop();
    _gamestate = gamestate.none;

    menugamepauseorcontinue.header = "暂停(_p)";

}

private void _timersettimetext_tick(object sender, eventargs e)
{
    textblocktime.text = ((int)_stopwatchgame.elapsed.totalseconds).tostring();
}

private void _timerwinanim_tick(object sender, eventargs e)
{
}

private void _timerbomb_tick(object sender, eventargs e)
{
}

后面动画计时事件方法以后再加入,这里先空着。

 

七、初始化游戏主数据

private void initgamedata(level level)
{
    switch (level)
    {
        case  level.simple:
            _gamelevel = new gamelevel(9, 9, 10);
            break;

        case level.normal:
            _gamelevel = new gamelevel(16, 16, 40);
            break;

        case level.hard:
            _gamelevel = new gamelevel(30, 16, 99);
            break;
    }

    // 设置窗口大小
    this.width = _cellsize.width * _gamelevel._rowgrid + 40;
    this.height = _cellsize.height * _gamelevel._colgrid + 20 + 100;

    // 获取屏幕大小
    double screenwidth = systemparameters.workarea.width;
    double screenheight = systemparameters.workarea.height;

    // 使窗口居中
    this.left = (screenwidth - this.width) / 2;
    this.top = (screenheight - this.height) / 2;

    // 设置绘图控件大小
    backcanvas.width = _gamelevel._colgrid * _cellsize.width;
    backcanvas.height = _gamelevel._rowgrid * _cellsize.height;
    forecanvas.width = backcanvas.width;
    forecanvas.height = backcanvas.height;

    // 初始化前景和背景数据
    _backdata = new int[_gamelevel._colgrid, _gamelevel._rowgrid];
    _foredata = new int[_gamelevel._colgrid, _gamelevel._rowgrid];

    for (int y = 0; y<_gamelevel._colgrid; y++)
    {
        for (int x=0; x<_gamelevel._rowgrid; x++)
        {
            _backdata[y, x] = (int)backstate.blank;
            _foredata[y, x] = (int)forestate.normal;
        }
    }

    // 初始化前景和背景图片数组
    _backimage = new image[_gamelevel._colgrid, _gamelevel._rowgrid];
    _foreimage = new image[_gamelevel._colgrid, _gamelevel._rowgrid];

    // 清理绘制区
    backcanvas.children.clear();
    forecanvas.children.clear();
}