贪吃蛇小游戏

贪吃蛇介绍

贪吃蛇是一款很老的游戏了,大家一定都玩过,我们一起写个简单的贪吃蛇游戏吧。

本款贪吃蛇的规则如下:

1.不能撞自己

2.不能撞墙

3.蛇吃食物的时候,自身要长一截

4.可以通过用户控制对蛇加速和减速

5.更重要的一点是游戏当然得有加分。

这里我们可以从MVC的角度考虑(Model View Controller)

Model :数据结构/结构体,就是整个游戏的结构

View : UI Console/Web/MFC,在这里指用户界面,就是我们我们表面看到的

Controller : 控制流程 算法/函数,这里指的是整个游戏的运行

Model层

Model层,我们需要对游戏初始化,这就涉及了蛇的初始化以及食物的初始化了,我们规定蛇的初识长度为3,是以链表的方式进行存储,初始化蛇的时候要注意以上规则的前两点,而初始化食物也需要注意一下三点:

食物初始化的原则:

1.不能和蛇重叠

2.不能出墙

3.要有随机性

Model.h

#pragma once
#include
// 坐标结构体
typedef struct {
	int	x;
	int y;
}	Position;

// 链表的结点结构体
typedef struct Node {
	Position pos;
	struct Node *next;
}	Node;

// 蛇的前进方向
typedef enum {
	UP, RIGHT, DOWN, LEFT
}	Direction;

// 蛇
typedef struct {
	Node *head;	// 蛇头结点,链表的第一个结点
	Node *tail;
	Direction direction;
}	Snake;

// 游戏
typedef struct {
	Snake		snake;
	Position	food;
	int			width;
	int			height;
	int			speed;
	int			score;
}	Game;

void GameInit(Game *pGame);
void GameDestroy(Game *pGame);
void FoodInit(Position *pFood, int width, int height,
	const Snake *pSnake);


void SnakeAddHead(Snake *pSnake, Position nextPos);
void SnakeRemoveTail(Snake *pSnake);

Model.c

这里我们需要注意:蛇是以链表的形式存储的,但是蛇头是链表的尾,而蛇尾是链表的头,这里听起来可能有点绕,但理解起来并不是很困难。

#include "Model.h"
#include "View.h"
#include 
#include 

/*
static:
1. 修饰全局变量
修改链接属性	从外部修改成内部
2. 修饰局部变量
修改生命周期
3. 修饰函数
修改链接属性	从外部修改成内部
*/
static void SnakeInit(Snake *pSnake)
{//蛇的初始化
	pSnake->direction = RIGHT;
	pSnake->head = NULL;
	for (int i = 0; i < 3; i++) {
		int x = 3 + i;
		int y = 3;
		Node *node = (Node *)malloc(sizeof(Node));
		node->pos.x = x;
		node->pos.y = y;

		node->next = pSnake->head;
		pSnake->head = node;
	}
}

// 本质上是链表的查找问题
static bool IsOverlapSnake(int x, int y, const Snake *pSnake)
{//看蛇是否会撞到自己
	for (Node *cur = pSnake->head; cur != NULL; cur = cur->next) {
		if (x == cur->pos.x && y == cur->pos.y) {
			return true;
		}
	}

	return false;
}

// 生成食物
// 1. 随机
// 2. 不能出框
// 3. 不能和蛇重叠
void FoodInit(Position *pFood, int width, int height, const Snake *pSnake)
{
	int x;
	int y;

	do {
		x = rand() % width;
		y = rand() % height;
	} while (IsOverlapSnake(x, y, pSnake));

	pFood->x = x;
	pFood->y = y;
	DisplayFood(x, y);
}

void GameInit(Game *pGame)
{
	pGame->width = 28;
	pGame->height = 27;
	SnakeInit(&pGame->snake);
	FoodInit(&pGame->food, pGame->width, pGame->height, &pGame->snake);
	pGame->speed = 300;	//每个周期的间隔,单位是ms
	pGame->score = 0;
}

static void SnakeDestroy(Snake *pSnake)
{
	Node *cur, *next;
	for (cur = pSnake->head; cur != NULL; cur = next) {
		next = cur->next;
		free(cur);
	}
}

void GameDestroy(Game *pGame)
{
	SnakeDestroy(&pGame->snake);
}

void SnakeAddHead(Snake *pSnake, Position nextPos)
{
	Node *node = (Node *)malloc(sizeof(Node));
	node->pos.x = nextPos.x;
	node->pos.y = nextPos.y;

	node->next = pSnake->head;
	pSnake->head = node;

	DisplaySnakeBlock(nextPos.x, nextPos.y);
}

void SnakeRemoveTail(Snake *pSnake)
{
	// 删除最后一个结点,需要找到倒数第二个
	// 不需要去判断链表中有几个结点,因为一开始就有 3 个
	Node *cur;
	for (cur = pSnake->head; cur->next->next != NULL; cur = cur->next) {
	}
	CleanSnakeBlock(cur->next->pos.x, cur->next->pos.y);
	free(cur->next);
	cur->next = NULL;
}

Controller层

在这一层我们开始考虑运行游戏,以RunGame()展开,先获取蛇的方向,然后判断蛇是否会迟到食物,或者撞到自己,撞到墙…一系列情况。主要代码如下:

Controller.c

#include "Model.h"
#include "View.h"
#include 
#include 

static Position GetNextPosition(const Snake *pSnake)
{
	Position nextPos = pSnake->head->pos;

	switch (pSnake->direction) {
	case UP:
		nextPos.y -= 1;
		break;
	case RIGHT:
		nextPos.x += 1;
		break;
	case DOWN:
		nextPos.y += 1;
		break;
	case LEFT:
		nextPos.x -= 1;
		break;
	}

	return nextPos;
}

/*
inline 内联函数
*/
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 RunGame()
{
	Game game;
	GameInit(&game);
	ViewInit(game.width, game.height);
	DisplayWall(game.width, game.height);
	DisplaySnake(&game.snake);
	int count = 0;
	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_F1)) {
			game.speed -= 100;
		}
		else if (GetAsyncKeyState(VK_F2)) {
			game.speed += 100;
		}


		Position nextPos = GetNextPosition(&game.snake);
		if (IsEaten(nextPos, game.food)) {
			SnakeAddHead(&game.snake, nextPos);
			game.score += 10;
			if (game.speed >= 100) {
				game.speed -= 10;
			}
			FoodInit(&game.food, game.width, game.height, &game.snake);
		}
		else {
			SnakeAddHead(&game.snake, nextPos);
			SnakeRemoveTail(&game.snake);
		}

		if (IsHitWall(nextPos, game.width, game.height)) {
			break;
		}

		if (IsHitSelf(nextPos, &game.snake)) {
			break;
		}

		Sleep(game.speed);
	}
	SetCurPos(78, 20);
	printf("玩家得分:%d", game.score);
	GameDestroy(&game);
}


int main()
{
	RunGame();
	return 0;

}

View层

在这一层,我们将实现游戏的美观,就是简单的呈现出一个界面(虽然界面有点low),我们会看到墙,看到蛇,看到食物。
注意,我们坐标是x轴正方向向右,y轴正方向向下,如下图:

贪吃蛇小游戏_第1张图片

View.c

#include "Model.h"
#include 
#include 

 void SetCurPos(int X, int Y)
{
	HANDLE hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	// 标准输入/标准输出/标准错误输出
	COORD coord = { X, Y };
	SetConsoleCursorPosition(hStdOutput, coord);
}
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("█");
	}
	SetCurPos(74, 8);
	printf("※欢迎来到贪吃蛇小游戏......");
	SetCurPos(78, 10);
	printf("请按↑ ↓ ← →控制方向");
	SetCurPos(78, 12);
	printf("     F1加速    F2减速  ");
	SetCurPos(78, 14);
	printf("     ESC退出游戏!      ");

}
void DisplayFood(int x, int y)
{
	SetCurPos(2*(x + 1), y + 1);
	printf("◎");
}
void DisplaySnake(const Snake *pSnake)
{
	for (Node *cur = pSnake->head; cur != NULL; cur = cur->next) {
		SetCurPos(2*cur->pos.x+1, cur->pos.y+1);
		printf("▇");
	}
}

void DisplaySnakeBlock(int x, int y)
{
	SetCurPos(2*(x+1), y+1);	
	printf("▇");
}
void CleanSnakeBlock(int x, int y)
{
	SetCurPos(2*(x+1), y+1);	// 先统一两个坐标系
	printf("  ");
}
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);
}

View层实现后我们会看到以下界面:
贪吃蛇小游戏_第2张图片

在整个过程中,我们用到了一些新的函数,例如:控制方向的,这就涉及到了句柄的概念,还有隐藏光标的,放置光标的函数,这些不用深究,简单了解即可。

你可能感兴趣的:(C语言,数据结构)