这次在工作之余用C#写了一个简单的贪吃蛇程序,一般的都是WinForm形式的,这次弄了个控制台版本的,因为C# Console全部都是输入输出流,要在CMD窗口做这种有前台UI界面的程序应该是不适合的,但是想起之前的DOS版本的系统,我觉得应该是可以做到了,所以就花了几个晚上弄了这么一个东西,先上个截图:
界面比较简单,一个CMD窗口,其他的就是由字符构成的各种形状,做这种Console的贪吃蛇有以下几个需要注意的地方:
1.理解Console这个东西,它是一个标准的I/O输入输出流;
2.控制台有2个术语 :屏幕缓冲区和控制台窗口,我们一般获取的大小是控制台大小而不是缓冲区大小,这个可以看看MSDN;
3.关于贪吃蛇本身的有一下几个问题:如何让蛇移动?如何判断蛇吃了食物?如何判断蛇碰了边框?等等,下面就详细的说说。
首先,我想说,不管做什么程序或者软件,设计数据结构非常重要,数据结构设计好了,问题也就变的简单了,真的如此!
按照面向对象的要求,设计Snake类,由于Console程序中全部是以字符形式的,所以画Snake必须要有点,点构成线,首先设计Point类如下:
1.Point.cs
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace Snake
- {
- /// <summary>
- /// Class Point
- /// </summary>
- public class Point : IComparable<Point>
- {
- private int x = 0;
- /// <summary>
- /// Gets or sets the x
- /// </summary>
- public int X
- {
- get { return x; }
- set { x = value; }
- }
- private int y = 0;
- /// <summary>
- /// Gets or sets the y
- /// </summary>
- public int Y
- {
- get { return y; }
- set { y = value; }
- }
- /// <summary>
- /// Compare the two point
- /// </summary>
- /// <param name="other">Other point</param>
- /// <returns>-1 if x < other.X && y < other.Y , 0 if x == other.X && y == other.Y , otherwise 1</returns>
- public int CompareTo(Point other)
- {
- if (x < other.X && y < other.Y)
- {
- return -1;
- }
- else if (x == other.X && y == other.Y)
- {
- return 0;
- }
- else
- {
- return 1;
- }
- }
- }
- }
这里需要说一下这个point继承了IComparable接口,是为了下面的比较和判断蛇是否触碰了其他物体或者自己本身,好了下面就可以设计Snake.cs类了。
2.Snake.cs
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace Snake
- {
- /// <summary>
- /// Class Snake
- /// </summary>
- public class Snake
- {
- private Point head = null;
- /// <summary>
- /// Gets or sets the snake's head
- /// </summary>
- public Point Head
- {
- get { return head; }
- set { head = value; }
- }
- private Point[] body = null;
- /// <summary>
- /// Gets or sets the snake's body
- /// </summary>
- public Point[] Body
- {
- get { return body; }
- set { body = value; }
- }
- private Point tail = null;
- /// <summary>
- /// Gets or sets the snake's tail
- /// </summary>
- public Point Tail
- {
- get { return tail; }
- set { tail = value; }
- }
- }
- }
为什么要有蛇头、蛇身和蛇尾呢?简单的来说,按照面向对象的话你会想到的,复杂的就要看代码的逻辑处理了,这里不多说了,自己体会。接下来就是食物类Food.cs。
3.Food.cs
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace Snake
- {
- /// <summary>
- /// Class Food
- /// </summary>
- public class Food
- {
- private Point position = null;
- /// <summary>
- /// Gets or sets the food's position
- /// </summary>
- public Point Position
- {
- get { return position; }
- set { position = value; }
- }
- }
- }
食物就是一个位置上的一个点就足够了,没有其他属性。这样基本的数据结构就构造好了,剩下的就是怎么将这些结构联系起来构造这个游戏的逻辑了,首先,进入游戏,肯定是要按方向键来控制蛇的移动的,Console里面就有这样判断键盘输入的方法和属性,如下:
- ConsoleKey key = Console.ReadKey(true).Key;
ConsoleKey这个类里面就有属性判断是属于按下了键盘上的哪个键,这个可以看MSDN。ConsoleKey这个搞定了之后再来看看如何画出游戏界面,这个就不是太复杂,Console类里面有这样的一个方法如下:
- Console.SetCursorPosition(i, j);
设置光标所在的位置,这样就可以再这个为写入字符了,如果没有了这个方法,那我们就需要每次蛇移动都要重新画整个界面,这样会导致一个问题:控制台窗口屏幕会一直闪,因为我们是绘制整个控制台,有了这个方法,我们只需要刷新局部区域就可以了,比如蛇移动我们只需要更新蛇的位置就可以了。 最后说一下蛇碰到食物和边框之后怎么处理,当蛇碰到食物,这时候我们就可以将食物作为当前的蛇头,之前的蛇头作为蛇的身体的第一个节点,这样蛇身长度加1,蛇尾还是原来的蛇尾;当蛇头碰到边框游戏就结束。还有,当蛇移动且没碰到食物也没碰到边框的时候,这时蛇头就变成蛇当前移动到的点,蛇身的第一个点就变成了之前的蛇头的那个点,以此类推,蛇就像前移动了一个位置。 好了,大概就这么多了,最后就是画界面,代码如下:
- /// <summary>
- /// Draw console ui
- /// </summary>
- private void DrawConsoleUI()
- {
- Console.Clear();
- Point tempPoint = null;
- Console.SetCursorPosition(0, 0);
- Console.WriteLine("Name : " + name);
- Console.SetCursorPosition(0, 1);
- Console.WriteLine("Score : " + score);
- Console.SetCursorPosition(0, 2);
- Console.WriteLine("Press R to restart game after you losed the game, press E to exit game");
- for (int i = 0; i < uiWidth; i += 2)
- {
- Console.SetCursorPosition(i, 3);
- Console.Write(".");
- }
- for (int i = 2; i < uiHeight; i++)
- {
- for (int j = 0; j < uiWidth; j++)
- {
- tempPoint = new Point();
- tempPoint.X = j;
- tempPoint.Y = i;
- if (tempPoint.CompareTo(currentFood.Position) == 0)
- {
- Console.SetCursorPosition(j, i);
- Console.Write(GetFoodElement());
- }
- else if (tempPoint.CompareTo(currentSnake.Head) == 0)
- {
- Console.SetCursorPosition(j, i);
- Console.Write(GetHeadElement());
- }
- else if (IsSnakeBodyCoverPoint(currentSnake.Body, tempPoint))
- {
- Console.SetCursorPosition(j, i);
- Console.Write(GetBodyElement());
- }
- else if (tempPoint.CompareTo(currentSnake.Tail) == 0)
- {
- Console.SetCursorPosition(j, i);
- Console.Write(GetTailElement());
- }
- }
- }
- }