十四、响应鼠标点击事件
    (1)设置对应坐标位置为相应的前景状态

/// <summary>
/// 设置单元格图样
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="state"></param>
private void setcellfore(int x, int y, forestate state)
{
    if (state > forestate.question || state < forestate.none)
        return;

    _foredata[y, x] = (int)state;

    if (forecanvas.children.contains(_foreimage[y, x]))
        forecanvas.children.remove(_foreimage[y, x]);

    if (state == forestate.none)
        return;

    _foreimage[y, x].source = imagehelper.cutimage(_bmpforeground,
        new int32rect((_foredata[y, x] - 1) * _cellsize.width, 0, _cellsize.width, _cellsize.height));
    forecanvas.children.add(_foreimage[y, x]);
}

如果当前坐标位置设置的前景状态为允许值范围,则将其赋给相应的_foredata元素,并删除原来的图形。如果设置状态为问号或小红旗,则重新设置该图形。

(2)鼠标点击空白区域时,自动打开附近连片的空白区域。使用了以下递归方法。

/// <summary>
/// 自动打开附近空白区域
/// </summary>
/// <param name="y"></param>
/// <param name="x"></param>
private void openneartospace(int y, int x)
{
    if (y < 0 || y >= _gamelevel._colgrid || x < 0 || x >= _gamelevel._rowgrid)//越界
        return;

    if (_backdata[y, x] == (int)backstate.blank && _foredata[y, x] == (int)forestate.normal)
    {

        _foredata[y, x] = (int)forestate.none;        //已打开
        if (forecanvas.children.contains(_foreimage[y, x]))
            forecanvas.children.remove(_foreimage[y, x]);

        if (y - 1 >= 0 && x - 1 >= 0)
        {
            openneartospace(y - 1, x - 1);
        }

        if (y - 1 >= 0)
        {
            openneartospace(y - 1, x);
        }

        if (y - 1 >= 0 && x + 1 <= _gamelevel._rowgrid - 1)
        {
            openneartospace(y - 1, x + 1);
        }

        if (y + 1 <= _gamelevel._colgrid - 1 && x - 1 >= 0)
        {
            openneartospace(y + 1, x - 1);
        }

        if (y + 1 <= _gamelevel._colgrid - 1)
        {
            openneartospace(y + 1, x);
        }

        if (y + 1 <= _gamelevel._colgrid - 1 && x + 1 <= _gamelevel._rowgrid - 1)
        {
            openneartospace(y + 1, x + 1);
        }
        if (x + 1 <= _gamelevel._rowgrid - 1)
        {
            openneartospace(y, x + 1);
        }

        if (x - 1 >= 0)
        {
            openneartospace(y, x - 1);
        }

        open8box(y, x);     // 打开周围8个方格
    }

    return;
}

/// <summary>
/// 打开周围8个方格
/// </summary>
/// <param name="y"></param>
/// <param name="x"></param>
private void open8box(int y, int x)
{
    if (y - 1 >= 0 && x - 1 >= 0)
    {
        _foredata[y - 1, x - 1] = (int)forestate.none;
        if (forecanvas.children.contains(_foreimage[y - 1, x - 1]))
            forecanvas.children.remove(_foreimage[y - 1, x - 1]);
    }

    if (y - 1 >= 0)
    {
        _foredata[y - 1, x] = (int)forestate.none;
        if (forecanvas.children.contains(_foreimage[y - 1, x]))
            forecanvas.children.remove(_foreimage[y - 1, x]);
    }
    if (y - 1 >= 0 && x + 1 <= _gamelevel._rowgrid - 1)
    {
        _foredata[y - 1, x + 1] = (int)forestate.none;
        if (forecanvas.children.contains(_foreimage[y - 1, x + 1]))
            forecanvas.children.remove(_foreimage[y - 1, x + 1]);
    }
    if (x - 1 >= 0)
    {
        _foredata[y, x - 1] = (int)forestate.none;
        if (forecanvas.children.contains(_foreimage[y, x - 1]))
            forecanvas.children.remove(_foreimage[y, x - 1]);
    }
    if (x + 1 <= _gamelevel._rowgrid - 1)
    {
        _foredata[y, x + 1] = (int)forestate.none;
        if (forecanvas.children.contains(_foreimage[y, x + 1]))
            forecanvas.children.remove(_foreimage[y, x + 1]);
    }
    if (y + 1 <= _gamelevel._colgrid - 1 && x - 1 >= 0)
    {
        _foredata[y + 1, x - 1] = (int)forestate.none;
        if (forecanvas.children.contains(_foreimage[y + 1, x - 1]))
            forecanvas.children.remove(_foreimage[y + 1, x - 1]);
    }
    if (y + 1 <= _gamelevel._colgrid - 1)
    {
        _foredata[y + 1, x] = (int)forestate.none;
        if (forecanvas.children.contains(_foreimage[y + 1, x]))
            forecanvas.children.remove(_foreimage[y + 1, x]);
    }
    if (y + 1 <= _gamelevel._colgrid - 1 && x + 1 <= _gamelevel._rowgrid - 1)
    {
        _foredata[y + 1, x + 1] = (int)forestate.none;
        if (forecanvas.children.contains(_foreimage[y + 1, x + 1]))
            forecanvas.children.remove(_foreimage[y + 1, x + 1]);
    }
}

(3)添加鼠标左键事件
编辑xaml文件,在forecanvas元素内添加mouseleftbuttondown=”forecanvas_mouseleftbuttondown”。后台代码如下:

private void forecanvas_mouseleftbuttondown(object sender, mousebuttoneventargs e)
{
    if (_gamestate == gamestate.none)
        return;

    // 获取鼠标点击的格子位置
    point mousepoint = e.mousedevice.getposition(forecanvas);
    int dx = (int)(mousepoint.x / _cellsize.width);
    int dy = (int)(mousepoint.y / _cellsize.height);

    // 已打开区域
    if (_foredata[dy, dx] == (int)forestate.none)
        return;

    if (_backdata[dy, dx] > (int)backstate.blank)
    {
        if (forecanvas.children.contains(_foreimage[dy, dx]))
        {
            forecanvas.children.remove(_foreimage[dy, dx]);
            _foredata[dy, dx] = (int)forestate.none;
        }
    }

    if (_backdata[dy, dx] == (int)backstate.blank)
    {
        openneartospace(dy, dx);
    }

    if (_backdata[dy, dx] == (int)backstate.mine)
    {
        if (forecanvas.children.contains(_foreimage[dy, dx]))
        {
            forecanvas.children.remove(_foreimage[dy, dx]);
            _foredata[dy, dx] = (int)forestate.none;
        }

        // friedmine(dy, dx);等待后补
    }

    // 是否胜利判断
}

实现了主要状态判断和动作,踩雷和胜利判断的情况因为要使用动画效果,所以这里先留空,待后再做。

(4)添加鼠标右键功能
编辑xaml文件,在forecanvas元素内添加mouserightbuttondown=”forecanvas_mouserightbuttondown”,后台代码:

private void forecanvas_mouserightbuttondown(object sender, mousebuttoneventargs e)
{
    if (_gamestate == gamestate.none)
        return;

    // 获取鼠标点击的格子位置
    point mousepoint = e.mousedevice.getposition(forecanvas);
    int dx = (int)(mousepoint.x / _cellsize.width);
    int dy = (int)(mousepoint.y / _cellsize.height);

    // 已打开区域
    if (_foredata[dy, dx] == (int)forestate.none)
        return;

    if (forecanvas.children.contains(_foreimage[dy, dx]))
    {
        // 循环前景数据
        _foredata[dy, dx]++;
        if (_foredata[dy, dx] > 3)
        {
            _foredata[dy, dx] = 1;
        }

        // 设置相应的图片(原始、红旗、问号)
        _foreimage[dy, dx].source = imagehelper.cutimage(_bmpforeground,
            new int32rect((_foredata[dy, dx]- 1) * _cellsize.width, 0, _cellsize.width, _cellsize.height));

        // 计算地雷数
        int num = 0;
        for (int y=0; y<_gamelevel._colgrid; y++)
        {
            for (int x=0; x<_gamelevel._rowgrid; x++)
            {
                if (_foredata[y, x] == (int)forestate.flag)
                    num++;
            }
        }
        textblockminenum.text = (_gamelevel._minenum - num).tostring();

        // 待加胜利检测
    }
}

每单击一次右键,将相应的单元格的图片从原始-红旗-问号-原始,循环递增,并重新计算显示的地雷数。

十五、添加判断是否胜利
看注释的判断。

private bool iswin()
{
    bool flag = true;
    for (int y = 0; y < _gamelevel._colgrid; y++)
    {
        for (int x = 0; x < _gamelevel._rowgrid; x++)
        {
            // 地雷未被红旗标记
            if (_backdata[y, x] == (int)backstate.mine && _foredata[y, x] != (int)forestate.flag)
            {
                flag = false;
                break;
            }

            // 存在未打开格子或标记为问号的格子
            if (_foredata[y, x] == (int)forestate.normal || _foredata[y, x] == (int)forestate.question)
            {
                flag = false;
                break;
            }
        }

        if (!flag)
            break;
    }

    return flag;
}

将该方法添加到前景forecanvas控件的左、右键事件中进行调用。

if (iswin())
{
    winprocess();
}

 

这是胜利后的处理方法:先停止计时,然后重新覆盖前景图片,启用计时动画事件,从下往上逐消去前景图片:

private void winprocess()
{
    // 停止计时
    _stopwatchgame.stop();
    _timersettimetext.stop();
    _gamestate = gamestate.none;

    // 重新覆盖前景图片
    forecanvas.children.clear();
    for (int y=0; y<_gamelevel._colgrid; y++)
    {
        for (int x=0; x<_gamelevel._rowgrid; x++)
        {
            _foreimage[y, x] = new image();
            _foreimage[y, x].source = imagehelper.cutimage(_bmpforeground, new int32rect(0, 0,
                   _cellsize.width, _cellsize.height));

            _foreimage[y, x].setvalue(canvas.leftproperty, x * (double)_cellsize.width);
            _foreimage[y, x].setvalue(canvas.topproperty, y * (double)_cellsize.height);
            forecanvas.children.add(_foreimage[y, x]);
        }
    }

    // 动画行数
    icount = _gamelevel._colgrid - 1;

    _timerwinanim.start();
}

 

这是计时动画事件方法:

private void _timerwinanim_tick(object sender, eventargs e)
{
    if (icount >= 0)
    {
        storyboard sb1 = new storyboard();
        doubleanimation daopacity = null;

        for (int x = 0; x < _gamelevel._rowgrid; x++)
        {
            if (_backdata[icount, x] == (int)backstate.mine)
            {
                setcellfore(x, icount, forestate.flag);
                continue;
            }

            daopacity = new doubleanimation();
            daopacity.from = 1d;
            daopacity.to = 0d;
            daopacity.duration = new duration(timespan.frommilliseconds(600));

            storyboard.settarget(daopacity, _foreimage[icount, x]);
            storyboard.settargetproperty(daopacity, new propertypath("(image.opacity)"));

            sb1.children.add(daopacity);
        }

        sb1.begin();
        icount--;
    }
    else
    {
        _timerwinanim.stop();

         if (messagebox.show("你胜利了。要重新开始吗?", "恭喜", messageboxbutton.okcancel, messageboximage.information)
                    == messageboxresult.ok)
        {
            menugamestart_click(null, null);
        }
    }
}

 

十六、踩雷后的处理
为了不让主程序复杂化,我们另外创建一个bomb的新类

public class bomb
{
    image bombimg = new image();
    bitmapsource bmpbomb = null;
    const int frame_count = 6;
    bitmapsource[] bmpsourcebomb = new bitmapsource[frame_count];
    int currframe = 0;
    dispatchertimer timerbomb;
    canvas _canvas = new canvas();
    int dx, dy;

    public bomb(canvas canvas, int dx, int dy, bitmapsource bmpimgbomb)
    {
        _canvas = canvas;
        this.dx = dx;
        this.dy = dy;
        bmpbomb = bmpimgbomb;

        for (int i=0; i<frame_count; i++)
        {
            bmpsourcebomb[i] = imagehelper.cutimage(bmpbomb, new system.windows.int32rect(i * 35, 0, 35, 35));
        }

        timerbomb = new dispatchertimer();
        timerbomb.interval = timespan.frommilliseconds(200);
        timerbomb.tick += timerbomb_tick;
    }

    private void timerbomb_tick(object sender, eventargs e)
    {
        bombimg.source = bmpsourcebomb[currframe];
        currframe++;
        if (currframe == 5)
        {
            currframe = 0;

            if (_canvas.children.contains(bombimg))
            {
                _canvas.children.remove(bombimg);
            }

            timerbomb.stop();
        }
    }

    public void drawbomb()
    {
        if (!_canvas.children.contains(bombimg))
            _canvas.children.add(bombimg);

        canvas.setleft(bombimg, dy * 35);
        canvas.settop(bombimg, dx * 35);

        timerbomb.start();
    }

    public system.drawing.point getposition()
    {
        return new system.drawing.point(dx, dy);
    }
}

创建实例时,一并将相关参数传递过去。然后调用drawbomb在启动内部计时器,该计时器顺序更新前景图片源,从而实现爆炸效果。

主程序添加踩雷动作方法:

private void friedmines(int y, int x)
{
    if (_backdata[y, x] == (int)backstate.mine)
    {
        _backimage[y, x].source = imagehelper.cutimage(_bmpbomb, new int32rect(1 * _cellsize.width, 0, 
            _cellsize.width, _cellsize.height));
    }

    int bombcount = 0;
    for (int j=0; j<_gamelevel._colgrid; j++)
    {
        for (int i=0; i<_gamelevel._rowgrid; i++)
        {
            if (_backdata[j,i] == (int)backstate.mine && _foredata[j,i] != (int)forestate.none
                && forecanvas.children.contains(_foreimage[j,i]))
            {
                bombs[bombcount++] = new bomb(forecanvas, j, i, _bmpbomb);
            }
        }
    }

    _timerbomb.start();
}

 

我们一开始在初始化阶段根据地雷数创建了bombs数组,现在遍历游戏区,在有地雷的位置创建bomb实例,然后启动_timerbomb计时事件。

private void _timerbomb_tick(object sender, eventargs e)
{
    bombs[bombcount].drawbomb();
    forecanvas.children.remove(_foreimage[bombs[bombcount].getposition().x, bombs[bombcount].getposition().y]);
    bombcount++;

    if (bombcount == (bombs.length - 1))
    {
        bombcount = 0;
        _timerbomb.stop();
    }
}

该计时器按顺序调用bomb类的drawbomb方法,实现爆炸效果。

在forecanavas的左键事件中调用该方法,处理踩雷事件。

if (_backdata[dy, dx] == (int)backstate.mine)
{
    if (forecanvas.children.contains(_foreimage[dy, dx]))
    {
        forecanvas.children.remove(_foreimage[dy, dx]);
    }

    friedmines(dy, dx);

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

先到这里了,其他次要功能就不再这里啰嗦了。