贪吃蛇

很多人都玩过贪吃蛇这个游戏,今天我们就来简单完成一个贪吃蛇的小游戏。首先,贪吃蛇有以下几个要求:

  1. 只有一条蛇
  2. 每次只出现一个食物
  3. 蛇吃到食物长度会加1
  4. 蛇撞到墙或者撞到自己,游戏结束

一般情况下,写一些项目,会经常用到一种思想,MVC——Model(模型)-View(视图)-Controller(控制器),在Model里面通常会写一些游戏的所包含的各种事物的设计,例如:用结构体来表示蛇,食物······Model里面的基本都是只有游戏设计者才能看见的东西。而View里面主要存放显示视图的代码,也就是我们玩游戏的界面以及玩家所能看见的东西,最后Controller主要存放游戏的主体代码,也就是一个玩游戏的过程。

Model

我们先从Model开始,我们需要在游戏里面准备哪些东西?
蛇应该用二维数组表示还是链表表示,如果用二维数组,那么每一次蛇的移动都要从新规划坐标,会特别的麻烦,如果我们用链表,我们完全可以在链表的每个节点里面存放坐标,这样蛇一动起来就会比较容易。另外,蛇的前进方向也需要保存,不然如何移动。

由此,需要一个坐标的结构体,一个蛇的结构体,一个蛇前进方向的枚举,链表节点的结构体,还要一个游戏的结构体(用来存放墙的位置,宽度,高度,食物,分数,速度)

	//坐标结构体
	typedef struct Position
	{
		int x;
		int y;
	}Position;
	
	//蛇前进方向枚举
	typedef enum Direction
	{
		UP,RIGHT,DOWN,LEFT
	}Direction;
	
	//链表结点的结构体
	typedef struct Node
	{
		Position pos;
		struct Node *next;
	}Node;
	//蛇本身
	typedef struct Snake
	{
		Node *head;
		Direction direction;
	}Snake;
	//游戏
	typedef struct Game
	{
		Snake snake;
		Position food;
		int score;
		int speed;
		int width;
		int height;
	}Game;

然后就要开始初始化游戏的各项内容,有初始化就要有销毁(针对不同的情况)

	//蛇的初始化
	void SnakeInit(Snake *pSnake)
	{
		assert(pSnake);
		pSnake->head = NULL;
		pSnake->direction = RIGHT;
		for (int i = 0; i < 3; i++)
		{
			Node *newNode = (Node *)malloc(sizeof(Node));
			newNode->pos.x = 3 + i;
			newNode->pos.y = 3;
			newNode->next = pSnake->head;
			pSnake->head = newNode;
		}
	}
	
	bool IsOverSnake(Snake *pSnake, int x, int y)
	{
		for (Node *cur = pSnake->head; cur != NULL; cur = cur->next)
		{
			if (cur->pos.x == y && cur->pos.y == y)
			{
				return true;
			}
		}
		return false;
	}
	//食物初始化(以后会用到,因为每一次都要更新食物)
	void FoodInit(Position *pFood, int width, int height, Snake *pSnake)
	{
		assert(pFood);
		assert(pSnake);
		int x = 0;
		int y = 0;
		do
		{
			x = rand() % width;
			y = rand() % height;
		} while (IsOverSnake(pSnake, x, y));
		pFood->x = x;
		pFood->y = y;
		DisplayFood(x, y);
	}
	//游戏初始化
	void GameInit(Game *pGame)
	{
		assert(pGame);
		pGame->score = 0;
		pGame->width = 28;
		pGame->height = 27;
		pGame->speed = 300;
		SnakeInit(&pGame->snake);
		FoodInit(&pGame->food, pGame->width, pGame->height, &pGame->snake);
	}
	//蛇的销毁
	void SnakeDestroy(Snake *pSnake)
	{
		assert(pSnake);
		Node *cur, *next;
		for (cur = pSnake->head; cur != NULL; cur = next)
		{
			next = cur->next;
			free(cur);
		}
	}
	//游戏销毁
	void GameDestroy(Game *pGame)
	{
		assert(pGame);
		SnakeDestroy(&pGame->snake);
	}

蛇又是如何移动的?我们可以将蛇看做去尾添头,就是把尾巴去掉,再在头解一个节点,蛇就移动了

	//添头
	void AddSnakeHead(Snake *pSnake, Position nextPos)
	{
		assert(pSnake);
		Node *newNode = (Node *)malloc(sizeof(Node));
		newNode->pos.x = nextPos.x;
		newNode->pos.y = nextPos.y;
		newNode->next = pSnake->head;
		pSnake->head = newNode;
		DisplaySnake(pSnake); //这个函数在View里面
	}
	//去尾
	void RemoveSnakeTail(Snake *pSnake)
	{
		assert(pSnake);
		Node *cur;
		for (cur = pSnake->head; cur->next->next != NULL; cur = cur->next)
		{
			;
		}
		CleanSnakeBlock(cur->next->pos.x, cur->next->pos.y);//这个函数也在View里面
		free(cur->next);
		cur->next = NULL;
	}

View

我们需要在视图里面展示墙、蛇、食物、分数

	//显示墙
	void DisplayWall(int width, int height)
	{
		SetCurPos(0, 0);
		for (int i = 0; i < width + 2; i++)
		{
			printf("▇");
		}
		SetCurPos(0, height + 1);
		for (int i = 0; i < width + 2; i++)
		{
			printf("▇");
		}
		for (int i = 0; i < height + 2; i++)
		{
			SetCurPos(0, i);
			printf("▇");
		}
		for (int i = 0; i < height + 2; i++)
		{
			SetCurPos(2 * (width + 1), i);
			printf("▇");
		}
	}
	//显示蛇
	//下面代码中坐标会*2是因为食物以及蛇本身用到的"▇"是一个中文符号,占两个字节,也就是两个光标位置
	void DisplaySnake(Snake *pSnake)
	{
		assert(pSnake);
		for (Node *cur = pSnake->head; cur != NULL; cur = cur->next)
		{
			SetCurPos(2 * (cur->pos.x + 1), cur->pos.y + 1);
			if (cur == pSnake->head)
			{
				printf("⊙");
			}
			else
			{
				printf("▇");
			}
		}
	}
	//显示食物
	void DisplayFood(int x, int y)
	{
		SetCurPos(2 * (x + 1), y + 1);
		printf("▇");
	}
	//显示分数
	void DisplayScore(int score)
	{
		SetCurPos(30 * 2 + 10, 10);
		printf("得分: %d", score);
	}

上面函数中有一个SetCurPos的函数,是用来设置光标的位置,然后接下来的内容就会在光标位置处显示
下面代码中用到了几个Windows的函数

	void SetCurPos(int x, int y)
	{
		HANDLE hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
		COORD coord = { x, y };
		SetConsoleCursorPosition(hStdOutput, coord);
	}
	//隐藏光标
	void ViewInit(int width, int height)
	{
		HANDLE hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
		CONSOLE_CURSOR_INFO info;
		GetConsoleCursorInfo(hStdOutput, &info);
		info.bVisible = 0;
		SetConsoleCursorInfo(hStdOutput, &info);
	}

Controller


	static void Pause()
	{
		while (1)
		{
			Sleep(300);
			if (GetAsyncKeyState(VK_SPACE))
			{
				break;
			}
		}
	}
	
	static Position GetNextPosition(Snake *pSnake)
	{
		Position nextPos = pSnake->head->pos;
		switch (pSnake->direction)
		{
		case UP:
			nextPos.y -= 1;
			break;
		case DOWN:
			nextPos.y += 1;
			break;
		case LEFT:
			nextPos.x -= 1;
			break;
		case RIGHT:
			nextPos.x += 1;
			break;
		}
		return nextPos;
	}
	//判断食物是否被吃
	static bool IsEaten(Position nextPos, Position food)
	{
		return nextPos.x == food.x && nextPos.y == food.y;
	}
	//判断是否撞到墙
	static bool IsHitWall(Position nextPos, int width, int height)
	{
		if (nextPos.x < 0)
		{
			return true;
		}
		if (nextPos.x >= width)
		{
			return true;
		}
		if (nextPos.y < 0)
		{
			return true;
		}
		if (nextPos.y >= height)
		{
			return true;
		}
		return false;
	}
	//判断是否撞到自己
	static bool IsHitSelf(Position nextPos, Snake *pSnake)
	{
		for (Node *cur = pSnake->head->next; cur != NULL; cur = cur->next)
		{
			if (cur->pos.x == nextPos.x && cur->pos.y == nextPos.y)
			{
				return true;
			}
		}
		return false;
	}
	
	void test()
	{
		Game game;
		GameInit(&game);
		ViewInit(game.width, game.height);
		DisplayWall(game.width, game.height);
		DisplaySnake(&game.snake);
		DisplayScore(game.score);
		SetCurPos(30 * 2 + 10, 7);
		printf("上下左右按键表示移动,SPACE表示暂停,F1加速");
		while (1)
		{
			if (GetAsyncKeyState(VK_UP) && game.snake.direction != DOWN)
			{
				game.snake.direction = UP;
			}
			else if (GetAsyncKeyState(VK_DOWN) && game.snake.direction != UP)
			{
				game.snake.direction = DOWN;
			}
			else if (GetAsyncKeyState(VK_LEFT) && game.snake.direction != RIGHT)
			{
				game.snake.direction = LEFT;
			}
			else if (GetAsyncKeyState(VK_RIGHT) && game.snake.direction != LEFT)
			{
				game.snake.direction = RIGHT;
			}
			else if (GetAsyncKeyState(VK_SPACE))
			{
				Pause();
			}
			else if (GetAsyncKeyState(VK_F1)) {
				game.speed = 100;
			}
			Position nextPos = GetNextPosition(&game.snake);
			if (IsEaten(nextPos, game.food))
			{
				AddSnakeHead(&game.snake, nextPos);
				game.score += 10;
				if (game.speed >= 100)
				{
					game.speed -= 20;
				}
				DisplayScore(game.score);
				FoodInit(&game.food, game.width, game.height, &game.snake);
			}
			else
			{
				RemoveSnakeTail(&game.snake);
				AddSnakeHead(&game.snake, nextPos);
			}
			if (IsHitWall(nextPos, game.width, game.height))
			{
				break;
			}
			if (IsHitSelf(nextPos, &game.snake))
			{
				break;
			}
			Sleep(game.speed);
		}
		GameDestroy(&game);
	}

上述代码中GetAsyncKeyState()函数用于获取按键信息(上下左右按键)。

上述代码不完整,附完整代码链接
博主贪吃蛇代码

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值