zoukankan      html  css  js  c++  java
  • 练手WPF(三)——扫雷小游戏的简易实现(下)

    十四、响应鼠标点击事件
        (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 Bomb[] bombs;
    private int bombCount = 0;

    在InitGameData方法中进行初始化:

    bombs = new Bomb[_gameLevel._mineNum];

    踩雷动作方法:

    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();
    }

     现在遍历游戏区,在有地雷的位置创建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();
    }

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

    另外:发现个小问题,如果困难难度踩着地雷的话,按原来设定的时间间隙,99个地雷爆炸需要比较长时间。这样调整下:

    在InitGameData方法中的switch里,分别添加一行:

    case  Level.SIMPLE:
        ...
        _timerBomb.Interval = new TimeSpan(0, 0, 0, 0, 220);
        ...
    
    case Level.NORMAL:
        ...
       _timerBomb.Interval = new TimeSpan(0, 0, 0, 0, 120);
        ...
    
    case Level.HARD:
       ...
       _timerBomb.Interval = new TimeSpan(0, 0, 0, 0, 50);
       ...
  • 相关阅读:
    linux下启动和关闭网卡命令及DHCP上网
    python 编码问题
    paddlepaddle
    Convolutional Neural Network Architectures for Matching Natural Language Sentences
    deep learning RNN
    Learning Structured Representation for Text Classification via Reinforcement Learning 学习笔记
    Python IO密集型任务、计算密集型任务,以及多线程、多进程
    EM 算法最好的解释
    tensorflow 调参过程
    tensorflow 学习纪录(持续更新)
  • 原文地址:https://www.cnblogs.com/moonblogcore/p/10945224.html
Copyright © 2011-2022 走看看