衡阳网站设计ss0734百度推广怎么收费标准
本篇博客梳理贪吃蛇小游戏具体的代码实现
游戏代码文件分布逻辑如下:
- Snake.h:标准库头文件的包含,类型的声明,函数的声明
- Snake.c:游戏功能的具体实现
- test.c:游戏的测试逻辑
下面来进行贪吃蛇的具体代码实现
一、游戏开始(运行前准备)-GameStart
函数原型如下
void GameStart(pSnake ps);
void GameStart(pSnake ps)
{ //1.设置游戏窗口的大小与窗口名字system("mode con cols=100 lines=30"); system("title 贪吃蛇"); //2.隐藏屏幕光标HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取标准输出的句柄CONSOLE_CURSOR_INFO CursorInfo; //创建光标信息结构体GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 CursorInfo.bVisible = false; //隐藏控制台光标 SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态 //3.打印欢迎界⾯ WelcomeToGame(); //4.创建地图 CreateMap(); //5.初始化蛇身 InitSnake(ps); //6.创造⻝物 CreateFood(ps);
}
1.打印欢迎界面:WelcomeToGame函数
逻辑:定位光标->打印->清屏
void WelcomeToGame()
{SetPos(40, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");SetPos(42, 20);system("pause");system("cls");SetPos(25, 14);wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");SetPos(25, 15);wprintf(L"加速能够得到更高的分数\n");SetPos(42, 20);system("pause");system("cls");
}
注:SetPos定位的坐标可以自行调整,目的是为了打印的布局好看一些
2.创建地图:CreateMap函数
逻辑:实现27行,58列(包括墙体在内)的地图,在头文件中定义一下墙的符号会更为方便
在头文件中定义
#define WALL L'□'
void CreateMap()
{//上边界int i = 0;for (i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//下边界SetPos(0, 26);for (i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//左边界for (i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右边界for (i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}
3.初始化蛇身:InitSnake函数
逻辑:将指向蛇的指针传给这个函数,并作如下操作
(1)创建一条五个节点的蛇
(2)打印蛇
(3)设置蛇的属性
void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;//创建蛇身的五个节点for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake()::malloc()");return;}cur->next = NULL;cur->x = POS_X + 2 * i;//POS_X:在头文件中声明的初始x坐标cur->y = POS_Y;//POS_Y:在头文件中声明的初始y坐标//头插法插入链表if (ps->_pSnake == NULL) //空链表{ps->_pSnake = cur;}else //非空{cur->next = ps->_pSnake;ps->_pSnake = cur;}}cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//设置贪吃蛇的属性ps->_dir = RIGHT;//默认向右ps->_score = 0;//当前分数是0分ps->_food_weight = 10;//每个食物10分ps->_sleep_time = 200;//单位是毫秒ps->_status = OK;}
4.创建食物:CreateFood函数
逻辑如下
(1)随机生成x和y,x的范围是[2,54],y的范围是[1,25]
(2)确定x和y不能和蛇身坐标冲突
(3)创建食物的节点
食物节点直接利用蛇的节点即可,只不过要把其中的next指针置为NULL,这样子就不用创建新的类型了
void CreateFood(pSnake ps)
{int x = 0;int y = 0;//特别注意:生成的x是2的倍数//x:2~54//y: 1~25
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//遍历蛇,看坐标有无冲突pSnakeNode cur = ps->_pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;//如果生成的x,y和蛇身坐标冲突,就回去继续生成新的x,y}cur = cur->next;}//创建食物的节点pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreateFood()::malloc()");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;SetPos(x, y);//定位食物的位置wprintf(L"%lc", FOOD);ps->_pFood = pFood;//把食物放到贪吃蛇结构中去维护
}
二、游戏运行-GameRun
函数原型如下
void GameRun(pSnake ps);
虚拟键值注明如下:
• 上:VK_UP
• 下:VK_DOWN
• 左:VK_LEFT
• 右:VK_RIGHT
• 空格:VK_SPACE
• ESC:VK_ESCAPE
• F3:VK_F3
• F4:VK_F4
void GameRun(pSnake ps)
{PrintHelpInfo();//打印帮助信息do{//打印总分数和食物的分值SetPos(64, 10);printf("总分数:%d\n", ps->_score);SetPos(64, 11);printf("当前食物的分数:%2d\n", ps->_food_weight);
//获取按键情况:KEY_PRESSif (KEY_PRESS(VK_UP) && ps->_dir != DOWN){ps->_dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP){ps->_dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT){ps->_dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT){ps->_dir = RIGHT;}else if (KEY_PRESS(VK_SPACE)){Pause();//暂停游戏——睡眠即可}else if (KEY_PRESS(VK_ESCAPE)){//正常退出游戏ps->_status = END_NORMAL;}else if (KEY_PRESS(VK_F3)){//加速if (ps->_sleep_time > 80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->_food_weight > 2){ps->_sleep_time += 30;ps->_food_weight -= 2;}}SnakeMove(ps);//蛇走一步的过程Sleep(ps->_sleep_time);} while (ps->_status == OK);
}
1.打印说明信息:PrintHelpInfo函数
void PrintHelpInfo()
{SetPos(64, 14);wprintf(L"%ls", L"不能穿墙,不能咬到自己");SetPos(64, 15);wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动");SetPos(64, 16);wprintf(L"%ls", L"按F3加速,F4减速");SetPos(64, 17);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
}
2.根据按键情况移动蛇:SnakeMove函数
void SnakeMove(pSnake ps)
{//创建一个结点,表示蛇即将到的下一个节点pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("SnakeMove()::malloc()");return;}
//根据蛇头的坐标与方向,计算下一个节点的坐标switch (ps->_dir){case UP:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y - 1;break;case DOWN:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y + 1;break;case LEFT:pNextNode->x = ps->_pSnake->x - 2;pNextNode->y = ps->_pSnake->y;break;case RIGHT:pNextNode->x = ps->_pSnake->x + 2;pNextNode->y = ps->_pSnake->y;break;}//检测下一个坐标处是不是食物if (NextIsFood(pNextNode, ps)){EatFood(pNextNode, ps);}else{NoFood(pNextNode, ps);}//检测蛇是否撞墙KilledByWall(ps);//检测蛇是否撞到自己KilledBySelf(ps);
}
(1)判断下一个节点是不是食物:NextIsFood函数
是就返回1,不是就返回0
int NextIsFood(pSnakeNode pn, pSnake ps)
{return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
}
①是:吃掉食物并作相应处理——EatFood函数
void EatFood(pSnakeNode pn, pSnake ps)
{//头插法,把食物节点头插到蛇身ps->_pFood->next = ps->_pSnake;ps->_pSnake = ps->_pFood;//释放下一个位置的节点free(pn);pn = NULL;pSnakeNode cur = ps->_pSnake;//打印蛇while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->_score += ps->_food_weight;//重新创建新的食物CreateFood(ps);
}
②不是:尾巴删除一节——NoFood函数
void NoFood(pSnakeNode pn, pSnake ps)
{//头插法,把下一个节点插到蛇身pn->next = ps->_pSnake;ps->_pSnake = pn;pSnakeNode cur = ps->_pSnake;while (cur->next->next != NULL){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//把最后一个结点打印成空格SetPos(cur->next->x, cur->next->y);printf(" ");//释放最后一个结点free(cur->next);//把倒数第二个节点的地址置为NULLcur->next = NULL;
}
(2)判断是否撞墙:KilledByWall
void KilledByWall(pSnake ps)
{if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||ps->_pSnake->y == 0 || ps->_pSnake->y == 26){ps->_status = KILL_BY_WALL;}
}
(3)判断是否撞上自己:KilledBySelf
void KilledBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while (cur){if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y){ps->_status = KILL_BY_SELF;break;}cur = cur->next;}
}
(4)暂停:Pause函数
void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}
三、游戏结束(善后工作)-GameEnd函数
void GameEnd(pSnake ps)
{SetPos(24, 12);switch (ps->_status){case END_NORMAL:wprintf(L"您主动结束游戏\n");break;case KILL_BY_WALL:wprintf(L"您撞到墙上,游戏结束\n");break;case KILL_BY_SELF:wprintf(L"您撞到了自己,游戏结束\n");break;}//释放蛇身的链表pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}
四、完整代码展示
Snake.h
#pragma once#include <windows.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>#define POS_X 24
#define POS_Y 5#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'//类型的声明//蛇的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//蛇的状态
//正常、撞墙、撞到自己、正常退出
enum GAME_STATUS
{OK, //正常KILL_BY_WALL, //撞墙KILL_BY_SELF, //撞到自己END_NORMAL //正常退出
};//蛇身的节点类型
typedef struct SnakeNode
{//坐标int x;int y;//指向下一个节点的指针struct SnakeNode* next;
}SnakeNode, * pSnakeNode;//typedef struct SnakeNode* pSnakeNode;//贪吃蛇
typedef struct Snake
{pSnakeNode _pSnake;//指向蛇头的指针pSnakeNode _pFood;//指向食物节点的指针enum DIRECTION _dir;//蛇的方向enum GAME_STATUS _status;//游戏的状态int _food_weight;//一个食物的分数int _score; //总成绩int _sleep_time; //休息时间,时间越短,速度越快,时间越长,速度越慢
}Snake, * pSnake;//函数的声明//定位光标位置
void SetPos(short x, short y);//游戏的初始化
void GameStart(pSnake ps);//欢迎界面的打印
void WelcomeToGame();//创建地图
void CreateMap();//初始化蛇身
void InitSnake(pSnake ps);//创建食物
void CreateFood(pSnake ps);//游戏运行的逻辑
void GameRun(pSnake ps);//蛇的移动-走一步
void SnakeMove(pSnake ps);//判断下一个坐标是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps);//下一个位置是食物,就吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps);//检测蛇是否撞墙
void KilledByWall(pSnake ps);//检测蛇是否撞到自己
void KilledBySelf(pSnake ps);//游戏善后的工作
void GameEnd(pSnake ps);
Snake.c
#define _CRT_SECURE_NO_WARNINGS 1#include "snake.h"void SetPos(short x, short y)
{//获得标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置COORD pos = { x, y };SetConsoleCursorPosition(houtput, pos);
}void WelcomeToGame()
{SetPos(40, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");SetPos(42, 20);system("pause");system("cls");SetPos(25, 14);wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");SetPos(25, 15);wprintf(L"加速能够得到更高的分数\n");SetPos(42, 20);system("pause");system("cls");
}void CreateMap()
{//上边界int i = 0;for (i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//下边界SetPos(0, 26);for (i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//左边界for (i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右边界for (i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake()::malloc()");return;}cur->next = NULL;cur->x = POS_X + 2 * i;cur->y = POS_Y;//头插法插入链表if (ps->_pSnake == NULL) //空链表{ps->_pSnake = cur;}else //非空{cur->next = ps->_pSnake;ps->_pSnake = cur;}}cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//设置贪吃蛇的属性ps->_dir = RIGHT;//默认向右ps->_score = 0;ps->_food_weight = 10;ps->_sleep_time = 200;//单位是毫秒ps->_status = OK;}void CreateFood(pSnake ps)
{int x = 0;int y = 0;//生成x是2的倍数//x:2~54//y: 1~25
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//x和y的坐标不能和蛇的身体坐标冲突pSnakeNode cur = ps->_pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//创建食物的节点pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreateFood()::malloc()");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;SetPos(x, y);//定位位置wprintf(L"%lc", FOOD);ps->_pFood = pFood;
}void GameStart(pSnake ps)
{//0. 先设置窗口的大小,再光标隐藏system("mode con cols=100 lines=30");system("title 贪吃蛇");HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态//1. 打印环境界面和功能介绍WelcomeToGame();//2. 绘制地图CreateMap();//3. 创建蛇InitSnake(ps);//4. 创建食物CreateFood(ps);
}void PrintHelpInfo()
{SetPos(64, 14);wprintf(L"%ls", L"不能穿墙,不能咬到自己");SetPos(64, 15);wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动");SetPos(64, 16);wprintf(L"%ls", L"按F3加速,F4减速");SetPos(64, 17);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");}#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}int NextIsFood(pSnakeNode pn, pSnake ps)
{return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
}void EatFood(pSnakeNode pn, pSnake ps)
{//头插法ps->_pFood->next = ps->_pSnake;ps->_pSnake = ps->_pFood;//释放下一个位置的节点free(pn);pn = NULL;pSnakeNode cur = ps->_pSnake;//打印蛇while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->_score += ps->_food_weight;//重新创建食物CreateFood(ps);
}void NoFood(pSnakeNode pn, pSnake ps)
{//头插法pn->next = ps->_pSnake;ps->_pSnake = pn;pSnakeNode cur = ps->_pSnake;while (cur->next->next != NULL){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//把最后一个结点打印成空格SetPos(cur->next->x, cur->next->y);printf(" ");//释放最后一个结点free(cur->next);//把倒数第二个节点的地址置为NULLcur->next = NULL;
}void KilledByWall(pSnake ps)
{if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||ps->_pSnake->y == 0 || ps->_pSnake->y == 26){ps->_status = KILL_BY_WALL;}
}void KilledBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while (cur){if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y){ps->_status = KILL_BY_SELF;break;}cur = cur->next;}
}void SnakeMove(pSnake ps)
{//创建一个结点,表示蛇即将到的下一个节点pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("SnakeMove()::malloc()");return;}switch (ps->_dir){case UP:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y - 1;break;case DOWN:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y + 1;break;case LEFT:pNextNode->x = ps->_pSnake->x - 2;pNextNode->y = ps->_pSnake->y;break;case RIGHT:pNextNode->x = ps->_pSnake->x + 2;pNextNode->y = ps->_pSnake->y;break;}//检测下一个坐标处是否是食物if (NextIsFood(pNextNode, ps)){EatFood(pNextNode, ps);}else{NoFood(pNextNode, ps);}//检测蛇是否撞墙KilledByWall(ps);//检测蛇是否撞到自己KilledBySelf(ps);
}void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();do{//打印总分数和食物的分值SetPos(64, 10);printf("总分数:%d\n", ps->_score);SetPos(64, 11);printf("当前食物的分数:%2d\n", ps->_food_weight);if (KEY_PRESS(VK_UP) && ps->_dir != DOWN){ps->_dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP){ps->_dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT){ps->_dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT){ps->_dir = RIGHT;}else if (KEY_PRESS(VK_SPACE)){Pause();}else if (KEY_PRESS(VK_ESCAPE)){//正常退出游戏ps->_status = END_NORMAL;}else if (KEY_PRESS(VK_F3)){//加速if (ps->_sleep_time > 80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->_food_weight > 2){ps->_sleep_time += 30;ps->_food_weight -= 2;}}SnakeMove(ps);//蛇走一步的过程Sleep(ps->_sleep_time);} while (ps->_status == OK);
}void GameEnd(pSnake ps)
{SetPos(24, 12);switch (ps->_status){case END_NORMAL:wprintf(L"您主动结束游戏\n");break;case KILL_BY_WALL:wprintf(L"您撞到墙上,游戏结束\n");break;case KILL_BY_SELF:wprintf(L"您撞到了自己,游戏结束\n");break;}//释放蛇身的链表pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1#include <locale.h>
#include "snake.h"//完成的是游戏的测试逻辑
void test()
{int ch = 0;do{system("cls");//创建贪吃蛇Snake snake = { 0 };//初始化游戏//1. 打印环境界面//2. 功能介绍//3. 绘制地图//4. 创建蛇//5. 创建食物//6. 设置游戏的相关信息GameStart(&snake);//运行游戏GameRun(&snake);//结束游戏 - 善后工作GameEnd(&snake);SetPos(20, 15);printf("再来一局吗?(Y/N):");ch = getchar();while (getchar() != '\n');} while (ch == 'Y' || ch == 'y');SetPos(0, 27);
}int main()
{//设置适配本地环境setlocale(LC_ALL, "");srand((unsigned int)time(NULL));test();return 0;
}