很多人都玩过贪吃蛇这个游戏,今天我们就来简单完成一个贪吃蛇的小游戏。首先,贪吃蛇有以下几个要求:
- 只有一条蛇
- 每次只出现一个食物
- 蛇吃到食物长度会加1
- 蛇撞到墙或者撞到自己,游戏结束
一般情况下,写一些项目,会经常用到一种思想,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()函数用于获取按键信息(上下左右按键)。
上述代码不完整,附完整代码链接
博主贪吃蛇代码