其实第四周是考试没有作业,周末自己做了一下贪吃蛇。
总结分为: 教训、思路、代码三个部分。
一.教训
这次的教训总结的都是浪费1-3小时找BUG的点
1.一个方法只做一件事
在写随机生成实物的方法时,一运行就报错引用没添加到对象上,但是明明之前运行没问题,找来找去才发现原来是把设置地图参数和打印地图放在一个方法里,导致生成食物放在这个2个功能的方法前报错。
2.按键按下和蛇移动中间间隔时间不好把握的问题
使用If判断+while+sleep解决了快速按其他方向然后按与运动方向相反方向可走反方向的Bug,多线程会出现这个问题。花了很多时间在多线程解决这个问题,最终也没想出来。
3.没想清楚是把二维数组里的块加到List列表里还是直接在列表里new一个block
由于一开始没想清楚这个问题,导致后面判断碰蛇身死亡的时候无法生效,其实原因就是我其实是在列表里new了block的,但是思路还是以为蛇身是二维数组里的block。
4.没想清楚蛇应该怎么移动
一开始想给每节身子写个方法,遍历他们让他们自己调用自己的移动方法,只需要给他们一个目标位置即可。但是实际写代码的过程中发现没有很好的办法在遍历所有身子的情况下储存前面身子移动前的位置,只好改主意为让蛇头在方向上insert一个块,再在尾部最后抹去一个块。
5.最后做出来又发现每次刷新地图,屏幕闪的难受
于是又改变画的方式,改成每次单独画食物,蛇身遍历List调用画自己的方法,同时抹去食物和蛇尾都改成单独抹去。
6.由于第五点的改动引出一个新BUG
有小概率,食物会出现在蛇身的位置,那么蛇身画的时候就会抹去食物,那么久看不到绿色的食物了,但是实际食物还是在的!食物的那个二维数组格子的状态是有效的,最后在随机生成食物的方法里遍历了一遍蛇身的坐标,让食物不能在蛇身所在之处生成。
二.思路
1.使用面向对象的思路写出基类Block,里面有所有游戏物体都具有的坐标,状态和画自己的方法;
2.使用List列表来储存蛇身,初始在屏幕中间生产蛇头,状态标记为蛇头;
3.蛇的移动是在List的索引0位置insert一个新的block块标记为蛇头,索引1标记为蛇身,同时remove掉count-1位置的蛇身;
4.随机在状态为normal且不为蛇身的块上生成食物,状态标为food;
5.蛇头位置与食物一致时,食物状态变为normal,在count-1的位置add一个新蛇身,由于是在蛇尾处新生成,在判定死亡的时候必须在count>2时才开始,不然一吃实物就死亡了。
6.判死亡是当蛇头位置和边界border一致时死亡,或者蛇头位置和蛇身一致的时候死亡,此时是遍历蛇身的坐标,如果蛇头和他们任一一个重合则死亡。
7.这里食物是单独画的在CreateFood方法里,单独抹去和改状态在DetectFood方法里;蛇尾的抹去也是单独抹去的,在SnakeMove方法里。
三.代码
一共2个类:Program和Block
下面就直接上全部代码:
Block
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public struct Vector2
{
public int x;
public int y;
public Vector2(int x, int y)
{
this.x = x;
this.y = y;
}
public static Vector2 Up = new Vector2(0, -1);
public static Vector2 Down = new Vector2(0, 1);
public static Vector2 Left = new Vector2(-1, 0);
public static Vector2 Right = new Vector2(1, 0);
}
public class Block
{
public Vector2 position;
public ConsoleColor foreColor;
public ConsoleColor backColor;
public ObjectState state;
public Block(int x, int y, ObjectState state)
{
position.x = x;
position.y = y;
this.state = state;
}
public void Draw()
{
ConsoleColor tempForeColor = Console.ForegroundColor;
ConsoleColor tempBackColor = Console.BackgroundColor;
SetColor();
Console.ForegroundColor = foreColor;
Console.BackgroundColor = backColor;
Console.SetCursorPosition(position.x * 2, position.y);
Console.Write("□");
Console.ForegroundColor = tempForeColor;
Console.BackgroundColor = tempBackColor;
}
public void SetColor()
{
switch (state)
{
case ObjectState.SnakeHead:
foreColor = ConsoleColor.Yellow;
backColor = ConsoleColor.Black;
break;
case ObjectState.SnakeBody:
foreColor = ConsoleColor.White;
backColor = ConsoleColor.Black;
break;
case ObjectState.Food:
foreColor = ConsoleColor.Green;
backColor = ConsoleColor.Green;
break;
case ObjectState.Border:
foreColor = ConsoleColor.Gray;
backColor = ConsoleColor.Gray;
break;
case ObjectState.normal:
foreColor = ConsoleColor.Black;
backColor = ConsoleColor.Black;
break;
}
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`
Program:
public enum ObjectState
{
SnakeHead,
SnakeBody,
Food,
Border,
normal
}
class Program
{
public static int Wide = 25;
public static int Height = 20;
public static Block[,] blocks = new Block[Height, Wide];
public static List<Block> snakeList = new List<Block>();
public static Vector2 CurrentDir = Vector2.Left;
public static int moveInterval = 500;
public static bool isGameOver = false;
public static Random roll = new Random();
static void Main(string[] args)
{
Console.CursorVisible = false;//光标不可见
AddSnakeHead(Wide / 2, Height / 2);
SetMap();//一个方法只做一件事,这个耽误我2-3小时
CreateFood();
DrawMap();
while (!isGameOver)
{
switch (snakeList.Count.ToString())
{
case "5": moveInterval = 300; break;
case "20": moveInterval = 200; break;
case "50": moveInterval = 150; break;
case "80": moveInterval = 100; break;
case "100": moveInterval = 50; break;
}
if (Console.KeyAvailable)//使用If判断+while+sleep解决了快速按其他方向然后按与运动方向相反方向可走反方向的Bug,多线程会出现这个问题,这个耽误我2-3小时
{
#region 键盘监听
ConsoleKeyInfo info = Console.ReadKey(true);
switch (info.Key)
{
case ConsoleKey.LeftArrow:
if (!CurrentDir.Equals(Vector2.Right)) CurrentDir = Vector2.Left;
else CurrentDir = Vector2.Right;
break;
case ConsoleKey.UpArrow:
if (!CurrentDir.Equals(Vector2.Down)) CurrentDir = Vector2.Up;
else CurrentDir = Vector2.Down;
break;
case ConsoleKey.RightArrow:
if (!CurrentDir.Equals(Vector2.Left)) CurrentDir = Vector2.Right;
else CurrentDir = Vector2.Left;
break;
case ConsoleKey.DownArrow:
if (!CurrentDir.Equals(Vector2.Up)) CurrentDir = Vector2.Down;
else CurrentDir = Vector2.Up;
break;
}
#endregion
SnakeMove(CurrentDir);
DetectFood();
DrawSnake();
DetectDeath();
Thread.Sleep(moveInterval);
}
else
{
SnakeMove(CurrentDir);//如果没有按键输入,就按当前方向移动
DetectFood();
DrawSnake();
DetectDeath();
Thread.Sleep(moveInterval);
}
}
Console.SetCursorPosition(0, 20);
Console.WriteLine("游戏结束!!");
Console.WriteLine($"蛇的长度达到了{snakeList.Count}节,不错,再接再厉!");
Console.SetCursorPosition(0, 22);
Console.ReadKey(true);
}
public static void DetectDeath()
{
if (snakeList.Count>2)//这个判断是为了不会在第一次吃食物就死亡,因为增加的身体的坐标是列表的末尾的坐标
{
for (int i = 1; i < snakeList.Count; i++)
{
if (snakeList[0].position.x == snakeList[i].position.x && snakeList[0].position.y == snakeList[i].position.y)
{
isGameOver = true;
}
}
}
if (blocks[snakeList[0].position.y, snakeList[0].position.x].state == ObjectState.Border)
{
isGameOver = true;
}
}
public static void DetectFood()
{
if (blocks[snakeList[0].position.y, snakeList[0].position.x ].state==ObjectState.Food)
{
AddSnakeBody();
blocks[snakeList[0].position.y , snakeList[0].position.x ].state = ObjectState.normal;
//单独抹去点
Console.SetCursorPosition(snakeList[0].position.x * 2, snakeList[0].position.y);
Console.Write(" ");
CreateFood();
}
}
public static void CreateFood()
{
bool isNottouched = true;
bool isCreated = false;
while (!isCreated)
{
for (int y = 0; y < blocks.GetLength(0); y++)
{
for (int x = 0; x < blocks.GetLength(1); x++)
{
foreach (var item in snakeList)
{
if (item.position.x == x && item.position.y == y)
{
isNottouched = false;//如果生成食物的坐标和列表里任一一个坐标相同则不能生成
}
}
if (blocks[y, x].state == ObjectState.normal&& isNottouched)
{
int randomNum = roll.Next(0, 501);
if (randomNum < 2)
{
blocks[y, x].foreColor = ConsoleColor.Green;
blocks[y, x].backColor = ConsoleColor.Green;
blocks[y, x].state = ObjectState.Food;
//单独画,不用二维数组画
ConsoleColor tempForeColor = Console.ForegroundColor;
ConsoleColor tempBackColor = Console.BackgroundColor;
Console.ForegroundColor = ConsoleColor.Green;
Console.BackgroundColor = ConsoleColor.Green;
Console.SetCursorPosition(x * 2, y);
Console.Write("□");
Console.ForegroundColor = tempForeColor;
Console.BackgroundColor = tempBackColor;
isCreated = true;
break;
}
}
isNottouched = true;
}
if (isCreated) break;
}
}
}
public static void SnakeMove(Vector2 direction)//先是想写个方法向目标物体移动,但写不出,浪费2-3小时
{
//在首位置插入一个新蛇头
snakeList.Insert(0, new Block(snakeList[0].position.x + direction.x, snakeList[0].position.y + direction.y, ObjectState.SnakeHead));
snakeList[1].state = ObjectState.SnakeBody;
//删除末尾
//尝试不刷新二维数组,单独抹去点
Console.SetCursorPosition(snakeList[snakeList.Count - 1].position.x*2, snakeList[snakeList.Count - 1].position.y);
Console.Write(" ");
snakeList.RemoveAt(snakeList.Count - 1);//最后抹去,不然就会把蛇头的坐标作为单独抹去的点
}
public static void AddSnakeHead(int x,int y)
{
snakeList.Add(new Block(x, y, ObjectState.SnakeHead));//这个是new出新的block,和二维数组里不是一个对象,这里没想清楚,浪费2小时
}
public static void AddSnakeBody()
{
snakeList.Add(new Block(snakeList[snakeList.Count-1].position.x, snakeList[snakeList.Count - 1].position.y, ObjectState.SnakeBody));//这个是new出新的block,和二维数组里不是一个对象,这里没想清楚,浪费2小时
}
public static void SetMap()
{
//初始化地图,画出边界
for (int y = 0; y < blocks.GetLength(0); y++)
{
for (int x = 0; x < blocks.GetLength(1); x++)
{
if (y == 0 || y == blocks.GetLength(0) - 1 || x == 0 || x == blocks.GetLength(1) - 1)
{
blocks[y, x] = new Block(x, y, ObjectState.Border);
}
else
{
blocks[y, x] = new Block(x, y, ObjectState.normal);
}
}
}
}
public static void DrawMap()
{
//打印地图
for (int y = 0; y < blocks.GetLength(0); y++)
{
for (int x = 0; x < blocks.GetLength(1); x++)
{
blocks[y, x].Draw();
}
}
}
public static void DrawSnake()
{
//为了屏幕不闪,改成单独画点和擦去点
//for (int y = 0; y < blocks.GetLength(0); y++)//刷新除边框的其他块
//{
// for (int x = 0; x < blocks.GetLength(1); x++)
// {
// if (y != 0 || y != blocks.GetLength(0) - 1 || x != 0 || x != blocks.GetLength(1) - 1)
// blocks[y, x].Draw();
// }
//}
foreach (var item in snakeList)
{
item.Draw();
}
}
}