在我们写这个小程序之前我们考虑以下的几点:
一、明确需求:
(1) 只有一条蛇
(2) 只有一个食物
(3) 游戏界面,只有边界和部分有墙
(4) game over : (1)撞墙 (2)撞自己
(5) 字符界面
二、MVC(model:数据结构、view:界面、 controller:控制流)
1. M:自顶向下分析
蛇:看作链表,用坐标表示
食物:坐标
墙(已知宽和高)
GAME
我们可以知道需要以下结构体:
(1) 坐标
(2) 链表
(3) 蛇(蛇头、方向)
(4) 方向
(5) 其他
#pragma once //坐标原点在左上角 //x是向右 y是向下 typedef struct Position{ int x; int y; }Position; //链表结点(单向) typedef struct Node{ struct Node *pNext; Position data; }Node; //方向 (枚举列举所有可能) typedef enum Direction{ UP, DOWN, LEFT, RIGHT }Direction; //蛇的结构 typedef struct Snake{ Node *pHead; Direction direction; }Snake; //游戏用到的其他非独立字段 typedef struct Game{ Snake snake; Position foodPosition; int sorePerfood; int core; int speed; int width; // 宽 int height; // 高 }Game;
2. V :view
(1)显示墙
#pragma once #include <windows.h> #include <stdio.h> #include "model.h" void SetPos(int X, int Y) { HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);//获得句柄 COORD coord;//实现光标的移动 coord.X = X; coord.Y = Y; SetConsoleCursorPosition(hStdout, coord); //控制光标 } //显示外墙 void DisplayWall(int width, int height) { int i; //上边 SetPos(0,0); for (i = 0; i < width + 2; i++) { printf("■"); } //下边 SetPos(0,height + 1); for (i = 0; i < width + 2; i++){ printf("■"); } //左边 for (i = 0; i < height +2 ; i++){ SetPos(0, i); printf("■"); } //右边 for (i = 0; i < height + 2; i++){ SetPos(2*(width + 1), i); //由于用了中文字符,一个中文是二个字符 printf("■"); } }(2)显示蛇
//显示蛇的位置 void DisplaySnakeNode(Position pos) { SetPos(2 * (pos.x + 1), pos.y + 1); //用到了中文字符 printf("●"); } //显示蛇 void DisplaySnake(const Snake *pSnake) { Node *pNode = pSnake->phead; while (pNode) { DisplaySnakeNode(pNode->data);//显示蛇的位置 pNode = pNode->pNext; } }
(3)显示食物
//显示食物 void DisplayFood(Position pos) { SetPos(2 * (pos.x + 1), pos.y + 1); //用了中文字符 printf("★"); }
我对宽和高做了改动,所以界面比上面的大
(4)清除数据
//清除数据 void CleanSnakeNode(Position pos) { SetPos(2 * (pos.x + 1), pos.y + 1); //用了中文字符 printf(" "); }
3.controller 控制流
游戏的一系列实现
(1)对游戏先初始化
//蛇的初始化 void SnakeInit(Snake *psnake) { //假定蛇开始长度为3,坐标分别为(5,5)、(6,5)、(7,5) //(7,5)->(6,5)->(5,5) //采用头插进行初始化 int i; psnake->pHead= NULL; for (i = 0; i < 3; i++) { Position pos; pos.x = i + 5; pos.y = 5; Node *pNewNode = (Node *)malloc(sizeof(Node)); assert(pNewNode); pNewNode->data = pos; pNewNode->pNext = psnake->pHead; psnake->pHead = pNewNode; } psnake->direction = RIGHT; } //1重叠 0不重叠 int IsOverlapSnake(int x, int y, const Snake *pSnake) { Node *pNode; for (pNode = pSnake->pHead; pNode; pNode = pNode->pNext) { if (pNode->data.x == x && pNode->data.y == y) { return 1; } } return 0; } //食物的初始化 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; } //game的初始化 void GameInit(Game *pGame) { pGame->height =32; pGame->width =28; system("mode con cols=60 lines=38"); //pGame->score = 0; pGame->speed = 200; //pGame->scorePerFood= 10; SnakeInit(&(pGame->snake)); //食物(1)随机生成 (2)不能出墙 (3)不能和蛇重合 FoodInit(&(pGame->foodPosition), pGame->width, pGame->height, &(pGame->snake)); }
(2)实现字符界面(墙、蛇、食物)
DisplayWall(game.width, game.height); DisplaySnake(&(game.snake)); DisplayFood(game.foodPosition);
(3)如何走
1.判断蛇不可以180度走
//判断蛇不可以180度转向 if (GetAsyncKeyState(VK_UP) && game.snake.direction != DOWN) { game.snake.direction = UP; } if (GetAsyncKeyState(VK_DOWN) && game.snake.direction != UP) { game.snake.direction = DOWN; } if (GetAsyncKeyState(VK_LEFT) && game.snake.direction != RIGHT) { game.snake.direction = LEFT; } if (GetAsyncKeyState(VK_RIGHT) && game.snake.direction != LEFT) { game.snake.direction = RIGHT; }2.一个周期内可能做得事请
//一个周期内可能做得事情 //1 蛇前进 (1)没吃到食物 添加蛇头结点,删除蛇尾结点 // (2)吃到食物 添加蛇头结点,不删除蛇尾,在生成食物 // 判断下一个前进方向和食物有无重合 //2 游戏结束 (1)撞墙 (2)撞自己 Position NextPos = GetNextPosition(&(game.snake)); //得到下一个前进的位置 //进行判断 if (NextPos.x == game.foodPosition.x && NextPos.y == game.foodPosition.y) { //吃到食物 PushFront(&(game.snake),NextPos); //添加蛇头 game.score += game.sorePerfood; //得分 FoodInit(&(game.foodPosition), game.width, game.height, &(game.snake));//生成食物 DisplayFood(game.foodPosition); } else{ //没吃到食物 PushFront(&(game.snake), NextPos); //添加蛇头 PopBack(&(game.snake)); //删除蛇尾 } if (IsCashWall(game.width, game.height, NextPos)) { break; } if (IsCashhimself(&(game.snake))) { break; } Sleep(game.speed); }所有代码如下~
main.c
#include <windows.h> #include "controller.h" #include "view.h" int main() { printf("欢迎\n"); system("pause"); system("cls"); printf(" 欢迎\n"); system("pause"); system("cls"); printf("热烈欢迎\n"); system("pause"); system("cls"); GameStart(); system("pause"); return 0; }view.h
#pragma once #include <windows.h> #include <stdio.h> #include "model.h" void SetPos(int X, int Y) { HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);//获得句柄 COORD coord;//实现光标的移动 coord.X = X; coord.Y = Y; SetConsoleCursorPosition(hStdout, coord); //控制光标 } //显示外墙 void DisplayWall(int width, int height) { int i; //上边 SetPos(0,0); for (i = 0; i < width + 2; i++) { printf("■"); } //下边 SetPos(0,height + 1); for (i = 0; i < width + 2; i++){ printf("■"); } //左边 for (i = 0; i < height +2 ; i++){ SetPos(0, i); printf("■"); } //右边 for (i = 0; i < height + 2; i++){ SetPos(2*(width + 1), i); printf("■"); } } //显示蛇的位置 void DisplaySnakeNode(Position pos) { SetPos(2 * (pos.x + 1), pos.y + 1); //用到了中文字符 printf("●"); } //显示蛇 void DisplaySnake(const Snake *pSnake) { Node *pNode = pSnake->pHead; while (pNode) { DisplaySnakeNode(pNode->data);//显示蛇的位置 pNode = pNode->pNext; } } //显示食物 void DisplayFood(Position pos) { SetPos(2 * (pos.x + 1), pos.y + 1); //用了中文字符 printf("★"); } //清除数据 void CleanSnakeNode(Position pos) { SetPos(2 * (pos.x + 1), pos.y + 1); //用了中文字符 printf(" "); }model.h
#pragma once //坐标原点在左上角 //x是向右 y是向下 typedef struct Position{ int x; int y; }Position; //链表结点(单向) typedef struct Node{ struct Node *pNext; Position data; }Node; //方向 (枚举列举所有可能) typedef enum Direction{ UP, DOWN, LEFT, RIGHT }Direction; //蛇的结构 typedef struct Snake{ Node *pHead; Direction direction; }Snake; //游戏用到的其他非独立字段 typedef struct Game{ Snake snake; Position foodPosition; int score; int sorePerfood; int speed; int width; // 宽 int height; // 高 }Game;
controller.h
#pragma once #include "model.h" #include "view.h" #include <windows.h> #include <stdio.h> #include <assert.h> #include <stdlib.h> //蛇的初始化 void SnakeInit(Snake *psnake) { //假定蛇开始长度为3,坐标分别为(5,5)、(6,5)、(7,5) //(7,5)->(6,5)->(5,5) //采用头插进行初始化 int i; psnake->pHead= NULL; for (i = 0; i < 3; i++) { Position pos; pos.x = i + 5; pos.y = 5; Node *pNewNode = (Node *)malloc(sizeof(Node)); assert(pNewNode); pNewNode->data = pos; pNewNode->pNext = psnake->pHead; psnake->pHead = pNewNode; } psnake->direction = RIGHT; } //1重叠 0不重叠 int IsOverlapSnake(int x, int y, const Snake *pSnake) { Node *pNode; for (pNode = pSnake->pHead; pNode; pNode = pNode->pNext) { if (pNode->data.x == x && pNode->data.y == y) { return 1; } } return 0; } //食物的初始化 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; } //game的初始化 void GameInit(Game *pGame) { pGame->height =32; pGame->width =28; system("mode con cols=60 lines=38"); pGame->score = 0; pGame->speed = 200; //pGame->scorePerFood= 10; SnakeInit(&(pGame->snake)); //食物(1)随机生成 (2)不能出墙 (3)不能和蛇重合 FoodInit(&(pGame->foodPosition), pGame->width, pGame->height, &(pGame->snake)); } //得到蛇的下一个前进方向 Position GetNextPosition(const Snake *pSnake) { Position nextPos; nextPos.x = pSnake->pHead->data.x; nextPos.y = pSnake->pHead->data.y; 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; } //添加蛇头(头插) void PushFront(Snake *pSnake, Position NextPos) { Node *pNewNode = (Node *)malloc(sizeof(Node)); assert(pNewNode); pNewNode->data = NextPos; pNewNode->pNext = pSnake->pHead; pSnake->pHead= pNewNode; DisplaySnakeNode(NextPos); } //删除蛇尾(尾删) void PopBack(Snake *pSnake) //删除蛇尾 { Node *pNode ; for (pNode = pSnake->pHead; pNode->pNext->pNext != NULL; pNode = pNode->pNext) {} //找到了倒数第二个结点 CleanSnakeNode(pNode->pNext->data);//清楚掉倒数第一个 free(pNode->pNext); pNode->pNext = NULL; } //撞墙 1撞了 0没有 int IsCashWall(int width, int height,Position NextPos) { if (NextPos.x <0 || NextPos.x >= width ) { return 1; } if (NextPos.y < 0 || NextPos.y>= height) { return 1; } return 0; } //撞自己 1撞了 0没有 int IsCashhimself(const Snake *pNake) { Node *pHead = pNake->pHead; Node *pNode = pNake->pHead->pNext; while (pNode!=NULL) { if (pHead->data.x == pNode->data.x && pHead->data.y == pNode->data.y) { return 1; } pNode = pNode->pNext; } return 0; } void GameStart() { Game game; GameInit(&game); DisplayWall(game.width, game.height); DisplaySnake(&(game.snake)); DisplayFood(game.foodPosition); while (1) { //判断蛇不可以180度转向 if (GetAsyncKeyState(VK_UP) && game.snake.direction != DOWN) { game.snake.direction = UP; } if (GetAsyncKeyState(VK_DOWN) && game.snake.direction != UP) { game.snake.direction = DOWN; } if (GetAsyncKeyState(VK_LEFT) && game.snake.direction != RIGHT) { game.snake.direction = LEFT; } if (GetAsyncKeyState(VK_RIGHT) && game.snake.direction != LEFT) { game.snake.direction = RIGHT; } //一个周期内可能做得事情 //1 蛇前进 (1)没吃到食物 添加蛇头结点,删除蛇尾结点 // (2)吃到食物 添加蛇头结点,不删除蛇尾,在生成食物 // 判断下一个前进方向和食物有无重合 //2 游戏结束 (1)撞墙 (2)撞自己 Position NextPos = GetNextPosition(&(game.snake)); //得到下一个前进的位置 //进行判断 if (NextPos.x == game.foodPosition.x && NextPos.y == game.foodPosition.y) { //吃到食物 PushFront(&(game.snake),NextPos); //添加蛇头 game.score += game.sorePerfood; //得分 FoodInit(&(game.foodPosition), game.width, game.height, &(game.snake));//生成食物 DisplayFood(game.foodPosition); } else{ //没吃到食物 PushFront(&(game.snake), NextPos); //添加蛇头 PopBack(&(game.snake)); //删除蛇尾 } if (IsCashWall(game.width, game.height, NextPos)) { break; } if (IsCashhimself(&(game.snake))) { break; } Sleep(game.speed); } }
运行结果:
