基于控制台实现贪吃蛇游戏

1)、引言

  学习编程,我个人觉得最好的办法就是根据自己的水平不断的给自己设定一个小目标。而这个小目标就是一个有意思的项目,通过完成这个项目,对自己的成果(也包括失败的)进行分析总结,从中提炼出对应的技术并分享出来,不断的往复,如此,为的就是让我们永远保持编写程序的兴趣和热情,完了,还提高我们的技术。而本文就是总结自己的一个小目标(基于控制台实现的贪吃蛇游戏而写的总结)

2)、技术实现

  大家小时候一定玩过贪吃蛇的游戏。贪吃蛇游戏的控制过程其实也不复杂。简单的可以概括为以下4个部分。

 1.1  、组成蛇的小块以及食物(Block)

  在本程序中,食物以及蛇的组成都用一个对象表示,因为它们的作用都是一样,仅仅只需要一个坐标对。以及提供一个静态方法,即可以通过在游戏的地图内生随机产生一个坐标对并返回Block对象。

 1.2、管理蛇的部分(Snake)

  程序的主体主要“蛇”这个对象的属性以及方法的设计。现在我们想想在游戏中这个对象需要有哪些属性以及行为,首先“蛇”有长度以及“蛇”的组成,这就是蛇的属性,可以由一个相邻的Block类型的集合snakeList来表示;其次,蛇能移动(上下左右),能吃食物,并且不能碰到边框以及头部不能触碰自己的身体,这就可以抽象为蛇的三个行为,Move(),IsEatFood (),IsOver(),分别为移动,吃食物,检测自身是否满足游戏的规则。

  1、Move()

  //蛇移动的关键代码如下。

 public void Move(Direction dir)
        {
            Block head = this.snakeLIst[0];//获取蛇头
            this.snakeLIst.RemoveAt(this.snakeLIst .Count -1);//移除末尾项
            Block newBlock=null;
            switch (dir)//获取蛇当前运行的方向,然后把根据蛇头的位置计算出新的蛇头的位置。相当于把蛇尾的坐标进行计算插入到蛇头。
            {
                case Direction.Top :
                    newBlock = new Block(head.Row-1, head.Col);
                    break;
                case Direction .Bottom :
                    newBlock = new Block(head.Row+1, head.Col);
                    break;
                case Direction .Left :
                    newBlock = new Block(head.Row, head.Col-1);
                    break;
                case Direction .Right:
                    newBlock = new Block(head.Row , head.Col+1);
                    break;
            }
            this.snakeLIst.Insert(0, newBlock);//将新的位置插入到蛇头
        }

  蛇移动的程序的动态过程如下图:(以向左走为例)

  

   2、IsEatFood ()

  //代码如下

 /// <summary>
        /// 判断蛇是否达到食物的位置,如果到达这eat
        /// </summary>
        /// <param name="b">食物对象</param>
        /// <returns>返回bool,好让调用方知道是否需要产生新的食物</returns>
        public bool IsEatFood(Block b)
        {
            Block head = this.snakeLIst[0];//获取蛇头
            if (head.IsEqual(b))//是食物的位置一致
            {
                this.snakeLIst.Add(b);//添加一个block到蛇的集合中,并这下一次move中移动到蛇头,保证有序。
                return true ;
            }
            return false ;
        }

  3、IsOver()

  //代码如下 

 public bool IsOver()
        {
            Block head = this.snakeLIst[0];
            if (head.Row == 0 || head.Col == 0 || head.Row == 25 || head.Col == 80)//是否遇到边界
            {
                return true;
            }
            for (int i = 1; i < this.snakeLIst.Count; i++)//是否遇到自身
            {
                if (head.IsEqual(this.snakeLIst[i]))
                {
                    return true ;
                }
            }
            return false;
        }

 1.3、管理游戏界面的类(MapManager)

  我们都知道游戏都是画面不断变化的一个过程,所以我们必须在极短的时间内去更新游戏的画面,所以就离不开定时器。然后就可以实时的去绘制游戏的当前的状态,那么一系列的状态连起来就一个动态的游戏画面。

  • 1、我把整个游戏的地图存储在一个二位数组里面,如果其值为0则代表为空的,如果为1,则输出“#”,因为这和在窗体中绘制游戏画面不一样,控制台只能从左至右,从上至下的输出。
  • 2、首先绘制边界,实际上就是把二维数组(地图)中的某一些值赋值为1.
  • 3、建立“对象”并初始化,遍历组成蛇的Block集合,来获取”蛇“在二维数组(地图)的位置,并把对应的位置赋值为1.
  • 4、初始化食物,也即是block对象,在二维数组(地图)中找到他的位置,和第3步一样将对应的位置赋值为1。
  • 5、上面的4步已经把“蛇”、食物以及游戏的边界绘制出来,接下来就是蛇的运动了。蛇的运动就是每次调用“蛇”的Move()行为,然后再蛇运行之前都要判断下面两个条件,第一,当前蛇头的位置是不是在食物的位置,如果在则吃掉食物,生成新的食物,调用的方法为IsEatFood();第二,“蛇”是否满足规则,如果不满足则游戏结束,调用IsOver()。
  • 6、当上面的步骤都完成之后,即二维数组(地图)都赋值好了,然后再从上之下,从左至右依次输出(其实就是一长串字符串)。最后一步才是真正的在界面输出,其中前面的步骤都是游戏画面的缓存。关于计时器是使

  其中System.Threading中的Timer类,具体的用户可以查看其它资料,也可以看后面程序中是如何使用Timer类的

 1.4、与用户交互的设计

  程序将用户交互的接口放在了MapManager,主要功能为,启动计时器去管理游戏的绘制与运行,然后另外就是处理用户的输入去改变游戏运行状态。

其中,需要注意的是,由于是基于控制台的实现,用户的输入肯定是不能按enter之后然后程序才能接受,而是实时的接受用户的输入且还不能将用户的输入显示到控制中,还好c#提供了Console.Readkey(true),可以满足程序的要求。

 1.5、下面是程序运行的流程图

  

  注意:

其中需要注意的是,最后输出赋值好的二维数组(地图)时候,不是遍历二维数组(地图)遍历一项就输出一项,而是用StringBuilding对象去添加,直到遍历完了,一次性输出StringBuilding对象,达到双缓存的效果,使得控制台绘制不会闪烁。

3)程序编码的实现

  本程序是基于c#控制台实现的,开发工具为2013

  1、Block类

  public class Block
    {
        private int x;
        private int y;

        public Block()
        { 

        }
        public Block(int x, int y)
        {
            this.x = x;
            this.y = y;
        }

        public int Row
        {
            get { return this.x; }
            set { this.x = value; }
        }

        public int Col
        {
            get { return this.y; }
            set { this.y = value; }
        }
        public bool IsEqual(Block b)
        {
            if (this.x == b.Row&& this.y == b.Col)
            {
                return true;
            }
            return false;
        }

        public static Block ProvideFood()
        {
            Random r=new Random ();
            Block b = new Block(r.Next(1, 25), r.Next(1, 80));
            return b;
        }
    }

2、Snake类

public class Snake
    {
        List<Block> snakeLIst = new List<Block>();//存储蛇的结构

        public List<Block> SnakeLIst
        {
            get { return snakeLIst; }
        }

        public Snake()
        {
            InitSnake();
        }
        private void InitSnake()
        {
            int rowStart = 2;
            int colStart=5;
            int lenth = 20+colStart ;
            Block b;
            for (int i = colStart; i < lenth; i++)
            {
                b = new Block(rowStart ,i);
                this.snakeLIst.Insert(0, b);
            }
        }

        /// <summary>
        /// 判断蛇是否达到食物的位置,如果到达这eat
        /// </summary>
        /// <param name="b">食物对象</param>
        /// <returns>返回bool,好让调用方知道是否需要产生新的食物</returns>
        public bool IsEatFood(Block b)
        {
            Block head = this.snakeLIst[0];//获取蛇头
            if (head.IsEqual(b))//是食物的位置一致
            {
                this.snakeLIst.Add(b);//添加一个block到蛇的集合中,并这下一次move中移动到蛇头,保证有序。
                return true ;
            }
            return false ;
        }

        public void Move(Direction dir)
        {
            Block head = this.snakeLIst[0];//获取蛇头
            this.snakeLIst.RemoveAt(this.snakeLIst .Count -1);//移除末尾项
            Block newBlock=null;
            switch (dir)//获取蛇当前运行的方向,然后把根据蛇头的位置计算出新的蛇头的位置。相当于把蛇尾的坐标进行计算插入到蛇头。
            {
                case Direction.Top :
                    newBlock = new Block(head.Row-1, head.Col);
                    break;
                case Direction .Bottom :
                    newBlock = new Block(head.Row+1, head.Col);
                    break;
                case Direction .Left :
                    newBlock = new Block(head.Row, head.Col-1);
                    break;
                case Direction .Right:
                    newBlock = new Block(head.Row , head.Col+1);
                    break;
            }
            this.snakeLIst.Insert(0, newBlock);//将新的位置插入到蛇头
        }

        public bool IsOver()
        {
            Block head = this.snakeLIst[0];
            if (head.Row == 0 || head.Col == 0 || head.Row == 25 || head.Col == 80)//是否遇到边界
            {
                return true;
            }
            for (int i = 1; i < this.snakeLIst.Count; i++)//是否遇到自身
            {
                if (head.IsEqual(this.snakeLIst[i]))
                {
                    return true ;
                }
            }
            return false;
        }
    }

  3、MapManager类(包括与用户的交互)

 public class MapManager
    {
        const int row = 25;
        const int col = 80;
        Snake snake;//蛇
        Block b;//食物
        Timer t;//定时器
        int[,] gameMap = new int[row, col];//地图
        int count=0;
        StringBuilder mapBuffer;//缓存区
        bool isNormal = true;

        //初始化地图+绘制边界
        private void InitMap()
        {

            for (int i = 0; i < row; i++)
            {
                if (i == row - 1 || i == 0)
                {
                    for (int j = 0; j < col; j++)
                    {
                        this.gameMap[i, j] = 1;
                    }
                }
                else
                {
                    for (int j = 0; j < col; j++)
                    {
                        if (j == col - 1 || j == 0)
                        {
                            this.gameMap[i, j] = 1;
                        }
                        else
                        {
                            this.gameMap[i, j] = 0;
                        }
                    }
                }
            }
        }

        //绘制蛇
        private void InitSnake()
        {
            foreach (var s in snake.SnakeLIst)
            {
                gameMap[s.Row, s.Col] = 1;
            }
        }
        //绘制食物
        private void InitFood()
        {
            gameMap[this.b.Row, this.b.Col] = 1;
        }

        //输出控制台(游戏画面)
        private void DrawMap()
        {
            mapBuffer.Clear();
            Console.Clear();
            for (int i = 0; i < row; i++)
            {
                for (int j = 0; j < col; j++)
                {
                    if (gameMap[i, j] == 1)
                    {
                        mapBuffer.Append("*");
                    }
                    else
                    {
                        mapBuffer.Append(" ");
                    }
                }
                mapBuffer.Append("\n");

            }
            Console.WriteLine("\n-----------------------------当前得分{0}------------------------\n", count);
            Console .Write(mapBuffer .ToString ());//从缓存区中输出整个游戏画面
        }

        //游戏运行管理
        private void GameRun(object o)
        {
            InitMap();
            InitSnake();
            InitFood();
            if (snake.IsEatFood(b))
            {
                b = Block.ProvideFood();//产生新的食物
                count++;//得分
            }
            snake.Move(GlobalVar.dir);
            if (snake.IsOver())
            {
                GameOver();
            }
            DrawMap();
        }

        private void GameOver()
        {
            Console.Clear();
            Console.WriteLine("Game Over");
            isNormal = false;
            t.Dispose();
        }

        private void GameInit()
        {
            Console.WriteLine("按[w,s.a.d]作为上下左右,按[q]退出游戏!!!");
            Console.WriteLine("按任何键进入游戏");
            Console.ReadKey(true);
        }

        //程序开始,该方法包括启动定时器,以及与用户的交互
        public void Start()
        {
            GameInit();
            snake = new Snake();
            b = Block.ProvideFood();
            mapBuffer = new StringBuilder();
            //GameRun(null);
            t = new Timer(GameRun, null, 200, 100);
            char c;
            while (isNormal)
            {
                c=Console .ReadKey(true ).KeyChar ;
                switch (c)
                {
                    case ‘s‘:
                        if (GlobalVar.dir != Direction.Top)
                        {
                            GlobalVar.dir = Direction.Bottom;
                        }
                        break;
                    case ‘w‘:
                        if (GlobalVar.dir != Direction.Bottom)
                        {
                            GlobalVar.dir = Direction.Top;
                        }
                        break;
                    case ‘a‘:
                        if (GlobalVar.dir != Direction.Right)
                        {
                            GlobalVar.dir = Direction.Left;
                        }
                        break;
                    case ‘d‘:
                        if (GlobalVar.dir != Direction.Left)
                        {
                            GlobalVar.dir = Direction.Right;
                        }
                        break;
                    case ‘q‘:
                        GameOver();
                        break;
                }
            }
            Console.ReadLine();
        }
    }

  4、方向枚举(Direction)

    public enum Direction
    {
        Left,Right,Top,Bottom
    }

  5、main(程序入口)

static void Main(string[] args)
        {
            MapManager mm = new MapManager();
            mm.Start();

        }

  6、程序效果

   本来想插入视频,但是不可以直接上传,就截几个图吧。

  

4)结论

其实程序的最重要的部分是设计思路而不是编码,就这个程序也可以使用c、python等语言实现,都不是很那难。一旦程序的流程清晰了,编码的过程自然也会浮现出来啦。

  最后有需要源码的朋友们,可以留邮箱,也可以耐心等我整理好了放到一个公开的链接上,一起相互学习。

时间: 2024-10-13 16:21:46

基于控制台实现贪吃蛇游戏的相关文章

有意思的代码:控制台输出贪吃蛇游戏

/* F12贪吃蛇小游戏 */ (function () { function play(simsun,speed){ var maxLog = 200;//maxLog个就清除一次,以免浏览器卡死 var logNum = 0; var speed = speed?speed:3; if(simsun===false){ var cubeletter = '回'; }else{ cubeletter = '〓'; } var time = 1000/speed; var width=32,he

WebGL实现HTML5的3D贪吃蛇游戏

js1k.com收集了小于1k的javascript小例子,里面有很多很炫很酷的游戏和特效,今年规则又增加了新花样,传统的classic类型基础上又增加了WebGL类型,以及允许增加到2K的++类型,多次想尝试提交个小游戏但总无法写出让自己满意还能控制在这么小的字节范围. 自己写不出来,站在巨人肩膀总是有机会吧,想起<基于HTML5的电信网管3D机房监控应用>这篇提到的threejs,babylonjs和Hightopo的几种基于WebGL的3D引擎,突然想挑战下自己实现个100行JS的3D小

贪吃蛇游戏

学习C语言也差不多学完了想做一个游戏,而贪吃蛇和俄罗斯方块都是非常经典的游戏,在网上也找到了许多相关的参考资料,便动手做了,这个游戏室控制台版的 游戏流程图 函数模块 函数名 函数功能 CursorPosition 光标定位函数 CreateSnake 蛇初始化函数 ShowWall 显示墙体 UpdateSnake 更新界面上的蛇体.分数.等级.食物 CollisionDetection 判断蛇是否咬到自己 RandFood 随机产生食物 Move 控制方向 程序代码 #include <st

A.探路者——贪吃蛇游戏(测评人:黄泽宇)

一.基于NABCD评论作品,及改进建议 每个小组评论其他小组Alpha发布的作品:1.根据(不限于)NABCD评论作品的选题:2.评论作品对选题的实现效果:3.就现有技术和工作量,不改变选题的主要方向,为该作品在beta版本可增减的功能提出改进意见. 1.根据(不限于)NABCD评论作品的选题. 根据探路者的Alpha发布选题背景及意义进行考量: 贪吃蛇游戏团队的选题背景及意义 NABCD标准考量 贪吃蛇作为一个经典的游戏,设计简单,实用和娱乐性高.对于贪吃蛇传统的玩法,大家众所周知,即:玩家通

JS贪吃蛇游戏

<!DOCTYPE html><html> <head>    <meta charset="utf-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <title>JS贪吃蛇游戏</title>    <style>    * {        margin: 0;    

用Java开发贪吃蛇游戏

贪吃蛇游戏的设计步骤: Part 1: 设计游戏图纸 画出900*700的白色窗口 在窗口上添加画布 在画布上添加标题 在画布上添加黑色游戏区 Part 2: 放置静态的蛇:一个头.两个身体 加上开始提示:按空格键开始游戏 让蛇动起来:监听Timer事件,平移数据 实现游戏暂停 实现转向功能 Part 3: 添加食物 吃掉食物 添加死亡条件 实现“重新开始”功能 添加分数和长度 游戏图纸如下: 蛇及游戏框的素材如下:                              Snake主类: 1

Qt版贪吃蛇游戏

Qt版贪吃蛇游戏 转载请标明出处:牟尼的专栏 http://blog.csdn.net/u012027907 最近在学习Qt,用了一个多月的时间掌握了Qt中最基本的知识,也完成了<Qt版音乐播放器>.<Qt版贪吃蛇游戏>.<Qt版双人俄罗斯方块>以及<Qt版科学计算器>等,之前在VC下写过这些程序,所以在Qt下只是改变了显示等语句,我写过<C++版贪吃蛇游戏>.<VC版贪吃蛇游戏>,当时将与显示等无关的东西封装起来,在Qt下直接用,只

【141030】VC++贪吃蛇游戏源码(Win32+API)

不错的贪吃蛇游戏,运用了Win32的API.完整源代码,在VS2005下编译通过.内附有编程要点,很好的学习范例. 游戏源码下载地址:点击下载

结对-开发贪吃蛇游戏-开发环境搭建过程

项目:贪吃蛇游戏开发 环境搭建: Mac下: 1)官网下载jkd1.8版本. 2)下载IDE--eclipse或Myeclipse win下: 1)官网下载JDK,找到符合自己电脑的版本,下载的本地 2)下载IDE--eclipse或Myeclipse 3)配置环境变量: i.计算机->属性->高级系统设置 ii.单击高级系统设置->环境变量在系统变量里面分别设置JAVA_HOME.CLASSPATH和Path iii.在系统变量里找变量名JAVA_HOME,如果没有就点击新建.输入变量