本文共 6665 字,大约阅读时间需要 22 分钟。
很多人都玩过贪吃蛇这个游戏,今天我们就来简单完成一个贪吃蛇的小游戏。首先,贪吃蛇有以下几个要求:
一般情况下,写一些项目,会经常用到一种思想,MVC——Model(模型)-View(视图)-Controller(控制器),在Model里面通常会写一些游戏的所包含的各种事物的设计,例如:用结构体来表示蛇,食物······Model里面的基本都是只有游戏设计者才能看见的东西。而View里面主要存放显示视图的代码,也就是我们玩游戏的界面以及玩家所能看见的东西,最后Controller主要存放游戏的主体代码,也就是一个玩游戏的过程。
我们先从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; }
我们需要在视图里面展示墙、蛇、食物、分数
//显示墙 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); }
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()函数用于获取按键信息(上下左右按键)。
上述代码不完整,附完整代码链接
转载地址:http://ddwdb.baihongyu.com/