做夢想起來的C#簡單實現貪吃蛇程序(LinQ + Entity)


最近一直在忙着單位核心開發組件的版本更新,前天加了一個通宵,昨天晚上卻睡不着,腦子里面突然不知怎的一直在想貪吃蛇的實現方法。以往也有類似的情況,白天一直想不通的問題,晚上做夢有時會想到更好的版本,於是抽出時間按照夢里想到的方法測試編寫一下,沒想到從打開VisualStudio到完成初稿測試,只用了4個小時。不敢獨享,又加上好久沒有寫文章了,於是將我的是實現方法寫出來供大家一起討論,高手也請多多指教。

完成實現截圖:

1、實現方法設計

貪吃蛇的主要三個元素是棋盤地圖、蛇身、獎勵豆,蛇身只能在棋盤地圖內進行移動,吃到獎勵豆,自身長度增加一格,蛇身碰到自己的身體則Game Over。

實現的難點在於如何判斷蛇身如何移動,什么時候吃了獎勵豆,蛇身咬住自己了等,這些其實都是在判斷格式。如果我們將每一個格子都定義為實體,然后棋盤地圖和蛇身以及獎勵包含多個格子實體,那么就可以直接使用LinQ來簡單實現查找和對比了。

按照上述的想法,我們定義如下實體對象:

格子ModelElement:包含橫坐標、縱坐標、格子是否包含獎勵屬性

地圖ModelMap:包含地圖的行數、列數、格子實體集合、格子大小、格子默認顏色

蛇身ModelSnake:包含移動方向、移動速度、格子實體集合、蛇身顏色

具體代碼如下:

using System.Collections.Generic;
using System.Drawing;

namespace imStudio.Game.EsurientSnake.Models
{
    /// <summary>
    /// 格子實體
    /// </summary>
    public class ModelElement
    {
        /// <summary>
        /// 橫坐標
        /// </summary>
        public int Abscissa { get; set; }
        /// <summary>
        /// 縱坐標
        /// </summary>
        public int Ordinate { get; set; }
        /// <summary>
        /// 獎勵屬性
        /// </summary>
        public bool Bonus { get; set; }
        /// <summary>
        /// 初始化格子
        /// </summary>
        public ModelElement()
        {
            Abscissa = 0;
            Ordinate = 0;
            Bonus = false;
        }
    }

    /// <summary>
    /// 蛇身實體
    /// </summary>
    public class ModelSnake
    {
        /// <summary>
        /// 移動速度
        /// </summary>
        public int Speed { get; set; }
        /// <summary>
        /// 蛇身顏色
        /// </summary>
        public Color Color { get; set; }
        /// <summary>
        /// 運動方向
        /// </summary>
        public ModelEnum.Direction Direction { get; set; }
        /// <summary>
        /// 蛇身格子實體結合
        /// </summary>
        public List<ModelElement> Body { get; set; }
    }

    /// <summary>
    /// 地圖格子大小屬性實體
    /// </summary>
    public class ModelBox
    {
        /// <summary>
        /// 格子寬度
        /// </summary>
        public int Width { get; set; }
        /// <summary>
        /// 格子高度
        /// </summary>
        public int Height { get; set; }
    }

    /// <summary>
    /// 地圖視圖
    /// </summary>
    public class ModelMap
    {
        /// <summary>
        /// 行數
        /// </summary>
        public int Row { get; set; }
        /// <summary>
        /// 列數
        /// </summary>
        public int Column { get; set; }
        /// <summary>
        /// 棋盤紋路顏色
        /// </summary>
        public Color Line { get; set; }
        /// <summary>
        /// 棋盤格子顏色
        /// </summary>
        public Color Color { get; set; }
        /// <summary>
        /// 棋盤格子大小
        /// </summary>
        public ModelBox Box { get; set; }
        /// <summary>
        /// 棋盤格子實體集合
        /// </summary>
        public List<ModelElement> Body { get; set; }
    }
}
View Code

有了上述的實體之后下面就可以開始正式編寫程序了。具體實現步驟如下:

  • 描繪棋盤地圖
  • 初始化蛇身
  • 蛇身移動、蛇身增加、死亡判斷
  • 隨機獎勵

2、描繪棋盤地圖

總體實現方法:按照預先定義的棋盤地圖實體初始化地圖,然后使用C#的System.Drawing中CreateGraphics方法來進行描繪地圖。

按照定義的棋盤格子初始化棋盤地圖實體:

        /// <summary>
        /// 創建地圖
        /// </summary>
        /// <param name="rowCount"></param>
        /// <param name="colCount"></param>
        /// <param name="boxWidth"></param>
        /// <param name="boxHeight"></param>
        /// <param name="line"></param>
        /// <param name="box"></param>
        /// <returns></returns>
        public static ModelMap GenMap(int rowCount, int colCount, int boxWidth, int boxHeight, Color line, Color box)
        {
            var map = new ModelMap
                {
                    Row = rowCount,
                    Column = colCount,
                    Box = new ModelBox
                        {
                            Width = boxWidth,
                            Height = boxHeight
                        },
                    Line =  line,
                    Color = box,
                    Body = new List<ModelElement>()
                };
            #region 初始化地圖實體
            for (int ri = 0; ri < rowCount; ri++)
            {
                for (int ci = 0; ci < colCount; ci++)
                {
                    map.Body.Add(new ModelElement
                        {
                            Abscissa = ri,
                            Ordinate = ci
                        });
                }
            }
            #endregion

            return map;
        }
View Code

有了實體之后,就開始進行描邊和填充格子:

       /// <summary>
        /// 地圖描邊
        /// </summary>
        /// <param name="panel"></param>
        /// <param name="map"></param>
        public static void DrawMap(Panel panel, ModelMap map)
        {
            #region 勾畫地圖
            var g = panel.CreateGraphics();
            #region 畫橫線
            for (int ri = 0; ri <= map.Row; ri++)
            {
                g.DrawLine(new Pen(Color.Black), 0, ri * map.Box.Height, map.Column * map.Box.Width, ri * map.Box.Height);
            }

            #endregion
            #region 畫豎線
            for (int ci = 0; ci <= map.Column; ci++)
            {
                g.DrawLine(new Pen(Color.Black), ci * map.Box.Width, 0, ci * map.Box.Height, map.Row * map.Box.Width);
            }
            #endregion
            #region 勾畫方塊
            foreach (var b in map.Body)
            {
                DrawMapBox(panel, map.Color, b.Abscissa, b.Ordinate, map.Box.Width, map.Box.Height);
            }
            #endregion
            #endregion
        }

        /// <summary>
        /// 格子顏色填充
        /// </summary>
        /// <param name="panel"></param>
        /// <param name="color"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public static void DrawMapBox(Panel panel, Color color, int x, int y, int width, int height)
        {
            var g = panel.CreateGraphics();
            g.FillRectangle(new HatchBrush(HatchStyle.Percent90, color), x * width + 1, y * height + 1,width - 1, height - 1);
        }
View Code

完成效果截圖:

3、初始化蛇身

總體實現方法:實現方法類似地圖描述,只是需要計算一下蛇身大小。

       /// <summary>
        /// 在地圖上初始化蛇身
        /// </summary>
        /// <param name="map"></param>
        /// <param name="snake"></param>
        /// <returns></returns>
        public static ModelSnake GenSnakeOnMap(ModelMap map,ModelSnake snake)
        {
            var sk = snake;
            var centerX = map.Row/2;
            var centerY = map.Column/2;
            sk.Body = new List<ModelElement>
                {
                    new ModelElement
                        {
                            Abscissa = centerX,
                            Ordinate = centerY
                        },
                    new ModelElement
                        {
                            Abscissa = centerX,
                            Ordinate = centerY - 1
                        }
                };
            return sk;
        }

        /// <summary>
        /// 蛇身描繪至地圖
        /// </summary>
        /// <param name="panel"></param>
        /// <param name="map"></param>
        /// <param name="snake"></param>
        /// <returns></returns>
        public static ModelSnake DrawSnakeOnMap(Panel panel, ModelMap map,ModelSnake snake)
        {
            snake = GenSnakeOnMap(map, snake);
            foreach (var b in snake.Body)
            {
                DrawMapBox(panel, snake.Color, b.Abscissa, b.Ordinate, map.Box.Width, map.Box.Height);
            }
            return snake;
        }
View Code

完成實現截圖:

4、蛇身移動、增加蛇身、死亡判斷

總體實現方法:設定一個Timer實現每隔多少時間蛇身移動一次。每移動一次,蛇身尾部消失,頭部按照運動方向增加一格。這個時候就體現到使用實體的好處了,只需要對蛇身格子實體集合進行一次Remove和一次Insert就可以了。而蛇身是否咬到自己,判斷起來就更加簡單了,直接使用LinQ判斷即將Insert的實體是否已經在蛇身實體結合中存在即可。實現代碼如下:

       /// <summary>
        /// 蛇身在移動圖上移動
        /// </summary>
        /// <param name="panel"></param>
        /// <param name="map"></param>
        /// <param name="snake"></param>
        /// <param name="direction"></param>
        /// <param name="enableBack"></param>
        /// <param name="dead"></param>
        /// <returns></returns>
        public static ModelSnake MoveSnakeOnMap(Panel panel, ModelMap map, ModelSnake snake, ModelEnum.Direction direction, bool enableBack, out bool dead)
        {
            dead = false;
            if (!enableBack)
            {
                if (direction.Equals(ModelEnum.Direction.Up) && snake.Direction.Equals(ModelEnum.Direction.Down))
                    direction = snake.Direction;
                else if (direction.Equals(ModelEnum.Direction.Down) && snake.Direction.Equals(ModelEnum.Direction.Up))
                    direction = snake.Direction;
                else if (direction.Equals(ModelEnum.Direction.Left) && snake.Direction.Equals(ModelEnum.Direction.Right))
                    direction = snake.Direction;
                else if (direction.Equals(ModelEnum.Direction.Right) &&
                         snake.Direction.Equals(ModelEnum.Direction.Left))
                    direction = snake.Direction;
            }
            else
            {
                if (direction.Equals(ModelEnum.Direction.Up) && snake.Direction.Equals(ModelEnum.Direction.Down))
                    snake.Body.Reverse();
                else if (direction.Equals(ModelEnum.Direction.Down) && snake.Direction.Equals(ModelEnum.Direction.Up))
                    snake.Body.Reverse();
                else if (direction.Equals(ModelEnum.Direction.Left) && snake.Direction.Equals(ModelEnum.Direction.Right))
                    snake.Body.Reverse();
                else if (direction.Equals(ModelEnum.Direction.Right) &&
                         snake.Direction.Equals(ModelEnum.Direction.Left))
                    snake.Body.Reverse();
            }

            var head = new ModelElement
                {
                    Abscissa = snake.Body[0].Abscissa,
                    Ordinate = snake.Body[0].Ordinate
                };
            switch (direction)
            {
                case ModelEnum.Direction.Left:
                    head.Abscissa--;
                    break;
                case ModelEnum.Direction.Right:
                    head.Abscissa++;
                    break;
                case ModelEnum.Direction.Up:
                    head.Ordinate--;
                    break;
                case ModelEnum.Direction.Down:
                    head.Ordinate++;
                    break;
            }
            if (head.Abscissa < 0) head.Abscissa = map.Column - 1;
            else if (head.Abscissa == map.Column) head.Abscissa = 0;
            if (head.Ordinate < 0) head.Ordinate = map.Row - 1;
            else if (head.Ordinate == map.Row) head.Ordinate = 0;

            var d = snake.Body.SingleOrDefault(t => t.Abscissa == head.Abscissa && t.Ordinate == head.Ordinate);
            if (d != null)
            {
                dead = true;
            }

            var tail = snake.Body[snake.Body.Count - 1];
            var m = map.Body.SingleOrDefault(t => t.Bonus && t.Abscissa == tail.Abscissa && t.Ordinate == tail.Ordinate);
            if (m == null)
            {
                DrawMapBox(panel, map.Color, tail.Abscissa, tail.Ordinate, map.Box.Width, map.Box.Height);
                snake.Body.Remove(tail);
            }
            else
            {
                DrawMapBox(panel, snake.Color, head.Abscissa, head.Ordinate, map.Box.Width, map.Box.Height);
                m.Bonus = false;
            }

            DrawMapBox(panel, snake.Color, head.Abscissa, head.Ordinate, map.Box.Width, map.Box.Height);
            snake.Body.Insert(0, head);
            snake.Direction = direction;

            return snake;
        }
View Code

5、隨機獎勵

總體實現方法:在蛇身移動的Insert之前,判斷一下即將移動到的地圖格子是否包含獎勵就可以了。如果包含格子,多增加一個蛇身格子就可以了。第四部分代碼中已經包含這部分代碼了。

 

到此核心的代碼就已經完成了,剩下的就是將這些方法組合起來了。在這里就不多寫了,附上源代碼,額外增加了獎勵永存和倒車功能,開玩~~~

備注:暫未實現鍵盤監測,只能通過界面上的箭頭按鈕進行控制。

源代碼下載地址:https://github.com/songhaipeng/imStudio.Game

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM