准备工作
首先我们需要更改一下运行之后调用的控制台界面
如果运行之后出现的是上面的界面,就需要更改一下,鼠标右键点击控制台顶端,再点击设置
再启动就可以了,之后也可以自己自定义控制台的样式,例如颜色,字体,还是右键点击控制台顶端,再点击属性,就可以找到设置了。
之后我们可以通过一些命令来对控制台进行设置
system("mode con cols=100 lines=30");//设置窗口大小 system("title 贪吃蛇");//设置标题
以下是我们需要达到的最终效果
用到的win32API
COORD控制台坐标
COORD是一个结构体类型,在使用时需要包含的头文件是windows.h,定义的参数分别为x,y轴坐标
GetStdHandle
它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值)。这个句柄可以用于后续对设备进行操作或修改其属性。可以理解为就是一只手,通过这个手我们才能后续对设备进行操作
GetConsoleCursorInfo
用于检索有关指定的控制台屏幕缓冲区的光标的可见性和大小信息
示例:
int main() { HANDLE houtput = NULL;//获取句柄 houtput = GetStdHandle(STD_OUTPUT_HANDLE); //定义光标信息结构体 CONSOLE_CURSOR_INFO cursor_info = { 0 }; //获取和houtput句柄相关的控制台上的光标信息,并存放在cursor_info中 GetConsoleCursorInfo(houtput, &cursor_info); printf("%d\n", cursor_info.dwSize); return 0; }
当我们把光标信息打印出来时,显示为25,也就是当前光标大小占整个字符的25%
//修改光标占比 cursor_info.dwSize = 50; //设置和houtput句柄相关的控制台上光标信息 SetConsoleCursorInfo(houtput, &cursor_info);
可以加入上面的代码对光标进行修改,显示的就是占比50%的光标
还可以根据以下代码设置光标的位置
//定位光标位置 COORD pos = { 10,20 }; SetConsoleCursorPosition(houtput, pos);
GetAsyncKeyState
用于检测指定的键是否被按下或释放,接受一个虚拟键码作为参数,并返回一个short类型的值,如果指定的键被按下,则返回一个负数,表示该键此前被按下并一直保持按下状态;如果指定的键未被按下,则返回零。
//把GetAsyncKeyState定义为宏,类似与函数 int main(){ while (1) { //分别传入0,1,2的虚拟键码 if (KEY_PRESS(0x30)) { printf("0\n"); } else if (KEY_PRESS(0x31)) { printf("1\n"); } else if (KEY_PRESS(0x32)) { printf("2\n"); } } return 0; }
这样就能实现键盘监听的效果
<locale.h>本地化
用于改变程序的行为以适应不同的文化和语言环境,例如,中文的一个文字是宽字符,需要占用两个单字符
int main() { char* ret = setlocale(LC_ALL, NULL);//c语言默认模式 printf("%s\n", ret); ret = setlocale(LC_ALL, "");//简体中文模式 printf("%s\n", ret); char a = 'a', b = 'b'; printf("%c%c\n", a, b); //宽字符打印 wchar_t w1 = L'你'; wchar_t w2 = L'好'; wprintf(L"%lc\n", w1); wprintf(L"%lc\n", w2); return 0; }
设置本地化之后,就可以打印一些本地化的符号了,例如中文宽字符的打印,也可以明显的看出,宽字符要占用两个普通字符的宽度。
在后续的操作中需要注意,一个坐标能放一个普通字符,两个坐标才能放一个宽字符
主要部分
初始化成员
//蛇前进的方向 enum DIRECTTON { 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 Snake { pSnakeNode psnake; //指向蛇头的指针 pSnakeNode pFood;//指向食物的指针 enum DIRECTTON dir;//蛇的方向 enum GAME_STATUS status;//游戏的状态 int food_weight;//一个食物的分数 int score;//总分 int sleep_time; }Snake,*psnake;
这样我们就完成了所有成员变量的初始化
地图创建
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); } system("pause"); }
蛇的初始化
void InitSnake(psnake ps) { pSnakeNode cur = NULL; for (int i = 0; i < 5; i++) { cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("InitSnake"); 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; getchar(); }
随机生成食物
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 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); }
完整源码
snake.h
enum DIRECTTON { 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 Snake { pSnakeNode psnake; //指向蛇头的指针 pSnakeNode pFood;//指向食物的指针 enum DIRECTTON dir;//蛇的方向 enum GAME_STATUS status;//游戏的状态 int food_weight;//一个食物的分数 int score;//总分 int sleep_time; }Snake,*psnake; void GameStart(psnake p); 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 KillByWall(psnake ps); void KillBySelf(psnake ps); void GameEnd(psnake ps);
snake.c
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); } system("pause"); } void InitSnake(psnake ps) { pSnakeNode cur = NULL; for (int i = 0; i < 5; i++) { cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("InitSnake"); 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; getchar(); } void GameStart(psnake ps) { //设置窗口 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);//设置控制台光标状态 //开始界面 WelcomeToGame(); //绘制地图 CreateMap(); //创建蛇 InitSnake(ps); //创建食物 CreateFood(ps); } 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 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退出游戏,按空格暂停游戏"); SetPos(64, 18); wprintf(L"%ls", L"制作"); } 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); //把倒数第二个节点的地址置为NULL cur->next = NULL; } void KillByWall(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 KillBySelf(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); } //检测蛇是否撞墙 KillByWall(ps); //检测蛇是否撞到自己 KillBySelf(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); } }
text.c
void test() { int ch = 0; do { system("cls"); //创建贪吃蛇 Snake snake = { 0 }; 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; }